JDK-8226916 : Lookup.in should allow teleporting from a lookup class in a named module without dropping all access
  • Type: CSR
  • Component: core-libs
  • Sub-Component: java.lang.invoke
  • Priority: P3
  • Status: Closed
  • Resolution: Approved
  • Fix Versions: 14
  • Submitted: 2019-06-27
  • Updated: 2020-01-10
  • Resolved: 2019-07-23
Related Reports
CSR :  
Relates :  
Description
Summary
-------

This CSR proposes to enhance `Lookup::in` and `MethodHandles::privateLookup` API
for cross module teleporting and the access check to use the previous lookup class
from which this `Lookup` object was teleported in addition to this `Lookup` context
(current lookup class and allowed modes) such that a target class is accessible 
if and only if it is equally accessible to both the previous lookup class and current
lookup class.

Problem
-------

`Lookup::in` API currently does not support cross module teleporting.
It currently specifies to drop all access when teleporting from
a lookup class in a named module to a lookup in another module.
This proposes to enhance `Lookup::in` and `MethodHandles::privateLookupIn`
API to perform double module access when the `Lookup` object
is a result of cross module teleporting.

Solution
--------

Extend `Lookup` object to maintain a previous lookup class when
a `Lookup` object is created from cross-module teleporting. 

`Lookup::in` and `MethodHandles::privateLookupIn` produces
a `Lookup` object with a previous lookup class if and only if
it teleports from a lookup on `C` in one module to `D` in another module.
For a `Lookup` object with a previous lookup class, its lookup mode
must have MODULE bit cleared.

A new `Lookup::previousLookupClass` API will be added to query
the previous lookup class, or null if not present.

Method handle lookup will perform access check against both the
lookup class and the previous lookup class, if present, and
the referenced class must be equally accessible to the module of
the previous lookup class and the lookup class as specified in
`Lookup::accessClass`.

There is no change to `MethodHandles::lookup` API and it produces a `Lookup` 
object with PRIVATE, PROTECTED, PACKAGE, MODULE, PUBLIC bits set.

Incompatibility
----------------

This section summarizes the spec changes and the incompatibility for each of them.

__`Lookup::in` will drop all access if it hops to a third module (named or unnamed)__

When `Lookup::in` is invoked to teleport from lookup class `C`  
in module `M0` to `D` in module `M1`,  the returned Lookup
will have PUBLIC bit set that can be used to access public members of
public types in any module that both M0 and M1 read the type is
in a package that is exported at least to both M0 and M1.

```
   Lookup CL = MethodHandles.lookup();   // caller is C
   :
   Lookup DL = CL.in(D.class); 
   :
   Lookup EL = DL.in(E.class);
```

If it attempts to teleport from `C` in `M0` to `D` in `M1` and then to 
`E` in `M2` and they all are unnamed modules, as the example code above,
`DL` is the lookup object on `D` with `C` as previous lookup class with
PUBLIC bit set.  When this lookup object is called to teleport to `E`,
the returned `Lookup` object, `EL`, will have all access dropped since
`DL` does not have `MODULE` bit.  In other words, a Lookup can do
one single hop at most to produce a lookup object with PUBLIC access. 
A `Lookup` attempting to hop to a third module (named or unnamed) 
will drop all access.

In JDK 13, the returned Lookup object on E still has PUBLIC bit set.
Existing code using a `Lookup` produced by teleporting to a third unnamed
module will fail in looking up public members of public types in unnamed module.

The compatibility risk is expected to be low as we anticipate the common 
usage of `Lookup::in` would be intra-module.

__`MethodHandles::publicLookup` produces a `Lookup` object with
`UNCONDITIONAL` bit set__

The new public Lookup object no longer has `PUBLIC` bit set. No impact to what the types this public lookup can access.

`publicLookup.in(C.class)` produces a new `Lookup` object on `C`
with `UNCONDITIONAL` bit set that can access any public type
in any exported API packages in any module.  Now there can be
more than one `Lookup` object with `UNCONDITIONAL` bit set.

Previously, the `Lookup` object returned by `publicLookup.in(C.class)`
with `UNCONDITIONAL` bit dropped that can access a public type in
a package that is exported by C's module or exported by other module
that C's module reads.

The compatibility risk for the above is minimal since existing code can
only use `MethodHandles::publicLookup` for public lookup.

`publicLookup::dropLookupMode(UNCONDITIONAL)` drops `UNCONDITIONAL`
and hence the returned `Lookup` object has no access.
Previously the returned `Lookup` object has `PUBLIC` bit remained
that can access all public members of public types in a package
that is exported unconditionally by other module that the module of
its lookup class can read.

`dropLookupMode` was introduced in Java SE 9 for a library to hand
its `Lookup` with limited access to a framework to access its internals.
So it is expected that `dropLookupMode(UNCONDITIONAL)` on 
a public lookup is very rare. 

__`privateLookupIn` method requires both `PRIVATE` and `MODULE` bit__

`privateLookupIn` method requires both `PRIVATE` and `MODULE` bit 
to produce a `Lookup` object with private access in addition to the
existing checks for deep reflection.

Previously it requires only `MODULE` access.  Existing code that only
has a `Lookup` object with no private access used to successfully call
`privateLookupIn` method will fail with this change. 

It is expected that a framework library will start teleporting with its own
`Lookup` object with private access via `MethodHandles::lookup` method
and this change does not impact one-hop cross module teleporting.

This impacts the second hop from a `Lookup` object produced
by `privateLookupIn`.
The `Lookup` object produced by `privateLookupIn(D,CL)` will have
`C` as the previous lookup class where `C` is CL's lookup class.

```
    Lookup DL = privateLookupIn(D, CL);
    :
    Lookup EL = privateLookupIn(E, DL);
```

Previously, `privateLookupIn(E, DL)` may succeed if D reads E and
E's module opens E's package at least to D's module.

With this change, `DL` does not have `MODULE` bit and `privateLookupIn(E, DL)` 
will fail to get a private Lookup.   Such code would need to be updated
to use a Lookup with private access.

The compatibility risk is medium although we anticipate existing library or framework
are likely using `Lookup` object with private access rather than a reduced
access in practice and also rarely do multi-hop `privateLookupIn`.

__`UNCONDITIONAL` is no longer used in conjunction with `PUBLIC`__

No impact to existing code.

__`MODULE` can be dropped while other bits are set__

Previously when MODULE bit is set in conjunction with PUBLIC bit,
a Lookup with module mode can access all public types in the module
of the lookup class and  public types in packages exported by
other modules to the module of  the lookup class.

A Lookup with `MODULE` bit set, there is no previous lookup class.
It can access all public types in the module of the lookup class.

If a Lookup on `C` in `M1` with a previous lookup class (`PLC` in `M0`)
with PUBLIC bit, it can access public members of public types in
module M2 that both M0 and M1 read the type is
in a package that is exported at least to both M0 and M1.

No impact to existing code.

Specification
-----------------

Attached the javadoc of `MethodHandles` and `MethodHandles.Lookup` and
MethodHandles.java.patch and specdiff.

List of API change:

1. three new sections added in `Lookup` class spec 
    - Cross-module lookup
    - Cross-module access check
    - Access modes
2. `MethodHandles::privateLookupIn`
3. `Lookup::in`
4. `Lookup::accessClass`
5. `Lookup::dropLookupMode`
6. `Lookup::previousLookupClass`
7. `Lookup::toString`
8. `Lookup::MODULE`
9. `Lookup::UNCONDITIONAL`




Comments
I note that the main bug is already tagged for a release note; moving to Approved.
23-07-2019

Moving to Provisional.
11-07-2019