Команда вызова вызова (invokevirtual/invokestatic) заменяется некоторыми неожиданными инструкциями

Я исследовал эту ошибку в течение целых трех дней, но до сих пор нет прогресса. Надеюсь, я смогу получить отсюда несколько советов.

То, что я пытаюсь сделать, - это встраивать MethodNode в SiteHandle Call Site (# 5, # 17 и # 30) в библиотеку ASM. Для упрощения MethodHandle в # 5 ссылается на статический метод 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 (до) в исходной команде заменяется инструкциями WRONG # 7 - # 12 (after), которые здесь не должны существовать. Вместо этого должна быть только одна инструкция:

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

Хуже того, № 7 invokeExact (after) полностью незаконна, что привело к сбою 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 отсутствуют и добавлены некоторые незаконные инструкции). Я отлаживаю свой код (извините, я не могу опубликовать его все здесь, потому что слишком много файлов), и он контролирует все параметры, которые записываются в MethodWriter в visitMethodInsn InliningAdapter. Это доказывает, что все эти значения (Functions, isFooString, (String;) Z) правильны, что совпадает с моим ожиданием.

Итак, мои вопросы:

  • Что-то не так в моем конструкторе MethodNode?
  • Какие причины могут привести к этой проблеме? Существуют и другие аналогичные случаи, когда первые команды invokevirtual ошибочно заменяются на что-то вроде: nop, fdouble и asatore (эти замены не существуют в моем коде).

  • Кроме того, я подтверждаю, что метод переопределения visitMethodInsn не вызывается после завершения visitMethodInsn до accept. Это означает, что неправильные инструкции, т.е. # 10 fload и # 12 iconst, не являются встроенным MethodNode.

  • Я сделал еще одну попытку, которая отключит вложение первого invokevirtual (# 5//Before). Сгенерированный класс успешно завершен и может быть запущен. Для меня это похоже на проблему в isFooString BuildingNode.