Java Byte Code de Compiler Survey Extended
Java Byte Code de Compiler Survey Extended
This paper is a revised, updated and extended version of a previously published paper [Hamil-
ton and Danicic(2009)] which summarised our results. This extended paper provides a more
in-depth analysis of the results of our tests and encourages the reader to study the quality of
output from the latest versions of various decompilers available.
James Hamilton
Department of Computing, Goldsmiths, University of London, New Cross, London, SE14 6NW,
United Kingdom
E-mail: [email protected]
Sebastian Danicic
Department of Computing, Goldsmiths, University of London, New Cross, London, SE14 6NW,
United Kingdom
E-mail: [email protected]
2
1 Introduction
1 This figure is disputed, by some, as the report’s conclusions are based on mathematical
1.1 Background
The Java Virtual Machine is essentially a simple stack based machine which can be
separated into five parts: the heap, program counter registers, method area, Java
stacks and native method stacks [Venners(1996)] . The Java Virtual Machine Spec-
ification [Lindholm and Yellin(1999)] defines the required behaviour of a Java virtual
machine but does not specify any implementation details. Therefore the implemen-
tation of the Java Virtual Machine Specification can be designed different ways for
different platforms as long as it adheres to the specification. A Java Virtual Machine
executes Java bytecode in class files conforming to the class file specification which is
part of the Java Virtual Machine Specification [Lindholm and Yellin(1999)] and up-
dated for Java 1.6 in JSR202 [Buckley et al(2006)Buckley, Rose, Coglio, Corporation,
Sun Microsystems, Tmax Soft, Technologies, and AG].
An advantage of the virtual machine architecture is portability - any machine
that implements the Java Virtual Machine Specification [Lindholm and Yellin(1999)]
is able to execute Java bytecode hence the slogan “Write once, run anywhere” [Kramer
et al(1996)Kramer, Joy, and Spenhoff]. Java bytecode is not strictly linked to the Java
language and there are many compilers, and other tools, available which produce Java
bytecode [Tolksdorf(2010),Gough and Corney(2001)] such as the Jikes compiler, Eclipse
Java Development Tools or Jasmin bytecode assembler.
Another advantage of the Java Virtual Machine is the runtime type-safefy of pro-
grams. These two main advantages are properties of the Java Virtual Machine not
the Java language [Gough and Corney(2001)] which, combined, provides an attractive
platform for other languages.
Java bytecode can be generated in three ways:
1. from a Java source program using a Java compiler (such as Sun’s javac),
2. using a language other than Java to Java bytecode compiler (such as JGNAT
[jgn(2009)] - an open-source Ada to Java bytecode compiler) or
3. by writing a class file by hand.
The tedious task of hand-writing a Java class file can be made easier by using
a Java assembler, such as Jasmin [Meyer et al(2004)Meyer, Reynaud, Kharon et al],
which accepts a human readable form of Java bytecode instructions and generates a
Java class file. We use Jasmin source in this paper for examples programs which are
written manually, or for examples which require inspection of the bytecode.
Java bytecode can also be manipulated by tools such as obfuscators and optimisers
which perform semantics preserving transformations on bytecode contained within a
Java class file. Figure 1 shows the Java bytecode cycle from generation to decompilation
to Java source.
Java bytecode retains type information about fields, method returns and parame-
ters but it does not, for example, contain type information for local variables. The type
information in the Java class file renders the task of decompilation of bytecode eas-
ier than decompilation of machine code [Emmerik(2007)]. Decompiling Java bytecode,
thus, requires analysis of most local variable types, flattening of stack-based instruc-
tions and structuring of loops and conditionals. The task of bytecode decompilation,
however, is much harder than compilation. We show that often decompilers cannot
fully perform their intended function [Cifuentes(1994)].
4
1.1.2 Decompilation
2 http://www.sable.mcgill.ca/
6
As an example javac produces bytecode which leaves the stack height the same
before and after any statement. This is not a requirement of the Verifier and any
classfiles which do not follow this break certain decompilers [Emmerik(2007)]. Some
of the early decompilers are fooled by the insertion of a pop opcode at the end of a
method as this is unexpected even though it passes the Java Verifier.
One source of the problem of arbitrary bytecode is that the class file format is
entirely independent of the Java language and bytecode is more powerful than the
Java language [Ladue(1997)]. For example in the Java Virtual Machine specification
[Lindholm and Yellin(1999)] in relation to exception handling it lists the conditions
with which javac produces exception handling bytecode but it notes that there are no
restrictions enforced by the Verifier to check these conditions and suggests this does
not pose a threat to the integrity of the Java Virtual Machine. Therefore unexpected
exception handling code can be written and still pass the verification process.
Tools such as bytecode optimisers and code obfuscation change bytecode which
can result in verificable bytecode which doesn’t easily translate to syntactically correct
Java source code.
The decompilation of Java bytecode involves transforming the low-level, stack based
bytecode instructions into high-level Java source. There are several main problems
[Miecznikowski and Hendren(2002), Emmerik(2007)] to solve in the decompilation of
Java bytecode:
– Data is already separated from code as all the data is separately stored a class file’s
constant pool.
– Separating pointers (references in Java) from constants is easy (e.g. some opcodes
such as aload work with references whereas iconst works with constant integers).
– Method parameter, method return and field types are stored in the class file.
– There is no need to decode global data since everything is stored within class files.
7
In this section, we present a step by step decompilation of the Jasmin code in listing
1 using a stack evaluation method and taking advantage of Java to Java bytecode
patterns.
Firstly, we can see that the method signature, public static sum([I)V, is similar
to Java source: it is public, static, the name of the method is sum and the parameters
are shown in parenthesis. However, the syntax for parameters is not the same as Java,
and the method return type is at the end of the signature.
In Java bytecode the I denotes an integer type and the square bracket preceding
it declares a one dimensional array; notice also, that the parameter does not have a
name. The capital V denotes the void return type. So we can deduce that the method
signature in Java source will be (we’ve made up the variable name):
The next two lines are instructions for the compiler - the maximum stack height
and the number of local variables that this method uses.
The first bytecode instruction, iconst_0, pushes a 0 onto the stack and the next
bytecode instruction, istore_1, pops an integer from the stack and stores it in local
variable slot 1. Local variable slot 0 contains the parameter array (if this was an instance
method local variable slot 0 would contain a reference to the instance of the class i.e.
the this variable in Java). This combination can be thought of as assigning the value
of 0 to an integer variable in Java; we must also declare the variable if it hasn’t already
been declared:
int total = 0;
Similarly, the next two bytecode instructions declare another integer variable and
assign it the value 0:
int i = 0;
The next three instructions can be considered together: iload_2, aload_0 and
arraylength. When we’re decompiling a program we can use the stack to build up
expressions by pushing and popping the variables and expressions, instead of their
values. The first instruction pushes the integer in local variable slot 2 onto the stack,
the second pushes the parameter array onto the stack. So after these instructions our
expression stack is:
numbers
total
Next, the arraylength instruction pops an array from the stack and pushes it’s
length. We pop the numbers variable from our expression stack and push the numbers.length
expression:
numbers.length
total
the bytecode condition transfers control flow to the end of the loop if the condition is
true; however, we want the loop to start at the beginning. Our condition is therefore
i < numbers.length.
The next 3 instructions push the values of the 3 variables in our method onto the
stack; we push the variables onto our expression stack:
i
numbers
total
The next instruction, iaload, pops an integer i and an array reference arr from
the stack; it then pushes the value at index i in the array arr onto the stack. We push
the expression numbers[i] onto our expression stack:
numbers[i]
total
Next, the instruction iadd pops two integers from the stack and pushes their sum
back onto the stack. We therefore pop our two variables from our expression stack and
push the expression total + numbers[i].
The following instruction, istore_1, pops an integer from the stack and stores it
in local variable slot 1. We pop an expression from our expression stack and assign it
to our variable total.
total = total + numbers[i];
The last instruction within the loop is an integer increment instruction iinc 2 1 -
it increments the integer value in local variable slot 2 by 1. This is equivalent to i++.
So the entire while loop looks like this:
int i = 0;
while(i < numbers.length) {
total = total + numbers[i];
i++;
}
10
After the loop, the first instruction is iload_1 which pushes the value in local
variable slot 1 onto the stack; we push the variable onto our expression stack:
total
Next, we have another condition jump instruction, ifge d, which pops an integer
value from the stack and jumps to the specified label if the value is greater than or
equal to zero. This time we are not dealing with a while loop; we know this because the
goto instruction preceding the jump target is a forward jump, rather than a backward
jump. Therefore, the code between the conditional jump and the goto is the if’s then;
the code from the goto until the goto’s jump target is the else. Again, we must invert
the conditional giving us the condition total < 0.
The first instruction in the body of the then clause is getstatic which pushes a ref-
erence to the specified static field onto the stack - in this case the field java.lang.System.out.
The next instruction, ldc, pushes a constant onto the stack. Our expression stack there-
fore looks like this:
“total is less than zero :(”
java.lang.System.out
The else clause is similar to the then clause but we build up a longer string using a
java.lang.StringBuilder instance. The first instruction, again, pushes java.lang.System.out
onto the stack. The next instruction is new which creates an instance of the specified
11
class and pushes a reference to the instance onto the stack. This is followed by dup
which duplicates the item at the top of the stack, giving us an expression stack like
this:
new java.lang.StringBuilder()
new java.lang.StringBuilder()
java.lang.System.out
“total is”
new java.lang.StringBuilder()
java.lang.System.out
The invokevirtual instruction then pops the string from the stack, followed by the
java.lang.StringBuilder instance and invokes the public StringBuilder java.lang.StringBuilder.append(String s)
method; it pushes the result back onto the stack:
Then iload_1 is used to push the integer value in local variable slot 1 onto the stack.
In our case, we push the variable total onto the stack and then use invokevirtual to
call the append method again:
Putting all this together gives us the Java code in listing 2. We can tidy the code up
slightly by removing java.lang., converting the while loop to a for loop and turning
the StringBuilder into standard string concatenation to obtain listing 3.
int i = 0;
while ( i < numbers . length ) {
total = total + numbers [ i ];
i ++;
}
if ( total < 0) {
java . lang . System . out . println (" total is less than zero :(") ;
} else {
java . lang . System . out ( new java . lang . StringBuilder () . append (" total is ")
. append ( total ) . toString () ) ;
}
}
12
if ( total < 0) {
System . out . println (" total is less than zero :(") ;
} else {
System . out . println (" total is " + total ) ;
}
}
13
The currently available decompilers are summarised in figure 7 and explained further
in this section.
1.4 Mocha
Mocha [moc(1996)], released as a beta version in 1996, was one of the first decompilers
available for Java along with a companion obfuscator named Crema. Mocha can only
decompile earlier versions of Java as it is an old program. Mocha is obsolete but is still
available on several websites as the original license permitted its free distribution. The
product was discontinued and never made it out of beta.
1.6 SourceAgain
1.7 ClassCracker3
1.8 Jad
Jad [Kouznetsov(2001)] is a popular decompiler that is free for non-commercial use but
is no longer maintained3 . It is a closed source program written in C. The last update
for Linux and Windows version for Jad was in 2001, while a small update added an
OS X version in 2006. Jad is used as the back-end by many decompiler GUIs including
an Eclipse IDE plug-in named JadClipse [Grishchenko and Gyger(2009)].
1.9 JODE
1.10 jReversePro
1.11 Dava
1.12 jdec
from http://www.varaneckas.com/jad
4 http://www.sable.mcgill.ca/
15
NMI Code Viewer was included in the original survey with results ‘startlingly similar’
to Jad [Emmerik(2003)]. We do not include this (unmaintained) decompiler as, in
actual fact, it is a front-end for Jad [Kouznetsov(2001)].
1.15 jAscii
jAscii was included in the original survey, with poor results [Emmerik(2003)], but it is
now obsolete and unavailable for our evaluation.
16
2.1 Casting
The program in listing 4 shows an example in which some decompilers omit the int
typecast [Emmerik(2003)].
System . out . println (" ascii code for " + c + " is " + ( int ) c ) ;
}
The implementation of inner classes in Java is handled by the compiler - the Java
Virtual Machine has no concept of inner classes. The Java compiler converts the inner
classes in a Java source file into separate class files therefore a decompiler needs to be
able to reconstruct the original class file from several separate class files.
If the type inference algorithm is optimised for the common-case rather than the
worst case it is still possible to perform type analysis for most real-world code as worst-
case scenerios are unlikely [Bellamy et al(2008)Bellamy, Avgustinov, de Moor, and
Sereni]. Bellamy et al. [Bellamy et al(2008)Bellamy, Avgustinov, de Moor, and Sereni]
provide a common-case optimised algorithm to perform type analysis which outper-
forms Gangon et al.’s [Gagnon et al(2000)Gagnon, Hendren, and Marceau] worst-case
optimised algorithm. The algorithm relies on the assumption that multiple inheritance,
via interfaces, is rarely used in real-world Java programs which could prove a problem
if their assumption is false, or becomes false in the future.
Java’s restricted form of multiple inheritance, interfaces, leads to problems in find-
ing a static type in some cases where the code is valid but not generated directly from
Java source [Gagnon et al(2000)Gagnon, Hendren, and Marceau]. This causes prob-
lems for some decompilers which expect javac generated code. Using an static single
assignment form, as in [Knoblock and Rehof(2001)], may change the type hierarchy
when types conflict due to interfaces [Miecznikowski and Hendren(2002)].
Listing 5, page 18 (from [Strk et al(2001)Strk, Schmid, and Brger]) shows a program
which some decompilers may decompile better than others. If a decompiler does not
perform type inference it will type x as Object and insert a typecast in the invocation
of method m2(Comparable) whereas others may correctly type x as Comparable. The
Java Virtual Machine Specification states that “the merged state contains a reference to
an instance of the first common superclass of the two types” in regards to the bytecode
verifier - the first common superclass of Integer and String is Object. Therefore the
verifier has to insert a run-time check to ensure that both Integer and String are of
type Comparable.
The goto statement is found in many programming languages which causes the execu-
tion of a program to jump to another position, usually labelled with an identifier or a
number depending on the language. Java bytecode has two forms of goto: conditional
and unconditional.
The Java language has restricted variants of goto in the form of the break and con-
tinue statements. The break statement is either labelled or unlabelled. The unlabelled
form can be used to terminate a loop or to terminate case statements within a switch
block. A labelled break statement terminates the labelled statement, such as a for-loop,
and is useful when using nested loops to break an outer loop.
Java switch statements are effectively multi-way goto statements where the exe-
cution flow is changed based on an expression and possible values of the evaluated
expression.
Due to the arbitrary control flow in Java bytecode and the more restricted high-
level constructs in Java it can be difficult to translate Java bytecode program into
Java.
Ramshaw’s goto elmination technique can be used to replace gotos in a program, if
and only if the control flow graph is reducible, by replacing them with multi-level loop
exit statements [Ramshaw(1988)]. The Krakatoa decompiler uses an extended version
of Ramshaw’s goto elimination technique [Proebsting and Watterson(1997)].
Java bytecode can contain non-reducible control flow which cannot be represented
in Java source requiring techniques such as described in [Janssen and Corporaal(1997)]
18
if ( i != null )
x = i;
else
x = s;
m2 ( x ) ;
}
// bytecode :
2.5 Exceptions
Fig. 8: Java Exception Hierarchy. Unchecked exception classes are coloured grey.
allow programmers to catch errors from which recovery is usually possible (Exception)
and not require the inclusion of extra code to catch errors from which recovery is
usually impossible (Error). Figure 8, page 19 shows the Java exception hierarchy.
Figure 6, page 19 shows a try-catch block with a call to the method Integer.parseInt
that takes a Unicode string and returns it’s int value or throws a NumberFormatExcep-
tion if the string does not represent an integer. If an exception is thrown control passes
to the catch block corresponding to the class, or a super-class, of the exception thrown.
The first catch clause, in order of appearance in the source file with a matching type
is executed - in this case if s is not an integer the message “Enter an integer” will be
displayed. The order of catch clauses is important because more than one catch clause
could handle the same exception. For example the clause catch(Exception e) {...} can
also handle the NumberFormatException as Exception is a superclass; subclass catch
clauses must precede superclass catch clauses.
Programmers can define their own exceptions by extending Throwable or any of its
subclasses. The Java Language Specification [Gosling et al(2000)Gosling, Joy, Steele,
and Bracha] recommends that all user defined exceptions extend Exception rather than
Throwable or RuntimeException because Exception and its subclasses are checked
exceptions whereas RuntimeException and its subclasses are unchecked.
A Java compiler ensures that a program contains handlers for checked exceptions
during compilation so for each method which could possibly throw a checked exception
there must be a handler or the method must declare that it itself throws the exception.
20
try {
FileReader f = getFil eReader 1 ( args [0]) ;
} catch ( F i l e N o t F o u n d E x c e p t i o n e ) {
// handle file not found exception
}
Programmers can throw exceptions themselves using the throw keyword. Listing 8,
page 21 shows a method yesOrNo which takes a string and returns true if the string
contains ‘yes’ and false if the string contains ‘no’ (including combinations of upper
and lower case letters). If the string contains anything else WrongInputException is
thrown.
WrongInputException is a subclass of Exception which means that it is a checked
exception therefore when the yesOrNo method is invoked an exception handler must
be used or the calling method must declare that it throws a WrongInputException.
Fig. 10: Java bytecode control flow graph for listing 6, page 19 with highlighted try-
catch sections.
An exception is thrown in bytecode using the athrow opcode which pops a Throw-
able object from the stack and throws it.
23
2.5.1 Try-Finally
The finally clause of a try statement is guaranteed to execute even if the try block
completes abruptly. The subroutine for the finally clause is invoked at each exit point
of a try block and its associated catch block. The finally clause allows ‘clean-up’ code
to be executed such as closing of database connections or deleting temporary files, even
if the try block completes abruptly. The finally clause is executed after the try block
completes normally and also if the try block exits with a return, break or throwing of
an exception.
Many of the old decompilers expect the use of subroutines for try-finally blocks but
javac 1.4.2+ generates in-line code instead. An arbitrary decompiler, such as Dava,
may also have problems decompiling this simple program due to the way it analyses
bytecode for decompilation.
Java bytecode subroutines, like subroutines in any other programming language,
were designed to allow code re-use for the implementation of finally clauses. For each
exit point in a try clause the same finally block must be executed which seems to suggest
subroutines would be a good idea to implement this. Java 1.4.2 and above repeat the
finally clause bytecode at every try exit point rather than using subroutines. This
has the disadvantage that the code size is much bigger than when using subroutines
but data flow analysis is much simplified [Freund(1998)]. It also means that bytecode
verification is simplified as the verifier performs a form of data flow analysis on the
bytecode. However, bytecode produced by javac <1.4.2 and other compilers could still
contain subroutines.
The jsr opcode takes a two-byte operand indicating the offset from the jsr instruc-
tion to the subroutine. When the jsr opcode is executed the address of the next opcode
is pushed onto the stack and control is passed to the jsr specified by the operand.
The first instruction of a finally clause pops the stack and stores the return address
(bytecode type returnAddress) in a local variable.
The ret opcode takes one operand - the index of the local variable slot where the
return address is stored - execution then continues at the address loaded from this local
variable slot.
It is possible for a local variable slot to take values of distinct types at different places
in a method [Gagnon et al(2000)Gagnon, Hendren, and Marceau] which is not possible
in Java. For example listing 9 shows a simple valid method in which local variable 0 is
used to store an integer then a String. At byte 1 local variable 0 contains a variable of
type integer but at byte 3 it contains an object reference type. This is perfectly valid
bytecode and is akin to the declaration of an integer followed by the declaration of a
String in Java, but in the bytecode these two distinct variables are using the same local
variable slot.
Multiple types for local variables is valid bytecode as long as the lifetimes of the
two uses of the local variable does not overlap [Knoblock and Rehof(2001)] i.e. a local
variable only has the type (and value) of the last variable stored in it.
This may be overcome by converting the bytecode to static single assignment form
[Alpern et al(1988)Alpern, Wegman, and Zadeck, Rosen et al(1988)Rosen, Wegman,
and Zadeck, Cytron et al(1989)Cytron, Ferrante, Rosen, Wegman, and Zadeck] where
24
all static assignments to a variable will have unique names. The possibility of multiple
types for a single local variable slot causes a problem for decompilation as each variable
needs a single type which is valid for all uses of that variable.
Arbitrary bytecode may differ from compiler generated bytecode and may code that
isn’t easily translatable into Java source. In most cases this might not be a problem;
for example, is there really a need for decompiling to Java and program compiled
from Ada. However, optimising a javac generated class file turns the bytecode into a
form of arbitrary code - that is, the optimisations transform the code in ways which
decompilers don’t expect.
25
3 Empirical Evaluation
The test programs were taken from sources dealing with different problem areas of
bytecode decompilation and they provide interesting problems for decompilers.
The original survey showed that different decompilers are sometimes better in dif-
ferent areas but no decompiler passed all the tests [Emmerik(2003)]. Of the decom-
pilers tested in the original survey, JODE [Hoenicke(2004)] performed the best by
correctly decompiling 6 out of the 9 test programs, with Dava [Miecznikowski(2003)]
and Jad [Kouznetsov(2001)] close behind.
In our tests we include two types of Java class file: those generated by javac and
those generated by other tools, which will be refered to as arbitrary bytecode class
files. Java source files are compiled with javac version 1.6.0 10. Arbitrary Java class
files include two hand-written using the Jasmin assembler version 2.1, one optimised
using the Soot Framework [Valle-Rai et al(1999)Valle-Rai, Co, Gagnon, Hendren, Lam,
and Sundaresan] and one compiled by JGNAT [jgn(2009)].
3.1.1 Fibo
Fibo (Listing 10, page 26) is a fairly simple program to output the Fibonacci number
of a given input number. It should be a trivial test for a decompiler but contains many
of the basic Java language constructs. The program tests decompilation of basic Java
language constructs such as if statements, methods declarations & invocations and
exception handling.
3.1.2 Casting
try {
number = Integer . parseInt ( args [0]) ;
} catch ( Exception e ) {
System . out . println (" Input error ") ;
System . exit (1) ;
}
value = fib ( number ) ;
System . out . println (" fibonacci (" + number + ") = " + value ) ;
}
3.1.3 Usa
Usa [Nolan(2004)] is a simple program (Listing 13, page 28) containing inner classes.
The implementation of inner classes in Java is handled by the compiler - the Java
Virtual Machine has no concept of inner classes. The Java compiler converts the inner
classes in a Java source file into separate class files. For example, the Usa test program’s
main class is called Usa and it has an inner class called England - the Java compiler
generates a class named Usa$England for the inner class and the inner class contains a
private variable this$0 which is a reference to the outer class. Some decompilers in the
original survey [Emmerik(2003)] could not reconstruct the original Java source from
the constituent class files.
3.1.4 Sable
. bytecode 50.0
. source < Unknown >
. class public Casting
. super java / lang / Object
aload_0
invokespecial java / lang / Object / < init >() V
return
. end method
Label2 :
iconst_0
istore_1
Label1 :
iload_1
sipush 128
if_icmpge Label0
getstatic java . lang . System . out Ljava / io / PrintStream ;
new java / lang / StringBuilder
dup
invokespecial java / lang / StringBuilder / < init >() V
ldc " ascii "
invokevirtual java / lang / StringBuilder / append ( Ljava / lang / String ;) Ljava
/ lang / StringBuilder ;
iload_1
invokevirtual java / lang / StringBuilder / append ( I ) Ljava / lang /
StringBuilder ; ; #1
ldc " character "
invokevirtual java / lang / StringBuilder / append ( Ljava / lang / String ;) Ljava
/ lang / StringBuilder ;
iload_1
invokevirtual java / lang / StringBuilder / append ( C ) Ljava / lang /
StringBuilder ; ; #2
invokevirtual java / lang / StringBuilder / toString () Ljava / lang / String ;
invokevirtual java / io / PrintStream / println ( Ljava / lang / String ;) V
iload_1
iconst_1
iadd
i2c
istore_1
goto Label1
Label0 :
return
. end method
28
Fig. 11: Type hierarchy for the type inference test program. Drawable is the lowest
common ancestor of Circle and Rectangle.
lowest common ancestor of Circle and Rectangle (see Figure 11, page 28). The original
paper [Miecznikowski and Hendren(2002)], written by the developers of Dava, tested
three different decompilers with the Sable test program, against Dava. They showed
that their decompiler could correctly type variable d whereas the other decompilers
failed to do so. Some decompilers do not perform type inference but insert a typecast
where necessary in order to produce a semantically equivalent program [Miecznikowski
and Hendren(2002), Emmerik(2003)].
3.1.5 TryFinally
TryFinally is a simple test program (Listing 18, page 30) to determine whether decom-
pilers can decompile the implementation of try-finally blocks using in-line code instead
of Java bytecode subroutines.
Listing 19, page 30 shows the Jasmin source for the TryFinally test program. Sec-
tions b and d contain duplicate code which is the finally clause of the TryFinally block.
Compare this with listing 20, page 31 which shows the Jasmin source for the TryFinally
test program compiled with Jikes. This version uses a Java subroutine (section d) to
implement the finally clause of the test program, which is invoked from sections b and
e.
In our tests, the decompiled TryFinally test programs must use a try-finally block
to be classified as correct, rather than relying on a combination of try-catch and in-lined
finally clause code.
29
if ( i > 10) { // 6
r = new Rectangle (i , i ) ; // 7
is_fat = r . isFat () ; // 8
d = r; // 9
}
else {
c = new Circle ( i ) ; // 12
is_fat = c . isFat () ; // 13
d = c; // 14
}
if (! is_fat ) d . draw () ; // 16
} // 17
0: aload_0
1: invokespecial java / lang / Object / < init >() V
4: return
. end method
. bytecode 48.0
. source < Unknown >
. class public TryFinally
. super java / lang / Object
a:
getstatic java . lang . System . out Ljava / io / PrintStream ;
ldc " try "
invokevirtual java / io / PrintStream / println ( Ljava / lang / String ;) V
goto e
b:
astore_1
jsr d
c:
aload_1
athrow
d:
astore_2
getstatic java . lang . System . out Ljava / io / PrintStream ;
ldc " finally "
invokevirtual java / io / PrintStream / println ( Ljava / lang / String ;) V
ret 2
e:
jsr d
f:
return
. end method
aload_0
invokespecial java / lang / Object / < init >() V
return
. end method
3.1.6 ControlFlow
} catch ( R u n t i m e E x c e p t i o n re ) {
i = 10;
continue ;
}
break ;
}
return j ;
}
Fig. 12: Control flow graph for the control flow program.
inner loop throws a RuntimeException then the outer loop continues otherwise the
outer loop is broken.
3.1.7 Exceptions
The Exceptions test program [Miecznikowski and Hendren(2002)] contains two inter-
secting try-catch blocks. The intersecting try-catch block is allowed in Java bytecode
33
. bytecode 50.0
. source < Unknown >
. class public ControlFlow
. super java / lang / Object
aload_0
invokespecial java / lang / Object / < init >() V
return
. end method
. end method
34
Fig. 13: Control flow graph for the exceptions test program [Miecznikowski and Hen-
dren(2002)]. Solid edges indicate normal control flow while dashed edges represent
exception control flow.
but would not be generated by a Java compiler - the program here is created using Jas-
min. The program used in the original survey [Emmerik(2003)] is incorrect and Dava,
which should be able to decompile the program, exits with a null pointer exception. A
re-written version (Listing 23, page 34) is used in our tests based on the call graph in
the original paper [Miecznikowski and Hendren(2002)]. Figure 13, page 34 shows the
program’s control flow graph and outlines the try-catch blocks.
3.1.8 Optimised
a:
getstatic java / lang / System / out Ljava / io / PrintStream ;
ldc " a "
invokevirtual java / io / PrintStream / println ( Ljava / lang / String ;) V
b:
getstatic java / lang / System / out Ljava / io / PrintStream ;
ldc " b "
invokevirtual java / io / PrintStream / println ( Ljava / lang / String ;) V
c:
getstatic java / lang / System / out Ljava / io / PrintStream ;
ldc " c "
invokevirtual java / io / PrintStream / println ( Ljava / lang / String ;) V
d:
getstatic java / lang / System / out Ljava / io / PrintStream ;
ldc " d "
invokevirtual java / io / PrintStream / println ( Ljava / lang / String ;) V
f:
getstatic java / lang / System / out Ljava / io / PrintStream ;
ldc " f "
invokevirtual java / io / PrintStream / println ( Ljava / lang / String ;) V
return
; catch blocks
e:
astore_0
getstatic java / lang / System / out Ljava / io / PrintStream ;
ldc " e "
invokevirtual java / io / PrintStream / println ( Ljava / lang / String ;) V
goto f
g:
astore_0
getstatic java / lang / System / out Ljava / io / PrintStream ;
ldc " g "
invokevirtual java / io / PrintStream / println ( Ljava / lang / String ;) V
goto f
. end method
36
0: aload_0
1: invokespecial java / lang / Object / < init >() V
4: return
. end method
. end method
0: bipush 11
2: invokestatic Sable / f ( S ) V
5: return
. end method
37
Listing 25: Jasmin source for the Optimised (TypeInference) test program
. bytecode 46.0
. source Optimised . java
. class public Optimised
. super java / lang / Object
0: aload_0
1: invokespecial java / lang / Object / < init >() V
4: return
. end method
. end method
0: bipush 11
2: invokestatic Optimised / f ( S ) V
5: return
. end method
38
iconst_0
istore_0
return
. end method
3.1.9 Args
Args (listing 26, page 38) re-uses the local variable slot 0 in the main method. At the
start of the method local variable slot 0 is of type String[] but it is then re-used as
type int.
3.1.10 connectfour
The effectiveness of a Java decompiler, depends heavily on how the bytecode was pro-
duced. Arbitrary bytecode can contain instruction sequences for which there is no valid
Java source due to the more powerful and less-restrictive nature of Java bytecode. For
example there are no arbitrary control flow instructions in Java but there are in Java
bytecode. In fact, many Java bytecode obfuscators rely on the fact that most decom-
pilers fail when encountering unexpected, but valid, bytecode sequences [Batchelder
and Hendren(2007)].
39
hard to read. Category 2 and higer programs are harder to understand as they are
syntactically and/or semantically incorrect.
41
Fig. 15: Decompiler Test Results: Each decompiler was tested in different problem
areas, with 6 test programs representing javac generated bytecode and 4 representing
arbitrary bytecode. The results are given using our effectiveness measurement scale
with 0 being a perfect decompilation and 9 being the case in which a decompiler
fails. No decompiler was able to correctly decompile all our test programs with JODE
correctly decompiling the most correctly.
No decompiler was able to decompile all test programs with JODE decompiling the
most programs correctly. JODE only managed to decompile 5 programs while four
(unmaintained) decompilers could not decompile any of the test programs correctly.
The top four decompilers (excluding obsolete SourceAgain) were Java Decompiler,
JODE, Dava and Jad whereas the worst decompilers were the unmaintained commercial
decompilers - SourceTec, ClassCracker3 and Mocha. Some decompilers failed simply
because they could not parse the latest class files or arbitrary class files.
Dava, surprisingly, was better at decompiling the javac bytecode test programs than
the arbitrary test programs. Dava is the joint second best (with JODE) based on our
effectiveness measures (excluding SourceAgain), but one of the best arbitrary bytecode
decompilers along with Java Decompiler - Java Decompiler slighlty outperforms Dava
in the arbitrary bytecode tests. Dava performs similarly to jdec with javac generated
bytecode, both decompiling two of these correctly. Overall Dava decompiles correctly
twice the number of programs that jdec decompiles.
Java Decompiler scored the highest using our effectiveness measures, beating Jad
and JODE by performing slightly better at decompiling the arbitrary bytecode pro-
grams. JODE was able to decompile one more program correctly than Java Decompiler.
JODE was the best decompiler in the original survey and is still one of the best in our
evaluation but Java Decompiler beats JODE with our effectiveness measures and only
decompiles one less program correctly than JODE.
Every decompiler that could parse the latest Java class file format could decompile
the Fibonacci test program. The decompilers showed varying degrees of success with
the other programs; we discuss the results here.
Figure 15, page 41 shows a table of raw results detailing our effectivess score given
to each decompiler for each program. Figures 16(a) and 16(b), page 42 show the the
decompiler effectiveness scores. The charts show percentages of effectiveness with higher
percentage meaning a higher effectiveness score, calculated from table 15, page 41.
42
4 Discussion of Results
In this section we discuss the results of the tests, explaining the output of the decom-
pilers when they failed to decompile a program. Full program listings of the decompiled
programs are in appendix A, beginning on page 59.
4.1 ClassCracker3
ClassCracker3 did not decompile any of our test programs completely with just the
method signatures in the resulting Java source file. The original survey found a similar
result and the author concluded that the decompiler ‘needs some work to cater for the
tricky cases covered in these tests’ [Emmerik(2003)].
4.2 Dava
Dava, being a decompiler aimed at arbitrary bytecode, performed better than other
decompilers in tests with class files that were not generated by javac. However, for
the others it did not perform as well. It could not decompile the trivial, TryFinally
program which most other decompilers could. Dava attempts to reconstruct the pro-
gram’s control flow whereas other decompilers recognise the pattern of a try-finally
block. Dava perfectly decompiles the Exception test program which is unsurprising as
this test program is from the creators of Dava. Interestingly, Dava was unable to cor-
rectly decompile the TypeInference test program, which was created originally to show
that Dava outperforms other decompilers, due to a failure to insert a simple typecast
for an argument in a method invocation. However, the main problem that the Type-
Inference test program is designed to show, type inference of a specific variable, was
performed correctly.
4.2.1 Casting
The decompiled casting test program (Listing 28, page 59) indicates that Dava was
unable to detect the need to insert a cast, and therefore Dava produced a semantically
incorrect program. The decompiled program iterates through the characters as in the
original program (except using Unicode instead of ASCII) but in the System.out.println
invocation there is no cast inserted. The output of the program, when executed, is
therefore two lists of characters rather than the original ASCII code followed by ASCII
character.
4.2.2 Args
Dava correctly decompiled the Args test program (Listing 29, page 59) but typed the
int local variable as byte, and inserted two unnecessary typecasts. We therefore classify
this program as semantically equivalent but with ‘untidy’ source code.
44
4.2.3 ControlFlow
Dava produces a semantically equivalent ControlFlow program (Listing 30, page 60)
which is slightly different from the original program. Dava’s program contains a labelled
while loop, and uses labelled continue statements to continue iterating from two points:
inside the try section and inside the catch section. This version of the program has only
one loop and uses a labelled continue to mimic the inner loop in the original program
(Listing 21, page 32). Both Java class files contain the same bytecode sequences though
they are generated from different Java source (Listing 22, page 33).
4.2.4 Sable
Dava correctly performs type inference on the Sable test program (Listing 14, page
29) but unfortunately fails to insert a typecast in the method invocation for method
f , which results in a syntactically incorrect Java program (Listing 31, page 61). The
main problem, type inference for variable d, was solved correctly and inserting a type
cast results in a semantically and syntactically correct program.
4.2.5 Usa
Dava failed to correctly decompile the Usa test program resulting in a class file with
out the inner classes (Listing 32, page 62). The resulting program contains none of the
inner classes present in the original program (Listing 13, page 28).
4.2.6 Exceptions
Dava fails to correctly decompile the Exceptions test program, producing an incom-
plete program (Listing 27, page 59). This is surprising because the program was de-
signed by the creators of Dava to demonstrate their decompiler [Miecznikowski and
Hendren(2002)]. The previous version of Dava, 2.3.0, could decompile the program
correctly and the result decreased the effectiveness score in this version.
4.2.7 TryFinally
The decompiled TryFinally program (Listing 33, page 62) is interesting as it decompiles
a nearly syntactically, and somewhat semantically correct program. The decompiled
program contains a small syntactic error (variable $r5 was originally $r4, which had
already been declared) which when corrected produces a compiled Java program which
produces an equivalent output to the original program. Even though both programs
produce the same output they are not identical. The decompiled program does not
make use of a try-finally block, and instead uses a mix of try-catch blocks and a
labelled while-loop and a labelled continue statement. If re-compiled the program does
not produce the same bytecode as the original program.
4.2.8 Optimised
The optimised program is incorrect as it is missing a typecast; this is the same problem
as the type inference and casting test programs.
45
4.2.9 Ada
Dava failed to correctly decompile the Ada test program with several syntactic and
semantic errors. Listing 35, page 60 shows an extract from the decompiled program
where a boolean is compared with an int.
4.3 Jad
Jad produced some pleasing results, with a higher overall score than Dava. Jad performs
best with javac generated code and fails to correctly decompile arbitrary bytecode. Jad
also fails the trivial TryFinally test program which is due to Jad being outdated - finally
blocks used to be implemented using subroutines but javac no longer generates subrou-
tines and instead in-lines finally blocks. Jad correctly decompiles three javac generated
class files whereas Dava only correctly decompiles two of these, which demonstrates
the difference between between the two types of decompiler. Jad is comparable to Java
Decompiler and SourceAgain and overall performs very similarly.
4.3.1 Sable
Jad produced a semantically equivalent Java program (Listing 36, page 64) but did not
perform type inference. Variable d (Obj in the decompiled program) is typed as Object,
and an explicit typecast is inserted at the point where the draw() method is invoked. We
therefore classify this program as category 1 - semantically and syntactically correct,
but no type inference was performed.
4.3.2 ControlFlow
Jad decompiles the ControlFlow program correctly (Listing 37, page 65) though pro-
duces slightly different Java source than the original. The program uses a for-loop
instead of a while loop for the inner loop and a do-while loop instead of a standard
while loop for the outer loop.
4.3.3 Casting
The casting program is missing a typecast - the same problem as Dava had with this
program.
4.3.4 Usa
4.3.5 Exceptions
Jad fails to correctly decompile the Exceptions test program, producing a syntactically
incorrect program (Listing 40, page 67). The program contains some illegal Java code,
including goto statements and the the word ‘this’. The program also contains only one
try-catch block which includes ‘c’ and ‘d’ in the try clause and ‘e’ in the catch clause.
The program is syntactically incorrect and not equivalent to the original.
4.3.6 Optimised
The Optimised test program is incorrectly decompiled by Jad. The program (Listing
41, page 68) contains only one local variable in the f method compared with 4 in the
original. The program also contains some bytecode instructions where Jad could not
determine how the objects are constructed using the optimisations. Jad was expecting
javac generated code so could not deal with the unexpected optimised sequence for
object construction.
4.3.7 TryFinally
Jad produces a syntactically incorrect TryFinally program (Listing 42, page 69). The
decompiled program contains illegal Java code as it had trouble analysing the control
flow of the program.
4.3.8 Args
Jad produces a syntactically incorrect Java program (Listing 43, page 69). Jad attempts
to assign an integer to an array of String objects, which is syntactically incorrect.
4.4.1 Casting
Java Decompiler produces a semantically incorrect program (Listing 45, page 70) in the
same way that Dava does (Listing 28, page 59). The decompiled program loop iterates
through the characters as in the original program (except using unicode instead of
ASCII) but in the System.out.println invocation there is no cast inserted. The output
of the program, when executed, is therefore two lists of characters rather than the
original ASCII code followed by ASCII character
47
4.4.2 InnerClass
The InnerClass program is semantically correct but contains some redundant code
(Listing 46.
4.4.3 Sable
Java Decompiler produces a syntactically incorrect program, with the same problem
as in the Casting test program - failure to insert a typecast. Dava also failed to insert
the same typecast. Further interesting syntactic errors include object instantiations
that are spread over two lines where the first uses the new keyword along with the
class name. Java Decompiler attempts to call the object’s constructor separately, on
the next line. An object’s constructor method is known by the special name < innit >
in bytecode and this is what Java Decompiler attempts to invoke. The program also
contains less variables and an attempt is made to assign a boolean to a short.
4.4.4 ControlFlow
Java Decompiler produces a semantically incorrect control flow program (Listing 48,
page 71) containing an extra break statement and some labels. If the syntactic errors
are removed the program is almost semantically correct.
4.4.5 Exceptions
Java Decompiler fails to correctly decompile the Exceptions test program resulting in
a syntactically incorrect program (Listing 49, page 72). The program uses the word
‘this’ as a variable name which is a reserved word in Java. Correcting these syntactic
errors leads to a semantically incorrect program (see Figure 13, page 72).
4.4.6 Optimised
Java Decompiler produces a syntactically incorrect program with some interesting syn-
tactic errors (Listing 50, page 72) in the same way as the TypeInference test program.
4.4.7 Args
Java Decompiler produces a semantically correct Java program (Listing 51, page 73).
4.4.8 connectfour
4.5 jdec
jdec is another javac orientated decompiler but does not perform as well as the other
newer decompilers. Java Decompiler can correctly decompile inner classes while jdec
cannot. jdec also cannot decompile arbitrary bytecode correctly.
4.5.1 Casting
jdec decompiles the Casting test program with the same semantic error (Listing 54,
page 74) as other decompilers such as Dava (Listing 28, page 59) - the crucial cast from
char to int is missing. The decompiled program loop iterates through the characters
as in the original program but in the System.out.println invocation there is no cast
inserted. The output of the program, when executed, is therefore two lists of characters
rather than the original ASCII code followed by ASCII character
4.5.2 Usa
jdec fails to decompile the Usa test program, decompiling only the main class (Listing
55, page 75). Dava also has this problem.
4.5.3 Sable
jdec produces a syntactically and semantically incorrect type inference test program,
with corrected syntactic errors leading to a semantically incorrect program (Listing 56,
page 76). This program contains two Rectangle objects rather than the one Rectangle
original and one Drawable object in the original.
4.5.4 ControlFlow
jdec produces a syntactically incorrect control flow test program with a catch clause
intersecting a else clause which, if corrected produces a semantically incorrect program
(Listing 57, page 77).
4.5.5 Exceptions
jdec only partially decompiles the exceptions test program, with blocks ‘d’, ‘e’ and ‘f’
missing. The program is syntactically and semantically incorrect (Listing 58, page 78).
4.5.6 Optimised
jdec produces a syntactically and semantically incorrect optimised program, with sev-
eral syntactic errors (Listing 59, page 79). The program contains variable assignment
statements within constructor invocation parameters and uses the variable this in many
places. The method is static so should contain no use of the this keyword. The method
also contains no arguments which is strange because jdec introduced a variable called
arg0.
49
4.5.7 Args
jdec produces a syntactically incorrect program (Listing 60, page 80), in a similar way
to Jad by attempting to assign an integer to a string array. jdec also attempts to
re-declare the method argument.
4.5.8 connectfour
jdec produces a seemingly incomplete, and syntactically incorrect, program with many
empty blocks (for example, listing 63, page 81) and syntax errors. The program has
several examples of missing commas within method parameter lists (for example, listing
62, page 73) and assignment statements within array declarations (for example, listing
61, page 81).
4.6 JODE
JODE has a similar overall result to Java Decompiler and Jad but is beaten using
our effectiveness measures by Java Decompiler. In comparison to Java Decompiler and
Jad, JODE is able to decompile 5 test programs perfectly whereas Java Decompiler and
Jad only decompile 3 test programs correctly. Surprisingly JODE is unable to correctly
decompile the TryFinally test program. JODE was the best decompiler in the original
survey and is still one of the best in our evaluation.
4.6.1 TryFinally
JODE’s decompilation of the TryFinally test program (Listing 64, page 81) fails to
include a try-finally block but instead includes a try-catch block and an incorrectly
typed exception variable. The catch clause includes an exception variable typed as
Object but such a variable must be typed Exception or a sub-class of Exception.
This makes the decompiled program both syntactically and semantically incorrect.
The finally clause code is duplicated after the try-catch block, and if the syntactic
error is corrected the program produces the same output as the original. However, this
is not the same as the original program which uses a try-finally block rather than a
try-catch block.
4.6.2 Usa
JODE could not parse the inner class test program, exiting with an exception.
4.6.3 ControlFlow
JODE produces a semantically correct decompilation (Listing 65, page 81) of the con-
trol flow test program (Listing 21, page 32). However, the source is more obtuse than
the original. JODE’s decompilation uses for-loops instead of while loops which, while
semantically equivalent, do not look as user-friendly as the original. In this version of
the program there is no continue statement in the catch clause but instead a break is
used in the try clause. Control flows from the catch clause to the beginning of the loop
as there is no break clause.
50
4.6.4 Exceptions
JODE could not decompile the exceptions test program, exiting with an exception
regarding the intersecting try-catch blocks (Listing 67, page 82).
4.6.5 Optimised
JODE has a slight problem with object construction in the optimised test program,
similar to other decompilers such as Java Decompiler. If the constructor problem is
corrected the program is semantically correct, including the correctly typed variable
(Listing 67, page 82).
4.6.6 Ada
JODE could not parse the Ada test program and exits with an exception (Listing 68,
page 83), due to the arbitrary nature of the bytecode instructions.
4.7 jReversePro
jReversePro performed badly in many of the tests and was unable to decompile the test
programs correctly, as it can not parse the latest class file format. In fact, jReversePro
failed to parse many of the test programs and only partially decompiled others. The
remaining programs produced were semantically incorrect.
4.7.1 Fibo
jReversePro is unable to decompile the trivial Fibo test program as it could not parse
the latest class file format (Listing 69, page 83).
4.7.2 Casting
jReversePro was able to parse the simpler casting test program but produced a seman-
tically incorrect program (Listing 70, page 84). The program fails to include the crucial
cast in the same way as other decompilers such as Jad and Java Decompiler but also
contains an infinite loop due to the inclusion of an extra variable.
4.7.3 Usa
The inner class program, as decompiled by jReversePro, contains none of the inner
classes (Listing 71, page 85).
4.7.4 Sable
jReversePro is unable to parse the type inference test program and exits with an
exception.
51
4.7.5 TryFinally
jReversePro does not fully decompile the try finally test program as it leaves out the
most important part: the try-finally block.
4.7.6 Exceptions
jReversePro exits with an exception due to intersecting try-catch blocks (Listing 73,
page 86), like JODE.
4.7.7 Optimised
jReversePro was able to parse the optimised program, most likely because the opti-
miser output an earlier version class file format, but produced a syntactically incorrect
program (Listing 74, page 87). The program contains several syntactic errors, included
attempting to assign a boolean to an int and constructor problems.
4.7.8 Args
jReversePro incorrectly decompiled the variable re-use test program producing a syn-
tactically incorrect program (Listing 75, page 88), similar to Jad and Java Decompiler.
4.7.9 connectfour
4.8 Mocha
Mocha is an obsolete decompiler which never made it past a beta version, though we
include it here as it is still available to use. The results from Mocha are not surprising
as they confirm that Mocha is no longer a viable decompiler for the latest Java class
files. Mocha fails to parse all programs generated by the latest version of javac, and
also fails to parse two of the arbitrary test programs.
4.8.1 Args
4.8.2 Exceptions
Mocha could not decompile the exceptions test program and exits with a ‘Method
Exceptions: Flow analysis could not complete’ error message, due to the intersecting
try-catch blocks.
52
4.8.3 connectfour
Mocha’s decompilation of the connectfour test program contains many syntactic errors
(for example, listing 79, page 85), and some bytecode instructions are left in place (for
example, listing 81, page 90 and listing 80, page 89).
4.9 SourceAgain
SourceAgain is the only commercial decompiler that performed well in our tests.
SourceAgain is able to perfectly decompile four of our test programs including the
Args test program which is only decompiled correctly by JODE and Dava. However,
the product is no longer sold or supported and is only available online to decompiler
single class files. SourceAgain is therefore obsolete and not useful for any real-world
decompilation tasks.
4.9.1 Casting
SourceAgain, like other decompilers such as Jad, fails to insert the crucial cast resulting
in a semantically incorrect program (Listing 82, page 90).
4.9.2 Usa
SourceAgain fails to correctly decompile (Listing 71, page 85) the inner class test pro-
gram but unlike other decompilers, such as jReversePro, this is because the decompiler
is web-based and only accepts single class files - the inner classes could not be uploaded
along with the main class. The original survey [Emmerik(2003)] used the professional
version of the decompiler which was able to correctly decompile inner classes. This is
no longer available.
4.9.3 Sable
SourceAgain produces a semantically equivalent type inference program but could not
correctly type the variable d (Listing 84, page 91). The online decompiler warns that
it could not determine the inheritance relationship between the objects as a result of
the restriction of decompiling single class files. In the original survey [Emmerik(2003)]
the professional version of SourceAgain did not correctly type the variable.
4.9.4 TryFinally
SourceAgain produces a syntactically incorrect try finally test program containing two
try-finally blocks, duplicated finally clause code and an undeclared variable (Listing
85, page 92).
53
4.9.5 ControlFlow
4.9.6 Exceptions
4.9.7 Optimised
SourceAgain produces a syntactically incorrect program (Listing 88, page 94) contain-
ing, like other decompilers, object initialisation and constructor problems. SourceAgain
also includes self assignments which aren’t necessary.
4.9.8 connectfour
SourceAgain produces a syntactically incorrect connectfour test program but with fewer
errors than other decompilers. If problems such as misnamed this variables (Listing
89, page 90) and multiple variable declarations (Listing 90, page 91) are removed the
program becomes compilable and semantically correct. SourceAgain also inserts a large
number of warning notices into the source which must be removed as they begin with
a #.
SourceTec (Jasmine), a patch to Mocha, also fails to parse all javac generated class
files producing the same results as Mocha.
54
Many of the companies producing commercial decompilers have disappeared and their
decompilers have been left unmaintained. Even some free and/or open-source decom-
pilers such as Jad and JODE have been unmaintained for some time. Jad is not open-
source so the project cannot be taken up by others and the last major update was in
2001.
Decompilation has many uses in the real world, such as the recovery of lost source
code for a crucial application [Emmerik and Waddington(2004)], therefore if the quality
of Java decompilers increased they might be of more use commercially.
One of the most active decompiler projects is the open-source Dava [Miecznikowski(2003),
Naeem and Hendren(2006),Naeem(2007),Miecznikowski and Hendren(2001),Miecznikowski
and Hendren(2002)] decompiler, part of the Soot Optimisation Framework [Valle-Rai
et al(1999)Valle-Rai, Co, Gagnon, Hendren, Lam, and Sundaresan], which is a research
project carried out by the Sable Research Group at McGill University. Dava differs from
other decompilers in that it aims to decompile arbitrary bytecode whereas other decom-
pilers rely on known patterns produced by Java compilers (and this is usually javac).
Dava is better at decompiling arbitrary bytecode whereas other decompilers are better
at decompiling javac generated bytecode. However, Java Decompiler was just as good
at decompiling arbitrary bytecode according to our effectivess score. Java Decompiler
is aimed at decompiling javac generated bytecode.
A decompiler aimed at decompiling arbitrary bytecode, like Dava, can be more
useful in some instances than a decompiler aimed at bytecode generated by a spe-
cific compiler. Java bytecode can be generated by tools other than a Java decompiler
and many decompilers are aimed at patterns produced by Java decompilers and some
specifically javac. Knowing the patterns that a compiler will produce makes decom-
pilation of bytecode easier and it can sometimes be just a matter of reversing those
patterns.
Decompiling bytecode arbitrarily, i.e. not by inverting known patterns produced by
compilers, can be a disadvantage in some cases, for example Dava could not correctly
decompile the trivial TryFinally test program. Other decompilers could decompile this
test program by finding a known pattern produced by a compiler for try-finally blocks.
Though there is a lack of commercial Java decompilers Java bytecode decompilation
and decompilation in general are fruitful research areas. One of the main areas for re-
search is type inference both in bytecode (e.g. [Bellamy et al(2008)Bellamy, Avgustinov,
de Moor, and Sereni, Miecznikowski and Hendren(2001), Knoblock and Rehof(2001)])
and machine code (e.g. [Mycroft(1999)]). The task of type inference in Java bytecode
is simpler than that of machine code due to the information contained within a Java
class file - a Java class file contains type information for fields and method parameters
and returns.
Type inference is an interesting problem in decompilation and two of the best
decompilers tested (Dava and JODE) were both able to correctly type the variables
in the type inference test. Most other decompilers, which did not perform type in-
ference, typed variables as Object and inserted a typecast where necessary. The type
inference problem is NP-Hard in the worst case [Gagnon et al(2000)Gagnon, Hendren,
and Marceau], however, if the type inference algorithm is optimised for the common-
case rather than the worst case it is possible to perform type analysis efficiently for
most real-world code as worst-case scenarios are unlikely [Bellamy et al(2008)Bellamy,
Avgustinov, de Moor, and Sereni].
55
All the decompilers tested had some problems decompiling some of the tests. In
terms of our effectiveness measures, Dava, Jad, Java Decompiler and JODE were the
four best decompilers (excluding SourceAgain). Of these, JODE and Java Decompiler
are the best decompilers: JODE correctly decompiles 5 out of the 10 test programs
correctly and Java Decompiler performs best using our effectiveness measures but de-
compiles one less program correctly. JODE is open-source and is therefore open to
further improvements but is not currently maintained, while Java Decompiler is in
development and available free (but is not open-source). Unfortunately Jad is unmain-
tained and so is not guaranteed to work for future versions of Java class files. The
commercial decompiler SourceAgain performs well in the effectiveness measures, but
was only able to decompile 4 programs correctly. SourceAgain performed similarly to
Dava but is now obsolete and only available as a web application which can decom-
pile single class files. Java Decompiler is a newer decompiler in active development,
which performs highest in our effectiveness measures and correctly decompiles 4 out
of 10 programs. This decompiler performs best at javac generated bytecode and may
improve in the future even more as it is in development.
Knowing the tool that generated a class file can be useful in knowing which de-
compiler to use. If a class file was generated by javac then a javac specific decompiler
would be more useful than an arbitrary decompiler such as Dava. If the class file was
generated by other means, or modified by an obfuscator or optimiser, a javac specific
decompiler would most likely fail so an arbitrary decompiler would be more useful in
this case.
We have demonstrated the effectiveness of several Java decompilers on a small
set of test programs, each of which were designed to test different problem areas in
decompilation. Such a small test set of programs may not be representative of real-world
Java programs and, in fact, some problem areas tested may not be of high relevance
in real-world programs.
We proposed a system to quantify the effectiveness of a decompiler, however the
analysis is some-what subjective and could be further formalised.
In terms of our evaluation, Dava and Java Decompiler are the best decompilers.
Dava faces some challenges in decompilation of Java specific code while the other
decompilers have problems with arbitrary bytecode.
We have shown that, though not perfect, decompilers are a real threat to distributed
Java bytecode class files. Software companies would need to introduce technical mea-
sures to provide protection for their intellectual property. One such method is software
watermarking which does not attempt to stop software theft but instead deters attack-
ers by providing a method of proving ownership of copied software. The next chapter
presents a survey of static software watermarking techniques.
References
[Alpern et al(1988)Alpern, Wegman, and Zadeck] Alpern B, Wegman MN, Zadeck FK (1988)
Detecting equality of variables in programs. In: POPL ’88: Proceedings of the 15th ACM
SIGPLAN-SIGACT symposium on Principles of programming languages, ACM, New
York, NY, USA, p 111, DOI http://doi.acm.org/10.1145/73560.73561
[Batchelder and Hendren(2007)] Batchelder M, Hendren L (2007) Obfuscating java: the most
pain for the least gain . Braga, Portugal
[Bellamy et al(2008)Bellamy, Avgustinov, de Moor, and Sereni] Bellamy B, Avgustinov P,
de Moor O, Sereni D (2008) Efficient local type inference. SIGPLAN Not 43(10):475492,
DOI http://doi.acm.org/10.1145/1449955.1449802
[Belur and Bettadapura(2008)] Belur S, Bettadapura K (2008) Jdec: Java decompiler. URL
http://jdec.sourceforge.net/, 2008
[Buckley et al(2006)Buckley, Rose, Coglio, Corporation, Sun Microsystems, Tmax Soft, Technologies, and AG]
Buckley A, Rose E, Coglio A, Corporation BS, Sun Microsystems I, Tmax Soft I, Tech-
nologies S, AG E (2006) JSR 202: JavaTM class file specification update. URL
http://jcp.org/en/jsr/detail?id=202
[Caudle(1980)] Caudle W (1980) On inverse of compiling. Sperry-UNIVAC URL
http://www.program-transformation.org/view/Transform/OnInverseOfCompiling?
skin=print.pattern
[Cifuentes(1994)] Cifuentes C (1994) Reverse compilation techniques. PhD thesis, Queensland
University of Technology, URL http://citeseer.ist.psu.edu/cifuentes94reverse.html
[Collberg and Nagra(2009)] Collberg C, Nagra J (2009) Surreptitious Software: Obfuscation,
Watermarking, and Tamperproofing for Software Protection. Addison-Wesley Professional
[Collberg and Thomborson(1998)] Collberg C, Thomborson C (1998) On the lim-
its of software watermarking. Tech. Rep. 164, URL http://www.cs.auckland.
ac.nz/collberg/Research/Publications/CollbergThomborson98e/index.html,
http://www.cs.auckland.ac.nz/˜collberg/Research/Publications/CollbergThomborson98e/index.html
[Collberg et al(1997)Collberg, Thomborson, and Low] Collberg C, Thombor-
son C, Low D (1997) A taxonomy of obfuscating transformations. Tech.
Rep. 148, URL <ahref="http://www.cs.auckland.ac.nz/collberg/Research/
Publications/CollbergThomborsonLow97a/index.html">Collberg97a</a>,
http://www.cs.auckland.ac.nz/$\sim$collberg/Research/Publications/CollbergThomborsonLow97a/index.html
[Collberg and Thomborson(2002)] Collberg CS, Thomborson C (2002) Watermarking,
Tamper-Proofing, and obfuscation - tools for software protection. In: IEEE Transac-
tions on Software Engineering, vol 28, p 735746, URL http://citeseer.nj.nec.com/
collberg02watermarking.html
[Cytron et al(1989)Cytron, Ferrante, Rosen, Wegman, and Zadeck] Cytron R, Ferrante J,
Rosen BK, Wegman MN, Zadeck FK (1989) An efficient method of computing static
single assignment form. In: POPL ’89: Proceedings of the 16th ACM SIGPLAN-SIGACT
symposium on Principles of programming languages, ACM, New York, NY, USA, p 2535,
DOI http://doi.acm.org/10.1145/75277.75280
[Dagenais and Hendren(2008)] Dagenais B, Hendren L (2008) Enabling static analysis for
partial java programs. SIGPLAN Not 43(10):313328, DOI http://doi.acm.org/10.1145/
1449955.1449790
[Dupuy(2009)] Dupuy E (2009) Java decompiler. URL http://java.decompiler.free.fr/
[Dyer(1997)] Dyer D (1997) Java decompilers compared. URL http://www.javaworld.com/
javaworld/jw-07-1997/jw-07-decompilers.html
[Emmerik(2003)] Emmerik MV (2003) Java decompiler tests. http://www.program-
transformation.org/Transform/JavaDecompilerTests, URL http://www.
program-transformation.org/Transform/JavaDecompilerTests
[Emmerik(2007)] Emmerik MV (2007) Static single assignment for decompilation. PhD thesis,
The University of Queensland
[Emmerik and Waddington(2004)] Emmerik MV, Waddington T (2004) Using a decompiler
for Real-World source recovery. In: WCRE ’04: Proceedings of the 11th Working Confer-
ence on Reverse Engineering, IEEE Computer Society, Washington, DC, USA, p 2736
[Fagin and Carlisle(2005)] Fagin B, Carlisle M (2005) Connect Four(TM) game, written
in ada. URL http://webdiis.unizar.es/asignaturas/EDA/gnat/jgnat/connect_four/
test.html, 2005
[Freund(1998)] Freund SN (1998) The costs and benefits of java bytecode subroutines. In: In
Formal Underpinnings of Java Workshop at OOPSLA
[Gagnon et al(2000)Gagnon, Hendren, and Marceau] Gagnon E, Hendren LJ, Marceau G
(2000) Efficient inference of static types for java bytecode. In: Static Analysis Sympo-
sium, p 199219, URL www.sable.mcgill.ca/publications
57
[Gosling et al(2000)Gosling, Joy, Steele, and Bracha] Gosling J, Joy B, Steele G, Bracha G
(2000) The Java Language Specification Second Edition. Addison-Wesley, Boston, Mass.,
URL citeseer.ist.psu.edu/gosling00java.html
[Gosling et al(2005)Gosling, Joy, Steele, and Bracha] Gosling J, Joy B, Steele G, Bracha G
(2005) Java(TM) Language Specification, The (3rd Edition) (Java (Addison-Wesley)).
Addison-Wesley Professional
[Gough and Corney(2001)] Gough KJ, Corney D (2001) Implementing languages other than
java on the java virtual machine
[Grishchenko and Gyger(2009)] Grishchenko V, Gyger J (2009) JadClipse. URL http://
jadclipse.sourceforge.net/wiki/index.php/Main_Page
[Haggar(2001)] Haggar P (2001) Understanding bytecode makes you a better program-
mer. http://www.ibm.com/developerworks/ibm/library/it-haggar bytecode/, URL http:
//www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/
[Halstead(1977)] Halstead MH (1977) Elements of software science (Operating and program-
ming systems series). Elsevier, published: Hardcover
[Hamilton and Danicic(2009)] Hamilton J, Danicic S (2009) An evaluation of current java
bytecode decompilers. In: Ninth IEEE International Workshop on Source Code Analysis
and Manipulation, IEEE Computer Society, Edmonton, Alberta, Canada, vol 0, pp 129–
136, DOI http://doi.ieeecomputersociety.org/10.1109/SCAM.2009.24
[Hoenicke(2004)] Hoenicke J (2004) JODE. URL http://jode.sourceforge.net/, 2004
[Janssen and Corporaal(1997)] Janssen J, Corporaal H (1997) Making graphs reducible with
controlled node splitting. ACM Trans Program Lang Syst 19(6):10311052, DOI http://
doi.acm.org/10.1145/267959.269971
[Kearney et al(1986)Kearney, Sedlmeyer, Thompson, Gray, and Adler] Kearney, Sedlmeyer,
Thompson, Gray, Adler (1986) Software complexity measurement. Commun ACM
29(11):10441050, DOI http://doi.acm.org/10.1145/7538.7540
[Knoblock and Rehof(2001)] Knoblock TB, Rehof J (2001) Type elaboration and subtype
completion for java bytecode. ACM Trans Program Lang Syst 23(2):243272, DOI
http://doi.acm.org/10.1145/383043.383045
[Kouznetsov(2001)] Kouznetsov P (2001) Jad - the fast java decompiler. URL http://www.
varaneckas.com/jad
[Kramer et al(1996)Kramer, Joy, and Spenhoff] Kramer D, Joy B, Spenhoff D (1996) The
java[tm] platform. Tech. rep., Sun Microsystems, URL http://java.sun.com/docs/white/
platform/javaplatformTOC.doc.html
[Kumar(2005)] Kumar K (2005) JReversePro - java decompiler / disassembler. URL http:
//jreversepro.blogspot.com
[Ladue(1997)] Ladue MD (1997) When java was one: Threats from hostile byte code. In:
Proceedings of the 20th National Information Systems Security Conference
[Leroy(2003)] Leroy X (2003) Java bytecode verification: Algorithms and formalizations. J
Autom Reason 30(3-4):235269
[Lindholm and Yellin(1999)] Lindholm T, Yellin F (1999) The Java(TM) Virtual Machine
Specification (2nd Edition). Prentice Hall PTR, published: Paperback
[Masnick(2008)] Masnick M (2008) A detailed explanation of how the BSA misleads with
piracy stats | techdirt. http://www.techdirt.com/articles/20080718/1226541724.shtml,
URL http://www.techdirt.com/articles/20080718/1226541724.shtml
[Meyer et al(2004)Meyer, Reynaud, Kharon et al] Meyer J, Reynaud D, Kharon I, et al (2004)
Jasmin. URL http://jasmin.sourceforge.net/
[Miecznikowski(2003)] Miecznikowski J (2003) New algorithms for a java decompiler and their
implementation in soot. Masters thesis, McGill University
[Miecznikowski and Hendren(2001)] Miecznikowski J, Hendren L (2001) Decompiling java us-
ing staged encapsulation. In: WCRE ’01: Proceedings of the Eighth Working Conference
on Reverse Engineering (WCRE’01), IEEE Computer Society, Washington, DC, USA, p
368
[Miecznikowski and Hendren(2002)] Miecznikowski J, Hendren LJ (2002) Decompiling java
bytecode: Problems, traps and pitfalls. In: CC ’02: Proceedings of the 11th International
Conference on Compiler Construction, Springer-Verlag, London, UK, p 111127
[Mycroft(1999)] Mycroft A (1999) Type-Based decompilation (or program reconstruction via
type reconstruction). In: ESOP ’99: Proceedings of the 8th European Symposium on Pro-
gramming Languages and Systems, Springer-Verlag, London, UK, p 208223
[Naeem(2007)] Naeem NA (2007) Programmer-Friendly decompiled java. Masters thesis, URL
http://www.sable.mcgill.ca/publications/thesis/#nomairMastersThesis
58
class Exceptions2
{
Exceptions2 $r1 ;
$r1 = new Exceptions2 () ;
}
char c0 ;
for ( c0 = ’\ u0000 ’; c0 < ’\ u0080 ’; c0 = ( char ) ( c0 + 1) )
{
System . out . println (( new StringBuilder () ) . append (" ascii ") .
append ( c0 ) . append (" character ") . append ( c0 ) . toString () ) ;
}
}
}
class Args
{
byte b0 ;
b0 = ( byte ) ( byte ) 0;
System . out . println ( b0 ) ;
}
}
60
int $i2 ;
label_0 :
while ( true )
{
try
{
if ( i0 < i1 )
{
$i2 = i1 ;
i1 ++;
i0 = $i2 / i0 ;
continue label_0 ;
}
}
catch ( R u n t i m e E x c e p t i o n $r1 )
{
i0 = 10;
continue label_0 ;
}
return i1 ;
}
}
Listing 35: Test Result: Dava - Extract 1 from connectfour test program
boolean $z4 , z5 ;
....
if (( $z4 & $z5 ) != 0) {
r1 . all = 1;
}
61
Rectangle r0 ;
boolean z0 ;
Drawable r1 ;
Circle r2 ;
label_0 :
{
while ( s0 <= 10)
{
r2 = new Circle ( s0 ) ;
z0 = r2 . isFat () ;
r1 = r2 ;
break label_0 ;
}
r0 = new Rectangle ( s0 , s0 ) ;
z0 = r0 . isFat () ;
r1 = r0 ;
} // end label_0 :
label_1 :
{
while ( z0 )
{
break label_1 ;
}
r1 . draw () ;
} // end label_1 :
Sable . f (11) ;
}
}
62
public Usa ()
{
this . this () ;
}
}
Listing 33: Test Result: Dava - TryFinally test program. Variable $r5 was
originally $r4.
import java . io . PrintStream ;
Throwable r2 ;
try
{
System . out . println (" try ") ;
}
catch ( Throwable $r4 )
{
label_0 :
while ( true )
{
try
{
r2 = $r4 ;
}
catch ( Throwable $r4 )
{
continue label_0 ;
}
Rectangle r0 ;
boolean z0 ;
Drawable r1 ;
Circle r2 ;
label_0 :
{
while ( s0 <= 10)
{
r2 = new Circle ( s0 ) ;
z0 = r2 . isFat () ;
r1 = r2 ;
break label_0 ;
}
r0 = new Rectangle ( s0 , s0 ) ;
z0 = r0 . isFat () ;
r1 = r0 ;
} // end label_0 :
label_1 :
{
while ( z0 )
{
break label_1 ;
}
r1 . draw () ;
} // end label_1 :
Optimised . f (11) ;
}
}
64
public Sable ()
{
}
Listing 44: Test Result: Jad - Extract 1 from connectfour test program
if ( _loop_index == _index_max )
break ; /* Loop / switch isn ’ t completed */
_src_tmp [ _loop_index ];
goto _L1
_L3 :
_trg_tmp [ _loop_index ];
_trg_tmp [ _loop_index ];
_L1 :
0;
if ( true ) goto _L3 ; else goto _L2
_L2 :
JVM INSTR arraylength . length ;
0;
JVM INSTR swap ;
System . arraycopy () ;
_loop_index ++;
if ( true ) goto _L5 ; else goto _L4
_L4 :
return _result ;
65
public ControlFlow ()
{
}
public Casting ()
{
}
}
}
66
public Ireland ()
{
this$1 = England . this ;
super () ;
name = " Dublin ";
}
}
public England ()
{
this$0 = Usa . this ;
super () ;
name = " London ";
}
}
public Usa ()
{
name = " Detroit ";
}
class Exceptions
{
Exceptions ()
{
}
public Optimised ()
{
}
public TryFinally ()
{
}
class Args
{
Args ()
{
}
Listing 52: Test Result: Java Decompiler - Extract 1 from connectfour test
program
standard . access_string tmp10_7 = new jgnat / adalib / s t a n d a r d $ a c c e s s _ s t r i n g ;
tmp10_7 . < init >() ;
Listing 53: Test Result: Java Decompiler - Extract 2 from connectfour test
program
public static void _elabb () throws {
71
Listing 61: Test Result: jdec - Extract 1 from connectfour test program
int [] Jde c G e n e r a t e d 1 7 3 = new int []{ _aggr =( J d e c G e n e r a t e d 1 7 3 )
;71 ,143 ,214 ,286 ,357 ,429 ,500};
72
class Exceptions {
public static void main ( String [] p a r a m A r r a y O f S t r i n g ) {
new Exceptions () ;
}
class Args
{
public static void main ( String [] p a r a m A r r a y O f S t r i n g )
{
p ar a m A r r a y O f S t r i n g = 0;
System . out . println ( p a r a m A r r a y O f S t r i n g ) ;
}
}
Listing 62: Test Result: jdec - Extract 3 from connectfour test program
public static int [][] c o n n e c t f o u r $ T b o a r d _ a r r a y _ t y p e B _ d e e p _ c o p y ( int
[][] _target , int _trgstart , int [][] _source , int _srccount ,
int _srcstart int [][] _result int _loop_index int _trgindex int
_srcindex int [][] _trg_tmp int [][] _src_tmp int _loop_index
int _index_max ) { ... }
public static void place_disk ( int [][] board , int column , Int row ,
int who Throwable _exc_var ) { ... }
public static void check_won ( int [][] board , int who , Int won int
_loop_param int _loop_limit int _loop_param int _loop_limit
Throwable _exc_var ) { ... }
public static void check_tie ( int [][] board , Int is_tie int
_loop_param int _loop_limit Throwable _exc_var ) { ... }
// End of Import
// CLASS : Casting :
public Casting ( )
{
super () ;
return ;
// CLASS : Casting :
public static void main ( String [] aString1 )
{
char char1 = 0;
char1 =0;
while ( true )
{
if ( char1 >= 128)
{
break ;
}
if ( char1 < 128)
{
StringBuilder J de c Ge ne r at ed 14 = new StringBuilder () ;
System . out . println ( Jd ec G en er at e d1 4 . append (" ascii ") . append ( char1 )
. append (" character ") . append ( char1 ) . toString () ) ;
char1 =( char ) (( char1 + 1) ) ;
continue ;
}
return ;
}
75
// End of Import
/***
** Class Fields
***/
public String name ;
// CLASS : Usa :
public Usa () {
super () ;
this . name =" Detroit ";
return ;
}
}
76
// End of Import
// CLASS : Sable :
public static void f ( short short2 ) {
Circle aCircle1 = null ;
Rectangle aRectangle1 = null ;
Rectangle aRectangle2 = null ;
int int1 = 0;
if ( short2 > 10) {
Rectangle Jdec Generat ed8 = new Rectangle ( short2 , short2 ) ;
aRectangle1 = J decGene rated8 ;
int1 = aRectangle1 . isFat () ;
aRectangle2 = aRectangle1 ;
} else {
Circle J d ec Ge ne r at ed 29 = new Circle ( short2 ) ;
aCircle1 = J de cG e ne ra te d 29 ;
int1 = aCircle1 . isFat () ;
aCircle2 = aCircle1 ;
}
if ( int1 ==0) {
aCircle2 . draw () ;
return ;
}
}
// CLASS : Sable :
public static void main ( String [] aString1 ) {
f (( short ) 11) ;
return ;
}
}
77
// End of Import
// CLASS : ControlFlow :
public ControlFlow () {
super () ;
return ;
}
// CLASS : ControlFlow :
public int foo ( int int4 , int int5 ) {
int int4 = 0;
while ( true ) {
try {
if ( int4 < int5 ) {
int4 = ( int5 ++) / ( int4 ) ;
continue ;
} else {
} catch ( R u n t i m e E x c e p t i o n aRuntimeException1 ) {
}
int4 =10;
continue ;
}
return int5 ;
}
}
78
// End of Import
class Exceptions2
// CLASS : Exceptions2 :
Exceptions2 ( )
{
super () ;
return ;
// CLASS : Exceptions2 :
public static void main ( String [] aString1 )
{
// CLASS : Exceptions2 :
public void Exceptions2 () {
System . out . println (" a ") ;
try {
System . out . println (" b ") ;
try {
System . out . println (" c ") ;
return ;
} catch ( R u n t i m e E x c e p t i o n this ) {
System . out . println (" g ") ;
}
}
}
}
79
// End of Import
// End of Import
class Args
// CLASS : Args :
Args ( )
{
super () ;
return ;
// CLASS : Args :
public static void main ( java . lang . String [] aString1 )
{
java . lang . String [] aString1 = 0;
aString1 =0;
System . out . println ( aString1 ) ;
return ;
}
81
Listing 63: Test Result: jdec - Extract 4 from connectfour test program
if ( board [( _loop_param - 1) ][(( _loop_param + 1) - 1) ] != who ) {
} else {
}
82
public Casting ()
{
;
return ;
}
Listing 76: Test Result: jReversePro - Extract 1 from connectfour test program
else if ( k == 0) {
}
else if (0 & 1 != 0) {
}
else if ( j != 1) {
}
else if ( k != 1) {
}
else if ( j != 2) {
}
else if (1 | 0 & 1 != 0) {
}
else if (1 & ( o ^ 1) != 0) {
}
else if ( k != 1) {
}
else if (1 & ( o ^ 1) != 0) {
}
85
public Usa ()
{
;
name = " Detroit ";
return ;
}
Listing 77: Test Result: jReversePro - Extract 2 from connectfour test program
for (; n <= m ;) {
else if ( iArr [ n - 1] == 0) {
}
else if (0 & 1 != 0) {
}
}
Listing 79: Test Result: Mocha - Extract 1 from connectfour test program
public connectfour () throws {
$this . < init >() ;
}
86
public TryFinally ()
{
;
return ;
}
public Optimised ()
{
;
return ;
}
return ;
}
}
88
class Args {
Args ()
{
;
return ;
}
Listing 80: Test Result: Mocha - Extract 2 from connectfour test program
public static void i n i t i a l i z e _ b o a r d ( int board [][]) throws {
int _loop_param ;
int _loop_limit ;
int _loop_param ;
int _loop_limit ;
expression 1
_loop_limit = 6;
pop _loop_param
if ( _loop_param > _loop_limit )
expression 1
_loop_limit = 7;
pop _loop_param
for (; _loop_param <= _loop_limit ; _loop_param ++)
board [ _loop_param - 1][ _loop_param - 1] = 0;
_loop_param ++;
}
90
Listing 81: Test Result: Mocha - Extract 3 from connectfour test program
if ( who != 1) goto 82 else 10;
Listing 89: Test Result: SourceAgain - Extract 1 from connectfour test program
public connectfour () {
$this () ;
}
91
if ( si > 10 )
{
Object obj1 = new Rectangle ( si , si ) ;
Listing 90: Test Result: SourceAgain - Extract 2 from connectfour test program
public static void i n i t i a l i z e _ b o a r d ( int [][] board ) {
int _loop_limit = 6;
int _loop_param = 0;
class Exceptions {
}
}
94
if ( arg0 > 10 )
{
obj = new Rectangle ;
( Rectangle ) obj ( arg0 , arg0 ) ;
bool = (( Rectangle ) obj ) . isFat () ;
obj = obj ;
}
else
{
obj = new Circle ;
( Circle ) obj ( bool ) ;
bool = (( Circle ) obj ) . isFat () ;
obj = obj ;
}
if ( ! bool )
(( Drawable ) obj ) . draw () ;
}