代码之家  ›  专栏  ›  技术社区  ›  St.Antario

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

  •  0
  • St.Antario  · 技术社区  · 7 年前

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

    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
    }
    

    加载类时,我遇到以下错误:

    Expecting a stackmap frame at branch target 13
    
    Exception Details:
      Location:
        TestClass.m()Z @6: ifeq
      Reason:
        Expected stackmap frame at this location.
      Bytecode:
        0x0000000: b200 0cb6 000f 9900 0704 a700 0403 ac  
      Stackmap Table:
        same_frame_extended(@14)
    

    但在我看来,这个类的代码还可以:

    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代码直接访问opearand堆栈。

    我做错了什么?

    1 回复  |  直到 7 年前
        1
  •  2
  •   Holger    7 年前

    您可以简单地指定 COMPUTE_FRAMES ClassWriter –s构造函数,让ASM同时计算最大堆栈和;局部变量和堆栈映射表框架项。正如文档中所述 –computeFrames意味着computeMaxs

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

    然而,您尝试的解决方案还远远不够:

    mv.visitFrame(F_APPEND, 0, new Object[]{ }, 0, new Object[]{ trueLabel });
    

    F_APPEND 意味着新的 变量 已添加,这与您添加 堆栈 进入此外,只有在引用 NEW 指令,表示未初始化的对象。但在这里,你推了一个 INTEGER 价值

    正确的代码如下所示:

    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
    

    请注意,对于特殊的压缩帧类型,大多数参数都是隐含的 F_SAME ,所有其他参数都是无关的,因为 F_SAME1 ,只有最后一个参数中指定的新堆栈条目类型才重要。

    但您不需要处理不同类型的压缩帧。如果有疑问,您可以随时指定 F_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 state is "no variables, no stack entries"
    mv.visitFrame(F_NEW, 0, null, 0, null);
    mv.visitLabel(trueLabel);
    mv.visitInsn(ICONST_0);
    // merge point of the two branches, frame state is "no variables, one INTEGER on the stack"
    mv.visitFrame(F_NEW, 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();
    

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