java-bytecode-asm - 生成代码中 ASM 和堆栈帧映射的 COMPUTE_FRAMES 问题

标签 java-bytecode-asm

我正在为编译器编写一个代码生成器,我将其用作我正在教授的编译器类中的示例。我们使用 ASM 5.0.3 生成 JVM 代码。我能够评估最直接的表达式和语句,其中一些调用运行时方法,就很好。对于示例引用语言,我们只有 bool 值和整数,以及一些带有类型推断的简单控制结构。所有“程序”都被编译为结果类中的静态主方法。这是我第一年使用 ASM。我们之前用过 Jasmine 。

我在堆栈图框架方面遇到问题。这是我正在编译的一个示例程序,它将显示该问题:

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。我缺少什么?我花了很多时间试图纠正这一点,可以想象我的学生一定会感到多么沮丧。我开始后悔从 Jasmine 的转变。

最佳答案

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

所以你可以改变你的 ifeq指令if_icmpeq以反射(reflect)您的初衷,但保留 ifeq 会更有效指令并移除零常数的插入。

请注意,所有三个指令都执行相同的操作似乎是另一个错误 ifeq测试。我猜,你想编译你的 < 0> 0代码为 ifltifgt指示。再次注意 iflt 之间的区别/ifgtificmplt/ificmpgt说明...

关于java-bytecode-asm - 生成代码中 ASM 和堆栈帧映射的 COMPUTE_FRAMES 问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28727501/

相关文章:

java - ASM COMPUTE_FRAMES If + 赋值错误

java - 将字节数组保存为 .class

hook - AspectJ 和 ASM 有什么区别?

javac 无法找到导入

java - ASM 如何在复制操作数堆栈(DUP_X1 和 DUP 操作数)时检查最大堆栈大小

java 代理 - 检测选定的文件(排除所有内置的 java 类和方法)

java - "final"在运行时是最终的吗?

java - 检测调用指令中的方法是否是 ASM/java 字节码中的 native 方法

java - 字节码检测生成 java validator 错误

java - ASM 中的哪些指令仅用于调试?