36

Consider this function :

public boolean foo(){
   System.exit(1);
   //The lines beyond this will not be read
   int bar = 1;                  //L1
   //But the return statement is required for syntactically correct code
   return false;                 //L2

   //error here for unreachable code
   //int unreachable = 3;        //L3

}

Can someone please explain why L1 and L2 visibly not reachable does not give warnings but L3 does.

user
  • 5,335
  • 7
  • 47
  • 63
Nitin Chhajer
  • 2,299
  • 1
  • 24
  • 35

8 Answers8

50

Because as far as the compiler is concerned, System.exit() is just another method call.

The fact that what it does is end the process can only be found out from the implementation (which is native code, not that it makes any difference).

If you have to put System.exit() in your code (usually it's best to avoid it, unless you want to return a code other than 0), it should really be in a method that returns void, main() for example. It's nicer that way.

As for the reachability, the explanation is the same: return is a keyword of the Java language, so the compiler or the parser the IDE uses can tell that it's theoretically impossible for code after the return statement to be executed. These rules are defined here.

biziclop
  • 48,926
  • 12
  • 77
  • 104
  • 3
    in fact, you could even call another method `foo()` which in turn calls `System.exit(..)` so we would know that `foo()` never returns but the compiler can't check all possible cases. – Andre Holzner Jul 14 '12 at 20:38
  • Or you could even have your own native method that does the same. – biziclop Jul 14 '12 at 20:40
  • 4
    Yes, there's usually no need to call `System.exti()`. Simply return from the `main` method to exit the program. – Steve Kuo Jul 14 '12 at 21:34
  • 3
    **No, System.exit() should always be used!** If you just let the program die naturally, you'll get this error output every once in a while (took me less than 10 attempts on Windows, Java 1.8.0_74): "ERROR: JDWP Unable to get JNI 1.2 environment, jvm->GetEnv() return code = -2 [etc.]" It's a [known](https://stackoverflow.com/questions/2225737/error-jdwp-unable-to-get-jni-1-2-environment) Java bug. Also, some Swing components (I think e.g. the file dialog.), once used, prevent the program from *ever* dying naturally. See for example [here](https://stackoverflow.com/a/3114841/3500521). – Dreamspace President Apr 06 '16 at 11:14
  • " it should really be in a method that returns void, main() for example. It's nicer that way." - what if I need to stop application when there is a critical ProvisionException from Guice? Is it worth using ThrowingProviders in this case (it's not so easy, because I can't just import new libe like this)? Or do you have better idea? – Line Feb 22 '18 at 11:59
  • 1
    That is a bad design of Java. It could introduce another return type such as `never`. The return type indicate the function never returns (either a dead loop or halt the program). – tsh Jul 22 '20 at 02:33
15

The Java compiler doesn't know anything about System.exit. It's just a method as far as it's concerned - so the end of the statement is reachable.

You say that L1 and L2 are "visibly not reachable" but that's only because you know what System.exit does. The language doesn't - whereas it does know what a return statement does, so it knows that L3 really isn't reachable.

I sometimes think it would be useful to be able to declare that a method isn't just void, but never terminates normally - it never just returns (although it may throw an exception). The compiler would then be able to use that information to make the end of any calling expression unreachable, preventing this sort of thing from being a problem. However, that's just my dreams around language design - Java doesn't have anything similar, and it would be a very bad idea for the compiler to "know" that particular JRE methods will never return normally, when that concept can't be expressed directly within the language.

Instead, the compiler is bound by the rules of section 14.21 of the JLS, including:

  • The first statement in a non-empty block that is not a switch block is reachable iff the block is reachable.
  • 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.

...

An expression statement can complete normally iff it is reachable.

(A method call is an expression statement.)

Then from section 8.4.7:

If a method is declared to have a return type, then a compile-time error occurs if the body of the method can complete normally (§14.1).

and in 14.1:

Unless otherwise specified, a statement completes normally if all expressions it evaluates and all substatements it executes complete normally.

So the call to System.exit() can complete normally as far as the compiler is concerned, which means the body of the foo method can complete normally, which leads to the error.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
10

Me: "Can anything be executed after a return statement?"
Java: "NO."

Me: "Can anything be executed after I call System.exit?"
Java: "That's a method, I don't know what it does - it's not a reserved keyword, it doesn't affect program flow as far as I know" (and it may not even work (I don't know if exit can throw exceptions (or future variants of it)))

adrianton3
  • 2,258
  • 3
  • 20
  • 33
7

From the language standpoint, there are only 2 ways to escape current scope: return and throw. Method calls are never considered the same way, even if they consist of the only line of code:

void method() {
  throw new RuntimeException();
}

Even more. In theory, any method call can cause RuntimeException to be thrown. In this case, compiler should probably give you warnings for absolutely any method call which is not inside a try block:

...
method(); // WARNING: may throw in theory
anotherMethod(); // WARNING: possible unreachable code, also may throw
anotherMethod2(); // WARNING: possible unreachable code, also may throw
// etc
...

For your question logic is the same.

Andrey Agibalov
  • 7,624
  • 8
  • 66
  • 111
2

If a method is declared to return a non-void value, then it must contain a return statement somewhere, even if it's never reached (like in the code in the question).

From the compiler's point of view, System.exit() is just another method call, with nothing special about it that indicates that the program ends as soon as it's reached. Only you, as the programmer, know this fact - but it's something outside of the compiler's knowledge.

About the second part of your question - nothing can go after a return statement in a block of code inside a method, as that will always be unreacheable code. That explains why the compiler complains about the L3 line.

Óscar López
  • 232,561
  • 37
  • 312
  • 386
2

The compiler checks whether some code is reachable only with respect to the return keyword (also throw and break(in case of loops), in general). For the compiler the exit method call is just another call, it doesn't know its meaning, so it doesn't know that the code afterwards will never be reached.

Razvan
  • 9,925
  • 6
  • 38
  • 51
2

I know this does not make sense, and still this is how the Java compiler works, when you're calling a method.
The compiler does not know at this point what Sytem.exist does (why should rt.jar be different than other jars you compile with , in that sense?).
This is in contrast for example to the next piece of code -

 public int test() {
  throw new NullPointerException("aaaa");
}

Where the compiler can tell that an exception is always thrown, and therefore no return is needed.

Yair Zaslavsky
  • 4,091
  • 4
  • 20
  • 27
0

It's possible that the static code analysis tool is not taking into account that the application is terminating.

Wulfram
  • 3,292
  • 2
  • 15
  • 11