What happens if I remove a super constructor call from a class file?

When the constructor does not explicitly call the constructor of the superclass (or this() ), the compiler inserts super() .

What happens if this call is removed from the class file (after compilation)?

+6
source share
3 answers

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 using the class file editor.

The JVM seems to refuse 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 this is a specific behavior.

EDIT

According to Raedwald, I also need to change the manipulation of the stack.

So, I also deleted aload_0 , which was before the call to the super constructor.

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) 

I was curious, so I reordered the constructor instructions:

 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 

What worked!

+5
source

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

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

At the bytecode level, the only requirement is that there is exactly one constructor call along each returned code, plus there are restrictions on what you can do with the this value before initialization.

In particular, this means that

  • A constructor call should not appear at the beginning of a method
  • You can choose between calls to different constructors based on conditional logic
  • There may be exception handlers in the constructor, including catch exceptions thrown by the superclass constructor
  • You cannot have any constructor calls at all if each 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 an argument to invoke the constructor, and the code to evaluate this expression must obviously happen before the constructor is invoked. But the rest of these options cannot be executed in Java.

Note that if you violate these restrictions, what happens if you remove the constructor call from the regular Java constructor that returns normally, the constructor will now be invalid because it will return, but does not have a ctor call. Thus, an attempt to load a class will work at runtime using VerifyError.

For completeness, here is what you can do with the uninitialized value of this : compare it to zero and save (but not read) the fields defined in the same class.

Not so much, right? But the ability to store fields defined in one class before calling ctor is actually used by the Java compiler. At the bytecode level, there is no concept of inner classes, so when you refer to an outer class in an inner class, the compiler generates a hidden field in the inner class containing a reference to the outer class. To ensure that this works correctly, if the superclass constructor calls a method that is redefined in the inner class and calls the outer class, this hidden field must be initialized before the superclass constructor is called. Of course, you can do this when writing bytecode itself.

+3
source

The latest JVM specification talks about this in section 4.9.2 Structural Constraints :

"Each instance initialization method, except for the instance initialization method obtained from the constructor of the Object class, MUST call either another instance initialization method of this or the instance initialization method of its super superclass super before its instance members are available."

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

(And according to research by @Jimmy T, it is!)

The rationale for this limitation is explained by other answers.

+3
source

Source: https://habr.com/ru/post/971165/


All Articles