6

When a constructor doesn't have a explicit call of the super class constructor (or this()) then the compiler inserts super().

What would happen if this call was removed from the class file (after compilation)?

Jimmy T.
  • 4,033
  • 2
  • 22
  • 38

3 Answers3

5

I tried it myself.

class Test
{
    public Test()
    {
        System.out.println("Hello World");
    }

    public static void main(String[] args)
    {
        new Test()
    }
}

I compiled it and removed invokespecial java/lang/Object/<init>()V from the constructor with a class file editor.

It seems like the JVM refuses to load the class:

Exception in thread "main" java.lang.VerifyError: Operand stack overflow
Exception Details:
  Location:
    Test.<init>()V @4: ldc
  Reason:
    Exceeded max stack size.
  Current Frame:
    bci: @4
    flags: { flagThisUninit }
    locals: { uninitializedThis }
    stack: { uninitializedThis, 'java/io/PrintStream' }
  Bytecode:
    0000000: 2ab2 0002 1203 b600 04b1

        at java.lang.Class.getDeclaredMethods0(Native Method)
        at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
        at java.lang.Class.getMethod0(Unknown Source)
        at java.lang.Class.getMethod(Unknown Source)
        at sun.launcher.LauncherHelper.validateMainClass(Unknown Source)
        at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)

I still don't know if that is a defined behaviour.

EDIT

According to Raedwald I also have to alter the stack manipulation.

So I also removed aload_0 which was before the super constructor call.

Now I get the following exception:

Exception in thread "main" java.lang.VerifyError: Constructor must call super()
or this() before return
    Exception Details:
  Location:
    org/exolin/geno/Test.<init>()V @8: return
  Reason:
    Error exists in the bytecode
  Bytecode:
    0000000: b200 0212 03b6 0004 b1

        at java.lang.Class.getDeclaredMethods0(Native Method)
        at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
        at java.lang.Class.getMethod0(Unknown Source)
        at java.lang.Class.getMethod(Unknown Source)
        at sun.launcher.LauncherHelper.validateMainClass(Unknown Source)
        at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)

This made me curious so I reordered the constructor instructions to:

getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "Message"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
aload_0
invokespecial java/lang/Object/<init>()V
return

Which worked!

Jimmy T.
  • 4,033
  • 2
  • 22
  • 38
  • "Exceeded max stack size." Suggests some nearby bytecodes for manipulating the stack also need to be altered. – Raedwald Jun 22 '14 at 11:15
  • http://stackoverflow.com/questions/14633109/determining-in-the-bytecode-where-is-the-super-method-call-all-constructors-mu is a related question about which parts of the bytecode to manipulate. – Raedwald Jun 22 '14 at 13:37
3

There is a distinction between what is required at the Java language level and what is required at the bytecode level, which is much laxer.

At the Java language level, you must have a constructor call as the first statement, but the compiler will insert one implicitly if you leave it out.

At the bytecode level, the only requirement is that there is exactly one constructor call along every code path that returns normally, plus there are restrictions on what you can do with the this value prior to initialization.

In particular, this means that

  • The constructor call does not have to appear at the beginning of the method
  • You can choose between calling different constructors based on conditional logic
  • You can have exception handlers in a constructor, including catching exceptions thrown by the superclass constructor
  • You can have no constructor calls at all if every code path throws an exception or goes into an infinite loop.

The first option actually appears in Javac generated code, because in Java, you can pass an expression as argument to the constructor call, and the code to calculate that expression obviously has to happen before the constructor is called. But the rest of those options are impossible to accomplish in Java.

Note that if you violate these restrictions, which is what will happen if you remove the constructor call from an ordinary Java constructor that returns normally, the constructor will now be invalid because it returns but has no ctor call. Thus, attempting to load the class will fail at runtime with a VerifyError.

For completeness, here is what you can do with an uninitialized this value: compare it against null, and store (but not read) fields defined in the same class.

Not much, right? But the ability to store fields defined in the same class prior to the ctor call is actually used by the Java compiler. There is no notion of inner classes at the bytecode level, so when you reference the outer class in the inner class, the compiler generates a hidden field in the inner class containing a reference to the outer class. In order to make sure this works properly if the superclass constructor calls a method that is overriden in the inner class and that accesses the outer class, this hidden field has to be initialized prior to the superclass constructor call. Of course, there's more you can do with this when you're writing the bytecode yourself.

Antimony
  • 37,781
  • 10
  • 100
  • 107
3

The latest JVM spec says this in the 4.9.2 Structural Constraints section:

"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."

In other words, if you remove the invokespecial <init> instruction sequence corresponding to the constructor's super(...) or this(...) call, then the class file is invalid, and the verifier should detect it.

(And according to @Jimmy T's investigations, it does!)

The reasoning behind this constraint is explained by other Answers.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216