On Dec 14, 2015, at 7:02 AM, Alan Bateman <alan.bate...@oracle.com> wrote: > ... > In graph terms then the vertices in the readability graph are the modules. A > directed edge from module M to module M2 means that M reads M2. The graph is > mutable in that edges can be added via the API at runtime. Code in module M > can add a directed edge so that M reads M2. Code in M2 might add a read edge > in the other direction. Code in a module cannot change other modules: M > cannot change M2 so that M2 reads M or M3 or any other module. For > completeness then I should say that edges are only removed when the the > vertices (modules) they connect are GC'ed.
This graph of the "M1 READS M2" relation is a handy visualization. How does it interact with exports, and the unconditional/qualified distinction? Let me try to figure it out… I guess the READS edges would be labeled by the types that flow across them. Let's say TYPES_VIA(M1 READS M2) = {m2pkg.P, m2pkg.Q, …} is the set of types (all public) from M2 that M1 can read. And, I guess that TYPES_VIA(M1 READS M2) contains exactly M2's unconditional (non-qualified) exports, plus M2's qualified (non-unconditional) exports to M1. Which is not to say that code in M1 actually uses all those types, but it could. And it can't use anything else from M2. Yes? (TIA) >> 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. … > > Case A is where our current approach might be too limited. This may be tied > into the discussion point as to how to choose A. If A is in the same module > as LC then it's as before. However if code in named module M creates a > full-power lookup and chooses A in another module M2 then the resulting > L.in(A) has zero access. > > It wouldn't be hard to change this to allow PUBLIC be preserved so that > L.in(A) would at least allow access to public types in packages that are > exported unconditionally by the modules that m(A) reads. Would that increase > usefulness? Would such cases be cases where PL is equally useful? Hmm… What PL does is ignore the M1 READS M2 graph, or (equivalently) adds temporary edges as needed to access unconditionally exported names. (Anticipating a reply to Alex's message of today…) Possibly, a PUBLIC-type lookup QL would only be able to see types readable by M1=MODULE(QL.LC). That is, QL (possibly) would respect the READS graph as it exists, and not attempt to extend it on the fly like PL (publicLookup) does. Points about this: 1. Such a lookup QL is stronger than PL in that it might see qualified exports readable by M1. (This would be Alex's QUALIFIED mode, which I like.) But QL would also be weaker than PL, in that it would respect the existing READS edges, and not create temporary edges like PL does. (Am I right that this is a real distinction? I'm coming up to speed here!) 2. The restriction on QL is interesting in theory but maybe not in practice. After all, if a nosy browser of modules wants to see an unconditional export from M2, he doesn't need to bother with a QL in M1; he just uses PL. 3. How much do we all care that modules are completely leaky in their unconditional (unqualified) exports? It's really helpful that a module can hide its unexported types (and seal them up for optimization, etc.). It's also helpful that modules can use qualified exports to be friends. Do we (can we) discourage Java apps from browsing modules for their unconditional exports? I'm guessing "no (and no)" is the answer. If that's true then there is little reason to limit any QL's ability to be a superset of PL. 4. OTOH, suppose a user is trying to be a good citizen of Module World, and wants to avoid accessing unqualified exports in the absence of a previously declared READS edge. It that a real use case? If so, then having a QL that throws an error if a READS edge is missing will help debug the READS graph, instead of sweeping errors under the rug. From this point of view, usage of PL would be slightly bug-prone, since PL ignores the carefully constructed READS graph. Bottom line: If the READS graph provides useful restrictions, even for unconditional (unqualified) exports, then it would be useful to have a QL (not stronger than PL) which emulates a lookup from some M1, including the *inability* to read any old export.. Bottom line #2: I like Alex's suggestion of having a QL which mimics the lookup "perspective" of public types visible to some M1, but external to M1. (This QL would be not weaker than PL, as well as not stronger.) I was kind of fishing for that extra "mode bit", and I think we caught something. Bottom line #3: If we believe that QL should be incommensurable with PL, it follows that PL has unique capabilities which should be used with care. (Not for security, so much as for bug hygiene.) Bottom line #4: I think we want an API point Lookup.restrictModes(int) that can be used to clear unwanted bits from Lookup.lookupModes(). The Lookup.in API point does this also, but less directly than restrictModes. Given the new degrees of freedom, it is good to provide the direct API point, rather than forcing the user to find A (for L.in(A)) when A might not exist or be easily discoverable. Possible code for restrictModes: public Lookup restrictModes(int mask) { int newModes = (lookupModes() & mask); validateModes(newModes); return new Lookup(lookupClass(), newModes); } // We don't want to find out what happens with lookups that // are private but not public, etc. private static void validateModes(int modes) { if (modes != normalizeModes(modes)) throw new IllegalArgumentException("bad mode mask "+Integer.toHexString(modes)); } // Drop bits from modes until it looks like one that arises in nature. private static int normalizeModes(int modes) { modes &= ALL_MODES; if ((modes & PUBLIC) == 0) return 0; if ((modes & MODULE) == 0) return PUBLIC; if ((modes & PACKAGE) == 0) return (PUBLIC|MODULE); … } > Preserving PUBLIC would mean compromising on the guarantee that there be no > more access that the original but that is only because m(A) might read > modules that m(LC) does not read. It would not give access to non-exported > types in m(A). It would also not give access to public type in packages that > are conditionally exported to m(A). Yes, I see. Well, as stated above, my take is that it may be useful to *not* compromise the guarantee, and just let PL be an oddity. I'm increasingly uncomfortable with allowing PL, with its odd laxity about READS, be an underlying capability of almost every Lookup. I really like the monotonicity guarantee: It makes reasoning about lookup delegation easier. Maybe it could be broken safely in the case of PL powers, but, gee, exceptions like that make security analysis harder, and it's already hard enough. — John