-
Bug
-
Resolution: Not an Issue
-
P3
-
None
-
14
-
x86_64
-
windows_10
ADDITIONAL SYSTEM INFORMATION :
openjdk version "14-ea" 2020-03-17
OpenJDK Runtime Environment (build 14-ea+10-332)
OpenJDK 64-Bit Server VM (build 14-ea+10-332, mixed mode, sharing)
A DESCRIPTION OF THE PROBLEM :
MethodHandles.privateLookupIn now records the original lookup class and checks readability against both original lookup class and the target class.
This change in behavior means that code that was valid and permitted once no longer runs.
The behavior matches the new specification of privateLookupIn, but this new specification causes a regression.
REGRESSION : Last worked in version 12.0.2
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Define 3 modules, named "a", "b" and "c".
The module "a" should be open.
Export a package in "b" only to module "a".
Use MethodHandles.privateLookupIn() with a Lookup from a class in "c" on a class in module "a".
Try to access a class in module "b" with the resulting lookup.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Code works as it did in Java 9, 10, 11, 12.
data=test
ACTUAL -
An exception is thrown:
Exception in thread "main" java.lang.IllegalAccessException: access to public member failed: b.B.data/java.lang.String/getField, from class a.A (module a), previous lookup c.Serializer (module c)
at java.base/java.lang.invoke.MemberName.makeAccessException(MemberName.java:950)
at java.base/java.lang.invoke.MethodHandles$Lookup.checkAccess(MethodHandles.java:2933)
at java.base/java.lang.invoke.MethodHandles$Lookup.checkField(MethodHandles.java:2883)
at java.base/java.lang.invoke.MethodHandles$Lookup.getDirectFieldCommon(MethodHandles.java:3092)
at java.base/java.lang.invoke.MethodHandles$Lookup.getDirectFieldNoSecurityManager(MethodHandles.java:3087)
at java.base/java.lang.invoke.MethodHandles$Lookup.unreflectField(MethodHandles.java:2610)
at java.base/java.lang.invoke.MethodHandles$Lookup.unreflectGetter(MethodHandles.java:2566)
at c/c.Serializer.serialize(Serializer.java:23)
at a/a.A.main(A.java:10)
---------- BEGIN SOURCE ----------
--- a/module-info.java
open module a {
requires b;
requires c;
}
--- a/a/A.java
package a;
import b.B;
import c.Serializer;
public class A {
public static void main(String[] args) throws Throwable {
B b = new B("test");
System.out.println(Serializer.serialize(b));
}
}
--- b/module-info.java
module b {
exports b to a;
}
--- b/b/B.java
package b;
public class B {
public final String data;
public B(String data) {
this.data = data;
}
}
--- c/module-info.java
module c {
exports c;
}
--- c/c/Serializer.java
package c;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Field;
public class Serializer {
private static final StackWalker WALKER = StackWalker
.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
private static final Lookup LOOKUP = MethodHandles.lookup();
public static String serialize(Object o) throws Throwable {
Class<?> caller = WALKER.getCallerClass();
// Add a read edge to the caller module
Serializer.class.getModule().addReads(caller.getModule());
Lookup remote = MethodHandles.privateLookupIn(caller, LOOKUP);
StringBuilder sb = new StringBuilder();
String sep = "";
for (Field f : o.getClass().getDeclaredFields()) {
sb.append(f.getName());
sb.append("=");
sb.append(remote.unreflectGetter(f).invoke(o));
sb.append(sep);
sep = ",";
}
return sb.toString();
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Call MethodHandles.lookup() using the result of privateLookupIn and use that lookup instead.
Lookup remote = (Lookup) MethodHandles.privateLookupIn(caller, LOOKUP).findStatic(MethodHandles.class, "lookup", methodType(Lookup.class)).invokeExact();
FREQUENCY : always
openjdk version "14-ea" 2020-03-17
OpenJDK Runtime Environment (build 14-ea+10-332)
OpenJDK 64-Bit Server VM (build 14-ea+10-332, mixed mode, sharing)
A DESCRIPTION OF THE PROBLEM :
MethodHandles.privateLookupIn now records the original lookup class and checks readability against both original lookup class and the target class.
This change in behavior means that code that was valid and permitted once no longer runs.
The behavior matches the new specification of privateLookupIn, but this new specification causes a regression.
REGRESSION : Last worked in version 12.0.2
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Define 3 modules, named "a", "b" and "c".
The module "a" should be open.
Export a package in "b" only to module "a".
Use MethodHandles.privateLookupIn() with a Lookup from a class in "c" on a class in module "a".
Try to access a class in module "b" with the resulting lookup.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Code works as it did in Java 9, 10, 11, 12.
data=test
ACTUAL -
An exception is thrown:
Exception in thread "main" java.lang.IllegalAccessException: access to public member failed: b.B.data/java.lang.String/getField, from class a.A (module a), previous lookup c.Serializer (module c)
at java.base/java.lang.invoke.MemberName.makeAccessException(MemberName.java:950)
at java.base/java.lang.invoke.MethodHandles$Lookup.checkAccess(MethodHandles.java:2933)
at java.base/java.lang.invoke.MethodHandles$Lookup.checkField(MethodHandles.java:2883)
at java.base/java.lang.invoke.MethodHandles$Lookup.getDirectFieldCommon(MethodHandles.java:3092)
at java.base/java.lang.invoke.MethodHandles$Lookup.getDirectFieldNoSecurityManager(MethodHandles.java:3087)
at java.base/java.lang.invoke.MethodHandles$Lookup.unreflectField(MethodHandles.java:2610)
at java.base/java.lang.invoke.MethodHandles$Lookup.unreflectGetter(MethodHandles.java:2566)
at c/c.Serializer.serialize(Serializer.java:23)
at a/a.A.main(A.java:10)
---------- BEGIN SOURCE ----------
--- a/module-info.java
open module a {
requires b;
requires c;
}
--- a/a/A.java
package a;
import b.B;
import c.Serializer;
public class A {
public static void main(String[] args) throws Throwable {
B b = new B("test");
System.out.println(Serializer.serialize(b));
}
}
--- b/module-info.java
module b {
exports b to a;
}
--- b/b/B.java
package b;
public class B {
public final String data;
public B(String data) {
this.data = data;
}
}
--- c/module-info.java
module c {
exports c;
}
--- c/c/Serializer.java
package c;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Field;
public class Serializer {
private static final StackWalker WALKER = StackWalker
.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
private static final Lookup LOOKUP = MethodHandles.lookup();
public static String serialize(Object o) throws Throwable {
Class<?> caller = WALKER.getCallerClass();
// Add a read edge to the caller module
Serializer.class.getModule().addReads(caller.getModule());
Lookup remote = MethodHandles.privateLookupIn(caller, LOOKUP);
StringBuilder sb = new StringBuilder();
String sep = "";
for (Field f : o.getClass().getDeclaredFields()) {
sb.append(f.getName());
sb.append("=");
sb.append(remote.unreflectGetter(f).invoke(o));
sb.append(sep);
sep = ",";
}
return sb.toString();
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Call MethodHandles.lookup() using the result of privateLookupIn and use that lookup instead.
Lookup remote = (Lookup) MethodHandles.privateLookupIn(caller, LOOKUP).findStatic(MethodHandles.class, "lookup", methodType(Lookup.class)).invokeExact();
FREQUENCY : always