Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8173022

java.lang.VerifyError produced when compiling unused code in layered Generic types...

    XMLWordPrintable

Details

    • generic
    • generic

    Description

      FULL PRODUCT VERSION :
      java version "1.8.0_121"
      Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
      Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)


      ADDITIONAL OS VERSION INFORMATION :
      Linux lapos 4.9.3-200.fc25.x86_64 #1 SMP Fri Jan 13 01:01:13 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

      A DESCRIPTION OF THE PROBLEM :
      Suppose you have an abstract (AbstractL1), which in may be enclosed (as a generic type field of) another abstract class (AbstractL2<L1Type extends AbstractL1>). The enclosed L1Type has a nested abstract class <L1Type>.SubClass(), which we want to use.

      Now, consider a particular implementation of these classes L1 (extends AbstractL1) and L2 (extends AbstractL2<L1>). L1 has a test method which simply instantiates a nested subclass of L1.

      While this is all well defined ('l1' is of inferred type L1 inside L2),

         l1.new SubClass() {};

      Fails with a VerifyError, claiming that 'AbstractL1' is not assignable from 'L1' (even though 'L1 extends AbstractL1' by construct!).



      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. compile the Test class (attached as source code)

         > javac Test.java

      2. run the Test class

         > java Test


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Test code should complete without errors with the following console output:

      Success!
      ACTUAL -
      Test code does not complete, but results in java.lang.VerifyError.

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
      Exception Details:
        Location:
          L2.test()V @14: invokespecial
        Reason:
          Type 'AbstractL1' (current frame, stack[3]) is not assignable to 'L1'
        Current Frame:
          bci: @14
          flags: { }
          locals: { 'L2' }
          stack: { uninitialized 0, uninitialized 0, 'L2', 'AbstractL1' }
        Bytecode:
          0x0000000: bb00 0259 2a2a b400 0359 b600 0457 b700
          0x0000010: 0557 b1

      at Test.main(Test.java:4)


      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      public class Test {
          public static void main(String[] args) {
              L1 l1 = new L1();
              L2 l2 = new L2(l1);
              //l2.testWithUnnecessaryCast();
              l2.test();
              
              System.out.println("Success!");
          }
      }

      // <---------------- Abstract classes and containers using generic types ----------------->


      //An abstract level 1 class...
      abstract class AbstractL1 {
          
          public void set() { }

          // This is a nested subclass, which may access elements from the outer (Level 1) class...
          public abstract class SubClass {}
      }

      // This is a Level 2 enclosure that wraps a Level 1 class...
      abstract class AbstractL2<L1Type extends AbstractL1> {
          L1Type l1;

          public AbstractL2(L1Type enclosure) { this.l1 = enclosure; }
      }


      // <--------------- Actual class implementations for the above ------------>


      class L1 extends AbstractL1 {
      }

      class L2 extends AbstractL2<L1> {
          
          public L2(L1 l1) {
              super(l1);
          }

          // This test fails, with a VerifyError claiming that
          // "Type 'AbstractL1' (current frame, stack[3]) is not assignable to 'L1'"
          // even though L1 is clearly a direct subclass of AbstractL1...
          public void test() {
              l1.new SubClass() {};
          }
          
          // This test succeeds, but only if the body of the above 'test()' function is commented out...
          // -- a strong indication that 'test()' above triggers a compiler bug, since
          // the existence of that unused code above should not affect the independent code below...
          // Even so, the cast that makes it work should not be required, since
          // the 'l1' field is explicitly set to be of type L1 in the class declaration above...
          public void testWithUnnecessaryCast() {
              ((L1) l1).new SubClass() {};
          }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      A strange workaround with an even stranger caveat to it exists...

      First the workaround: Using explicit cast of 'L2.l1' to L1 (even though it should not be necessary). Follow these steps to alter the test code:

       1. comment line 48 [the body of 'L2.test()'].
       2. comment line 6 [the call to 'L2.test()'].
       3. uncomment line 5 [the call to 'L2.testWithUnnecessaryCast()'].

      Now, the test case works, outputting 'Success!!!'.

      Now the caveat to the above workaround:

       4. uncomment line 48 [the body of 'L2.test()'].
       
      The test fails again, even though the only line changed was uncommenting the body of the L2.test() method, which is not even used in the workaround. The fact that unused code breaks the working test case seems to strongly indicate that it is a compiler error of sorts...
       

      Attachments

        Activity

          People

            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: