usethesource/flybytes

Bug: Error when decompiling method with one argument

bys1 opened this issue · 6 comments

bys1 commented

I have compiled the following class with javac:

public class Test {

    public int test1() {
        return 3;
    }

    public int test2(int x) {
        return 5;
    }

}

When decompiling this using Flybytes (decompile from lang::flybytes::Decompiler), it throws an error:

|jar+file:///Users/bys1/.m2/repository/org/rascalmpl/flybytes/0.1.7/flybytes-0.1.7.jar!/src/lang/flybytes/Disassembler.rsc|(1465,237,<18,0>,<20,75>): Java("IndexOutOfBoundsException","Index 1 out of bounds for length 0")
	at jdk.internal.util.Preconditions.outOfBounds(|unknown:///Preconditions.java|(0,0,<64,0>,<64,0>))
	at jdk.internal.util.Preconditions.outOfBoundsCheckIndex(|unknown:///Preconditions.java|(0,0,<70,0>,<70,0>))
	at jdk.internal.util.Preconditions.checkIndex(|unknown:///Preconditions.java|(0,0,<266,0>,<266,0>))
	at java.util.Objects.checkIndex(|unknown:///Objects.java|(0,0,<359,0>,<359,0>))
	at java.util.ArrayList.get(|unknown:///ArrayList.java|(0,0,<427,0>,<427,0>))
	at lang.flybytes.internal.ClassDisassembler.formals(|unknown:///ClassDisassembler.java|(0,0,<229,0>,<229,0>))
	at lang.flybytes.internal.ClassDisassembler.method(|unknown:///ClassDisassembler.java|(0,0,<205,0>,<205,0>))
	at lang.flybytes.internal.ClassDisassembler.methods(|unknown:///ClassDisassembler.java|(0,0,<178,0>,<178,0>))
	at lang.flybytes.internal.ClassDisassembler.readClass(|unknown:///ClassDisassembler.java|(0,0,<105,0>,<105,0>))
	at lang.flybytes.internal.ClassDisassembler.disassemble(|unknown:///ClassDisassembler.java|(0,0,<92,0>,<92,0>))
	at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(|unknown:///NativeMethodAccessorImpl.java|(0,0,<0,0>,<0,0>))
	at disassemble(|jar+file:///Users/bys1/.m2/repository/org/rascalmpl/flybytes/0.1.7/flybytes-0.1.7.jar!/src/lang/flybytes/Disassembler.rsc|(1685,5,<20,58>,<20,63>))
	at $shell$(|prompt:///|(0,48,<1,0>,<1,48>)ok
rascal>

This happens whenever the class contains a method with exactly one argument. When I remove the method test2, or remove the argument x from test2, the class decompiles fine. When I add another argument to test2, such as int y, it also decompiles without errors. However, with one argument, it throws the above error. Since I would expect Flybytes to be able to decompile such a simple Java class, I am assuming that this is a bug.

Thanks for reporting this; and especially for the simple example that triggers this bug.

I've been having some trouble reproducing the stacktrace:

rascal>decompile(|project://flybytes/target/classes/lang/flybytes/internal/Test.class|, "test2")
Method: method(
  methodDesc(
    integer(),
    "test2",
    [integer()]),
  [var(
      integer(),
      "x")],
  [return(const(
        integer(),
        5))],
  modifiers={public()})

Perhaps its because my test class is nested in a package. going to try and move it to the root now.

So we're looking at the following code, and it does depend on some contextual parameters, namely whether or not javac was called with the -debug flag, and/or the -parameters flag:

private IList formals(List<ParameterNode> parameters, List<LocalVariableNode> locals, IList types, boolean isStatic) {
		IListWriter lw = VF.listWriter();
	
		if (parameters != null && !parameters.isEmpty()) {
			// only when class was compiled with javac -parameters
			int i = 0;
			for (IValue elem : types) {
				lw.append(ast.Formal_var((IConstructor) elem, parameters.get(i++).name));
			}
		}
		else if (locals != null && locals.size() >= types.length() - (isStatic?0:1)) {
			// only when class was compiled with javac -debug
			int i = 0;
			for (IValue elem : types) {
                                 // HERE the exception is thrown; `locals` does not have enough elements apparantly. 
				LocalVariableNode local = locals.get(i + (isStatic?0:1));
				lw.append(ast.Formal_var((IConstructor) elem, local.name));
				i++;
			}
		}
		else {
			// otherwise we "invent" the parameter names
			int i = 0;
			for (IValue elem : types) {
				lw.append(ast.Formal_var((IConstructor) elem, "arg_" + i));
			}
		}

		return lw.done();
	}

This was after using javac Test.java without the -g and without -parameters. With either of the parameters added, the bug does not trigger.

rascal>decompile(|project://flybytes/src/Test.class|)
)
|file:///Users/jurgenv/git/flybytes/src/lang/flybytes/Disassembler.rsc|(1465,237,<18,0>,<20,75>): Java("IndexOutOfBoundsException","Index 1 out of bounds for length 0")
        at jdk.internal.util.Preconditions.outOfBounds(|unknown:///Preconditions.java|(0,0,<64,0>,<64,0>))
        at jdk.internal.util.Preconditions.outOfBoundsCheckIndex(|unknown:///Preconditions.java|(0,0,<70,0>,<70,0>))
        at jdk.internal.util.Preconditions.checkIndex(|unknown:///Preconditions.java|(0,0,<248,0>,<248,0>))
        at java.util.Objects.checkIndex(|unknown:///Objects.java|(0,0,<372,0>,<372,0>))
        at java.util.ArrayList.get(|unknown:///ArrayList.java|(0,0,<459,0>,<459,0>))
        at lang.flybytes.internal.ClassDisassembler.formals(|unknown:///ClassDisassembler.java|(0,0,<229,0>,<229,0>))
        at lang.flybytes.internal.ClassDisassembler.method(|unknown:///ClassDisassembler.java|(0,0,<205,0>,<205,0>))
        at lang.flybytes.internal.ClassDisassembler.methods(|unknown:///ClassDisassembler.java|(0,0,<178,0>,<178,0>))
        at lang.flybytes.internal.ClassDisassembler.readClass(|unknown:///ClassDisassembler.java|(0,0,<105,0>,<105,0>))
        at lang.flybytes.internal.ClassDisassembler.disassemble(|unknown:///ClassDisassembler.java|(0,0,<92,0>,<92,0>))
        at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(|unknown:///NativeMethodAccessorImpl.java|(0,0,<0,0>,<0,0>))
        at disassemble(|file:///Users/jurgenv/git/flybytes/src/lang/flybytes/Disassembler.rsc|(1685,5,<20,58>,<20,63>))
        at $shell$(|prompt:///|(0,57,<1,0>,<1,57>)ok

Ok the bug is fixed. However, it does show that the decompiler is depending on some of the debug information. You see that the constructor is not reduced to a super call anymore, but that the load instruction remains in an asm block. This is a different kind of bug though.

Need to figure out a better way to test this in the current project setup. We should have the same files compiled with different javac settings and then decompiled to comparable ASTs.