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

* Initialization of up-level links, immediately after super(), occurs too late.

XMLWordPrintable

    • beta
    • generic, x86, sparc
    • generic, linux, solaris_2.5, solaris_2.5.1, solaris_2.6, solaris_8, windows_95, windows_nt, windows_2000

      Because of verifier limitations, the compiler cannot arrange to initialize
      synthetic fields like this$0 and val$array before the superclass constructor
      runs. This is a problem because Java allows subclass methods to be run from
      the superclass constructor.

      // this is JCK 1.1 test innr017c
      class SuperInitBug {
      static class S {
      void hi() { System.out.println("You shouldn't see this message"); }
      S() { hi(); }
      }
      static class T {
      void greet() { System.out.println("You won't see this either."); }
      class N extends S {
      void hi() {
      T.this.greet(); //throws a NullPointerException!!
      }
      }
      }
      public static void main(String av[]) { new T().new N(); }
      }
      /*
      --- Output is: ---
      java.lang.NullPointerException
      at SuperInitBug$T$N.hi(SuperInitBug.java:10)
      at SuperInitBug$S.<init>(SuperInitBug.java:4)
      at SuperInitBug$T$N.<init>(SuperInitBug.java:8)
      at SuperInitBug.main(SuperInitBug.java:14)
      ---
      */


      Name: tb29552 Date: 03/20/2000


      java version "1.2.2"
      Classic VM (build JDK-1.2.2-001, native threads, symcjit)

      This is caused by the same issues as cause bug #4109858, but is much harder to
      recognise.

      The problem occurs when an inner class implements an abstract method in a
      superclass that is called from the superclass constructor, and accesses a
      method of the outer class. An example follows below.

      public class OuterClass
      {
        Vector tableData = new Vector();

        private class InnerClass extends javax.swing.table.DefaultTableModel
        {
          ...
          public int getRowCount()
          {
            return tableData.size();
          }
          ...
        }

        public exampleMethod()
        {
          new InnerClass();
        }
      }

      Note that the DefaultTableModel default constructor calls the method getRowCount
      (), which is abstract and implemented in InnerClass.

      The net effect of this innocuous-looking code is that a NullPointerException
      will be be thrown in exampleMethod. The reason is due to the various code
      transformations performed by the Java compiler, which will insert a single
      argument constructor in InnerClass which takes a reference to the outer class
      that

        - calls super()
      THEN
        - assigns the reference to the outer class to a member of the inner class
      called OuterClass$0

      The getRowCount() method in InnerClass is transformed to look like

        return OuterClass$0.tableData.size();

      The assignment to OuterClass$0 does not happen until after the superclass
      constructor is called, causing getRowCount() to illegally access an outer class
      member before its constructor has completed.

      The problem is that this is not obvious from inspecting OuterClass and
      InnerClass in isolation - it is necessary to both realize the DefaultTableModel
      constructor calls getRowCount() and understand the exact transformations done
      on inner classes.

      I'm a relatively experienced Swing programmer with detailed knowledge of the
      JVM, and it took me a while to realise what was going on here. It would
      probably completely confuse a novice!

      The solution is probably to have some compiler analysis of the code flow from
      inner class constructors to ensure that outer classes are not accessed until
      they are complete and provide errors if they are.
      (Review ID: 102675)
      ======================================================================

      Name: tb29552 Date: 09/18/2000


      /*
      Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.0beta_refresh-b09)
      Java HotSpot(TM) Client VM (build 1.3.0beta-b07, mixed mode)

      I see a number of semi-related bugs but nothing exactly like this
      and my example is substantially smaller/less convoluted than the
      other submissions.


      In the example below, the line:
           public void go() { mValue = 2; }
      results in a NullPointerException

      */
      public class test {
          private int mValue;

          public test () {
              new inner2();
          }

          private abstract class inner {
              public inner () {
                  go();
              }
              public abstract void go();
          }

          private class inner2 extends inner {
              public void go() {
                  mValue = 2;
              }
          }

          public static void main(String args[]) {
              new test ();
          }
      }
      (Review ID: 107107)

      ======================================================================
      The following is copied from 4255913, and regards local classes.
      ======================================================================

      The compiler might have a bug in code generation for anonymous
      classes.

      Non-local variables referenced in an anonymous subclass are
      not copied before the base class constructor is called.

      (This might not be a compiler bug but might be a flaw in
      how inner classes are defined. That is, the language spec.
      might define that non-local variables are defined to be copied
      in during initialization of the anonymous subclass (instead of
      before construction of the base class even starts). If
      that's the case, then that definition should be reworked to
      yield more intuitive (and useful) behavior.)



      Here's my test case:

      package test;

      /*
        Demonstrates possible code generation error in JDK 1.1.7B compiler.

        It appears that non-local variables that are referenced in an anonymous
        class are not properly copied (or otherwise made accessible) before the
        base class constructor is called.
        

        The output from the JDK 1.1.7B compiler is:

      callingMethod.1: parameter = whatever
      callingMethod.1: local_var = hello
      calledByConstructor (constructor): parameter = null
      calledByConstructor (constructor): local_var = null
      <init>: parameter = whatever
      <init>: local_var = hello
      calledByConstructor (callingMethod): parameter = whatever
      calledByConstructor (callingMethod): local_var = hello
      callingMethod.2: parameter = whatever
      callingMethod.2: local_var = hello

        Note how variables "parameter" and "local_var" are seen as null
        from the anonymous subclass at the time the base class constructor
        is active.
      */


      import java.util.Enumeration;
      import java.util.Vector;

      class Test
      {

          public static void main( String[] args )
          {
      callingMethod( "whatever" );
          } // main();


          protected static void callingMethod( final String parameter )
          {
      final String local_var = "hello";

      System.err.println( "callingMethod.1: parameter = " + parameter );
      System.err.println( "callingMethod.1: local_var = " + local_var );

      BaseClass enum = new BaseClass()
      {
      {
      System.err.println( "<init>: parameter = " + parameter );
      System.err.println( "<init>: local_var = " + local_var );

      //enum.calledByConstructor();
      }

      public void calledByConstructor( String caller )
      {
      System.err.println( "calledByConstructor (" + caller + "): parameter = " + parameter );
      System.err.println( "calledByConstructor (" + caller + "): local_var = " + local_var );
      }
      };
      enum.calledByConstructor( "callingMethod" );
      System.err.println( "callingMethod.2: parameter = " + parameter );
      System.err.println( "callingMethod.2: local_var = " + local_var );
          } // callingMethod(...)


      } // class Test


      abstract class BaseClass
      {

          protected BaseClass()
          {
      calledByConstructor( "constructor" );
          } // BaseClass()


          protected abstract void calledByConstructor( String caller );

      } // class BaseClass


      ======================================================================


      Name: tb29552 Date: 01/31/2001


      java version "1.3.0"
      Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.0-C)
      Java HotSpot(TM) Client VM (build 1.3.0-C, mixed mode)


      Javac 1.3 emits code for inner class constructors in the wrong order.
      Therefore, with well-formed Java, it is possible to get a spurious
      NullPointerException.

      class Outer {
        Integer i = new Integer(10);
        class A {
          public void doValidStatement() {
            System.out.println(i);
          }
        }
        abstract class Super {
          A a;
          Super() {
            a = getA();
          }
          abstract A getA();
        }
        class Middle extends Super {
          A access() {
            return a;
          }
          A getA() {
            return new Inner();
          }
          class Inner extends A { }
        }
        public static void main(String args[]) {
          new Outer().new Middle().access().doValidStatement();
        }
      }

      Some output from javap, for discussion:

      Method Outer. Middle(Outer) // constructor for Outer.Middle
         0 aload_0
         1 aload_1
         2 invokespecial #2 <Method Outer. Super(Outer)>
         5 aload_0
         6 aload_1
         7 putfield #1 <Field Outer this$0>
        10 return

      Method Outer. Middle.Inner(Outer.Middle) // constructor for Outer.Middle.Inner
         0 aload_0
         1 aload_1
         2 invokestatic #1 <Method Outer access$000(Outer.Middle)>
         5 invokespecial #2 <Method Outer. A(Outer)>
         8 aload_0
         9 aload_1
        10 putfield #3 <Field Outer.Middle this$1>
        13 return

      Method Outer.A getA() // Outer.Middle.getA
         0 new #4 <Class Outer. A>
         3 dup
         4 aload_0
         5 getfield #1 <Field Outer this$0>
         8 invokespecial #5 <Method Outer. A(Outer)>
        11 areturn

      Method void doValidStatement() // Outer.A.doValidStatement
         0 getstatic #3 <Field java.io.PrintStream out>
         3 aload_0
         4 getfield #2 <Field Outer this$0>
         7 getfield #4 <Field java.lang.Integer i>
        10 invokevirtual #5 <Method void println(java.lang.Object)>
        13 return
      }

      Notice that Middle.getA() is called from the superconstructor of Outer.Middle().
      This happens before the constructor Outer.Middle(Outer) has had a chance to
      assign Outer.Middle.this$0 (the enclosing instance of a particular Middle). In
      getA(), we create a new Inner(), which must call its superconstructor
      Outer.A(Outer). In the code for the Inner constructor, you use an accessor
      method to read Outer.Middle.this$0, which is still null as the default value.
      Therefore, you have just created an instance of Outer.A with null for an
      enclosing instance. Now, trying to do anything in A that requires the enclosing
      instance, such as printing a variable contained in Outer, throws a
      NullPointerException.

      The problem would be solved if you rewrote the constructors as follows:

      Method Outer. Middle(Outer) // constructor for Outer.Middle
         0 aload_0
         1 dup
         2 aload_1
         3 putfield #1 <Field Outer this$0>
         6 aload_1
         7 invokespecial #2 <Method Outer. Super(Outer)>
         10 return

      Method Outer. Middle.Inner(Outer.Middle) // constructor for Outer.Middle.Inner
         0 aload_0
         1 dup
         2 aload_1
         3 putfield #3 <Field Outer.Middle this$1>
         6 aload_1
         7 invokestatic #1 <Method Outer access$000(Outer.Middle)>
         10 invokespecial #2 <Method Outer. A(Outer)>
         13 return

      Notice that this does NOT violate JLS 12.5 or the JVMS. In fact, it is the only
      way to correctly avoid a NullPointerException from a reference to this$0 during
      something called by a superconstructor.

      JLS 12.5 does require that all instance variables be set to their default value
      before the superconstructor, and that they are then initialized in the slice
      between the call to super() and the rest of the constructor. However, this rule
      only applies to actual variables declared in the source code. As this$0 is a
      synthetic field created by the compiler in order to comply with the semantics
      elsewhere in the JLS regarding lexically enclosing instances, it does not meet
      the same restrictions of being null until after the call to super().

      For a similar argument, look at the synthetic code generated in an interface,
      when you use a construct like Object.class. The synthetic static helper method
      class$() generated to determine the value of the class literal is immune to
      normal JLS requirements that interfaces have no static methods.

      According to the JVMS second edition, 4.8.2: "Each instance
      initialization method, except for the instance initialization method
      derived from the constructor of class Object, must call either another
      instance initialization method of this or an instance initialization
      method of its direct superclass super before its instance members are
      accessed. However, instance fields of this that are declared in the
      current class may be assigned before calling any instance initialization
      method." Therefore, it is explicitly legal to pre-initialize this$0 to
      its final value before calling the superconstructor.
      (Review ID: 115915)
      ======================================================================

            gafter Neal Gafter (Inactive)
            jrose John Rose
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved:
              Imported:
              Indexed: