Method invocation instruction (invokevirtual/invokestatic) is substituted by some unexpected instructions

JavaJvmBytecodeJava Bytecode-Asm

Java Problem Overview


I have been investigating this error for a whole three days, but still no progress. I hope I can get some tips from here.

What I am trying to do is to inline a MethodNode into a MethodHandle Call Site (#5, #17, and #30) with ASM library. For simplification, the MethodHandle at #5 references a static method static Functions.isFooString(String)boolen.

At the call site, the instruction before inlining is like

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

The inlining rule is that copy inline's body into the call site after storing the arguments to local variables. After inlining, the instruction list is:

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

}

Clearly, the #5 (before)in the original instruction is replaced by WRONG instructions #7--#12 (after), which should not exist here. Instead, there should only be one instruction, here:

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

Worse, the #7 of the invokeExact(after) is completely illegal which caused the JVM crash down. The other two instructions #17 and #30 (before) are correctly substituted.

It is clear from the generated bytecodes, but this is different from the behavior defined by my code. The way to build the MethodNode is:

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

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

For each invokevirtual instruction (#5, #17. #30) in the original bytecodes, it is like:

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. 

To resolve the issue( the parameters for the first visited invokestatic is missing and some illegal instructions are added). I debug my code (Sorry I cannot post all of it here because there are too many files), and it monitors all parameters that are written to the MethodWriter in thevisitMethodInsn of the InliningAdapter. It proves that all these values (Functions, isFooString, (String;)Z) are correct, which are the same as my expectation.

So my questions are:

  • Is there something wrong in my MethodNode builder?

  • What kinds of reasons can result to this issue? There are also other similar cases, where the first invokevirtual instructions are wrongly substituted by something like, nop, fdouble, and asatore (These substitutions do not exist in my code).

  • Also, I confirm the override method visitMethodInsn is not invoked after the visitMethodInsn before accept completes. That means, the wrong instructions, i.e., #10 fload and #12 iconst, are not from the built MethodNode.

  • I made another try that disable the inlining of the first invokevirtual (#5 //Before). The generated class succeeds and can be launched. For me, it looks like the problem is at isFooString MethodNode building.

Java Solutions


Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionshijie xuView Question on Stackoverflow