FYI, we just had a useful conversation about how the new readability
restrictions from Jigsaw modules should affect the MHs.Lookup API.
Specifically, we need a couple more access modes to represent the new
layers of type readability (interacts with lookup and accessibility),
plus a special behavior (another access mode bit) for publicLookup,
to represent the fact that there is no longer a true global scope.

Given the increased complexity of access layers we also want to
add a (trivial) API point to directly drop access mode bits from a Lookup.

(copied below) and nearby messages.

The Jigsaw guys have a charming name for what does:
It "teleports" a lookup to another location.  Teleporting outside a module
tends to shut down all access rights (to preserve monotonicity of privilege),
except for publicLookup which retains its special globalized viewpoint.

This is a refinement on the current Lookup enhancement in the Jigsaw repo.
As one consequence, the publicLookup.lookupClass can go back to
being Object, and is usefully teleportable elsewhere, so that (in turn)
it is broadly useful with the new API point Lookup.findClass, which
uses the lookupClass as a starting point for name resolution.

— John

Begin forwarded message:

From: John Rose <>
Subject: Re: MethodHandles.Lookup and modules
Date: December 18, 2015 at 12:20:08 AM PST
To: Alex Buckley <>

On Dec 17, 2015, at 6:01 PM, John Rose <> 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.  
> 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 
> 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 
- MODULE mode: A public type T is accessible (within or beyond its package), 
- QUALIFIED mode: A public type T is accessible beyond its module, when 
- PUBLIC mode: A public type T is accessible beyond its module friends when 

These conditions can be tested independently.  PACKAGE implies MODULE, but 
everything else is disjoint.

- UNCONDITIONAL: In this mode, a type T is accessible if 
- 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 

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 
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 
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
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.

About monotonicity:

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, 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.

— John

mlvm-dev mailing list

Reply via email to