COMPUTE_FRAMES问题与生成的代码中的ASM和堆栈帧映射

时间:2015-02-25 19:04:24

标签: java-bytecode-asm

我正在编写一个编译器的代码生成器,我在我正在教授的编译器类中使用它作为示例。我们使用ASM 5.0.3生成JVM代码。我能够评估大多数直接表达式和语句,有些调用运行时方法,就好了。对于示例参考语言,我们只有boolean和ints,以及一些带有类型推断的简单控制结构。所有“程序”都会在结果类中编译为静态main方法。这是我使用ASM的第一年。我们以前用过茉莉花。

我遇到了stackmap帧的问题。这是我正在编译的示例程序,它将显示问题:

i <- 5
if
  i < 0 :: j <- 0
  i = 0 :: j <- 1
  i > 0 :: j <- 2
fi

相当于Java程序:

int i = 5, j;
if (i < 0) j = 0;
else if (i == 0)j = 1;
else if (i > 0) j = 2;

如果我只是用一个替代方案编写程序,它会生成正常的。但是当我有多个时,就像在这种情况下,我得到这样的痕迹:

java.lang.VerifyError: Inconsistent stackmap frames at branch target 39
Exception Details:
  Location:
    djkcode/Test.main([Ljava/lang/String;)V @12: goto
  Reason:
    Current frame's stack size doesn't match stackmap.
  Current Frame:
    bci: @12
    flags: { }
    locals: { '[Ljava/lang/String;', integer, integer }
    stack: { integer }
  Stackmap Frame:
    bci: @39
    flags: { }
    locals: { '[Ljava/lang/String;', integer }
    stack: { integer, integer, integer }
  Bytecode:
    0x0000000: 120b 3c1b 120c 9900 0912 0c3d a700 1b1b
    0x0000010: 120c 9900 0912 0d3d a700 0f1b 120c 9900
    0x0000020: 0912 0e3d a700 03b1                    
  Stackmap Table:
    full_frame(@15,{Object[#16],Integer},{Integer})
    full_frame(@27,{Object[#16],Integer},{Integer,Integer})
    full_frame(@39,{Object[#16],Integer},{Integer,Integer,Integer})

我可以在此调试输出中看到我做的ASM调用:

DBG>     cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
DBG>     cw.visit(V1_8, ACC_PUBLIC+ACC_STATIC, "djkcode/Test", null, "java/lang/Object", null
DBG>        Generate the default constructor..this works in all cases
DBG>     
Start the main method
DBG>     mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null
DBG>     mv.visitCode()
DBG>     mv.visitLdcInsn(5);
DBG>     mv.visitVarInsn(ISTORE, assign.getId().getAddress());
DBG> Enter Alternative
DBG>     Label endLabel = new Label();  // value = L692342133
DBG> Enter Guard
DBG>     Label failLabel = new Label(); // failLabel = L578866604
DBG>     mv.visitVarInsn(ILOAD, 1);
DBG>     mv.visitLdcInsn(0);
DBG>     mv.visitJumpInsn(IFEQ, failLabel);
DBG>     mv.visitLdcInsn(0);
DBG>     mv.visitVarInsn(ISTORE, assign.getId().getAddress());
DBG>     mv.visitJumpInsn(GOTO, guardLabelStack.peek());  // label value = L692342133
DBG>     mv.visitLabel(failLabel);  // failLabel = L578866604
DBG> L578866604:
DBG> Exit Guard
DBG> Enter Guard
DBG>     Label failLabel = new Label(); // failLabel = L1156060786
DBG>     mv.visitVarInsn(ILOAD, 1);
DBG>     mv.visitLdcInsn(0);
DBG>     mv.visitJumpInsn(IFEQ, failLabel);
DBG>     mv.visitLdcInsn(1);
DBG>     mv.visitVarInsn(ISTORE, assign.getId().getAddress());
DBG>     mv.visitJumpInsn(GOTO, guardLabelStack.peek());  // label value = L692342133
DBG>     mv.visitLabel(failLabel);  // failLabel = L1156060786
DBG> L1156060786:
DBG> Exit Guard
DBG> Enter Guard
DBG>     Label failLabel = new Label(); // failLabel = L1612799726
DBG>     mv.visitVarInsn(ILOAD, 1);
DBG>     mv.visitLdcInsn(0);
DBG>     mv.visitJumpInsn(IFEQ, failLabel);
DBG>     mv.visitLdcInsn(2);
DBG>     mv.visitVarInsn(ISTORE, assign.getId().getAddress());
DBG>     mv.visitJumpInsn(GOTO, guardLabelStack.peek());  // label value = L692342133
DBG>     mv.visitLabel(failLabel);  // failLabel = L1612799726
DBG> L1612799726:
DBG> Exit Guard
DBG>     mv.visitLabel(endLabel);  // endLabel = L692342133
DBG> L692342133:
DBG> Exit Alternative
DBG>     mv.visitInsn(RETURN)
DBG>     mv.visitMaxs(0, 0);
DBG>     mv.visitEnd();
DBG>     cw.visitEnd();

如果我在Eclips ASM字节码浏览器中查看该类,我会得到:

package asm.djkcode;
...
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;

cw.visit(52, ACC_PUBLIC + ACC_STATIC, "djkcode/Test", null, "java/lang/Object", null);

...

{
mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
...
mv.visitJumpInsn(GOTO, l1);
mv.visitLabel(l0);
mv.visitFrame(Opcodes.F_FULL, 2, new Object[] {"[Ljava/lang/String;", Opcodes.INTEGER}, 1, new Object[] {Opcodes.INTEGER});
...
mv.visitJumpInsn(GOTO, l1);
mv.visitLabel(l2);
mv.visitFrame(Opcodes.F_FULL, 2, new Object[] {"[Ljava/lang/String;", Opcodes.INTEGER}, 2, new Object[] {Opcodes.INTEGER, Opcodes.INTEGER});
...
mv.visitJumpInsn(GOTO, l1);
mv.visitLabel(l1);
mv.visitFrame(Opcodes.F_FULL, 2, new Object[] {"[Ljava/lang/String;", Opcodes.INTEGER}, 3, new Object[] {Opcodes.INTEGER, Opcodes.INTEGER, Opcodes.INTEGER});
mv.visitInsn(RETURN);
mv.visitMaxs(4, 3);
mv.visitEnd();
}
cw.visitEnd();

似乎有一些visitFrame语句不正确,但文档(我使用的是ASM 5.0.3)说如果使用COMPUTE_FRAMES,则不必调用visitFrame。我错过了什么?我花了很多时间试图弄清楚这一点,并且可以想象我的学生必须得到多么沮丧。我开始讽刺茉莉花了。

1 个答案:

答案 0 :(得分:2)

问题是您正在使用ifeq指令错误。 ifeq一个参数与零进行比较,并相应地进行分支。您正在将两个值推入堆栈,因为您似乎将其与if_icmpeq混淆,后者将测试两个操作数是否相等。因此,在每个条件块之后,悬空int保留在堆栈上,因此块之前的代码具有与其后面的代码不同的堆栈深度,这意味着您不能分支它,因为如果不允许分支源和目标具有不同的堆栈深度(这正是VerifierError告诉您的,您可以看到每个显式帧在堆栈上还有一个Integer

因此,您可以将ifeq指令更改为if_icmpeq以反映您的初衷,但保留ifeq指令并移除零常数的推送会更有效。

请注意,所有三个指令执行相同的ifeq测试似乎是另一个错误。我想,您希望将< 0> 0代码编译为相应的ifltifgt指令。再次,请注意iflt / ifgtificmplt / ificmpgt说明之间的差异......