ClassCracker 3 Java Decompiler Simple Tests

I performed some simple tests on ClassCracker 3 (version 3.01), purely as a decompiler.

Fibo

For source code, see DecompilerFiboTestSource. The output from ClassCracker was this:
/* CONVERSION by ClassCracker 3 */

import java.io.PrintStream;

class Fibo extends Object
{

  Fibo( )
  {
  }

  private static int fib( int locVar_0)
  {
    if( locVar_0 > 1 )  {
      return  ( fib( ( locVar_0 - 1 ) ) + fib( ( locVar_0 - 2 ) ) );
    }
    return  locVar_0;
  }

  public static void main( String[] args)  throws Exception
  {
    int locVar_1;
    int locVar_2;
    Object locVar_3;

    locVar_1 = 0;
    try {
      locVar_1 = Integer.parseInt( args[ 0 ] );
    }
    catch( Exception locVar_3 ) {
      System.out.println( "Input error" );
      System.exit( 1 );
    }
    locVar_2 = fib( locVar_1 );
    System.out.println( (new StringBuffer(  )).toString(  ) + "fibonacci(" +
String.valueOf( locVar_1 ) + ") = " + String.valueOf( locVar_2 ) );
  }
}
The variable names aren't as easy to read as some of the other decompilers, but that's a minor point. The println call is also more difficult to read than most. When recompiled, the above results in an error:
Fibo.java:31: locVar_3 is already defined in main(java.lang.String[])
    catch( Exception locVar_3 ) {
ClassCracker doesn't seem to have been tested with many programs containing exceptions. When I commented out the first declaration of locVar_3, the program compiled and ran correctly.

Casting

For source code, see DecompilerCastingTestSource. Here is the output from ClassCracker for main:
  public static void main( String[] args)
  {
    int locVar_1;

    locVar_1 = 0;
    while( locVar_1 < 128 )  {
      System.out.println( (new StringBuffer(  )).toString(  ) + "ascii " +
String.valueOf( locVar_1 ) + " character " + String.valueOf( locVar_1 ) );
      locVar_1 = ( (char) ( locVar_1 + 1 ) );
    }
  }
Here, the char loop variable has been changed to int, and there is no cast back to char, so the program does not work like the original. The output compiled without errors, and if a cast is added to the last use of locVar_1, the decompiled program works the same as the original.

Inner classes

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

/* CONVERSION by ClassCracker 3 */

public class Usa extends Object
{
  public String name;

  public Usa( )
  {
    this.name = "Detroit";
  }
}
This is equivalent to what is literally in the file Usa.class. However, there is are also files Usa$England.class and Usa$England$Ireland.class (which show up clearly in ClassCracker's GUI interface). When the latter is decompiled, the result is
/* CONVERSION by ClassCracker 3 */

import java.io.PrintStream;

/*
NOTE:
This is an inner class named "Ireland"
  within a class named "England"
    within a class named "Usa"
*/

public class Usa$England$Ireland extends Object
{
  public String name;
  private final Usa$England this$1;

  public Usa$England$Ireland( Usa$England locVar_1)
  {
    this.this$1 = locVar_1;
    this.name = "Dublin";
  }

  public void print_names( )
  {
    System.out.println( this.name );
  }
}
Unfortunately, the above doesn't compile. At least, it appears to be "inner class aware".

Deuces Wild

As a much larger test (class file is about 30 times as large as Fibo's), I tried to decompile a file called deuceswild.class. It is an applet of some 38K bytes (.class file), which plays the casino game of Deuces Wild. It also calculates the optimal play, and prompts the user if an error (suboptimal play) is made. The decompilation was quite fast (for a decompiler written in Java); about 2 seconds, but only the first 5 methods are decompiled. Because of this, the output was not compilable. It did not have the problem with indeck that JReversePro did. The output is cluttered with many unnecessary and distracting prefixes of " this. ".

Sable Test Program

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

  public static void f( short locVar_0)
  {
    Object locVar_1;
    Object locVar_2;
    Object locVar_3;
    int locVar_4;

    if( locVar_0 > 10 )  {
      locVar_2 = (new Rectangle( locVar_0, locVar_0 ));
      locVar_4 = locVar_2.isFat(  );
      locVar_3 = locVar_2;
    }
    else {
      locVar_1 = (new Circle( locVar_0 ));
      locVar_4 = locVar_1.isFat(  );
      locVar_3 = locVar_1;
    }
    if( locVar_4 == 0 )  {
      locVar_3.draw(  );
    }
  }
No attempt has been made to type the references; they are all of type Object, and there are't even casts to allow the code to recompile. Even the boolean is typed as an int (other decompilers are able to type it as a boolean, presumably from the return type of isFat. Using the "batch mode" of ClassCracker on all 4 class files at once did not produce any improvement.

Optimised Bytecodes

For source code, see DecompilerOptimisedTestSource. Output from ClassCracker for this code was (method f only):

  public static void f( short locVar_0)
  {
    Object locVar_1;

    if( locVar_0 > 10 )  {
      locVar_1 = (  );
      locVar_0 = locVar_1.isFat(  );
      locVar_1 = locVar_1;
    }
    else {
      locVar_1 = (  );
      locVar_0 = locVar_1.isFat(  );
      locVar_1 = locVar_1;
    }
    if( locVar_0 == 0 )  {
      locVar_1.draw(  );
    }
  }
All decompilers apart from Dava seem to have trouble with this code. In addition to the above problems, the constructors for the Rectangle and Circle objects are missing.

Simple control flow

For source code, see DecompilerControlFlowTestSource. ClassCracker produces:

  public int foo( int locVar_1, int locVar_2)
  {
    Object locVar_3;

    L0: {  // this line may need to be moved but within the same
indentation depth
      break L0;
      try {
        while( locVar_1 < locVar_2 )  {
          locVar_2 ++;
          locVar_1 = ( locVar_2 / locVar_1 );
        }
      }
    }
    catch( RuntimeException locVar_3 ) {
      locVar_1 = 10;
      break L0;
    }
    return  locVar_2;
  }
This code doesn't even compile. The first break L0 is clearly wrong, and neither of the break statements is in a loop. The compiler (Sun's javac) does not consider that the try and catch are connected.

Exceptions

For source code, see DecompilerExceptionTestSource. Here is Class Cracker's output:

  public void foo( )
  {
    L1: {  // this line may need to be moved but within the same
indentation depth
      System.out.println( "a" );
      try {
        System.out.println( "b" );
        try {
          System.out.println( "c" );
        }
        catch( RuntimeException this ) {
          System.out.println( "g" );
          break L1;
        }
        System.out.println( "d" );
      }
      catch( Exception this ) {
        System.out.println( "e" );
      }
    }
    System.out.println( "f" );
  }
This looks somewhat promising, but fails for a number of reasons. The output doesn't compile, because this can't be used as an exception variable. If an Exception occurs in block b, flow will go to e, which is not original program behaviour. Also, an Exception in c does not go to e as it should.

Conclusion

The GUI for this product takes a little getting used to, but after a while it becomes natural. It seems very fast for a Java based decompiler, but that could be because it only decompiles the first 5 methods (only in the demo version, obviously). Unfortunately, the decompiler, despite now being in its third version, needs some work to cater for the tricky cases covered in these tests.

CategoryDecompilation

Revision: r1.10 - 13 Feb 2003 - 22:17 - MikeVanEmmerik
Copyright © 1999-2020 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback