benf.org :  other :  cfr :  Renaming methods - (For obfuscation or otherwise).

nb: This is related to, but different from illegal field/method names, and renamed field names.

Renaming methods

Related to field renaming is method renaming - because the JVM performs lookups based on the entire signature of a method, it's possible to create/use an obfuscator to rebuild a class so that two functions which have the same signature apart from return type are renamed to the same name.

public int foo(int x) {
  ...
}

public String foo(int x) {
  ...
}

The JVM will have no trouble distinguishing these, but it's illegal java. (There's a seperate discussion about bridge methods I'm not going to go into, unless someone asks!) This is a pretty nice technique for rendering decompiled code harder to read.

Issues

Whole jar analysis

One problem that might occur is to have two interfaces, which each implement a legitimate method, and a class which implements both interfaces - to decompile this and fix the names, CFR has to rewrite the names in both interface definitions (and all other implementors of these interfaces) as well as in the clashing class.

/*
 * Duplicate member names - consider using --renamedupmembers true
 */
public interface BadDescI1 {
    public int test1();
}

public interface BadDescI2 {
    public String test1();
}

public class BadDesc1
implements BadDescI1,
BadDescI2 {
    @Override
    public int test1() {
        return 100;
    }

    @Override
    public String test1() {
        return "fred";
    }
}

public class BadDesc2
extends BadDesc1 {
    @Override
    public String test1() {
        return "A : " + super.test1() + ", B : " + super.test1();
    }
}

public class BadDesc3
implements BadDescI2 {
    @Override
    public String test1() {
        return "FIDDLE";
    }
}

Above - only BadDesc1 can see that there's a possible clash, but all of these classes/interfaces need to be rewritten in order to generate code which will compile. The flag --renamedupmembers true will perform an additional pass before anything is decompiled to check for this.

// Decompiled with cfr_0_95, --renamedupmembers true

public interface BadDescI1 {
    public int int_test1();
}

public interface BadDescI2 {
    public String java_lang_String_test1();
}

public class BadDesc1
implements BadDescI1,
BadDescI2 {
    @Override
    public int int_test1() {
        return 100;
    }
    
    @Override
    public String java_lang_String_test1() {
        return "fred";
    }
}

public class BadDesc2
extends BadDesc1 {
    @Override
    public String java_lang_String_test1() {
        return "A : " + super.int_test1() + ", B : " + super.java_lang_String_test1();
    }
}

public class BadDesc3
implements BadDescI2 {
    @Override
    public String java_lang_String_test1() {
        return "FIDDLE";
    }
}

You can find this example here.

Illegal 'non' overrides vs legal overrides...

This is legal java

public class Base {
    public Object foo() {
        return null;
    }
}

public class D1
extends Base {
    @Override
    public String foo() {
        return null;
    }
}

However, this isn't

public class Base {
    public int foo() {
        return 1;
    }
}

public class D1
extends Base {
    @Override
    public String foo() {
        return null;
    }
}

You can find this example here

This is because in the first example, we've used Java's support for covariant return types, where as no such relationship exists between int and String.

As of 0_95, CFR correctly spots that the former is legal java, and the renamedupmembers flag has no effect, however it will rewrite the names in the latter example.

Support

CFR 0_95 now attempts to fix these issues when the renamedupmembers flag is set, however, I suspect there may be edge cases I don't yet get correct - please feel free to mail me if so!


Last updated 01/2015