我可以在ASM中获得putfield或putstatic指令的Field实例吗?

时间:2014-07-28 17:26:13

标签: java java-bytecode-asm

我正在使用ASM并希望操纵类文件以跟踪某些字段写入。我已经了解到putfieldputstatic指令是ASM中FieldInsnNode类的实例,我想在运行时注入一些代码来构造Field对象并调用其他方法将此Field对象作为参数。

我通过编译一个简单的Java源代码做了一些实验:

package com.test.simple;

public class Simple {
    public int a,b;

    public void foo() {
        a = 20;
        b = 10;
    }
}

然后使用javap检查类文件:

$ javap -c -l Simple.class 
Compiled from "Simple.java"
public class com.test.simple.Simple {
  public int a;

  public int b;

  public com.test.simple.Simple();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        
    LineNumberTable:
      line 3: 0

  public void foo();
    Code:
       0: aload_0       
       1: bipush        20
       3: putfield      #2                  // Field a:I
       6: aload_0       
       7: bipush        10
       9: putfield      #3                  // Field b:I
      12: return        
    LineNumberTable:
      line 7: 0
      line 8: 6
      line 9: 12
}

在这里我可以发现putfield实际上跟着#2之类的东西,我想这是对常量池的引用。 (而且我猜测常量实际上是Field)的实例

但是,在ASM中,FieldInsnNode只有3个字段(即ownernamedesc)隐藏了有关常量字段的所有详细信息,因此我不这样做知道如何调整我的猜测。

我的问题是:

  • 如果Field个对象确实存在于常量池中,我该如何检索它并将其推送到ASM中的堆栈?
  • 如果找不到Field个对象,是否可以使用FieldInsnNode.nameFieldInsoNode.owner来实例化Field的实例(因为没有公共构造函数) for Field)?
  • 如果上述所有方法都不起作用,我想我仍然可以打印FieldInsnNode.name,所以至少我可以知道指令写入哪个字段。但看起来所有字符串也都位于常量池中,那么如何构建一些指令以在运行时实例化String?

1 个答案:

答案 0 :(得分:2)

您可以简单地使用ldc指令将常量池中的String常量加载到操作数堆栈。但是你不能用Field来做到这一点。与您的假设相反,常量池上没有Field实例。 JVM不会将Reflection用于其正常操作。

putfield(等)指令的关联常量池条目是指一个描述符,简单地说,它完全包含ASM提供的信息,所有者类,字段名称及其类型签名。 / p>

要从这些信息中获取Field实例,您可以使用

ldc owner // a Class instance from constant pool
ldc name  // a String instance from constant pool
invokevirtual java/lang/Class getDeclaredField (Ljava/lang/String;)Ljava/lang/reflect/Field; // returns the Field

这里可以忽略desc,因为无论其类型如何,Reflection都会返回唯一命名的字段。

ASM库提供了构造上述指令序列的方法。请注意visitFieldInsn为所有者类String提供ldc,然后传递给you have to convert to a Type instance,因为您希望生成生成相关Class的{​​{1}}指令{1}}实例,而不是包含类名称的String实例。


为了完整性,从Java 7开始,对于编译时已知的字段,有可能从常量池包装字段操作(如MethodHandle)中获取putfield,但结果句柄仅允许使用其invoke方法执行封装的字段操作,但不检查封装字段的属性。这是为Java 8或更新版本保留的,它引入了visitLdcInsn,但这并不比上面的三个指令序列简单,后者适用于从Java 5开始的所有JVM。