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
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:
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