Why does Java have an "unreachable statement" compiler error?
Asked Answered
C

8

90

I often find when debugging a program it is convenient, (although arguably bad practice) to insert a return statement inside a block of code. I might try something like this in Java ....

class Test {
        public static void main(String args[]) {
                System.out.println("hello world");
                return;
                System.out.println("i think this line might cause a problem");
        }
}

of course, this would yield the compiler error.

Test.java:7: unreachable statement

I could understand why a warning might be justified as having unused code is bad practice. But I don't understand why this needs to generate an error.

Is this just Java trying to be a Nanny, or is there a good reason to make this a compiler error?

Creationism answered 25/9, 2010 at 21:15 Comment(1)
Java isn't entirely consistent about this either. For some control flows that cause dead code, but Java doesn't complain. For others it does. Determining what code is dead is an uncomputable problem. I don't know why Java decided to start something it couldn't finish.Guizot
E
66

Because unreachable code is meaningless to the compiler. Whilst making code meaningful to people is both paramount and harder than making it meaningful to a compiler, the compiler is the essential consumer of code. The designers of Java take the viewpoint that code that is not meaningful to the compiler is an error. Their stance is that if you have some unreachable code, you have made a mistake that needs to be fixed.

There is a similar question here: Unreachable code: error or warning?, in which the author says "Personally I strongly feel it should be an error: if the programmer writes a piece of code, it should always be with the intention of actually running it in some scenario." Obviously the language designers of Java agree.

Whether unreachable code should prevent compilation is a question on which there will never be consensus. But this is why the Java designers did it.


A number of people in comments point out that there are many classes of unreachable code Java doesn't prevent compiling. If I understand the consequences of Gödel correctly, no compiler can possibly catch all classes of unreachable code.

Unit tests cannot catch every single bug. We don't use this as an argument against their value. Likewise a compiler can't catch all problematic code, but it is still valuable for it to prevent compilation of bad code when it can.

The Java language designers consider unreachable code an error. So preventing it compiling when possible is reasonable.


(Before you downvote: the question is not whether or not Java should have an unreachable statement compiler error. The question is why Java has an unreachable statement compiler error. Don't downvote me just because you think Java made the wrong design decision.)

Escobar answered 25/9, 2010 at 21:22 Comment(25)
Obviously the language designers of Java agree "Language designers of Java" are humans and prone to mistakes too. Personally, I feel that other side in the discussion you refer has much stronger arguments.Grantor
I don't think this explains why "meaningless" code is sufficient to justify a compiler error, which would normally be reserved for syntax issues.Creationism
@bolt but do any generate errors for issues that aren't related to syntax?Creationism
for final production code, yes. during development, absolutely disagree.Preempt
Well this is the thing, isn't it. There's no objective way to say warning or error is better, it comes down to personal opinion. C#'s take is its a warning. My take is that the UI design principle of designing an interface to prevent incorrect input entirely applies to languages either. What does an unreachable statement mean? If its a copy and paste error, we want to be forced to fix it. If the code is intended to be there, it isn't meaningful to the compiler, is therefore there for developers, and can therefore be a comment.Escobar
@Mike: Compilers prevent all sorts of things that aren't syntax related - access to uninitialized variables to name a small example.Escobar
@Mike: Java spits a plethora of compile-time errors that aren't always related to syntax. For instance, forgetting to override abstract methods, attempting to catch exceptions that are never thrown from their try blocks, etc.Alded
SamStephens: It sounds like you've argued against any sort of compiler warning. I don't see why something completely harmless needs to be an error.Tsana
@Gabe: Because it's not harmless - it's almost certainly a mistake. Either you've put your code in the wrong place, or misunderstood that you've written your statement in such a way that some of it can't be reached. Making this an error prevents incorrect code from being written, and if you want something in an unreachable place in your code for other developers to read (the only audience for unreachable code), use a comment instead.Escobar
SamStephens: What qualifies as a compiler warning to you?Tsana
@Gabe: My personal opinion is that warnings should be used for constructs that should be tidied, but are most likely not indicative of an actual error in coding. Uncalled private methods and unused variables are two I can think of. However, the real answer is that a compiler warning is anything a language designer decides is a warning!Escobar
SamStephens: Seeing as how uncalled methods and unused variables are essentially all forms of unreachable code. Why allow some forms and not others? In particular, why disallow such a useful debugging mechanism?Tsana
@Gabe: Interesting question. For me personally, I still think unreachable code is much more likely to indicate a coding fault than an unused method or variable. However, like I said, it's not something there's a hard and fast answer to, and it's up to the language designer to decide whether warning or error is best for their language.Escobar
Aren't comments also unreachable? In my mind, unreachable code is actually a form of comments (this is what I was trying to do earlier, etc). But considering "real" comments aren't "reachable" either... maybe they should also raise this error ;)Preempt
Comments are not code, and are not intended for consumption by the compiler - they are for human consumption only. There's no need to allow code that is only intended for documentation purposes, as we have other means of doing this. I still stand by my earlier comment "What does an unreachable statement mean? If its a copy and paste error, we want to be forced to fix it. If the code is intended to be there, it isn't meaningful to the compiler, is therefore there for developers, and can therefore be a comment" Really it's up to language designers to decide. If you don't like, use something elseEscobar
I feel you're missing the point. Something's definitely wrong if there's unreachable code, but that doesn't mean the code can't be executed anyway, which can be useful in debugging. And as long as the compiler still screams "warning" every time it comes across such a problem, isn't that enough?Fiche
Again, it's a matter of opinion. As you say, "something's definitely wrong if there's unreachable code". Sure a program can be executed when there's unreachable code. But executable code is a minimum standard - good languages try to force you towards correct code. Java takes the point of view that unreachable code is incorrect enough that you should be forced to fix it before compiling. Warning is good enough for the authors of the C# compiler. It isn't for Java's authors. Java reflects the philosophies of its authors, and those philosophies are legitimate.Escobar
The problem is that they (java language designers) are inconsistent with this. If there is actual flow analysis, it's trivial to realize that both return; System.out.println(); and if(true){ return; } System.out.println(); are guaranteed to have the same behavior, but one is a compile error, and the other is not. So, when I'm debugging, and I use this method to "comment out" code temporarily, that's how I do it. The problem with commenting out code is that you have to find the appropriate place to stop your comment, which may take longer than throwing in a return; real quick.Burrows
Having unused code doesn't merrit an error IMHO. A warning at the very most would be nice. What if I have something unfinished. I don't want to remove it just because it's unreachable.Length
Why not let the designers of Java write "correct" code and let those who don't wish to do so not do so? Just because a language doesn't enforce "correct" reachable code doesn't mean people with philosophies like the creators of Java have to write incorrect code.Length
Meaningless from the point of view of compiler != meaningless from the point of view of developers. Compile error on unreachable code is the stupidest thing I saw recently.Undertow
@enrey, my comment from Sep 25 '10 at 21:40 on this answer covers your point I think.Escobar
@Escobar "it's almost certainly a mistake"... "Almost certainly" isn't very certain, and for things that are "probably error", compiler has warnings. Plain return in middle of a function is almost never an error though, it's being done all the time on purpose. Why don't we use comments? Comments don't nest, so you can't just enclose the rest of a function in block comment if there are some other block comments already. You have to write some stupid workaround like if(true)return; which looks far more like an error, produces no warning and is more likely to be forgotten in the end.Undertow
Since SamStephens explicitly asks for downvote explanations: "Unreachable code is meaningless, and therefore an error." was enough for me to push that down arrow. for(int i=0;i<5; i++){} is meaningless but produces no error (not even a warning, nothing). And I see no reason meaningless code would be an error. Actually, all debug paterns are meaningless in the terms of function. And I intended to use return; to break function sooner than expected for debug purposes.Parfleche
Thanks @TomášZato, you've spurred me to rewrite this answer with a lot more clarity.Escobar
D
49

There is no definitive reason why unreachable statements must be not be allowed; other languages allow them without problems. For your specific need, this is the usual trick:

if (true) return;

It looks nonsensical, anyone who reads the code will guess that it must have been done deliberately, not a careless mistake of leaving the rest of statements unreachable.

Java has a little bit support for "conditional compilation"

http://java.sun.com/docs/books/jls/third_edition/html/statements.html#14.21

if (false) { x=3; }

does not result in a compile-time error. An optimizing compiler may realize that the statement x=3; will never be executed and may choose to omit the code for that statement from the generated class file, but the statement x=3; is not regarded as "unreachable" in the technical sense specified here.

The rationale for this differing treatment is to allow programmers to define "flag variables" such as:

static final boolean DEBUG = false;

and then write code such as:

if (DEBUG) { x=3; }

The idea is that it should be possible to change the value of DEBUG from false to true or from true to false and then compile the code correctly with no other changes to the program text.

Delft answered 25/9, 2010 at 22:47 Comment(4)
Still don't get why you'd bother with the trick you show. Unreachable code is meaningless to the compiler. So the only audience for it is developers. Use a comment. Although I guess if you're adding a return in temporarily, using your workaround might be fractionally quicker than just putting in a return, and commenting out the following code.Escobar
@Escobar But everytime you wanted to switch you would have to remember all the places where you have to comment out code and where you have to do the opposite. You would have to read through the whole file, changing the source code, probably making some subtle mistakes every now and then instead of just replacing "true" with "false" at one place. I think it's better to code the switching logic once and don't touch it unless necessary.Osprey
"anyone who reads the code will guess that it must have been done deliberately". This is exactly the problem in my opinion, people shouldn't be guessing, they should understand. Remembering code is communication with people, not just instructions for computers. If it's anything other than extremely temporary, you should be using configuration in my opinion. Looking at what you're showing above, if (true) return; doesn't indicate WHY you're skipping the logic, whereas if (BEHAVIOURDISABLED) return; communicates intention.Escobar
This trick is really useful for debugging. I often add an if(true) return to eliminate "the rest" of a method trying to find out where it's failing. There are other ways, but this is clean and quick--but it's not something you should ever check in.Franky
P
20

It is Nanny. I feel .Net got this one right - it raises a warning for unreachable code, but not an error. It is good to be warned about it, but I see no reason to prevent compilation (especially during debugging sessions where it is nice to throw a return in to bypass some code).

Preempt answered 25/9, 2010 at 21:35 Comment(7)
java was designed way earlier, each byte counts at that time, floppy disks were still high tech.Delft
For the debug early return, it takes another five seconds to comment out the lines following your early return. A small price to play for the bugs prevented by making unreachable code an error.Escobar
it is also easy for the compiler to exclude unreachable code. no reason to force the developer to do so.Preempt
@Escobar not if you already have comments there, since comments do not stack, it's unnecessary pain to work around this with comments.Undertow
@Escobar Commented out code does not refactor well.Rager
Why are you refactoring unreachable code? If the code has a reason to live, it should be reachable under some circumstances, even if that circumstance is a change in configuration.Escobar
How could java ever do anything with floppy disks with it's horrible memory usage?Parfleche
M
16

I only just noticed this question, and wanted to add my $.02 to this.

In case of Java, this is not actually an option. The "unreachable code" error doesn't come from the fact that JVM developers thought to protect developers from anything, or be extra vigilant, but from the requirements of the JVM specification.

Both Java compiler, and JVM, use what is called "stack maps" - a definite information about all of the items on the stack, as allocated for the current method. The type of each and every slot of the stack must be known, so that a JVM instruction doesn't mistreat item of one type for another type. This is mostly important for preventing having a numeric value ever being used as a pointer. It's possible, using Java assembly, to try to push/store a number, but then pop/load an object reference. However, JVM will reject this code during class validation,- that is when stack maps are being created and tested for consistency.

To verify the stack maps, the VM has to walk through all the code paths that exist in a method, and make sure that no matter which code path will ever be executed, the stack data for every instruction agrees with what any previous code has pushed/stored in the stack. So, in simple case of:

Object a;
if (something) { a = new Object(); } else { a = new String(); }
System.out.println(a);

at line 3, JVM will check that both branches of 'if' have only stored into a (which is just local var#0) something that is compatible with Object (since that's how code from line 3 and on will treat local var#0).

When compiler gets to an unreachable code, it doesn't quite know what state the stack might be at that point, so it can't verify its state. It can't quite compile the code anymore at that point, as it can't keep track of local variables either, so instead of leaving this ambiguity in the class file, it produces a fatal error.

Of course a simple condition like if (1<2) will fool it, but it's not really fooling - it's giving it a potential branch that can lead to the code, and at least both the compiler and the VM can determine, how the stack items can be used from there on.

P.S. I don't know what .NET does in this case, but I believe it will fail compilation as well. This normally will not be a problem for any machine code compilers (C, C++, Obj-C, etc.)

Miguelmiguela answered 1/8, 2013 at 21:48 Comment(7)
How aggressive is Java's dead code warning system? Could it be expressed as part of the grammar (e.g. { [flowingstatement;]* }=>flowingstatement, { [flowingstatement;]* stoppingstatement; }=>stoppingstatement, return=>stoppingstatement, if (condition) stoppingstatement; else stoppingstatement=>stoppingstatement; etc.?Convoluted
I'm not good with grammars, but I believe so, yes. The "stopping", however, can be local, for example a "break" statement inside a loop. Also, the end of a method is a implicit stopping statement for the method level, if the method is void. 'throw' will also be an explicit stopping statement.Miguelmiguela
What would the Java standard say about something like void blah() { try {return;} catch (RuntimeError ex) { } DoSomethingElse(); } Would Java squawk that there was no way the try block could actually exit via an exception, or would it figure that any kind of unchecked exception could occur within any try block?Convoluted
The best way to find out is to try and compile that. But compiler allows for even empty try/catch statements. However, any JVM instruction can theoretically throw an exception, so try block may exit so. But I'm not sure how is this relevant to this question.Miguelmiguela
Basically, the question is whether the only forbidden "unreachable" statements are those which could be identified as unreachable on the basis of parsing, without having to actually evaluate any expressions, or whether the standard could forbid unreachable statements like if (constantThatEqualsFive < 3) {doSomething;};.Convoluted
I just found the spec and looked it up; if is parsed grammatically without regard for a constant expression, but while actually evaluates the value. The spec doesn't say whether x && y is considered constant false and y isn't, but if it is that could cause some reasonable code compile or fail to do so based upon the values of constants.Convoluted
Thanks for the explanation. FYI, .NET treats unreachable code as a warning.Escobar
A
5

While I think this compiler error is a good thing, there is a way you can work around it. Use a condition you know will be true:

public void myMethod(){

    someCodeHere();

    if(1 < 2) return; // compiler isn't smart enough to complain about this

    moreCodeHere();

}

The compiler is not smart enough to complain about that.

Atmospheric answered 25/9, 2010 at 21:36 Comment(7)
The question here is why? Unreachable code is meaningless to the compiler. So the only audience for it is developers. Use a comment.Escobar
So I get downvotes because I agree with the way the java guys desgined their compile? Jeez...Atmospheric
Just wait 'til I start the 'where to the brackets go' discussion.Intussusception
You get downvotes for not answering the actual question. See SamStephens' comment.Alded
javac can definitely infer that 1<2 is true, and the rest of the method doesn't have to be included in the byte code. the rules of "unreachable statements" must be clear, fixed, and independent of the smartness of compilers.Delft
Afraid not - question is why it's an error, not how to work around the error.Escobar
@Sam with that attitude, you're going to have to downvote 50% of the best answers of this site (and I am not saying that my answer is among those). A big part of answering questions on SO, as in any IT consulting situation, is to identify the real question (or relevant side questions) from a given question.Atmospheric
A
5

One of the goals of compilers is to rule out classes of errors. Some unreachable code is there by accident, it's nice that javac rules out that class of error at compile time.

For every rule that catches erroneous code, someone will want the compiler to accept it because they know what they're doing. That's the penalty of compiler checking, and getting the balance right is one of the tricker points of language design. Even with the strictest checking there's still an infinite number of programs that can be written, so things can't be that bad.

Adjustment answered 25/9, 2010 at 22:2 Comment(0)
A
0

It is certainly a good thing to complain the more stringent the compiler is the better, as far as it allows you to do what you need. Usually the small price to pay is to comment the code out, the gain is that when you compile your code works. A general example is Haskell about which people screams until they realize that their test/debugging is main test only and short one. I personally in Java do almost no debugging while being ( in fact on purpose) not attentive.

Accidence answered 25/9, 2010 at 22:35 Comment(0)
E
0

If the reason for allowing if (aBooleanVariable) return; someMoreCode; is to allow flags, then the fact that if (true) return; someMoreCode; does not generate a compile time error seems like inconsistency in the policy of generating CodeNotReachable exception, since the compiler 'knows' that true is not a flag (not a variable).

Two other ways which might be interesting, but don't apply to switching off part of a method's code as well as if (true) return:

Now, instead of saying if (true) return; you might want to say assert false and add -ea OR -ea package OR -ea className to the jvm arguments. The good point is that this allows for some granularity and requires adding an extra parameter to the jvm invocation so there is no need of setting a DEBUG flag in the code, but by added argument at runtime, which is useful when the target is not the developer machine and recompiling & transferring bytecode takes time.

There is also the System.exit(0) way, but this might be an overkill, if you put it in Java in a JSP then it will terminate the server.

Apart from that Java is by-design a 'nanny' language, I would rather use something native like C/C++ for more control.

Elenaelenchus answered 8/6, 2012 at 21:50 Comment(1)
Changing something from a static final variable which is set in a static initialization block to a static final variable which is set in its declaration shouldn't be a breaking change. It's entirely plausible that when a class is first written it may have sometimes been unable to do something (and not known until run-time whether it would be able to do it or not), but later versions of the class might have been able to do that action always. Changing myClass.canFoo to a constant should make if (myClass.canFoo) myClass.Foo(); else doSomethingElse(); more efficient--not break it.Convoluted

© 2022 - 2024 — McMap. All rights reserved.