views:

98

answers:

2

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.

A: 

The JadClipse Eclipse plugin for decompiling also provides the JODE decompiler, which you may want to try. I use it when Jad gives up.

Also the Dava decompiler uses Soot which - last time i looked - was very ambitious in reconstructing the original Java code. I have not tried with your example, but you may want to have a look. http://www.sable.mcgill.ca/dava/

Thorbjørn Ravn Andersen
Dava failed like the others. Jode worked, but since Jode tends to crash on almost every program that ran through any obfuscator, this does not really help me :( Guess I asked the wrong question. ;)
mihi
Downvote _and_ accept the answer? That was new :)
Thorbjørn Ravn Andersen
+2  A: 

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 :)

Stiver
Yes, I know the general problem (that's why it was not hard for me to reduce a much longer class to the sample above). But, as I understand it, if you do not have class information about the target class, you can always add a (superfluous) cast to the exact type of the parameter, can't you? You can still use Eclipse's quickfix to remove all superfluous casts later.(i. e. I'd prefer correctness to beauty when decompiling code)
mihi
You are right, it could be done. The only downside would be that we generate some very ugly code with all these unnecessary casts. I'll implement an option for this behaviour, thank you for the suggestion.
Stiver