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

为什么methodvistor.visitMaxs(0,0)在JavaASM中崩溃?,java,java-bytecode-asm,Java,Java Bytecode Asm,我正在使用JavaASM(4.0)编写一个简单的编译器。我使用classWriter(COMPUTE_FRAMES)来编写一个类 对于简单的程序来说,这一切都很好,但是当我开始嵌套跳转(例如,IfThenElse语句中的while语句)之后,我调用methodVisitor.visitMaxs(0,0)时,会出现以下错误: java.lang.NullPointerException at org.objectweb.asm.Frame.a(Unknown Source) at org.obje

我正在使用JavaASM(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();
还有我的朋友:

    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(此)将插入正确的条件并跳转到堆栈上推送的最后一个标签(例如IFEQ jumplabels.peek())


我希望任何人都能告诉我我做错了什么。很抱歉代码可能不清楚。

您应该重新考虑编译这些语句的方式。If语句通常没有两条
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
,并使用稍微不同的命名格式)

()()


为完整起见,以下是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);

()

我不知道您到底做错了什么,但此时的崩溃总是表明您编写的字节码在某种程度上是不正确的。当我们点击这个按钮时,我们在启用调试开关的情况下再次运行它,其中调试开关触发对CheckClassAdapter.verify()的调用,该调用生成字节码列表,通过实践,您可以对该列表进行解释以找出错误所在

在回答你的问题时,这里是我们所做工作的更多细节

实际上我们可以设置两个选项,分别是
displayByteCode
debugByteCode
。display选项无条件地将字节码打印到文件中,debug选项只有在出现问题时才会启动

首先,我们创建一个
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");
}

这段代码几乎肯定可以改进,但因为它只在紧急情况下才需要,所以对于我们的目的来说已经足够好了

非常感谢你!我的方法真是太草率了。现在它就像一个符咒。嗨,你能解释一下吗?我非常渴望知道如何调试这个,或者至少能够看到字节码。我已经添加了更多信息。