user5549139
user5549139

Reputation: 187

How does invokedynamic instruction decide its description?

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

Answers (1)

Holger
Holger

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

Related Questions