Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java uninitialized variable with finally curiosity

I was trying to come up with obscure test cases for an alternative open-source JVM I am helping with (Avian) when I came across an interesting bit of code, and I was surprised that it didn't compile:

public class Test {
    public static int test1() {
        int a;
        try {
            a = 1;
            return a; // this is fine
        } finally {
            return a; // uninitialized value error here
        }
    }
    public static void main(String[] args) {
        int a = test1();
    }
}

The most obvious code path (the only one that I see) is to execute a = 1, "attempt" to return a (the first time), then execute the finally, which actually returns a. However, javac complains that "a" might not have been initialized:

    Test.java:8: variable a might not have been initialized  
        return a;  
               ^  

The only thing I can think of that might cause / allow a different code path is if an obscure runtime exception were to occur after the start of the try but before the value 1 is assigned to a - something akin to an OutOfMemoryError or a StackOverflowException, but I can't think of any case where these could possibly occur at this place in the code.

Can anyone more familiar with the specifics of the Java standard shed some light on this? Is this just a case where the compiler is being conservative - and therefore refusing to compile what would otherwise be valid code - or is something stranger going on here?

like image 965
Joshua Warner Avatar asked Aug 08 '09 05:08

Joshua Warner


People also ask

What is uninitialized variable in Java?

In computing, an uninitialized variable is a variable that is declared but is not set to a definite known value before it is used. It will have some value, but not a predictable one. As such, it is a programming error and a common source of bugs in software.

What happens in Java if you try to use an uninitialized variable?

Accessing an uninitialized local variable will result in a compile-time error. Show activity on this post. The default value will be based on the type of the data and place where you are using initialized variable .

Why are uninitialized variables bad?

"Uninitialized variables contain some value" is a incorrect statement which unfortunately is teached. A program who access an uninitialized variable has Undefined Behavior, which means it can have any behavior.

What will happen if you try to access an uninitialized local variable?

There will be no default values or ability to run the code. It will be a compile time error and your code won't run. If the variables were class fields they would get default values for certain types or null otherwise.


5 Answers

It may seem counter intuitive that an exception could occur on the a=1 line, but a JVM error could occur. Thus, leaving the variable a uninitialized. So, the compiler error makes complete sense. This is that obscure runtime error that you mentioned. However, I would argue that an OutOfMemoryError is far from obscure and should be at least thought about by developers. Furthermore, remember that the state that sets up the OutOfMemoryError could happen in another thread and the one action that pushes the amount of heap memory used past the limit is the assignment of the variable a.

Anyways, since you are looking at compiler design, I'm also assuming that you already know how silly it is to return values in a finally block.

like image 179
Elijah Avatar answered Oct 01 '22 04:10

Elijah


The Java Language Specification requires that a variable is assigned before it is used. The JLS defines specific rules for that known as "Definite Assignment" rules. All Java compilers need to adhere to them.

JLS 16.2.15:

V is definitely assigned before the finally block iff V is definitely assigned before the try statement.

In another words, when considering the finally statement, the try and catch block statements within a try-catch-finally statement assignments are not considered.

Needless to say, that specification is being very conservative here, but they would rather have the specification be simple while a bit limited (believe the rules are already complicated) than be lenient but hard to understand and reason about.

Compilers have to follow these Definite Assignment rules, so all compilers issue the same errors. Compilers aren't permitted to perform any extra analysis than the JLS specifies to suppress any error.

like image 36
notnoop Avatar answered Oct 01 '22 06:10

notnoop


I believe it's just due to the semantics of a try-catch-finally relationship. From the Java Language Specification:

If execution of the try block completes normally, then the finally block is executed...

If execution of the try block completes abruptly because of a throw of a value V...

If execution of the try block completes abruptly for any other reason R, then the finally block is executed...

The last case seems to be the most relevant here. It seems that the finally block should be able to be correctly executed if the the try block completes abruptly for ANY reason. Obviously if the try block ended before the assignment the finally block would not be valid. Though, as you said, this is not particularly likely.

like image 33
Falaina Avatar answered Oct 01 '22 04:10

Falaina


It's very likely javac is required to make the blanket assumption that an exception could occur at any point in the try block, even during the assignment, and that therefore the finally might return an uninitialized variable. In theory it could do a detailed analysis and discover that in all paths through the try block 'a' would always be initialized successfully, but that's a lot of work for almost no gain.

Now if someone just can point out the relevant section in the Java Language Specification ...

like image 37
Jim Ferrans Avatar answered Oct 01 '22 04:10

Jim Ferrans


compiler errors occur in conditional blocks if the compiler is not certain that the following(succeeding) statement will run like

int i=5;int d;
if(i<10)
{system.out.println(d);}

compiler errors will not occur if the conditional statement is certain and undoubtful code will not be reached like

int i;

if(true){}

else
{System.out.println(d);}

and compiler errors will occur if the conditional statement will definitely occur and undoubtful code will be reached like

int i;
if(true)
{System.out.println(d);}
else{}

as try blocks come under this they follow the same rules.

like image 2
sri kanth Avatar answered Oct 01 '22 05:10

sri kanth