从invokedynamic调用Java varargs方法

www*_*www 1 java jvm bytecode invokedynamic java-bytecode-asm

我想从 Java 动态调用本机方法。因为方法签名在编译时是未知的,所以我为大多数具有相同签名的原始返回类型创建了通用本机方法:

class NativeHook {
    
    public static native int callInt(String funcName, Object... funcArgs);
    public static native void callVoid(String funcName, Object... funcArgs);
    public static native Object callObject(String funcName, Object... funcArgs);

    private static MethodHandle getNativeMethod(String callName, Class<?> returnType) {
        return MethodHandles.lookup().findStatic(NativeHook.class, callName,
            MethodType.methodType(returnType, String.class, Object[].class));
    }
}
Run Code Online (Sandbox Code Playgroud)

我正在寻找创建一个 MethodHandle ,然后调用匹配的callXXX方法并传入装箱的方法,funcArgs就好像它们是单独提供的一样。这些callXXX方法可以这样访问:

MethodHandle callInt = getNativeMethod("callInt", int.class);
MethodHandle boundCallInt = callInt.bindTo("my_c_function_name").asVarargsCollector(Object[].class);

// returns NativeHook.callInt("my_c_function_name", 1, 2, 3)
boundCallInt.invokeWithArguments(1, 2, 3);
Run Code Online (Sandbox Code Playgroud)

我正在使用这个 bootstrap 方法callXXX在 invokeDynamic 中间接引用这个方法,它的工作方式与上面相同:

public static CallSite bootstrap(MethodHandles.Lookup caller, String name, MethodType type) {
    if (type.returnType() == int.class) {
        MethodHandle callInt = getNativeMethod("callInt", int.class);
        return new ConstantCallSite(callInt.bindTo(name).asVarargsCollector(Object[].class));
    }
}
Run Code Online (Sandbox Code Playgroud)

然后使用invokedynamic完成调用,如下所示:

mv.visitIntInsn(BIPUSH, 1);
mv.visitIntInsn(BIPUSH, 2);
mv.visitIntInsn(BIPUSH, 3);
mv.visitInvokeDynamicInsn("my_c_function_name", "(III)I", NativeHook.bootstrapHandle);
Run Code Online (Sandbox Code Playgroud)

但是,这不会按预期工作并引发异常:

Caused by: java.lang.invoke.WrongMethodTypeException: MethodHandle(Object[])int should be of type (int,int,int)int
    at java.lang.invoke.CallSite.wrongTargetType(CallSite.java:194)
    at java.lang.invoke.CallSite.makeSite(CallSite.java:335)
    ... 16 more
Run Code Online (Sandbox Code Playgroud)

如何构造一个合适的 MethodHandle ,它像常规方法一样接受参数,然后调用 varargcallXXX方法?

Hol*_*ger 6

In the package documentation we find the statement

The type of the call site's target must be exactly equal to the type derived from the invocation's type descriptor and passed to the bootstrap method.

So it is not enough to be compatible in terms of invoke, but it has to be compatible with invokeExact.

After applying .asVarargsCollector(Object[].class), it is possible to invoke the handle, but it’s not matching the exact signature. But we can adapt it via asType:

If the current method is a variable arity method handle argument list conversion may involve the conversion and collection of several arguments into an array, as described elsewhere.

This implies that the combination of asVarargsCollector and asType should work. But we can also consider the general relationship between invoke and invokeExact mentioned in the same method documentation:

This method provides the crucial behavioral difference between invokeExact and plain, inexact invoke. The two methods perform the same steps when the caller's type descriptor exactly matches the callee's, but when the types differ, plain invoke also calls asType (or some internal equivalent) in order to match up the caller's and callee's types.

In other words, if invoke works successfully, the asType conversion also must be possible to meet the requirements for invokeExact.

Which we can demonstrate:

MethodHandles.Lookup l = MethodHandles.lookup();
MethodHandle h = l.bind(System.out, "printf",
    MethodType.methodType(PrintStream.class, String.class, Object[].class));

h = h.bindTo("%s %s %s%n").asVarargsCollector(Object[].class);

try {
    System.out.println("invoke(1, 2, 3): ");
    h.invoke(1, 2, 3);
} catch(Throwable t) {
    System.out.println(t);
}
try {
    System.out.println("\ninvokeExact(1, 2, 3): ");
    h.invokeExact(1, 2, 3);
} catch(Throwable t) {
    System.out.println(t);
}

MethodType type = MethodType.methodType(void.class, int.class, int.class, int.class);

try {
    System.out.println("\n.asType(type).invokeExact(1, 2, 3): ");
    h.asType(type).invokeExact(1, 2, 3);
} catch(Throwable t) {
    System.out.println(t);
}
Run Code Online (Sandbox Code Playgroud)
invoke(1, 2, 3): 
1 2 3

invokeExact(1, 2, 3): 
java.lang.invoke.WrongMethodTypeException: expected (Object[])PrintStream but found (int,int,int)void

.asType(type).invokeExact(1, 2, 3): 
1 2 3
Run Code Online (Sandbox Code Playgroud)

The bootstrap method does receive the required MethodType as third argument already, so all it needs to do, it to apply .asType(type) using that type.