I've been tinkering with reverse engineering a Java app, and I've stumbled upon something interesting. The bytecode I found seems to break the rules by not initializing the superclass first in a constructor.
I'm trying to figure out how this is possible. Could it be a normal behavior of Java compilers, or is it some sneaky obfuscation technique (Note: It's worth mentioning that the original class name hasn't been stripped by the obfuscator, which indicates that the obfuscation process might not have been very thorough. So, it's less likely that the bytecode structure is a deliberate result of obfuscation.)
Could anyone perhaps offer some insight on what could the original code have looked like to generate such unconventional bytecode? I'm eager to learn and unravel this mystery. Thanks a bunch!
Here is the bytecode.
final class a/ka$a extends java/lang/Thread {
<ClassVersion=51>
<SourceFile=CLThreadPool.java>
private synthetic a.ka a;
public ka$a(a.ka arg0, java.lang.String arg1, boolean arg2) { // <init> //(La/ka;Ljava/lang/String;Z)V
<localVar:index=0 , name=this , desc=La/ka$a;, sig=null, start=L0, end=L4>
<localVar:index=2 , name=name , desc=Ljava/lang/String;, sig=null, start=L0, end=L4>
<localVar:index=3 , name=daemon , desc=Z, sig=null, start=L0, end=L4>
L0 {
aload 0 // reference to self
aload 1 // reference to arg0
putfield a/ka$a.a:a.ka
}
L1 {
aload 0 // reference to self
aload 1 // reference to arg0
new java/lang/StringBuilder
dup
aload 2 // reference to arg1
invokestatic java/lang/String.valueOf(Ljava/lang/Object;)Ljava/lang/String;
invokespecial java/lang/StringBuilder.<init>(Ljava/lang/String;)V
ldc ".pool[" (java.lang.String)
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
aload 1 // reference to arg0
dup
invokestatic a/ka.a(La/ka;)I
dup_x1
iconst_1
iadd
invokestatic a/ka.a(La/ka;I)V
invokevirtual java/lang/StringBuilder.append(I)Ljava/lang/StringBuilder;
ldc "]" (java.lang.String)
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
invokespecial java/lang/Thread.<init>(Ljava/lang/ThreadGroup;Ljava/lang/String;)V
}
L2 {
aload 0 // reference to self
iload 3
invokevirtual a/ka$a.setDaemon(Z)V
}
L3 {
return
}
L4 {
}
}
The compiler preferences were also still in the obfuscated jar:
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=1.7
I've considered the possibility that there might have been a static method involved, responsible for the logic that ended up getting inlined during compilation. Despite my attempts, I haven't been able to reproduce a similar output. Additionally, I noticed the presence of a synthetic field and the fact that this class was a inner class. These factors seem to play a role in the unusual bytecode structure.
Loading
thisand assigning its instance fields before calling another constructor is perfectly allowed by the JVM. It is the Java language that does not allow setting instance fields before callingthis(...)orsuper(...).From the JVM spec:
So the JVM does not allow reading instance fields before calling another constructor, but it allows writes. Implementations of a Java compiler can totally rearrange the code to produce something like this, given that it can prove that the behaviours are the same.
It could also be the work of an obfuscator. One possibility is that this is intended to cause (naive) decompilers to output illegal code. A decompiler looking at this might read the line numbers and assume that there is a
this.a = arg0;statement on the first line, causing the decompiler's output to not compile.In your particular case, based on the fact that the field is synthetic and this is an inner class, this field is highly likely to store the enclosing instance.
For example, the
Innerclass below would need to store an instanceOuter, and a synthetic field would be created for that.My compiler generates a
putfieldbefore the superclass constructor call, that assigns the first constructor parameter to the synthetic field. In Java code, theInnerclass would look something like this (this is invalid Java code, just for illustrative purposes):Generating
putfieldbefore thesupercall is required (credits to Holger for informing me of this!), because the superclass constructor could call a method that the subclass overrides. That method could access the enclosing instance.