如何创建一个将当前 class 静态添加到堆栈的 ASM LdcInsnNode?

How does one create an ASM LdcInsnNode that statically adds the current class to the stack?

我正在使用 ASM library 修改其他人创建的字节码。对于任意 class 中的任意方法,我想创建一个 LdcInsnNode 将当前 class 添加到堆栈。

例如,假设我正在转换一个名为 com.example.ExampleClass 的 class。我想创建等同于 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.insertBefore(AbstractInsnNode, printClass()) 将此方法返回的 InsnList 插入到现有 AbstractInsnNode 之前。当字节码到达这一点时,我得到一个错误,原因如下:

Type 'java/lang/String' (current frame, stack[1]) is not assignable to 'java/lang/Class'

这显然是因为 LDC 指令添加了字符串 "Lcom/example/ExampleClass;.class" 而不是实际的 class Lcom/example/ExampleClass;.class.

有什么解决方法吗?直接将 Class 对象添加到 LdcInsnNode 似乎是不可能的,因为 class 还不存在。但是有没有办法添加加载Class对象的指令?

在我的特殊情况下,调用 Object.getClass() 方法不是一个选项,因为它需要在静态上下文中工作。

您不需要引用 Class 对象,事实上,它甚至不受支持(直接)。如果要通过 ASM 将 Class 压入操作数堆栈,则必须将其称为 Type 实例。

例如

new LdcInsnNode(Type.getObjectType(name))

使用 Type.getObjectType(…) 工厂方法,它期望 name 表示内部名称形式,例如com/example/ExampleClass 周围没有 L … ;。对于非数组类型,它等同于 Type.getType('L'+name+';')。工厂方法的选择很重要,因为 Type 对象也可以表示基本类型或方法类型。因为 ldcType.getObjectType 都只支持引用类型,所以这是自然的组合。

似乎 LdcInsnNode constructor documentation is a bit outdated, as it only mentions number types and String (Class support with ldc exists since Java 5, but the documentation contains links to 1.4.2). You may refer to MethodVisitor.visitLdcInsn(…) 而不是,在生成字节码时最终将对象传递给它。