我一直在调查这个错误整整三天,但仍然没有进展。我希望我能从这里得到一些提示。
我要做的是使用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)是正确的,这与我的期望相同。
所以我的问题是:
这个问题会导致什么样的原因?还有其他类似的情况,其中第一个invokevirtual
指令被nop,fdouble和asatore错误地替换(我的代码中不存在这些替换)。
此外,我确认在visitMethodInsn
完成之前visitMethodInsn
之后未调用覆盖方法accept
。这意味着,错误的指令,即#10 fload
和#12 iconst
,不是来自构建的MethodNode。
我再次尝试禁用第一个invokevirtual的内联(#5 // Before)。生成的类成功并可以启动。对我来说,问题出现在isFooString
MethodNode构建。