The test set was not specifically designed to validate or torture decompilers, and there is no way to know if the results here are representative of all classes, or if the list of problems encountered is complete. It should, however, give you some idea what to watch for.
I organized decompilation errors according to the taxonomy below, based on the general idea that easy-to-spot and easy-to-fix errors were less significant than hidden or hard to fix errors. The very worst thing a decompiler can do is produce code that passes through a compiler without complaint, but which is not functionally equivalent to the original code.
| Class 1 errors | Class 2 errors | Class 3 errors | Class 4 errors | Class 5 errors | Class 6 errors | |
| general description | flagged by compiler, easily fixed | flagged by compiler, not easily fixed. | Ugly, Incomprehinsible, but correct code. | Suble misprints. Subtly Incorrect programs | Total failure. | Gross errors Severly damaged semmantics No warning, and hard to identify |
| example | boolean variable incorrectly identified as int
missing, but trivial type cast. |
generating code containing goto | unreconstructed flow conrol
unreconstructed use of + for string append |
failing to use \ to escape characters in string constants
misprinting character constants |
crash without producing output | misuse or non-use of "this."
other patently incorrect code |
| Class 1 errors | Class 2 errors | Class 3 errors | Class 4 errors | Class 5 errors | Class 6 errors | |
| Mocha
version beta 1 |
a few | no | no | no | yes, mocha crashes on some class files. | no |
| WingDis
version 2.06 |
just one | no | overuse of if(x!=false) and similar construction | no | no | misuse or non-use of of "super."
mistranslation of x=a++; to a++; x=a; |
| DeJaVu
version 1.0 |
a few | no | major problem with flow analysis | yes | no | no |
Your mileage may vary.
PrintStream PrintStream()
{
return new PrintStream(outputstream, 1); // 1 should be true
}
public ConsoleWindow(String string, int i1)
{
dead = false;
styles = { "Plain", "Bold", "Italic" };
sizes = { "8", "9", "10", "12", "14", "16", "18", "24" };
...
Wingdis produced equally beautiful, syntactically correct, but semanticly
incorrect code for this, making a class 6 error.
On the other hand, DejaVu emitted perfecty legal, but ugly, code as follows:
public ConsoleWindow(String arg1, int arg2) {
...
String[] Har1;
Har1 = new String[3];
Har1[0] = "Plain";
Har1[1] = "Bold";
Har1[2] = "Italic";
this.styles = Har1;
...
DeJaVu sometimes emits correct but nearly incomprehensible code, see the class 3 section
All three programs were able to reconstruct simple loops quite well, but only mocha and wingdis were able to handle almost everything with equal grace. Dejavu frequently resorts to emitting correct, but nearly incomprehensible code dominated by switch statements.
| use of "A" + B to generate strings | simple loop reconstruction | |
| original |
public String toString ()
{
String myname = this.getName();
return("#<"
+ super.toString()
+ (myname!=null ? (" " + myname) : "" )
+ ">");
}
|
public static int LList_Length(LList l)
{ int len=0;
while(l!=null) { len++; l=l.next; }
return(len);
}
|
| mocha |
public String toString()
{
String string;
string = getName();
return "#<" + super.toString()
+ ((string != null)
? (" " + string)
: "")
+ ">";
}
|
public static int LList_Length(LList lList)
{
int i = 0;
for (; lList != null; lList = lList.next)
i++;
return i;
}
|
| wingdis |
public String toString() {
String Stri1= getName();
return "#<" + super.toString()
+ ( (Stri1 == null)
? ""
: new StringBuffer(" "
+ Stri1).toString() )
+ ">";
}
|
public static int LList_Length(
LList LLis0) {
int int1;
for (int1= 0; (LLis0!= null) ; LLis0= LLis0.next) {
int1++;
}
return int1;
}
|
| Dejavu |
public String toString() {
String obj;
StringBuffer Hobj1;
String Hobj;
obj = this.getName();
Hobj1 = new StringBuffer().append("#<")
.append(super.toString());
if (!(obj == null)) {
Hobj = new StringBuffer().append(" ")
.append(obj).toString();
}
else {
Hobj = "";
}
return Hobj1.append(Hobj).append(">").toString();
}
|
public static int LList_Length(LList arg0) {
int i;
i = 0;
while (arg0 != null) {
i++;
arg0 = arg0.next;
} /* end while loop */
return i;
}
|
//the original
if((ch == '\r') || (ch =='\n'))
{ charisready = true; break;
}
//Oops! character constants in this format are base 8, so should be '\15'
if (c == '\13' || !(c != '\n')) {
this.charisready = true;
}
public void run()
{
int target;
while((target = pickclass())<classlist.length)
{
try
{Class.forName(classlist[target]);
classes_to_go--;
}
catch (ClassNotFoundException err)
{classes_to_go--;
System.out.println("Class " + classlist[target]
+ " not found " + err.toString());
}
changeLabel();
}}
For exmaple, this:
public void Dispose()
{
super.Dispose();
}
became
public void Dispose()
{ Dispose(); //missing "super"
return;
}
Wingdis produced this beautiful, legal java code for a static initializer,
public ConsoleWindow(String string, int i1)
{
dead = false;
String [] styles = { "Plain", "Bold", "Italic" };
String [] sizes = { "8", "9", "10", "12", "14", "16", "18", "24" };
...
unfortunately, the desired effect of initializing the instance variables
"styles" and "sizes" are not preserved. One would have to also add a line
such as "this.styles=styles;"
Wingdis also has a problem reconstructing expressions which contained ++ and --, for example,
a[b++]=c;tends to become
b++; a[b]=c;which uses the wrong, already incremented, value of b.