instructions et parameters inattendus pour invokevirtual dans le corps de la méthode en ligne

J’ai suivi l’exemple de code dans la “3.2.6 méthode en ligne” dans http://asm.ow2.org/current/asm-transformations.pdf , pour incorporer un MethodNode à un site d’appel.

Mon problème est qu’il y a des instructions inattendues affichées dans le bytecode généré après l’insertion (ces bytecodes sont incompatibles avec mon code), et le problème existe uniquement lorsqu’un ifeq trouve après le corps de méthode intégré et que la variable de la stack est chargée par xLoad.

Je n’ai toujours pas trouvé la cause première du problème. Maintenant, je commence à supprimer tous les codes inutiles, dans le but de le reproduire avec le moins de code. Toute personne ayant de bonnes suggestions est la bienvenue.

Voici l’un de mes fondements existants: le problème n’est pas lié à la trame, car le problème est toujours là lorsque Configuration pour ClassRewiter est COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS et Configuration pour COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES

Pour simplifier le problème, le corps de l’appelé est:

 public invokeExact(Ljava/lang/Ssortingng;)Z      ICONST_0          IRETURN 

Et l’appelant est:

 public Ssortingng invokeExact(Ssortingng a, Ssortingng b){       boolean flag = _guard.invokeExact(a);       if(flag)       {         return a;       }       return b;     } 

. La trace de manipulation de bytecode correspondante de l’appelant sur MethodWriter est la suivante:

 public java.lang.Ssortingng invokeExact(java.lang.Ssortingng, java.lang.Ssortingng)      ....        4: aload_1             5: astore_3            6: astore       4        8: iconst_0            visitJumpInsn goto   L1029004533         //visitmax() empty implementation.         //visitEnd() Empty implementation.         visitlabel   L1029004533  // This label is newly created once inlining starts, but is visited until the end of inlining as the target of all xReturn instructions in the Callee's method body.      visitVarInsn istore 5      visitVarInsn iload 5      visitJumpInsn ifeq L980604133      visitVarInsn  aload 1      visitInsn       areturn      visitLabel     L980604133      visitVarInsn  aload 2      visitInsn       areturn 

Enfin, le fichier de classe généré est:

 public java.lang.Ssortingng invokeExact(java.lang.Ssortingng, java.lang.Ssortingng);   stack=2, locals=6, args_size=3        0: aload_0             1: getfield     #17                // Field _guard:Ltest/code/jit/asm/simple/MHGuard;        4: aload_1             5: astore_3            6: astore       4        8: iconst_0            **9: goto         9       12: fconst_0           13: iconst_2**           14: iload        5       16: ifeq         21       19: aload_1            20: areturn            21: aload_2            22: areturn          StackMapTable: number_of_ensortinges = 2          frame_type = 255 /* full_frame */         offset_delta = 12         locals = [ class test/code/jit/asm/simple/GWTSample, class java/lang/Ssortingng, class java/lang/Ssortingng, class java/lang/Ssortingng, class test/code/jit/asm/simple/MHGuard ]         stack = [ int ]          frame_type = 252 /* append */            offset_delta = 8       locals = [ int ] 

où # 9, # 12 et # 13 sont faux.


Des parties de mon code sont (je vais continuer à simplifier mon code le week-end):

 public class MethodCallInliner extends LocalVariablesSorter { protected MethodContext _context; private IPlugin _plugin; public MethodCallInliner(int access, Ssortingng desc, MethodContext context){ // context.getRawMV() return a Class MethodWriter. super(Opcodes.ASM5, access, desc, context.getRawMV()); _context = context; //_fieldVisitor = new FieldManipulationVisitor(mv, context); _plugin = NameMappingService.get().getPlugin(); //removed some unncessary codes.. } @Override public void visitMethodInsn(int opcode, Ssortingng owner, Ssortingng name, Ssortingng desc, boolean itf) { if(opcode != Opcodes.INVOKEVIRTUAL){ mv.visitMethodInsn(opcode, owner, name, desc, itf); return; } MethodNode mn = _plugin.map(owner, name, desc, _context, this); if(mn == null){ mv.visitMethodInsn(opcode, owner, name, desc, itf); return; } //ASMUtil.debug(mn); //to double confirm the mn content is correct. performInline(ASMUtil.isStaticMethod(mn)?Opcodes.INVOKESTATIC:Opcodes.INVOKEVIRTUAL, owner, desc, mn); _plugin.postProcess(mn, this, _context); } protected void performInline(int opcode, Ssortingng owner, Ssortingng desc, MethodNode mn){ Remapper remapper = Mapper.getMapper(_context, _context.getReceiverFieldName()); mn.instructions.resetLabels(); Label end = new Label(); System.out.println("++"+end.toSsortingng()); _context.beginInline(); mn.accept(new InliningAdapter(this, opcode == Opcodes.INVOKESTATIC ? Opcodes.ACC_STATIC : 0, desc, remapper, end, _context)); _context.endInline(); super.visitLabel(end); } public void visitJumpInsn(int opcode, Label label) { super.visitJumpInsn(opcode, label); } @Override public void visitVarInsn(final int opcode, final int var){ super.visitVarInsn(opcode, var);; } ... } 

[Nouveaux résultats]

Je pense que je suis beaucoup plus proche du problème maintenant.

  • Le visiteur en ligne MethodCallInliner devrait être correct car un autre test indépendant de ce visiteur avec les mêmes classes réussit.
  • Le problème est de savoir comment créer la chaîne MethodVisitor. a) Je ne souhaite qu’une seule visite sur les instructions de la méthode. 2) MethodCallInliner est disposé à la fin de la chaîne. Avant cela, quelques visiteurs supplémentaires sont insérés dans des informations de type inférence, qui pourraient être utilisées lors de l’inclusion de méthodes dans MethodCallInliner .

Mon constructeur de chaîne est:

 @Override public MethodVisitor visitMethod(int access, Ssortingng name, Ssortingng desc, Ssortingng signature, Ssortingng[] exceptions) { ..... MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); return new TransformationChain(Opcodes.ASM5, access, name, desc, signature, mv, _context); //return new MethodCallInliner(access, desc, context); //This is OK. } public class TransformationChain extends BaseMethodTransform { public TransformationChain(int api, int access, Ssortingng name, Ssortingng desc, Ssortingng signature, MethodVisitor mv, ClassContext classContext) { super(api, mv, classContext.getClassName(), name, desc); .... ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS); _visitors.add(new AnalyzerAdapter(Opcodes.ASM5, owner, access, name,desc, cw.visitMethod(access, name, desc, owner, null)){ @Override public void visitJumpInsn(final int opcode, final Label label){ super.visitJumpInsn(opcode, label); } }); MethodNode node = new MethodNode(access, name, desc, signature, null); _visitors.add(node); //cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS); //MethodNode node = context.getClassContext().getMethodNode(name, desc); //_visitors.add(new TypeInferencer(Opcodes.ASM5, cw.visitMethod(access, name, desc, null, null), node, context)); _visitors.add(name.equals(Constants.CONSTRUCTOR)?new ConstructorMerge(access, desc, context): new MethodCallInliner(access, desc, context)); } } abstract class BaseMethodTransform extends MethodVisitor { protected final List _visitors = new LinkedList(); public BaseMethodTransform(int api, MethodVisitor mv, Ssortingng className, Ssortingng methodName, Ssortingng methodDesc) { super(api, mv); } @Override public void visitMethodInsn(int opcode, Ssortingng owner, Ssortingng name, Ssortingng desc, boolean itf) { for (MethodVisitor mv : _visitors) { mv.visitMethodInsn(opcode, owner, name, desc, itf); } } @Override public void visitIntInsn(int opcode, int operand) { for (MethodVisitor mv : _visitors) { mv.visitIntInsn(opcode, operand); } } @Override public void visitMaxs(int maxStack, int maxLocals) { for (MethodVisitor mv : _visitors) { if (mv!= _visitors.get(_visitors.size()-1) || mv instanceof TraceMethodVisitor) { continue; } mv.visitMaxs(maxStack, maxLocals); } } @Override public void visitJumpInsn(final int opcode, final Label label) { for (MethodVisitor mv : _visitors) { mv.visitJumpInsn(opcode, label); } } ...... } 

Ma découverte ici est que la classe générée est correcte si je commente _visitors.add(new AnalyzerAdapter..); dans le TransformationChain , le MethodVisitor de qui est nouvellement créé ici. Il semble que certains éléments d’une méthode aient un statut, qui pourrait être modifié par MethodWriters (même s’ils sont tous indépendants) et que la modification précédente a un impact sur les visiteurs ultérieurs .

J’ai aussi remarqué que c’est l’étiquette:

 /** * Informations about forward references. Each forward reference is * described by two consecutive integers in this array: the first one is the * position of the first byte of the bytecode instruction that contains the * forward reference, while the second is the position of the first byte of * the forward reference itself. In fact the sign of the first integer * indicates if this reference uses 2 or 4 bytes, and its absolute value * gives the position of the bytecode instruction. This array is also used * as a bitset to store the subroutines to which a basic block belongs. This * information is needed in {@linked MethodWriter#visitMaxs}, after all * forward references have been resolved. Hence the same array can be used * for both purposes without problems. */ private int[] srcAndRefPositions; 

Lors de sa première visite par AnalyzerAdapter :: visitJmpAdadpter, deux ints, par exemple 10 et 11, sont insérés au début du tableau. Ensuite, dans la prochaine itération `MethodCallInliner :: visitJmpInsn`, deux nouveaux ints supplémentaires sont ajoutés à la position 2 et 3. Le contenu du tableau est maintenant:

[10, 11, 16, 17, 0, 0] dans lequel la paire (10,11) est pour AnalyzerAdapter et la paire (16,17) est pour Method MethodCallInliner .

Mais ce qui me surprend ici, c’est que l’ASM devrait être capable de distinguer différentes paires pour le bon MethodVisitor lors de la génération de la classe bytcode (ou bloc, calcul de la stack, peu importe).

Le code est accessible par https://github.com/xushijie/InlineMethod/tree/typeinference

Le problème est dû au fait que l’étiquette (le lecteur de classe lit un fichier de classe) est visitée par un pipeline MethodVisitor . Le libellé a un champ int [] srcAndRefPositions . Deux de ses positions consécutives (cfr. La fin de mon message d’origine) sont mises à jour une fois que le label est accédé par un MethodVisitor. Dans mon cas, l’étiquette dans l’ ifeq label contient 2 MethodVisitors. Il semble que la position incorrecte dans srcAndRefPositions soit utilisée lors de la génération du fichier de classe (en utilisant le dernier MethodVisitor).

Je n’ai pas étudié la cause première. Au lieu de cela, ma solution consistait à cloner l’étiquette puis à utiliser la nouvelle étiquette lorsqu’elle est visitée par un MethodVisitor.