内联方法体中调用虚拟的意外指令和参数

我按照 http://asm.ow2.org/current/asm-transformations.pdf 中“3.2.6 内联方法”中的示例代码,将 MethodNode 内联到调用站点。

我的问题是,内联后生成的字节码中显示了一些意想不到的指令(这些字节码与我的代码不一致),并且仅当内联方法体之后并且堆栈上的变量由xLoad加载时,问题才存在。ifeq

我仍然没有找到问题的根本原因。现在我开始删除所有不必要的代码,旨在用最少的代码重现它。欢迎任何有好建议的人。

这是我现有的一个基础:问题与Frame无关,因为当ClassRewiter的配置和ClassReader的配置时,问题仍然存在。COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXSClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES

为了简化问题,被叫方的身体是:

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

呼叫者是:

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

.方法编写器上调用方的相应字节码操作跟踪为:

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

最后,生成的类文件是:

 

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 ]

其中 #9、#12 和 #13 是错误的。


我的部分代码是(我将在周末继续简化我的代码):

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

[新发现]

我想我现在离这个问题更近了。

  • 内联访问者应该是正确的,因为具有相同类的此访问者的另一个独立测试成功。MethodCallInliner
  • 问题在于如何构建方法访问器链。a) 我只想要一张通行证,按照方法说明进行访问。2)布置在链的末端。在此之前,还会插入更多的访问者来推断类型信息,这些信息可能会在 .MethodCallInlinerMethodCallInliner

我的链条制造商是:

@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);
            }
        }
     ......
}

我在这里发现是,如果我在 中注释掉生成的类,则生成的类是正确的,其方法访问器是在此处新创建的。似乎方法的某些元素具有状态,这些状态可能会被MethodWriters修改(即使它们都是独立的),并且先前的修改对以后的访问者有影响_visitors.add(new AnalyzerAdapter..);TransformationChain

我还注意到它是标签:

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

当 AnalyzerAdapter::visitJmpAdadpter 首次访问它时,在数组的开头插入两个整数,例如 10 和 11。然后在下一个迭代“MethodCallInliner::visitJmpInsn”中,在位置2和3处添加了另外两个新整数。现在数组内容是:

[10, 11, 16, 17, 0, 0] 其中对 (10,11) 用于 AnalyzerAdapter,对 (16,17) 用于方法 。MethodCallInliner

但让我感到困惑的是:ASM应该能够在生成bytcode类(或块,堆栈帧计算等)时为正确的方法访问器区分不同的对?

该代码可以通过 https://github.com/xushijie/InlineMethod/tree/typeinference


答案 1

当管道访问标签(类读取器从类文件中读取)时,会导致此问题。标签有一个字段 。它的两个连续位置(cfr.我原始帖子的末尾)一旦被方法访问器访问标签就会更新。在我的情况下,标签中的标签包含2个方法访问者。生成类文件时,似乎使用了 中的不正确位置(使用最后一个方法访问器)。MethodVisitorint [] srcAndRefPositionsifeq labelsrcAndRefPositions

我没有调查根本原因。相反,我的解决方案是克隆标签,然后在方法访问器访问新标签时使用新标签。


答案 2

推荐