MethodHandles.Lookup and modules
john.r.rose at oracle.com
Sat Dec 19 06:32:36 UTC 2015
> On Dec 18, 2015, at 4:44 PM, Alex Buckley <alex.buckley at oracle.com> wrote:
> So let's recap full power lookups:
> - Start with an arbitrary class in an arbitrary module calling MethodHandles.Lookup.lookup() to get a "full power" lookup object L. L's lookup modes are PUBLIC + QUALIFIED + MODULE + PROTECTED + PACKAGE + PRIVATE.
> - The arbitrary class obtains a Class object representing class A, then calls L.in(A). If L's lookup class cannot access A (for example, A is package-private in a different package than L's lookup class), then the resulting lookup object has lookup mode NOACCESS. Otherwise:
> -- If A is in a different module than L's lookup class, then the resulting lookup object has lookup mode NOACCESS.
> -- If A is in the same module as L's lookup class, but a different package, then the resulting lookup object has lookup modes no greater than PUBLIC + QUALIFIED + MODULE.
> -- If A is in the same module as L's lookup class, and in the same package, but A is a different class than L's lookup class, then the resulting lookup object has lookup modes no greater than PUBLIC + QUALIFIED + MODULE + PACKAGE.
> -- If A is nested in the same package member as L's lookup class, then the resulting lookup object has lookup modes no greater than PUBLIC + QUALIFIED + MODULE + PACKAGE + PRIVATE.
> -- If A is the same class as L's lookup class, then the resulting lookup object has the same lookup modes as L.
I agree; that's just how they teleport.
>> The publicLookup should have UNCONDITIONAL and PUBLIC set. An
>> original full-power lookup does *not* have UNCONDITIONAL set, just
>> PUBLIC. The purpose of UNCONDITIONAL is to allow publicLookup to be
>> unconcerned (as documented) about its LC. We can restore LC to be
> Since PUBLIC is just UNCONDITIONAL with a concern for readability, it's surprising that publicLookup cares about PUBLIC.
The PUBLIC bit is along for the ride. It's meaningless when accompanied by UNCONDITIONAL, and can stay on, meaningfully, after UNCONDITIONAL drops off.
(Actually, PUBLIC is also required when you are looking up a *member* C.M of class C using publicLookup. Both C and C.M had better be marked "public". And you had better have the PUBLIC mode bit set so you can make use of that fact. The C.M is not marked "unconditional".)
> Before saying any more about that, let me take a small detour. I seem to recall an intent to specify the public lookup object as representing an (undisclosed) lookup class in the unnamed module ... if so, then since the unnamed module reads all named modules by decree, we have UNCONDITIONAL+PUBLIC as trivially equal in access power to PUBLIC, and we don't need UNCONDITIONAL at all ... if not, then publicLookup could be UNCONDITIONAL only.
True; this uses that odd feature of the unnamed module. It's clever, but surprising.
With UNCONDITIONAL, though, we don't need to use that feature, and can compatibly retain the JDK7/8 behavior of PL.LC == Object.
Also, without UNCONDITIONAL, you can't teleport publicLookup to another module (you drop to NOACCESS).
That is a mildly bad thing, because Lookup.findClass assumes flexible teleportation.
The use case with PL is:
Q: I want to find an unconditionally readable class named "Foo", from the viewpoint (classloader & module) of the class Bar whose Class object Bar.class I possess. How?
Basically, adding UNCONDITIONAL allows Lookup to manage the magic directly, instead of leaning on the unnamed module, with more power to boot.
UNCONDITIONAL also tracks the fact that the Lookup object came from publicLookup, not full-power-lookup. If not a mode bit, how else will be do it?
> Returning from the detour ... does the public lookup object have PUBLIC solely so that it can teleport to give new lookup objects which drop UNCONDITIONAL but still retain the interesting PUBLIC mode?
Yes; see above for an example.
> Proceeding to walk through a publicLookup:
> - Start with an arbitrary class in an arbitrary module calling MethodHandles.Lookup.publicLookup() to get a public lookup object PL. PL's lookup mode is UNCONDITIONAL [+ PUBLIC?].
Yes, UNCONDITIONAL + PUBLIC.
> - The arbitrary class obtains a Class object representing class A, then calls PL.in(A). If PL's lookup class cannot access A [I'm not sure what PL's lookup class is, but it seems plausible that it can't access A], then the resulting lookup object has lookup mode NOACCESS.
If PACKAGE(A) is unconditionally exported by its module, then all is well, since the UNCONDITIONAL bit enables the required read.
> -- If A is in a different module than PL's lookup class, then the resulting lookup object has lookup mode UNCONDITIONAL. ???
Still UNCONDITIONAL + PUBLIC. When the teleported lookup is used, it also ignores read edges.
> -- If A is in the same module as PL's lookup class, then the resulting lookup object has lookup mode PUBLIC. ???
If PACKAGE(A) is unconditionally exported by its module, then the above rule applies.
If PACKAGE(A) is otherwise (mod-private or qual-export) then… A was not accessible to PL in the first place, so NOACCESS.
So a teleportation of PL either retains all rights or loses all rights.
It's also possible to teleport PL to some module M1 and then strip the result of UNCONDITIONAL, by saying restrictModes(PUBLIC)(.
At that point the lookup is marooned in M1, and will only ever see the unconditional exports via M1 READS M2.
>> As an odd use case, a stripped lookup with only PACKAGE modes
>> will be able to see any package-mate T of LC, and any package-private
>> API point T.M, but it won't be able to query anything *outside* of the
>> package of T. Unfortunately, it also won't be able to query any public
>> member T.M, unless the PUBLIC bit is present. So I suppose stripping
>> MODULE and QUALIFIED, leaving PUBLIC and PACKAGE, would
>> provide useful access to T.M even if M were public.
> I think there's an "old" meaning of PUBLIC floating around here. Now that PUBLIC pertains to public types _in unconditionally exported packages_, it shouldn't relate to intra-package access, since for such access you don't care if the package is exported. It's now MODULE that accesses public types within the module, so arguably, access to a public member M of package-mate T should be possible with lookup modes MODULE+PACKAGE, not PUBLIC+PACKAGE.
Yes, that's arguable, but it makes my brain hurt to argue it. It
means that, when access-checking a findStatic of C.M, if M is public,
we have to ask if we have MODULE access, not PUBLIC access. This is
sort of consistent with the deeper meaning of M's "public" bit, but
will require endless explanation. Meanwhile, if we (mis-)use the
PUBLIC bit when accessing a "public" M, nobody gets hurt, since all
natural lookups with MODULE also have PUBLIC.
If you use restrictModes to drop PUBLIC and keep MODULE, you get an
artificial lookup which stays at home in its module but never looks
outside. No lookup today operates this way, but it's logical and
might be useful (with findClass). But if you apply this artificial
lookup to findStatic(C, "M") on a MODULE-private C, do you really
have a right to access C.M? Given that the docs clearly say that
the lookupModes bits modulate access to *both* C and M?
Hence, as I said, I prefer to apply those bits as mechanically as possible.
More information about the jigsaw-dev