John,
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
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.
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
readability.
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.
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
Class::getClassLoader.
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
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.
We have looked of taking snapshots and persisting intersections but it
diverges from bytecode behavior which I think rules it out.
-Alan