Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Method invocation instruction (invokevirtual/invokestatic) is substituted by some unexpected instructions [duplicate]

I followed the sample code in the "3.2.6 Inline Method“ in the http://asm.ow2.org/current/asm-transformations.pdf, to inline a MethodNode to a call site.

My problem is that there are some unexpected instructions shown in the generated bytecode after inlining (these bytecodes are inconsistent to my code), and the problem only exists when an ifeq is after inlineed method body and the variable on the stack is loaded by xLoad.

I still have not found the root cause for the issue. Now i am start to remove all unncessary codes, aiming to reproduce it with least code. Anyone has good suggestions are welcome.

Here is one of my existing founding: the problem is not related to the Frame, because the problem is still there when Configuration for ClassRewiter is COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS and Configuration for ClassReader ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES

To simplify the problem, the callee's body is:

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

 And the Caller is:

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

. The corresponding bytecode manipulation trace of the caller on the MethodWriter is:

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's 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 is:

 

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 wrong.


Parts of my code are (I will continue to simple my 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 Findings]

I think I am much closer to the problem now.

  • The inlining visitor MethodCallInliner should be correct as another independent testing of this visitor with the same classes succeeds.
  • The issue is at the way how to build the MethodVisitor chain. a) I want only one pass visiting on the Method instructions. 2) The MethodCallInliner is arranged at the end of the chain. Before it, some more visitors are inserted to inference type information, which would possible used during method inlining in the MethodCallInliner.

My chain builder is:

@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 here is that the generated class is correct if I comment out _visitors.add(new AnalyzerAdapter..); in the TransformationChain, the MethodVisitor of which is newly created here. It seems that some elements of a method have status, which might be modified by MethodWriters (even they are all independent) and the previous modification has impacts on the later visitors.

I also noticed it is the Label:

/**  * 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, e.g., 10 and 11, are inserted at the begin of the array. Then in the next iteration ``MethodCallInliner::visitJmpInsn`, another two new ints are added at position 2 and 3. Now the array content is:

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

But what puzzles me here is: the ASM should be able to distinct different pairs for the right MethodVisitor when generating the bytcode class (or block, stack frame calculation whatever)?

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

like image 985
shijie xu Avatar asked Aug 17 '15 14:08

shijie xu


People also ask

How the Java virtual machine handles method invocation and return?

When the JVM invokes a method, it creates a stack frame of the proper size for that method. Adding a new frame onto the Java stack when a method is invoked is called "pushing" a stack frame; removing a frame when a method returns is called "popping" a stack frame. The Java stack is made up solely of these frames.

What is Invokevirtual in Java?

invokevirtual retrieves the Java class for objectref, and searches the list of methods defined by that class and then its superclasses, looking for a method called methodname, whose descriptor is descriptor. Once a method has been located, invokevirtual calls the method.

What is Invokespecial in Java?

The invokespecial instruction is used to invoke instance initialization methods as well as private methods and methods of a superclass of the current class. In other words, invokespecial is used to call methods without concern for dynamic binding, in order to invoke the particular class' version of a method.


1 Answers

The problem is caused when the label (the classreader reads from a class file) is visited by a MethodVisitor pipeline. The label has a field int [] srcAndRefPositions. Two of its consecutive positions (cfr. the end of my original post) are updated once the label is accessed by a MethodVisitor. In my case, the label in the ifeq label holds 2 MethodVisitors. It seems the incorrect position in the srcAndRefPositions is used when generating the class file (using the last MethodVisitor).

I did not investigate the root cause. Instead, my solution was to clone the label and then use the new label when it is visited by a MethodVisitor.

like image 192
shijie xu Avatar answered Sep 29 '22 00:09

shijie xu