Как инструкция invokedynami c определяет ее описание? - PullRequest
1 голос
/ 04 марта 2020

Я использую ASM для изменения ссылки на метод, так что я могу подключить его. Мой метод - изменить bootstrap s 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: я не могу понять следующие инструкции с инструкцией задней invokeydynami c .

    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    DUP
    INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
    POP

Q2: Почему описание invokedynami c имеет параметр PrintStream? На чем основаны правила.

INVOKEDYNAMIC accept(Ljava/io/PrintStream;)Ljava/util/function/Consumer;

Извините, я не могу точно описать вопросы. Возможно, мой последний вопрос - как работает инструкция invokedynamic в байт-коде.


Обновление
Я обнаружил важный момент, который изменяет ссылку на метод на нормальное лямбда-выражение. Когда вы генерируете среднее имя метода, вы должны установить доступ к этому методу ACC_SYNTHETI C, тогда вы можете получить желаемое.
Например,
Ссылка на метод:

MethodReferenceMain referenceMain = new MethodReferenceMain();
Comparator<User> comparator2 = referenceMain::compareByName;

Генерируемый класс:

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

Примечание : Должно быть больше проблем, с которыми я не встречался. Но это отличный шаг для меня.

1 Ответ

4 голосов
/ 04 марта 2020

Инструкция invokedynamic не требует подписи. Это генератор кода, который решает использовать конкретную сигнатуру и метод bootstrap, которые вместе определяют фактическое семантико c.

Например, сигнатура и ее значение полностью отличаются при использовании StringConcatFactory.makeConcat(…) вместо LambdaMetafactory.metafactory(…) в качестве метода bootstrap.

Документация по этим методам и содержащим их классам подробно описывает поведение. Но прежде чем копаться в деталях байт-кода, вы должны сначала понять особенности исходного кода, то есть захват ссылок на методы, как объяснено в Что такое эквивалентное лямбда-выражение для System.out :: println . Ссылка на метод System.out::println фиксирует PrintStream, найденный в поле c stati System.out при создании экземпляра Consumer. Следовательно, сгенерированный фабричный метод потребляет PrintStream и производит Consumer, что приводит к подписи (Ljava/io/PrintStream;)Ljava/util/function/Consumer;.

Таким образом, перед выполнением инструкции invokedynamic поле должно быть прочитано с GETSTATIC java/lang/System.out инструкция. Последовательность DUP; INVOKEVIRTUAL getClass() является частью встроенной проверки c null, которая обсуждалась в В Java Почему лямбда вызывается getClass () для захваченной переменной

...