Dava Java Decompiler Tests

These tests were performed on the Dava decompiler which comes with Soot 2.0.1. The author stated in early 2003 that there is a newer version, but it just needs to be merged with a recent version of Soot. It doesn't seem to be forthcoming. A few of the tests were re-run with Soot 2.1.0 (released Dec 2003); no differences were found compared with 2.0.1.

Fibo

For source, see DecompilerFiboTestSource. Decompiled output from Dava was:
import java.io.*;

class Fibo
{
    Fibo()
    {
        super();
        return;
    }

    private static int fib(int i0)
    {
        if (i0 <= 1)
        {
            return i0;
        }
        else
        {
            return Fibo.fib(i0 - 1) + Fibo.fib(i0 - 2);
        }
    }

    public static void main(java.lang.String[] r0) throws java.lang.Exception
    {
        int i0, i1;
        java.lang.Exception r1;
        i0 = 0;

        label_0:
        {
            try
            {
                i0 = Integer.parseInt(r0[0]);
            }
            catch (Exception $r3)
            {
                r1 = $r3;
                System.out.println("Input error");
                System.exit(1);
                break label_0;
            }
        }

        i1 = Fibo.fib(i0);
        System.out.println((new StringBuffer()).append("fibonacci(").append(i0)
.append(") = ").append(i1).toString());
        return;
    }
}
While difficult to read and verbose, it is correct and recompiles and runs without modification. Version 2.0.1 is even more verbose than 1.2.5, e.g. r1 = $r3;

Casting

For source, see DecompilerCastingTestSource. Here is Dava's output for main:

    public static void main(java.lang.String[] r0)
    {
        char c0;

        c0 = '\u0000';

        while (c0 < '\u0080')
        {
            System.out.println((new StringBuffer()).append("ascii ").append(c0)
.append(" character ").append(c0).toString());
            c0 = (char) (c0 + 1);
        }

        return;
    }
Apart from being hard to read, the cast to integer is missing, so the recompiled program has the wrong behaviour.

Inner classes

For source, see DecompilerInnerClassesTestSource. When decompiled with Dava, the result is

public class Usa
{
    public java.lang.String name;

    public Usa()
    {
        super();

        name = "Detroit";
        return;
    }
}
It has failed to reproduce the inner classes. However, when javac compiles Usa.java, it creates in addition to Usa.class the files Usa$England.class and Usa$England$Ireland.class. When the latter is decompiled with Dava, it produces Usa$England$Ireland.java, with this in it:
import java.io.*;
public class Usa$England$Ireland
{
    public java.lang.String name;
    private final Usa$England this$1;
    public Usa$England$Ireland(Usa$England r1)
    {
        super();
        this$1 = r1;
        name = "Dublin";
        return;
    }
    public void print_names()
    {
        System.out.println(name);
        return;
    }
}

This is a reasonable decompilation of what is literally contained in the associated .class file, but it does not compile with Sun's javac compiler (this$0 and this$1 are reserved for internal use).

Deuces Wild

I decided to try to decompile a moderately large .class file, deuceswild.class from http://www.thewizardofodds.com/java/deuceswild/deuceswild.html (the Wizard of Odds; thanks, Wiz!). The class file can be saved by accessing http://www.thewizardofodds.com/java/deuceswild/deuceswild.class in a web browser; it's about 38K.

First I tried

dava --app deuceswild

using the dava script mentioned above. It warned me that card is a phantom class (meaning that I didn't provide the source for it). No problem; I typed the URL http://www.thewizardofodds.com/java/deuceswild/card.class into my browser, and it dutifully downloaded it for me, asking me where to save it. The same thing happed for class cardeffects. Then it took a while, perhaps 3 minutes, to come up with java.lang.OutOfMemoryError. So I added -Xmx128M to the dava script, and tried again. This time, it succeeded, though it apparently decompiled every library function that was used. Decompiling the libraries can be avoided by using dava deuceswild; dava card; dava cardeffects instead of dava --app deuceswild.

An error involving the type of an internal temporary variable has been fixed in the verson of Dava that comes with soot 1.2.4. It compiled without error. The applet seemed to run perfectly. The source code was difficult to read in places, but then, evaluating the expected value of a hand in Video Poker is difficult and complex at the best of times. There is code like this where the decompiler could easily do a slightly better job, e.g.

        $r15 = new String[13];
        $r15[0] = "Two";
        $r15[1] = "Three";
        ...
        $r15[11] = "King";
        $r15[12] = "Ace";
        cardrank = $r15;
Here, the stack variable (as indicated by the $ in the name) could be eliminated, and the meaningfully named cardrank could be used instead.

In version 2.0.1, it no longer decompiles all the library files by default, but you have to decompile the card and cardeffects classes separately. Although the compiled .class file is larger than before, it compiled and ran with no modifications.

Sable test program

For source, see DecompilerSableTestSource. Here is the result for function f (as produced by Dava 1.2.5):

    public static void f(short s0)
    {
        boolean z0;
        Drawable r1;
        Rectangle $r3;
        Circle $r4;

        if (s0 <= 10)
        {
            $r4 = new Circle(s0);
            z0 = $r4.isFat();
            r1 = $r4;
        }
        else
        {
            $r3 = new Rectangle(s0, s0);
            z0 = $r3.isFat();
            r1 = $r3;
        }

        if (z0 == false)
        {
            r1.draw();
        }

        return;
    }
This result is correct and readable. The d variable of the original source code is here called r1 (first reference). Irritatingly, the call from main() to f() is with constant 11, which lacks a cast to short. However, this is not part of the current test. The output for version 2.0.1 is similar, with different variable names.

Optimised bytecode

For source, see DecompilerOptimisedTestSource. This was the result from Dava 1.2.5 for the method f:

    public static void f(short s0)
    {
        Rectangle r0;
        boolean z0;
        Drawable r1;
        Circle r2;

        if (s0 <= 10)
        {
            r2 = new Circle(s0);
            z0 = r2.isFat();
            r1 = r2;
        }
        else
        {
            r0 = new Rectangle(s0, s0);
            z0 = r0.isFat();
            r1 = r0;
        }

        if (z0 == false)
        {
            r1.draw();
        }

        return;
    }
This is essentially the same code as the unoptimised version, and is correct. All seven other decompilers tested here fail this test. In the "Pitfalls" paper, the authors report that Wingsoft also fails this test.

Simple control flow

For source, see DecompilerControlFlowTestSource. Dava 1.2.5 produces:

    public int foo(int i0, int i1)
    {
        int $i2;

        label_0:
        while (true)
        {
            try
            {
                if (i0 < i1)
                {
                    $i2 = i1;
                    i1 = i1 + 1;
                    i0 = $i2 / i0;
                    continue label_0;
                }
            }
            catch (RuntimeException $r2)
            {
                i0 = 10;
                continue label_0;
            }

            return i1;
        }
    }
It's a pity the continue is labelled; it is not needed. (In the example in the paper above, there is no label). It's also a shame that the i = j++/i is split over three lines. However, the code is correct and compiles without modification. Output for version 2.0.1 is the same, with some extra exception code.

Exceptions

For source, see DecompilerExceptionTestSource. Unfortunately, Dava failed for me with a null pointer exception. In the paper, they report Dava producing correct code, handling cases b, c, and d separately with three try statements. The exception code (blocks g and e) are replicated, and the goto to block f is accomplished with a labelled break statement. The new version of Dava is expected to correct this problem. The exception still occurs in version 2.0.1.

Life

This program was originally written in Ada 95. The applet was at http://www.appletmagic.com/download/demo/LifeRect.html; you may be able to get it now at http://www.inmet.com/javadir/download/demo/LifeRect.html. This is a beautiful implementation of Conway's Game of Life. You can see it running and read the source code at the above URL. During the decompilation of the whole program (about 12 class files), there were 4 runtime exceptions: two null pointer exceptions, and 2 class cast exceptions. The first two errors occured during the decompilation of classes LifeRect (the top level class), and LR_lifeCol. Unfortunately, the results that were produced did not compile; the first error was with this code in LR_Colony.java:

            r15 = new LR_Colony;
            $r2 = r15;
            r15.<init>();
This sort of code is mentioned proudly in the McGill paper on type inferencing; they use their second stage analysis to solve this typing problem. Unfortunately, while pretty pseudo code, it isn't Java. When I edited the above to
            r15 = new LR_Colony();
            $r2 = r15;
the Java compiler seemed to want to compile another file, which had import .*;. When that was commented out, there was another error similar to the one with r15 above.

In version 2.0.1 of Dava, the missing file causes an error that I could not work around, as per earlier versions.


CategoryDecompilation

Revision: r1.5 - 06 Jan 2004 - 07:11 - MikeVanEmmerik
Copyright © 1999-2020 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback