MethodHandles.Lookup and modules
john.r.rose at oracle.com
Fri Dec 11 20:38:30 UTC 2015
On Dec 11, 2015, at 6:02 AM, Alan Bateman <Alan.Bateman at oracle.com> wrote:
> 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).
Does this mean adding edges to the readability graph at M?
If so, then that's covered by the blanket exception mentioned later.
If you mean something else, maybe we need a point 2d.
>> 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 lost
> 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.
That's fine. There are two main use cases for Lookup.in,
neither of which require the tracking of long chains of L.in(A/B/C…).
A. Agent with full-power lookup wants to invoke another agent with the lookup,
but wants to limit access, because he doesn't fully trust the other agent.
He does a single L.in(A) to a remote-enough type A, creating a non-full-power lookup.
(Note: Picking A is sometimes non-trivial. This might be an API flaw.)
B. Agent with full-power lookup wants to get access to private nestmate in A.
He does a single L.in(A) where LC and A are in the same package member.
This works around differences between access checks at JVM and JLS levels,
just as the package-private accessor methods from javac do. (Yuck!)
>> 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.
So points 4abc are just wrong. I can live with that.
And, publicLookup can be used to get a lookup object that
provides controlled access to a given module's M unconditional
exports, without at the same time conferring access to M's internals.
This is an important kind of weakened lookup.
Going back to graceful degradation:
3e. If A is in a named module and LC in the unnamed module (or L is a public lookup), only public names readable from A are retained
And then we get:
4. downward convergence to empty privileges
4a. if A is inaccessible to LC=L.lookupClass, L.in(A) has no privileges
4b. if A and LC are in different named modules, L.in(A) has no privileges (there is no attempt to retain an intersection of readability sets)
> 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 readability.
Right. (FWIW, the term "unconditional" is not in the Lookup javadoc.)
Should there be a way to build a lookup, for two modules M1/M2, which
reads those names of M2 which M1 can read, except no internals
of M1? I wonder if such a thing would be useful? Probably not.
But it would be useful to have a lookup in a module M1 which can
read the exports of *every* M2 that M1 can see, except no M1 internals.
(This includes the unconditionally exported public names of M1.)
This would be a Lookup with an LC in M1 and flags of PUBLIC only.
I guess that is the effect of PL.in(M1) and point 3a, right?
>> 5. publicLookup has a reasonable minimal set of globally acceptable privileges
>> 5a. this set of privileges is singular, and does not depend on the lookupClass
>> 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) reads.
5. publicLookup has a reasonable minimal set of globally acceptable privileges
5a. this set of privileges is singular, and does not depend on the lookupClass (but it will always be in the unnamed module)
5b. the only possible results of publicLookup.in(A) are no change, and (following 3e) a public-only access from a named module
>> 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 odd.
>> 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.
Thanks for discussing this. I understand it better now, and agree that these
are the two most useful new "powers" for lookup objects.
> We have looked of taking snapshots and persisting intersections but it diverges from bytecode behavior which I think rules it out.
Well, other lookups diverge from bytecode behavior also, but only by
dropping away access modes (like private, package, etc.).
This covers use case A above. You could argue that intersecting
readability sets is useful in a similar way, but it is way too complex.
We don't aspire to create a Lookup object on exactly three API
points and no more—that's overkill. A hypothetical application
that wants to express intersections (or unions) of Lookup capabilities
can build this on top of lookups.
More information about the jigsaw-dev