Java JVM中的If(true)。如何生成适当的指令?

Java JVM中的If(true)。如何生成适当的指令?,java,class,if-statement,jvm,java-bytecode-asm,Java,Class,If Statement,Jvm,Java Bytecode Asm,我试图生成一个简单的条件跳转指令。下面是课堂: public static Class<?> getKlass2(){ String className = "TestClass"; ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); classWriter.visit(V1_8, ACC_PUBLIC, className, null, getInternalName(O

我试图生成一个简单的条件跳转指令。下面是课堂:

public static Class<?> getKlass2(){
    String className = "TestClass";
    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);

    classWriter.visit(V1_8, ACC_PUBLIC, className, null, getInternalName(Object.class), null);

    MethodVisitor mv = classWriter.visitMethod(ACC_PUBLIC + ACC_STATIC, "m", "()Z",null, null);
    Label trueLable = new Label();
    Label afterFalseLable = new Label();
    mv.visitFieldInsn(GETSTATIC, getInternalName(Boolean.class), "TRUE", "Ljava/lang/Boolean;");
    mv.visitMethodInsn(INVOKEVIRTUAL, getInternalName(Boolean.class), "booleanValue", "()Z", false);
    mv.visitJumpInsn(IFEQ, trueLable);
    mv.visitInsn(ICONST_1);
    mv.visitJumpInsn(GOTO, afterFalseLable);
    mv.visitLabel(trueLable);
    mv.visitInsn(ICONST_0);
    mv.visitFrame(F_APPEND, 0, null, 0, null);
    mv.visitLabel(afterFalseLable);
    mv.visitInsn(IRETURN);
    mv.visitMaxs(1, 1);
    mv.visitEnd();
    //converting classWriter.toByteArray() to Class<?> instance
}
但在我看来,这门课的代码还行:

public class TestClass {
  public static boolean m();
    Code:
       0: getstatic     #12                 // Field java/lang/Boolean.TRUE:Ljava/lang/Boolean;
       3: invokevirtual #15                 // Method java/lang/Boolean.booleanValue:()Z
       6: ifeq          13
       9: iconst_1
      10: goto          14
      13: iconst_0
      14: ireturn
}
所以我试着手动添加一个框架:

mv.visitFrame(F_APPEND, 0, new Object[]{ }, 0, new Object[]{ trueLabel });
但它也以同样的例外失败了。他们是否希望使用相同的操作数堆栈重新创建帧? 但这似乎毫无意义,因为我无法从Java代码直接访问Opera和堆栈

我做错了什么?

您可以简单地指定
ClassWriter
的构造函数,让ASM为您计算max stack&locals和stack map table frame条目。正如文档所述,“…computeFrames意味着computeMaxs”

但是,我始终建议尝试理解堆栈映射,因为从头开始的计算不仅成本高昂,而且还有一些基本限制(如中所述)。因为您应该已经知道堆栈框架应该是什么样子,所以编码这些知识应该不会太难。因为这也意味着知道局部变量和操作数堆栈项的最大数量,所以手动指定它们也是一致的

但是,您尝试的解决方案远远不够:

mv.visitFrame(F_APPEND, 0, new Object[]{ }, 0, new Object[]{ trueLabel });
F_APPEND
意味着添加了新变量,这与添加堆栈项的明显意图不符。此外,只有在引用
NEW
指令的位置以表示未初始化的对象时,将标签指定为堆栈条目才有效。但在这里,您推送了一个
整数

正确的代码如下所示:

String className = "TestClass";
ClassWriter classWriter = new ClassWriter(0);
classWriter.visit(V1_8, ACC_PUBLIC, className, null, getInternalName(Object.class), null);
MethodVisitor mv = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "m", "()Z",null, null);
Label trueLabel = new Label();
Label afterFalseLabel = new Label();
mv.visitFieldInsn(GETSTATIC, getInternalName(Boolean.class),"TRUE","Ljava/lang/Boolean;");
mv.visitMethodInsn(INVOKEVIRTUAL,getInternalName(Boolean.class),"booleanValue","()Z",false);
mv.visitJumpInsn(IFEQ, trueLabel);
mv.visitInsn(ICONST_1);
mv.visitJumpInsn(GOTO, afterFalseLabel);
// target of IFEQ, the frame matches the initial frame (no variables, no stack entries)
mv.visitFrame(F_SAME, 0, null, 0, null);
mv.visitLabel(trueLabel);
mv.visitInsn(ICONST_0);
// merge point of the two branches, now having an INTEGER on the stack
mv.visitFrame(F_SAME1, 0, null, 1, new Object[]{ INTEGER });
mv.visitLabel(afterFalseLabel);
mv.visitInsn(IRETURN);
// no variable at all, at most one stack entry (the integer)
mv.visitMaxs(1, 0);
mv.visitEnd();
//converting classWriter.toByteArray() to Class<?> instance
顺便说一下,我发现将抽象名称生成(如
getInternalName(Boolean.class)
)与硬编码签名(如
“Ljava/lang/Boolean;”
)结合起来有点奇怪。这两种方法都是有效的,但最好是一致地决定哪种方法。

您可以简单地指定
ClassWriter
的构造函数,让ASM为您计算最大堆栈和局部数以及堆栈映射表框架项。正如文档所述,“…computeFrames意味着computeMaxs”

但是,我始终建议尝试理解堆栈映射,因为从头开始的计算不仅成本高昂,而且还有一些基本限制(如中所述)。因为您应该已经知道堆栈框架应该是什么样子,所以编码这些知识应该不会太难。因为这也意味着知道局部变量和操作数堆栈项的最大数量,所以手动指定它们也是一致的

但是,您尝试的解决方案远远不够:

mv.visitFrame(F_APPEND, 0, new Object[]{ }, 0, new Object[]{ trueLabel });
F_APPEND
意味着添加了新变量,这与添加堆栈项的明显意图不符。此外,只有在引用
NEW
指令的位置以表示未初始化的对象时,将标签指定为堆栈条目才有效。但在这里,您推送了一个
整数

正确的代码如下所示:

String className = "TestClass";
ClassWriter classWriter = new ClassWriter(0);
classWriter.visit(V1_8, ACC_PUBLIC, className, null, getInternalName(Object.class), null);
MethodVisitor mv = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "m", "()Z",null, null);
Label trueLabel = new Label();
Label afterFalseLabel = new Label();
mv.visitFieldInsn(GETSTATIC, getInternalName(Boolean.class),"TRUE","Ljava/lang/Boolean;");
mv.visitMethodInsn(INVOKEVIRTUAL,getInternalName(Boolean.class),"booleanValue","()Z",false);
mv.visitJumpInsn(IFEQ, trueLabel);
mv.visitInsn(ICONST_1);
mv.visitJumpInsn(GOTO, afterFalseLabel);
// target of IFEQ, the frame matches the initial frame (no variables, no stack entries)
mv.visitFrame(F_SAME, 0, null, 0, null);
mv.visitLabel(trueLabel);
mv.visitInsn(ICONST_0);
// merge point of the two branches, now having an INTEGER on the stack
mv.visitFrame(F_SAME1, 0, null, 1, new Object[]{ INTEGER });
mv.visitLabel(afterFalseLabel);
mv.visitInsn(IRETURN);
// no variable at all, at most one stack entry (the integer)
mv.visitMaxs(1, 0);
mv.visitEnd();
//converting classWriter.toByteArray() to Class<?> instance

顺便说一下,我发现将抽象名称生成(如
getInternalName(Boolean.class)
)与硬编码签名(如
“Ljava/lang/Boolean;”
)结合起来有点奇怪。这两种方法都是有效的,但最好是一致地决定哪种方法。

您不想使用
新的ClassWriter(ClassWriter.COMPUTE\u MAXS | ClassWriter.COMPUTE\u FRAMES)
让ASM自动为您生成StackMapTable吗?@apangin。谢谢@apangin不使用
COMPUTE|u max | COMPUTE_FRAMES
;明确声明“computeFrames暗示computeMaxs”,所以只要
COMPUTE\u FRAMES
就足够了。您不想使用
new ClassWriter(ClassWriter.COMPUTE\u MAXS | ClassWriter.COMPUTE\u FRAMES)
让ASM自动为您生成StackMapTable吗?@apangin。谢谢@apangin不使用
COMPUTE|u max | COMPUTE_FRAMES
;明确声明“computeFrames暗示computeMaxs”,因此只要
COMPUTE\u FRAMES
就足够了。