invokedynamic 指令如何决定它的描述?
How does invokedynamic instruction decide its description?
我正在使用 ASM 修改方法引用,所以我可以挂钩它。我的方法是修改 bootstrap 的 Handle
arg,并使其指向一个新方法,稍后我将生成它。以下是我实现此目标的代码片段。
class MyMethodVisitor extends MethodNode{
//...
@Override
public void visitEnd() {
super.visitEnd();
ListIterator<AbstractInsnNode> iterator = this.instructions.iterator();
while (iterator.hasNext()) {
AbstractInsnNode node = iterator.next();
if (node instanceof InvokeDynamicInsnNode) {
InvokeDynamicInsnNode tmpNode = (InvokeDynamicInsnNode) node;
String samName = tmpNode.name;
String middleMethodName = samName + "sa" + counter.incrementAndGet();
String middleMethodDesc = "";
Handle handle = (Handle) tmpNode.bsmArgs[1];
Type type = (Type) tmpNode.bsmArgs[2];
//handleNew will reference to the middleMethod
Handle handleNew = new Handle(Opcodes.H_INVOKESTATIC, "cn/curious/asm/lambda/LambdaModel", middleMethodName,
type.getDescriptor(), false);
tmpNode.bsmArgs[1] = handleNew;
middleMethodDesc = type.getDescriptor();
String dynamicNewDesc = "()" + Type.getReturnType(tmpNode.desc);
InvokeDynamicInsnNode newDynamicNode =
new InvokeDynamicInsnNode(tmpNode.name,
dynamicNewDesc,
tmpNode.bsm, tmpNode.bsmArgs[0], handleNew, type);
//Here, i remote the origin InvokeDynamicInsnNode and add mine
iterator.remove();
iterator.add(newDynamicNode);
MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC ,
middleMethodName,middleMethodDesc,null,null);
methodNode.visitEnd();
syntheticMethodList.add(methodNode);
}
}
accept(mv);
}
//...
}
而我的测试java源码如下:
public class LambdaModel {
public void test1() {
Consumer<String> consumer = System.out::println;
consumer.accept("hello world");
}
public void test2() {
Consumer<String> consumer = s -> System.out.println(s);
}
public static void main(String[] args) {
LambdaModel model = new LambdaModel();
model.test1();
}
}
并且,生成的class文件如下:
public class LambdaModel {
public LambdaModel() {
}
public void test1() {
System.out.getClass();
Consumer<String> consumer = LambdaModel::acceptsa1;
consumer.accept("hello world");
}
public void test2() {
Consumer<String> consumer = (s) -> {
System.out.println(s);
};
}
public static void lambda$accept(String str) {
System.out.println(str);
}
public static void main(String[] args) {
LambdaModel model = new LambdaModel();
model.test1();
}
//This method is generated by using ASM
public static void acceptsa1(String var0) {
}
}
大家可以看到,acceptsa1
方法没有代码体,因为我不知道如何解析对应的字节码。
下面是源代码的字节码片段,我的问题在里面。
public test1()V
L0
LINENUMBER 8 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
DUP
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
POP
INVOKEDYNAMIC accept(Ljava/io/PrintStream;)Ljava/util/function/Consumer; [
// handle kind 0x6 : INVOKESTATIC
java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
// arguments:
(Ljava/lang/Object;)V,
// handle kind 0x5 : INVOKEVIRTUAL
java/io/PrintStream.println(Ljava/lang/String;)V,
(Ljava/lang/String;)V
]
ASTORE 1
L1
Q1:follow instructions后面的invokeydynamic指令看不懂
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
DUP
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
POP
Q2:为什么invokedynamic的描述中有一个PrintStream参数?它基于什么规则。
INVOKEDYNAMIC accept(Ljava/io/PrintStream;)Ljava/util/function/Consumer;
抱歉,我无法准确描述问题。也许我的最后一个问题是 invokedynamic
指令如何在字节码中工作。
更新
我发现一个重要的点是将方法引用更改为普通的 lambda 表达式。生成中间方法名的时候,必须把这个方法的访问权限设置为ACC_SYNTHETIC,才能得到你想要的
例如,
方法参考:
MethodReferenceMain referenceMain = new MethodReferenceMain();
Comparator<User> comparator2 = referenceMain::compareByName;
生成Class:
MethodReferenceMain referenceMain = new MethodReferenceMain();
Comparator<User> comparator2 = (var1, var2) -> {
return referenceMain.compareByName(var1, var2);
};
注意:肯定还有我没遇到的问题。但这对我来说是重要的一步。
invokedynamic
指令不强制任何签名。代码生成器决定使用特定的签名和 bootstrap 方法,它们共同定义了实际的语义。
例如,将 StringConcatFactory.makeConcat(…)
instead of LambdaMetafactory.metafactory(…)
用作 bootstrap 方法时,签名及其含义完全不同。
这些方法及其包含的文档 类 对行为进行了详尽的描述。但在深入了解字节码细节之前,您应该首先了解源代码特性,即捕获方法引用,如 中所述。方法引用 System.out::println
在实例化 Consumer
时捕获在静态字段 System.out
中找到的 PrintStream
。因此,生成的工厂方法使用 PrintStream
并生成 Consumer
,导致签名 (Ljava/io/PrintStream;)Ljava/util/function/Consumer;
.
因此,在执行invokedynamic
指令之前,必须使用GETSTATIC java/lang/System.out
指令读取该字段。 DUP; INVOKEVIRTUAL getClass()
序列是内部 null
检查的一部分,已在
中讨论过
我正在使用 ASM 修改方法引用,所以我可以挂钩它。我的方法是修改 bootstrap 的 Handle
arg,并使其指向一个新方法,稍后我将生成它。以下是我实现此目标的代码片段。
class MyMethodVisitor extends MethodNode{
//...
@Override
public void visitEnd() {
super.visitEnd();
ListIterator<AbstractInsnNode> iterator = this.instructions.iterator();
while (iterator.hasNext()) {
AbstractInsnNode node = iterator.next();
if (node instanceof InvokeDynamicInsnNode) {
InvokeDynamicInsnNode tmpNode = (InvokeDynamicInsnNode) node;
String samName = tmpNode.name;
String middleMethodName = samName + "sa" + counter.incrementAndGet();
String middleMethodDesc = "";
Handle handle = (Handle) tmpNode.bsmArgs[1];
Type type = (Type) tmpNode.bsmArgs[2];
//handleNew will reference to the middleMethod
Handle handleNew = new Handle(Opcodes.H_INVOKESTATIC, "cn/curious/asm/lambda/LambdaModel", middleMethodName,
type.getDescriptor(), false);
tmpNode.bsmArgs[1] = handleNew;
middleMethodDesc = type.getDescriptor();
String dynamicNewDesc = "()" + Type.getReturnType(tmpNode.desc);
InvokeDynamicInsnNode newDynamicNode =
new InvokeDynamicInsnNode(tmpNode.name,
dynamicNewDesc,
tmpNode.bsm, tmpNode.bsmArgs[0], handleNew, type);
//Here, i remote the origin InvokeDynamicInsnNode and add mine
iterator.remove();
iterator.add(newDynamicNode);
MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC ,
middleMethodName,middleMethodDesc,null,null);
methodNode.visitEnd();
syntheticMethodList.add(methodNode);
}
}
accept(mv);
}
//...
}
而我的测试java源码如下:
public class LambdaModel {
public void test1() {
Consumer<String> consumer = System.out::println;
consumer.accept("hello world");
}
public void test2() {
Consumer<String> consumer = s -> System.out.println(s);
}
public static void main(String[] args) {
LambdaModel model = new LambdaModel();
model.test1();
}
}
并且,生成的class文件如下:
public class LambdaModel {
public LambdaModel() {
}
public void test1() {
System.out.getClass();
Consumer<String> consumer = LambdaModel::acceptsa1;
consumer.accept("hello world");
}
public void test2() {
Consumer<String> consumer = (s) -> {
System.out.println(s);
};
}
public static void lambda$accept(String str) {
System.out.println(str);
}
public static void main(String[] args) {
LambdaModel model = new LambdaModel();
model.test1();
}
//This method is generated by using ASM
public static void acceptsa1(String var0) {
}
}
大家可以看到,acceptsa1
方法没有代码体,因为我不知道如何解析对应的字节码。
下面是源代码的字节码片段,我的问题在里面。
public test1()V
L0
LINENUMBER 8 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
DUP
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
POP
INVOKEDYNAMIC accept(Ljava/io/PrintStream;)Ljava/util/function/Consumer; [
// handle kind 0x6 : INVOKESTATIC
java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
// arguments:
(Ljava/lang/Object;)V,
// handle kind 0x5 : INVOKEVIRTUAL
java/io/PrintStream.println(Ljava/lang/String;)V,
(Ljava/lang/String;)V
]
ASTORE 1
L1
Q1:follow instructions后面的invokeydynamic指令看不懂
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
DUP
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
POP
Q2:为什么invokedynamic的描述中有一个PrintStream参数?它基于什么规则。
INVOKEDYNAMIC accept(Ljava/io/PrintStream;)Ljava/util/function/Consumer;
抱歉,我无法准确描述问题。也许我的最后一个问题是 invokedynamic
指令如何在字节码中工作。
更新
我发现一个重要的点是将方法引用更改为普通的 lambda 表达式。生成中间方法名的时候,必须把这个方法的访问权限设置为ACC_SYNTHETIC,才能得到你想要的
例如,
方法参考:
MethodReferenceMain referenceMain = new MethodReferenceMain();
Comparator<User> comparator2 = referenceMain::compareByName;
生成Class:
MethodReferenceMain referenceMain = new MethodReferenceMain();
Comparator<User> comparator2 = (var1, var2) -> {
return referenceMain.compareByName(var1, var2);
};
注意:肯定还有我没遇到的问题。但这对我来说是重要的一步。
invokedynamic
指令不强制任何签名。代码生成器决定使用特定的签名和 bootstrap 方法,它们共同定义了实际的语义。
例如,将 StringConcatFactory.makeConcat(…)
instead of LambdaMetafactory.metafactory(…)
用作 bootstrap 方法时,签名及其含义完全不同。
这些方法及其包含的文档 类 对行为进行了详尽的描述。但在深入了解字节码细节之前,您应该首先了解源代码特性,即捕获方法引用,如 System.out::println
在实例化 Consumer
时捕获在静态字段 System.out
中找到的 PrintStream
。因此,生成的工厂方法使用 PrintStream
并生成 Consumer
,导致签名 (Ljava/io/PrintStream;)Ljava/util/function/Consumer;
.
因此,在执行invokedynamic
指令之前,必须使用GETSTATIC java/lang/System.out
指令读取该字段。 DUP; INVOKEVIRTUAL getClass()
序列是内部 null
检查的一部分,已在