[JDK-8209078] Unable to call default interface methods from named module

Johannes Kuhn info at j-kuhn.de
Sun Sep 16 16:27:03 UTC 2018


Hi,

thanks for your quick reply.

On 16/09/2018 11:15, Alan Bateman wrote:
> On 14/09/2018 20:34, Johannes Kuhn wrote:
>> :
>>
>> 1. Use Case:
>>
>> The most obvious use case is to to "implement" default methods in
>> instances created by j.l.r.Proxy. An implementation of
>> j.l.r.InvocationHandler that handles default methods could look like
>> this:
>>
>>      public Object invoke(Object proxy, Method method, Object[] args)
>>              throws Throwable {
>>          if (method.isDefault()) {
>>              MethodType mt = methodType(method.getReturnType(),
>>                      method.getParameterTypes());
>>              Class<?> declaringClass = method.getDeclaringClass();
>>              Lookup l = MethodHandles.lookup();
>>              MethodHandle mh = l.findSpecial(declaringClass,
>>                      method.getName(), mt, declaringClass)
>>                     .asFixedArity()
>>                     .bindTo(proxy);
>>              return mh.invokeWithArguments(args);
>>          }
>>      // Handle the other methods
>>      return null;
>>      }
>>
>> Such a feature is very valuable so the InvocationHandler does not break
>> if an interface is evolved by adding new default methods.
> The common case will be a public interface in an exported package in
> which case the proxy will be generated in the unnamed module of the
> specified class loader. This means you can create a full-power lookup on
> the proxy class with:
> 
> Class<?> proxyClass = proxy.getClass();
> this.getClass().getModule().addReads(proxyClass.getModule());
> Lookup l = MethodHandles.privateLookupIn(proxyClass,
> MethodHandles.lookup());
> 
> If you invoke findSpecial on this lookup then I would expect it should
> work.

Thank you, this works fine. Unless you don't have
j.l.r.ReflectPermission("suppressAccessChecks").

I try to avoid requiring additional permissions if possible, and code in
an unnamed module does not need privateLookupIn.

Also j.l.r.ReflectPermission("suppressAccessChecks") is considered a
dangerous permission that allows a full sandbox escape.
> 
> There can be cases where the proxy is encapsulated (because the
> interfaces are not public or not in exported packages) but I assume you
> don't need to be concerned with those for now.
> 

Right, I am not concerned about that now.

>> :
>>
>>
>> 3. Cause of the discrepancy between lookup class in named module vs
>> unnamed module
>>
>> As Mandy Chung observed:
>>
>>> IAE is thrown because the implementation calls Lookup::in teleporting
>>> to a Lookup of the special class which lost all access since the
>>> caller class and the requested lookup class are in different modules.
>> This behavior is documented in the Lookup.in() [6]:
>>
>>> * If the old lookup class is in a named module, and the new lookup
>>> class is in a different module M, then no members, not even public
>>> members in M's exported packages, will be accessible. The exception to
>>> this is when this lookup is publicLookup, in which case PUBLIC access
>>> is not lost.
>>> * If the old lookup class is in an unnamed module, and the new lookup
>>> class is a different module then MODULE access is lost.
>> This strikes me as a little bit odd, so I looked around for the
>> reasoning for this difference, and found an old discussion on jigsaw-dev
>> [7]. I especially found this mail from Alan Bateman [8] interesting,
>> where he gave a reasoning for this:
>>
>>> Preserved but perhaps with the (initially surprising) consequence that
>>> all access is lost when m(LC) is a named module and m(A) is a
>>> different module. This arises because the two modules may read very
>>> different sets of modules, the intersection cannot be expressed via a
>>> lookup class + modes.
>> While I agree that intersection might not be expressed via lookup class
>> + modes, I don't think that it is necessary to express that. Instead
>> don't allow any lookup on members in a different module if the MODULE
>> flag is not set.
>>
> Qualified exports complicated this, as does readability. JDK-8173978 [1]
> tracks the need to support checking the intersection.
> 

I also had some thoughts about what semantics Lookup.in() can have.

A Lookup for class A can only be created in 3 ways:

* MethodHandles.lookup() called from A - full access
* MethodHandles.privateLookupIn() - full access
* Lookup.in() - drops modes depending on the distance.

The reasoning for Lookup.in() should always be:

** If a mode bit is set, then the original lookup is the same class or
in the same nest/package/module (depending on the corresponding mode
bit). **

Note that the opposite is not required to be true.

This enforces that if the MODULE bit is set, we know the original module.

If we need to know the original class/nest/package/module to perform a
check but the corresponding mode bit is not set, then we can not know
the original class/nest/package/module, and we have to drop all modes.

Therefore modes should be dropped when teleporting from class A to B
(L.in(B), where L.lookupClass() == A) in this order:

a) If A and B are the same, no modes are dropped, otherwise PROTECTED is
always dropped.
b) If A and B are in the same nest and L has private access, no further
modes are dropped, otherwise PRIVATE is dropped.
c) If A and B are in the same (runtime) package and L has package
access, no further modes are dropped, otherwise PACKAGE is dropped.
d) If B is not public, all modes are dropped.
e) If A and B are in the same module and L has module access, then no
further modes are dropped, otherwise MODULE will be dropped.
f) If A and B are in the same module, L has PUBLIC access and B's
package is the same as A or unconditionally exported, then no further
modes are dropped.
g) If If L has module access (old modes) and
B.getModule().isExported(B.getPackageName(), A.getModule()) then no
further modes are dropped, otherwise all modes are dropped.

Notes:
* f) uses PUBLIC access for the same module. This is safe because module
access and package access was already checked when L was created.
* g) makes use of the fact, that if the MODULE bit is set, we know the
module of the original lookup.
* There is no difference between named and unnamed modules.

But maybe I'm missing some pieces here.


> -Alan
>
> [1] https://bugs.openjdk.java.net/browse/JDK-8173978
>
>
>

Thank you for this pointer, maybe I should rename this thread then?

-Johannes


More information about the core-libs-dev mailing list