Decompilation Nmi Test

Program-Transformation.Org: The Program Transformation Wiki

NMI Java Code Viewer

This is a commercial bytecode to Java decompiler and disassembler. It runs under Windows only, even though it claims to have been written in Java. I ran an evaluation copy of version 6.0. For brevity, NMI Java Code Viewer will be abbreviated to NJCV. Results are startlingly similar to those of Jad; the only difference is that Jad irritatingly puts data declarations after the methods.

Fibo

For source, see DecompilerFiboTestSource. Decompiled source from NJCV:
// NMI's Java Code Viewer 6.0 
// http://www.njcv.tk
// Registered to Evaluation Copy
// Generated Sun Feb 23 2003 22:20:45 
// Source File Name:   Fibo.java

import java.io.PrintStream;

class Fibo {
    Fibo() {
    }
    private static int fib(int i) {
        if(i > 1)
            return fib(i - 1) + fib(i - 2);
        else
            return i;
    }

    public static void main(String args[]) throws Exception {
        int i = 0;
        try {
            i = Integer.parseInt(args[0]);
        }
        catch(Exception exception) {
            System.out.println("Input error");
            System.exit(1);
        }
        int j = fib(i);
        System.out.println("fibonacci(" + i + ") = " + j);
    }
}

As you can see, the decompilation is almost identical to the source. The output compiled and ran perfectly with no changes required.

Casting

For source, see DecompilerCastingTestSource. Here is the output from NJCV:

public class Casting {
    public Casting() {
    }
    public static void main(String args[]) {
        for(char c = '\0'; c < 128; c++)
            System.out.println("ascii " + (int)c + " character " + c);
    }
}
As you can see, it's quite compact, readable, very similar to the original, and is correct. No modifications were needed to recompile it.

Inner classes

For source, see DecompilerInnerClassesTestSource. When decompiled with NJCV (turning on "inner class support"), the result is

public class Usa {
    public class England {
        public class Ireland {
            public String name;
            public void print_names() {
                System.out.println(name);
            }
            public Ireland() {
                name = "Dublin";
            }
        }
        public String name;
        public England() {
            name = "London";
        }
    }
    public String name;
    public Usa() {
        name = "Detroit";
    }
}

It reproduced the inner classes correctly.

Deuces Wild

This is a 38K applet with two dimensional arrays of integers, some floating point code, and so on. It decompiled in a fraction of a second, with no errors, and recompiled and ran perfectly with no modifications. The arrays of strings are initialised naturally, e.g.

    String cardrank[] = {
        "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine",
"Ten", "Jack", 
        "Queen", "King", "Ace"
    };
However, the string before this one also had ten strings on one line, producing a line that had well over a hundred columns. This is a very minor issue.

Sable Test Program

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

    public static void f(short word0) {
        Object obj;
        boolean flag;
        if(word0 > 10) {
            Rectangle rectangle = new Rectangle(word0, word0);
            flag = rectangle.isFat();
            obj = rectangle;
        } else {
            Circle circle = new Circle(word0);
            flag = circle.isFat();
            obj = circle;
        }
        if(!flag)
            ((Drawable) (obj)).draw();
    }

As you can see, it had no trouble with the tricky d variable (here called obj), and produced correct code which compiled without modification. However, the variable d of the original program becomes here obj of type Object, but it's cast as needed. Better decompilers infer that obj should be of type Drawable.

Optimised code.

For source, see DecompilerOptimisedTestSource. This was the result from NJCV for the method f:

    public static void f(short word0) {
        Object obj;
        if(word0 > 10) {
            obj = JVM INSTR new #37  <Class Rectangle>;
            ((Rectangle) (obj)).Rectangle(word0, word0);
            word0 = ((Rectangle) (obj)).isFat();
            obj = obj;
        } else {
            obj = JVM INSTR new #15  <Class Circle>;
            ((Circle) (obj)).Circle(word0);
            word0 = ((Circle) (obj)).isFat();
            obj = obj;
        }
        if(word0 == 0)
            ((Drawable) (obj)).draw();
    }

As you can see, it is confused with the two constructors. When the above problems were corrected e.g. obj = new Rectangle(word0, word0), the code still did not compile, because NJCV failed to separate the ranges of the local variable (part short, part boolean).

Simple control flow

For source, see DecompilerControlFlowTestSource. NJCV produces:

class foo {
    foo() {
    }
    public int foo(int i, int j) {
        while(true) 
            try {
                while(i < j) 
                    i = j++ / i;
                break MISSING_BLOCK_LABEL_28;
            }
            catch(RuntimeException runtimeexception) {
                i = 10;
            }
        return j;
    }
    public void main() {
        foo(2, 3);
    }
}
The "=while(true)" is preserved, but it has not gotten the continue or break correct.

Exceptions

NJVC produces this:
class foo {
    foo() {
    }
    public void foo() {
        System.out.println("a");
        System.out.println("b");
        System.out.println("c");
        break MISSING_BLOCK_LABEL_39;
        this;
        System.out.println("g");
        break MISSING_BLOCK_LABEL_59;
        System.out.println("d");
        break MISSING_BLOCK_LABEL_59;
        this;
        System.out.println("e");
        System.out.println("f");
        return;
    }
    public static void main() {
        foo foo1 = new foo();
    }
}
This code will obviously not compile, and it has made no attempt to separate the exception handling cases. In fact, the execptions have all gone.

Life

This version of Conway's Game of Life was compiled from Ada 95 source code. The applet is at http://www.appletmagic.com/download/demo/LifeRect.html. NJCV seemed to be happy decompiling one class at a time, but it produced code with errors, e.g.

        if(Result >= 0) goto _L2; else goto _L1
_L1:
        int i = Result + 60;
        i;
        if(i < 0 || i > 59)
            throw new IndexOutOfBoundsException();
        return;
_L2:
This would appear to be a simple if-then-else. Note the line with just "i;"; what is missing here? At least the index bounds checking is natural (compare with JODE output, which has a "do while(false)" loop).

NJCV was able to type some parameters that JODE regarded as type errors (e.g. the parameter to Directed_Copy).

At least it never bombs out with runtime errors, so it would presumably be possible to edit the code to get it to work properly (with a fair bit of effort).

Conclusion

This is overall a good decompiler: very fast, good output quality. It does have a few problems here and there, but most of the time it's easy to correct. Its output is not significantly better than that of Jad (which is free for non commercial use), but with NJCV you get support.

CategoryDecompilation
-- MikeVanEmmerik - 23 Feb 2003