java - JVM 中的 if(true)。如何生成合适的指令?

标签 java class if-statement jvm java-bytecode-asm

我正在尝试生成一个简单的条件跳转指令。这是类(class):

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

我做错了什么?

最佳答案

您可以简单地指定 COMPUTE_FRAMES到 ClassWriter 的构造函数,让 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;"。两者都是有效的,但最好始终如一地做出决定。

关于java - JVM 中的 if(true)。如何生成合适的指令?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49370130/

相关文章:

class - 带有父类(super class)的案例类副本 'method'

javascript - 无需编写多个 if else 语句即可验证多个变量

javascript - 运行 if 语句后的 jQuery 停止事件

sql - 为 Postgres 函数设置默认返回值

java - 如何解析 JSON 值中的转义字符?

java - 依次执行异步任务

java - 当设备处于 sleep 状态时在后台运行广播接收器?

java - 为什么在创建 CoordinateReferenceSystem 时会收到 NoSuchAuthorityCodeException?

html - CSS类命名不同?

C++ 空类或 typedef