Я исследовал эту ошибку в течение целых трех дней, но до сих пор нет прогресса. Надеюсь, я смогу получить отсюда несколько советов.
То, что я пытаюсь сделать, - это встраивать 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
. Это означает, что неправильные инструкции, т.е. # 10fload
и # 12iconst
, не являются встроенным MethodNode. -
Я сделал еще одну попытку, которая отключит вложение первого invokevirtual (# 5//Before). Сгенерированный класс успешно завершен и может быть запущен. Для меня это похоже на проблему в
isFooString
BuildingNode.