leibnitz27 / cfr

This is the public repository for the CFR Java decompiler
https://www.benf.org/other/cfr
MIT License
1.94k stars 249 forks source link

Obfuscated/renamed generic interface methods lead to incorrect decompilation. #233

Open rehwinkel opened 3 years ago

rehwinkel commented 3 years ago

CFR version

CFR 0.151

Compiler

Unknown, but presumably javac with proguard

Description

When decompiling a class implementing java.lang.Comparable the interface method compareTo, which, because it is generic, takes Object as a parameter, will be decompiled as such, rendering the decompiled code invalid because the parameter of the compareTo function doesnt match the generic argument.

Example

public class class_4_aa extends java.lang.Object implements java.lang.Comparable<class_4_aa>
  minor version: 0
  major version: 52
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #2                          // class_4_aa
  super_class: #5                         // java/lang/Object
  interfaces: 1, fields: 2, methods: 17, attributes: 4
{
  public int method_26_a(class_4_aa);
    descriptor: (Lclass_4_aa;)I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=2
        0: iconst_0
        1: ireturn

  public int compareTo(java.lang.Object);
    descriptor: (Ljava/lang/Object;)I
    flags: (0x1001) ACC_PUBLIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: checkcast     #2                  // class class_4_aa
         5: invokevirtual #236                // Method method_26_a:(Lclass_4_aa;)I
         8: ireturn
}
Signature: #324                         // Ljava/lang/Object;Ljava/lang/Comparable<Lclass_4_aa;>;

I assume that in this example, the compareTo method has been generated by an obfuscator when the method_26_a was renamed. However, Comparable should be accessible to CFR which means it should be able to fill in the erased type. The decompiled code of this is roughly this:

public class class_4_aa
    implements Comparable<class_4_aa> {
    public int method_26_a(class_4_aa arg) {
        return 0;
    }

    @Override
    public /* synthetic */ int compareTo(Object object) {
        return this.method_26_a((class_4_aa)object);
    }
}
rehwinkel commented 3 years ago

This is a minimal example that explains how this bug is created:

public class Test implements java.lang.Comparable<Test> {
    public int compareTo(Test t) {
        return 0;
    }
}

The code generated is this:

{
  public Test();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public int compareTo(Test);
    descriptor: (LTest;)I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: iconst_0
         1: ireturn
      LineNumberTable:
        line 3: 0

  public int compareTo(java.lang.Object);
    descriptor: (Ljava/lang/Object;)I
    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: checkcast     #7                  // class Test
         5: invokevirtual #9                  // Method compareTo:(LTest;)I
         8: ireturn
      LineNumberTable:
        line 1: 0
}

As you can see, the synthetic compareTo method calls the specialized version. However, if the specialized version is renamed by a deobfuscator, CFR can no longer correctly decompile this code.