Is there any Java Decompiler that can correctly decompile calls to overloaded methods?

mihi picture mihi · May 15, 2010 · Viewed 7.1k times · Source

Consider this (IMHO simple) example:

public class DecompilerTest {
    public static void main(String[] args) {
        Object s1 = "The", s2 = "answer";
        doPrint((Object) "You should know:");
        for (int i = 0; i < 2; i++) {
            doPrint(s1);
            doPrint(s2);
            s1 = "is";
            s2 = new Integer(42);
        }
        System.out.println();
    }

    private static void doPrint(String s1) {
        System.out.print("Wrong!");
    }

    private static void doPrint(Object s1) {
        System.out.print(s1 + " ");
    }
}

Compile it with source/target level 1.1 without debug information (i.e. no local variable information should be present) and try to decompile it. I tried Jad, JD-GUI and Fernflower, and all of them got at least one of the call wrong (i. e. the program printed "Wrong!" at least once)

Is there really no java decompiler that can infer the right casts so that it will not call the wrong overload?

Edit: Target level 1.1 so that there is none of that Java6-specific fast-validation information present. That might give the decompiler a clue that s1 has been declared as Object and not as String. Decompilers should be able to decompile the code even without this information (not necessarily get the original variable type, but show the same behaviour), especially since lots of obfuscators strip it as well.

What decompilers got wrong:

  • They missed the cast to (Object) in the first call.
  • They inferred the type of s1 to be String, but forgot to add a cast to the call to doPrint (so that the String version is called instead of the Object version).
  • One crappy one (I have not even listed) even infers the type of s2 to be String, causing uncompilable code.

In any case, this code never calls the String overload, but the decompiled code did.

Answer

Stiver picture Stiver · Jun 10, 2010

Hallo mihi,

sorry for the late response. I'm copying my answer from http://www.reversed-java.com/fernflower/forum?threadfolder=2_DE

Your problem is actually a well known one. Let's see:

1) Pure bytecode doesn't contain any information about the type of object variables, so in the first pass s1 and s2 are declared as Object.

2) Decompiler is trying hard to assign the best possible type to each variable (= "narrowest type principle" implemented in Fernflower). So s1 and s2 are correctly identified as instances of String.

3) Invocation of doPrint give us a direct link to the correct method
private static void doPrint(Object s1)

4) Everything OK so far, right? Now we have got a String variable s1 passed to a function, which expects an Object. Do we need to cast it? Not as such, you would think, as Object is a super-type of String. And yet we do - because there is another function within the same class with the same name and a different parameter signature. So we need to analyse the whole class to find out, whether a cast is needed or not.

5) Generally speaking, it means we need to analyse ALL referenced classes in ALL libraries including the java runtime. A huge load of work! Indeed, this feature was implemented in some alpha version of Fernflower, but have not made it in the production yet because of performance and memory penalty. Other mentioned decompilers lack this ability by design.

Hope I have clarified things a bit :)