5

I have this code which works fine:

Method getterMethod = Person.class.getDeclaredMethod("getName");

MethodHandles.Lookup lookup = MethodHandles.publicLookup();
Class<?> declaringClass = getterMethod.getDeclaringClass();
Class<?> returnType = getterMethod.getReturnType();
CallSite getterSite = LambdaMetafactory.metafactory(lookup,
    "apply",
    MethodType.methodType(Function.class),
    MethodType.methodType(Object.class, Object.class),
    lookup.findVirtual(declaringClass, getterMethod.getName(), MethodType.methodType(returnType)),
    MethodType.methodType(propertyType, declaringClass));

Function getterFunction = (Function) getterSite.getTarget().invokeExact();

But if the getterMethod is a method from a class loaded from a different ClassLoader, it throws:

Caused by: java.lang.invoke.LambdaConversionException: Invalid caller: java.lang.Object
    at java.lang.invoke.AbstractValidatingLambdaMetafactory.<init>(AbstractValidatingLambdaMetafactory.java:118)
    at java.lang.invoke.InnerClassLambdaMetafactory.<init>(InnerClassLambdaMetafactory.java:155)
    at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:299)

How do I pass my ClassLoader instance to the LambdaMetafactory?

Geoffrey De Smet
  • 26,223
  • 11
  • 73
  • 120
  • I cannot understand your code. There is a double definition of the variable declaringClass (line 1 and 4) and no definition for getterMethod. Could you please correct it? – Pietro Braione Jan 25 '18 at 11:26
  • Agreed, fixed now. – Geoffrey De Smet Jan 25 '18 at 11:35
  • Interestingly enough, if I switch to the non-public `MethodHandles.lookup()`, I get java.lang.LinkageError: loader constraint violation: when resolving method "...ClassloadedTestdataEntity.getValue()L.../ClassloadedTestdataValue;" the class loader (instance of .../Launcher$AppClassLoader) of the current class, .../LamdbaBeanPropertyMemberAccessor, and the class loader (instance of .../ProjectClassLoader$DefaultInternalTypesClassLoader) for the method's defining class, .../ClassloadedTestdataEntity, have different Class objects for the type .../ClassloadedTestdataValue used in the signature` – Geoffrey De Smet Jan 25 '18 at 11:40
  • 2
    As shown in the docs of MethodHandles.publicLookup(): "As a matter of pure convention, the lookup class of this lookup object will be java.lang.Object. Discussion: The lookup class can be changed to any other class C using an expression of the form publicLookup().in(C.class). Since all classes have equal access to public names, such a change would confer no new access rights." – Fran Montero Jan 25 '18 at 11:43
  • That explains the message. I tried `MethodHandles.publicLookup().in(declaringClass)` but because the original lookup is created in the old classloader, this doesn't give any more rights on the declaringClass which is another classloader. So it also fails with an `Invalid caller` exception. – Geoffrey De Smet Jan 25 '18 at 18:49

2 Answers2

6

As said by this answer, the lookup modes must include the private access to be accepted by the LambdaMetaFactory. Basically, this implies that either, the caller is the specified class, as it created the particular lookup instance, or the lookup class has enough trust to pass the lookup object to the code which did the actual call (which is e.g. implied when using invokedynamic pointing to a particular bootstrap method).

Starting with Java 9, there is the method privateLookupIn(Class, MethodHandles.Lookup) to attempt to get a lookup object with private access to another class. As the documentation specifies, the access is checked against the module access rules, i.e. the caller must be “allowed to do deep reflection on the target class”. So the presence of the trust mentioned above, still is required, now in terms of module accessibility. I suppose, that’s the way to go for frameworks, where the code managed by the framework will be opened to the framework to support such access.

If that’s not feasible, this answer contains an alternative, in case you are the creator of the class loader. It uses the class loader’s API to inject a new class which creates the lookup object and allows the creator to access it. There are variations imaginable, including making it secure by letting the synthetic class call back into the creator’s code to hand over the lookup object, instead of storing it in a field where everyone could read it.

Holger
  • 285,553
  • 42
  • 434
  • 765
3

LambdaMetafactory's methods require you to pass PRIVATE lookup as their first parameter. Specification caller - Represents a lookup context with the accessibility privileges of the caller. might be a bit obscure, but it supposed to give the idea about the required privileges of the Lookup.

Why does it have to be private? That's sort of future proofing the API. Remember that LambdaMetafactory is supposed to be invoked via invokedynamic instructions generated by compiler for lambdas. In that case VM will always pass the caller's MethodHandles.lookup() to the method, so the Lookup will always be PRIVATE. Using LambdaMetafactory directly in Java code is perfectly fine if you need it for your use case, but it isn't the primary usage, so the API is restricted to give just enough for the invokedynamic case but nothing more (other words - if invokedynamic works with PRIVATE then let's prohibit everything else just in case).

Stanislav Lukyanov
  • 2,147
  • 10
  • 20
  • There's an API gap in the current API because it doesn't support doing a lookup of *public* methods of public classes in another, child ClassLoader. For example a JPA implementation in a JEE server can't do a lookup of a public getter in a class that is loaded by the war classloader. – Geoffrey De Smet Feb 06 '18 at 15:53
  • 2
    `Lookup` that you pass as the first parameter to the `metafactory(...)` is not really to *lookup* the implementation method. You already did the method lookup, created the needed method handle and passed it as another parameter. AFAIR `metafactory(...)` only uses the first parameter to figure out where it should define a class for the lambda (and `Lookup` is used to check that you have rights to define that class). Doesn't your code work if you pass `MethodHandles.lookup()` as the first parameter? You can still use the `publicLookup()` to create the method handle, that's fine. – Stanislav Lukyanov Feb 07 '18 at 06:02