0

I have an Android error handler which never returns (it logs a message and throws an error exception). If I call my error handler from a method which normally returns a value, the Android Studio lint checker reports an error because no value is returned. Is there a way either to tell Android Studio either that my error handler does not return or that the point in the code after calling it is in fact unreachable.

Of course I could put in an unnecessary return statement returning a dummy value of the correct type, but this is inelegant and clutters up my app with an unreachable statement.

I can't find a code inspection to disable to prevent the error, but even if there is one to disable, that would stop it reporting really missing return statements.

Just to repeat, this is not a Java syntax issue. People have said that a Java method must return a value of the type declared. This is
(a) not relevant
(b) not true.
The correct statement is that a Java method, if it returns, must return a value of the declared type. This bit of code

public long getUnsignedLong(String columnName)
    throws NumberFormatException, NoColumnException
{
    String s = getString(columnName, "getUnsignedLong");
    if ((s != null) && s.matches("^[0-9]+$")) {
        return Long.parseLong(s);
    }
    else
    {
        throw(new NumberFormatException("Bad number " + s));
    }
}

is perfectly valid Java, and AS does not complain about it. Indeed if I insert an unnecessary return like this

public long getUnsignedLong(String columnName)
    throws NumberFormatException, NoColumnException
{
    String s = getString(columnName, "getUnsignedLong");
    if ((s != null) && s.matches("^[0-9]+$")) {
        return Long.parseLong(s);
    }
    else
    {
        throw(new NumberFormatException("Bad number " + s));
    }
    return 0;
}

AS complains that it is unreachable.

My problem with throwing the exception is that if it actually happens, what my app's user sees is a popup window saying that the app has stopped and asking the user if they want to disable it. This isn't very helpful to the user and isn't very helpful to me when the user reports back to me that it has happened. So instead of throwing the exception I call my fatal error handler which looks like this:-

// Always invoked with fatal = true
// Report a fatal error.
// We send a message to the log file (if logging is enabled).
// If this thread is the UI thread, we display a Toast:
// otherwise we show a notification.
// Then we throw an Error exception which will cause Android to
// terminate the thread and display a (not so helpful) message.
public MyLog(Context context, boolean fatal, String small, String big) {
    new Notifier(context, small, big);
    new MyLog(context, big, false);
    throw(new Error());
}

Yes, I know that argument fatal isn't referenced, but its presence arranges that this particular overload of my error handler is called, and you can see that it throws an exception and doesn't return.

My actual problem is that if I replace the throw in getUnsignedLong by a call to my fatal error handler, which doesn't return, AS complains at the end of getUnsignedLong that it is returning without a value. Well, it's wrong: this point is just as unreachable as it was before. I tried putting a contract in front of MyLog saying that it always fails, but this doesn't help, and pressing right arrow at the AS error report doesn't offer any way of suppressing it. I could put in a dummy return statement or a dummy throw, but either of these would in fact be unreachable code, and I regard this as inelegant and unnecessary.

So my question stands as it was originally asked: how do I tell Android Studio that a method does not return?

3 Answers3

0

Your problem seems to be a Java problem.

In Java a non-void method must return the type it should return. In your case it's the same.

So the simple solution would be to return a dummy value. It's for the sake of the compiler.

The best (harder) solution is to avoid having that kind of construction. If a method normally returns a value, the method will have a return, and in case of an error an exception can occurs. An error handler should handle an error, which means that it's not the default behavior.

Also, you may want to have a look here: unreachable-code-error-vs-dead-code-warning-in-java

If you worry about unit test coverage. When you do unit test, there is always some parts of the code that you can't reach. That's one of the reason why we almost never want a coverage of 100

  • This isn't a syntax error: it's an error in the flow analysis. The java compiler in AS can detect that the error handler always throws an exception and doesn't return (and if I put in a return statement after the `throw` it complains), but it doesn't propagate that fact back to the flow analysis in the method that calls the error handler, so it (wrongly) thinks that there should be a return after the call to the error handler, although a return at that point would actually be unreachable. – user3791713 Dec 26 '19 at 20:13
0

A method is basically code that you want to access in different ways (easier to call one method in a line than code >50 lines, etc.). When you create the method you either call it:

"void" (never returns an value),

"String" (returns a set of characters/letters),

"int" (returns a number within it's bounds. Integer Types),

"boolean" (returns a 2-type value, true or false).

And more...

In those methods you cand do whatever you want, but make sure that in the end they return the value type specified in the initialization.

Example:

int y = 2;

boolean booleanMethod(){
  y = 6;
  return true; //or false, doesn't really matter in this case.
}

boolean trueOrFalse(){
  if (y == 2) return true;
  else return false;
}

//or

void method(int nr){
  nr = 10;
}

Those are just some basic examples of methods in Java*, because your problem was a syntax one, not really an AS one.

Ev0lv3zz
  • 58
  • 8
0

AS complains that it is unreachable.

Android Studio is correct. Android Studio is correctly implementing the reachability rules in the Java Language Specification.

This is a Java semantics issue. (It is not a syntax issue, but lets not split hairs.)

Lets create a simple but complete example to illustrate why Android Studio is correct:

public class Test {
    public int method1() throws Exception {
        int result;
        method2();
        return result;
    }
    
    public boolean method2() throws Exception {
        throw new Exception("bad stuff");
    }
}

$ javac Test.java 
  Test.java:5: error: variable result might not have been initialized
              return result;
                     ^
  1 error

(I don't have a copy of Android Studio to hand, but that would give a similar compilation error to javac. Try it.)

Why is this an error? After all, by your reasoning, the return statement should be unreachable.

Here's the problem. That is NOT what the JLS says. In fact, JLS 14.21 says the following, among other things. (These statements are excerpts, and I have added the numbers for clarity. "iff" is an abbreviation that the JLS uses for "if and only if".)

  1. "The block that is the body of a constructor, method, instance initializer, or static initializer is reachable."

  2. "The first statement in a non-empty block that is not a switch block is reachable iff the block is reachable."

  3. "A local variable declaration statement can complete normally iff it is reachable."

  4. "Every other statement S in a non-empty block that is not a switch block is reachable iff the statement preceding S can complete normally."

  5. "An expression statement can complete normally iff it is reachable."

Consider the body of method1.

  • By #1 - the block is reachable
  • By #2 - the declaration of result is reachable.
  • By #3 - the declaration can complete normally
  • By #4 - the call to method2() is reachable
  • By #5 - the call can return normally
  • By #4 - the return statement is reachable.

But it is also clear that if we reached the return statement, then result will not have been definitely initialized. (This is obviously true. And JLS 16 bears this out.)


OK so why did they specify Java this way?

In the general case, method1 and method2 can be in separate compilation units; e.g. cases A and B. That means that the methods can be compiled at different times, and only brought together at runtime. Now, if the compiler needs to analyze the body of B.method2 to determine if the return in A.method1 is reachable, then consider what happens if:

  • the code for B.method2 is modified and B recompiled after compiling A, or
  • a subclass C of B is loaded in which C.method2 returns normally.

In short, if we need to take account of the flow within of method2 when analyzing reachability in method, we cannot come to a come to a answer at compile time.


Conclusions:

  1. The JLS clearly says / means that the (my) example program is erroneous.
  2. It is not a specification mistake.
  3. If Android Studio (or javac) didn't call the example erroneous, then it wouldn't be a valid implementation of Java.
Community
  • 1
  • 1
Stephen C
  • 698,415
  • 94
  • 811
  • 1,216