8230501: Class data support for hidden classes
Reviewed-by: jvernee, psandoz, chegar
This commit is contained in:
parent
11dad148ff
commit
4356469a31
@ -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> T widenAndCast(Object result, Class<T> resultType) throws Throwable {
|
||||
if (!resultType.isPrimitive()) {
|
||||
return resultType.cast(result);
|
||||
}
|
||||
|
||||
Class<T> 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...
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 = "<init>";
|
||||
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
* <a href="MethodHandleInfo.html#directmh">direct method handles</a>
|
||||
* 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 <em>capability</em> which may be delegated to trusted agents.
|
||||
* Do not store it in place where untrusted code can access it.
|
||||
* <p>
|
||||
* 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 {
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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 <em>class data</em> associated with the lookup class
|
||||
* of the specified {@code Lookup} object, or {@code null}.
|
||||
* of the given {@code caller} lookup object, or {@code null}.
|
||||
*
|
||||
* <p> Classes can be created with class data by calling
|
||||
* {@link Lookup#defineHiddenClassWithClassData(byte[], Object, Lookup.ClassOption...)
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> 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 <T> 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> T classData(Lookup caller, String name, Class<T> 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}.
|
||||
*
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> 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.
|
||||
*
|
||||
* <p> 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 <T> the type to cast the class data object to
|
||||
* @param <T> 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> T classData(Lookup caller, String name, Class<T> type) throws IllegalAccessException {
|
||||
if (!caller.hasFullPrivilegeAccess()) {
|
||||
throw new IllegalAccessException(caller + " does not have full privilege access");
|
||||
public static <T> T classDataAt(Lookup caller, String name, Class<T> type, int index)
|
||||
throws IllegalAccessException
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Object> classdata = (List<Object>)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 {
|
||||
* <p style="font-size:smaller;">
|
||||
* 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}.
|
||||
* <p style="font-size:smaller;">
|
||||
* A lookup with <em>original access</em> 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:
|
||||
* <ul style="font-size:smaller;">
|
||||
* <li>create method handles which invoke <a href="MethodHandles.Lookup.html#callsens">caller sensitive</a> methods,
|
||||
* such as {@code Class.forName}
|
||||
* <li>obtain the {@linkplain MethodHandles#classData(Lookup, String, Class)
|
||||
* class data} associated with the lookup class</li>
|
||||
* </ul>
|
||||
* <p style="font-size:smaller;">
|
||||
* Each of these permissions is a consequence of the fact that a lookup object
|
||||
@ -811,6 +924,7 @@ public class MethodHandles {
|
||||
* <thead>
|
||||
* <tr>
|
||||
* <th scope="col">Lookup object</th>
|
||||
* <th style="text-align:center">original</th>
|
||||
* <th style="text-align:center">protected</th>
|
||||
* <th style="text-align:center">private</th>
|
||||
* <th style="text-align:center">package</th>
|
||||
@ -821,6 +935,7 @@ public class MethodHandles {
|
||||
* <tbody>
|
||||
* <tr>
|
||||
* <th scope="row" style="text-align:left">{@code CL = MethodHandles.lookup()} in {@code C}</th>
|
||||
* <td style="text-align:center">ORI</td>
|
||||
* <td style="text-align:center">PRO</td>
|
||||
* <td style="text-align:center">PRI</td>
|
||||
* <td style="text-align:center">PAC</td>
|
||||
@ -831,6 +946,7 @@ public class MethodHandles {
|
||||
* <th scope="row" style="text-align:left">{@code CL.in(C1)} same package</th>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">PAC</td>
|
||||
* <td style="text-align:center">MOD</td>
|
||||
* <td style="text-align:center">1R</td>
|
||||
@ -840,6 +956,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">MOD</td>
|
||||
* <td style="text-align:center">1R</td>
|
||||
* </tr>
|
||||
@ -849,6 +966,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">2R</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
@ -857,10 +975,12 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">2R</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code PRI1 = privateLookupIn(C1,CL)}</td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">PRO</td>
|
||||
* <td style="text-align:center">PRI</td>
|
||||
* <td style="text-align:center">PAC</td>
|
||||
@ -869,6 +989,7 @@ public class MethodHandles {
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code PRI1a = privateLookupIn(C,PRI1)}</td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">PRO</td>
|
||||
* <td style="text-align:center">PRI</td>
|
||||
* <td style="text-align:center">PAC</td>
|
||||
@ -879,6 +1000,7 @@ public class MethodHandles {
|
||||
* <td>{@code PRI1.in(C1)} same package</td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">PAC</td>
|
||||
* <td style="text-align:center">MOD</td>
|
||||
* <td style="text-align:center">1R</td>
|
||||
@ -888,6 +1010,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">MOD</td>
|
||||
* <td style="text-align:center">1R</td>
|
||||
* </tr>
|
||||
@ -897,11 +1020,13 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">2R</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code PRI1.dropLookupMode(PROTECTED)}</td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">PRI</td>
|
||||
* <td style="text-align:center">PAC</td>
|
||||
* <td style="text-align:center">MOD</td>
|
||||
@ -911,6 +1036,7 @@ public class MethodHandles {
|
||||
* <td>{@code PRI1.dropLookupMode(PRIVATE)}</td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">PAC</td>
|
||||
* <td style="text-align:center">MOD</td>
|
||||
* <td style="text-align:center">1R</td>
|
||||
@ -920,6 +1046,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">MOD</td>
|
||||
* <td style="text-align:center">1R</td>
|
||||
* </tr>
|
||||
@ -929,6 +1056,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">1R</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
@ -937,9 +1065,11 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">none</td>
|
||||
* <tr>
|
||||
* <td>{@code PRI2 = privateLookupIn(D,CL)}</td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">PRO</td>
|
||||
* <td style="text-align:center">PRI</td>
|
||||
* <td style="text-align:center">PAC</td>
|
||||
@ -948,6 +1078,7 @@ public class MethodHandles {
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code privateLookupIn(D,PRI1)}</td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">PRO</td>
|
||||
* <td style="text-align:center">PRI</td>
|
||||
* <td style="text-align:center">PAC</td>
|
||||
@ -960,12 +1091,14 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">IAE</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code PRI2.in(D2)} same package</td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">PAC</td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">2R</td>
|
||||
@ -976,6 +1109,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">2R</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
@ -984,6 +1118,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">2R</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
@ -992,11 +1127,13 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">none</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code PRI2.dropLookupMode(PROTECTED)}</td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">PRI</td>
|
||||
* <td style="text-align:center">PAC</td>
|
||||
* <td></td>
|
||||
@ -1006,6 +1143,7 @@ public class MethodHandles {
|
||||
* <td>{@code PRI2.dropLookupMode(PRIVATE)}</td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">PAC</td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">2R</td>
|
||||
@ -1016,6 +1154,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">2R</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
@ -1024,6 +1163,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">2R</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
@ -1032,11 +1172,13 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">none</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code CL.dropLookupMode(PROTECTED)}</td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">PRI</td>
|
||||
* <td style="text-align:center">PAC</td>
|
||||
* <td style="text-align:center">MOD</td>
|
||||
@ -1046,6 +1188,7 @@ public class MethodHandles {
|
||||
* <td>{@code CL.dropLookupMode(PRIVATE)}</td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">PAC</td>
|
||||
* <td style="text-align:center">MOD</td>
|
||||
* <td style="text-align:center">1R</td>
|
||||
@ -1055,6 +1198,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">MOD</td>
|
||||
* <td style="text-align:center">1R</td>
|
||||
* </tr>
|
||||
@ -1064,6 +1208,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">1R</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
@ -1072,6 +1217,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">none</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
@ -1080,6 +1226,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">U</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
@ -1088,6 +1235,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">U</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
@ -1096,6 +1244,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">U</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
@ -1104,6 +1253,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">none</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
@ -1112,6 +1262,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">IAE</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
@ -1120,6 +1271,7 @@ public class MethodHandles {
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td></td>
|
||||
* <td style="text-align:center">none</td>
|
||||
* </tr>
|
||||
* </tbody>
|
||||
@ -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.</li>
|
||||
* <li>{@code PRO} indicates {@link #PROTECTED} bit set,
|
||||
* <li>{@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 {
|
||||
* <p>
|
||||
* 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}.
|
||||
*
|
||||
* <p>
|
||||
* 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)}.
|
||||
* <p>
|
||||
* 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:<ul>
|
||||
* <li>If the new lookup class is different from the old lookup class,
|
||||
* i.e. {@link #ORIGINAL ORIGINAL} access is lost.
|
||||
* <li>If the new lookup class is in a different module from the old one,
|
||||
* i.e. {@link #MODULE MODULE} access is lost.
|
||||
* <li>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}.
|
||||
*
|
||||
* <p> 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 {
|
||||
*
|
||||
* <p> 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 {
|
||||
* <cite>The Java Virtual Machine Specification</cite>.
|
||||
* @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.
|
||||
*
|
||||
* <p> 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 <i>unnamed</i>
|
||||
* 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.
|
||||
*
|
||||
* <p> 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 {
|
||||
* <li>If public and package access are allowed, the suffix is "/package".
|
||||
* <li>If public, package, and private access are allowed, the suffix is "/private".
|
||||
* </ul>
|
||||
* 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 <em>full privilege access</em>,
|
||||
* 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 <a href="MethodHandles.Lookup.html#secmgr">access checks</a>.
|
||||
* 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;
|
||||
|
@ -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();
|
||||
|
@ -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))
|
||||
|
@ -867,15 +867,17 @@ public final class Unsafe {
|
||||
* <li>InterfaceMethodRef: (NYI) a method handle to invoke on that call site's arguments
|
||||
* </ul>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
@ -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:<ul>
|
||||
* [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");
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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<Integer> list = new ArrayList<>();
|
||||
list.add(null);
|
||||
Lookup lookup = hiddenClass(list);
|
||||
MethodHandles.classDataAt(lookup, "_", int.class, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullElement() throws IllegalAccessException {
|
||||
List<Object> 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<Integer> 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<Integer> 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<Integer> 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<MethodType> 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> T getClassDataEntry(Lookup lookup, String key, Class<T> type) throws IllegalAccessException {
|
||||
Map<String, T> 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<String, Object> 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, "<init>", "()V", null, null);
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, OBJECT_CLS, "<init>", "()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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user