invokedynamic指令如何决定其描述?

问题描述 投票:1回答:1

我正在使用ASM来修改方法引用,所以我可以将其挂钩。我的方法是修改引导程序的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();
    }
}

并且,生成的类文件如下:

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:我无法理解后面的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;

生成类:

MethodReferenceMain referenceMain = new MethodReferenceMain();
Comparator<User> comparator2 = (var1, var2) -> {
     return referenceMain.compareByName(var1, var2);
};

:肯定还有其他我没有遇到的问题。但这对我来说是重要的一步。

jvm bytecode
1个回答
4
投票

invokedynamic指令不要求任何签名。这是代码生成器,用于决定要使用的特定签名和引导程序方法,它们共同定义了实际的语义。

例如,使用StringConcatFactory.makeConcat(…)而不是StringConcatFactory.makeConcat(…)作为引导方法时,签名及其含义完全不同。

这些方法及其包含的类的文档全面描述了该行为。但是,在深入研究字节码细节之前,您应该首先了解源代码功能,即捕获方法引用,如LambdaMetafactory.metafactory(…)中所述。实例化LambdaMetafactory.metafactory(…)时,方法参考What is the equivalent lambda expression for System.out::println捕获在静态字段System.out::println中找到的PrintStream。因此,生成的工厂方法将消耗System.out并生成Consumer,从而导致签名PrintStream

因此,在执行Consumer指令之前,必须使用(Ljava/io/PrintStream;)Ljava/util/function/Consumer;指令读取该字段。 invokedynamic序列是固有GETSTATIC java/lang/System.out检查的一部分,已在DUP; INVOKEVIRTUAL getClass()中进行了讨论>

© www.soinside.com 2019 - 2024. All rights reserved.