diff --git a/src/java.base/share/classes/java/lang/invoke/BootstrapMethodInvoker.java b/src/java.base/share/classes/java/lang/invoke/BootstrapMethodInvoker.java index bd14bb81b05..44fa6101633 100644 --- a/src/java.base/share/classes/java/lang/invoke/BootstrapMethodInvoker.java +++ b/src/java.base/share/classes/java/lang/invoke/BootstrapMethodInvoker.java @@ -170,16 +170,7 @@ final class BootstrapMethodInvoker { } } } - if (resultType.isPrimitive()) { - // Non-reference conversions are more than just plain casts. - // By pushing the value through a funnel of the form (T x)->x, - // the boxed result can be widened as needed. See MH::asType. - MethodHandle funnel = MethodHandles.identity(resultType); - result = funnel.invoke(result); - // Now it is the wrapper type for resultType. - resultType = Wrapper.asWrapperType(resultType); - } - return resultType.cast(result); + return widenAndCast(result, resultType); } catch (Error e) { // Pass through an Error, including BootstrapMethodError, any other @@ -195,6 +186,38 @@ final class BootstrapMethodInvoker { } } + + /** + * If resultType is a reference type, do Class::cast on the result through + * an identity function of that type, as-type converted to return + * the corresponding reference wrapper type for resultType. + * Works like {@code MethodHandles.identity(resultType).invoke((Object)result)}. + * + * This utility function enforces type correctness of bootstrap method results. + * It is also used to enforce type correctness in other dependently-typed + * methods, such as classData. + */ + static T widenAndCast(Object result, Class resultType) throws Throwable { + if (!resultType.isPrimitive()) { + return resultType.cast(result); + } + + Class wrapperType = Wrapper.asWrapperType(resultType); + if (wrapperType.isInstance(result)) { + @SuppressWarnings("unchecked") + T wrapper = (T) result; + return wrapper; + } + // Non-reference conversions are more than just plain casts. + // By pushing the value through a funnel of the form (T x)->x, + // the boxed result can be widened as needed. See MH::asType. + // Note that this might widen byte into int, float into double, etc + MethodHandle funnel = MethodHandles.identity(resultType); + result = funnel.invoke(result); + // Now it is the wrapper type for resultType. + return wrapperType.cast(result); + } + // If we don't provide static type information for type, we'll generate runtime // checks. Let's try not to... diff --git a/src/java.base/share/classes/java/lang/invoke/ConstantBootstraps.java b/src/java.base/share/classes/java/lang/invoke/ConstantBootstraps.java index 71cae83e160..27d74284dc6 100644 --- a/src/java.base/share/classes/java/lang/invoke/ConstantBootstraps.java +++ b/src/java.base/share/classes/java/lang/invoke/ConstantBootstraps.java @@ -413,8 +413,8 @@ public final class ConstantBootstraps { MethodHandle conv = MethodHandles.explicitCastArguments(id, mt); try { return conv.invoke(value); - } catch (ClassCastException e) { - throw e; // specified, let CCE through + } catch (RuntimeException|Error e) { + throw e; // let specified CCE and other runtime exceptions/errors through } catch (Throwable throwable) { throw new InternalError(throwable); // Not specified, throw InternalError } diff --git a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java index 5454eeca025..a6dbe6ef23f 100644 --- a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java @@ -34,6 +34,7 @@ import sun.security.action.GetBooleanAction; import java.io.FilePermission; import java.io.Serializable; +import java.lang.constant.ConstantDescs; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; @@ -55,7 +56,7 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*; * @see LambdaMetafactory */ /* package */ final class InnerClassLambdaMetafactory extends AbstractValidatingLambdaMetafactory { - private static final int CLASSFILE_VERSION = 52; + private static final int CLASSFILE_VERSION = 59; private static final String METHOD_DESCRIPTOR_VOID = Type.getMethodDescriptor(Type.VOID_TYPE); private static final String JAVA_LANG_OBJECT = "java/lang/Object"; private static final String NAME_CTOR = ""; @@ -71,12 +72,10 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*; private static final String NAME_METHOD_WRITE_REPLACE = "writeReplace"; private static final String NAME_METHOD_READ_OBJECT = "readObject"; private static final String NAME_METHOD_WRITE_OBJECT = "writeObject"; - private static final String NAME_FIELD_IMPL_METHOD = "protectedImplMethod"; private static final String DESCR_CLASS = "Ljava/lang/Class;"; private static final String DESCR_STRING = "Ljava/lang/String;"; private static final String DESCR_OBJECT = "Ljava/lang/Object;"; - private static final String DESCR_METHOD_HANDLE = "Ljava/lang/invoke/MethodHandle;"; private static final String DESCR_CTOR_SERIALIZED_LAMBDA = "(" + DESCR_CLASS + DESCR_STRING + DESCR_STRING + DESCR_STRING + "I" + DESCR_STRING + DESCR_STRING + DESCR_STRING + DESCR_STRING + "[" + DESCR_OBJECT + ")V"; @@ -94,6 +93,9 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*; private static final boolean disableEagerInitialization; + // condy to load implMethod from class data + private static final ConstantDynamic implMethodCondy; + static { final String dumpProxyClassesKey = "jdk.internal.lambda.dumpProxyClasses"; String dumpPath = GetPropertyAction.privilegedGetProperty(dumpProxyClassesKey); @@ -101,6 +103,12 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*; final String disableEagerInitializationKey = "jdk.internal.lambda.disableEagerInitialization"; disableEagerInitialization = GetBooleanAction.privilegedGetProperty(disableEagerInitializationKey); + + // condy to load implMethod from class data + MethodType classDataMType = MethodType.methodType(Object.class, MethodHandles.Lookup.class, String.class, Class.class); + Handle classDataBsm = new Handle(H_INVOKESTATIC, Type.getInternalName(MethodHandles.class), "classData", + classDataMType.descriptorString(), false); + implMethodCondy = new ConstantDynamic(ConstantDescs.DEFAULT_NAME, MethodHandle.class.descriptorString(), classDataBsm); } // See context values in AbstractValidatingLambdaMetafactory @@ -361,14 +369,6 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*; } } - if (useImplMethodHandle) { - FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_STATIC, - NAME_FIELD_IMPL_METHOD, - DESCR_METHOD_HANDLE, - null, null); - fv.visitEnd(); - } - if (isSerializable) generateSerializationFriendlyMethods(); else if (accidentallySerializable) @@ -394,7 +394,7 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*; } try { // this class is linked at the indy callsite; so define a hidden nestmate - Lookup lookup = caller.defineHiddenClass(classBytes, !disableEagerInitialization, NESTMATE, STRONG); + Lookup lookup; if (useImplMethodHandle) { // If the target class invokes a method reference this::m which is // resolved to a protected method inherited from a superclass in a different @@ -403,8 +403,10 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*; // This lambda proxy class has no access to the resolved method. // So this workaround by passing the live implMethod method handle // to the proxy class to invoke directly. - MethodHandle mh = lookup.findStaticSetter(lookup.lookupClass(), NAME_FIELD_IMPL_METHOD, MethodHandle.class); - mh.invokeExact(implMethod); + lookup = caller.defineHiddenClassWithClassData(classBytes, implMethod, !disableEagerInitialization, + NESTMATE, STRONG); + } else { + lookup = caller.defineHiddenClass(classBytes, !disableEagerInitialization, NESTMATE, STRONG); } return lookup.lookupClass(); } catch (IllegalAccessException e) { @@ -554,8 +556,7 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*; visitInsn(DUP); } if (useImplMethodHandle) { - visitVarInsn(ALOAD, 0); - visitFieldInsn(GETSTATIC, lambdaClassName, NAME_FIELD_IMPL_METHOD, DESCR_METHOD_HANDLE); + visitLdcInsn(implMethodCondy); } for (int i = 0; i < argNames.length; i++) { visitVarInsn(ALOAD, 0); diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java index 8f4ccf65d5b..0112120e98e 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java @@ -35,6 +35,7 @@ import java.lang.reflect.Field; import static java.lang.invoke.MethodHandleNatives.Constants.*; import static java.lang.invoke.MethodHandleStatics.TRACE_METHOD_LINKAGE; +import static java.lang.invoke.MethodHandleStatics.UNSAFE; import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; /** @@ -683,10 +684,13 @@ class MethodHandleNatives { private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); /* - * A convenient method for LambdaForms to get the class data of a given class. - * LambdaForms cannot use condy via MethodHandles.classData + * Returns the class data set by the VM in the Class::classData field. + * + * This is also invoked by LambdaForms as it cannot use condy via + * MethodHandles.classData due to bootstrapping issue. */ static Object classData(Class c) { + UNSAFE.ensureClassInitialized(c); return JLA.classData(c); } } diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java index 7a615729c4e..80313f466e7 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java @@ -25,7 +25,6 @@ package java.lang.invoke; -import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.misc.Unsafe; import jdk.internal.misc.VM; @@ -42,6 +41,7 @@ import sun.invoke.util.Wrapper; import sun.reflect.misc.ReflectUtil; import sun.security.util.SecurityConstants; +import java.lang.constant.ConstantDescs; import java.lang.invoke.LambdaForm.BasicType; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -101,13 +101,16 @@ public class MethodHandles { * direct method handles * for any member that the caller has access to via bytecodes, * including protected and private fields and methods. + * This lookup object is created by the original lookup class + * and has the {@link Lookup#ORIGINAL ORIGINAL} bit set. * This lookup object is a capability which may be delegated to trusted agents. * Do not store it in place where untrusted code can access it. *

* This method is caller sensitive, which means that it may return different * values to different callers. * @return a lookup object for the caller of this method, with - * {@linkplain Lookup#hasFullPrivilegeAccess() full privilege access} + * {@linkplain Lookup#ORIGINAL original} and + * {@linkplain Lookup#hasFullPrivilegeAccess() full privilege access}. */ @CallerSensitive @ForceInline // to ensure Reflection.getCallerClass optimization @@ -201,13 +204,15 @@ public class MethodHandles { *

* Otherwise, if {@code M1} and {@code M2} are the same module, this method * returns a {@code Lookup} on {@code targetClass} with - * {@linkplain Lookup#hasFullPrivilegeAccess() full privilege access} and - * {@code null} previous lookup class. + * {@linkplain Lookup#hasFullPrivilegeAccess() full privilege access} + * with {@code null} previous lookup class. *

* Otherwise, {@code M1} and {@code M2} are two different modules. This method * returns a {@code Lookup} on {@code targetClass} that records - * the lookup class of the caller as the new previous lookup class and - * drops {@code MODULE} access from the full privilege access. + * the lookup class of the caller as the new previous lookup class with + * {@code PRIVATE} access but no {@code MODULE} access. + *

+ * The resulting {@code Lookup} object has no {@code ORIGINAL} access. * * @param targetClass the target class * @param caller the caller lookup object @@ -232,7 +237,8 @@ public class MethodHandles { if (targetClass.isArray()) throw new IllegalArgumentException(targetClass + " is an array class"); // Ensure that we can reason accurately about private and module access. - if (!caller.hasFullPrivilegeAccess()) + int requireAccess = Lookup.PRIVATE|Lookup.MODULE; + if ((caller.lookupModes() & requireAccess) != requireAccess) throw new IllegalAccessException("caller does not have PRIVATE and MODULE lookup mode"); // previous lookup class is never set if it has MODULE access @@ -242,7 +248,7 @@ public class MethodHandles { Module callerModule = callerClass.getModule(); // M1 Module targetModule = targetClass.getModule(); // M2 Class newPreviousClass = null; - int newModes = Lookup.FULL_POWER_MODES; + int newModes = Lookup.FULL_POWER_MODES & ~Lookup.ORIGINAL; if (targetModule != callerModule) { if (!callerModule.canRead(targetModule)) @@ -270,53 +276,154 @@ public class MethodHandles { /** * Returns the class data associated with the lookup class - * of the specified {@code Lookup} object, or {@code null}. + * of the given {@code caller} lookup object, or {@code null}. * - *

Classes can be created with class data by calling - * {@link Lookup#defineHiddenClassWithClassData(byte[], Object, Lookup.ClassOption...) + *

A hidden class with class data can be created by calling + * {@link Lookup#defineHiddenClassWithClassData(byte[], Object, boolean, Lookup.ClassOption...) * Lookup::defineHiddenClassWithClassData}. - * A hidden class with a class data behaves as if the hidden class - * has a private static final unnamed field pre-initialized with - * the class data and this method is equivalent as if calling - * {@link ConstantBootstraps#getStaticFinal(Lookup, String, Class)} to - * obtain the value of such field corresponding to the class data. + * This method will cause the static class initializer of the lookup + * class of the given {@code caller} lookup object be executed if + * it has not been initialized. + * + *

A hidden class created by {@link Lookup#defineHiddenClass(byte[], boolean, Lookup.ClassOption...) + * Lookup::defineHiddenClass} and non-hidden classes have no class data. + * {@code null} is returned if this method is called on the lookup object + * on these classes. * *

The {@linkplain Lookup#lookupModes() lookup modes} for this lookup - * must have {@link Lookup#ORIGINAL ORIGINAL} access in order to retrieve - * the class data. + * must have {@linkplain Lookup#ORIGINAL original access} + * in order to retrieve the class data. + * + * @apiNote + * This method can be called as a bootstrap method for a dynamically computed + * constant. A framework can create a hidden class with class data, for + * example that can be {@code Class} or {@code MethodHandle} object. + * The class data is accessible only to the lookup object + * created by the original caller but inaccessible to other members + * in the same nest. If a framework passes security sensitive objects + * to a hidden class via class data, it is recommended to load the value + * of class data as a dynamically computed constant instead of storing + * the class data in private static field(s) which are accessible to + * other nestmates. + * + * @param the type to cast the class data object to + * @param caller the lookup context describing the class performing the + * operation (normally stacked by the JVM) + * @param name must be {@link ConstantDescs#DEFAULT_NAME} + * ({@code "_"}) + * @param type the type of the class data + * @return the value of the class data if present in the lookup class; + * otherwise {@code null} + * @throws IllegalArgumentException if name is not {@code "_"} + * @throws IllegalAccessException if the lookup context does not have + * {@linkplain Lookup#ORIGINAL original} access + * @throws ClassCastException if the class data cannot be converted to + * the given {@code type} + * @throws NullPointerException if {@code caller} or {@code type} argument + * is {@code null} + * @see Lookup#defineHiddenClassWithClassData(byte[], Object, boolean, Lookup.ClassOption...) + * @see MethodHandles#classDataAt(Lookup, String, Class, int) + * @since 16 + * @jvms 5.5 Initialization + */ + public static T classData(Lookup caller, String name, Class type) throws IllegalAccessException { + Objects.requireNonNull(caller); + Objects.requireNonNull(type); + if (!ConstantDescs.DEFAULT_NAME.equals(name)) { + throw new IllegalArgumentException("name must be \"_\": " + name); + } + + if ((caller.lookupModes() & Lookup.ORIGINAL) != Lookup.ORIGINAL) { + throw new IllegalAccessException(caller + " does not have ORIGINAL access"); + } + + Object classdata = MethodHandleNatives.classData(caller.lookupClass()); + if (classdata == null) return null; + + try { + return BootstrapMethodInvoker.widenAndCast(classdata, type); + } catch (RuntimeException|Error e) { + throw e; // let CCE and other runtime exceptions through + } catch (Throwable e) { + throw new InternalError(e); + } + } + + /** + * Returns the element at the specified index in the + * {@linkplain #classData(Lookup, String, Class) class data}, + * if the class data associated with the lookup class + * of the given {@code caller} lookup object is a {@code List}. + * If the class data is not present in this lookup class, this method + * returns {@code null}. + * + *

A hidden class with class data can be created by calling + * {@link Lookup#defineHiddenClassWithClassData(byte[], Object, boolean, Lookup.ClassOption...) + * Lookup::defineHiddenClassWithClassData}. + * This method will cause the static class initializer of the lookup + * class of the given {@code caller} lookup object be executed if + * it has not been initialized. + * + *

A hidden class created by {@link Lookup#defineHiddenClass(byte[], boolean, Lookup.ClassOption...) + * Lookup::defineHiddenClass} and non-hidden classes have no class data. + * {@code null} is returned if this method is called on the lookup object + * on these classes. + * + *

The {@linkplain Lookup#lookupModes() lookup modes} for this lookup + * must have {@linkplain Lookup#ORIGINAL original access} + * in order to retrieve the class data. * * @apiNote * This method can be called as a bootstrap method for a dynamically computed * constant. A framework can create a hidden class with class data, for * example that can be {@code List.of(o1, o2, o3....)} containing more than - * one live object. The class data is accessible only to the lookup object + * one object and use this method to load one element at a specific index. + * The class data is accessible only to the lookup object * created by the original caller but inaccessible to other members - * in the same nest. If a framework passes security sensitive live objects + * in the same nest. If a framework passes security sensitive objects * to a hidden class via class data, it is recommended to load the value * of class data as a dynamically computed constant instead of storing - * the live objects in private fields which are accessible to other + * the class data in private static field(s) which are accessible to other * nestmates. * - * @param the type to cast the class data object to + * @param the type to cast the result object to * @param caller the lookup context describing the class performing the * operation (normally stacked by the JVM) - * @param name ignored - * @param type the type of the class data - * @return the value of the class data if present in the lookup class; - * otherwise {@code null} + * @param name must be {@link java.lang.constant.ConstantDescs#DEFAULT_NAME} + * ({@code "_"}) + * @param type the type of the element at the given index in the class data + * @param index index of the element in the class data + * @return the element at the given index in the class data + * if the class data is present; otherwise {@code null} + * @throws IllegalArgumentException if name is not {@code "_"} * @throws IllegalAccessException if the lookup context does not have - * original caller access - * @throws ClassCastException if the class data cannot be converted to - * the specified {@code type} - * @see Lookup#defineHiddenClassWithClassData(byte[], Object, Lookup.ClassOption...) - * @since 15 + * {@linkplain Lookup#ORIGINAL original} access + * @throws ClassCastException if the class data cannot be converted to {@code List} + * or the element at the specified index cannot be converted to the given type + * @throws IndexOutOfBoundsException if the index is out of range + * @throws NullPointerException if {@code caller} or {@code type} argument is + * {@code null}; or if unboxing operation fails because + * the element at the given index is {@code null} + * + * @since 16 + * @see #classData(Lookup, String, Class) + * @see Lookup#defineHiddenClassWithClassData(byte[], Object, boolean, Lookup.ClassOption...) */ - static T classData(Lookup caller, String name, Class type) throws IllegalAccessException { - if (!caller.hasFullPrivilegeAccess()) { - throw new IllegalAccessException(caller + " does not have full privilege access"); + public static T classDataAt(Lookup caller, String name, Class type, int index) + throws IllegalAccessException + { + @SuppressWarnings("unchecked") + List classdata = (List)classData(caller, name, List.class); + if (classdata == null) return null; + + try { + Object element = classdata.get(index); + return BootstrapMethodInvoker.widenAndCast(element, type); + } catch (RuntimeException|Error e) { + throw e; // let specified exceptions and other runtime exceptions/errors through + } catch (Throwable e) { + throw new InternalError(e); } - Object classData = MethodHandleNatives.classData(caller.lookupClass); - return type.cast(classData); } /** @@ -636,11 +743,17 @@ public class MethodHandles { *

* Private and module access are independently determined modes; a lookup may have * either or both or neither. A lookup which possesses both access modes is said to - * possess {@linkplain #hasFullPrivilegeAccess() full privilege access}. Such a lookup has - * the following additional capability: + * possess {@linkplain #hasFullPrivilegeAccess() full privilege access}. + *

+ * A lookup with original access ensures that this lookup is created by + * the original lookup class and the bootstrap method invoked by the VM. + * Such a lookup with original access also has private and module access + * which has the following additional capability: *

    *
  • create method handles which invoke caller sensitive methods, * such as {@code Class.forName} + *
  • obtain the {@linkplain MethodHandles#classData(Lookup, String, Class) + * class data} associated with the lookup class
  • *
*

* Each of these permissions is a consequence of the fact that a lookup object @@ -811,6 +924,7 @@ public class MethodHandles { * * * Lookup object + * original * protected * private * package @@ -821,6 +935,7 @@ public class MethodHandles { * * * {@code CL = MethodHandles.lookup()} in {@code C} + * ORI * PRO * PRI * PAC @@ -831,6 +946,7 @@ public class MethodHandles { * {@code CL.in(C1)} same package * * + * * PAC * MOD * 1R @@ -840,6 +956,7 @@ public class MethodHandles { * * * + * * MOD * 1R * @@ -849,6 +966,7 @@ public class MethodHandles { * * * + * * 2R * * @@ -857,10 +975,12 @@ public class MethodHandles { * * * + * * 2R * * * {@code PRI1 = privateLookupIn(C1,CL)} + * * PRO * PRI * PAC @@ -869,6 +989,7 @@ public class MethodHandles { * * * {@code PRI1a = privateLookupIn(C,PRI1)} + * * PRO * PRI * PAC @@ -879,6 +1000,7 @@ public class MethodHandles { * {@code PRI1.in(C1)} same package * * + * * PAC * MOD * 1R @@ -888,6 +1010,7 @@ public class MethodHandles { * * * + * * MOD * 1R * @@ -897,11 +1020,13 @@ public class MethodHandles { * * * + * * 2R * * * {@code PRI1.dropLookupMode(PROTECTED)} * + * * PRI * PAC * MOD @@ -911,6 +1036,7 @@ public class MethodHandles { * {@code PRI1.dropLookupMode(PRIVATE)} * * + * * PAC * MOD * 1R @@ -920,6 +1046,7 @@ public class MethodHandles { * * * + * * MOD * 1R * @@ -929,6 +1056,7 @@ public class MethodHandles { * * * + * * 1R * * @@ -937,9 +1065,11 @@ public class MethodHandles { * * * + * * none * * {@code PRI2 = privateLookupIn(D,CL)} + * * PRO * PRI * PAC @@ -948,6 +1078,7 @@ public class MethodHandles { * * * {@code privateLookupIn(D,PRI1)} + * * PRO * PRI * PAC @@ -960,12 +1091,14 @@ public class MethodHandles { * * * + * * IAE * * * {@code PRI2.in(D2)} same package * * + * * PAC * * 2R @@ -976,6 +1109,7 @@ public class MethodHandles { * * * + * * 2R * * @@ -984,6 +1118,7 @@ public class MethodHandles { * * * + * * 2R * * @@ -992,11 +1127,13 @@ public class MethodHandles { * * * + * * none * * * {@code PRI2.dropLookupMode(PROTECTED)} * + * * PRI * PAC * @@ -1006,6 +1143,7 @@ public class MethodHandles { * {@code PRI2.dropLookupMode(PRIVATE)} * * + * * PAC * * 2R @@ -1016,6 +1154,7 @@ public class MethodHandles { * * * + * * 2R * * @@ -1024,6 +1163,7 @@ public class MethodHandles { * * * + * * 2R * * @@ -1032,11 +1172,13 @@ public class MethodHandles { * * * + * * none * * * {@code CL.dropLookupMode(PROTECTED)} * + * * PRI * PAC * MOD @@ -1046,6 +1188,7 @@ public class MethodHandles { * {@code CL.dropLookupMode(PRIVATE)} * * + * * PAC * MOD * 1R @@ -1055,6 +1198,7 @@ public class MethodHandles { * * * + * * MOD * 1R * @@ -1064,6 +1208,7 @@ public class MethodHandles { * * * + * * 1R * * @@ -1072,6 +1217,7 @@ public class MethodHandles { * * * + * * none * * @@ -1080,6 +1226,7 @@ public class MethodHandles { * * * + * * U * * @@ -1088,6 +1235,7 @@ public class MethodHandles { * * * + * * U * * @@ -1096,6 +1244,7 @@ public class MethodHandles { * * * + * * U * * @@ -1104,6 +1253,7 @@ public class MethodHandles { * * * + * * none * * @@ -1112,6 +1262,7 @@ public class MethodHandles { * * * + * * IAE * * @@ -1120,6 +1271,7 @@ public class MethodHandles { * * * + * * none * * @@ -1132,7 +1284,8 @@ public class MethodHandles { * but {@code D} and {@code D2} are in module {@code M2}, and {@code E} * is in module {@code M3}. {@code X} stands for class which is inaccessible * to the lookup. {@code ANY} stands for any of the example lookups. - *

  • {@code PRO} indicates {@link #PROTECTED} bit set, + *
  • {@code ORI} indicates {@link #ORIGINAL} bit set, + * {@code PRO} indicates {@link #PROTECTED} bit set, * {@code PRI} indicates {@link #PRIVATE} bit set, * {@code PAC} indicates {@link #PACKAGE} bit set, * {@code MOD} indicates {@link #MODULE} bit set, @@ -1217,7 +1370,10 @@ public class MethodHandles { *

    * If a security manager is present and the current lookup object does not have * {@linkplain #hasFullPrivilegeAccess() full privilege access}, then - * {@link #defineClass(byte[]) defineClass} + * {@link #defineClass(byte[]) defineClass}, + * {@link #defineHiddenClass(byte[], boolean, ClassOption...) defineHiddenClass}, + * {@link #defineHiddenClassWithClassData(byte[], Object, boolean, ClassOption...) + * defineHiddenClassWithClassData} * calls {@link SecurityManager#checkPermission smgr.checkPermission} * with {@code RuntimePermission("defineClass")}. * @@ -1240,7 +1396,7 @@ public class MethodHandles { * In cases where the lookup object is * {@link MethodHandles#publicLookup() publicLookup()}, * or some other lookup object without the - * {@linkplain #hasFullPrivilegeAccess() full privilege access}, + * {@linkplain #ORIGINAL original access}, * the lookup class is disregarded. * In such cases, no caller-sensitive method handle can be created, * access is forbidden, and the lookup fails with an @@ -1351,16 +1507,32 @@ public class MethodHandles { */ public static final int UNCONDITIONAL = PACKAGE << 2; - private static final int ALL_MODES = (PUBLIC | PRIVATE | PROTECTED | PACKAGE | MODULE | UNCONDITIONAL); - private static final int FULL_POWER_MODES = (ALL_MODES & ~UNCONDITIONAL); + /** A single-bit mask representing {@code original} access + * which may contribute to the result of {@link #lookupModes lookupModes}. + * The value is {@code 0x40}, which does not correspond meaningfully to + * any particular {@linkplain java.lang.reflect.Modifier modifier bit}. + * + *

    + * If this lookup mode is set, the {@code Lookup} object must be + * created by the original lookup class by calling + * {@link MethodHandles#lookup()} method or by a bootstrap method + * invoked by the VM. The {@code Lookup} object with this lookup + * mode has {@linkplain #hasFullPrivilegeAccess() full privilege access}. + * + * @since 16 + */ + public static final int ORIGINAL = PACKAGE << 3; + + private static final int ALL_MODES = (PUBLIC | PRIVATE | PROTECTED | PACKAGE | MODULE | UNCONDITIONAL | ORIGINAL); + private static final int FULL_POWER_MODES = (ALL_MODES & ~UNCONDITIONAL); // with original access private static final int TRUSTED = -1; /* - * Adjust PUBLIC => PUBLIC|MODULE|UNCONDITIONAL + * Adjust PUBLIC => PUBLIC|MODULE|ORIGINAL|UNCONDITIONAL * Adjust 0 => PACKAGE */ private static int fixmods(int mods) { - mods &= (ALL_MODES - PACKAGE - MODULE - UNCONDITIONAL); + mods &= (ALL_MODES - PACKAGE - MODULE - ORIGINAL - UNCONDITIONAL); if (Modifier.isPublic(mods)) mods |= UNCONDITIONAL; return (mods != 0) ? mods : PACKAGE; @@ -1416,7 +1588,8 @@ public class MethodHandles { * {@linkplain #PROTECTED PROTECTED (0x04)}, * {@linkplain #PACKAGE PACKAGE (0x08)}, * {@linkplain #MODULE MODULE (0x10)}, - * and {@linkplain #UNCONDITIONAL UNCONDITIONAL (0x20)}. + * {@linkplain #UNCONDITIONAL UNCONDITIONAL (0x20)}, + * and {@linkplain #ORIGINAL ORIGINAL (0x40)}. *

    * A freshly-created lookup object * on the {@linkplain java.lang.invoke.MethodHandles#lookup() caller's class} has @@ -1473,6 +1646,8 @@ public class MethodHandles { * However, the resulting {@code Lookup} object is guaranteed * to have no more access capabilities than the original. * In particular, access capabilities can be lost as follows:

      + *
    • If the new lookup class is different from the old lookup class, + * i.e. {@link #ORIGINAL ORIGINAL} access is lost. *
    • If the new lookup class is in a different module from the old one, * i.e. {@link #MODULE MODULE} access is lost. *
    • If the new lookup class is in a different package @@ -1528,7 +1703,7 @@ public class MethodHandles { return new Lookup(requestedLookupClass, null, FULL_POWER_MODES); if (requestedLookupClass == this.lookupClass) return this; // keep same capabilities - int newModes = (allowedModes & FULL_POWER_MODES); + int newModes = (allowedModes & FULL_POWER_MODES) & ~ORIGINAL; Module fromModule = this.lookupClass.getModule(); Module targetModule = requestedLookupClass.getModule(); Class plc = this.previousLookupClass(); @@ -1569,7 +1744,8 @@ public class MethodHandles { * finds members, but with a lookup mode that has lost the given lookup mode. * The lookup mode to drop is one of {@link #PUBLIC PUBLIC}, {@link #MODULE * MODULE}, {@link #PACKAGE PACKAGE}, {@link #PROTECTED PROTECTED}, - * {@link #PRIVATE PRIVATE}, or {@link #UNCONDITIONAL UNCONDITIONAL}. + * {@link #PRIVATE PRIVATE}, {@link #ORIGINAL ORIGINAL}, or + * {@link #UNCONDITIONAL UNCONDITIONAL}. * *

      If this lookup is a {@linkplain MethodHandles#publicLookup() public lookup}, * this lookup has {@code UNCONDITIONAL} mode set and it has no other mode set. @@ -1578,8 +1754,9 @@ public class MethodHandles { * *

      If this lookup is not a public lookup, then the following applies * regardless of its {@linkplain #lookupModes() lookup modes}. - * {@link #PROTECTED PROTECTED} is always dropped and so the resulting lookup - * mode will never have this access capability. When dropping {@code PACKAGE} + * {@link #PROTECTED PROTECTED} and {@link #ORIGINAL ORIGINAL} are always + * dropped and so the resulting lookup mode will never have these access + * capabilities. When dropping {@code PACKAGE} * then the resulting lookup will not have {@code PACKAGE} or {@code PRIVATE} * access. When dropping {@code MODULE} then the resulting lookup will not * have {@code MODULE}, {@code PACKAGE}, or {@code PRIVATE} access. @@ -1600,19 +1777,21 @@ public class MethodHandles { * @param modeToDrop the lookup mode to drop * @return a lookup object which lacks the indicated mode, or the same object if there is no change * @throws IllegalArgumentException if {@code modeToDrop} is not one of {@code PUBLIC}, - * {@code MODULE}, {@code PACKAGE}, {@code PROTECTED}, {@code PRIVATE} or {@code UNCONDITIONAL} + * {@code MODULE}, {@code PACKAGE}, {@code PROTECTED}, {@code PRIVATE}, {@code ORIGINAL} + * or {@code UNCONDITIONAL} * @see MethodHandles#privateLookupIn * @since 9 */ public Lookup dropLookupMode(int modeToDrop) { int oldModes = lookupModes(); - int newModes = oldModes & ~(modeToDrop | PROTECTED); + int newModes = oldModes & ~(modeToDrop | PROTECTED | ORIGINAL); switch (modeToDrop) { case PUBLIC: newModes &= ~(FULL_POWER_MODES); break; case MODULE: newModes &= ~(PACKAGE | PRIVATE); break; case PACKAGE: newModes &= ~(PRIVATE); break; case PROTECTED: case PRIVATE: + case ORIGINAL: case UNCONDITIONAL: break; default: throw new IllegalArgumentException(modeToDrop + " is not a valid mode to drop"); } @@ -1905,7 +2084,9 @@ public class MethodHandles { * The Java Virtual Machine Specification. * @param initialize if {@code true} the class will be initialized. * @param options {@linkplain ClassOption class options} - * @return the {@code Lookup} object on the hidden class + * @return the {@code Lookup} object on the hidden class, + * with {@linkplain #ORIGINAL original} and + * {@linkplain Lookup#hasFullPrivilegeAccess() full privilege} access * * @throws IllegalAccessException if this {@code Lookup} does not have * {@linkplain #hasFullPrivilegeAccess() full privilege} access @@ -1958,19 +2139,37 @@ public class MethodHandles { * returning a {@code Lookup} on the newly created class or interface. * *

      This method is equivalent to calling - * {@link #defineHiddenClass(byte[], boolean, ClassOption...) defineHiddenClass(bytes, true, options)} - * as if the hidden class has a private static final unnamed field whose value - * is initialized to {@code classData} right before the class initializer is - * executed. The newly created class is linked and initialized by the Java - * Virtual Machine. + * {@link #defineHiddenClass(byte[], boolean, ClassOption...) defineHiddenClass(bytes, initialize, options)} + * as if the hidden class is injected with a private static final unnamed + * field which is initialized with the given {@code classData} at + * the first instruction of the class initializer. + * The newly created class is linked by the Java Virtual Machine. * *

      The {@link MethodHandles#classData(Lookup, String, Class) MethodHandles::classData} - * method can be used to retrieve the {@code classData}. + * and {@link MethodHandles#classDataAt(Lookup, String, Class, int) MethodHandles::classDataAt} + * methods can be used to retrieve the {@code classData}. + * + * @apiNote + * A framework can create a hidden class with class data with one or more + * objects and load the class data as dynamically-computed constant(s) + * via a bootstrap method. {@link MethodHandles#classData(Lookup, String, Class) + * Class data} is accessible only to the lookup object created by the newly + * defined hidden class but inaccessible to other members in the same nest + * (unlike private static fields that are accessible to nestmates). + * Care should be taken w.r.t. mutability for example when passing + * an array or other mutable structure through the class data. + * Changing any value stored in the class data at runtime may lead to + * unpredictable behavior. + * If the class data is a {@code List}, it is good practice to make it + * unmodifiable for example via {@link List#of List::of}. * * @param bytes the class bytes * @param classData pre-initialized class data + * @param initialize if {@code true} the class will be initialized. * @param options {@linkplain ClassOption class options} - * @return the {@code Lookup} object on the hidden class + * @return the {@code Lookup} object on the hidden class, + * with {@linkplain #ORIGINAL original} and + * {@linkplain Lookup#hasFullPrivilegeAccess() full privilege} access * * @throws IllegalAccessException if this {@code Lookup} does not have * {@linkplain #hasFullPrivilegeAccess() full privilege} access @@ -1990,11 +2189,23 @@ public class MethodHandles { * @throws LinkageError if the newly created class cannot be linked for any other reason * @throws NullPointerException if any parameter is {@code null} * - * @since 15 + * @since 16 * @see Lookup#defineHiddenClass(byte[], boolean, ClassOption...) * @see Class#isHidden() + * @see MethodHandles#classData(Lookup, String, Class) + * @see MethodHandles#classDataAt(Lookup, String, Class, int) + * @jvms 4.2.1 Binary Class and Interface Names + * @jvms 4.2.2 Unqualified Names + * @jvms 4.7.28 The {@code NestHost} Attribute + * @jvms 4.7.29 The {@code NestMembers} Attribute + * @jvms 5.4.3.1 Class and Interface Resolution + * @jvms 5.4.4 Access Control + * @jvms 5.3.5 Deriving a {@code Class} from a {@code class} File Representation + * @jvms 5.4 Linking + * @jvms 5.5 Initialization + * @jls 12.7 Unloading of Classes and Interface */ - /* package-private */ Lookup defineHiddenClassWithClassData(byte[] bytes, Object classData, ClassOption... options) + public Lookup defineHiddenClassWithClassData(byte[] bytes, Object classData, boolean initialize, ClassOption... options) throws IllegalAccessException { Objects.requireNonNull(bytes); @@ -2007,7 +2218,7 @@ public class MethodHandles { } return makeHiddenClassDefiner(bytes.clone(), Set.of(options), false) - .defineClassAsLookup(true, classData); + .defineClassAsLookup(initialize, classData); } static class ClassFile { @@ -2238,8 +2449,6 @@ public class MethodHandles { } Lookup defineClassAsLookup(boolean initialize, Object classData) { - // initialize must be true if classData is non-null - assert classData == null || initialize == true; Class c = defineClass(initialize, classData); return new Lookup(c, null, FULL_POWER_MODES); } @@ -2295,7 +2504,8 @@ public class MethodHandles { *

    • If public and package access are allowed, the suffix is "/package". *
    • If public, package, and private access are allowed, the suffix is "/private". *
    - * If none of the above cases apply, it is the case that full access + * If none of the above cases apply, it is the case that + * {@linkplain #hasFullPrivilegeAccess() full privilege access} * (public, module, package, private, and protected) is allowed. * In this case, no suffix is added. * This is true only of an object obtained originally from @@ -2329,12 +2539,13 @@ public class MethodHandles { case PUBLIC|PACKAGE: case PUBLIC|MODULE|PACKAGE: return cname + "/package"; - case FULL_POWER_MODES & (~PROTECTED): - case FULL_POWER_MODES & ~(PROTECTED|MODULE): - return cname + "/private"; + case PUBLIC|PACKAGE|PRIVATE: + case PUBLIC|MODULE|PACKAGE|PRIVATE: + return cname + "/private"; + case PUBLIC|PACKAGE|PRIVATE|PROTECTED: + case PUBLIC|MODULE|PACKAGE|PRIVATE|PROTECTED: case FULL_POWER_MODES: - case FULL_POWER_MODES & (~MODULE): - return cname; + return cname; case TRUSTED: return "/trusted"; // internal only; not exported default: // Should not happen, but it's a bitfield... @@ -3415,7 +3626,7 @@ return mh1; } if (allowedModes != TRUSTED && member.isCallerSensitive()) { Class callerClass = target.internalCallerClass(); - if (!hasFullPrivilegeAccess() || callerClass != lookupClass()) + if ((lookupModes() & ORIGINAL) == 0 || callerClass != lookupClass()) throw new IllegalArgumentException("method handle is caller sensitive: "+callerClass); } // Produce the handle to the results. @@ -3480,11 +3691,11 @@ return mh1; /** * Find my trustable caller class if m is a caller sensitive method. - * If this lookup object has full privilege access, then the caller class is the lookupClass. + * If this lookup object has original full privilege access, then the caller class is the lookupClass. * Otherwise, if m is caller-sensitive, throw IllegalAccessException. */ Lookup findBoundCallerLookup(MemberName m) throws IllegalAccessException { - if (MethodHandleNatives.isCallerSensitive(m) && !hasFullPrivilegeAccess()) { + if (MethodHandleNatives.isCallerSensitive(m) && (lookupModes() & ORIGINAL) == 0) { // Only lookups with full privilege access are allowed to resolve caller-sensitive methods throw new IllegalAccessException("Attempt to lookup caller-sensitive method using restricted lookup object"); } @@ -3510,7 +3721,8 @@ return mh1; * Returns {@code true} if this lookup has full privilege access, * i.e. {@code PRIVATE} and {@code MODULE} access. * A {@code Lookup} object must have full privilege access in order to - * access all members that are allowed to the {@linkplain #lookupClass() lookup class}. + * access all members that are allowed to the + * {@linkplain #lookupClass() lookup class}. * * @return {@code true} if this lookup has full privilege access. * @since 14 @@ -3531,14 +3743,14 @@ return mh1; if (smgr == null) return; // Step 1: - boolean fullPowerLookup = hasFullPrivilegeAccess(); - if (!fullPowerLookup || + boolean fullPrivilegeLookup = hasFullPrivilegeAccess(); + if (!fullPrivilegeLookup || !VerifyAccess.classLoaderIsAncestor(lookupClass, refc)) { ReflectUtil.checkPackageAccess(refc); } // Step 2b: - if (!fullPowerLookup) { + if (!fullPrivilegeLookup) { smgr.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION); } } @@ -3546,7 +3758,11 @@ return mh1; /** * Perform steps 1, 2a and 3 access checks. * Determines a trustable caller class to compare with refc, the symbolic reference class. - * If this lookup object has full privilege access, then the caller class is the lookupClass. + * If this lookup object has full privilege access except original access, + * then the caller class is the lookupClass. + * + * Lookup object created by {@link MethodHandles#privateLookupIn(Class, Lookup)} + * from the same module skips the security permission check. */ void checkSecurityManager(Class refc, MemberName m) { Objects.requireNonNull(refc); @@ -3558,21 +3774,21 @@ return mh1; if (smgr == null) return; // Step 1: - boolean fullPowerLookup = hasFullPrivilegeAccess(); - if (!fullPowerLookup || + boolean fullPrivilegeLookup = hasFullPrivilegeAccess(); + if (!fullPrivilegeLookup || !VerifyAccess.classLoaderIsAncestor(lookupClass, refc)) { ReflectUtil.checkPackageAccess(refc); } // Step 2a: if (m.isPublic()) return; - if (!fullPowerLookup) { + if (!fullPrivilegeLookup) { smgr.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION); } // Step 3: Class defc = m.getDeclaringClass(); - if (!fullPowerLookup && defc != refc) { + if (!fullPrivilegeLookup && defc != refc) { ReflectUtil.checkPackageAccess(defc); } } @@ -3787,9 +4003,11 @@ return mh1; // boundCaller must have full privilege access. // It should have been checked by findBoundCallerLookup. Safe to check this again. - if (!boundCaller.hasFullPrivilegeAccess()) + if ((boundCaller.lookupModes() & ORIGINAL) == 0) throw new IllegalAccessException("Attempt to lookup caller-sensitive method using restricted lookup object"); + assert boundCaller.hasFullPrivilegeAccess(); + MethodHandle cbmh = MethodHandleImpl.bindCaller(mh, boundCaller.lookupClass); // Note: caller will apply varargs after this step happens. return cbmh; diff --git a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java index de3e25b69b1..98c8b8971a5 100644 --- a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java +++ b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java @@ -1345,6 +1345,7 @@ public final class Unsafe { * @param data bytes of a class file * @param cpPatches where non-null entries exist, they replace corresponding CP entries in data */ + @Deprecated(since = "15", forRemoval = true) public Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches) { if (hostClass == null || data == null) { throw new NullPointerException(); diff --git a/src/java.base/share/classes/sun/invoke/util/VerifyAccess.java b/src/java.base/share/classes/sun/invoke/util/VerifyAccess.java index ed33151ad44..eab3afae080 100644 --- a/src/java.base/share/classes/sun/invoke/util/VerifyAccess.java +++ b/src/java.base/share/classes/sun/invoke/util/VerifyAccess.java @@ -39,6 +39,7 @@ public class VerifyAccess { private VerifyAccess() { } // cannot instantiate private static final int UNCONDITIONAL_ALLOWED = java.lang.invoke.MethodHandles.Lookup.UNCONDITIONAL; + private static final int ORIGINAL_ALLOWED = java.lang.invoke.MethodHandles.Lookup.ORIGINAL; private static final int MODULE_ALLOWED = java.lang.invoke.MethodHandles.Lookup.MODULE; private static final int PACKAGE_ONLY = 0; private static final int PACKAGE_ALLOWED = java.lang.invoke.MethodHandles.Lookup.PACKAGE; @@ -99,7 +100,7 @@ public class VerifyAccess { Class prevLookupClass, int allowedModes) { if (allowedModes == 0) return false; - assert((allowedModes & ~(ALL_ACCESS_MODES|PACKAGE_ALLOWED|MODULE_ALLOWED|UNCONDITIONAL_ALLOWED)) == 0); + assert((allowedModes & ~(ALL_ACCESS_MODES|PACKAGE_ALLOWED|MODULE_ALLOWED|UNCONDITIONAL_ALLOWED|ORIGINAL_ALLOWED)) == 0); // The symbolic reference class (refc) must always be fully verified. if (!isClassAccessible(refc, lookupClass, prevLookupClass, allowedModes)) { return false; @@ -189,7 +190,7 @@ public class VerifyAccess { Class prevLookupClass, int allowedModes) { if (allowedModes == 0) return false; - assert((allowedModes & ~(ALL_ACCESS_MODES|PACKAGE_ALLOWED|MODULE_ALLOWED|UNCONDITIONAL_ALLOWED)) == 0); + assert((allowedModes & ~(ALL_ACCESS_MODES|PACKAGE_ALLOWED|MODULE_ALLOWED|UNCONDITIONAL_ALLOWED|ORIGINAL_ALLOWED)) == 0); if ((allowedModes & PACKAGE_ALLOWED) != 0 && isSamePackage(lookupClass, refc)) diff --git a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java index e62f6338a50..bc29a0f7b88 100644 --- a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java +++ b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java @@ -867,15 +867,17 @@ public final class Unsafe { *
  • InterfaceMethodRef: (NYI) a method handle to invoke on that call site's arguments * * - * @deprecated Use the {@link java.lang.invoke.MethodHandles.Lookup#defineHiddenClass(byte[], boolean, MethodHandles.Lookup.ClassOption...)} - * method. + * @deprecated Use {@link java.lang.invoke.MethodHandles.Lookup#defineHiddenClass(byte[], boolean, MethodHandles.Lookup.ClassOption...)} + * or {@link java.lang.invoke.MethodHandles.Lookup#defineHiddenClassWithClassData(byte[], Object, boolean, MethodHandles.Lookup.ClassOption...)} + * instead. * * @param hostClass context for linkage, access control, protection domain, and class loader * @param data bytes of a class file * @param cpPatches where non-null entries exist, they replace corresponding CP entries in data */ @ForceInline - @Deprecated(since = "15", forRemoval = false) + @Deprecated(since = "15", forRemoval = true) + @SuppressWarnings("removal") public Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches) { return theInternalUnsafe.defineAnonymousClass(hostClass, data, cpPatches); } diff --git a/test/jdk/java/lang/invoke/AccessControlTest.java b/test/jdk/java/lang/invoke/AccessControlTest.java index c6be6179192..8555011e8b5 100644 --- a/test/jdk/java/lang/invoke/AccessControlTest.java +++ b/test/jdk/java/lang/invoke/AccessControlTest.java @@ -148,7 +148,8 @@ public class AccessControlTest { || lookupModes == (PUBLIC|MODULE|PACKAGE|PRIVATE)) suffix = "/private"; else if (lookupModes == (PUBLIC|PACKAGE|PRIVATE|PROTECTED) - || lookupModes == (PUBLIC|MODULE|PACKAGE|PRIVATE|PROTECTED)) + || lookupModes == (PUBLIC|MODULE|PACKAGE|PRIVATE|PROTECTED) + || lookupModes == (PUBLIC|MODULE|PACKAGE|PRIVATE|PROTECTED|ORIGINAL)) suffix = ""; else suffix = "/#"+Integer.toHexString(lookupModes); @@ -160,9 +161,11 @@ public class AccessControlTest { * Creates a lookup on the specified new lookup class. * [A1] The resulting object will report the specified * class as its own {@link #lookupClass lookupClass}. - * [A2] However, the resulting {@code Lookup} object is guaranteed + * [A1-a] However, the resulting {@code Lookup} object is guaranteed * to have no more access capabilities than the original. * In particular, access capabilities can be lost as follows:
      + * [A2] If the new lookup class is not the same as the old lookup class, + * then {@link #ORIGINAL ORIGINAL} access is lost. * [A3] If the new lookup class is in a different module from the old one, * i.e. {@link #MODULE MODULE} access is lost. * [A4] If the new lookup class is in a different package @@ -227,11 +230,14 @@ public class AccessControlTest { } if (!accessible) { // no access to c2; lose all access. - changed |= (PUBLIC|MODULE|PACKAGE|PRIVATE|PROTECTED|UNCONDITIONAL); // [A6] + changed |= (PUBLIC|MODULE|PACKAGE|PRIVATE|PROTECTED|UNCONDITIONAL); // [A7] + } + if (!sameClass) { + changed |= ORIGINAL; // [A2] } if (m2 != m1 && m0 != m1) { // hop to a third module; lose all access - changed |= (PUBLIC|MODULE|PACKAGE|PRIVATE|PROTECTED); // [A7] + changed |= (PUBLIC|MODULE|PACKAGE|PRIVATE|PROTECTED); // [A8] } if (!sameModule) { changed |= MODULE; // [A3] @@ -254,20 +260,21 @@ public class AccessControlTest { if ((modes1 & UNCONDITIONAL) != 0) plc = null; // [A8] LookupCase l2 = new LookupCase(c2, plc, modes2); assert(l2.lookupClass() == c2); // [A1] - assert((modes1 | modes2) == modes1); // [A2] (no elevation of access) + assert((modes1 | modes2) == modes1); // [A1-a] (no elevation of access) assert(l2.prevLookupClass() == null || (modes2 & MODULE) == 0); return l2; } LookupCase dropLookupMode(int modeToDrop) { int oldModes = lookupModes(); - int newModes = oldModes & ~(modeToDrop | PROTECTED); + int newModes = oldModes & ~(modeToDrop | PROTECTED | ORIGINAL); switch (modeToDrop) { case PUBLIC: newModes &= ~(MODULE|PACKAGE|PROTECTED|PRIVATE); break; case MODULE: newModes &= ~(PACKAGE|PRIVATE); break; case PACKAGE: newModes &= ~(PRIVATE); break; case PROTECTED: case PRIVATE: + case ORIGINAL: case UNCONDITIONAL: break; default: throw new IllegalArgumentException(modeToDrop + " is not a valid mode to drop"); } diff --git a/test/jdk/java/lang/invoke/CallerSensitiveAccess.java b/test/jdk/java/lang/invoke/CallerSensitiveAccess.java index a8aa649059e..894c7413805 100644 --- a/test/jdk/java/lang/invoke/CallerSensitiveAccess.java +++ b/test/jdk/java/lang/invoke/CallerSensitiveAccess.java @@ -117,6 +117,37 @@ public class CallerSensitiveAccess { MethodHandles.publicLookup().unreflect(method); } + /** + * Using a Lookup with no original access that can't lookup caller-sensitive + * method + */ + @Test(dataProvider = "callerSensitiveMethods", + expectedExceptions = IllegalAccessException.class) + public void testLookupNoOriginalAccessFind(@NoInjection Method method, String desc) throws Exception { + Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.ORIGINAL); + assertTrue(lookup.hasFullPrivilegeAccess()); + Class refc = method.getDeclaringClass(); + String name = method.getName(); + MethodType mt = MethodType.methodType(method.getReturnType(), method.getParameterTypes()); + if (Modifier.isStatic(method.getModifiers())) { + lookup.findStatic(refc, name, mt); + } else { + lookup.findVirtual(refc, name, mt); + } + } + + /** + * Using a Lookup with no original access that can't unreflect caller-sensitive + * method + */ + @Test(dataProvider = "callerSensitiveMethods", + expectedExceptions = IllegalAccessException.class) + public void testLookupNoOriginalAccessUnreflect(@NoInjection Method method, String desc) throws Exception { + Lookup lookup = MethodHandles.lookup().dropLookupMode(Lookup.ORIGINAL); + assertTrue(lookup.hasFullPrivilegeAccess()); + lookup.unreflect(method); + } + // -- Test method handles to setAccessible -- private int aField; diff --git a/test/jdk/java/lang/invoke/DropLookupModeTest.java b/test/jdk/java/lang/invoke/DropLookupModeTest.java index 1d3af286e0e..1a4c311be8b 100644 --- a/test/jdk/java/lang/invoke/DropLookupModeTest.java +++ b/test/jdk/java/lang/invoke/DropLookupModeTest.java @@ -44,7 +44,7 @@ public class DropLookupModeTest { public void testBasic() { final Lookup fullPowerLookup = MethodHandles.lookup(); final Class lc = fullPowerLookup.lookupClass(); - assertTrue(fullPowerLookup.lookupModes() == (PUBLIC|MODULE|PACKAGE|PROTECTED|PRIVATE)); + assertTrue(fullPowerLookup.lookupModes() == (PUBLIC|MODULE|PACKAGE|PROTECTED|PRIVATE|ORIGINAL)); Lookup lookup = fullPowerLookup.dropLookupMode(PRIVATE); assertTrue(lookup.lookupClass() == lc); @@ -78,7 +78,7 @@ public class DropLookupModeTest { public void testReducingAccess() { Lookup lookup = MethodHandles.lookup(); final Class lc = lookup.lookupClass(); - assertTrue(lookup.lookupModes() == (PUBLIC|MODULE|PACKAGE|PROTECTED|PRIVATE)); + assertTrue(lookup.lookupModes() == (PUBLIC|MODULE|PACKAGE|PROTECTED|PRIVATE|ORIGINAL)); lookup = lookup.dropLookupMode(PROTECTED); assertTrue(lookup.lookupClass() == lc); diff --git a/test/jdk/java/lang/invoke/MethodHandles/classData/ClassDataTest.java b/test/jdk/java/lang/invoke/MethodHandles/classData/ClassDataTest.java new file mode 100644 index 00000000000..ab5d81d4ec5 --- /dev/null +++ b/test/jdk/java/lang/invoke/MethodHandles/classData/ClassDataTest.java @@ -0,0 +1,510 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8230501 + * @library /test/lib + * @modules java.base/jdk.internal.org.objectweb.asm + * @run testng/othervm ClassDataTest + */ + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import jdk.internal.org.objectweb.asm.*; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static java.lang.invoke.MethodHandles.Lookup.*; +import static jdk.internal.org.objectweb.asm.Opcodes.*; +import static org.testng.Assert.*; + +public class ClassDataTest { + private static final Lookup LOOKUP = MethodHandles.lookup(); + + @Test + public void testOriginalAccess() throws IllegalAccessException { + Lookup lookup = hiddenClass(20); + assertTrue(lookup.hasFullPrivilegeAccess()); + + int value = MethodHandles.classData(lookup, "_", int.class); + assertEquals(value, 20); + + Integer i = MethodHandles.classData(lookup, "_", Integer.class); + assertEquals(i.intValue(), 20); + } + + /* + * A lookup class with no class data. + */ + @Test + public void noClassData() throws IllegalAccessException { + assertNull(MethodHandles.classData(LOOKUP, "_", Object.class)); + } + + @DataProvider(name = "teleportedLookup") + private Object[][] teleportedLookup() throws ReflectiveOperationException { + Lookup lookup = hiddenClass(30); + Class hc = lookup.lookupClass(); + assertClassData(lookup, 30); + + int fullAccess = PUBLIC|PROTECTED|PACKAGE|MODULE|PRIVATE; + return new Object[][] { + new Object[] { MethodHandles.privateLookupIn(hc, LOOKUP), fullAccess}, + new Object[] { LOOKUP.in(hc), fullAccess & ~(PROTECTED|PRIVATE) }, + new Object[] { lookup.dropLookupMode(PRIVATE), fullAccess & ~(PROTECTED|PRIVATE) }, + }; + } + + @Test(dataProvider = "teleportedLookup", expectedExceptions = { IllegalAccessException.class }) + public void illegalAccess(Lookup lookup, int access) throws IllegalAccessException { + int lookupModes = lookup.lookupModes(); + assertTrue((lookupModes & ORIGINAL) == 0); + assertEquals(lookupModes, access); + MethodHandles.classData(lookup, "_", int.class); + } + + @Test(expectedExceptions = { ClassCastException.class }) + public void incorrectType() throws IllegalAccessException { + Lookup lookup = hiddenClass(20); + MethodHandles.classData(lookup, "_", Long.class); + } + + @Test(expectedExceptions = { IndexOutOfBoundsException.class }) + public void invalidIndex() throws IllegalAccessException { + Lookup lookup = hiddenClass(List.of()); + MethodHandles.classDataAt(lookup, "_", Object.class, 0); + } + + @Test(expectedExceptions = { NullPointerException.class }) + public void unboxNull() throws IllegalAccessException { + List list = new ArrayList<>(); + list.add(null); + Lookup lookup = hiddenClass(list); + MethodHandles.classDataAt(lookup, "_", int.class, 0); + } + + @Test + public void nullElement() throws IllegalAccessException { + List list = new ArrayList<>(); + list.add(null); + Lookup lookup = hiddenClass(list); + assertTrue(MethodHandles.classDataAt(lookup, "_", Object.class, 0) == null); + } + + @Test + public void intClassData() throws ReflectiveOperationException { + ClassByteBuilder builder = new ClassByteBuilder("T1-int"); + byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, int.class).build(); + Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, 100, true); + int value = MethodHandles.classData(lookup, "_", int.class); + assertEquals(value, 100); + // call through condy + assertClassData(lookup, 100); + } + + @Test + public void floatClassData() throws ReflectiveOperationException { + ClassByteBuilder builder = new ClassByteBuilder("T1-float"); + byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, float.class).build(); + Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, 0.1234f, true); + float value = MethodHandles.classData(lookup, "_", float.class); + assertEquals(value, 0.1234f); + // call through condy + assertClassData(lookup, 0.1234f); + } + + @Test + public void classClassData() throws ReflectiveOperationException { + Class hc = hiddenClass(100).lookupClass(); + ClassByteBuilder builder = new ClassByteBuilder("T2"); + byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, Class.class).build(); + Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, hc, true); + Class value = MethodHandles.classData(lookup, "_", Class.class); + assertEquals(value, hc); + // call through condy + assertClassData(lookup, hc); + } + + @Test + public void arrayClassData() throws ReflectiveOperationException { + ClassByteBuilder builder = new ClassByteBuilder("T3"); + byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, String[].class).build(); + String[] colors = new String[] { "red", "yellow", "blue"}; + Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, colors, true); + assertClassData(lookup, colors.clone()); + // class data is modifiable and not a constant + colors[0] = "black"; + // it will get back the modified class data + String[] value = MethodHandles.classData(lookup, "_", String[].class); + assertEquals(value, colors); + // even call through condy as it's not a constant + assertClassData(lookup, colors); + } + + @Test + public void listClassData() throws ReflectiveOperationException { + ClassByteBuilder builder = new ClassByteBuilder("T4"); + byte[] bytes = builder.classDataAt(ACC_PUBLIC|ACC_STATIC, Integer.class, 2).build(); + List cd = List.of(100, 101, 102, 103); + int expected = 102; // element at index=2 + Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, cd, true); + int value = MethodHandles.classDataAt(lookup, "_", int.class, 2); + assertEquals(value, expected); + // call through condy + assertClassData(lookup, expected); + } + + @Test + public void arrayListClassData() throws ReflectiveOperationException { + ClassByteBuilder builder = new ClassByteBuilder("T4"); + byte[] bytes = builder.classDataAt(ACC_PUBLIC|ACC_STATIC, Integer.class, 1).build(); + ArrayList cd = new ArrayList<>(); + Stream.of(100, 101, 102, 103).forEach(cd::add); + int expected = 101; // element at index=1 + Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, cd, true); + int value = MethodHandles.classDataAt(lookup, "_", int.class, 1); + assertEquals(value, expected); + // call through condy + assertClassData(lookup, expected); + } + + private static Lookup hiddenClass(int value) { + ClassByteBuilder builder = new ClassByteBuilder("HC"); + byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, int.class).build(); + try { + return LOOKUP.defineHiddenClassWithClassData(bytes, value, true); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + private static Lookup hiddenClass(List list) { + ClassByteBuilder builder = new ClassByteBuilder("HC"); + byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, List.class).build(); + try { + return LOOKUP.defineHiddenClassWithClassData(bytes, list, true); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + @Test + public void condyInvokedFromVirtualMethod() throws ReflectiveOperationException { + ClassByteBuilder builder = new ClassByteBuilder("T5"); + // generate classData instance method + byte[] bytes = builder.classData(ACC_PUBLIC, Class.class).build(); + Lookup hcLookup = hiddenClass(100); + assertClassData(hcLookup, 100); + Class hc = hcLookup.lookupClass(); + Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, hc, true); + Class value = MethodHandles.classData(lookup, "_", Class.class); + assertEquals(value, hc); + // call through condy + Class c = lookup.lookupClass(); + assertClassData(lookup, c.newInstance(), hc); + } + + @Test + public void immutableListClassData() throws ReflectiveOperationException { + ClassByteBuilder builder = new ClassByteBuilder("T6"); + // generate classDataAt instance method + byte[] bytes = builder.classDataAt(ACC_PUBLIC, Integer.class, 2).build(); + List cd = List.of(100, 101, 102, 103); + int expected = 102; // element at index=2 + Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, cd, true); + int value = MethodHandles.classDataAt(lookup, "_", int.class, 2); + assertEquals(value, expected); + // call through condy + Class c = lookup.lookupClass(); + assertClassData(lookup, c.newInstance() ,expected); + } + + /* + * The return value of MethodHandles::classDataAt is the element + * contained in the list when the method is called. + * If MethodHandles::classDataAt is called via condy, the value + * will be captured as a constant. If the class data is modified + * after the element at the given index is computed via condy, + * subsequent LDC of such ConstantDynamic entry will return the same + * value. However, direct invocation of MethodHandles::classDataAt + * will return the modified value. + */ + @Test + public void mutableListClassData() throws ReflectiveOperationException { + ClassByteBuilder builder = new ClassByteBuilder("T7"); + // generate classDataAt instance method + byte[] bytes = builder.classDataAt(ACC_PUBLIC, MethodType.class, 0).build(); + MethodType mtype = MethodType.methodType(int.class, String.class); + List cd = new ArrayList<>(List.of(mtype)); + Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, cd, true); + // call through condy + Class c = lookup.lookupClass(); + assertClassData(lookup, c.newInstance(), mtype); + // modify the class data + assertTrue(cd.remove(0) == mtype); + cd.add(0, MethodType.methodType(void.class)); + MethodType newMType = cd.get(0); + // loading the element using condy returns the original value + assertClassData(lookup, c.newInstance(), mtype); + // direct invocation of MethodHandles.classDataAt returns the modified value + assertEquals(MethodHandles.classDataAt(lookup, "_", MethodType.class, 0), newMType); + } + + // helper method to extract from a class data map + public static T getClassDataEntry(Lookup lookup, String key, Class type) throws IllegalAccessException { + Map cd = MethodHandles.classData(lookup, "_", Map.class); + return type.cast(cd.get(key)); + } + + @Test + public void classDataMap() throws ReflectiveOperationException { + ClassByteBuilder builder = new ClassByteBuilder("map"); + // generate classData static method + Handle bsm = new Handle(H_INVOKESTATIC, "ClassDataTest", "getClassDataEntry", + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;", + false); + // generate two accessor methods to get the entries from class data + byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, Map.class) + .classData(ACC_PUBLIC|ACC_STATIC, "getClass", + Class.class, new ConstantDynamic("class", Type.getDescriptor(Class.class), bsm)) + .classData(ACC_PUBLIC|ACC_STATIC, "getMethod", + MethodHandle.class, new ConstantDynamic("method", Type.getDescriptor(MethodHandle.class), bsm)) + .build(); + + // generate a hidden class + Lookup hcLookup = hiddenClass(100); + Class hc = hcLookup.lookupClass(); + assertClassData(hcLookup, 100); + + MethodHandle mh = hcLookup.findStatic(hc, "classData", MethodType.methodType(int.class)); + Map cd = Map.of("class", hc, "method", mh); + Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, cd, true); + assertClassData(lookup, cd); + + // validate the entries from the class data map + Class c = lookup.lookupClass(); + Method m = c.getMethod("getClass"); + Class v = (Class)m.invoke(null); + assertEquals(hc, v); + + Method m1 = c.getMethod("getMethod"); + MethodHandle v1 = (MethodHandle) m1.invoke(null); + assertEquals(mh, v1); + } + + @Test(expectedExceptions = { IllegalArgumentException.class }) + public void nonDefaultName() throws ReflectiveOperationException { + ClassByteBuilder builder = new ClassByteBuilder("nonDefaultName"); + byte[] bytes = builder.classData(ACC_PUBLIC|ACC_STATIC, Class.class) + .build(); + Lookup lookup = LOOKUP.defineHiddenClassWithClassData(bytes, ClassDataTest.class, true); + assertClassData(lookup, ClassDataTest.class); + // throw IAE + MethodHandles.classData(lookup, "non_default_name", Class.class); + } + + static class ClassByteBuilder { + private static final String OBJECT_CLS = "java/lang/Object"; + private static final String MHS_CLS = "java/lang/invoke/MethodHandles"; + private static final String CLASS_DATA_BSM_DESCR = + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;"; + private final ClassWriter cw; + private final String classname; + + /** + * A builder to generate a class file to access class data + * @param classname + */ + ClassByteBuilder(String classname) { + this.classname = classname; + this.cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + cw.visit(V14, ACC_FINAL, classname, null, OBJECT_CLS, null); + MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, OBJECT_CLS, "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + byte[] build() { + cw.visitEnd(); + byte[] bytes = cw.toByteArray(); + Path p = Paths.get(classname + ".class"); + try (OutputStream os = Files.newOutputStream(p)) { + os.write(bytes); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return bytes; + } + + /* + * Generate classData method to load class data via condy + */ + ClassByteBuilder classData(int accessFlags, Class returnType) { + MethodType mtype = MethodType.methodType(returnType); + MethodVisitor mv = cw.visitMethod(accessFlags, + "classData", + mtype.descriptorString(), null, null); + mv.visitCode(); + Handle bsm = new Handle(H_INVOKESTATIC, MHS_CLS, "classData", + CLASS_DATA_BSM_DESCR, + false); + ConstantDynamic dynamic = new ConstantDynamic("_", Type.getDescriptor(returnType), bsm); + mv.visitLdcInsn(dynamic); + mv.visitInsn(returnType == int.class ? IRETURN : + (returnType == float.class ? FRETURN : ARETURN)); + mv.visitMaxs(0, 0); + mv.visitEnd(); + return this; + } + + /* + * Generate classDataAt method to load an element from class data via condy + */ + ClassByteBuilder classDataAt(int accessFlags, Class returnType, int index) { + MethodType mtype = MethodType.methodType(returnType); + MethodVisitor mv = cw.visitMethod(accessFlags, + "classData", + mtype.descriptorString(), null, null); + mv.visitCode(); + Handle bsm = new Handle(H_INVOKESTATIC, "java/lang/invoke/MethodHandles", "classDataAt", + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;I)Ljava/lang/Object;", + false); + ConstantDynamic dynamic = new ConstantDynamic("_", Type.getDescriptor(returnType), bsm, index); + mv.visitLdcInsn(dynamic); + mv.visitInsn(returnType == int.class? IRETURN : ARETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + return this; + } + + ClassByteBuilder classData(int accessFlags, String name, Class returnType, ConstantDynamic dynamic) { + MethodType mtype = MethodType.methodType(returnType); + MethodVisitor mv = cw.visitMethod(accessFlags, + name, + mtype.descriptorString(), null, null); + mv.visitCode(); + mv.visitLdcInsn(dynamic); + mv.visitInsn(returnType == int.class? IRETURN : ARETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + return this; + } + } + + /* + * Load an int constant from class data via condy and + * verify it matches the given value. + */ + private void assertClassData(Lookup lookup, int value) throws ReflectiveOperationException { + Class c = lookup.lookupClass(); + Method m = c.getMethod("classData"); + int v = (int) m.invoke(null); + assertEquals(value, v); + } + + /* + * Load an int constant from class data via condy and + * verify it matches the given value. + */ + private void assertClassData(Lookup lookup, Object o, int value) throws ReflectiveOperationException { + Class c = lookup.lookupClass(); + Method m = c.getMethod("classData"); + int v = (int) m.invoke(o); + assertEquals(value, v); + } + + /* + * Load a float constant from class data via condy and + * verify it matches the given value. + */ + private void assertClassData(Lookup lookup, float value) throws ReflectiveOperationException { + Class c = lookup.lookupClass(); + Method m = c.getMethod("classData"); + float v = (float) m.invoke(null); + assertEquals(value, v); + } + + /* + * Load a Class constant from class data via condy and + * verify it matches the given value. + */ + private void assertClassData(Lookup lookup, Class value) throws ReflectiveOperationException { + Class c = lookup.lookupClass(); + Method m = c.getMethod("classData"); + Class v = (Class)m.invoke(null); + assertEquals(value, v); + } + + /* + * Load a Class from class data via condy and + * verify it matches the given value. + */ + private void assertClassData(Lookup lookup, Object o, Class value) throws ReflectiveOperationException { + Class c = lookup.lookupClass(); + Method m = c.getMethod("classData"); + Object v = m.invoke(o); + assertEquals(value, v); + } + + /* + * Load an Object from class data via condy and + * verify it matches the given value. + */ + private void assertClassData(Lookup lookup, Object value) throws ReflectiveOperationException { + Class c = lookup.lookupClass(); + Method m = c.getMethod("classData"); + Object v = m.invoke(null); + assertEquals(value, v); + } + + /* + * Load an Object from class data via condy and + * verify it matches the given value. + */ + private void assertClassData(Lookup lookup, Object o, Object value) throws ReflectiveOperationException { + Class c = lookup.lookupClass(); + Method m = c.getMethod("classData"); + Object v = m.invoke(o); + assertEquals(value, v); + } +} + + diff --git a/test/jdk/java/lang/invoke/MethodHandles/privateLookupIn/test/p/PrivateLookupInTests.java b/test/jdk/java/lang/invoke/MethodHandles/privateLookupIn/test/p/PrivateLookupInTests.java index fe39676e24e..b17204de013 100644 --- a/test/jdk/java/lang/invoke/MethodHandles/privateLookupIn/test/p/PrivateLookupInTests.java +++ b/test/jdk/java/lang/invoke/MethodHandles/privateLookupIn/test/p/PrivateLookupInTests.java @@ -39,7 +39,6 @@ import static org.testng.Assert.*; @Test public class PrivateLookupInTests { - /** * A public and non-public types in the test module but in a different * package to the test class. @@ -75,6 +74,7 @@ public class PrivateLookupInTests { Lookup lookup = MethodHandles.privateLookupIn(nonPublicType, MethodHandles.lookup()); assertTrue(lookup.lookupClass() == nonPublicType); assertTrue(lookup.hasFullPrivilegeAccess()); + assertTrue((lookup.lookupModes() & ORIGINAL) == 0); // get obj field MethodHandle mh = lookup.findStaticGetter(nonPublicType, "obj", Object.class); @@ -88,6 +88,7 @@ public class PrivateLookupInTests { assertTrue((caller.lookupModes() & PRIVATE) == 0); assertTrue((caller.lookupModes() & PACKAGE) == 0); assertTrue((caller.lookupModes() & MODULE) != 0); + assertTrue((caller.lookupModes() & ORIGINAL) == 0); Lookup lookup = MethodHandles.privateLookupIn(nonPublicType, caller); } @@ -113,8 +114,8 @@ public class PrivateLookupInTests { Lookup lookup = MethodHandles.privateLookupIn(clazz, MethodHandles.lookup()); assertTrue(lookup.lookupClass() == clazz); - assertTrue((lookup.lookupModes() & PRIVATE) != 0); - assertFalse(lookup.hasFullPrivilegeAccess()); + assertTrue((lookup.lookupModes() & PRIVATE) == PRIVATE); + assertTrue((lookup.lookupModes() & MODULE) == 0); // get obj field MethodHandle mh = lookup.findStaticGetter(clazz, "obj", Object.class); @@ -138,7 +139,8 @@ public class PrivateLookupInTests { thisModule.addReads(clazz.getModule()); Lookup lookup = MethodHandles.privateLookupIn(clazz, MethodHandles.lookup()); assertTrue(lookup.lookupClass() == clazz); - assertTrue((lookup.lookupModes() & PRIVATE) != 0); + assertTrue((lookup.lookupModes() & PRIVATE) == PRIVATE); + assertTrue((lookup.lookupModes() & MODULE) == 0); } // test does not read m2, m2 opens p2 to test diff --git a/test/jdk/java/lang/invoke/RevealDirectTest.java b/test/jdk/java/lang/invoke/RevealDirectTest.java index d2f9f7d5e01..5e28db536c4 100644 --- a/test/jdk/java/lang/invoke/RevealDirectTest.java +++ b/test/jdk/java/lang/invoke/RevealDirectTest.java @@ -158,6 +158,10 @@ public class RevealDirectTest { // CS methods have to be revealed with a matching lookupClass testOnMembersNoReveal("testCallerSensitiveNegative/2", mems, Simple.localLookup(), publicLookup()); testOnMembersNoReveal("testCallerSensitiveNegative/3", mems, Simple.localLookup(), Nestmate.localLookup()); + // CS methods have to have original access + Lookup lookup = Simple.localLookup().dropLookupMode(Lookup.ORIGINAL); + testOnMembersNoLookup("testCallerSensitiveNegative/4", mems, lookup); + testOnMembersNoReveal("testCallerSensitiveNegative/5", mems, Simple.localLookup(), lookup); } @Test public void testMethodHandleNatives() throws Throwable { if (VERBOSE) System.out.println("@Test testMethodHandleNatives"); diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/HiddenNestmateTest.java b/test/jdk/java/lang/invoke/defineHiddenClass/HiddenNestmateTest.java index 7bba0090c0b..79668a1963f 100644 --- a/test/jdk/java/lang/invoke/defineHiddenClass/HiddenNestmateTest.java +++ b/test/jdk/java/lang/invoke/defineHiddenClass/HiddenNestmateTest.java @@ -72,6 +72,7 @@ public class HiddenNestmateTest { Lookup lookup = MethodHandles.lookup().defineHiddenClass(bytes, false); Class c = lookup.lookupClass(); assertTrue(lookup.hasFullPrivilegeAccess()); + assertTrue((lookup.lookupModes() & ORIGINAL) == ORIGINAL); assertTrue(c.getNestHost() == c); // host of its own nest assertTrue(c.isHidden()); @@ -137,6 +138,8 @@ public class HiddenNestmateTest { // Teleport to a hidden nestmate Lookup lc = MethodHandles.lookup().in(lookup.lookupClass()); assertTrue((lc.lookupModes() & PRIVATE) != 0); + assertTrue((lc.lookupModes() & ORIGINAL) == 0); + Lookup lc2 = lc.defineHiddenClass(bytes, false, NESTMATE); assertNestmate(lc2); } diff --git a/test/jdk/java/lang/invoke/modules/m3/jdk/test/ModuleAccessTest.java b/test/jdk/java/lang/invoke/modules/m3/jdk/test/ModuleAccessTest.java index c120aca323b..6a1e8efe103 100644 --- a/test/jdk/java/lang/invoke/modules/m3/jdk/test/ModuleAccessTest.java +++ b/test/jdk/java/lang/invoke/modules/m3/jdk/test/ModuleAccessTest.java @@ -91,7 +91,7 @@ public class ModuleAccessTest { * * [A0] targetClass becomes the lookup class * [A1] no change in previous lookup class - * [A2] PROTECTED and PRIVATE are dropped + * [A2] PROTECTED, PRIVATE and ORIGINAL are dropped */ @Test(dataProvider = "samePackage") public void testLookupInSamePackage(Lookup lookup, Class targetClass) throws Exception { @@ -102,7 +102,7 @@ public class ModuleAccessTest { assertTrue(lookupClass.getModule() == targetClass.getModule()); assertTrue(lookup2.lookupClass() == targetClass); // [A0] assertTrue(lookup2.previousLookupClass() == lookup.previousLookupClass()); // [A1] - assertTrue(lookup2.lookupModes() == (lookup.lookupModes() & ~(PROTECTED|PRIVATE))); // [A2] + assertTrue(lookup2.lookupModes() == (lookup.lookupModes() & ~(PROTECTED|PRIVATE|ORIGINAL))); // [A2] } @DataProvider(name = "sameModule") @@ -119,7 +119,7 @@ public class ModuleAccessTest { * * [A0] targetClass becomes the lookup class * [A1] no change in previous lookup class - * [A2] PROTECTED, PRIVATE and PACKAGE are dropped + * [A2] PROTECTED, PRIVATE, PACKAGE and ORIGINAL are dropped */ @Test(dataProvider = "sameModule") public void testLookupInSameModule(Lookup lookup, Class targetClass) throws Exception { @@ -130,7 +130,7 @@ public class ModuleAccessTest { assertTrue(lookupClass.getModule() == targetClass.getModule()); assertTrue(lookup2.lookupClass() == targetClass); // [A0] assertTrue(lookup2.previousLookupClass() == lookup.previousLookupClass()); // [A1] - assertTrue(lookup2.lookupModes() == (lookup.lookupModes() & ~(PROTECTED|PRIVATE|PACKAGE))); // [A2] + assertTrue(lookup2.lookupModes() == (lookup.lookupModes() & ~(PROTECTED|PRIVATE|PACKAGE|ORIGINAL))); // [A2] } @DataProvider(name = "anotherModule") @@ -148,7 +148,7 @@ public class ModuleAccessTest { * * [A0] targetClass becomes the lookup class * [A1] lookup class becomes previous lookup class - * [A2] PROTECTED, PRIVATE, PACKAGE, and MODULE are dropped + * [A2] PROTECTED, PRIVATE, PACKAGE, MODULE and ORIGINAL are dropped * [A3] no access to module internal types in m0 and m1 * [A4] if m1 reads m0, can access public types in m0; otherwise no access. * [A5] can access public types in m1 exported to m0 @@ -168,7 +168,7 @@ public class ModuleAccessTest { Lookup lookup2 = lookup.in(targetClass); assertTrue(lookup2.lookupClass() == targetClass); // [A0] assertTrue(lookup2.previousLookupClass() == lookup.lookupClass()); // [A1] - assertTrue(lookup2.lookupModes() == (lookup.lookupModes() & ~(PROTECTED|PRIVATE|PACKAGE|MODULE))); // [A2] + assertTrue(lookup2.lookupModes() == (lookup.lookupModes() & ~(PROTECTED|PRIVATE|PACKAGE|MODULE|ORIGINAL))); // [A2] // [A3] no access to module internal type in m0 // [A4] if m1 reads m0, @@ -289,7 +289,7 @@ public class ModuleAccessTest { Lookup lookup1 = lookup.in(c1); assertTrue(lookup1.lookupClass() == c1); assertTrue(lookup1.previousLookupClass() == c0); - assertTrue(lookup1.lookupModes() == (lookup.lookupModes() & ~(PROTECTED|PRIVATE|PACKAGE|MODULE))); + assertTrue(lookup1.lookupModes() == (lookup.lookupModes() & ~(PROTECTED|PRIVATE|PACKAGE|MODULE|ORIGINAL))); Lookup lookup2 = lookup1.in(c2); assertTrue(lookup2.lookupClass() == c2); // [A0] diff --git a/test/micro/org/openjdk/bench/java/lang/invoke/LookupDefineClass.java b/test/micro/org/openjdk/bench/java/lang/invoke/LookupDefineClass.java index 1b7e467e8a5..5ae02aa204d 100644 --- a/test/micro/org/openjdk/bench/java/lang/invoke/LookupDefineClass.java +++ b/test/micro/org/openjdk/bench/java/lang/invoke/LookupDefineClass.java @@ -230,7 +230,7 @@ public class LookupDefineClass { private static final MethodHandles.Lookup lookup = defineHostClass(new Loader("anonymous-class-loader"),"foo.AnonymousHost", FOO_HOST_BYTES); - @SuppressWarnings("deprecation") + @SuppressWarnings("removal") @Benchmark public Class load() throws ClassNotFoundException { return unsafe.defineAnonymousClass(lookup.lookupClass(), X_BYTECODE, null);