mentics
mentics

Reputation: 6999

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);

Upvotes: 4

Views: 4896

Answers (2)

mentics
mentics

Reputation: 6999

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.

Upvotes: 3

Shashank Bharadwaj
Shashank Bharadwaj

Reputation: 441

I'm answering the question based on the assumption that when you say:

// 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"));

you mean that the context.push creates a mv.visitLocalVariable.

I believe that the labels l1 and l2 must be visited first before you can visit the local variable.

See the ASM4 java doc of method visitor

visitTryCatchBlock must be called before the labels passed as arguments have been visited, and the visitLocalVariable and visitLineNumber methods must be called after the labels passed as arguments have been visited.

Not following the above could result in the stackmap being incorrectly generated. So moving the context.push to the bottom after mv.visitLabel(l5) should generate code with the correct stack map.

Upvotes: 4

Related Questions