Java 如何使用ASM生成模拟invokevirtual的InvokedDynamic调用

Java 如何使用ASM生成模拟invokevirtual的InvokedDynamic调用,java,bytecode,java-bytecode-asm,invokedynamic,Java,Bytecode,Java Bytecode Asm,Invokedynamic,我想看看如何使用与invokevirtual相同的调度逻辑进行invokedynamic调用 我问这个问题是因为目前在线使用ASM生成动态方法调用的示例太琐碎,无法推广,我认为这个案例对于任何想要实现自己的调度逻辑的人来说都是一个很好的起点 显然,我知道用invokedynamic调用替换invokevirtual调用在实践中是毫无意义的 为了明确起见,我想替换以下内容: methodVisitor.visitMethodInsn( Opcodes.INVOKEVIRTUAL,

我想看看如何使用与
invokevirtual
相同的调度逻辑进行
invokedynamic
调用

我问这个问题是因为目前在线使用ASM生成动态方法调用的示例太琐碎,无法推广,我认为这个案例对于任何想要实现自己的调度逻辑的人来说都是一个很好的起点

显然,我知道用
invokedynamic
调用替换
invokevirtual
调用在实践中是毫无意义的

为了明确起见,我想替换以下内容:

methodVisitor.visitMethodInsn(
    Opcodes.INVOKEVIRTUAL,
    myClassName,
    methodName,
    descriptor,
    false);
为此:

MethodType methodType =
    MethodType.methodType(
        CallSite.class,
        MethodHandles.Lookup.class,
        String.class,
        MethodType.class);

Handle handle =
    new Handle(
        Opcodes.H_INVOKESTATIC,
        "bytecode/generating/Class",
        "bootstrap",
        methodType.toMethodDescriptorString(),
        false);

methodVisitor.visitInvokeDynamicInsn(
    methodName,
    descriptor,
    handle);
//自举法

public static CallSite bootstrap(
    MethodHandles.Lookup caller,
    String name,
    MethodType type)
{
    // Dispatch logic here.
}

在这种情况下没什么可做的。唯一需要注意的是,
invokevirtual
有一个隐含的第一个参数,即接收方,您必须将其作为显式的第一个参数插入
invokedynamic
指令的描述符中:

public class ConvertToInvokeDynamic extends MethodVisitor {
    public static byte[] convertInvokeVirtual(
        InputStream in, String linkerClass, String linkerMethod) throws IOException {
        ClassReader cr = new ClassReader(in);
        ClassWriter cw = new ClassWriter(cr, 0);
        cr.accept(new ClassVisitor(Opcodes.ASM5, cw) {
            @Override
            public MethodVisitor visitMethod(int access, String name, String desc,
                                             String signature, String[] exceptions) {
                return new ConvertToInvokeDynamic(
                    super.visitMethod(access, name, desc, signature, exceptions),
                    linkerClass, linkerMethod);
            }
        }, 0);
        return cw.toByteArray();
    }
    private final Handle bsm;

    public ConvertToInvokeDynamic(
        MethodVisitor target, String linkerClass, String linkerMethod) {
        super(Opcodes.ASM5, target);
        bsm = new Handle(Opcodes.H_INVOKESTATIC, linkerClass, linkerMethod,
          "(Ljava/lang/invoke/MethodHandles$Lookup;"
         + "Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;");
    }

    @Override
    public void visitMethodInsn(
        int opcode, String owner, String name, String desc, boolean itf) {
        if(opcode == Opcodes.INVOKEVIRTUAL) {
            desc = '('+(owner.charAt(0)!='['? 'L'+owner+';': owner)+desc.substring(1);
            super.visitInvokeDynamicInsn(name, desc, bsm);
        }
        else super.visitMethodInsn(opcode, owner, name, desc, itf);
    }
}
只要这是唯一的更改,堆栈状态将保持与原始代码中相同的状态,因此,我们不需要重新计算堆栈帧或最大变量/操作数堆栈大小

代码假定原始类的版本足够高,可以支持
invokedynamic
指令。否则,转换将变得非常重要,因为我们不仅需要计算堆栈映射,还可能在旧类文件中遇到现在禁止的
jsr
ret
指令

提供一个引导方法来重新建立原始的
invokevirtual
行为,也是很简单的。现在,最大的(不是很大的)障碍是我们现在必须提取第一个显式参数类型并将其转换回接收方类型:

public class LinkLikeInvokeVirtual {
    public static CallSite bootstrap(MethodHandles.Lookup l, String name, MethodType type){
        Class<?> receiver = type.parameterType(0);
        type = type.dropParameterTypes(0, 1);
        System.out.println("linking to "+name+type+" in "+receiver);
        MethodHandle target;
        try {
            target = l.findVirtual(receiver, name, type);
        } catch(NoSuchMethodException|IllegalAccessException ex) {
            throw new BootstrapMethodError(ex);
        }
        return new ConstantCallSite(target);
    }
}
在第一次执行时(由于链接的调用站点保持链接,因此我们在下一次调用时不会看到引导方法的输出)

StringBuilder
方法是Java 9之前编译的字符串连接的产物,因此从Java 9开始,它将只打印

链接到类java.lang.Runtime中的freemory()long
链接到类java.io.PrintStream中的println(String)void
131449472字节可用
(当然,数字会有所不同)

如果您想根据实际的接收者执行替代的动态调度,您可以用如下内容替换
LinkLikeInvokeVirtual

public class LinkWithDynamicDispatch {
    static final MethodHandle DISPATCHER;
    static {
        try {
            DISPATCHER = MethodHandles.lookup().findStatic(LinkWithDynamicDispatch.class, "simpleDispatcher",
                MethodType.methodType(MethodHandle.class, MethodHandle.class, String.class, Object.class));
        } catch(NoSuchMethodException|IllegalAccessException ex) {
            throw new ExceptionInInitializerError(ex);
        }
    }
    public static CallSite bootstrap(MethodHandles.Lookup l, String name, MethodType type){
        MethodHandle target;
        try {
            target = l.findVirtual(type.parameterType(0), name, type.dropParameterTypes(0, 1));
        } catch(NoSuchMethodException|IllegalAccessException ex) {
            throw new BootstrapMethodError(ex);
        }
        MethodHandle d = MethodHandles.insertArguments(DISPATCHER, 0, target, name);
        target = MethodHandles.foldArguments(MethodHandles.exactInvoker(type),
            d.asType(d.type().changeParameterType(0, type.parameterType(0))));
        return new ConstantCallSite(target);
    }
    public static MethodHandle simpleDispatcher(
            MethodHandle invokeVirtualTarget, String methodName, Object rec) {
        System.out.println("simpleDispatcher(): invoke "+methodName+" on "
            + "declared receiver type "+invokeVirtualTarget.type().parameterType(0)+", "
            + "actual receiver "+(rec==null? "null": "("+rec.getClass().getName()+"): "+rec));
        return invokeVirtualTarget;
    }
}

这将基于静态类型执行类似于
invokevirtual
的查找,然后链接到
simpleDispatcher
方法,该方法将接收解析目标之外的实际接收器实例。然后,它可能只返回目标句柄,或者根据实际的接收者返回不同的句柄。

这太抽象和假设了。提供一些具体的例子,说明您正试图完成的任务,以及如果您希望避免问题被搁置,您将遇到什么问题。非常感谢您只需回答一个后续问题:是否可以在运行时调度接收方的具体类型?每次调用时接收方可能会有所不同,因此引导方法不能预先知道它,因为它必须链接到适合所有后续调用的目标。但它可以链接到将根据参数执行实际调度的代码,而不是直接链接到最终目标。但您必须提供/实现该调度器。也许会有点帮助。谢谢,这确实有帮助。有关于API如何公开参数的文档吗?您链接到的方法将接收它们作为参数值。如果要创建一个可以处理各种不同参数的dispatcher方法,可以使用其中一个来调整句柄,将参数放在某种容器中(例如,类似于varargs的数组),使用泛型参数调用dispatcher方法。或者对其进行调整,以仅获取实际接收器作为参数,如果这是调度器唯一需要的。考虑<代码>文件夹(ActhTrpCukes(type),…)< /代码>…
public class LinkWithDynamicDispatch {
    static final MethodHandle DISPATCHER;
    static {
        try {
            DISPATCHER = MethodHandles.lookup().findStatic(LinkWithDynamicDispatch.class, "simpleDispatcher",
                MethodType.methodType(MethodHandle.class, MethodHandle.class, String.class, Object.class));
        } catch(NoSuchMethodException|IllegalAccessException ex) {
            throw new ExceptionInInitializerError(ex);
        }
    }
    public static CallSite bootstrap(MethodHandles.Lookup l, String name, MethodType type){
        MethodHandle target;
        try {
            target = l.findVirtual(type.parameterType(0), name, type.dropParameterTypes(0, 1));
        } catch(NoSuchMethodException|IllegalAccessException ex) {
            throw new BootstrapMethodError(ex);
        }
        MethodHandle d = MethodHandles.insertArguments(DISPATCHER, 0, target, name);
        target = MethodHandles.foldArguments(MethodHandles.exactInvoker(type),
            d.asType(d.type().changeParameterType(0, type.parameterType(0))));
        return new ConstantCallSite(target);
    }
    public static MethodHandle simpleDispatcher(
            MethodHandle invokeVirtualTarget, String methodName, Object rec) {
        System.out.println("simpleDispatcher(): invoke "+methodName+" on "
            + "declared receiver type "+invokeVirtualTarget.type().parameterType(0)+", "
            + "actual receiver "+(rec==null? "null": "("+rec.getClass().getName()+"): "+rec));
        return invokeVirtualTarget;
    }
}