Assembler: Understanding assembler errors

Lets take a look at a basic method. First as source code, then with the assembler.

public static void main(String[] args) {
    String line;
    Scanner scanner = new Scanner(System.in);
    while ((line = scanner.nextLine()).length() > 0) {
        System.out.println("COMPUTED: " + Calculator.evaluate(line));
    }
}

We have two variables here. The main method’s arguments array is unused. Then we have nextLine which is simply set to whatever the user types in. Each time the user inputs a non-empty string we evaluate the text in nextLine then print it out with the prefix "COMPUTED: ". Simple enough, now lets take a look at it in the assembler. I’ve added some comments to better illustrate what portions of the bytecode relate to parts of the source code.

DEFINE PUBLIC STATIC main([Ljava/lang/String; args)V
// Scanner scanner = new Scanner(System.in)
A:
NEW java/util/Scanner
DUP
GETSTATIC java/lang/System.in Ljava/io/InputStream;
INVOKESPECIAL java/util/Scanner.<init>(Ljava/io/InputStream;)V
ASTORE scanner
// line = scanner.nextLine()
B:
ALOAD scanner
INVOKEVIRTUAL java/util/Scanner.nextLine()Ljava/lang/String;
DUP
ASTORE line
// (line).length() > 0    (break loop if line <= 0)
C:
INVOKEVIRTUAL java/lang/String.length()I
IFLE E
// System.out.println("COMPUTED: " + Calculator.evaluate(line))
D:
GETSTATIC java/lang/System.out Ljava/io/PrintStream;
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init>()V
LDC "COMPUTED: "
INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD line
INVOKESTATIC calc/Calculator.evaluate(Ljava/lang/String;)D
INVOKEVIRTUAL java/lang/StringBuilder.append(D)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;
INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
GOTO B
E:
RETURN

Perfectly valid bytecode here. Now Lets introduce different errors and see what messages we get back.

Cannot pop operand off an empty stack

Here is a modification to the above code around label C that removes the calling context from the call to line.length()

This will result in an attempt to pop the calling context off the stack, which is empty, causing the error.

C:
// String is on the stack
POP
// The String has been removed frrom the stack, so now the stack is empty
INVOKEVIRTUAL java/lang/String.length()I
IFLE E

Copying an uninitialized value should not occur

Here is a modification to the above code that introduces a typo when using the variable line. Instead it will be referenced as loon. This is a more tricky case to detect at times since:

This will result in an attempt to resolve two separate variables since they do not share the same name, causing the error.

B:
ALOAD scanner
INVOKEVIRTUAL java/util/Scanner.nextLine()Ljava/lang/String;
DUP
// Here the variable is refered to as "line"
ASTORE line
C:
INVOKEVIRTUAL java/lang/String.length()I
IFLE E
D:
GETSTATIC java/lang/System.out Ljava/io/PrintStream;
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init>()V
LDC "COMPUTED: "
INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
// Here the variable is refered to as "loon" which has never been specified before
// Since the variable has never had its value set, this code is illegal
// (NULL is not a default for object-type variables, but 0 is for primitives)
ALOAD loon
INVOKESTATIC calc/Calculator.evaluate(Ljava/lang/String;)D
INVOKEVIRTUAL java/lang/StringBuilder.append(D)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;
INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
GOTO B
E:
RETURN

The solution here is to double check for typos.

However there are cases with heavy obfuscation where Recaf may get confused. Please open bug reports with examples of this when they occur.

Argument type was "Apple" expected "Orange"

Here is a modification to the above code that replaces java/util/Scanner with example/ExtendedScanner. In our example let us assume that example/ExtendedScanner is a class that extends java/util/Scanner. Let us also assume that we do not have example/ExtendedScanner loaded into Recaf in any of the workspace's resources.

This will result in an attempt to resolve the type hierarchy between java/util/Scanner and example/ExtendedScanner. Since we do not have example/ExtendedScanner loaded into Recaf, we are unable to prove that it extends java/util/Scanner. Thus the assembler will default to assuming the class extends java/lang/Object. Calling any non-object method will result in a type conflict, causing the error.

A:
NEW example/ExtendedScanner
DUP
GETSTATIC java/lang/System.in Ljava/io/InputStream;
INVOKESPECIAL example/ExtendedScanner.<init>(Ljava/io/InputStream;)V
ASTORE scanner
// ExtendedScanner extends Scanner... We as people know this, but Recaf does not.
B:
ALOAD scanner
INVOKEVIRTUAL java/util/Scanner.nextLine()Ljava/lang/String;

The solution here is to add the code that contains example/ExtendedScanner to the workspace.

Cannot call method on null reference

Here is a modification to the above code that replaces the instantiation of the java/util/Scanner with a single null.

This will cause the method call on the scanner object to call on a provable null value, causing the error.

A:
ACONST_NULL
ASTORE scanner
// This will effectively translate to:
// null.nextLine()
B:
ALOAD scanner
INVOKEVIRTUAL java/util/Scanner.nextLine()Ljava/lang/String;

The solution here is to make sure you track where null is pushed onto the stack via ACONST_NULL.

Expected type: Lcom/example/Something;

This can occur in a few situations, but the cause is the same. The usage of some type indicates it should be the given type shown in the error, but whatever was present on the stack was a different type.