有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

内联方法体中invokevirtual的字节码意外指令和参数

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

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

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

这是我现有的发现之一:问题与框架无关,因为当ClassRewiter的配置为COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS而ClassReader的配置为ClassReader.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;
      }

。MethodWriter上调用方的相应字节码操作跟踪是:

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应该是正确的,因为使用相同类对该访问者进行的另一个独立测试成功
  • 问题在于如何构建MethodVisitor链。a) 我只想通过一次方法说明。2) MethodCallInliner排列在链的末端。在此之前,会向推理类型信息插入更多的访问者,这可能会在^{中的方法内联期间使用

我的链生成器是:

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

我在这里的发现是,如果我在TransformationChain中注释掉_visitors.add(new AnalyzerAdapter..);,那么生成的类是正确的,这里新创建了该类的MethodVisitor一个方法的某些元素似乎有状态,可能会被方法编写者修改(即使他们都是独立的),之前的修改会对后来的访问者产生影响

我还注意到它的标签:

/**
 * 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::Visitjmpadpter首次访问它时,在数组的开头插入两个int,例如10和11。然后在下一次迭代“MethodCallInliner::visitJmpInsn”中,在位置2和3处添加了另外两个新的int。现在,数组内容是:

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

但让我困惑的是:在生成字节码类(或块、堆栈帧计算)时,ASM应该能够为正确的MethodVisitor区分不同的对

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


共 (1) 个答案

  1. # 1 楼答案

    当标签(类读取器从类文件中读取)被MethodVisitor管道访问时,就会出现问题。标签上有一个字段int [] srcAndRefPositions。它的两个连续位置(cfr,我的原始帖子的结尾)会在访问者访问标签后更新。在我的例子中,ifeq label中的标签包含2个方法访问者。生成类文件(使用最后一个MethodVisitor)时,似乎使用了srcAndRefPositions中的错误位置

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