为什么methodvistor.visitMaxs(0,0)在Java asm中崩溃?

时间:2015-03-19 16:58:14

标签: java java-bytecode-asm

我正在使用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())。

我希望有人能告诉我自己做错了什么。对于可能不清楚的代码感到抱歉。

2 个答案:

答案 0 :(得分:2)

我不确切地知道你做错了什么,但此时的崩溃总是表明你写的字节码在某种程度上是不正确的。当我们点击这个时,我们再次启用调试开关运行它,调试开关触发对CheckClassAdapter.verify()的调用,这会产生一个字节码列表,通过练习,你可以解释它来解决你去哪里错。

回答您的问题,这里有一些我们所做的更详细的信息。

我们实际上有两个可以设置的选项,称为displayByteCodedebugByteCode。 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);

WhileStatement Source