方法调用指令(invokevirtual / invokestatic)由一些意外的指令代替

时间:2015-08-13 20:55:10

标签: java jvm bytecode java-bytecode-asm

我一直在调查这个错误整整三天,但仍然没有进展。我希望我能从这里得到一些提示。

我要做的是使用ASM库将MethodNode内联到MethodHandle调用站点(#5,#17和#30)。为简化起见,#5处的MethodHandle引用了静态方法static Functions.isFooString(String)boolen

在呼叫站点,内联之前的指令就像

    //Before
  stack=3, locals=3, args_size=3
     0: aload_0
     1: getfield      #15                 // Field guard:Ljava/lang/invoke/MethodHandle;
     4: aload_1
     5: invokevirtual #29                 // Method java/lang/invoke/MethodHandle.invokeExact:(Ljava/lang/String;)Z
     8: ifeq          24
    11: aload_0
    12: getfield      #17                 // Field trueTarget:Ljava/lang/invoke/MethodHandle;
    15: aload_1
    16: aload_2
    17: invokevirtual #31                 // Method java/lang/invoke/MethodHandle.invokeExact:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
    20: checkcast     #33                 // class java/lang/String
    23: areturn
    24: aload_0
    25: getfield      #19                 // Field falseTarget:Ljava/lang/invoke/MethodHandle;
    28: aload_1
    29: aload_2
    30: invokevirtual #31                 // Method java/lang/invoke/MethodHandle.invokeExact:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
    33: checkcast     #33                 // class java/lang/String
    36: areturn
   StackMapTable: number_of_entries = 1
   frame_type = 24 /* same */
 Exceptions:
    throws java.lang.Throwable

内联规则是在将参数存储到局部变量之后将内联的主体复制到调用站点中。内联后,指令列表为:

  //After
Classfile /C:/temp/DYNGuardWithTestHandle1439587569404.class
  Last modified Aug 14, 2015; size 913 bytes
  MD5 checksum 055a99d52cb622a7e86c59de79347f3e
public class DYNGuardWithTestHandle1439587569404 extends java.lang.invoke.BaseTemplate
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Utf8               DYNGuardWithTestHandle1439587569404
   #2 = Class              #1             // DYNGuardWithTestHandle1439587569404
   #3 = Utf8               java/lang/invoke/BaseTemplate
   #4 = Class              #3             // java/lang/invoke/BaseTemplate
   #5 = Utf8               guard
   #6 = Utf8               Ljava/lang/invoke/MethodHandle;
   #7 = Utf8               trueTarget
   #8 = Utf8               falseTarget
   #9 = Utf8               <init>
  #10 = Utf8               (Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)V
  #11 = Utf8               ()V
  #12 = NameAndType        #9:#11         // "<init>":()V
  #13 = Methodref          #4.#12         // java/lang/invoke/BaseTemplate."<init>":()V
  #14 = NameAndType        #5:#6          // guard:Ljava/lang/invoke/MethodHandle;
  #15 = Fieldref           #2.#14         // DYNGuardWithTestHandle1439587569404.guard:Ljava/lang/invoke/MethodHandle;
  #16 = NameAndType        #7:#6          // trueTarget:Ljava/lang/invoke/MethodHandle;
  #17 = Fieldref           #2.#16         // DYNGuardWithTestHandle1439587569404.trueTarget:Ljava/lang/invoke/MethodHandle;
  #18 = NameAndType        #8:#6          // falseTarget:Ljava/lang/invoke/MethodHandle;
  #19 = Fieldref           #2.#18         // DYNGuardWithTestHandle1439587569404.falseTarget:Ljava/lang/invoke/MethodHandle;
  #20 = Utf8               eval
  #21 = Utf8               invokeExact
  #22 = Utf8               (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  #23 = Utf8               java/lang/Throwable
  #24 = Class              #23            // java/lang/Throwable
  #25 = Utf8               test/code/jit/asm/methodhandle/Functions
  #26 = Class              #25            // test/code/jit/asm/methodhandle/Functions
  #27 = Utf8               isFooString
  #28 = Utf8               (Ljava/lang/String;)Z
  #29 = NameAndType        #27:#28        // isFooString:(Ljava/lang/String;)Z
  #30 = Methodref          #26.#29        // test/code/jit/asm/methodhandle/Functions.isFooString:(Ljava/lang/String;)Z
  #31 = Utf8               printTrueTarget
  #32 = NameAndType        #31:#22        // printTrueTarget:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  #33 = Methodref          #26.#32        // test/code/jit/asm/methodhandle/Functions.printTrueTarget:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  #34 = Utf8               java/lang/String
  #35 = Class              #34            // java/lang/String
  #36 = Utf8               java/lang/Object
  #37 = Class              #36            // java/lang/Object
  #38 = Utf8               printFalseTarget
  #39 = NameAndType        #38:#22        // printFalseTarget:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  #40 = Methodref          #26.#39        // test/code/jit/asm/methodhandle/Functions.printFalseTarget:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  #41 = Utf8               Code
  #42 = Utf8               StackMapTable
  #43 = Utf8               Exceptions
{
  final java.lang.invoke.MethodHandle guard;
    descriptor: Ljava/lang/invoke/MethodHandle;
    flags: ACC_FINAL

  final java.lang.invoke.MethodHandle trueTarget;
    descriptor: Ljava/lang/invoke/MethodHandle;
    flags: ACC_FINAL

  final java.lang.invoke.MethodHandle falseTarget;
    descriptor: Ljava/lang/invoke/MethodHandle;
    flags: ACC_FINAL

  public DYNGuardWithTestHandle1439587569404(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
    descriptor: (Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=4
         0: aload_0
         1: invokespecial #13                 // Method java/lang/invoke/BaseTemplate."<init>":()V
         4: aload_0
         5: aload_1
         6: putfield      #15                 // Field guard:Ljava/lang/invoke/MethodHandle;
         9: aload_0
        10: aload_2
        11: putfield      #17                 // Field trueTarget:Ljava/lang/invoke/MethodHandle;
        14: aload_0
        15: aload_3
        16: putfield      #19                 // Field falseTarget:Ljava/lang/invoke/MethodHandle;
        19: return

  public void eval();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return

  public java.lang.String invokeExact(java.lang.String, java.lang.String) throws java.lang.Throwable;
    descriptor: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=8, args_size=3
         0: aload_0
         1: getfield      #15                 // Field guard:Ljava/lang/invoke/MethodHandle;
         4: aload_1
         5: astore_3
         6: aload_3
         7: invokestatic  #0                  // #0
        10: fload_2
        11: nop
        12: iconst_0
        13: swap
        14: pop
        15: ifeq          44
        18: aload_0
        19: getfield      #17                 // Field trueTarget:Ljava/lang/invoke/MethodHandle;
        22: aload_1
        23: aload_2
        24: astore        4
        26: astore        5
        28: aload         5
        30: aload         4
        32: invokestatic  #33                 // Method test/code/jit/asm/methodhandle/Functions.printTrueTarget:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
        35: goto          38
        38: swap
        39: pop
        40: checkcast     #35                 // class java/lang/String
        43: areturn
        44: aload_0
        45: getfield      #19                 // Field falseTarget:Ljava/lang/invoke/MethodHandle;
        48: aload_1
        49: aload_2
        50: astore        6
        52: astore        7
        54: aload         7
        56: aload         6
        58: invokestatic  #40                 // Method test/code/jit/asm/methodhandle/Functions.printFalseTarget:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
        61: goto          64
        64: swap
        65: pop
        66: checkcast     #35                 // class java/lang/String
        69: areturn
      StackMapTable: number_of_entries = 1
        frame_type = 254 /* append */
          offset_delta = 44
          locals = [ class java/lang/Object, class java/lang/Object, class java/lang/Object ]
    Exceptions:
      throws java.lang.Throwable

}

显然,原始指令中的#5(之前)被错误的指令#7 - #12(之后)替换,这里不应该存在。相反,这里应该只有一条指令:

   invokestatic Method test/code/jit/asm/methodhandle/Functions.isFooString:(Ljava/lang/String;)Z;

更糟糕的是,invokeExact(之后)的#7完全非法,导致JVM崩溃。 其他两个指令#17和#30(之前)被正确替换。

从生成的字节码中可以清楚地看到,但这与我的代码定义的行为不同。构建MethodNode的方法是:

   MethodNode buildMethod(...){
     ...
    access=ACC_PUBLIC+ACC_STATIC;
    String owner = Type.getType(definingClass).getInternalName();
    String desc = type.toMethodDescriptorString();
    MethodNode methodNode = new MethodNode(Opcodes.ASM5, access, mhName, type.toString(), null, null);
    if(name.equals("isFooString")){
        methodNode.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
        System.out.println("owner="+owner+" name="+name+"  desc="+desc);
       //owner=test/code/jit/asm/methodhandle/Functions name=isFooString  desc=(Ljava/lang/String;)Z
        methodNode.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, owner, name, desc, false));
        methodNode.instructions.add(new InsnNode(Opcodes.IRETURN));
        return methodNode;
    }

    AbstractInsnNode insn = new MethodInsnNode(originIsFindVirtual?Opcodes.INVOKEVIRTUAL: Opcodes.INVOKESTATIC, owner, name, desc, false);
    Type[] args = Type.getArgumentTypes(desc);

    for(Type arg : args){
        AbstractInsnNode node = new VarInsnNode(arg.getOpcode(Opcodes.ILOAD), start);
        start += arg.getSize();
        methodNode.instructions.add(node);
    }
    methodNode.instructions.add(insn);
    int Return = Type.getReturnType(desc).getOpcode(Opcodes.IRETURN);

    methodNode.instructions.add(new InsnNode(Return));
    return methodNode;
    }

InliningAdapter:

//The full version can be accessed by https://github.com/xushijie/InlineMethod/blob/typeinference/src/main/java/code/jit/asm/core/InliningAdapter.java
public class InliningAdapter extends RemappingMethodAdapter{

/** The main task in the Constructor is to store pushed parameters of the MethodNode to a new local variables and store the remaining arguments of the stack to another temporary stacks in case of exceptions in the MethodNode */ 
public InliningAdapter(LocalVariablesSorter lvsMV, int acc, String desc,
Remapper remapper, Label end, MethodContext context) {
   super(acc, desc, lvsMV, remapper);
   lvs = lvsMV;
   mv = context.getRawMV();
   this.end = end;
   _context = context;
   List<Type> types = _context.getOperandStack();
   int offset = ((acc & Opcodes.ACC_STATIC) != 0 ? 0 : 1);
   Type[] args = Type.getArgumentTypes(desc);
   for (int i = args.length - 1; i >= 0; i--) {
      super.visitVarInsn(args[i].getOpcode(Opcodes.ISTORE), i + offset);
   }
   int poped = args.length;
   if (offset > 0) {
   poped++;
   super.visitVarInsn(Opcodes.ASTORE, 0);
   }
   int left = types.size() - poped - 1;
   while (left > 0) {
   // NON-parameters in the stack => pop up too and restore back after
   // complete.
   int variable = newLocal(types.get(left));
   int opcode = types.get(left).getOpcode(Opcodes.ISTORE);
   __callerstacks.add(0, new StackEle(types.get(left), variable)); //    |-->TOP
   mv.visitVarInsn(opcode, variable);
   left--;
   }
}

@Override
public void visitMethodInsn(final int opcode, final String owner,
        final String name, final String desc, final boolean itf) {
    System.out.println("[Callee: ] invokeVirtual "+ owner +"  "+name+"  "+ desc);
    _context.getRawMV().visitMethodInsn(opcode, owner, name, desc, itf);
    //_context.getRawMV() is a MethodWriter. and the value of the (owner, name, desc) is /.//Functions, isFooString, (String;)Z, which is correct. 
}

@Override
public void visitVarInsn(final int opcode, final int var) {
    //I confirm this method is not invoked after visitMethodInsn. 
    super.visitVarInsn(opcode, var + firstLocal);
}
}

对于原始字节码中的每个invokevirtual指令(#5,#17。#30),它就像:

mn = buildMethodNode(...);
mn.accept(new InliningAdapter(this,
                    opcode == Opcodes.INVOKESTATIC ? Opcodes.ACC_STATIC : 0, desc,  _context));  //mn is my constructed MethodNode previsouly, which contains one method invocation. 

要解决此问题(缺少第一个访问过的invokestatic的参数,并添加了一些非法指令)。我调试了我的代码(抱歉我不能在这里发布所有文件,因为文件太多了),它监视写入visitMethodInsn InliningAdapter中MethodWriter的所有参数。它证明了所有这些值(函数,isFooString,(String;)Z)是正确的,这与我的期望相同。

所以我的问题是:

  • 我的MethodNode构建器有什么问题吗?
  • 这个问题会导致什么样的原因?还有其他类似的情况,其中第一个invokevirtual指令被nop,fdouble和asatore错误地替换(我的代码中不存在这些替换)。

  • 此外,我确认在visitMethodInsn完成之前visitMethodInsn之后未调用覆盖方法accept。这意味着,错误的指令,即#10 fload和#12 iconst,不是来自构建的MethodNode。

  • 我再次尝试禁用第一个invokevirtual的内联(#5 // Before)。生成的类成功并可以启动。对我来说,问题出现在isFooString MethodNode构建。

0 个答案:

没有答案