The JVM Spec 4.10.2.4 version 7, last paragraph, says
A valid instruction sequence must not have an uninitialized object on the operand stack or in a local variable at the target of a backwards branch if the special type of the uninitialized object is merged with a special type other than itself
Here's an example rejected by the verifier - I suspect that it should be accepted:
public scala.Tuple2<scala.runtime.Null$, scala.runtime.Null$> apply(boolean);
flags: ACC_PUBLIC
Code:
stack=4, locals=2, args_size=2
0: new #12 // class scala/Tuple2
3: dup
4: aconst_null
5: iload_1
6: ifne 5
9: aconst_null
10: invokespecial #16 // Method scala/Tuple2."<init>":(Ljava/lang/Object;Ljava/lang/Object;)V
13: areturn
LocalVariableTable:
Start Length Slot Name Signature
0 14 0 this LC;
0 14 1 x Z
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 5
locals = [ class C, int ]
stack = [ uninitialized 0, uninitialized 0, null ]
The error message complains about the backwards jump ifne 5
java.lang.VerifyError: Uninitialized object exists on backward branch 5
Exception Details:
Location:
C.apply(Z)Lscala/Tuple2; @6: ifne
There is indeed an uninitialized object on the stack at the jump target; however, it looks to me that the "special type of the uninitialized object" is merged with itself, just as required by the spec.
I think there is only one stack map frame, so it cannot be merged with anything else.
Interestingly, the restriction on backwards branches was removed in the JVM Spec version 8.
However, the Verifier in Java 8 VM still rejects the example.
Did I misread the JVM spec, or should the example really fail verification? I tried versions 1.7.0_60-b19
and 1.8.0_05-b13
.
General Research
The issue shows up in Scala (bugreport). To reproduce, take scala 2.11.1, make sure you're on a JVM >= 1.7 and run the following (be sure to pass -target:jvm-1.7
to scala):
localhost:sandbox luc$ scala -target:jvm-1.7
Welcome to Scala version 2.11.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_55).
Type in expressions to have them evaluated.
Type :help for more information.
scala> class C {
| def apply(x: Boolean) = new Tuple2(null, {
| while (x) { }
| null
| })
| }
defined class C
scala> new C
java.lang.VerifyError: Uninitialized object exists on backward branch 5
Exception Details:
Location:
C.apply(Z)Lscala/Tuple2; @6: ifne
Reason:
Error exists in the bytecode
Bytecode:
0000000: bb00 0959 011b 9aff ff01 b700 0db0
Stackmap Table:
full_frame(@5,{Object[#2],Integer},{Uninitialized[#0],Uninitialized[#0],Null})
... 32 elided
As mentioned above - JDK bugreport here, I hope to get a response there.
The JDK bug was fixed in JDK 8u25