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.
The problem is that you override the lookup object to be trusted, so its access to a
private
method ofString
will pass the lookup procedure and lambda meta factory, but its still bound to yourTest
class as that’s the class which created the lookup object viaMethodHandles.lookup()
and the generated class will live in the same context. The JVM is quite generous regarding accessibility when it comes to these generated classes but apparently, accessing aprivate
member of the bootstrap classjava.lang.String
from a class living in the context of your application class is not accepted.You can get a lookup object living in an appropriate context via, e.g.
MethodHandles.lookup() .in(String.class)
(and then patching it to haveprivate
or “trusted” access), but then, you will get another problem: a class living in the context ofjava.lang.String
(or just in the bootstrap loader’s context) will not have access to your custominterface StringCreator
and can’t implement it.The only solution is to use a lookup object living in the context of
String
and implementing one of the existing genericinterface
s, accessible from the bootstrap class loader: