groovy 如何使用ObjectWeb2 ASM引导接口方法引用

xdyibdwo  于 2022-11-01  发布在  其他
关注(0)|答案(1)|浏览(139)

我正在尝试修复Groovy中接口方法引用的元工厂调用:https://issues.apache.org/jira/browse/GROOVY-9853
给定小Java程序

public class J {
  public static void main(String[] args) {
    java.util.function.ToIntFunction<CharSequence> f = CharSequence::length;
    f.applyAsInt("");
  }
}

编译器编写以下引导方法:

Bootstrap methods:
  0 : # 58 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;
    Method arguments:
        #59 (Ljava/lang/Object;)I
        #66 null
        #68 (Ljava/lang/CharSequence;)I

给定一个非常类似的Groovy程序

class G {
  @groovy.transform.CompileStatic
  static main(args) {
    java.util.function.ToIntFunction<CharSequence> f = CharSequence::length
    f.applyAsInt("")
  }
}

Groovy编译器编写以下引导方法:

Bootstrap methods:
  0 : # 47 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;
    Method arguments:
        #31 (Ljava/lang/Object;)I
        #38 java/lang/CharSequence.length:()I
        #40 (Ljava/lang/CharSequence;)I

第二个常量池条目对于java是“null”,对于groovy是“java/lang/CharSequence.length:()I”。这是导致链接问题中提到的ClassFormatError的常量池条目。我正在尝试更改引导方法的输出,这是使用ASM完成的,如下所示:

methodVisitor.visitInvokeDynamicInsn(
                "applyAsInt",
                "()Ljava/util/function/ToIntFunction;",
                new Handle(
                    Opcodes.H_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;",
                    isInterface // false because enclosing class "G" is not an interface
                ),
                createBootstrapMethodArguments(
                        "()Ljava/util/function/ToIntFunction;",
                        Opcodes.H_INVOKEVIRTUAL,
                        typeOrTargetRefType, // CharSequence
                        methodRefMethod, // CharSequence#length
                        false
                ) // 3 arguments: [Type("(Ljava/lang/Object;)I"), Handle("java/lang/CharSequence.length()I (5 itf)"), Type("(Ljava/lang/CharSequence;)I")]
        );

    Object[] createBootstrapMethodArguments(String abstractMethodDesc, int insn, ClassNode methodOwnerClassNode, MethodNode methodNode, boolean serializable) {
        List<Object> arguments = new ArrayList<>(5);
        arguments.add(Type.getType(abstractMethodDesc));

        arguments.add(new Handle(
                insn,
                BytecodeHelper.getClassInternalName(methodOwnerClassNode.getName()),
                methodNode.getName(),
                BytecodeHelper.getMethodDescriptor(methodNode),
                methodOwnerClassNode.isInterface()));

        arguments.add(Type.getType(BytecodeHelper.getMethodDescriptor(methodNode.getReturnType(),
                (Parameter[]) methodNode.getNodeMetaData(ORIGINAL_PARAMETERS_WITH_EXACT_TYPE))));

        if (serializable) {
            arguments.add(5);
            arguments.add(0);
        }

        return arguments.toArray();
    }

是不是引导方法的参数需要不同?我不能很好地将java在常量表中的“null”与发送到ASM的类型和句柄联系起来。
下面是javap的输出:
一个

bpsygsoo

bpsygsoo1#

您是对的,这是Groovy编译器中的一个bug,是由于错误使用tagisInterface参数引起的。
让我们从isInterface参数开始:
当且仅当拥有者类别(也是Handle建构函式的参数)是界面时,这个参数才为true。
让我们看一下Groovy的一些代码:

default Handle createBootstrapMethod(boolean isInterface, boolean serializable) {
    if (serializable) {
        return new Handle(
                Opcodes.H_INVOKESTATIC,
                "java/lang/invoke/LambdaMetafactory",
                "altMetafactory",
                "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;",
                isInterface
        );
    }

    return new Handle(
            Opcodes.H_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;",
            isInterface
    );
}

(从此处调用)
java.lang.invoke.LambdaMetafactory不是接口,也永远不会是。
所以传递给Handle构造函数的正确的东西是一个常量false
接下来是目标方法本身的句柄:

createBootstrapMethodArguments(createMethodDescriptor(abstractMethod), H_INVOKEVIRTUAL, lambdaClass, lambdaMethod, expression.isSerializable())

您要创建的句柄是用于实现方法的-通常是同一个类中的私有方法。同样,如果所有者类是一个接口,则isInterfacetrue
您必须使用H_*常量,该常量的名称必须与用于调用该方法的指令的名称相似。
从设计的Angular 来看,可能有一个helper方法将任何MethodNode转换为Handle

相关问题