45

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

Rann Lifshitz
  • 4,040
  • 4
  • 22
  • 42
Lukas Rytz
  • 1,894
  • 14
  • 27
  • On a sidenote, that bytecode seems rather useless. It infinite loops in the case local1 is 0 and otherwise ignores it. – Antimony Jun 05 '14 at 14:22
  • Which version of the JVM are you using? The verifier at http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/tip/src/share/native/common/check_code.c should accept your code just fine (it doesn't even include the error message you posted in the source code). – Antimony Jun 05 '14 at 14:26
  • 1
    I'm using `Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_55` - will try some others. – Lukas Rytz Jun 05 '14 at 14:56
  • I think your reading of the spec is correct, the _special type_ is unchanged before the jump. This code (and bytecode) does work for me on Arch with `Scala version 2.11.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0)` – ggovan Jun 05 '14 at 15:01
  • I just tried on `Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_05` and got the same crash. @ggovan, did you run the scala interpreter using `-target:jvm-1.7`? – Lukas Rytz Jun 05 '14 at 15:03
  • Missed that bit, so `-target:jvm-1.7` breaks mine as well. – ggovan Jun 05 '14 at 15:15
  • Perhaps it's a bug in the new verifier. – Antimony Jun 06 '14 at 01:40
  • P.S. One other step you could take to narrow this down - is Scala generating different bytecode for 1.7 and 1.8 targets? Does the 1.7 code work if you manually degrade the classfile to 1.6 and remove the stackmap attribute? – Antimony Jun 06 '14 at 04:10
  • Yes, I verified that. If you don't pass the `-target:jvm-1.7` to scala, then it will create a classfile with version 50 (the version for JDK 1.6) instead of 51. In that case, the verifier does not enforce correctness of stack map frames (see JVM spec 4.10.1), the error does not trigger. – Lukas Rytz Jun 06 '14 at 05:47
  • 1
    You were looking at the wrong chapter. JVM Spec §4.10.2 describes the type inference verifier which applies to version <=49 (or ==50 when `StackMapTable` verification failed) only. For class files >50 `StackMapTable`s are mandatory and falling back to type inference verifier is not allowed. Hence, only [JVM Spec §4.10.1](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10.1) applies (see the first two sentences). Note that §4.9.2 contained the backward branch restriction too, and it has been removed for Java 8 as well. But seems that §4.10.1 never had this restriction. – Holger Jun 27 '14 at 16:06
  • Hi Holger, thanks for your comment, this is a very good point. Now I'm even more confused :) In the spec for version 6, the [Structural Constraints](http://docs.oracle.com/javase/specs/jvms/se5.0/html/ClassFile.doc.html#9308) say "There must never be an uninitialized class instance on the operand stack [..] when any backwards branch is taken". This restriction has been relaxed a bit in version 7, and removed in version 8. HOWEVER, JDK 6 doesn't complain about a classfile that has such a backwards branch, while JDK 7 and 8 do. – Lukas Rytz Jun 29 '14 at 09:23
  • It’s not unusual to have differences between specification and implementation. So it seems the type inference verifier accepted such backward branch and that’s the big point of targeting Java 7 rather than older versions: with class file version 50 (aka Java 6), the verifier will fall back to the type inference algorithm if stack map verification fails. While this was intended to work-around bugs in byte code producing tools, it helped great overlooking bugs in the new verifier as well… – Holger Jun 30 '14 at 09:35
  • This bug, [8046233](http://bugs.java.com/view_bug.do?bug_id=8046233), is listed as fixed in the [8u25 bug fixes](http://www.oracle.com/technetwork/java/javase/2col/8u25-bugfixes-2298227.html). – David Conrad Oct 14 '14 at 21:48
  • Why did you delete you answer? – Holger Oct 02 '18 at 09:34
  • I updated the question, but maybe it's more clear to keep the answer. – Lukas Rytz Oct 03 '18 at 10:06

1 Answers1

2

The JDK bug was fixed in JDK 8u25

Lukas Rytz
  • 1,894
  • 14
  • 27