Storyyeller / Krakatau

Java decompiler, assembler, and disassembler
GNU General Public License v3.0
1.97k stars 220 forks source link

"this" is not "super" #103

Open TomSie opened 7 years ago

TomSie commented 7 years ago

Hey,

I experience problems when decompiling JAVA classes that use "super". Minimal Example:

public class B extends A { public void test(int i){ System.out.println("B"); super.test(i); } }

Using Krakatau it gets decompiled to

public class BDecomp extends A {
public void test(int i) { System.out.println("BDecomp"); ((A)this).test(i); }

} For reference the A class:

public class A {

int i;

public void test(int i) {
    System.out.println("A");
    this.i = i;
}

}

If I run the follwing code B b = new B(); b.test(0);

    BDecomp d = new BDecomp();
    d.test(0);

I get:

B A BDecomp BDecomp BDecomp BDecomp ... (crash)

In short: Methods that use "super" get falsely decompiled to "this". Recompiling leads to buggy behavior.

Best regards,

Tom

Storyyeller commented 7 years ago

Unfortunately, this is a difficult issue, and I am not aware of a good solution. I'm open to suggestions though.

At any rate, you shouldn't expect to be able to recompile code. This is true for any decompiler, not just Krakatau. Decompilation is for reverse engineering - if you want to modify code, you need to use the disassembler and assembler.

TomSie commented 7 years ago

Hey,

thank you for your quick response ;) In my experience it works quite well to manually replace every occurrence of "((PARENT)this)" with "super" in Krakatau's output.

I don't know much about the internals of Krakatau, but would it be a bad idea to do this by default? After all "((PARENT)this)" looks like the code wants to call the parent's function but - according to my tests - in fact does call its own function.

Best Regards, Tom

Storyyeller commented 7 years ago

I could do it, but I prefer to avoid hacks like that. Besides, there is a risk it could change places where (Foo)this is actually intentional.

TomSie commented 7 years ago

Hey,

I modified my B class to look like this: `public class B extends A {

public void test(int i) {
    System.out.println("B");
    this.i = i;
}

public void testSuper(int i){
    System.out.println("B - testSuper");
    super.test(i);

}

public void testSuperThis(int i){
    System.out.println("B - testSuperThis");
    ((A)this).test(i);

}

public void testThis(int i){
    System.out.println("B - testThis");
    this.test(i);

}

}`

Additionally, I wrote the following test script: B b = new B(); System.out.println("1"); b.testSuper(0); System.out.println("2"); b.testThis(0); System.out.println("3"); b.testSuperThis(0);

Output: 1 B - testSuper A 2 B - testThis B 3 B - testSuperThis B

As you can see, ((A)this). is functionally equivalent to this.. So if you actually want to call a parent's function, you need to use super. Just putting a cast in front of it doesn't work - at least not according to my tests.

So correct me if I'm wrong, but I cannot imagine a legitimate use of (Foo)this, as it seems to be functionally equivalent to this.

However, I can very well understand your point of not doing said hack as I tried to decompile my B.class with Krakatau. Result:

`public class B extends A { public B() { super(); }

public void test(int i)
{
    System.out.println("B");
    this.i = i;
}

public void testSuper(int i)
{
    System.out.println("B - testSuper");
    ((A)this).test(i);
}

public void testSuperThis(int i)
{
    System.out.println("B - testSuperThis");
    ((A)this).test(i);
}

public void testThis(int i)
{
    System.out.println("B - testThis");
    this.test(i);
}

} `

So after decompilation with Krakatau testSuper became equivalent to testSuperThis, while in the actual code, testSuperThis is equivalent to testThis.

Any Idea how this could be handled?

Best Regards, Tom