我正在使用ASM library修改其他人创建的字节码。对于任意类中的任意方法,我想创建一个LdcInsnNode
,将当前类添加到堆栈中。
例如,假设我正在转换一个名为com.example.ExampleClass
的类。我想创建相当于System.out.println(ExampleClass.class.getName());
的字节码。
这似乎是一项相对简单的任务。当我使用Eclipse Bytecode Outline插件时,它表示以下字节码是等效的:
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC Lcom/example/ExampleClass;.class
INVOKEVIRTUAL java/lang/Class.getName ()Ljava/lang/String;
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
我尝试了以下代码:
private InsnList printClass() {
InsnList result = new InsnList();
result.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
result.add(new LdcInsnNode("L" + name + ";.class"));
result.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;", false));
result.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false));
return result;
}
这是在ClassNode
的扩展名中运行,因此name
引用ClassNode.name
字段。使用InsnList
在现有AbstractInsnNode
之前插入此方法返回的InsnList.insertBefore(AbstractInsnNode, printClass())
。当在字节码中达到这一点时,我得到一个错误,原因如下:
Type 'java/lang/String' (current frame, stack[1]) is not assignable to 'java/lang/Class'
这显然是因为LDC指令正在添加字符串"Lcom/example/ExampleClass;.class"
而不是实际的类Lcom/example/ExampleClass;.class
。
这有什么解决方法吗?将Class
对象直接添加到LdcInsnNode
似乎是不可能的,因为该类尚不存在。但是有没有办法添加一个加载Class
对象的指令?
在我的特定情况下,调用Object.getClass()
方法不是一个选项,因为它需要在静态上下文中工作。
答案 0 :(得分:2)
您不需要引用Class
对象,事实上,它甚至不支持(直接)。如果要通过ASM将Class
推送到操作数堆栈,则必须将其称为Type
实例。
E.g。
new LdcInsnNode(Type.getObjectType(name))
使用Type.getObjectType(…)
工厂方法,该方法希望name
代表内部名称格式,例如com/example/ExampleClass
周围没有L … ;
。对于非数组类型,它等效于Type.getType('L'+name+';')
。工厂方法的选择很重要,因为Type
对象也可以表示基本类型或方法类型。由于ldc
和Type.getObjectType
都只支持引用类型,因此这是自然组合。
似乎LdcInsnNode
constructor documentation有点过时了,因为它只提到了数字类型,自{5}以来String
Class
支持ldc
,但文档包含链接到1.4.2)。您可以参考MethodVisitor.visitLdcInsn(…)
代替,在生成字节代码时最终将对象传递给该对象。