Неправильный тип локальной переменной в методе

Я использую ASM 4 для создания некоторых классов на лету. Все шло довольно хорошо, пока я не начал генерировать код для обработки исключений. Сгенерированный байт-код находится внизу. Вот ошибка, которую я получаю:

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

Вот байт-код:

// 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}]
}

Я использовал ASMifier для начала:

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

но затем мне пришлось изменить его, чтобы сделать его общим. Вот код, который генерирует часть try/catch:

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

person mentics    schedule 30.06.2012    source источник


Ответы (2)


Я отвечаю на вопрос, исходя из предположения, что когда вы говорите:

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

вы имеете в виду, что context.push создает mv.visitLocalVariable.

Я считаю, что метки l1 и l2 должны быть посещены в первую очередь, прежде чем вы сможете посетить локальную переменную.

См. java-документ ASM4 для посетителя метода.

VisitTryCatchBlock должен быть вызван до меток, переданных в качестве аргументов, которые были посещены, а методы visitLocalVariable и visitLineNumber должны быть вызваны после посещения меток, переданных в качестве аргументов.

Несоблюдение вышеизложенного может привести к неправильному созданию карты стека. Таким образом, перемещение context.push вниз после mv.visitLabel(l5) должно генерировать код с правильной картой стека.

person Shashank Bharadwaj    schedule 02.07.2012
comment
Ваше предположение верно, за исключением того, что он не вызывает visitLocalVariable до тех пор, пока непосредственно перед вызовом visitMaxs в конце метода. Итак, я думаю, что остальная часть того, что вы описываете, удовлетворяет. Извините, мой вопрос не прояснил это. - person mentics; 03.07.2012

Я спросил в списке ASM, и кто-то любезно предоставил этот совет:

«Попробуйте использовать CheckClassAdapter с параметром checkDataFlow, чтобы получить дополнительные сведения».

Это с некоторым устранением неполадок устранило проблему. Я почти уверен, что это было связано с неправильным разграничением области действия связанных переменных и необходимостью объявлять локальные переменные с помощью visitLocalVariable. По крайней мере, это одна из вещей, которые я исправил между тем, когда он не работал, и когда он начал работать.

person mentics    schedule 03.07.2012