Java Decompiled Output

The decompiled output produced by ClassCracker is as near as possible to what is expected in a Java source file. This output can be re-compiled by any Java compiler without error (except possibly unlabelled blocks - see below).

Unexpected Output

Often, the output produced by ClassCracker when decompiling to a java file will not be identical to the original Java source file. This is not entirely surprising because it is possible for a number of different source code approaches to produce the same JVM (Java Virtual Machine) code. This is a feature shared by all decompilers, not just Classcracker.

Even though ClassCracker may not produce identical source code, it does produce functionally equivalent code. For example, the code:

  for( i=0; i<10; i++) statement;

is functionally equivalent to:

  i = 0;
  while( i < 10 ) {
    statement;
    i++;
  }

Similarly, the code:

  if( a == 2 ) {
    if( b == 4 ) {
      statement;
    }
  }

is functionally equivalent to:

  if( (a == 2) && (b == 4( ) statement;

Boolean Type

The JVM (Java Virtual Machine) treats the boolean type as an int with true represented by a 1 and false represented by a 0. Thus when the source code:

  private boolean fn() {
    boolean var = false;
    ...
    ...
    if( var == true ) ...;
    ...
    return true;
  }

is compiled and then decompiled the following would be produced:

  private boolean fn() {
    boolean var = 0;
    ...
    ...
    if( var == 1 ) ...;
    ...
    return 1;
  }

The above is not valid Java code and is not compilable. To overcome this problem, ClassCracker converts all booleans (including function returns) to type int but leaves a hint that the variable (or function) was intended to be a boolean. ClassCracker produces the following decompiled code:

  private int/*boolean*/ fn() {
    int/*boolean*/ var = 0;
    ...
    ...
    if( var == 1 ) ...;
    ...
    return 1;
  }

This code is valid Java code and is compilable. This code can be left as is or, alternatively, it can be manually modified by replacing the int type by the boolean type.

Although in the above simple example ClassCracker could easily convert int to boolean, such automatic conversion is error-prone for more complicated examples. The approach taken be ClassCracker is guaranteed to produce compiler-correct code.

Labelled Blocks

The only time when the decompiled code produced by ClassCracker may need to be manually modified is in the case of labelled blocks. Labelled blocks are of the form:

  ...
  label: {      // start of block
    ...
    break label;
    ...
  }            // end of block
  ...

Labelled blocks can only be referred to by a  break label;  statement. It is in general impossible for ClassCracker (or any other decompiler) to determine the exact start of a labelled block (the end of a labelled block, however, is always known exactly). This problem occurs because of the way the JVM is constructed.

Whenever a labelled block occurs, ClassCraker will produce the following warning at the start of the block:

  label: { // this line may need to be moved but within the same indentation depth

The placement of this line is ClassCracker's best initial guess, which is at the top or at the bottom of the range of possible placements. This line is always at the correct depth of indentation. This line may be moved manually within the range. It will often be possible to infer the required position of the start of the labelled block by examining the code.

The following example explains the procedure:

  int k = 5;
  ...
  while( i < 10 ) {       // this is the upper limit of the range
    k++;
    k = 5.2;
    L1: {               // this is ClassCracker's guess
      while( i < 5 ) {   // this is the lower limit of the range
        ...
        break L1;       // this is the break to labelled block
        ...
      }                // this is the correct end of the labelled block
    }
  }
  ...

Examination of this code shows that the variable k is first used as an int and then is used as a float or double. Clearly, k has been redefined in a new block, and the above code should be manually modified to:

  int k = 5;
  ...
  while( i < 10 ) {
    k++;
    L1: {
      k = 5.2;
      while( i < 5 ) {
        ...
        break L1;
        ...
      }
    }
  }
  ...

Unlabelled Blocks

It is possible in Java to have blocks which are not referred to by a  break label;  statement. These blocks are therefore unlabelled, for example:

  ...
  int k = 5;
  ...
  {    // this is the start of an unlabelled block
  boolean k = true;
  ...
  ...
  }    // this is the end of an unlabelled block
  ...
  k++;
  ...

Detection of unlabelled blocks is currently not possible with ClassCracker.

Strange Decompiled Code

On rare occasions, the decompiled code produced by ClassCracker will appear to be nonsensical or not logical, even though the code itself is valid Java code that can be readily recompiled. For example, empty  if..else  blocks:

  ...
  if( a == b ) {
  }
  else {
  }
  ...

Such "strange" code is the result of certain advanced optimisations used by some compilers. ClassCracker can already handle a large number of optimisations employed by the most well-known Java compilers. However, as the number of different Java compilers grows and each of these evolves through different version numbers, the number and types of optimisations is potentially infinite. No decompiler (including ClassCracker) can ever hope to handle them all!

However, it is always possible to decompile the "strange" code by decompiling it as jasm (Java Assembly) code and then manually converting the jasm code to java code - even though this process can be time consuming and requires a good knowledge of the JVM (Java Virtual Machine) opcodes.

In such cases, ClassCracker's ability to produce jasm code is a distinct advantage over other decompilers that can only produce java or jdump code.

Inner Classes

The Java Language Specification 2nd Edition and Java Virtual Machine Specification 2nd Edition allow inner classes in Java. There are three types of inner classes in Java:

  1. member inner classes
  2. local inner classes
  3. anonymous inner classes

Member Inner Classes

Member inner classes are classes that are defined within another class - the outer class. For example:

  class Outer {
    ...
    class Inner {
      ...
      class Deeper {
        ...
      }
    }
    ...
  }

When the above code is compiled the files Outer.class, Outer$Inner.class and Outer$Inner$Deeper.class are created.

ClassCracker is able to decompile each of these three classes but for the two inner classes, it displays a note preceeding the class definition in the decompiled output to indicate that these are inner classes. For example, when Outer$Inner$Deeper.class is decompiled, the output will contain the following note:

  /*
  NOTE:
  This is an inner class named "Deeper"
    within a class named "Inner"
      within a class named "Outer"
  */

This note assists the user in manually modifying of the decompiled code to insert the source code of the inner classes into their outer classes. Note that this is purely optional and is not necessary - recompilation of the three source files Outer.java, Outer$Inner.java and Outer$Inner$Deeper.java produced by ClassCracker will produce three class files that are functionally identical to the original class files (see "Why doesn't ClassCracker automatically insert inner classes in their outer classes?" below).

Local Inner Classes

Local inner classes are classes that are defined within a method which is a member of an outer class. For example:

  class Outer {
    ...
    void DoIt() {
      ...
      class Inner {
        ...
      }
    }
    ...
  }

When the above code is compiled the files Outer.class and Outer$1$Inner.class are created.

When Classcracker decompiles Outer$1$Inner.class, the output will contain the following note:

  /*
  NOTE:
  This is an inner class named "Inner"
    which is local to method "1" (method name was compiler-generated)
      within a class named "Outer"
  */

The problem here is that the method name "1" is compiler-generated and bears no relationship to the method name, method location or method index (eg. in a Outer.dump file). It is, therefore, difficult to establish in which method the inner class is located. This problem is a consequence of the design of Java compilers and of the structure of the Java Virtual Machine - it is not a deficiency in ClassCracker itself.

Visual inspection of the decompiled code will often allow an educated guess of the method where the inner class should be located. In any case, this is not necessary as recompilation of the Outer.java and Outer$1$Inner.java files produced by ClassCracker will produce two class files that are functionally identical to the original class files.

Anonymous Inner Classes

An anonymous inner class is an unnamed class that is defined within an outer class. For example:

  public class Outer {
    String s = "Hello";
    public Enumeration listem() {
      return new Enumeration()
      { // start of anonymous class
        int x;
        public boolean hasMoreElements() {
          return x < s.length();
        }
        public Object nextElement() {
          return new Character( s.charAt(x++));
        }
      }; // end of anonymous class
    }
  }

When the above code is compiled the files Outer.class and Outer$1.class are created.

When Classcracker decompiles Outer$1.class, the output will contain the following note:

  /*
  NOTE:
  This is an anonymous inner class
    within a class named "Outer"
  */

As for local inner classes the "1" in the Outer$1.class name is compiler-generated and gives no indication of where the anonymous class is located within the outer class. A guess of this location may be made by visual inspection of the decompiled code from Outer.class. As before, this is not necessary since recompilation of the Outer.java and Outer$1.java files will produce class files which are functionally identical to the original class files.

Why doesn't ClassCracker automatically insert inner classes in their outer classes?

Java is designed such that each class is compiled to an individual class file. This means that, for example, a source file myApplet.java may contain several class file definitions (both normal classes or inner classes) but when compiled, a separate class file will be generated for each class in the myApplet.java file.

Therefore, each class file contains the definition of only a single Java class. A class file never contains the definition of both the outer class and inner classes - the inner class definitions are in separate class files. The structure of class files is such that, at best, the class file which defines the outer class usually (but not always!) contains a reference to the name only of an inner class and not its location within the outer class.

Because Java is class-file oriented, the task of a Java decompiler is to convert individual class files to java files. Java applications and applets are usually composed of more than one class file, but a decompiler can never assume that all class files are present. It is not the primary task of a Java decompiler to decompile a whole application or applet - although this is often possible in practice, especially with jar files.

In summary, ClassCracker does not automatically insert inner classes in their outer classes because:

The last two points would require ClassCracker to make an educated guess, with the inherent risk of an erroneous decompilation. It has always been the ClassCracker philosophy to opt for the solution which guarantees accurate decompilation.

"Fake" Inner Classes

The Java language allows the $ symbol to be part of a class name. Thus it is perfectly legal to give an outer class a name containing the $ symbol. This would give the mistaken impression that this is an inner class when it is really an outer class.

For example a file named Fake$Inner.java containing:

  class Fake$Inner {
    ...
  }

could be compiled and a file named Fake$Inner.class would be generated. It could then be assumed that Fake$Inner.class refers to an inner class!

ClassCracker is able to detect such "fake" inner classes and will not produce a note such as

  /*
  NOTE:
  This is an inner class named "Inner"
    within a class named "Fake"
  */

in its decompiled output.


Copyright © 2002 Mayon Enterprises Pty Ltd.