Search code examples
javabytecodejava-bytecode-asm

Bad local variable type in method


I'm using ASM 4 to generate some classes on the fly. Everything went quite well until I got to generating code to do exception handling. The generated bytecode is at the bottom. Here is the error I'm getting:

java.lang.VerifyError: Instruction type does not match stack map in method some.eval.ToEvaluate$0.apply()Ljava/lang/Object; at offset 44
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2404)
at java.lang.Class.getConstructor0(Class.java:2714)
at java.lang.Class.newInstance0(Class.java:343)
at java.lang.Class.newInstance(Class.java:325)
    ...

Here's the bytecode:

// Compiled from com/pkg/some/Source.java (version 1.7 : 51.0, super bit)
public class some.eval.ToEvaluate$0 extends com.pkg.lang.Lambda0 {

  // Method descriptor #7 ()V
  // Stack: 1, Locals: 1
  public ToEvaluate$0();
    0  aload_0 [this]
    1  invokespecial com.pkg.lang.Lambda0() [9]
    4  return
      Line numbers:
        [pc: 0, line: 1]
        [pc: 0, line: 2]
        [pc: 4, line: 3]
      Local variable table:
        [pc: 0, pc: 5] local: this index: 0 type: new some.eval.ToEvaluate(){}

  // Method descriptor #13 ()Ljava/lang/Object;
  // Stack: 5, Locals: 3
  public java.lang.Object apply();
     0  getstatic com.pkg.some.Primitives.equal : com.pkg.lang.Lambda [19]
     3  checkcast com.pkg.lang.Lambda2 [21]
     6  getstatic com.pkg.some.Primitives.divide : com.pkg.lang.Lambda [26]
     9  checkcast com.pkg.lang.Lambda2 [21]
    12  ldc2_w <Long 1> [27]
    15  invokestatic java.lang.Long.valueOf(long) : java.lang.Long [34]
    18  ldc2_w <Long 0> [35]
    21  invokestatic java.lang.Long.valueOf(long) : java.lang.Long [34]
    24  invokevirtual com.pkg.lang.Lambda2.apply(java.lang.Object, java.lang.Object) : java.lang.Object [39]
    27  astore_1 [v1]
    28  goto 44
    31  astore_2 [e]
    32  new some.lambda.ToRun$1 [41]
    35  dup
    36  invokespecial some.lambda.ToRun$1() [42]
    39  aload_2 [e]
    40  invokevirtual com.pkg.lang.Lambda1.apply(java.lang.Object) : java.lang.Object [47]
    43  astore_1
    44  ldc2_w <Long -1> [48]
    47  invokestatic java.lang.Long.valueOf(long) : java.lang.Long [34]
    50  invokevirtual com.pkg.lang.Lambda2.apply(java.lang.Object, java.lang.Object) : java.lang.Object [39]
    53  areturn
      Exception Table:
        [pc: 6, pc: 28] -> 31 when : java.lang.Throwable
      Line numbers:
        [pc: 6, line: 50]
        [pc: 12, line: 21]
        [pc: 18, line: 21]
        [pc: 31, line: 51]
        [pc: 32, line: 52]
        [pc: 44, line: 54]
        [pc: 44, line: 21]
      Local variable table:
        [pc: 0, pc: 54] local: this index: 0 type: new some.eval.ToEvaluate(){}
        [pc: 28, pc: 31] local: v1 index: 1 type: java.lang.Object
        [pc: 32, pc: 44] local: e index: 2 type: java.lang.Throwable
        [pc: 44, pc: 44] local: v2 index: 1 type: java.lang.Object
      Stack map table: number of frames 2
        [pc: 31, same_locals_1_stack_item, stack: {java.lang.Throwable}]
        [pc: 44, full, stack: {com.pkg.lang.Lambda2}, locals: {some.eval.ToEvaluate$0, java.lang.Object}]
}

I used ASMifier to start with on this:

public static Object trycatch(Object test, Lambda1 handler) {
    Object v;
    try {
        v = test;
    } catch (Throwable e) {
        v = handler.apply(e);
    }
    return v;
}

but then I had to modify it to make it general. Here's the code that's generating the try/catch part:

    int varOffset = context.getVarOffset();

    Label l0 = new Label();
    Label l1 = new Label();
    Label l2 = new Label();
    Label l3 = new Label();
    Label l4 = new Label();
    Label l5 = new Label();

    // mv.visitLocalVariable("v", "Ljava/lang/Object;", null, l1, l2, 2); // 2 == varOffset + 0
    context.push(1, new VarInfo(varOffset, "v1", l1, l2, false, "java/lang/Object"));
    // mv.visitLocalVariable("v", "Ljava/lang/Object;", null, l3, l5, 2); // 2 == varOffset + 0
    context.push(1, new VarInfo(varOffset, "v2", l3, l5, false, "java/lang/Object"));
    // mv.visitLocalVariable("e", "Ljava/lang/Throwable;", null, l4, l3, 3); // 3 == varOffset+1
    context.push(1, new VarInfo(varOffset + 1, "e", l4, l3, false, "java/lang/Throwable"));

    mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Throwable");
    mv.visitLabel(l0);
    mv.visitLineNumber(50, l0);

    args[0].visit(context, mv); // mv.visitVarInsn(ALOAD, 0); // execute block
    mv.visitVarInsn(ASTORE, varOffset); // store v, the result

    mv.visitLabel(l1);
    mv.visitJumpInsn(GOTO, l3);
    mv.visitLabel(l2);
    mv.visitLineNumber(51, l2);
    // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] { "java/lang/Throwable" });
    mv.visitVarInsn(ASTORE, varOffset + 1); // e
    mv.visitLabel(l4);
    mv.visitLineNumber(52, l4);

    args[1].visit(context, mv); // mv.visitVarInsn(ALOAD, 1); // catch block
    mv.visitVarInsn(ALOAD, varOffset + 1); // e

    mv.visitMethodInsn(INVOKEVIRTUAL, "com/pkg/lang/Lambda1", "apply", "(Ljava/lang/Object;)Ljava/lang/Object;");
    mv.visitVarInsn(ASTORE, varOffset); // store v, the result

    mv.visitLabel(l3);
    mv.visitLineNumber(54, l3);
    // mv.visitFrame(F_APPEND, 1, new Object[] { "java/lang/Object" }, 0, null);
    mv.visitVarInsn(ALOAD, varOffset); // load v, the result
    // mv.visitInsn(ARETURN);
    mv.visitLabel(l5);
    // mv.visitLocalVariable("test", "Ljava/lang/Object;", null, l0, l5, 0);
    // mv.visitLocalVariable("handler", "Lcom/pkg/lang/Lambda1;", null, l0, l5, 1);

Solution

  • I asked on the ASM list and someone kindly provided this tip:

    "Try using a CheckClassAdapter with the checkDataFlow option to have more details."

    This with some troubleshooting fixed the problem. I'm pretty sure it was related to not properly differentiating between the scope of bound variables, and needing to declare local vars with visitLocalVariable. At least that's one of the things I fixed between it not working and when it started working.