Finn Voichick Finn Voichick - 19 days ago 5
Java Question

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

I'm using the ASM library to modify bytecode created by others. For an arbitrary method in an arbitrary class, I'd like to create an

LdcInsnNode
that adds the current class to the stack.

For example, let's say I'm transforming a class called
com.example.ExampleClass
. I'd like to create bytecode that is equivalent to
System.out.println(ExampleClass.class.getName());
.

This seems like a relatively simple task. When I use the Eclipse Bytecode Outline plugin, it says that the following bytecode is equivalent:

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


I tried the following code:

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;
}


This is being run in an extension of
ClassNode
, so
name
refers to the
ClassNode.name
field. The
InsnList
returned by this method is being inserted before an existing
AbstractInsnNode
using
InsnList.insertBefore(AbstractInsnNode, printClass())
. When this point is reached in the bytecode, I get an error with the following reason:

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


This is clearly because the LDC instruction is adding the String
"Lcom/example/ExampleClass;.class"
instead of the actual class
Lcom/example/ExampleClass;.class
.

Is there any workaround for this? It seems impossible to directly add the
Class
object to an
LdcInsnNode
because the class doesn't exist yet. But is there a way to add an instruction that loads the
Class
object?

In my particular case, calling the
Object.getClass()
method is not an option because it needs to work from a static context.

Answer

You don’t need to refer to a Class object, in fact, it isn’t even supported (directly). If you want to push a Class to the operand stack via ASM, you have to refer to it as Type instance.

E.g.

new LdcInsnNode(Type.getObjectType(name))

using the Type.getObjectType(…) factory method, which expects name to represent the internal name form, e.g. com/example/ExampleClass without L … ; around it. It’s equivalent to Type.getType('L'+name+';') for non-array types. The choice of the factory method is important as a Type object may represent primitive types or method types as well. Since both, ldc and Type.getObjectType, only support reference types, this is the natural combination.

It seems the 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(…) instead, to which the object is eventually passed when producing the byte code.

Comments