我正在使用ASM并希望操纵类文件以跟踪某些字段写入。我已经了解到putfield
和putstatic
指令是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个字段(即owner
,name
,desc
)隐藏了有关常量字段的所有详细信息,因此我不这样做知道如何调整我的猜测。
我的问题是:
Field
个对象确实存在于常量池中,我该如何检索它并将其推送到ASM中的堆栈?Field
个对象,是否可以使用FieldInsnNode.name
和FieldInsoNode.owner
来实例化Field
的实例(因为没有公共构造函数) for Field
)?FieldInsnNode.name
,所以至少我可以知道指令写入哪个字段。但看起来所有字符串也都位于常量池中,那么如何构建一些指令以在运行时实例化String?答案 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。