benrush
benrush

Reputation: 321

MethodHandle cannot be cracked when using LambdaMetafactory?

I want to convert a Record's constructor to a Function<Object[], T> using lambdametafactory (T is a generic type), here are my codes:

public record R(
        String a,
        String b
) {
}

private static void testRecord() throws Throwable {
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle constructor = lookup.findConstructor(R.class, MethodType.methodType(void.class, String.class, String.class));
    constructor = constructor.asSpreader(Object[].class, 2);
    R r = (R) constructor.invokeExact(new Object[]{"a", "b"});
    System.out.println(r.a());
    System.out.println(r.b());
    MethodType methodType = constructor.type();
    CallSite callSite = LambdaMetafactory.metafactory(lookup,
            "apply",
            MethodType.methodType(Function.class),
            methodType.erase(),
            constructor,
            methodType);
    Function<Object[], R> f = (Function<Object[], R>) callSite.getTarget().invokeExact();
    R apply = f.apply(new Object[]{"a", "b"});
    System.out.println(apply.a());
    System.out.println(apply.b());
} 

When using constructor.invokeExact() method, the record could be instantiated successfully, but the CallSite couldn't be generated by LambdaMetafactory.metafactory() method because of following errors:

Exception in thread "main" java.lang.invoke.LambdaConversionException: MethodHandle(Object[])R is not direct or cannot be cracked
    at java.base/java.lang.invoke.AbstractValidatingLambdaMetafactory.<init>(AbstractValidatingLambdaMetafactory.java:143)
    at java.base/java.lang.invoke.InnerClassLambdaMetafactory.<init>(InnerClassLambdaMetafactory.java:168)
    at java.base/java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:336)

How can I fix this?

Upvotes: 1

Views: 401

Answers (2)

Holger
Holger

Reputation: 298143

There is a trick to circumvent this restriction. Instead of creating a Function bound to the constructor handle, create a Function bound to the invokeExact method of the handle, to be invoked on the specific handle:

private static void testRecord() throws Throwable {
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle constructor = lookup.findConstructor(R.class,
        MethodType.methodType(void.class, String.class, String.class));
    constructor = constructor.asSpreader(Object[].class, 2);
    R r = (R)constructor.invokeExact(new Object[] { "a", "b" });
    System.out.println(r.a());
    System.out.println(r.b());
    MethodType methodType = constructor.type();
    CallSite callSite = LambdaMetafactory.metafactory(lookup,
        "apply",
        MethodType.methodType(Function.class, MethodHandle.class),
        methodType.erase(),
        MethodHandles.exactInvoker(methodType), // target MethodHandle.invokeExact
        methodType);
    Function<Object[], R> f = (Function<Object[], R>)
        callSite.getTarget().invokeExact(constructor); // bind to <constructor>
    R apply = f.apply(new Object[] { "a", "b" });
    System.out.println(apply.a());
    System.out.println(apply.b());
}

The crucial part is to specify MethodHandles.exactInvoker(methodType) which creates an unbound handle to the invokeExact method of a handle like ours, i.e. having the type (MethodHandle,Object[])R. Then, specify the actual handle as argument to the instantiation, to be captured by the Function.

Upvotes: 7

Jorn Vernee
Jorn Vernee

Reputation: 33865

Direct method handles are those that directly refer to Java methods, however, your method handle does not, because you use an adapter:

constructor = constructor.asSpreader(Object[].class, 2);

As such, it can not be used in combination with LambdaMetafactory.

LMF is a runtime API that serves as the implementation for the lambda and method reference language features. It is not really meant to be used directly, so there are some sharp edges as you've found out.


For indirect method handles, your options are either MethodHandleProxies:

MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle constructor = lookup.findConstructor(R.class, MethodType.methodType(void.class, String.class, String.class));
constructor = constructor.asSpreader(Object[].class, 2);
Function<Object[], R> f = (Function<Object[], R>) MethodHandleProxies.asInterfaceInstance(Function.class, constructor);

Using a lambda expression to wrap the method handle:

MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle constructor = lookup.findConstructor(R.class, MethodType.methodType(void.class, String.class, String.class));
constructor = constructor.asSpreader(Object[].class, 2);
Function<Object[], R> f = args -> {
    try {
        return (R) constructor.invokeExact(args);
    catch (Throwable t) {
        throw new IllegalStateException("Should no happen", t);
    }
};

Or you could spin your own class that implements the interface and wraps the method handle using a bytecode library like ASM.

Upvotes: 6

Related Questions