Unexpected instructions and parameters for invokevirtual in the inline method case

I executed the example code in "3.2.6 Inline Method" at http://asm.ow2.org/current/asm-transformations.pdf to embed MethodNode on the call site,

My problem is that there are some unexpected instructions displayed in the generated bytecode after inline (these bytecodes are incompatible with my code), and the problem exists only if ifeq is located after the inlineed method body and xLoad is loaded on the stack.

I still have not found the root cause of the problem. Now I'm starting to delete all the extra codes, trying to play it with the smallest code. Someone has some good suggestions.

Here is one of my existing reasons: the problem is not frame related because the problem still exists when Configuration for ClassRewiter COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS and Configuration for ClassReader ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES

To simplify the task, the body of the called person:

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

And the caller:

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

. The corresponding bytecode trace of the caller in the MethodWriter method:

 public java.lang.String invokeExact(java.lang.String, java.lang.String)      ....        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 method body.      visitVarInsn istore 5      visitVarInsn iload 5      visitJumpInsn ifeq L980604133      visitVarInsn  aload 1      visitInsn       areturn      visitLabel     L980604133      visitVarInsn  aload 2      visitInsn       areturn 

Finally, the generated class file:

 public java.lang.String invokeExact(java.lang.String, java.lang.String);   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_entries = 2          frame_type = 255 /* full_frame */         offset_delta = 12         locals = [ class test/code/jit/asm/simple/GWTSample, class java/lang/String, class java/lang/String, class java/lang/String, class test/code/jit/asm/simple/MHGuard ]         stack = [ int ]          frame_type = 252 /* append */            offset_delta = 8       locals = [ int ] 

where # 9, # 12 and # 13 are incorrect.




Parts of my code (I will continue simple code on the weekend):

 public class MethodCallInliner extends LocalVariablesSorter { protected MethodContext _context; private IPlugin _plugin; public MethodCallInliner(int access, String 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, String owner, String name, String 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, String owner, String desc, MethodNode mn){ Remapper remapper = Mapper.getMapper(_context, _context.getReceiverFieldName()); mn.instructions.resetLabels(); Label end = new Label(); System.out.println("++"+end.toString()); _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);; } ... } 



[New Results]

I think that now I am much closer to the problem.

  • The incoming MethodCallInliner visitor must be correct, as other independent testing of this visitor with the same classes succeeds.
  • The problem lies in the method of constructing the MethodVisitor chain. a) I want only one passage to be visited in the instructions of the Method. 2) MethodCallInliner is located at the end of the chain. Before this, several more visitors are inserted into information such as output, which could be used during the selection of a method in MethodCallInliner .

My chain builder:

 @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] 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, String name, String desc, String 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<MethodVisitor> _visitors = new LinkedList<MethodVisitor>(); public BaseMethodTransform(int api, MethodVisitor mv, String className, String methodName, String methodDesc) { super(api, mv); } @Override public void visitMethodInsn(int opcode, String owner, String name, String 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); } } ...... } 

My Finding is that the generated class is correct if I comment on _visitors.add(new AnalyzerAdapter..); in the TransformationChain whose CreateVisitor method is created here again. It seems that some elements of the method have a status that MethodWriters can change (even they are all independent), and the previous modification affects later visitors .

I also noticed that this is a shortcut:

 /** * 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; 

When it is first visited by AnalyzerAdapter :: visitJmpAdadpter, two ints are inserted at the beginning of the array, for example, 10 and 11. Then, in the next iteration of “MethodCallInliner :: visitJmpInsn” two more new ints are added at positions 2 and 3. Now the contents of the array:

[10, 11, 16, 17, 0, 0] in which the pair (10.11) is for the AnalyzerAdapter, and the pair (16.17) is for the MethodCallInliner method.

But what puzzles me: should ASM be able to distinguish different pairs for the correct MethodVisitor when creating the bytcode class (or block, compute stack frames)?

The code can be accessed https://github.com/xushijie/InlineMethod/tree/typeinference

+2
java-bytecode-asm bytecode bytecode-manipulation
Aug 17 '15 at 14:47
source share
1 answer

The problem occurs when a label (reading a class reading from a class file) is visited by the MethodVisitor pipeline. The label has an int [] srcAndRefPositions . Two of its consecutive positions (cfr. End of my original message) are updated after a label is accessed by MethodVisitor. In my case, the label in the ifeq label contains 2 MethodVisitors. It seems that the incorrect position in srcAndRefPositions used when creating the class file (using the last MethodVisitor method).

I have not investigated the root cause. Instead, my solution was to clone the shortcut, and then use the new shortcut when it visits MethodVisitor.

+1
Aug 24 '15 at 14:22
source share



All Articles