Reputation: 187
I'm using ASM to modify method reference, so what i can hook it. My method is modify the bootstrap's Handle
arg, and make it target to a new method which i will generate it later. The follow is my code snippet for this goal.
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);
}
//...
}
And my test java source code is as follows:
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();
}
}
And, the generated class file is as follows:
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) {
}
}
You can see that, the acceptsa1
method hasn't code body, because i don't know how to analyze the corresponding bytecode.
Follow is the source code's bytecode snippet, and my question is inside.
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: I can't understand the follow instructions with the rear invokeydynamic instruction.
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
DUP
INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
POP
Q2: Why the invokedynamic's description has a PrintStream parameter? What's rules it based.
INVOKEDYNAMIC accept(Ljava/io/PrintStream;)Ljava/util/function/Consumer;
I am sorry i can't describe the questions exactly. Maybe my final question is how the invokedynamic
instruction works in bytecode.
Update
I find an important point that change a method reference to a normal lambda expression. When you generate the middle method name, you must set the access of this method to ACC_SYNTHETIC, then you can get your wanted.
e.g.,
Method Reference:
MethodReferenceMain referenceMain = new MethodReferenceMain();
Comparator<User> comparator2 = referenceMain::compareByName;
Generated Class:
MethodReferenceMain referenceMain = new MethodReferenceMain();
Comparator<User> comparator2 = (var1, var2) -> {
return referenceMain.compareByName(var1, var2);
};
Note: There must have more problem that i have not meet. But that's a great step for me.
Upvotes: 1
Views: 559
Reputation: 298409
The invokedynamic
instruction does not mandate any signature. It’s the code generator which decides for a particular signature and bootstrap method to use, which together define the actual semantic.
E.g., the signature and its meaning is entirely different when using StringConcatFactory.makeConcat(…)
instead of LambdaMetafactory.metafactory(…)
as bootstrap method.
The documentation of these methods and their containing classes describes the behavior thoroughly. But before digging into the bytecode details you should first understand the source code features, i.e. of capturing method references, as explained in What is the equivalent lambda expression for System.out::println. The method reference System.out::println
captures the PrintStream
found in the static field System.out
when instantiating the Consumer
. Hence, the generated factory method consumes a PrintStream
and produces a Consumer
, resulting in the signature (Ljava/io/PrintStream;)Ljava/util/function/Consumer;
.
So, before executing the invokedynamic
instruction, the field has to be read with a GETSTATIC java/lang/System.out
instruction. The DUP; INVOKEVIRTUAL getClass()
sequence is part of an intrinsic null
check which has been discussed in In Java Lambda's why is getClass() called on a captured variable
Upvotes: 4