2

J'utilise le ASM library pour modifier le code intermédiaire créé par d'autres. Pour une méthode arbitraire dans une classe arbitraire, je voudrais créer un LdcInsnNode qui ajoute la classe en cours à la pile.Comment créer un LdcInsnNode ASM qui ajoute statiquement la classe actuelle à la pile?

Par exemple, disons que je suis la transformation d'une classe appelée com.example.ExampleClass. Je voudrais créer un bytecode équivalent à System.out.println(ExampleClass.class.getName());.

Cela semble être une tâche relativement simple. Lorsque j'utilise l'Eclipse bytecode plugin Plan, il est dit que le bytecode suivante est équivalente:

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 

J'ai essayé le code suivant:

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

Ceci est en cours d'exécution dans une extension de ClassNode, si name fait référence au champ ClassNode.name. Le InsnList retourné par cette méthode est inséré avant un AbstractInsnNode existant à l'aide InsnList.insertBefore(AbstractInsnNode, printClass()). Lorsque ce point est atteint dans le bytecode, je reçois une erreur avec la raison suivante:

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

Ceci est clairement parce que l'instruction LDC ajoute la chaîne "Lcom/example/ExampleClass;.class" au lieu de la classe réelle Lcom/example/ExampleClass;.class.

Y a-t-il une solution de contournement pour cela? Il semble impossible d'ajouter directement l'objet Class à un LdcInsnNode car la classe n'existe pas encore. Mais existe-t-il un moyen d'ajouter une instruction qui charge l'objet Class?

Dans mon cas particulier, appelant la méthode Object.getClass() n'est pas une option, car elle a besoin de travailler dans un contexte statique.

Répondre

2

Vous n'avez pas besoin de se référer à un objet Class, en fait, il est même pas pris en charge (directement). Si vous souhaitez insérer un Class dans la pile d'opérandes via ASM, vous devez vous référer à l'instance Type.

E.g.

new LdcInsnNode(Type.getObjectType(name)) 

en utilisant le procédé de l'usine Type.getObjectType(…), qui attend name pour représenter la forme du nom interne, par exemple com/example/ExampleClass sans L … ; autour d'elle. C'est équivalent à Type.getType('L'+name+';') pour les types non-tableau. Le choix de la méthode d'usine est important car un objet Type peut également représenter des types primitifs ou des types de méthode. Depuis à la fois, ldc et Type.getObjectType, seuls les types de référence de soutien, c'est la combinaison naturelle.

Il semble que le LdcInsnNode constructor documentation est un peu obsolète, car il ne mentionne que les types de nombre et String (Class avec le soutien ldc existe depuis Java 5, mais la documentation contient des liens vers 1.4.2). Vous pouvez vous référer à MethodVisitor.visitLdcInsn(…) à la place, à laquelle l'objet est finalement passé lors de la production du code d'octet.