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)
This method may be used by a caller in class
C
to enable access to a member of declaring classD
if any of the following hold:
C
andD
are in the same module.- The member is
public
andD
ispublic
in a package that the module containingD
exports to at least the module containingC
.- The member is
protected static
,D
ispublic
in a package that the module containingD
exports to at least the module containingC
, andC
is a subclass ofD
.D
is in a package that the module containingD
opens to at least the module containingC
. All packages in unnamed and open modules are open to all modules and so this method always succeeds whenD
is in an unnamed or open module.
MethodHandles.privateLookupIn(…)
A caller, specified as a
Lookup
object, in moduleM1
is allowed to do deep reflection on moduleM2
and package of the target class if and only if all of the following conditions aretrue
:[...]
- If the caller module
M1
differs from the target moduleM2
then both of the following must be true:
M1
readsM2
.M2
opens the package containing the target class to at leastM1
.
privateLookupIn(…)
has stricter rules than setAccessible(true)
, which is not surprising, as it has a bigger impact than enabling access to a specific single member. Since the member in question is private
, 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 class Foo
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 using in
; unlike ordinary lookup objects, this will not clear the permissions.
Field internal = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
internal.setAccessible(true);
TRUSTED = (MethodHandles.Lookup) internal.get(null);
MethodHandles.Lookup lookup = TRUSTED.in(Foo.class);
MethodHandle mh = lookup.findSpecial(
Foo.class, "bar", MethodType.methodType(void.class), Foo.class);
// mh.invokeExact(foo); // could simply invoke it here
CallSite site = LambdaMetafactory.metafactory(lookup, "accept",
MethodType.methodType(Consumer.class),
mh.type().erase(), mh, mh.type());
Consumer<Foo> func = (Consumer<Foo>) site.getTarget().invoke();
func.accept(foo);
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.