ChatGPT解决这个技术问题 Extra ChatGPT

Infinite loops in Java

Look at the following infinite while loop in Java. It causes a compile-time error for the statement below it.

while(true) {
    System.out.println("inside while");
}

System.out.println("while terminated"); //Unreachable statement - compiler-error.

The following same infinite while loop, however works fine and doesn't issue any errors in which I just replaced the condition with a boolean variable.

boolean b=true;

while(b) {
    System.out.println("inside while");
}

System.out.println("while terminated"); //No error here.

In the second case also, the statement after the loop is obviously unreachable because the boolean variable b is true still the compiler doesn't complain at all. Why?

Edit : The following version of while gets stuck into an infinite loop as obvious but issues no compiler errors for the statement below it even though the if condition within the loop is always false and consequently, the loop can never return and can be determined by the compiler at the compile-time itself.

while(true) {

    if(false) {
        break;
    }

    System.out.println("inside while");
}

System.out.println("while terminated"); //No error here.

while(true) {

    if(false)  { //if true then also
        return;  //Replacing return with break fixes the following error.
    }

    System.out.println("inside while");
}

System.out.println("while terminated"); //Compiler-error - unreachable statement.

while(true) {

    if(true) {
        System.out.println("inside if");
        return;
    }

    System.out.println("inside while"); //No error here.
}

System.out.println("while terminated"); //Compiler-error - unreachable statement.

Edit : Same thing with if and while.

if(false) {
    System.out.println("inside if"); //No error here.
}

while(false) {
    System.out.println("inside while");
    // Compiler's complain - unreachable statement.
}

while(true) {

    if(true) {
        System.out.println("inside if");
        break;
    }

    System.out.println("inside while"); //No error here.
}      

The following version of while also gets stuck into an infinite loop.

while(true) {

    try {
        System.out.println("inside while");
        return;   //Replacing return with break makes no difference here.
    } finally {
        continue;
    }
}

This is because the finally block is always executed even though the return statement encounters before it within the try block itself.

Who cares? It's obviously just a feature of the compiler. Don't concern yourself with this sort of stuff.
How could another thread change a local non-static variable?
The internal state of the object might be changed concurrently through reflection. That's why the JLS mandates that only final (constant) expressions be checked.
I hate these stupid errors. Unreachable code should be a warning not an error.
@CJ7: I would not call this a "feature", and it makes it very tedious (for no reason) to implement a conforming Java compiler. Enjoy your by-design vendor lock-in.

T
Tiny

The compiler can easily and unequivocally prove that the first expression always results in an infinite loop, but it's not as easy for the second. In your toy example it's simple, but what if:

the variable's contents were read from a file?

the variable wasn't local and could be modified by another thread?

the variable relied on some user input?

The compiler is clearly not checking for your simpler case because it's forgoing that road altogether. Why? Because it's much harder forbidden by the spec. See section 14.21:

http://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.21-300-M

(By the way, my compiler does complain when the variable is declared final.)


-1 - It is not about what the compiler can do. It is nothing about checks being easier or harder. It is about what the compiler is allowed to do ... by the Java Language Spec. The second version of the code is valid Java according to the JLS, so a compilation error would be wrong.
@StephenC - Thanks for that info. Happily updated to reflect as much.
Still -1; none of your "what ifs" apply here. As it is, the compiler can indeed solve the problem - in static analysis terms this is called constant-propagation, and is extensively used successfully in many other situations. The JLS is the only reason here, and how hard the problem is to solve is immaterial. Of course the reason the JLS is written that way in the first place might be related to the difficulty of that problem, though I personally suspect it's actually because it is difficult enforcing a standardized constant-propagation solution across different compilers.
@Oak - I conceded in my answer that "In [the OP's] toy example it's simple" and, based on Stephen's comment, that the JLS is the limiting factor. I'm pretty sure we agree.
T
Tiny

According the specifications, the following is said about while statements.

A while statement can complete normally iff at least one of the following is true: The while statement is reachable and the condition expression is not a constant expression with value true. There is a reachable break statement that exits the while statement.\

So, the compiler will only say that the code following a while statement is unreachable if the while condition is a constant with a true value, or there is a break statement within the while. In the second case, since the value of b is not a constant, it doesn't consider the code following it to be unreachable. There's a whole lot more information behind that link to give you more details on just what is and what isn't considered unreachable.


+1 for noting that it's not just what the compiler writers can or can't think up -- the JLS tells them what they can and can't consider unreachable.
h
hope_is_grim

Because true is constant and b can be changed in the loop.


Correct, but irrelevant, as b is NOT being changed in the loop. You could equally argue that the loop that uses true could include a break statement.
@deworde - as long as there is a chance that it could be changed (by another thread, say) then the compiler cannot call it an error. You cannot tell just by looking at the loop itself whether or not b is going to be changed while the loop is running.
k
kba

Because analyzing variable state is hard, so the compiler has pretty much just given up and let you do what you wish. Additionally, the Java Language Specification has clear rules about how the compiler is allowed to detect unreachable code.

There are many ways to trick the compiler - another common example is

public void test()
{
    return;
    System.out.println("Hello");
}

which wouldn't work, as the compiler would realize that the area was unreacable. Instead, you could do

public void test()
{
    if (2 > 1) return;
    System.out.println("Hello");
}

This would work since the compiler is unable to realize that the expression will never be false.


As other answers have stated, this is not (directly) related to what might be easy or hard for the compiler to detect the unreachable code. The JLS goes to great lengths to specify the rules for detecting unreachable statements. According to those rules, the first example is not valid Java, and the second example is valid Java. That's it. The compiler just implements the rules as specified.
I don't follow the JLS argument. Are compilers really obligated to turn all legal Java into byte code? even if it can be proven to be nonsensical.
@emory Yes, that would be the definition of a standards-compliant browser, that it obeys the standard and compiles legal Java code. And if you use a non-standards-compliant browser, while it might have some nice features, you're now relying on a busy compiler designer's theory about what is a "sensible" rule. Which is a truly terrible scenario: "Why would anyone want to use TWO conditionals in the same if? I never have")
This example using if is a little different from a while, because the compiler will compile even the sequence if(true) return; System.out.println("Hello"); without complaining. IIRC, this is a special exception in the JLS. The compiler would be able to detect the unreachable code after the if as easy as with while.
@emory - when you feed a Java program through a 3rd-party annotation processor, it is ... in a very real sense ... not Java anymore. Rather, it is Java overlaid with whatever linguistic extensions and modifications that the annotation processor implements. But that is NOT what is going on here. The OP's questions are about vanilla Java, and the JLS rules govern what is and what is not a valid vanilla Java program.
C
Cheesegraterr

The latter is not unreachable. The boolean b still has the possibility of being altered to false somewhere inside the loop causing an ending condition.


Correct, but irrelevant, as b is NOT being changed in the loop. You could equally argue that the loop that uses true could include a break statement, which would give an ending condition.
C
Carl Manaster

My guess is the variable "b" has possibility to change its value, so compiler think System.out.println("while terminated"); can be reached.


P
Paul Bellora

Compilers aren't perfect - nor should they be

The responsibility of the compiler is to confirm syntax - not to confirm execution. Compilers can ultimately catch and prevent many run time problems in a strongly typed language - but they cannot catch all such errors.

The practical solution is to have batteries of unit tests to complement your compilers checks OR use object oriented components for implementing logic that are known to be robust, rather then relying on primitive variables and stop conditions.

Strong Typing and OO : increasing compiler's efficacy

Some errors are syntactical in nature - and in Java, the strong typing makes a lot of run time exceptions catchable. But, by using better types, you can help your compiler to enforce better logic.

If you want the compiler to enforce logic more effectively, in Java, the solution is to build robust, required objects that can enforce such logic, and using those objects to build up your application, rather than primitives.

A classic example of this is the use of the iterator pattern, combined with Java's foreach loop this construct is less vulnerable to the type of bug you illustrate than a simplistic while loop.


Note that OO is not the only way to help static strong typing mechanisms finding errors. Haskell's parametric types and type classes (which are a bit different from what's called classes in OO languages) are actually quite arguably better at this.
J
Jonathan M

The compiler is not sophisticated enough to run through the values that b may contain (though you only assign it once). The first example is easy for the compiler to see it will be an infinite loop because the condition is not variable.


This is not related to the compiler's "sophistication". The JLS goes to great lengths to specify the rules for detecting unreachable statements. According to those rules, the first example is not valid Java, and the second example is valid Java. That's it. The compiler writer must implement the rules as specified ... or else his compiler is non-conformant.
s
sarnold

I'm surprised your compiler refused to compile the first case. That seems strange to me.

But the second case isn't optimized to the first case because (a) another thread might update the value of b (b) the called function might modify the value of b as a side effect.


If you are surprised, then you haven't read enough of the Java Language Spec :-)
Haha :) It's nice to know where I stand. Thanks!
B
Bill K

Actually I don't think anyone got it QUITE right (at least not in the original questioner's sense). The OQ keeps mentioning:

Correct, but irrelevant, as b is NOT being changed in the loop

But it doesn't matter because the last line IS reachable. If you took that code, compiled it into a class file and handed the class file to someone else (say as a library), they could link the compiled class with code that modifies "b" through reflection, exiting the loop and causing the last line to execute.

This is true of any variable that isn't a constant (or final which compiles to a constant in the location where it's used--sometimes causing bizarre errors if you recompile the class with the final and not a class that references it, the referencing class will still hold the old value without any errors whatsoever)

I've used the ability of reflection to modify non-final private variables of another class to monkey-patch a class in a purchased library--fixing a bug so we could continue developing while we waited for official patches from the vendor.

By the way, this may not actually work these days--although I've done it before, there is a chance that such a small loop will be cached in the CPU cache and since the variable is not marked volatile the cached code may never pick up the new value. I've never seen this in action but I believe it's theoretically true.


Good Point. I assume that b is a method variable. Can you really modify a method variable using reflection? Regardless the overall point is that we should not assume that the last line is not reachable.
Ouch, you are right, I assumed b was a member. If b is a method variable then I believe the compiler "Can" know that it won't change but doesn't (which makes all the other answers QUITE correct after all)
L
Lion

It is simply because the compiler doesn't to too much baby sitting work, though it is possible.

The example shown is simple and reasonable for compiler to detect the infinite loop. But how about we insert 1000 lines of code without any relationship with variable b? And how about those statements are all b = true;? The compiler definitely can evaluate the result and tell you it is true eventually in while loop, but how slow it will be to compile a real project?

PS, lint tool definitely should do it for you.


S
SundayMonday

From the compiler's perspective the b in while(b) could change to false somewhere. The compiler just doesn't bother checking.

For fun try while(1 < 2), for(int i = 0; i < 1; i--) etc.


W
Wilmer

Expressions are evaluated at run time so, when replacing the scalar value "true" with something like a boolean variable, you changed a scalar value into a boolean expression and thus, the compiler has no way to know it at compilation time.


H
Hand-E-Food

If the compiler can conclusively determine that the boolean will evaluate to true at run-time, it will throw that error. The compiler assumes that the variable you declared can be changed (albeit we know here as humans it will not).

To emphasize this fact, if variables are declared as final in Java, most compilers will throw the same error as if you substituted for the value. This is because the variable is defined at compile time (and cannot be changed at run-time) and the compiler can therefore conclusively determine that the expression evaluates to true at run-time.


S
Sheo

The first Statement always result in an infinite loop because we have specify a constant in condition of while loop, where as in second case compiler assume that, there is possibility of change of value of b inside loop.