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

Why does methodvistor.visitMaxs(0,0) crash in Java asm?

我正在使用 Java ASM (4.0) 编写一个简单的编译器。我使用 classWriter(COMPUTE_FRAMES) 写一个 class.

这一切都适用于简单的程序,但是当我开始嵌套跳转时(例如 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())。

我希望任何人都可以告诉我我做错了什么。对于可能不清楚的代码,我们深表歉意。

您应该重新考虑编译这些语句的方式。 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 编写跳转指令,如果表达式的计算结果为假,则跳转到指定的标签。两者都是成员并在 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)

我不知道你究竟做错了什么,但此时的崩溃总是表明你写的字节码在某些方面不正确。当我们点击它时,我们再次 运行 它并启用调试开关,其中调试开关触发对 CheckClassAdapter.verify() 的调用,它会生成字节码列表,通过练习,您可以解释找出你哪里出错了。

为了回答您的问题,以下是我们所做工作的更多详细信息。

我们实际上有两个选项可以设置,称为 displayByteCodedebugByteCode。 display 选项无条件地将字节码打印到文件中,debug 选项只有在出现错误时才会起作用。

首先,我们创建一个 ClassWriter cw,我们用它来创建 class。如果 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");
}

这段代码几乎肯定可以改进,但由于它只在紧急情况下需要,所以它足以满足我们的目的。