JDK-4030374 : * Initialization of up-level links, immediately after super(), occurs too late.
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version:
    unknown,1.1,1.1.2,1.1.3,1.1.5,1.1.6,1.1.7,1.2.0,1.2.1,1.2.2,1.3.0,1.4.1,1.4.2 unknown,1.1,1.1.2,1.1.3,1.1.5,1.1.6,1.1.7,1.2.0,1.2.1,1.2.2,1.3.0,1.4.1,1.4.2
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS:
    generic,linux,solaris_2.5,solaris_2.5.1,solaris_2.6,solaris_8,windows_95,windows_nt,windows_2000 generic,linux,solaris_2.5,solaris_2.5.1,solaris_2.6,solaris_8,windows_95,windows_nt,windows_2000
  • CPU: generic,x86,sparc
  • Submitted: 1997-02-05
  • Updated: 2001-02-02
  • Resolved: 2001-02-02
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
1.4.0 betaFixed
Related Reports
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Description
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)
======================================================================

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: merlin merlin-beta FIXED IN: merlin-beta INTEGRATED IN: merlin-beta
14-06-2004

WORK AROUND Don't call subclass methods from the superclass constructor. After the superclass constructor has returned it is safe; one option is to move such code into every subclass constructor.
11-06-2004

SUGGESTED FIX In order to fix this bug, the verifier will have to be loosened to allow simple initialization of local fields before the superclass constructor. This seems generally useful, harmless, and necessary for any sort of block-scoped language implementation. See the E-mail in the attachment, for a brief exchange with JLS authors on this proposal.
11-06-2004

PUBLIC COMMENTS <p>In previous releases of the compiler, up-level references from a class's instance methods did not work until after the superclass constructor has returned. For example, the following program would fail at runtime: <pre> 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(); } } </pre> <p>Fixing this bug required initializing the synthetic member <tt>this$0</tt> before calling the superclass constructor. Although specifically allowed by the VM specification (4.8.2), the verifier failed to allow it prior to 1.4. Therefore, this code change only appears when you use the <tt>-target 1.4</tt> compiler flag.
10-06-2004

EVALUATION The verifier needs loosened, and its important to happen in time for 1.2. david.stoutamire@Eng 1997-09-04 It has been decided to postpone this fix until after 1.2fcs (perhaps 1.2.1). This is because the compiler change should follow any minor version number change, and we want to avoid impacting testing at this time. This should be marked as "committed for 1.2.1" when bugtraq knows about that release. It is also being reprioritized to P4 because it is no longer a candidate to fix in 1.2FCS. david.stoutamire@Eng 1998-08-24 The compiler cannot comply with two conflicting specifications. Either the language needs to forbid this construct, or the verifier needs to let us generate code to make it work. One or the other specification needs to be changed. neal.gafter@Eng 2000-12-15 Regarding the same problem for local classes, the following evaluation is copied from 4255913: neal.gafter@Eng 2000-12-15 The val$parameter and val$local_var fields of the anonymous class are initialized during the execution of the constructor for the inner anonymous class. This constructor must first call the superclass constructor before assigning any fields, due to verification requirements. We admit that this behavior is anomalous and undesirable, but it cannot be done differently without a liberalization of the verification rules, a change that would break compatibility with all existing VM's. Most likely, such a change could be done only as a part of a major overhaul of the VM, which is not being planned at this time. There is a well-known analogous problem with the initializatio of the outer instance pointers, i.e., this$0. This is the reason for the following statement in the inner classes spec, which is misleading, because it applies to *all* Java compilers that correctly generate verifiable code: There is a limitation in some implementations of Java 1.1., under which the initialization of this$0 is delayed until after any superclass constructor is run. This means that up-level references made by a subclass method may fail if the method happens to be executed by the superclass constructor. The same problem exists in jdk1.3 as well. It is not a compiler problem per-se, but a compromise in the implementation required by the existing verification rules. william.maddox@Eng 1999-08-04 The JVM spec second edition specifically allows initializing members of the current class before calling the superclass constructor. If the verifier doesn't allow it, then the verifier has a bug. If the verifier does allow it, then we can produce correct code. Either way, it needs to be fixed in the compiler and conditioned on a -target flag. neal.gafter@Eng 2001-01-02 The 1.4 verifier chokes on the resulting (correct) code. I have filed a bug against the verifier (4408261) but included the fix in the compiler conditioned on -target=1.4. neal.gafter@Eng 2001-01-25
25-01-2001