mercredi 23 mars 2016

Can Lambda expressions access private methods of classes outside their scope?

I want to gain reflective access to java.lang.String's package private constructor.

Namely, this one:

/*
* Package private constructor which shares value array for speed.
* this constructor is always expected to be called with share==true.
* a separate constructor is needed because we already have a public
* String(char[]) constructor that makes a copy of the given char[].
*/
String(char[] value, boolean share) {
    // assert share : "unshared not supported";
    this.value = value;
}

Creating a MethodHandle for it is simple enough, and so is invoking it. The same is true for using Reflection directly.

But I'm curious whether it's possible to directly call the constructor via functional interfaces.

27602758 touches on a somewhat similar issue, but the solutions provided do not appear to work in this case.

The test case below compiles without issues. Everything works, except for the actual interface invocation.

package test;

import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;

public class Test {

    // Creates a new String that shares the supplied char[]
    private static interface StringCreator {

        public String create(char[] value, boolean shared);
    }

    // Creates a new conventional String
    private static String create(char[] value, boolean shared) {
        return String.valueOf(value);
    }

    public static void main(String[] args) throws Throwable {
        // Reflectively generate a TRUSTED Lookup for the calling class
        Lookup caller = MethodHandles.lookup();
        Field modes = Lookup.class.getDeclaredField("allowedModes");
        modes.setAccessible(true);
        modes.setInt(caller, -1);   // -1 == Lookup.TRUSTED

        // create handle for #create()
        MethodHandle conventional = caller.findStatic(
            Test.class, "create", MethodType.methodType(String.class, char[].class, boolean.class)
        );
        StringCreator normal = getStringCreator(caller, conventional);
        System.out.println(
            normal.create("foo".toCharArray(), true)
        // prints "foo"
        );

        // create handle for shared String constructor
        MethodHandle constructor = caller.findConstructor(
            String.class, MethodType.methodType(void.class, char[].class, boolean.class)
        );
        // test directly if the construcor is correctly accessed
        char[] chars = "foo".toCharArray();
        String s = (String) constructor.invokeExact(chars, true);
        chars[0] = 'b'; // modify array contents
        chars[1] = 'a';
        chars[2] = 'r';
        System.out.println(
            s
        // prints "bar"
        );

        // generate interface for constructor
        StringCreator shared = getStringCreator(caller, constructor);
        System.out.println(
            shared.create("foo".toCharArray(), true)
        // throws error
        );
    }

    // returns a StringCreator instance
    private static StringCreator getStringCreator(Lookup caller, MethodHandle handle) throws Throwable {
        CallSite callSite = LambdaMetafactory.metafactory(
            caller,
            "create",
            MethodType.methodType(StringCreator.class),
            handle.type(),
            handle,
            handle.type()
        );
        return (StringCreator) callSite.getTarget().invokeExact();
    }
}

Specficially the instruction

shared.create("foo".toCharArray(), true)

throws the following error:

Exception in thread "main" java.lang.IllegalAccessError: tried to access method java.lang.String.<init>([CZ)V from class test.Test$$Lambda$2/989110044 at test.Test.main(Test.java:59)

Why is this error still being thrown, despite access ostensibly being granted?

Can anyone come up with an explanation for why the generated interface has no access to a method that all of its components have access to?

Is there a solution or a viable alternative that actually works for this particular use case, without reverting to pure Reflection or MethodHandles?

Because I'm stumped.





Aucun commentaire:

Enregistrer un commentaire