我正在使用Java ASM(4.0)编写一个简单的编译器。我使用classWriter(COMPUTE_FRAMES)来编写一个类。
这一切都适用于简单的程序,但是当我开始嵌套跳转(例如,在IfThenElse语句中的while语句)之后,我调用methodVisitor.visitMaxs(0,0)它会给我以下错误:
java.lang.NullPointerException
at org.objectweb.asm.Frame.a(Unknown Source)
at org.objectweb.asm.MethodWriter.visitMaxs(Unknown Source)
at front.ir.visitors.ClassWriterVisitor.visitAstProcedure(ClassWriterVisitor.java:182)
at front.ir.visitors.ClassWriterVisitor.visitAstProcedure(ClassWriterVisitor.java:1)
at front.ir.ASTProcedure.accept(ASTProcedure.java:27)
at front.ir.visitors.ClassWriterVisitor.visitProgram(ClassWriterVisitor.java:235)
at front.ir.visitors.ClassWriterVisitor.visitProgram(ClassWriterVisitor.java:1)
at front.ir.ASTProgram.accept(ASTProgram.java:37)
at front.FrontEnd.main(FrontEnd.java:122)
我的while语句的代码:
jumplabels.push(new Label());
L2 = new Label();
mv.visitJumpInsn(Opcodes.GOTO, L2);
mv.visitLabel(jumplabels.peek());
whi.stmt.accept(this);
mv.visitLabel(L2);
whi.condition.accept(this);
jumplabels.pop();
我的IfThenElse:
jumplabels.push(new Label());
L2 = new Label();
L1 = new Label();
ifthenelse.condition.accept(this);
mv.visitJumpInsn(Opcodes.GOTO, L2);
mv.visitLabel(jumplabels.peek());
ifthenelse.thenStmt.accept(this);
mv.visitJumpInsn(Opcodes.GOTO, L1);
mv.visitLabel(L2);
if (ifthenelse.elseStmt != null) {
ifthenelse.elseStmt.accept(this);
}
mv.visitLabel(L1);
jumplabels.pop();
condition.accept(this)将插入正确的条件并跳转到堆栈上推送的最后一个标签(例如IFEQ jumplabels.peek())。
我希望有人能告诉我自己做错了什么。对于可能不清楚的代码感到抱歉。
答案 0 :(得分:2)
我不确切地知道你做错了什么,但此时的崩溃总是表明你写的字节码在某种程度上是不正确的。当我们点击这个时,我们再次启用调试开关运行它,调试开关触发对CheckClassAdapter.verify()的调用,这会产生一个字节码列表,通过练习,你可以解释它来解决你去哪里错。
回答您的问题,这里有一些我们所做的更详细的信息。
我们实际上有两个可以设置的选项,称为displayByteCode
和debugByteCode
。 display选项无条件地将字节码打印到文件中,如果出现问题,调试选项只会动作。
首先,我们创建一个ClassWriter cw
,我们用它来创建类。如果启用了displayByteCode
选项,那么我们立即将其包装在TraceClassVisitor
中,并为其生成PrintWriter
生成的字节码。
完成后(调用cw.visitEnd()
后),如果启用了debugByteCode
选项,我们会执行以下操作:
StringWriter sw = new StringWriter();
CheckClassAdapter.verify(new ClassReader(cw.toByteArray()), false, new PrintWriter(sw));
if (sw.toString().length() != 0) {
System.err.println("Verify Output for " + objectName + ":");
try {
BufferedWriter out = new BufferedWriter(new FileWriter("ByteCodeOutput.txt"));
out.write(sw.toString());
out.close();
} catch (IOException e) {
System.out.println("Exception " + e);
}
System.err.println(sw);
throw new IllegalStateException("Bytecode failed verification");
}
这个代码几乎可以肯定地得到改进,但由于它仅在紧急情况下需要,所以它对我们的目的来说已经足够了。
答案 1 :(得分:0)
您应该重新考虑编译这些语句的方式。如果声明通常没有两条GOTO
说明。此外,你应该摆脱jumplabels
堆栈。在我的编译器中,这是我编译if
语句的方式:
org.objectweb.asm.Label elseEnd = new org.objectweb.asm.Label();
// Condition
this.condition.writeInvJump(writer, elseStart);
this.then.writeStatement(writer);
writer.writeJumpInsn(Opcodes.GOTO, elseEnd);
writer.writeFrameLabel(elseStart);
this.elseThen.writeStatement(writer);
writer.writeFrameLabel(elseEnd);
writeStatement
只是编写AST节点(这段代码本身在writeStatement
)。 writeInvJump
写一个跳转指令,如果表达式求值为false,则跳转到指定的标签。两者都是成员,并在IValue
的所有子类型中实现。 (请注意,我使用自定义MethodWriter
将调用委托给ASM MethodWriter
并使用略有不同的命名格式。
(IfStatement Source)(IValue Source)
为了完整起见,这里是WhileStatement.writeStatement
的代码:
writer.writeFrameLabel(this.startLabel.target);
this.condition.writeInvJump(writer, this.endLabel.target);
this.action.writeStatement(writer);
writer.writeJumpInsn(Opcodes.GOTO, this.startLabel.target);
writer.writeFrameLabel(this.endLabel.target);