为什么 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);
我不知道你究竟做错了什么,但此时的崩溃总是表明你写的字节码在某些方面不正确。当我们点击它时,我们再次 运行 它并启用调试开关,其中调试开关触发对 CheckClassAdapter.verify() 的调用,它会生成字节码列表,通过练习,您可以解释找出你哪里出错了。
为了回答您的问题,以下是我们所做工作的更多详细信息。
我们实际上有两个选项可以设置,称为 displayByteCode
和 debugByteCode
。 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");
}
这段代码几乎肯定可以改进,但由于它只在紧急情况下需要,所以它足以满足我们的目的。
我正在使用 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);
我不知道你究竟做错了什么,但此时的崩溃总是表明你写的字节码在某些方面不正确。当我们点击它时,我们再次 运行 它并启用调试开关,其中调试开关触发对 CheckClassAdapter.verify() 的调用,它会生成字节码列表,通过练习,您可以解释找出你哪里出错了。
为了回答您的问题,以下是我们所做工作的更多详细信息。
我们实际上有两个选项可以设置,称为 displayByteCode
和 debugByteCode
。 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");
}
这段代码几乎肯定可以改进,但由于它只在紧急情况下需要,所以它足以满足我们的目的。