JDK-8244090 : public lookup should find public members of public exported types
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang.invoke
  • Affected Version: 14.0.1
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2020-04-29
  • Updated: 2022-06-02
  • Resolved: 2020-09-03
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 16
16 b15Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Description
In Jetty 10, we use method handles in the WebSocket implementation.

We need to scan WebSocket EndPoint application classes (MyEndPoint) for annotated methods whose signature may vary and may take application classes, for example `@OnMessage void onMessage(Session s, MyString m)` where @OnMessage and Session are JSR 356 classes and MyEndPoint and MyString are application classes.

It may be possible that the same web application is deployed twice, perhaps with different configurations.

Therefore we have:
* class "Impl" is the Jetty implementation class that creates
MethodHandle.Lookup, loaded by serverClassLoader (and in a named JPMS module - although modules do not seem to matter)
* class MyEndPoint and MyString are in webapp1's WEB-INF/classes, loaded by webClassLoader1 in an unnamed JPMS module
* class MyEndPoint and MyString are also in webapp2's WEB-INF/classes, loaded by webClassLoader2 in another unnamed JPMS module

Class Impl does this:
MethodHandles.publicLookup().in(endPointClass).findVirtual(...), where
"endPointClass" is class MyEndPoint loaded by the web class loader
(either webClassLoader1 or webClassLoader2).

We must call .in() to separate the 2 web applications (failing to call
.in() results in linkage errors).

Calling publicLookup().in() works in Java 11-13.

In Java 14, it does not work anymore.
The reason seems to stem from the fixes for
https://bugs.openjdk.java.net/browse/JDK-8173978.

In particular, in Java 14 publicLookup() returns a Lookup with
mode=UNCONDITIONAL (but not PUBLIC? Is this intended?) and
Lookup.lookupClassOrNull() returns Object.class rather than the class passed to .in() - like it was doing in Java 11.

A reproducer project is attached.

Make sure that classes from module1 are compiled into module1/classes and run it from the root with: 

java --class-path module2/classes org.module2.Main
Comments
URL: https://hg.openjdk.java.net/jdk/jdk/rev/44ecc51da901 User: mchung Date: 2020-09-03 19:43:37 +0000
03-09-2020

Review thread: https://mail.openjdk.java.net/pipermail/core-libs-dev/2020-August/068147.html
11-08-2020

> The current workaround is to use lookup().in(endPointClass1).dropLookupMode(PACKAGE) which will produce a Lookup on endPointClass1 with PUBLIC access. It works when using the class-path, but not when using the module-path when module2 has a module-info-java (that just exports org.module2): $ java11 --module-path module2/classes --module module2/org.module2.Main lookup1 = org.module1.MyEndPoint/noaccess Exception in thread "main" java.lang.IllegalAccessException: symbolic reference class is not accessible: class org.module1.MyEndPoint, from org.module1.MyEndPoint/noaccess (unnamed module @42d80b78) at java.base/java.lang.invoke.MemberName.makeAccessException(MemberName.java:942) at java.base/java.lang.invoke.MethodHandles$Lookup.checkSymbolicClass(MethodHandles.java:2054) at java.base/java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:2026) at java.base/java.lang.invoke.MethodHandles$Lookup.findVirtual(MethodHandles.java:1194) at module2/org.module2.Main.main(Main.java:33) $ java14 --module-path module2/classes --module module2/org.module2.Main lookup1 = org.module1.MyEndPoint/org.module2.Main/noaccess Exception in thread "main" java.lang.IllegalAccessException: symbolic reference class is not accessible: class org.module1.MyEndPoint, from class org.module1.MyEndPoint (unnamed module @533ddba), previous lookup org.module2.Main (module module2) at java.base/java.lang.invoke.MemberName.makeAccessException(MemberName.java:954) at java.base/java.lang.invoke.MethodHandles$Lookup.checkSymbolicClass(MethodHandles.java:2809) at java.base/java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:2781) at java.base/java.lang.invoke.MethodHandles$Lookup.findVirtual(MethodHandles.java:1883) at module2/org.module2.Main.main(Main.java:33)
30-04-2020

JDK-8173977 should probably be a longer-term solution. The current workaround is to use lookup().in(endPointClass1).dropLookupMode(PACKAGE) which will produce a Lookup on endPointClass1 with PUBLIC access.
29-04-2020

`publicLookup().in(endPointClass1)` in JDK 13 produces a Lookup object on endPointClass1 with PUBLIC access only. UNCONDITIONAL bit was lost. The resulting Lookup can access classes unconditionally exported from module M that the module of endPointClass1 can read. The resulting Lookup is no longer a public lookup, i.e. the lookup context is the lookup class (i.e. endPointClass1) and the lookup mode (i.e. PUBLIC). `publicLookup().in(endPointClass1)` in JDK 14 produces a Lookup object on endPointClass1 with UNCONDITIONAL bit due to the spec change for JDK-8173978. The resulting Lookup remains to be a public lookup which can access any unconditionally exported classes from any module. In JDK 13, MethodHandles::publicLookup is the only factory method to obtain a public lookup whose lookup class is java.lang.Object. In JDK 14, a public lookup can be teleported into any unconditionally exported class via Lookup::in that produces a new public Lookup object. Although the lookup class of the teleported public Lookup is endPointClass1, it should not affect the lookup context. The reproducer shows that the new public Lookup uses Object as the lookup clsas that violates the loader constraint. lookup1 finds "org.module1.MyEndPoint" loaded by CL1 and it adds a loader constraint with the boot loader (rather than CL1) due to the workaround fixed by JDK-8228671 that uses Object as the lookup class. When lookup2 finds "org.module1.MyEndPoint" loaded by CL2, the lookup fails with loader constraint violation. Exception in thread "main" java.lang.IllegalAccessException: no such method: org.module1.MyEndPoint.onMessage(MyString)void/invokeVirtual at java.base/java.lang.invoke.MemberName.makeAccessException(MemberName.java:971) at java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1114) at java.base/java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:2785) at java.base/java.lang.invoke.MethodHandles$Lookup.findVirtual(MethodHandles.java:1883) at org.module2.Main.main(Main.java:39) Caused by: java.lang.LinkageError: loader constraint violation: when resolving method 'void org.module1.MyEndPoint.onMessage(org.module1.MyString)' the class loader 'bootstrap' of the current class, java/lang/Object, and the class loader 'CL2' @7440e464 for the method's defining class, org/module1/MyEndPoint, have different Class objects for the type org/module1/MyString used in the signature (java.lang.Object is in module java.base of loader 'bootstrap'; org.module1.MyEndPoint is in unnamed module of loader 'CL2' @7440e464, parent loader 'app') at java.base/java.lang.invoke.MethodHandleNatives.resolve(Native Method) at java.base/java.lang.invoke.MemberName$Factory.resolve(MemberName.java:1084) at java.base/java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1111) ... 3 more
29-04-2020