Decompilation Dava Test
Program-Transformation.Org: The Program Transformation Wiki
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