I would like to use LambdaMetaFactory to efficiently access a private method.
public class Foo {
private void bar() { // here's what I want to invoke
System.out.println("bar!");
}
}
I know it is not a security violation, because the following code works:
Foo foo = new Foo();
Method m = Foo.class.getDeclaredMethod("bar");
m.setAccessible(true);
m.invoke(foo); // output: bar!
However, my attempts to use LambdaMetaFactory fail:
MethodHandles.Lookup lookup = MethodHandles.lookup();
Method m = Foo.class.getDeclaredMethod("bar");
m.setAccessible(true);
CallSite site = LambdaMetafactory.metafactory(lookup, "accept",
MethodType.methodType(Consumer.class),
MethodType.methodType(void.class, Object.class),
lookup.unreflect(m),
MethodType.methodType(void.class, Foo.class));
Consumer<Foo> func = (Consumer<Foo>) site.getTarget().invoke();
func.accept(foo); // IllegalAccessException: member is private
Clearly m.setAccessible(true)
is not enough here. I tried changing lookup
to MethodHandles.privateLookupIn(Foo.class, MethodHandles.lookup())
, which did solve it in my toy example ... but not in my actual application, where it generates an IllegalAccessException
saying my class "does not have full privilege access". I have been unable to discover why my application "does not have full privilege access", or how to fix it.
The only thing I've found to sort-of-almost work is this:
MethodHandles.Lookup original = MethodHandles.lookup();
Field internal = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
internal.setAccessible(true);
TRUSTED = (MethodHandles.Lookup) internal.get(original);
Which allows me to use TRUSTED
in place of lookup
, as long as I have --illegal-access=permit
in the VM options, which I can do. This produces a NoClassDefFoundError
instead (saying it can't find Foo
), which seems promising ... but I still can't figure out how to get it to work entirely, just produce this error instead of the other ones.
What is happening here, and how can I access bar
through LambdaMetaFactory
?
I suppose, you only tried your
setAccessible(true)
approach in the toy example, rather than the actual application. The differences between the rules of these operations are small in your case.Method.setAccessible(boolean)
MethodHandles.privateLookupIn(…)
privateLookupIn(…)
has stricter rules thansetAccessible(true)
, which is not surprising, as it has a bigger impact than enabling access to a specific single member. Since the member in question isprivate
, the points effectively applying to the operation do not differ. The access is granted if either, the accessor is in the same module or the member’s package has been opened to the caller’s module. (Since you’re accessing the classFoo
in the caller code directly, the read edge obviously does already exist.)In your toy example, the classes are likely in the same module or not using modules at all (getting placed in the unnamed module at runtime). In contrast, the application code is trying to access another module’s member. Since your approach accessing
IMPL_LOOKUP
does work by using the option--illegal-access=permit
, the caller code must be in the unnamed module whereas the target must be in a different module not opening the member’s package to the unnamed module.This implementation specific lookup object is special. It has the trusted flag allowing it to access everything, but its lookup class is
java.lang.Object
, so it can only see classes visible to the bootstrap loader. You have to change the lookup class usingin
; unlike ordinary lookup objects, this will not clear the permissions.But it’s important to emphasize that this hack only works with this specific implementation and is even expected to stop working in a future version. The only clean way to implement the access, is to set up the modules correctly, i.e. add an appropriate
opens
directive.