Can lambda expressions access private methods of classes beyond their capabilities?

I want to get reflective access to the private package constructor java.lang.String.

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 Handle method for it is quite simple, and therefore calls it. The same is true for direct use of Reflection.

But I'm curious if the constructor can be called directly through the functional interfaces.

27602758 addresses a somewhat similar issue, but the solutions provided do not seem to work in this case.

The test case below compiles without problems. Everything works, except for the actual interface call.

 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(); } } 

In particular, the instruction

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

produces 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 thrown, despite the fact that access is supposedly granted?

Can someone explain why the generated interface does not have access to the method, which all its components have access to?

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

Because I'm at a standstill.

+6
source share
1 answer

The problem is that you are overriding the lookup object you want to trust, so its access to the private String method will go through the search procedure and the lambda meta factory, but it is still bound to your Test class as thats the class that created the search object via MethodHandles.lookup() , and the generated class will live in the same context. The JVM is quite generous in terms of accessibility when it comes to these generated classes, but apparently access to the private member of the bootstrap java.lang.String class from a class living in the context of your application class is not accepted.

You can get a search object living in the appropriate context via, for example, MethodHandles.lookup() .in(String.class) (and then fix it to have private or "trusted" access), but then you get one more problem: a class living in the context of java.lang.String (or only in the context of the boot loader) will not have access to your user interface StringCreator and cannot implement it.

The only solution is to use a search object that lives in the context of String , and implement one of the existing common interface s available from the bootloader:

 import java.lang.invoke.*; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Field; import java.util.function.BiFunction; public class Test { public static void main(String[] args) throws Throwable { // Reflectively generate a TRUSTED Lookup for the String class Lookup caller = MethodHandles.lookup().in(String.class); Field modes = Lookup.class.getDeclaredField("allowedModes"); modes.setAccessible(true); modes.setInt(caller, -1); // -1 == Lookup.TRUSTED // create handle for shared String constructor MethodHandle constructor = caller.findConstructor( String.class, MethodType.methodType(void.class, char[].class, boolean.class) ); // generate interface implementation for constructor BiFunction<char[],Boolean,String> shared=getStringCreator(caller, constructor); // test if the construcor is correctly accessed char[] chars = "foo".toCharArray(); String s = shared.apply(chars, true); chars[0] = 'b'; chars[1] = 'a'; chars[2] = 'r';// modify array contents System.out.println(s); // prints "bar" chars[0] = '1'; chars[1] = '2'; chars[2] = '3'; System.out.println(s); // prints "123" } private static BiFunction<char[],Boolean,String> getStringCreator( Lookup caller, MethodHandle handle) throws Throwable { CallSite callSite = LambdaMetafactory.metafactory( caller, "apply", MethodType.methodType(BiFunction.class), handle.type().generic(), handle, handle.type() ); return (BiFunction) callSite.getTarget().invokeExact(); } } 
+2
source

All Articles