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

我之所以要问这个问题,是因为当前在线使用ASM生成动态方法调用的示例太琐碎而不能一概而论,并且我认为对于希望实现自己的分派逻辑的任何人来说,这种情况都是一个很好的起点。

显然,我知道仅用invokevirtual替换invokedynamic调用在实践中将是没有意义的事情。

为了清楚起见,我想替换为:

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指令。否则,转换将变得不平凡,因为我们不仅可能需要计算堆栈映射,而且还可能在较早的类文件中遇到现在禁止的jsrret指令。

提供引导方法来重新建立原始的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);
    }
}


现在,我们可以在一个简单的测试用例中结合这两个类:

public class Test {
    public static void main(String[] args) throws IOException,ReflectiveOperationException{
        byte[] code;
        try(InputStream is = Test.class.getResourceAsStream("Test.class")) {
            code = ConvertToInvokeDynamic.convertInvokeVirtual(is,
                LinkLikeInvokeVirtual.class.getName(), "bootstrap");
        }
        Class<?> transformed = new ClassLoader() {
            Class<?> get() {return defineClass("Test", code, 0, code.length); }
        }.get();
        transformed.getMethod("example").invoke(null);
    }

    public static void example() {
        System.out.println(Runtime.getRuntime().freeMemory()+" bytes free");
    }
}


其转换后的example()产生

linking to freeMemory()long in class java.lang.Runtime
linking to append(long)StringBuilder in class java.lang.StringBuilder
linking to append(String)StringBuilder in class java.lang.StringBuilder
linking to toString()String in class java.lang.StringBuilder
linking to println(String)void in class java.io.PrintStream
131449472 bytes free


在第一次执行时(由于链接的呼叫站点保持链接状态,因此在下一次调用时我们不会看到bootstrap方法的输出)。

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

linking to freeMemory()long in class java.lang.Runtime
linking to println(String)void in class java.io.PrintStream
131449472 bytes free


(当然,数字会有所不同)

如果要基于实际接收者执行替代动态调度,则可以将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方法,该方法将另外向解析的目标接收实际的接收者实例。然后,它可能会根据实际的接收者只返回目标句柄或其他句柄。

07-24 19:06