MethodHandles.Lookup and modules
john.r.rose at oracle.com
Fri Dec 18 08:20:08 UTC 2015
On Dec 17, 2015, at 6:01 PM, John Rose <john.r.rose at oracle.com> wrote:
> I think I would prefer case 2. The user model is PUBLIC is the weakest (non-empty) access
> mode available to bytecode behaviors. As such it respects the LC's position in the module
> graph, and excludes module-private, package-private, and class-private. UNCONDITIONAL
> is the special thing provided by publicLookup, which ignores the module graph. Then
> PACKAGE opens up the LC's package, MODULE opens up the LC's module, and PRIVATE
> opens up the LC itself (plus its nestmates). Feels pretty good, especially since MODULE
> and PACKAGE continue to have a parallel sense of restriction.
> What do you think?
So I caught you in the hall and we talked, and this seems agreeable to us both,
perhaps with a name change to UNCONDITIONAL, and also a distinction between
PUBLIC and QUALIFIED (as you originally proposed).
To try and tease out some symmetry here:
- Always, any type T is accessible to itself, when T = LC.
- PACKAGE mode: Any type T is accessible (within its own package), when PACKAGE(T) = PACKAGE(LC).
- MODULE mode: A public type T is accessible (within or beyond its package), when MODULE(T) = MODULE(LC).
- QUALIFIED mode: A public type T is accessible beyond its module, when IS_CE(T, LC),
where IS_CE(T, LC) = IS_CONDITIONALLY_EXPORTED(PACKAGE(T), MODULE(LC)) and MODULE(LC) READS MODULE(T).
- PUBLIC mode: A public type T is accessible beyond its module friends when IS_UE(T, LC),
where IS_UE(T, LC) = IS_UNCONDITIONALLY_EXPORTED(PACKAGE(T)) and MODULE(LC) READS MODULE(T).
These conditions can be tested independently. PACKAGE implies MODULE, but everything else is disjoint.
- UNCONDITIONAL: In this mode, a type T is accessible if IS_UNCONDITIONALLY_EXPORTED(PACKAGE(T)), regardless of LC.
- PRIVATE/PROTECTED: These protection modes apply only to non-types (JVM does not enforce "private" on classes).
- NOACCESS: This is not a mode but the absence of any combination of modes; no access is allowed.
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 java.lang.Object.
The distinction between QUALIFIED and PUBLIC is present simply because of
the logical fact (as you point out) that, if you teleport to a new module, you
must lose your qualified imports, but you shouldn't lose your unconditional ones.
The distinction between PUBLIC and UNCONDITIONAL is present in order
to capture the differing behaviors of lookups derived from publicLookup and
those derived from full-power lookups.
The presence of MODULE captures the larger but package-like scope of
a module's internal names.
About "mode stripping":
You suggested (which sounds OK) that there is no need to "validate" bit masks of lookup objects.
Just have excludeModes clear some bits and continue. This means there can be lookups which
are able to read (say) using PACKAGE mode but not PUBLIC mode. (Today's lookups can
have PUBLIC without PACKAGE but not vice versa.) Each mode bit enables a single
line in the above logic, and (as you can see) the lines can be applied independently.
Some implications follow.
When looking up a type member T.M, the additional access checking on the
member's enclosing type T (from LC) may lack PACKAGE, MODULE, QUALIFIED,
and PUBLIC modes (if excludeModes stripped those bits from a full-power lookup).
This means that such mode-stripped lookups can (logically) only obtain members
LC.M from the single lookup class LC (since a class always has access to itself).
That seems reasonable and useful.
A stripped lookup with only MODULE or QUALIFIED bits will never be able to "see"
any T.M, since members only match in PUBLIC, PRIVATE, PROTECTED, and
PACKAGE modes. Odd, and probably not useful, but logical.
There does not seem to be a way to say "give me only the T.M for which T
is package-private and M is public", or "give me only the T.M for which T
is module-private but M is public". Those can be composed on top,
especially with the new Lookup.accessClass API point as a post-test on T.
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.
As you see, I'm trying to apply the same mode bits to both types
and their members, as mechanically as possible. One place where
that is tricky is with UNCONDITIONAL. That is rescued by making
sure that UNCONDITIONAL will not lose PUBLIC even if teleported.
The rules for monotonicity of access modes will be strictly applied.
All transforms that create lookups from previous ones will never
produce lookups with additional mode bits set. For excludeModes
this is trivially true. For Lookup.in, it is true in a complex way, as
modes are shed as you "teleport" farther away from the old LC
to the new LC.
The design principle of monotonicity of accessible types and members
(which I mentioned earlier) is stressed by the case of teleporting
between modules, where (you might think) that PUBLIC in module M1
should continue as PUBLIC in modules M2. But that is not monotonic.
The bytecode behaviors involving cross-module readability depend on
the read edges of MODULE(LC).
I think the best path is pretty simple: If you teleport between modules,
you lose all bits, unless UNCONDITIONAL is set, in which case you are
also allowed to keep PUBLIC (since you were already ignoring the read
It's more obvious that teleporting between modules should immediately
drop QUALIFIED. FTR, you could check the read graph and the exports
and if the two LC's had exactly the same read edges, you could justify
keeping the PUBLIC bit set, and similarly for the QUALIFIED bit, if you
check for equal qualified exports. But I think that's too much complexity
for too little benefit. Basically, only the publicLookup is able to leap
between modules. Which seems just fine to me.
More information about the jigsaw-dev