java - ASM : visitLabel generates too many labels and nop instructions

标签 java java-bytecode-asm bytecode-manipulation jvm-bytecode

ASM文档说标签代表一个基本 block ,它是控制图中的一个节点。因此,我在这个简单的示例中测试了 visitLabel 方法:

public static void main(String[] args) {
    int x = 3, y = 4;
    if (x < y) {
        x++;
    }
}

对于 visitLabel 方法,我使用 native API 来检测它:setID(int id),其中 id 是增量的。在此示例中,CFG 应具有 3 个节点:一个位于开头,一个用于 if 语句的每个分支。所以我预计 setID 将在 3 个位置被调用。然而,它被调用了5次,并且有很多nop指令。有人能为我解释一下为什么吗?

这是上述程序的检测字节码。

  public static void main(java.lang.String[]);
    Code:
       0: iconst_2
       1: invokestatic  #13                 // Method setId:(I)V
       4: iconst_3
       5: istore_1
       6: iconst_3
       7: invokestatic  #13                 // Method setId:(I)V
      10: iconst_4
      11: istore_2
      12: iconst_4
      13: invokestatic  #13                 // Method setId:(I)V
      16: iload_1
      17: iload_2
      18: if_icmpge     28
      21: iconst_5
      22: invokestatic  #13                 // Method setId:(I)V
      25: iinc          1, 1
      28: bipush        6
      30: invokestatic  #13                 // Method setId:(I)V
      33: return
      34: nop
      35: nop
      36: nop
      37: nop
      38: athrow

我不明白的是为什么每个istore指令之前有一个标签。没有分支使其成为 CFG 中的新节点。

最佳答案

标签的主要用途是表示字节码序列中的位置。由于这是分支目标所必需的,因此您可以使用它们来识别基本 block 。但您必须注意,当 LineNumberTable 时,它们也用于报告行号。属性存在并用于在 LocalVariableTable 时报告局部变量范围属性也存在,对于较新的类文件,它们的类型注释记录在 RuntimeVisibleTypeAnnotations 中。属性。此外,标签可以标记异常处理程序的 protected 区域。对于从 Java 源代码生成的代码,此 protected 区域与 try block 匹配,因此它是一个基本 block ,但不需要保留其他字节码。

查看

由于局部变量的范围可能跨越最后一个 return 指令,因此有可能在最后一条指令之后遇到标签,这就是您的情况。您在 return 指令后注入(inject) bipush 7, invokestatic#13,导致代码无法访问。

显然,您还使用COMPUTE_FRAMES选项让ASM从头开始重新计算堆栈映射帧,但由于未知的初始堆栈状态,无法计算无法访问的代码的帧。 ASM 通过用 nop 指令替换不可访问的代码并后跟单个 athrow 语句来解决此问题。对于这个序列,可以指定一个有效的初始堆栈帧,并且它对执行没有影响(因为代码无法访问)。

如您所见,四个 nop 指令加上一个 athrow 指令跨度五个字节,这与替换的 bipush 7, invokestatic#13 的大小相同 序列有。

您可以通过指定 ClassReader.SKIP_DEBUG 来删除大部分报告的标签。到其accept method 。然后,您的示例仅获得一个报告标签,即与 if 语句关联的分支目标。但你必须处理visitJumpInsn识别条件代码的开始。

因此,要识别所有基本 block ,您必须处理所有分支指令,即通过 visitJumpInsnvisitLookupSwitchInsnvisitTableSwitchInsn,如下以及所有结束指令,即 athrowreturn 的所有变体。此外,您需要处理所有 visitTryCatchBlock 调用。如果您需要在一次传递中识别分支指令的潜在目标,我会使用 visitFrame 而不是标签,因为对于类文件版本 51 (Java 7) 或更高。

顺便说一句,当您注入(inject)的只是加载常量和调用静态方法(在可到达的位置)的这些序列时,我会使用 COMPUTE_MAXS 而不是 COMPUTE_FRAMES,因为当一般代码结构不改变时,不需要昂贵的重新计算。

关于java - ASM : visitLabel generates too many labels and nop instructions,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53232522/

相关文章:

java - Maven项目没有引用java中的sikuli-api依赖

从零开始的 Java PRNG

java - 如何在应用程序启动时使用 Spring Cloud Stream 发布消息?

java - 需要有关验证自动完成的帮助

java - ASMifier 显示不够

java - 如何使用 ASM 转换字节码以初始化静态 block 中的原始常量?

Javassist 似乎生成无效的字段访问代码

java - 生成实现 JSR 308 "instanceof @MyAnotations"运行时检查的代码

java - ASM 如何在复制操作数堆栈(DUP_X1 和 DUP 操作数)时检查最大堆栈大小

java - 如何使用ASM访问内部类的方法?