MethodHandles.Lookup and modules
Alan.Bateman at oracle.com
Fri Dec 11 14:02:52 UTC 2015
Thanks for jumping in on this and the guidance. Comments inline.
On 10/12/2015 23:08, John Rose wrote:
> This stuff makes my head hurt, but I'm fine with any semantics that
> preserves the following:
> 1. full power: MethodHandles.lookup() has the same privileges as
> (non-constructor) bytecode in the caller class
Preserved, no changes.
> 2. downward monotonic
> 2a. L.in(A) never has more privileges than L (for all L, A)
> 2b. L.in(A) never has more privileges than a full power lookup on A
> (for all L, A)
> 2c. as a corollary, a chain L.in(A).in(B).in(C)… has no more
> privileges than L or any lookups in A, B, C, …
Preserved with the exception of A, B and C in the same named module M
and where the set modules that M reads increases (say where code in M
reads additional modules).
> 3. graceful degradation: L.in(A) loses only privileges broken by the
> distance between LC=L.lookupClass and A
> 3a. if A==LC no privileges are lost; the identical L can be the result
> 3b. if A and LC are nestmates, only protected privileges may be lost
> (dynamic emulation of JLS nestmate access)
> 3c. if A and LC are in the same package, only private privileges may
> be lost
> 3d. if A and LC are in the same module, only package privileges may be
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.
> 4. downward convergence (to publicLookup or empty privileges)
> 4a. if A is inaccessible to LC=L.lookupClass, L.in(A) has no
> privileges (less than publicLookup)
> 4b. if A is accessible to LC and L has non-empty privileges, L.in(A)
> is no less privileged than publicLookup
> 4c. for any L with non-empty privileges, there is a sequence of types
> A,B where L.in(A).in(B) is equivalent to publicLookup
Downward convergence to zero access/empty privileges.
No downward convergence to publicLookup because
m(publicLookup.lookupClass) must read all modules, thus a superset of
the modules that named modules will read. I should say of course that
the publicLookup can only be used to create method handles to public
members in packages that are exported unconditionally. So nothing that
code in a named module couldn't otherwise access when it increases
> 5. publicLookup has a reasonable minimal set of globally acceptable
> 5a. this set of privileges is singular, and does not depend on the
> 5b. the only possible results of publicLookup.in(A) are no change, and
> falling to empty privileges
publicLookup is minimally trusted and so can only create method handles
where the target type is public and exported (unconditionally).
For 5b then publicLookup.in(A) may result in no change or degrade but
not to empty privileges in one hop. The "no change" case is where A is
in an unnamed module (think class path). The "degrade" case is where A
is a named module and so the resulting lookup can only be used to access
the public types that are exported unconditionally by modules that m(A)
> Access can only be measured against the current state of the module graph,
> which means that certain results can vary over time, in a consistent way
> across the whole system.
Yes, consistent with bytecode and also the access checks in core reflection.
> The above are my preferences; I can imaging tweaking some of those things
> in order to make the API fit more gracefully into modules. I rely on
> Alan & co.
> to figure that out!
and we rely on you to ensure that what we do make sense. I hope this
does not constitute a circular dependency :-)
> (BTW, see https://bugs.openjdk.java.net/browse/JDK-8145070 )
The defining loader of publicLookup.lookupClass() is no longer null and
looks like Nashorn trips up on the permission check in
> I agree with this. Lookups are primarily about emulating bytecode
> behavior (not reflection behavior), and providing a framework for safe
> and sane delegation.
Adding edges to the readability graph impacts all access checks
consistently and I think in a safe way.
> Does publicLookup.in(A) create a new set of privileges depending on A?
> I think that is the current design; maybe it's best, but it's a little
> If there really is a uniquely minimal set of public privileges,
> then that should be publicLookup, and PL.in(A) should not affect
> that minimal set. That's the intention of saying the LC of PL is
> mere convention.
As publicLookup is no longer the minimal set of privileges then
publicLookup.in(A) will impact reduce access for the case that m(A) is a
named module. Stanislav makes a good point about the javadoc as a named
module may not read all modules and so publicLookup needs to be in an
unnamed module for now.
> We can modify axiom #5 to say: PL accesses a unique family of
> privileges sets, indexed by the LC of each PL. This family is
> minimal in the sense that, for any PL in the family, and any
> lesser privileged L, L is either another member of the PL family,
> or has empty privileges.
> In this case, the LC of a PL provides a scoped or restricted
> view of "really public" names, all of which are non-problematic
> to view from any point in the system. The fact that you have
> to dial in a special LC to get to one of those names is merely
> a formality. No abstraction can ever be broken via a PL,
> even if the various PLs have slightly different capabilities.
> (N.B. Because of 4a, PL.in(A) can go empty, if PL.LC cannot
> access A.)
I hope the adjustments that we have are reasonable. We start with a
singleton PL that can produce method handles to public members of
exported packages. It can then be restricted, with PL.in(A), to just the
public types exported unconditionally by A or the modules that A reads.
We have looked of taking snapshots and persisting intersections but it
diverges from bytecode behavior which I think rules it out.
More information about the jigsaw-dev