Other |
---|
1.4.0 betaFixed |
Duplicate :
|
|
Duplicate :
|
|
Duplicate :
|
|
Duplicate :
|
|
Duplicate :
|
|
Duplicate :
|
|
Duplicate :
|
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) ======================================================================
|