diff --git a/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/util/FrequencyEncoder.java b/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/util/FrequencyEncoder.java index c92e506525dd..2592e9bd6086 100644 --- a/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/util/FrequencyEncoder.java +++ b/compiler/src/org.graalvm.compiler.core.common/src/org/graalvm/compiler/core/common/util/FrequencyEncoder.java @@ -89,6 +89,17 @@ public void addObject(T object) { entry.frequency++; } + /** + * Returns whether the given object has been previously added to the array. + */ + public boolean contains(T object) { + if (object == null && containsNull) { + return true; + } + Entry entry = map.get(object); + return entry != null; + } + /** * Returns the index of an object in the array. The object must have been * {@link #addObject(Object) added} before. diff --git a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest index d0b30d0a078d..df2964afe9ff 100644 --- a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest +++ b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest @@ -904,6 +904,7 @@ meth public !varargs static void register(boolean,java.lang.reflect.Field[]) anno 0 java.lang.Deprecated() meth public !varargs static void register(java.lang.Class[]) meth public !varargs static void register(java.lang.reflect.Executable[]) +meth public !varargs static void registerAsQueried(java.lang.reflect.Executable[]) meth public !varargs static void register(java.lang.reflect.Field[]) meth public !varargs static void registerForReflectiveInstantiation(java.lang.Class[]) supr java.lang.Object diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java index 2d20addae50f..732375802e00 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java @@ -80,7 +80,19 @@ public static void register(Class... classes) { * @since 19.0 */ public static void register(Executable... methods) { - ImageSingletons.lookup(RuntimeReflectionSupport.class).register(ConfigurationCondition.alwaysTrue(), methods); + ImageSingletons.lookup(RuntimeReflectionSupport.class).register(ConfigurationCondition.alwaysTrue(), false, methods); + } + + /** + * Makes the provided methods available for reflection queries at run time. The methods will be + * returned by {@link Class#getMethod}, {@link Class#getMethods}, and all the other methods on + * {@link Class} that return a single or a list of methods, but will not be invocable and will + * not be considered reachable. + * + * @since 21.3 + */ + public static void registerAsQueried(Executable... methods) { + ImageSingletons.lookup(RuntimeReflectionSupport.class).register(ConfigurationCondition.alwaysTrue(), true, methods); } /** diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java index 7890a95893d7..8fa0be7b8b2e 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java @@ -46,7 +46,7 @@ public interface ReflectionRegistry { void register(ConfigurationCondition condition, Class... classes); - void register(ConfigurationCondition condition, Executable... methods); + void register(ConfigurationCondition condition, boolean queriedOnly, Executable... methods); void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields); diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java index 9d1ec47a2bb5..54a4c7486029 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java @@ -40,6 +40,10 @@ */ package org.graalvm.nativeimage.impl; +import java.lang.reflect.Executable; +import java.util.Set; + public interface RuntimeReflectionSupport extends ReflectionRegistry { // specific to java.lang.reflect reflection + Set getQueriedOnlyMethods(); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/OriginalMethodProvider.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/OriginalMethodProvider.java index 631d6136fd7f..173879d1b759 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/OriginalMethodProvider.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/OriginalMethodProvider.java @@ -40,22 +40,44 @@ static Executable getJavaMethod(SnippetReflectionProvider reflectionProvider, Re return ((OriginalMethodProvider) method).getJavaMethod(); } try { - ResolvedJavaMethod.Parameter[] parameters = method.getParameters(); - Class[] parameterTypes = new Class[parameters.length]; - ResolvedJavaType declaringClassType = method.getDeclaringClass(); - for (int i = 0; i < parameterTypes.length; i++) { - parameterTypes[i] = OriginalClassProvider.getJavaClass(reflectionProvider, parameters[i].getType().resolve(declaringClassType)); - } - Class declaringClass = OriginalClassProvider.getJavaClass(reflectionProvider, declaringClassType); - if (method.isConstructor()) { - return declaringClass.getDeclaredConstructor(parameterTypes); - } else { - return declaringClass.getDeclaredMethod(method.getName(), parameterTypes); - } + return getJavaMethodInternal(reflectionProvider, method); } catch (NoSuchMethodException e) { throw AnalysisError.shouldNotReachHere(); } } + static boolean hasJavaMethod(SnippetReflectionProvider reflectionProvider, ResolvedJavaMethod method) { + if (method instanceof OriginalMethodProvider) { + return ((OriginalMethodProvider) method).hasJavaMethod(); + } + try { + getJavaMethodInternal(reflectionProvider, method); + return true; + } catch (NoSuchMethodException | LinkageError | RuntimeException e) { + /* + * These exceptions may happen anytime during the lookup, so we can't simply use the + * result of getJavaMethodInternal. + */ + return false; + } + } + + static Executable getJavaMethodInternal(SnippetReflectionProvider reflectionProvider, ResolvedJavaMethod method) throws NoSuchMethodException { + ResolvedJavaMethod.Parameter[] parameters = method.getParameters(); + Class[] parameterTypes = new Class[parameters.length]; + ResolvedJavaType declaringClassType = method.getDeclaringClass(); + for (int i = 0; i < parameterTypes.length; i++) { + parameterTypes[i] = OriginalClassProvider.getJavaClass(reflectionProvider, parameters[i].getType().resolve(declaringClassType)); + } + Class declaringClass = OriginalClassProvider.getJavaClass(reflectionProvider, declaringClassType); + if (method.isConstructor()) { + return declaringClass.getDeclaredConstructor(parameterTypes); + } else { + return declaringClass.getDeclaredMethod(method.getName(), parameterTypes); + } + } + Executable getJavaMethod(); + + boolean hasJavaMethod(); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java index 545a4731fb23..12e8443811f0 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java @@ -554,6 +554,11 @@ public Executable getJavaMethod() { return OriginalMethodProvider.getJavaMethod(universe.getOriginalSnippetReflection(), wrapped); } + @Override + public boolean hasJavaMethod() { + return OriginalMethodProvider.hasJavaMethod(universe.getOriginalSnippetReflection(), wrapped); + } + /** * Unique, per method, context insensitive invoke. The context insensitive invoke uses the * receiver type of the method, i.e., its declaring class. Therefore this invoke will link with diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 22cf2f454c20..504635073a32 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -136,6 +136,9 @@ final class BreakpointInterceptor { /** Enables experimental support for class definitions via {@code ClassLoader.defineClass}. */ private static boolean experimentalClassDefineSupport = false; + /** Enables tracking of reflection queries for fine-tuned configuration. */ + private static boolean trackReflectionMetadata = false; + /** * Locations in methods where explicit calls to {@code ClassLoader.loadClass} have been found. */ @@ -157,7 +160,16 @@ private static void traceBreakpoint(JNIEnvironment env, JNIObjectHandle clazz, J result, stackTrace, args); - guarantee(!testException(env)); + JNIObjectHandle exception = handleException(env, false); + if (exception.notEqual(nullHandle())) { + /* + * A stack overflow error happening during a breakpoint should be handled by the + * program, not the agent. + */ + guarantee(jniFunctions().getIsInstanceOf().invoke(env, exception, agent.handles().javaLangStackOverflowError)); + return; + } + clearException(env); } } @@ -367,22 +379,108 @@ private static boolean getEnclosingMethod(JNIEnvironment jni, Breakpoint bp, Int return true; } + private static boolean invokeMethod(JNIEnvironment jni, Breakpoint bp, InterceptedState state) { + return handleInvokeMethod(jni, bp, state, true); + } + + private static boolean unreflectMethod(JNIEnvironment jni, Breakpoint bp, InterceptedState state) { + return handleInvokeMethod(jni, bp, state, false); + } + + private static boolean handleInvokeMethod(JNIEnvironment jni, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state, boolean isInvoke) { + JNIObjectHandle callerClass = state.getDirectCallerClass(); + JNIObjectHandle method = getObjectArgument(isInvoke ? 0 : 1); + + JNIObjectHandle declaring = Support.callObjectMethod(jni, method, agent.handles().javaLangReflectMemberGetDeclaringClass); + if (clearException(jni)) { + declaring = nullHandle(); + } + + JNIObjectHandle nameHandle = Support.callObjectMethod(jni, method, agent.handles().javaLangReflectMemberGetName); + if (clearException(jni)) { + nameHandle = nullHandle(); + } + String name = fromJniString(jni, nameHandle); + + JNIObjectHandle paramTypesHandle = Support.callObjectMethod(jni, method, agent.handles().getJavaLangReflectExecutableGetParameterTypes(jni)); + if (clearException(jni)) { + paramTypesHandle = nullHandle(); + } + Object paramTypes = getClassArrayNames(jni, paramTypesHandle); + + traceBreakpoint(jni, declaring, declaring, callerClass, "invokeMethod", declaring.notEqual(nullHandle()), state.getFullStackTraceOrNull(), name, paramTypes); + + /* + * Calling Class.newInstance through Method.invoke should register the class for reflective + * instantiation + */ + if (isInvoke && isClassNewInstance(jni, declaring, name)) { + JNIObjectHandle clazz = getObjectArgument(1); + JNIMethodId result = newInstanceMethodID(jni, clazz); + traceBreakpoint(jni, clazz, nullHandle(), callerClass, "newInstance", result.notEqual(nullHandle()), state.getFullStackTraceOrNull()); + } + return true; + } + + private static boolean isClassNewInstance(JNIEnvironment jni, JNIObjectHandle declaring, String name) { + if (!"newInstance".equals(name)) { + return false; + } + JNIObjectHandle classNameHandle = Support.callObjectMethod(jni, declaring, agent.handles().javaLangClassGetName); + if (clearException(jni)) { + classNameHandle = nullHandle(); + } + String className = fromJniString(jni, classNameHandle); + return "java.lang.Class".equals(className); + } + + private static boolean invokeConstructor(JNIEnvironment jni, Breakpoint bp, InterceptedState state) { + return handleInvokeConstructor(jni, bp, state, getObjectArgument(0)); + } + + private static boolean unreflectConstructor(JNIEnvironment jni, Breakpoint bp, InterceptedState state) { + return handleInvokeConstructor(jni, bp, state, getObjectArgument(1)); + } + + private static boolean handleInvokeConstructor(JNIEnvironment jni, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state, JNIObjectHandle constructor) { + JNIObjectHandle callerClass = state.getDirectCallerClass(); + + JNIObjectHandle declaring = Support.callObjectMethod(jni, constructor, agent.handles().javaLangReflectMemberGetDeclaringClass); + if (clearException(jni)) { + declaring = nullHandle(); + } + + JNIObjectHandle paramTypesHandle = Support.callObjectMethod(jni, constructor, agent.handles().getJavaLangReflectExecutableGetParameterTypes(jni)); + if (clearException(jni)) { + paramTypesHandle = nullHandle(); + } + Object paramTypes = getClassArrayNames(jni, paramTypesHandle); + + traceBreakpoint(jni, declaring, declaring, callerClass, "invokeConstructor", declaring.notEqual(nullHandle()), state.getFullStackTraceOrNull(), paramTypes); + return true; + } + private static boolean newInstance(JNIEnvironment jni, Breakpoint bp, InterceptedState state) { JNIObjectHandle callerClass = state.getDirectCallerClass(); + JNIObjectHandle self = getObjectArgument(0); + JNIMethodId result = newInstanceMethodID(jni, self); + traceBreakpoint(jni, self, nullHandle(), callerClass, bp.specification.methodName, result.notEqual(nullHandle()), state.getFullStackTraceOrNull()); + return true; + } + + private static JNIMethodId newInstanceMethodID(JNIEnvironment jni, JNIObjectHandle clazz) { JNIMethodId result = nullPointer(); String name = ""; String signature = "()V"; - JNIObjectHandle self = getObjectArgument(0); - if (self.notEqual(nullHandle())) { + if (clazz.notEqual(nullHandle())) { try (CCharPointerHolder ctorName = toCString(name); CCharPointerHolder ctorSignature = toCString(signature)) { - result = jniFunctions().getGetMethodID().invoke(jni, self, ctorName.get(), ctorSignature.get()); + result = jniFunctions().getGetMethodID().invoke(jni, clazz, ctorName.get(), ctorSignature.get()); } if (clearException(jni)) { result = nullPointer(); } } - traceBreakpoint(jni, self, nullHandle(), callerClass, bp.specification.methodName, result.notEqual(nullHandle()), state.getFullStackTraceOrNull()); - return true; + return result; } private static boolean newArrayInstance(JNIEnvironment jni, Breakpoint bp, InterceptedState state) { @@ -861,7 +959,7 @@ private static boolean methodTypeFromDescriptor(JNIEnvironment jni, Breakpoint b } private static JNIObjectHandle shouldIncludeMethod(JNIEnvironment jni, JNIObjectHandle result, JNIObjectHandle... acceptedExceptions) { - JNIObjectHandle exception = handleException(jni); + JNIObjectHandle exception = handleException(jni, true); if (exception.notEqual(nullHandle())) { for (JNIObjectHandle acceptedException : acceptedExceptions) { if (jniFunctions().getIsInstanceOf().invoke(jni, exception, acceptedException)) { @@ -1068,12 +1166,13 @@ private static void onClassFileLoadHook(@SuppressWarnings("unused") JvmtiEnv jvm public static void onLoad(JvmtiEnv jvmti, JvmtiEventCallbacks callbacks, Tracer writer, NativeImageAgent nativeImageTracingAgent, Supplier currentThreadJavaStackAccessSupplier, - boolean exptlClassLoaderSupport, boolean exptlClassDefineSupport) { + boolean exptlClassLoaderSupport, boolean exptlClassDefineSupport, boolean trackReflectionData) { BreakpointInterceptor.tracer = writer; BreakpointInterceptor.agent = nativeImageTracingAgent; BreakpointInterceptor.interceptedStateSupplier = currentThreadJavaStackAccessSupplier; BreakpointInterceptor.experimentalClassLoaderSupport = exptlClassLoaderSupport; BreakpointInterceptor.experimentalClassDefineSupport = exptlClassDefineSupport; + BreakpointInterceptor.trackReflectionMetadata = trackReflectionData; JvmtiCapabilities capabilities = UnmanagedMemory.calloc(SizeOf.get(JvmtiCapabilities.class)); check(jvmti.getFunctions().GetCapabilities().invoke(jvmti, capabilities)); @@ -1122,7 +1221,13 @@ public static void onVMInit(JvmtiEnv jvmti, JNIEnvironment jni) { JNIObjectHandle lastClass = nullHandle(); String lastClassName = null; - for (BreakpointSpecification br : BREAKPOINT_SPECIFICATIONS) { + BreakpointSpecification[] breakpointSpecifications = BREAKPOINT_SPECIFICATIONS; + if (trackReflectionMetadata) { + breakpointSpecifications = new BreakpointSpecification[BREAKPOINT_SPECIFICATIONS.length + REFLECTION_QUERIES_BREAKPOINT_SPECIFICATIONS.length]; + System.arraycopy(BREAKPOINT_SPECIFICATIONS, 0, breakpointSpecifications, 0, BREAKPOINT_SPECIFICATIONS.length); + System.arraycopy(REFLECTION_QUERIES_BREAKPOINT_SPECIFICATIONS, 0, breakpointSpecifications, BREAKPOINT_SPECIFICATIONS.length, REFLECTION_QUERIES_BREAKPOINT_SPECIFICATIONS.length); + } + for (BreakpointSpecification br : breakpointSpecifications) { JNIObjectHandle clazz = nullHandle(); if (lastClassName != null && lastClassName.equals(br.className)) { clazz = lastClass; @@ -1245,24 +1350,19 @@ private interface BreakpointHandler { brk("java/lang/Class", "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;", BreakpointInterceptor::forName), brk("java/lang/Class", "getFields", "()[Ljava/lang/reflect/Field;", BreakpointInterceptor::getFields), - brk("java/lang/Class", "getMethods", "()[Ljava/lang/reflect/Method;", BreakpointInterceptor::getMethods), - brk("java/lang/Class", "getConstructors", "()[Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getConstructors), brk("java/lang/Class", "getClasses", "()[Ljava/lang/Class;", BreakpointInterceptor::getClasses), brk("java/lang/Class", "getDeclaredFields", "()[Ljava/lang/reflect/Field;", BreakpointInterceptor::getDeclaredFields), - brk("java/lang/Class", "getDeclaredMethods", "()[Ljava/lang/reflect/Method;", BreakpointInterceptor::getDeclaredMethods), - brk("java/lang/Class", "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getDeclaredConstructors), brk("java/lang/Class", "getDeclaredClasses", "()[Ljava/lang/Class;", BreakpointInterceptor::getDeclaredClasses), brk("java/lang/Class", "getField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;", BreakpointInterceptor::getField), brk("java/lang/Class", "getDeclaredField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;", BreakpointInterceptor::getDeclaredField), - brk("java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", BreakpointInterceptor::getMethod), - brk("java/lang/Class", "getConstructor", "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getConstructor), - brk("java/lang/Class", "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", BreakpointInterceptor::getDeclaredMethod), - brk("java/lang/Class", "getDeclaredConstructor", "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getConstructor), brk("java/lang/Class", "getEnclosingMethod", "()Ljava/lang/reflect/Method;", BreakpointInterceptor::getEnclosingMethod), brk("java/lang/Class", "getEnclosingConstructor", "()Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getEnclosingMethod), + brk("java/lang/reflect/Method", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", BreakpointInterceptor::invokeMethod), + brk("sun/reflect/misc/MethodUtil", "invoke", "(Ljava/lang/reflect/Method;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", BreakpointInterceptor::invokeMethod), + brk("java/lang/reflect/Constructor", "newInstance", "([Ljava/lang/Object;)Ljava/lang/Object;", BreakpointInterceptor::invokeConstructor), brk("java/lang/Class", "newInstance", "()Ljava/lang/Object;", BreakpointInterceptor::newInstance), brk("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;I)Ljava/lang/Object;", BreakpointInterceptor::newArrayInstance), brk("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;[I)Ljava/lang/Object;", BreakpointInterceptor::newArrayInstanceMulti), @@ -1333,6 +1433,12 @@ private interface BreakpointHandler { optionalBrk("java/lang/invoke/MethodHandles$Lookup", "findClass", "(Ljava/lang/String;)Ljava/lang/Class;", BreakpointInterceptor::findClass), + optionalBrk("java/lang/invoke/MethodHandles$Lookup", "unreflect", + "(Ljava/lang/reflect/Method;)Ljava/lang/invoke/MethodHandle;", + BreakpointInterceptor::unreflectMethod), + optionalBrk("java/lang/invoke/MethodHandles$Lookup", "unreflectConstructor", + "(Ljava/lang/reflect/Constructor;)Ljava/lang/invoke/MethodHandle;", + BreakpointInterceptor::unreflectConstructor), optionalBrk("java/lang/invoke/MethodHandles$Lookup", "unreflectGetter", "(Ljava/lang/reflect/Field;)Ljava/lang/invoke/MethodHandle;", BreakpointInterceptor::unreflectField), @@ -1353,6 +1459,18 @@ private interface BreakpointHandler { private static final BreakpointSpecification CLASSLOADER_LOAD_CLASS_BREAKPOINT_SPECIFICATION = optionalBrk("java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", BreakpointInterceptor::loadClass); + private static final BreakpointSpecification[] REFLECTION_QUERIES_BREAKPOINT_SPECIFICATIONS = { + brk("java/lang/Class", "getMethods", "()[Ljava/lang/reflect/Method;", BreakpointInterceptor::getMethods), + brk("java/lang/Class", "getConstructors", "()[Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getConstructors), + brk("java/lang/Class", "getDeclaredMethods", "()[Ljava/lang/reflect/Method;", BreakpointInterceptor::getDeclaredMethods), + brk("java/lang/Class", "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getDeclaredConstructors), + + brk("java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", BreakpointInterceptor::getMethod), + brk("java/lang/Class", "getConstructor", "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getConstructor), + brk("java/lang/Class", "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", BreakpointInterceptor::getDeclaredMethod), + brk("java/lang/Class", "getDeclaredConstructor", "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getConstructor), + }; + private static BreakpointSpecification brk(String className, String methodName, String signature, BreakpointHandler handler) { return new BreakpointSpecification(className, methodName, signature, handler, false); } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java index e11c17a8b3e4..6a833ff55d90 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java @@ -121,6 +121,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c boolean configurationWithOrigins = false; int configWritePeriod = -1; // in seconds int configWritePeriodInitialDelay = 1; // in seconds + boolean trackReflectionMetadata = true; String[] tokens = !options.isEmpty() ? options.split(",") : new String[0]; for (String token : tokens) { @@ -189,6 +190,10 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c build = Boolean.parseBoolean(getTokenValue(token)); } else if (token.equals("experimental-configuration-with-origins")) { configurationWithOrigins = true; + } else if (token.equals("track-reflection-metadata")) { + trackReflectionMetadata = true; + } else if (token.startsWith("track-reflection-metadata=")) { + trackReflectionMetadata = Boolean.parseBoolean(getTokenValue(token)); } else { return usage(1, "unknown option: '" + token + "'."); } @@ -306,7 +311,8 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c } try { - BreakpointInterceptor.onLoad(jvmti, callbacks, tracer, this, interceptedStateSupplier, experimentalClassLoaderSupport, experimentalClassDefineSupport); + BreakpointInterceptor.onLoad(jvmti, callbacks, tracer, this, interceptedStateSupplier, + experimentalClassLoaderSupport, experimentalClassDefineSupport, trackReflectionMetadata); } catch (Throwable t) { return error(3, t.toString()); } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java index c7945fdc5b07..f4b8e7801fef 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java @@ -42,6 +42,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { final JNIMethodId javaLangReflectMemberGetName; final JNIMethodId javaLangReflectMemberGetDeclaringClass; + private JNIMethodId javaLangReflectExecutableGetParameterTypes = WordFactory.nullPointer(); final JNIMethodId javaUtilEnumerationHasMoreElements; @@ -52,6 +53,8 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { final JNIMethodId javaLangObjectGetClass; + final JNIObjectHandle javaLangStackOverflowError; + private JNIMethodId javaLangInvokeMethodTypeParameterArray = WordFactory.nullPointer(); private JNIMethodId javaLangInvokeMethodTypeReturnType = WordFactory.nullPointer(); final JNIObjectHandle javaLangIllegalAccessException; @@ -94,11 +97,21 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { JNIObjectHandle javaLangObject = findClass(env, "java/lang/Object"); javaLangObjectGetClass = getMethodId(env, javaLangObject, "getClass", "()Ljava/lang/Class;", false); + javaLangStackOverflowError = newClassGlobalRef(env, "java/lang/StackOverflowError"); + javaLangIllegalAccessException = newClassGlobalRef(env, "java/lang/IllegalAccessException"); javaLangInvokeWrongMethodTypeException = newClassGlobalRef(env, "java/lang/invoke/WrongMethodTypeException"); javaLangIllegalArgumentException = newClassGlobalRef(env, "java/lang/IllegalArgumentException"); } + JNIMethodId getJavaLangReflectExecutableGetParameterTypes(JNIEnvironment env) { + if (javaLangReflectExecutableGetParameterTypes.isNull()) { + JNIObjectHandle javaLangReflectExecutable = findClass(env, "java/lang/reflect/Executable"); + javaLangReflectExecutableGetParameterTypes = getMethodId(env, javaLangReflectExecutable, "getParameterTypes", "()[Ljava/lang/Class;", false); + } + return javaLangReflectExecutableGetParameterTypes; + } + JNIMethodId getJavaLangInvokeMethodTypeReturnType(JNIEnvironment env) { if (javaLangInvokeMethodTypeReturnType.isNull()) { JNIObjectHandle javaLangInvokeMethodType = newClassGlobalRef(env, "java/lang/invoke/MethodType"); diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java index 1790d63783be..508600f2cb2d 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java @@ -39,7 +39,9 @@ import org.junit.Assert; import org.junit.Test; -import com.oracle.svm.configure.config.ConfigurationMemberKind; +import com.oracle.svm.configure.config.ConfigurationMemberInfo; +import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberAccessibility; +import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberDeclaration; import com.oracle.svm.configure.config.ConfigurationMethod; import com.oracle.svm.configure.config.ConfigurationSet; import com.oracle.svm.configure.config.ConfigurationType; @@ -125,13 +127,13 @@ public void testConfigDifference() { } private static void doTestGeneratedTypeConfig() { - TypeMethodsWithFlagsTest typeMethodsWithFlagsTestDeclared = new TypeMethodsWithFlagsTest(ConfigurationMemberKind.DECLARED); + TypeMethodsWithFlagsTest typeMethodsWithFlagsTestDeclared = new TypeMethodsWithFlagsTest(ConfigurationMemberDeclaration.DECLARED); typeMethodsWithFlagsTestDeclared.doTest(); - TypeMethodsWithFlagsTest typeMethodsWithFlagsTestPublic = new TypeMethodsWithFlagsTest(ConfigurationMemberKind.PUBLIC); + TypeMethodsWithFlagsTest typeMethodsWithFlagsTestPublic = new TypeMethodsWithFlagsTest(ConfigurationMemberDeclaration.PUBLIC); typeMethodsWithFlagsTestPublic.doTest(); - TypeMethodsWithFlagsTest typeMethodsWithFlagsTestDeclaredPublic = new TypeMethodsWithFlagsTest(ConfigurationMemberKind.DECLARED_AND_PUBLIC); + TypeMethodsWithFlagsTest typeMethodsWithFlagsTestDeclaredPublic = new TypeMethodsWithFlagsTest(ConfigurationMemberDeclaration.DECLARED_AND_PUBLIC); typeMethodsWithFlagsTestDeclaredPublic.doTest(); } @@ -152,10 +154,12 @@ private static void doTestExpectedMissingTypes(TypeConfiguration typeConfig) { private static void doTestTypeFlags(TypeConfiguration typeConfig) { ConfigurationType flagTestHasDeclaredType = getConfigTypeOrFail(typeConfig, "FlagTestC"); - Assert.assertTrue(flagTestHasDeclaredType.haveAllDeclaredClasses() || flagTestHasDeclaredType.haveAllDeclaredFields() || flagTestHasDeclaredType.haveAllDeclaredConstructors()); + Assert.assertTrue(flagTestHasDeclaredType.haveAllDeclaredClasses() || flagTestHasDeclaredType.haveAllDeclaredFields() || + flagTestHasDeclaredType.getAllDeclaredConstructors() == ConfigurationMemberAccessibility.ACCESSED); ConfigurationType flagTestHasPublicType = getConfigTypeOrFail(typeConfig, "FlagTestD"); - Assert.assertTrue(flagTestHasPublicType.haveAllPublicClasses() || flagTestHasPublicType.haveAllPublicFields() || flagTestHasPublicType.haveAllPublicConstructors()); + Assert.assertTrue(flagTestHasPublicType.haveAllPublicClasses() || flagTestHasPublicType.haveAllPublicFields() || + flagTestHasPublicType.getAllPublicConstructors() == ConfigurationMemberAccessibility.ACCESSED); } private static void doTestFields(TypeConfiguration typeConfig) { @@ -226,15 +230,15 @@ class TypeMethodsWithFlagsTest { static final String INTERNAL_SIGNATURE_ONE = "([Ljava/lang/String;)V"; static final String INTERNAL_SIGNATURE_TWO = "([Ljava/lang/String;Ljava/lang/String;)V"; - final ConfigurationMemberKind methodKind; + final ConfigurationMemberDeclaration methodKind; - final Map methodsThatMustExist = new HashMap<>(); - final Map methodsThatMustNotExist = new HashMap<>(); + final Map methodsThatMustExist = new HashMap<>(); + final Map methodsThatMustNotExist = new HashMap<>(); final TypeConfiguration previousConfig = new TypeConfiguration(); final TypeConfiguration currentConfig = new TypeConfiguration(); - TypeMethodsWithFlagsTest(ConfigurationMemberKind methodKind) { + TypeMethodsWithFlagsTest(ConfigurationMemberDeclaration methodKind) { this.methodKind = methodKind; generateTestMethods(); populateConfig(); @@ -242,21 +246,21 @@ class TypeMethodsWithFlagsTest { } void generateTestMethods() { - Map targetMap; + Map targetMap; - targetMap = getMethodsMap(ConfigurationMemberKind.DECLARED); - targetMap.put(new ConfigurationMethod("", INTERNAL_SIGNATURE_ONE), ConfigurationMemberKind.DECLARED); - targetMap.put(new ConfigurationMethod("testMethodDeclaredSpecificSignature", INTERNAL_SIGNATURE_ONE), ConfigurationMemberKind.DECLARED); - targetMap.put(new ConfigurationMethod("testMethodDeclaredMatchesAllSignature", null), ConfigurationMemberKind.DECLARED); + targetMap = getMethodsMap(ConfigurationMemberDeclaration.DECLARED); + targetMap.put(new ConfigurationMethod("", INTERNAL_SIGNATURE_ONE), ConfigurationMemberDeclaration.DECLARED); + targetMap.put(new ConfigurationMethod("testMethodDeclaredSpecificSignature", INTERNAL_SIGNATURE_ONE), ConfigurationMemberDeclaration.DECLARED); + targetMap.put(new ConfigurationMethod("testMethodDeclaredMatchesAllSignature", null), ConfigurationMemberDeclaration.DECLARED); - targetMap = getMethodsMap(ConfigurationMemberKind.PUBLIC); - targetMap.put(new ConfigurationMethod("", INTERNAL_SIGNATURE_TWO), ConfigurationMemberKind.PUBLIC); - targetMap.put(new ConfigurationMethod("testMethodPublicSpecificSignature", INTERNAL_SIGNATURE_ONE), ConfigurationMemberKind.PUBLIC); - targetMap.put(new ConfigurationMethod("testMethodPublicMatchesAllSignature", null), ConfigurationMemberKind.PUBLIC); + targetMap = getMethodsMap(ConfigurationMemberDeclaration.PUBLIC); + targetMap.put(new ConfigurationMethod("", INTERNAL_SIGNATURE_TWO), ConfigurationMemberDeclaration.PUBLIC); + targetMap.put(new ConfigurationMethod("testMethodPublicSpecificSignature", INTERNAL_SIGNATURE_ONE), ConfigurationMemberDeclaration.PUBLIC); + targetMap.put(new ConfigurationMethod("testMethodPublicMatchesAllSignature", null), ConfigurationMemberDeclaration.PUBLIC); } - Map getMethodsMap(ConfigurationMemberKind otherKind) { - if (methodKind.equals(otherKind) || methodKind.equals(ConfigurationMemberKind.DECLARED_AND_PUBLIC)) { + Map getMethodsMap(ConfigurationMemberDeclaration otherKind) { + if (methodKind.equals(otherKind) || methodKind.equals(ConfigurationMemberDeclaration.DECLARED_AND_PUBLIC)) { return methodsThatMustNotExist; } return methodsThatMustExist; @@ -268,26 +272,26 @@ void populateConfig() { previousConfig.add(oldType); ConfigurationType newType = new ConfigurationType(ConfigurationCondition.alwaysTrue(), getTypeName()); - for (Map.Entry methodEntry : methodsThatMustExist.entrySet()) { + for (Map.Entry methodEntry : methodsThatMustExist.entrySet()) { newType.addMethod(methodEntry.getKey().getName(), methodEntry.getKey().getInternalSignature(), methodEntry.getValue()); } - for (Map.Entry methodEntry : methodsThatMustNotExist.entrySet()) { + for (Map.Entry methodEntry : methodsThatMustNotExist.entrySet()) { newType.addMethod(methodEntry.getKey().getName(), methodEntry.getKey().getInternalSignature(), methodEntry.getValue()); } currentConfig.add(newType); } void setFlags(ConfigurationType config) { - if (methodKind.equals(ConfigurationMemberKind.DECLARED) || methodKind.equals(ConfigurationMemberKind.DECLARED_AND_PUBLIC)) { + if (methodKind.equals(ConfigurationMemberDeclaration.DECLARED) || methodKind.equals(ConfigurationMemberDeclaration.DECLARED_AND_PUBLIC)) { config.setAllDeclaredClasses(); - config.setAllDeclaredConstructors(); - config.setAllDeclaredMethods(); + config.setAllDeclaredConstructors(ConfigurationMemberAccessibility.ACCESSED); + config.setAllDeclaredMethods(ConfigurationMemberAccessibility.ACCESSED); config.setAllDeclaredFields(); } - if (methodKind.equals(ConfigurationMemberKind.PUBLIC) || methodKind.equals(ConfigurationMemberKind.DECLARED_AND_PUBLIC)) { + if (methodKind.equals(ConfigurationMemberDeclaration.PUBLIC) || methodKind.equals(ConfigurationMemberDeclaration.DECLARED_AND_PUBLIC)) { config.setAllPublicClasses(); - config.setAllPublicConstructors(); - config.setAllPublicMethods(); + config.setAllPublicConstructors(ConfigurationMemberAccessibility.ACCESSED); + config.setAllPublicMethods(ConfigurationMemberAccessibility.ACCESSED); config.setAllPublicFields(); } } @@ -304,13 +308,13 @@ void doTest() { } else { Assert.assertNotNull("Generated configuration type " + name + " does not exist. Has the test code changed?", configurationType); - for (Map.Entry methodEntry : methodsThatMustExist.entrySet()) { - ConfigurationMemberKind kind = configurationType.getMethodKindIfPresent(methodEntry.getKey()); + for (Map.Entry methodEntry : methodsThatMustExist.entrySet()) { + ConfigurationMemberDeclaration kind = configurationType.getMethodKindIfPresent(methodEntry.getKey()).getMemberKind(); Assert.assertNotNull("Method " + methodEntry.getKey() + " unexpectedly NOT found in the new configuration.", kind); Assert.assertEquals("Method " + methodEntry.getKey() + " contains a different kind than expected in the new configuration.", kind, methodEntry.getValue()); } - for (Map.Entry methodEntry : methodsThatMustNotExist.entrySet()) { - ConfigurationMemberKind kind = configurationType.getMethodKindIfPresent(methodEntry.getKey()); + for (Map.Entry methodEntry : methodsThatMustNotExist.entrySet()) { + ConfigurationMemberInfo kind = configurationType.getMethodKindIfPresent(methodEntry.getKey()); Assert.assertNull("Method " + methodEntry.getKey() + " unexpectedly found in the new configuration.", kind); } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMemberInfo.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMemberInfo.java new file mode 100644 index 000000000000..9e53fca94891 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMemberInfo.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2021, 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.configure.config; + +public final class ConfigurationMemberInfo { + private static final ConfigurationMemberInfo[][] cache = new ConfigurationMemberInfo[ConfigurationMemberDeclaration.values().length][ConfigurationMemberAccessibility.values().length]; + + static { + for (ConfigurationMemberDeclaration memberKind : ConfigurationMemberDeclaration.values()) { + for (ConfigurationMemberAccessibility accessKind : ConfigurationMemberAccessibility.values()) { + cache[memberKind.ordinal()][accessKind.ordinal()] = new ConfigurationMemberInfo(memberKind, accessKind); + } + } + } + + private final ConfigurationMemberDeclaration memberKind; + private final ConfigurationMemberAccessibility accessKind; + + private ConfigurationMemberInfo(ConfigurationMemberDeclaration memberKind, ConfigurationMemberAccessibility accessKind) { + this.memberKind = memberKind; + this.accessKind = accessKind; + } + + public ConfigurationMemberDeclaration getMemberKind() { + return memberKind; + } + + public ConfigurationMemberAccessibility getAccessKind() { + return accessKind; + } + + public static ConfigurationMemberInfo get(ConfigurationMemberDeclaration memberKind, ConfigurationMemberAccessibility accessKind) { + return cache[memberKind.ordinal()][accessKind.ordinal()]; + } + + public enum ConfigurationMemberDeclaration { + /** + * The member is public and declared in the type in question. + */ + DECLARED_AND_PUBLIC, + + /** + * The member is declared in the type in question. + */ + DECLARED, + + /** + * The member is public and is either declared or inherited in the type in question. + */ + PUBLIC, + + /** + * The member is either declared or inherited in the type in question. + */ + PRESENT; + + private boolean isMoreSpecificThan(ConfigurationMemberDeclaration other) { + return other == null || ordinal() < other.ordinal(); + } + + public ConfigurationMemberDeclaration intersect(ConfigurationMemberDeclaration other) { + if (equals(DECLARED) && PUBLIC.equals(other) || equals(PUBLIC) && DECLARED.equals(other)) { + return DECLARED_AND_PUBLIC; + } + return this.isMoreSpecificThan(other) ? this : other; + } + + private ConfigurationMemberDeclaration union(ConfigurationMemberDeclaration other) { + return equals(other) ? this : PRESENT; + } + + public boolean includes(ConfigurationMemberDeclaration other) { + if (equals(DECLARED_AND_PUBLIC)) { + return DECLARED.equals(other) || PUBLIC.equals(other); + } + if (equals(PRESENT)) { + return true; + } + return equals(other); + } + } + + public enum ConfigurationMemberAccessibility { + NONE, + QUERIED, + ACCESSED; + + public ConfigurationMemberAccessibility combine(ConfigurationMemberAccessibility other) { + return (ordinal() < other.ordinal()) ? other : this; + } + + public ConfigurationMemberAccessibility remove(ConfigurationMemberAccessibility other) { + return other.includes(this) ? NONE : this; + } + + public boolean includes(ConfigurationMemberAccessibility other) { + return ordinal() >= other.ordinal(); + } + } + + public ConfigurationMemberInfo intersect(ConfigurationMemberInfo other) { + return get(memberKind.intersect(other.memberKind), accessKind.combine(other.accessKind)); + } + + public ConfigurationMemberInfo union(ConfigurationMemberInfo other) { + return get(memberKind.union(other.memberKind), accessKind.combine(other.accessKind)); + } + + public boolean includes(ConfigurationMemberInfo other) { + return memberKind.includes(other.memberKind) && accessKind.includes(other.accessKind); + } +} diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMemberKind.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMemberKind.java deleted file mode 100644 index c502c1798501..000000000000 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationMemberKind.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2019, 2019, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ -package com.oracle.svm.configure.config; - -public enum ConfigurationMemberKind { - /** The member is public and declared in the type in question. */ - DECLARED_AND_PUBLIC, - - /** The member is declared in the type in question. */ - DECLARED, - - /** The member is public and is either declared or inherited in the type in question. */ - PUBLIC, - - /** The member is either declared or inherited in the type in question. */ - PRESENT; - - private boolean isMoreSpecificThan(ConfigurationMemberKind other) { - return other == null || ordinal() < other.ordinal(); - } - - public ConfigurationMemberKind intersect(ConfigurationMemberKind other) { - if (equals(DECLARED) && PUBLIC.equals(other) || equals(PUBLIC) && DECLARED.equals(other)) { - return DECLARED_AND_PUBLIC; - } - return this.isMoreSpecificThan(other) ? this : other; - } - - public ConfigurationMemberKind union(ConfigurationMemberKind other) { - return equals(other) ? this : PRESENT; - } - - public boolean includes(ConfigurationMemberKind other) { - if (equals(DECLARED_AND_PUBLIC)) { - return DECLARED.equals(other) || PUBLIC.equals(other); - } - if (equals(PRESENT)) { - return true; - } - return equals(other); - } -} diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java index eca768ac720f..7d95a4c1834a 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java @@ -28,11 +28,16 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; import java.util.function.BiPredicate; import java.util.function.Consumer; +import java.util.stream.Collectors; import org.graalvm.nativeimage.impl.ConfigurationCondition; +import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberAccessibility; +import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberDeclaration; import com.oracle.svm.configure.json.JsonPrintable; import com.oracle.svm.configure.json.JsonPrinter; import com.oracle.svm.configure.json.JsonWriter; @@ -42,16 +47,16 @@ public class ConfigurationType implements JsonPrintable { private final String qualifiedJavaName; private Map fields; - private Map methods; + private Map methods; private boolean allDeclaredClasses; private boolean allPublicClasses; private boolean allDeclaredFields; private boolean allPublicFields; - private boolean allDeclaredMethods; - private boolean allPublicMethods; - private boolean allDeclaredConstructors; - private boolean allPublicConstructors; + private ConfigurationMemberAccessibility allDeclaredMethodsAccess = ConfigurationMemberAccessibility.NONE; + private ConfigurationMemberAccessibility allPublicMethodsAccess = ConfigurationMemberAccessibility.NONE; + private ConfigurationMemberAccessibility allDeclaredConstructorsAccess = ConfigurationMemberAccessibility.NONE; + private ConfigurationMemberAccessibility allPublicConstructorsAccess = ConfigurationMemberAccessibility.NONE; public ConfigurationType(ConfigurationCondition condition, String qualifiedJavaName) { assert qualifiedJavaName.indexOf('/') == -1 : "Requires qualified Java name, not internal representation"; @@ -75,7 +80,7 @@ public void mergeWith(ConfigurationType other) { } private void mergeFlagsWith(ConfigurationType other) { - setFlagsFromOtherUsingPredicate(other, (our, their) -> our || their); + setFlagsFromOther(other, (our, their) -> our || their, ConfigurationMemberAccessibility::combine); } private void mergeFieldsWith(ConfigurationType other) { @@ -98,10 +103,10 @@ private void mergeFieldsWith(ConfigurationType other) { private void maybeRemoveFields(boolean hasAllDeclaredFields, boolean hasAllPublicFields) { if (hasAllDeclaredFields) { - removeFields(ConfigurationMemberKind.DECLARED); + removeFields(ConfigurationMemberDeclaration.DECLARED); } if (hasAllPublicFields) { - removeFields(ConfigurationMemberKind.PUBLIC); + removeFields(ConfigurationMemberDeclaration.PUBLIC); } } @@ -110,32 +115,34 @@ private void mergeMethodsWith(ConfigurationType other) { if (methods == null) { methods = new HashMap<>(); } - for (Map.Entry methodEntry : other.methods.entrySet()) { + for (Map.Entry methodEntry : other.methods.entrySet()) { methods.compute(methodEntry.getKey(), (key, value) -> { if (value != null) { - return value.equals(ConfigurationMemberKind.PRESENT) ? methodEntry.getValue() : value; + return value.intersect(methodEntry.getValue()); } else { return methodEntry.getValue(); } }); } } - maybeRemoveMethods(allDeclaredMethods, allPublicMethods, allDeclaredConstructors, allPublicConstructors); + maybeRemoveMethods(allDeclaredMethodsAccess, allPublicMethodsAccess, allDeclaredConstructorsAccess, allPublicConstructorsAccess); } - private void maybeRemoveMethods(boolean hasAllDeclaredMethods, boolean hasAllPublicMethods, boolean hasAllDeclaredConstructors, boolean hasAllPublicConstructors) { - if (hasAllDeclaredMethods) { - removeMethods(ConfigurationMemberKind.DECLARED, false); + private void maybeRemoveMethods(ConfigurationMemberAccessibility hasAllDeclaredMethods, ConfigurationMemberAccessibility hasAllPublicMethods, + ConfigurationMemberAccessibility hasAllDeclaredConstructors, + ConfigurationMemberAccessibility hasAllPublicConstructors) { + if (hasAllDeclaredMethods != ConfigurationMemberAccessibility.NONE) { + removeMethods(ConfigurationMemberDeclaration.DECLARED, hasAllDeclaredMethods, false); } - if (hasAllDeclaredConstructors) { - removeMethods(ConfigurationMemberKind.DECLARED, true); + if (hasAllDeclaredConstructors != ConfigurationMemberAccessibility.NONE) { + removeMethods(ConfigurationMemberDeclaration.DECLARED, hasAllDeclaredConstructors, true); } - if (hasAllPublicMethods) { - removeMethods(ConfigurationMemberKind.PUBLIC, false); + if (hasAllPublicMethods != ConfigurationMemberAccessibility.NONE) { + removeMethods(ConfigurationMemberDeclaration.PUBLIC, hasAllPublicMethods, false); } - if (hasAllPublicConstructors) { - removeMethods(ConfigurationMemberKind.PUBLIC, true); + if (hasAllPublicConstructors != ConfigurationMemberAccessibility.NONE) { + removeMethods(ConfigurationMemberDeclaration.PUBLIC, hasAllPublicConstructors, true); } } @@ -148,7 +155,7 @@ public void removeAll(ConfigurationType other) { } private void removeFlags(ConfigurationType other) { - setFlagsFromOtherUsingPredicate(other, (our, their) -> our && !their); + setFlagsFromOther(other, (our, their) -> our && !their, ConfigurationMemberAccessibility::remove); } private void removeFields(ConfigurationType other) { @@ -164,8 +171,8 @@ private void removeFields(ConfigurationType other) { } private void removeMethods(ConfigurationType other) { - maybeRemoveMethods(allDeclaredMethods || other.allDeclaredMethods, allPublicMethods || other.allPublicMethods, - allDeclaredConstructors || other.allDeclaredConstructors, allPublicConstructors || other.allPublicConstructors); + maybeRemoveMethods(allDeclaredMethodsAccess.combine(other.allDeclaredMethodsAccess), allPublicMethodsAccess.combine(other.allPublicMethodsAccess), + allDeclaredConstructorsAccess.combine(other.allDeclaredConstructorsAccess), allPublicConstructorsAccess.combine(other.allPublicConstructorsAccess)); if (methods != null && other.methods != null) { methods.entrySet().removeAll(other.methods.entrySet()); if (methods.isEmpty()) { @@ -174,15 +181,16 @@ private void removeMethods(ConfigurationType other) { } } - private void setFlagsFromOtherUsingPredicate(ConfigurationType other, BiPredicate predicate) { - allDeclaredClasses = predicate.test(allDeclaredClasses, other.allDeclaredClasses); - allPublicClasses = predicate.test(allPublicClasses, other.allPublicClasses); - allDeclaredFields = predicate.test(allDeclaredFields, other.allDeclaredFields); - allPublicFields = predicate.test(allPublicFields, other.allPublicFields); - allDeclaredMethods = predicate.test(allDeclaredMethods, other.allDeclaredMethods); - allPublicMethods = predicate.test(allPublicMethods, other.allPublicMethods); - allDeclaredConstructors = predicate.test(allDeclaredConstructors, other.allDeclaredConstructors); - allPublicConstructors = predicate.test(allPublicConstructors, other.allPublicConstructors); + private void setFlagsFromOther(ConfigurationType other, BiPredicate flagPredicate, + BiFunction accessCombiner) { + allDeclaredClasses = flagPredicate.test(allDeclaredClasses, other.allDeclaredClasses); + allPublicClasses = flagPredicate.test(allPublicClasses, other.allPublicClasses); + allDeclaredFields = flagPredicate.test(allDeclaredFields, other.allDeclaredFields); + allPublicFields = flagPredicate.test(allPublicFields, other.allPublicFields); + allDeclaredMethodsAccess = accessCombiner.apply(allDeclaredMethodsAccess, other.allDeclaredMethodsAccess); + allPublicMethodsAccess = accessCombiner.apply(allPublicMethodsAccess, other.allPublicMethodsAccess); + allDeclaredConstructorsAccess = accessCombiner.apply(allDeclaredConstructorsAccess, other.allDeclaredConstructorsAccess); + allPublicConstructorsAccess = accessCombiner.apply(allPublicConstructorsAccess, other.allPublicConstructorsAccess); } public boolean isEmpty() { @@ -190,16 +198,18 @@ public boolean isEmpty() { } private boolean allFlagsFalse() { - return !(allDeclaredClasses || allPublicClasses || allDeclaredFields || allPublicFields || allDeclaredMethods || allPublicMethods || allDeclaredConstructors || allPublicConstructors); + return !(allDeclaredClasses || allPublicClasses || allDeclaredFields || allPublicFields || + allDeclaredMethodsAccess != ConfigurationMemberAccessibility.NONE || allPublicMethodsAccess != ConfigurationMemberAccessibility.NONE || + allDeclaredConstructorsAccess != ConfigurationMemberAccessibility.NONE || allPublicConstructorsAccess != ConfigurationMemberAccessibility.NONE); } public String getQualifiedJavaName() { return qualifiedJavaName; } - public void addField(String name, ConfigurationMemberKind memberKind, boolean finalButWritable) { + public void addField(String name, ConfigurationMemberDeclaration memberKind, boolean finalButWritable) { if (!finalButWritable) { - if ((memberKind.includes(ConfigurationMemberKind.DECLARED) && haveAllDeclaredFields()) || (memberKind.includes(ConfigurationMemberKind.PUBLIC) && haveAllPublicFields())) { + if ((memberKind.includes(ConfigurationMemberDeclaration.DECLARED) && haveAllDeclaredFields()) || (memberKind.includes(ConfigurationMemberDeclaration.PUBLIC) && haveAllPublicFields())) { fields = maybeRemove(fields, map -> { FieldInfo fieldInfo = map.get(name); if (fieldInfo != null && !fieldInfo.isFinalButWritable()) { @@ -217,16 +227,34 @@ public void addField(String name, ConfigurationMemberKind memberKind, boolean fi : FieldInfo.get(memberKind, finalButWritable)); } - public void addMethodsWithName(String name, ConfigurationMemberKind memberKind) { - addMethod(name, null, memberKind); + public void addMethodsWithName(String name, ConfigurationMemberDeclaration memberKind) { + addMethod(name, null, memberKind, ConfigurationMemberAccessibility.ACCESSED); } - public void addMethod(String name, String internalSignature, ConfigurationMemberKind memberKind) { + public void addMethodsWithName(String name, ConfigurationMemberDeclaration memberKind, ConfigurationMemberAccessibility accessKind) { + addMethod(name, null, memberKind, accessKind); + } + + public void addMethod(String name, String internalSignature, ConfigurationMemberDeclaration memberKind) { + addMethod(name, internalSignature, memberKind, ConfigurationMemberAccessibility.ACCESSED); + } + + public void addMethod(String name, String internalSignature, ConfigurationMemberDeclaration memberKind, ConfigurationMemberAccessibility accessKind) { + ConfigurationMemberInfo kind = ConfigurationMemberInfo.get(memberKind, accessKind); boolean matchesAllSignatures = (internalSignature == null); - if (ConfigurationMethod.isConstructorName(name) ? (haveAllDeclaredConstructors() || (memberKind.includes(ConfigurationMemberKind.PUBLIC) && haveAllPublicConstructors())) - : ((memberKind.includes(ConfigurationMemberKind.DECLARED) && haveAllDeclaredMethods()) || (memberKind.includes(ConfigurationMemberKind.PUBLIC) && haveAllPublicMethods()))) { + if (ConfigurationMethod.isConstructorName(name) ? hasAllConstructors(memberKind, accessKind) : hasAllMethods(memberKind, accessKind)) { if (!matchesAllSignatures) { - methods = maybeRemove(methods, map -> map.remove(new ConfigurationMethod(name, internalSignature))); + if (accessKind == ConfigurationMemberAccessibility.ACCESSED) { + methods = maybeRemove(methods, map -> map.remove(new ConfigurationMethod(name, internalSignature))); + } else if (accessKind == ConfigurationMemberAccessibility.QUERIED) { + methods = maybeRemove(methods, map -> { + ConfigurationMethod method = new ConfigurationMethod(name, internalSignature); + /* Querying all methods should not remove individually accessed methods. */ + if (map.containsKey(method) && map.get(method).getAccessKind() == ConfigurationMemberAccessibility.QUERIED) { + map.remove(method); + } + }); + } } return; } @@ -235,16 +263,26 @@ public void addMethod(String name, String internalSignature, ConfigurationMember } ConfigurationMethod method = new ConfigurationMethod(name, internalSignature); if (matchesAllSignatures) { // remove any methods that the new entry matches - methods.compute(method, (k, v) -> v != null ? memberKind.union(v) : memberKind); + methods.compute(method, (k, v) -> v != null ? kind.union(v) : kind); methods = maybeRemove(methods, map -> map.entrySet().removeIf(entry -> name.equals(entry.getKey().getName()) && - memberKind.includes(entry.getValue()) && !method.equals(entry.getKey()))); + kind.includes(entry.getValue()) && !method.equals(entry.getKey()))); } else { - methods.compute(method, (k, v) -> v != null ? memberKind.intersect(v) : memberKind); + methods.compute(method, (k, v) -> v != null ? kind.intersect(v) : kind); } assert methods.containsKey(method); } - public ConfigurationMemberKind getMethodKindIfPresent(ConfigurationMethod method) { + private boolean hasAllConstructors(ConfigurationMemberDeclaration memberKind, ConfigurationMemberAccessibility accessKind) { + return (memberKind.includes(ConfigurationMemberDeclaration.DECLARED) && allDeclaredConstructorsAccess.includes(accessKind)) || + (memberKind.includes(ConfigurationMemberDeclaration.PUBLIC) && allPublicConstructorsAccess.includes(accessKind)); + } + + private boolean hasAllMethods(ConfigurationMemberDeclaration memberKind, ConfigurationMemberAccessibility accessKind) { + return (memberKind.includes(ConfigurationMemberDeclaration.DECLARED) && allDeclaredMethodsAccess.includes(accessKind)) || + (memberKind.includes(ConfigurationMemberDeclaration.PUBLIC) && allPublicMethodsAccess.includes(accessKind)); + } + + public ConfigurationMemberInfo getMethodKindIfPresent(ConfigurationMethod method) { return methods == null ? null : methods.get(method); } @@ -278,48 +316,48 @@ public boolean haveAllPublicFields() { public void setAllDeclaredFields() { this.allDeclaredFields = true; - removeFields(ConfigurationMemberKind.DECLARED); + removeFields(ConfigurationMemberDeclaration.DECLARED); } public void setAllPublicFields() { this.allPublicFields = true; - removeFields(ConfigurationMemberKind.PUBLIC); - } - - public boolean haveAllDeclaredMethods() { - return allDeclaredMethods; + removeFields(ConfigurationMemberDeclaration.PUBLIC); } - public boolean haveAllPublicMethods() { - return allPublicMethods; - } - - public void setAllDeclaredMethods() { - this.allDeclaredMethods = true; - removeMethods(ConfigurationMemberKind.DECLARED, false); + public void setAllDeclaredMethods(ConfigurationMemberAccessibility accessKind) { + if (!this.allDeclaredMethodsAccess.includes(accessKind)) { + this.allDeclaredMethodsAccess = accessKind; + removeMethods(ConfigurationMemberDeclaration.DECLARED, accessKind, false); + } } - public void setAllPublicMethods() { - this.allPublicMethods = true; - removeMethods(ConfigurationMemberKind.PUBLIC, false); + public void setAllPublicMethods(ConfigurationMemberAccessibility accessKind) { + if (!this.allPublicMethodsAccess.includes(accessKind)) { + this.allPublicMethodsAccess = accessKind; + removeMethods(ConfigurationMemberDeclaration.PUBLIC, accessKind, false); + } } - public boolean haveAllDeclaredConstructors() { - return allDeclaredConstructors; + public ConfigurationMemberAccessibility getAllDeclaredConstructors() { + return allDeclaredConstructorsAccess; } - public boolean haveAllPublicConstructors() { - return allPublicConstructors; + public ConfigurationMemberAccessibility getAllPublicConstructors() { + return allPublicConstructorsAccess; } - public void setAllDeclaredConstructors() { - this.allDeclaredConstructors = true; - removeMethods(ConfigurationMemberKind.DECLARED, true); + public void setAllDeclaredConstructors(ConfigurationMemberAccessibility accessKind) { + if (!this.allDeclaredConstructorsAccess.includes(accessKind)) { + this.allDeclaredConstructorsAccess = accessKind; + removeMethods(ConfigurationMemberDeclaration.DECLARED, accessKind, true); + } } - public void setAllPublicConstructors() { - this.allPublicConstructors = true; - removeMethods(ConfigurationMemberKind.PUBLIC, true); + public void setAllPublicConstructors(ConfigurationMemberAccessibility accessKind) { + if (!this.allPublicConstructorsAccess.includes(accessKind)) { + this.allPublicConstructorsAccess = accessKind; + removeMethods(ConfigurationMemberDeclaration.PUBLIC, accessKind, true); + } } @Override @@ -331,27 +369,46 @@ public void printJson(JsonWriter writer) throws IOException { writer.quote("name").append(':').quote(qualifiedJavaName); optionallyPrintJsonBoolean(writer, haveAllDeclaredFields(), "allDeclaredFields"); optionallyPrintJsonBoolean(writer, haveAllPublicFields(), "allPublicFields"); - optionallyPrintJsonBoolean(writer, haveAllDeclaredMethods(), "allDeclaredMethods"); - optionallyPrintJsonBoolean(writer, haveAllPublicMethods(), "allPublicMethods"); - optionallyPrintJsonBoolean(writer, haveAllDeclaredConstructors(), "allDeclaredConstructors"); - optionallyPrintJsonBoolean(writer, haveAllPublicConstructors(), "allPublicConstructors"); + optionallyPrintJsonBoolean(writer, allDeclaredMethodsAccess == ConfigurationMemberAccessibility.ACCESSED, "allDeclaredMethods"); + optionallyPrintJsonBoolean(writer, allPublicMethodsAccess == ConfigurationMemberAccessibility.ACCESSED, "allPublicMethods"); + optionallyPrintJsonBoolean(writer, allDeclaredConstructorsAccess == ConfigurationMemberAccessibility.ACCESSED, "allDeclaredConstructors"); + optionallyPrintJsonBoolean(writer, allPublicConstructorsAccess == ConfigurationMemberAccessibility.ACCESSED, "allPublicConstructors"); optionallyPrintJsonBoolean(writer, haveAllDeclaredClasses(), "allDeclaredClasses"); optionallyPrintJsonBoolean(writer, haveAllPublicClasses(), "allPublicClasses"); + optionallyPrintJsonBoolean(writer, allDeclaredMethodsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllDeclaredMethods"); + optionallyPrintJsonBoolean(writer, allPublicMethodsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllPublicMethods"); + optionallyPrintJsonBoolean(writer, allDeclaredConstructorsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllDeclaredConstructors"); + optionallyPrintJsonBoolean(writer, allPublicConstructorsAccess == ConfigurationMemberAccessibility.QUERIED, "queryAllPublicConstructors"); if (fields != null) { writer.append(',').newline().quote("fields").append(':'); JsonPrinter.printCollection(writer, fields.entrySet(), Map.Entry.comparingByKey(), ConfigurationType::printField); } if (methods != null) { - writer.append(',').newline().quote("methods").append(':'); - JsonPrinter.printCollection(writer, - methods.keySet(), - Comparator.comparing(ConfigurationMethod::getName).thenComparing(Comparator.nullsFirst(Comparator.comparing(ConfigurationMethod::getInternalSignature))), - JsonPrintable::printJson); + Set accessedMethods = getMethodsForAccessKind(ConfigurationMemberAccessibility.ACCESSED); + if (!accessedMethods.isEmpty()) { + writer.append(',').newline().quote("methods").append(':'); + JsonPrinter.printCollection(writer, + accessedMethods, + Comparator.comparing(ConfigurationMethod::getName).thenComparing(Comparator.nullsFirst(Comparator.comparing(ConfigurationMethod::getInternalSignature))), + JsonPrintable::printJson); + } + Set queriedMethods = getMethodsForAccessKind(ConfigurationMemberAccessibility.QUERIED); + if (!queriedMethods.isEmpty()) { + writer.append(',').newline().quote("queriedMethods").append(':'); + JsonPrinter.printCollection(writer, + queriedMethods, + Comparator.comparing(ConfigurationMethod::getName).thenComparing(Comparator.nullsFirst(Comparator.comparing(ConfigurationMethod::getInternalSignature))), + JsonPrintable::printJson); + } } writer.append('}').unindent().newline(); } + private Set getMethodsForAccessKind(ConfigurationMemberAccessibility accessKind) { + return methods.entrySet().stream().filter(e -> e.getValue().getAccessKind() == accessKind).map(Map.Entry::getKey).collect(Collectors.toSet()); + } + private static void printField(Map.Entry entry, JsonWriter w) throws IOException { w.append('{').quote("name").append(':').quote(entry.getKey()); if (entry.getValue().isFinalButWritable()) { @@ -366,12 +423,13 @@ private static void optionallyPrintJsonBoolean(JsonWriter writer, boolean predic } } - private void removeFields(ConfigurationMemberKind memberKind) { + private void removeFields(ConfigurationMemberDeclaration memberKind) { fields = maybeRemove(fields, map -> map.values().removeIf(v -> memberKind.includes(v.getKind()))); } - private void removeMethods(ConfigurationMemberKind memberKind, boolean constructors) { - methods = maybeRemove(methods, map -> map.entrySet().removeIf(entry -> entry.getKey().isConstructor() == constructors && memberKind.includes(entry.getValue()))); + private void removeMethods(ConfigurationMemberDeclaration memberKind, ConfigurationMemberAccessibility accessKind, boolean constructors) { + ConfigurationMemberInfo kind = ConfigurationMemberInfo.get(memberKind, accessKind); + methods = maybeRemove(methods, map -> map.entrySet().removeIf(entry -> entry.getKey().isConstructor() == constructors && kind.includes(entry.getValue()))); } private static Map maybeRemove(Map fromMap, Consumer> action) { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/FieldInfo.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/FieldInfo.java index 9bf58e60cd9d..e7d38150674c 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/FieldInfo.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/FieldInfo.java @@ -24,27 +24,29 @@ */ package com.oracle.svm.configure.config; +import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberDeclaration; + public final class FieldInfo { private static final FieldInfo[] FINAL_NOT_WRITABLE_CACHE; static { - ConfigurationMemberKind[] values = ConfigurationMemberKind.values(); + ConfigurationMemberDeclaration[] values = ConfigurationMemberDeclaration.values(); FINAL_NOT_WRITABLE_CACHE = new FieldInfo[values.length]; - for (ConfigurationMemberKind value : values) { + for (ConfigurationMemberDeclaration value : values) { FINAL_NOT_WRITABLE_CACHE[value.ordinal()] = new FieldInfo(value, false); } } - static FieldInfo get(ConfigurationMemberKind kind, boolean finalButWritable) { + static FieldInfo get(ConfigurationMemberDeclaration kind, boolean finalButWritable) { if (finalButWritable) { // assumed to be rare return new FieldInfo(kind, finalButWritable); } return FINAL_NOT_WRITABLE_CACHE[kind.ordinal()]; } - private final ConfigurationMemberKind kind; + private final ConfigurationMemberDeclaration kind; private final boolean finalButWritable; - private FieldInfo(ConfigurationMemberKind kind, boolean finalButWritable) { + private FieldInfo(ConfigurationMemberDeclaration kind, boolean finalButWritable) { this.kind = kind; this.finalButWritable = finalButWritable; } @@ -66,7 +68,7 @@ public FieldInfo newWithDifferencesFrom(FieldInfo other) { return get(kind, newFinalButWritable); } - public ConfigurationMemberKind getKind() { + public ConfigurationMemberDeclaration getKind() { return kind; } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java index 64373a73d660..17884c0ccdf8 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java @@ -28,6 +28,8 @@ import org.graalvm.nativeimage.impl.ConfigurationCondition; +import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberAccessibility; +import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberDeclaration; import com.oracle.svm.core.TypeResult; import com.oracle.svm.core.configure.ReflectionConfigurationParserDelegate; @@ -58,29 +60,32 @@ public void registerType(ConfigurationType type) { @Override public void registerField(ConfigurationType type, String fieldName, boolean finalButWritable) { - type.addField(fieldName, ConfigurationMemberKind.PRESENT, finalButWritable); + type.addField(fieldName, ConfigurationMemberDeclaration.PRESENT, finalButWritable); } @Override - public boolean registerAllMethodsWithName(ConfigurationType type, String methodName) { - type.addMethodsWithName(methodName, ConfigurationMemberKind.PRESENT); + public boolean registerAllMethodsWithName(boolean queriedOnly, ConfigurationType type, String methodName) { + type.addMethodsWithName(methodName, ConfigurationMemberDeclaration.PRESENT, queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); return true; } @Override - public boolean registerAllConstructors(ConfigurationType type) { - type.addMethodsWithName(ConfigurationMethod.CONSTRUCTOR_NAME, ConfigurationMemberKind.PRESENT); + public boolean registerAllConstructors(boolean queriedOnly, ConfigurationType type) { + type.addMethodsWithName(ConfigurationMethod.CONSTRUCTOR_NAME, ConfigurationMemberDeclaration.PRESENT, + queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); return true; } @Override - public void registerMethod(ConfigurationType type, String methodName, List methodParameterTypes) { - type.addMethod(methodName, ConfigurationMethod.toInternalParamsSignature(methodParameterTypes), ConfigurationMemberKind.PRESENT); + public void registerMethod(boolean queriedOnly, ConfigurationType type, String methodName, List methodParameterTypes) { + type.addMethod(methodName, ConfigurationMethod.toInternalParamsSignature(methodParameterTypes), ConfigurationMemberDeclaration.PRESENT, + queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override - public void registerConstructor(ConfigurationType type, List methodParameterTypes) { - type.addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, ConfigurationMethod.toInternalParamsSignature(methodParameterTypes), ConfigurationMemberKind.PRESENT); + public void registerConstructor(boolean queriedOnly, ConfigurationType type, List methodParameterTypes) { + type.addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, ConfigurationMethod.toInternalParamsSignature(methodParameterTypes), ConfigurationMemberDeclaration.PRESENT, + queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override @@ -104,23 +109,23 @@ public void registerDeclaredFields(ConfigurationType type) { } @Override - public void registerPublicMethods(ConfigurationType type) { - type.setAllPublicMethods(); + public void registerPublicMethods(boolean queriedOnly, ConfigurationType type) { + type.setAllPublicMethods(queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override - public void registerDeclaredMethods(ConfigurationType type) { - type.setAllDeclaredMethods(); + public void registerDeclaredMethods(boolean queriedOnly, ConfigurationType type) { + type.setAllDeclaredMethods(queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override - public void registerPublicConstructors(ConfigurationType type) { - type.setAllPublicConstructors(); + public void registerPublicConstructors(boolean queriedOnly, ConfigurationType type) { + type.setAllPublicConstructors(queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override - public void registerDeclaredConstructors(ConfigurationType type) { - type.setAllDeclaredConstructors(); + public void registerDeclaredConstructors(boolean queriedOnly, ConfigurationType type) { + type.setAllDeclaredConstructors(queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java index 8ae0652b5b2d..7138da1ce204 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java @@ -32,7 +32,7 @@ import org.graalvm.compiler.phases.common.LazyValue; import org.graalvm.nativeimage.impl.ConfigurationCondition; -import com.oracle.svm.configure.config.ConfigurationMemberKind; +import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberDeclaration; import com.oracle.svm.configure.config.ConfigurationMethod; import com.oracle.svm.configure.config.TypeConfiguration; @@ -85,7 +85,7 @@ void processEntry(Map entry) { } String declaringClass = (String) entry.get("declaring_class"); String declaringClassOrClazz = (declaringClass != null) ? declaringClass : clazz; - ConfigurationMemberKind memberKind = (declaringClass != null) ? ConfigurationMemberKind.DECLARED : ConfigurationMemberKind.PRESENT; + ConfigurationMemberDeclaration memberKind = (declaringClass != null) ? ConfigurationMemberDeclaration.DECLARED : ConfigurationMemberDeclaration.PRESENT; TypeConfiguration config = configuration; switch (function) { case "GetStaticMethodID": diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java index 40795fc9e4f7..107919386380 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java @@ -35,7 +35,8 @@ import org.graalvm.compiler.phases.common.LazyValue; import org.graalvm.nativeimage.impl.ConfigurationCondition; -import com.oracle.svm.configure.config.ConfigurationMemberKind; +import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberAccessibility; +import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberDeclaration; import com.oracle.svm.configure.config.ConfigurationMethod; import com.oracle.svm.configure.config.ProxyConfiguration; import com.oracle.svm.configure.config.ResourceConfiguration; @@ -117,7 +118,8 @@ public void processEntry(Map entry) { if (advisor.shouldIgnore(lazyValue(clazz), lazyValue(callerClass))) { return; } - ConfigurationMemberKind memberKind = ConfigurationMemberKind.PUBLIC; + ConfigurationMemberDeclaration memberKind = ConfigurationMemberDeclaration.PUBLIC; + ConfigurationMemberAccessibility accessKind = ConfigurationMemberAccessibility.QUERIED; String clazzOrDeclaringClass = entry.containsKey("declaring_class") ? (String) entry.get("declaring_class") : clazz; switch (function) { case "getDeclaredFields": { @@ -130,21 +132,23 @@ public void processEntry(Map entry) { } case "getDeclaredMethods": { - configuration.getOrCreateType(condition, clazz).setAllDeclaredMethods(); + configuration.getOrCreateType(condition, clazz).setAllDeclaredMethods(accessKind); break; } case "asInterfaceInstance": + accessKind = ConfigurationMemberAccessibility.ACCESSED; + // fallthrough case "getMethods": { - configuration.getOrCreateType(condition, clazz).setAllPublicMethods(); + configuration.getOrCreateType(condition, clazz).setAllPublicMethods(accessKind); break; } case "getDeclaredConstructors": { - configuration.getOrCreateType(condition, clazz).setAllDeclaredConstructors(); + configuration.getOrCreateType(condition, clazz).setAllDeclaredConstructors(accessKind); break; } case "getConstructors": { - configuration.getOrCreateType(condition, clazz).setAllPublicConstructors(); + configuration.getOrCreateType(condition, clazz).setAllPublicConstructors(accessKind); break; } @@ -161,7 +165,7 @@ public void processEntry(Map entry) { case "findFieldHandle": case "unreflectField": case "getDeclaredField": - memberKind = "findFieldHandle".equals(function) ? ConfigurationMemberKind.PRESENT : ConfigurationMemberKind.DECLARED; + memberKind = "findFieldHandle".equals(function) ? ConfigurationMemberDeclaration.PRESENT : ConfigurationMemberDeclaration.DECLARED; // fall through case "getField": { configuration.getOrCreateType(condition, clazzOrDeclaringClass).addField(singleElement(args), memberKind, false); @@ -173,16 +177,18 @@ public void processEntry(Map entry) { case "getDeclaredMethod": case "findMethodHandle": - memberKind = "findMethodHandle".equals(function) ? ConfigurationMemberKind.PRESENT : ConfigurationMemberKind.DECLARED; + case "invokeMethod": + memberKind = "getDeclaredMethod".equals(function) ? ConfigurationMemberDeclaration.DECLARED : ConfigurationMemberDeclaration.PRESENT; // fall through case "getMethod": { + accessKind = (function.equals("invokeMethod") || function.equals("findMethodHandle")) ? ConfigurationMemberAccessibility.ACCESSED : ConfigurationMemberAccessibility.QUERIED; expectSize(args, 2); String name = (String) args.get(0); List parameterTypes = (List) args.get(1); if (parameterTypes == null) { // tolerated and equivalent to no parameter types parameterTypes = Collections.emptyList(); } - configuration.getOrCreateType(condition, clazzOrDeclaringClass).addMethod(name, SignatureUtil.toInternalSignature(parameterTypes), memberKind); + configuration.getOrCreateType(condition, clazzOrDeclaringClass).addMethod(name, SignatureUtil.toInternalSignature(parameterTypes), memberKind, accessKind); if (!clazzOrDeclaringClass.equals(clazz)) { configuration.getOrCreateType(condition, clazz); } @@ -191,16 +197,18 @@ public void processEntry(Map entry) { case "getDeclaredConstructor": case "findConstructorHandle": - memberKind = "findConstructorHandle".equals(function) ? ConfigurationMemberKind.PRESENT : ConfigurationMemberKind.DECLARED; + case "invokeConstructor": + memberKind = "getDeclaredConstructor".equals(function) ? ConfigurationMemberDeclaration.DECLARED : ConfigurationMemberDeclaration.PRESENT; // fall through case "getConstructor": { + accessKind = (function.equals("invokeConstructor") || function.equals("findConstructorHandle")) ? ConfigurationMemberAccessibility.ACCESSED : ConfigurationMemberAccessibility.QUERIED; List parameterTypes = singleElement(args); if (parameterTypes == null) { // tolerated and equivalent to no parameter types parameterTypes = Collections.emptyList(); } String signature = SignatureUtil.toInternalSignature(parameterTypes); assert clazz.equals(clazzOrDeclaringClass) : "Constructor can only be accessed via declaring class"; - configuration.getOrCreateType(condition, clazzOrDeclaringClass).addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, signature, memberKind); + configuration.getOrCreateType(condition, clazzOrDeclaringClass).addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, signature, memberKind, accessKind); break; } @@ -231,7 +239,8 @@ public void processEntry(Map entry) { if (clazz.equals("java.lang.reflect.Array")) { // reflective array instantiation configuration.getOrCreateType(condition, (String) args.get(0)); } else { - configuration.getOrCreateType(condition, clazz).addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, "()V", ConfigurationMemberKind.DECLARED); + configuration.getOrCreateType(condition, clazz).addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, "()V", ConfigurationMemberDeclaration.DECLARED, + ConfigurationMemberAccessibility.ACCESSED); } break; } @@ -257,7 +266,7 @@ private void addFullyQualifiedDeclaredMethod(String descriptor) { String qualifiedClass = descriptor.substring(0, classend); String methodName = descriptor.substring(classend + 1, sigbegin); String signature = descriptor.substring(sigbegin); - configuration.getOrCreateType(ConfigurationCondition.alwaysTrue(), qualifiedClass).addMethod(methodName, signature, ConfigurationMemberKind.DECLARED); + configuration.getOrCreateType(ConfigurationCondition.alwaysTrue(), qualifiedClass).addMethod(methodName, signature, ConfigurationMemberDeclaration.DECLARED); } private void addDynamicProxy(List interfaceList, LazyValue callerClass) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index cdb3029f5e26..cd99fbebf10f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -616,4 +616,8 @@ protected void onValueUpdate(EconomicMap, Object> values, String ol SubstrateDiagnostics.updateInitialInvocationCounts(newValue); } }; + + @APIOption(name = "configure-reflection-metadata")// + @Option(help = "Limit method reflection metadata to configuration entries instead of including it for all reachable methods")// + public static final HostedOptionKey ConfigureReflectionMetadata = new HostedOptionKey<>(true); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java index e439cb9d6f0e..27be5a52b763 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoAccess.java @@ -293,10 +293,22 @@ public static void lookupCodeInfo(CodeInfo info, long ip, SimpleCodeInfoQueryRes } @Uninterruptible(reason = "Nonmovable object arrays are not visible to GC until installed.") - public static void setFrameInfo(CodeInfo info, NonmovableArray encodings, NonmovableObjectArray objectConstants, - NonmovableObjectArray> sourceClasses, NonmovableObjectArray sourceMethodNames, NonmovableObjectArray names) { + public static void setFrameInfo(CodeInfo info, NonmovableArray encodings) { CodeInfoImpl impl = cast(info); impl.setFrameInfoEncodings(encodings); + } + + public static void setCodeInfo(CodeInfo info, NonmovableArray index, NonmovableArray encodings, NonmovableArray referenceMapEncoding) { + CodeInfoImpl impl = cast(info); + impl.setCodeInfoIndex(index); + impl.setCodeInfoEncodings(encodings); + impl.setStackReferenceMapEncoding(referenceMapEncoding); + } + + @Uninterruptible(reason = "Nonmovable object arrays are not visible to GC until installed.") + public static void setEncodings(CodeInfo info, NonmovableObjectArray objectConstants, + NonmovableObjectArray> sourceClasses, NonmovableObjectArray sourceMethodNames, NonmovableObjectArray names) { + CodeInfoImpl impl = cast(info); impl.setFrameInfoObjectConstants(objectConstants); impl.setFrameInfoSourceClasses(sourceClasses); impl.setFrameInfoSourceMethodNames(sourceMethodNames); @@ -307,13 +319,6 @@ public static void setFrameInfo(CodeInfo info, NonmovableArray encodings, } } - public static void setCodeInfo(CodeInfo info, NonmovableArray index, NonmovableArray encodings, NonmovableArray referenceMapEncoding) { - CodeInfoImpl impl = cast(info); - impl.setCodeInfoIndex(index); - impl.setCodeInfoEncodings(encodings); - impl.setStackReferenceMapEncoding(referenceMapEncoding); - } - public static Log log(CodeInfo info, Log log) { return info.isNull() ? log.string("null") : log.string(CodeInfo.class.getName()).string("@").hex(info); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java index 8f305f1cd143..76f6a232252b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java @@ -26,15 +26,27 @@ import static com.oracle.svm.core.util.VMError.shouldNotReachHere; -import com.oracle.svm.core.heap.ReferenceMapIndex; +// Checkstyle: stop +import java.lang.reflect.Executable; +// Checkstyle: resume +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.compiler.core.common.util.TypeConversion; +import org.graalvm.compiler.core.common.util.UnsafeArrayTypeReader; import org.graalvm.compiler.options.Option; import org.graalvm.nativeimage.ImageSingletons; import com.oracle.svm.core.annotate.AlwaysInline; import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.c.NonmovableArrays; +import com.oracle.svm.core.heap.ReferenceMapIndex; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.jdk.Target_jdk_internal_reflect_ConstantPool; import com.oracle.svm.core.option.HostedOptionKey; +import com.oracle.svm.core.reflect.RuntimeReflectionConstructors; import com.oracle.svm.core.util.ByteArrayReader; import com.oracle.svm.core.util.Counter; import com.oracle.svm.core.util.NonmovableByteArrayReader; @@ -491,6 +503,138 @@ private static long advanceOffset(long entryOffset, int entryFlags) { static CodeInfoDecoderCounters counters() { return ImageSingletons.lookup(CodeInfoDecoderCounters.class); } + + public static Executable[] getMethodMetadata(DynamicHub declaringType) { + return getMethodMetadata(declaringType.getTypeID()); + } + + public static Executable[] getAllMethodMetadata() { + List allMethods = new ArrayList<>(); + for (int i = 0; i < ImageSingletons.lookup(MethodMetadataEncoding.class).getIndexEncoding().length / Integer.BYTES; ++i) { + allMethods.addAll(Arrays.asList(getMethodMetadata(i))); + } + return allMethods.toArray(new Executable[0]); + } + + /** + * The metadata for methods in the image is split into two arrays: one for the index and the + * other for data. The index contains an array of integers pointing to offsets in the data, and + * indexed by type ID. The data array contains arrays of method metadata, ordered by type ID, + * such that all methods declared by a class are stored consecutively. The data for a method is + * stored in the following format: + * + *
+     * {
+     *     int methodNameIndex;        // index in frameInfoSourceMethodNames ("" for constructors)
+     *     int modifiers;
+     *     int paramCount;
+     *     {
+     *         int paramTypeIndex;     // index in frameInfoSourceClasses
+     *     } paramTypes[paramCount];
+     *     int returnTypeIndex;        // index in frameInfoSourceClasses (void for constructors)
+     *     int exceptionTypeCount;
+     *     {
+     *         int exceptionTypeIndex; // index in frameInfoSourceClasses
+     *     } exceptionTypes[exceptionTypeCount];
+     *     // Annotation encodings (see {@link CodeInfoEncoder})
+     *     int annotationsLength;
+     *     byte[] annotationsEncoding[annotationsLength];
+     *     int parameterAnnotationsLength;
+     *     byte[] parameterAnnotationsEncoding[parameterAnnotationsLength];
+     * }
+     * 
+ */ + public static Executable[] getMethodMetadata(int typeID) { + CodeInfo info = CodeInfoTable.getImageCodeInfo(); + MethodMetadataEncoding encoding = ImageSingletons.lookup(MethodMetadataEncoding.class); + byte[] index = encoding.getIndexEncoding(); + UnsafeArrayTypeReader indexReader = UnsafeArrayTypeReader.create(index, Integer.BYTES * typeID, ByteArrayReader.supportsUnalignedMemoryAccess()); + int offset = indexReader.getS4(); + if (offset == MethodMetadataEncoder.NO_METHOD_METADATA) { + return new Executable[0]; + } + byte[] data = ImageSingletons.lookup(MethodMetadataEncoding.class).getMethodsEncoding(); + UnsafeArrayTypeReader dataReader = UnsafeArrayTypeReader.create(data, offset, ByteArrayReader.supportsUnalignedMemoryAccess()); + + int methodCount = dataReader.getUVInt(); + Executable[] methods = new Executable[methodCount]; + for (int i = 0; i < methodCount; ++i) { + int classIndex = dataReader.getSVInt(); + Class declaringClass = NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceClasses(info), classIndex); + + int nameIndex = dataReader.getSVInt(); + /* Interning the string to ensure JDK8 method search succeeds */ + String name = NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceMethodNames(info), nameIndex).intern(); + + int modifiers = dataReader.getUVInt(); + + int paramCount = dataReader.getUVInt(); + Class[] paramTypes = new Class[paramCount]; + for (int j = 0; j < paramCount; ++j) { + int paramTypeIndex = dataReader.getSVInt(); + paramTypes[j] = NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceClasses(info), paramTypeIndex); + } + + int returnTypeIndex = dataReader.getSVInt(); + Class returnType = NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceClasses(info), returnTypeIndex); + + int exceptionCount = dataReader.getUVInt(); + Class[] exceptionTypes = new Class[exceptionCount]; + for (int j = 0; j < exceptionCount; ++j) { + int exceptionTypeIndex = dataReader.getSVInt(); + exceptionTypes[j] = NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceClasses(info), exceptionTypeIndex); + } + + int signatureIndex = dataReader.getSVInt(); + String signature = NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceMethodNames(info), signatureIndex); + + int annotationsLength = dataReader.getUVInt(); + byte[] annotations = new byte[annotationsLength]; + for (int j = 0; j < annotationsLength; ++j) { + annotations[j] = (byte) dataReader.getS1(); + } + + int parameterAnnotationsLength = dataReader.getUVInt(); + byte[] parameterAnnotations = new byte[parameterAnnotationsLength]; + for (int j = 0; j < parameterAnnotationsLength; ++j) { + parameterAnnotations[j] = (byte) dataReader.getS1(); + } + + int typeAnnotationsLength = dataReader.getUVInt(); + byte[] typeAnnotations = new byte[typeAnnotationsLength]; + for (int j = 0; j < typeAnnotationsLength; ++j) { + typeAnnotations[j] = (byte) dataReader.getS1(); + } + + boolean parameterDataPresent = dataReader.getU1() == 1; + String[] parameterNames = null; + int[] parameterModifiers = null; + if (parameterDataPresent) { + int parameterCount = dataReader.getUVInt(); + parameterNames = new String[parameterCount]; + parameterModifiers = new int[parameterCount]; + for (int j = 0; j < paramCount; ++j) { + int parameterNameIndex = dataReader.getSVInt(); + parameterNames[j] = NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceMethodNames(info), parameterNameIndex); + parameterModifiers[j] = dataReader.getS4(); + } + } + + if (name.equals("")) { + assert returnType == void.class; + methods[i] = ImageSingletons.lookup(RuntimeReflectionConstructors.class).newConstructor(declaringClass, paramTypes, exceptionTypes, modifiers, signature, + annotations, parameterAnnotations, typeAnnotations, parameterNames, parameterModifiers); + } else { + methods[i] = ImageSingletons.lookup(RuntimeReflectionConstructors.class).newMethod(declaringClass, name, paramTypes, returnType, exceptionTypes, modifiers, signature, + annotations, parameterAnnotations, null, typeAnnotations, parameterNames, parameterModifiers); + } + } + return methods; + } + + public static Target_jdk_internal_reflect_ConstantPool getMetadataPseudoConstantPool() { + return new Target_jdk_internal_reflect_ConstantPool(); + } } class CodeInfoDecoderCounters { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java index bc4d7e60c59b..f43f2fe72f06 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoEncoder.java @@ -26,23 +26,32 @@ import static com.oracle.svm.core.util.VMError.shouldNotReachHere; +// Checkstyle: stop +import java.lang.reflect.Executable; +// Checkstyle: resume import java.util.BitSet; import java.util.TreeMap; import org.graalvm.compiler.code.CompilationResult; import org.graalvm.compiler.core.common.NumUtil; +import org.graalvm.compiler.core.common.util.FrequencyEncoder; import org.graalvm.compiler.core.common.util.TypeConversion; import org.graalvm.compiler.core.common.util.UnsafeArrayTypeWriter; import org.graalvm.compiler.options.Option; import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; import com.oracle.svm.core.CalleeSavedRegisters; import com.oracle.svm.core.ReservedRegisters; +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.annotate.Uninterruptible; import com.oracle.svm.core.c.NonmovableArray; import com.oracle.svm.core.c.NonmovableArrays; +import com.oracle.svm.core.c.NonmovableObjectArray; import com.oracle.svm.core.code.FrameInfoQueryResult.ValueInfo; import com.oracle.svm.core.code.FrameInfoQueryResult.ValueType; import com.oracle.svm.core.config.ConfigurationValues; @@ -97,6 +106,52 @@ public static class Counters { final Counter virtualObjectsCount = new Counter(group, "Number of virtual objects", "Number of virtual objects encoded"); } + public static final class Encoders { + final FrequencyEncoder objectConstants; + final FrequencyEncoder> sourceClasses; + final FrequencyEncoder sourceMethodNames; + final FrequencyEncoder names; + + private Encoders() { + this.objectConstants = FrequencyEncoder.createEqualityEncoder(); + this.sourceClasses = FrequencyEncoder.createEqualityEncoder(); + this.sourceMethodNames = FrequencyEncoder.createEqualityEncoder(); + if (FrameInfoDecoder.encodeDebugNames() || FrameInfoDecoder.encodeSourceReferences()) { + this.names = FrequencyEncoder.createEqualityEncoder(); + } else { + this.names = null; + } + } + + private void encodeAllAndInstall(CodeInfo target, ReferenceAdjuster adjuster) { + JavaConstant[] encodedJavaConstants = objectConstants.encodeAll(new JavaConstant[objectConstants.getLength()]); + Class[] sourceClassesArray = null; + String[] sourceMethodNamesArray = null; + String[] namesArray = null; + final boolean encodeDebugNames = FrameInfoDecoder.encodeDebugNames(); + if (encodeDebugNames || FrameInfoDecoder.encodeSourceReferences()) { + sourceClassesArray = sourceClasses.encodeAll(new Class[sourceClasses.getLength()]); + sourceMethodNamesArray = sourceMethodNames.encodeAll(new String[sourceMethodNames.getLength()]); + } + if (encodeDebugNames) { + namesArray = names.encodeAll(new String[names.getLength()]); + } + install(target, encodedJavaConstants, sourceClassesArray, sourceMethodNamesArray, namesArray, adjuster); + } + + @Uninterruptible(reason = "Nonmovable object arrays are not visible to GC until installed in target.") + private static void install(CodeInfo target, JavaConstant[] objectConstantsArray, Class[] sourceClassesArray, + String[] sourceMethodNamesArray, String[] namesArray, ReferenceAdjuster adjuster) { + + NonmovableObjectArray frameInfoObjectConstants = adjuster.copyOfObjectConstantArray(objectConstantsArray); + NonmovableObjectArray> frameInfoSourceClasses = (sourceClassesArray != null) ? adjuster.copyOfObjectArray(sourceClassesArray) : NonmovableArrays.nullArray(); + NonmovableObjectArray frameInfoSourceMethodNames = (sourceMethodNamesArray != null) ? adjuster.copyOfObjectArray(sourceMethodNamesArray) : NonmovableArrays.nullArray(); + NonmovableObjectArray frameInfoNames = (namesArray != null) ? adjuster.copyOfObjectArray(namesArray) : NonmovableArrays.nullArray(); + + CodeInfoAccess.setEncodings(target, frameInfoObjectConstants, frameInfoSourceClasses, frameInfoSourceMethodNames, frameInfoNames); + } + } + static class IPData { protected long ip; protected int frameSizeEncoding; @@ -108,15 +163,23 @@ static class IPData { } private final TreeMap entries; + private final Encoders encoders; private final FrameInfoEncoder frameInfoEncoder; + @Platforms(Platform.HOSTED_ONLY.class)// + private MethodMetadataEncoder methodMetadataEncoder; + private NonmovableArray codeInfoIndex; private NonmovableArray codeInfoEncodings; private NonmovableArray referenceMapEncoding; public CodeInfoEncoder(FrameInfoEncoder.Customization frameInfoCustomization) { this.entries = new TreeMap<>(); - this.frameInfoEncoder = new FrameInfoEncoder(frameInfoCustomization); + this.encoders = new Encoders(); + this.frameInfoEncoder = new FrameInfoEncoder(frameInfoCustomization, encoders); + if (SubstrateUtil.HOSTED) { + this.methodMetadataEncoder = new MethodMetadataEncoder(encoders); + } } public static int getEntryOffset(Infopoint infopoint) { @@ -173,6 +236,14 @@ public void addMethod(SharedMethod method, CompilationResult compilation, int co ImageSingletons.lookup(Counters.class).codeSize.add(compilation.getTargetCodeSize()); } + public void prepareMetadataForClass(Class clazz) { + methodMetadataEncoder.prepareMetadataForClass(clazz); + } + + public void prepareMetadataForMethod(SharedMethod method, Executable reflectMethod) { + methodMetadataEncoder.prepareMetadataForMethod(method, reflectMethod); + } + private IPData makeEntry(long ip) { IPData result = entries.get(ip); if (result == null) { @@ -184,8 +255,12 @@ private IPData makeEntry(long ip) { } public void encodeAllAndInstall(CodeInfo target, ReferenceAdjuster adjuster) { + encoders.encodeAllAndInstall(target, adjuster); encodeReferenceMaps(); - frameInfoEncoder.encodeAllAndInstall(target, adjuster); + frameInfoEncoder.encodeAllAndInstall(target); + if (SubstrateUtil.HOSTED) { + methodMetadataEncoder.encodeAllAndInstall(); + } encodeIPData(); install(target); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java index 7cdbc0c1d959..d70629670fc0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoTable.java @@ -278,6 +278,7 @@ public void duringSetup(DuringSetupAccess access) { ImageSingletons.add(RuntimeCodeInfoHistory.class, new RuntimeCodeInfoHistory()); ImageSingletons.add(RuntimeCodeCache.class, new RuntimeCodeCache()); ImageSingletons.add(RuntimeCodeInfoMemory.class, new RuntimeCodeInfoMemory()); + ImageSingletons.add(MethodMetadataEncoding.class, new MethodMetadataEncoding()); } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java index cacd1ced21e4..cc5eb40a8a18 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/FrameInfoEncoder.java @@ -24,15 +24,12 @@ */ package com.oracle.svm.core.code; -import static com.oracle.svm.core.util.VMError.shouldNotReachHere; - import java.util.ArrayList; import java.util.BitSet; import java.util.List; import java.util.Objects; import org.graalvm.compiler.core.common.LIRKind; -import org.graalvm.compiler.core.common.util.FrequencyEncoder; import org.graalvm.compiler.core.common.util.TypeConversion; import org.graalvm.compiler.core.common.util.UnsafeArrayTypeWriter; import org.graalvm.nativeimage.ImageSingletons; @@ -42,8 +39,8 @@ import com.oracle.svm.core.annotate.Uninterruptible; import com.oracle.svm.core.c.NonmovableArray; import com.oracle.svm.core.c.NonmovableArrays; -import com.oracle.svm.core.c.NonmovableObjectArray; import com.oracle.svm.core.code.CodeInfoEncoder.Counters; +import com.oracle.svm.core.code.CodeInfoEncoder.Encoders; import com.oracle.svm.core.code.FrameInfoQueryResult.ValueInfo; import com.oracle.svm.core.code.FrameInfoQueryResult.ValueType; import com.oracle.svm.core.config.ConfigurationValues; @@ -180,24 +177,12 @@ static class FrameData { private final Customization customization; private final List allDebugInfos; - private final FrequencyEncoder objectConstants; - private final FrequencyEncoder> sourceClasses; - private final FrequencyEncoder sourceMethodNames; - private final FrequencyEncoder names; + private final Encoders encoders; - protected FrameInfoEncoder(Customization customization) { + protected FrameInfoEncoder(Customization customization, Encoders encoders) { this.customization = customization; + this.encoders = encoders; this.allDebugInfos = new ArrayList<>(); - this.objectConstants = FrequencyEncoder.createEqualityEncoder(); - if (FrameInfoDecoder.encodeDebugNames() || FrameInfoDecoder.encodeSourceReferences()) { - this.sourceClasses = FrequencyEncoder.createEqualityEncoder(); - this.sourceMethodNames = FrequencyEncoder.createEqualityEncoder(); - this.names = FrequencyEncoder.createEqualityEncoder(); - } else { - this.sourceClasses = null; - this.sourceMethodNames = null; - this.names = null; - } } protected FrameData addDebugInfo(ResolvedJavaMethod method, Infopoint infopoint, int totalFrameSize) { @@ -223,15 +208,15 @@ protected FrameData addDebugInfo(ResolvedJavaMethod method, Infopoint infopoint, } for (FrameInfoQueryResult cur = data.frame; cur != null; cur = cur.caller) { - sourceClasses.addObject(cur.sourceClass); - sourceMethodNames.addObject(cur.sourceMethodName); + encoders.sourceClasses.addObject(cur.sourceClass); + encoders.sourceMethodNames.addObject(cur.sourceMethodName); if (encodeDebugNames) { for (ValueInfo valueInfo : cur.valueInfos) { if (valueInfo.name == null) { valueInfo.name = ""; } - names.addObject(valueInfo.name); + encoders.names.addObject(valueInfo.name); } } } @@ -280,7 +265,7 @@ private FrameInfoQueryResult addFrame(FrameData data, BytecodeFrame frame, boole SharedMethod method = (SharedMethod) frame.getMethod(); if (customization.shouldStoreMethod()) { result.deoptMethod = method; - objectConstants.addObject(SubstrateObjectConstant.forObject(method)); + encoders.objectConstants.addObject(SubstrateObjectConstant.forObject(method)); } result.deoptMethodOffset = method.getDeoptOffsetInImage(); @@ -383,7 +368,7 @@ private ValueInfo makeValueInfo(FrameData data, JavaKind kind, JavaValue v, bool * Collect all Object constants, which will be stored in a separate Object[] * array so that the GC can visit them. */ - objectConstants.addObject(constant); + encoders.objectConstants.addObject(constant); } } ImageSingletons.lookup(Counters.class).constantValueCount.inc(); @@ -394,7 +379,7 @@ private ValueInfo makeValueInfo(FrameData data, JavaKind kind, JavaValue v, bool result.data = virtualObject.getId(); makeVirtualObject(data, virtualObject, isDeoptEntry); } else { - throw shouldNotReachHere(); + throw VMError.shouldNotReachHere(); } return result; } @@ -576,34 +561,14 @@ private static int computeOffset(ArrayList valueInfos, int startIndex return result; } - protected void encodeAllAndInstall(CodeInfo target, ReferenceAdjuster adjuster) { - JavaConstant[] encodedJavaConstants = objectConstants.encodeAll(new JavaConstant[objectConstants.getLength()]); - Class[] sourceClassesArray = null; - String[] sourceMethodNamesArray = null; - String[] namesArray = null; - final boolean encodeDebugNames = FrameInfoDecoder.encodeDebugNames(); - if (encodeDebugNames || FrameInfoDecoder.encodeSourceReferences()) { - sourceClassesArray = sourceClasses.encodeAll(new Class[sourceClasses.getLength()]); - sourceMethodNamesArray = sourceMethodNames.encodeAll(new String[sourceMethodNames.getLength()]); - } - if (encodeDebugNames) { - namesArray = names.encodeAll(new String[names.getLength()]); - } + protected void encodeAllAndInstall(CodeInfo target) { NonmovableArray frameInfoEncodings = encodeFrameDatas(); - install(target, frameInfoEncodings, encodedJavaConstants, sourceClassesArray, sourceMethodNamesArray, namesArray, adjuster); + install(target, frameInfoEncodings); } @Uninterruptible(reason = "Nonmovable object arrays are not visible to GC until installed in target.") - private static void install(CodeInfo target, NonmovableArray frameInfoEncodings, JavaConstant[] objectConstantsArray, Class[] sourceClassesArray, - String[] sourceMethodNamesArray, String[] namesArray, ReferenceAdjuster adjuster) { - - NonmovableObjectArray frameInfoObjectConstants = adjuster.copyOfObjectConstantArray(objectConstantsArray); - NonmovableObjectArray> frameInfoSourceClasses = (sourceClassesArray != null) ? adjuster.copyOfObjectArray(sourceClassesArray) : NonmovableArrays.nullArray(); - NonmovableObjectArray frameInfoSourceMethodNames = (sourceMethodNamesArray != null) ? adjuster.copyOfObjectArray(sourceMethodNamesArray) : NonmovableArrays.nullArray(); - NonmovableObjectArray frameInfoNames = (namesArray != null) ? adjuster.copyOfObjectArray(namesArray) : NonmovableArrays.nullArray(); - - CodeInfoAccess.setFrameInfo(target, frameInfoEncodings, frameInfoObjectConstants, frameInfoSourceClasses, frameInfoSourceMethodNames, frameInfoNames); - + private static void install(CodeInfo target, NonmovableArray frameInfoEncodings) { + CodeInfoAccess.setFrameInfo(target, frameInfoEncodings); afterInstallation(target); } @@ -642,7 +607,7 @@ private void encodeFrameData(FrameData data, UnsafeArrayTypeWriter encodingBuffe int deoptMethodIndex; if (cur.deoptMethod != null) { - deoptMethodIndex = -1 - objectConstants.getIndex(SubstrateObjectConstant.forObject(cur.deoptMethod)); + deoptMethodIndex = -1 - encoders.objectConstants.getIndex(SubstrateObjectConstant.forObject(cur.deoptMethod)); assert deoptMethodIndex < 0; assert cur.deoptMethodOffset == cur.deoptMethod.getDeoptOffsetInImage(); } else { @@ -664,8 +629,8 @@ private void encodeFrameData(FrameData data, UnsafeArrayTypeWriter encodingBuffe final boolean encodeDebugNames = needLocalValues && FrameInfoDecoder.encodeDebugNames(); if (encodeDebugNames || FrameInfoDecoder.encodeSourceReferences()) { - final int classIndex = sourceClasses.getIndex(cur.sourceClass); - final int methodIndex = sourceMethodNames.getIndex(cur.sourceMethodName); + final int classIndex = encoders.sourceClasses.getIndex(cur.sourceClass); + final int methodIndex = encoders.sourceMethodNames.getIndex(cur.sourceMethodName); cur.sourceClassIndex = classIndex; cur.sourceMethodNameIndex = methodIndex; @@ -677,7 +642,7 @@ private void encodeFrameData(FrameData data, UnsafeArrayTypeWriter encodingBuffe if (encodeDebugNames) { for (ValueInfo valueInfo : cur.valueInfos) { - valueInfo.nameIndex = names.getIndex(valueInfo.name); + valueInfo.nameIndex = encoders.names.getIndex(valueInfo.name); encodingBuffer.putUV(valueInfo.nameIndex); } } @@ -690,7 +655,7 @@ private void encodeValues(ValueInfo[] valueInfos, UnsafeArrayTypeWriter encoding for (ValueInfo valueInfo : valueInfos) { if (valueInfo.type == ValueType.Constant) { if (valueInfo.kind == JavaKind.Object) { - valueInfo.data = objectConstants.getIndex(valueInfo.value); + valueInfo.data = encoders.objectConstants.getIndex(valueInfo.value); } else { valueInfo.data = encodePrimitiveConstant(valueInfo.value); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/MethodMetadataEncoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/MethodMetadataEncoder.java new file mode 100644 index 000000000000..1c115fed93de --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/MethodMetadataEncoder.java @@ -0,0 +1,589 @@ +/* + * Copyright (c) 2021, 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.code; + +// Checkstyle: allow reflection + +import static com.oracle.svm.core.util.VMError.shouldNotReachHere; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import org.graalvm.compiler.core.common.util.TypeConversion; +import org.graalvm.compiler.core.common.util.UnsafeArrayTypeWriter; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.util.GuardedAnnotationAccess; + +import com.oracle.svm.core.hub.DynamicHubSupport; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.meta.SharedType; +import com.oracle.svm.core.util.ByteArrayReader; +import com.oracle.svm.util.ReflectionUtil; + +import sun.invoke.util.Wrapper; +import sun.reflect.annotation.AnnotationType; +import sun.reflect.annotation.TypeAnnotation; +import sun.reflect.annotation.TypeAnnotationParser; + +@Platforms(Platform.HOSTED_ONLY.class) +public class MethodMetadataEncoder { + public static final int NO_METHOD_METADATA = -1; + + private CodeInfoEncoder.Encoders encoders; + private TreeMap> methodData; + + private byte[] methodDataEncoding; + private byte[] methodDataIndexEncoding; + + MethodMetadataEncoder(CodeInfoEncoder.Encoders encoders) { + this.encoders = encoders; + this.methodData = new TreeMap<>(Comparator.comparingLong(t -> t.getHub().getTypeID())); + } + + void encodeAllAndInstall() { + encodeMethodMetadata(); + ImageSingletons.lookup(MethodMetadataEncoding.class).setMethodsEncoding(methodDataEncoding); + ImageSingletons.lookup(MethodMetadataEncoding.class).setIndexEncoding(methodDataIndexEncoding); + } + + @SuppressWarnings("unchecked") + public void prepareMetadataForClass(Class clazz) { + encoders.sourceClasses.addObject(clazz); + if (clazz.isAnnotation()) { + try { + for (String valueName : AnnotationType.getInstance((Class) clazz).members().keySet()) { + encoders.sourceMethodNames.addObject(valueName); + } + } catch (LinkageError | RuntimeException t) { + // ignore + } + } + } + + private static final Method parseAllTypeAnnotations = ReflectionUtil.lookupMethod(TypeAnnotationParser.class, "parseAllTypeAnnotations", AnnotatedElement.class); + + public void prepareMetadataForMethod(SharedMethod method, Executable reflectMethod) { + if (reflectMethod instanceof Constructor) { + encoders.sourceMethodNames.addObject(""); + } else { + encoders.sourceMethodNames.addObject(reflectMethod.getName()); + } + encoders.sourceMethodNames.addObject(getSignature(reflectMethod)); + for (Parameter parameter : reflectMethod.getParameters()) { + encoders.sourceMethodNames.addObject(parameter.getName()); + } + + /* Register string values in annotations */ + registerStrings(GuardedAnnotationAccess.getDeclaredAnnotations(reflectMethod)); + for (Annotation[] annotations : reflectMethod.getParameterAnnotations()) { + registerStrings(annotations); + } + try { + for (TypeAnnotation typeAnnotation : (TypeAnnotation[]) parseAllTypeAnnotations.invoke(null, reflectMethod)) { + // Checkstyle: allow direct annotation access + registerStrings(typeAnnotation.getAnnotation()); + // Checkstyle: disallow direct annotation access + } + } catch (IllegalAccessException | InvocationTargetException e) { + throw shouldNotReachHere(); + } + + SharedType declaringType = (SharedType) method.getDeclaringClass(); + methodData.computeIfAbsent(declaringType, t -> new HashSet<>()).add(reflectMethod); + } + + private static final Method hasRealParameterData = ReflectionUtil.lookupMethod(Executable.class, "hasRealParameterData"); + + private void encodeMethodMetadata() { + UnsafeArrayTypeWriter dataEncodingBuffer = UnsafeArrayTypeWriter.create(ByteArrayReader.supportsUnalignedMemoryAccess()); + UnsafeArrayTypeWriter indexEncodingBuffer = UnsafeArrayTypeWriter.create(ByteArrayReader.supportsUnalignedMemoryAccess()); + long lastTypeID = -1; + for (Map.Entry> entry : methodData.entrySet()) { + SharedType declaringType = entry.getKey(); + Set methods = entry.getValue(); + long typeID = declaringType.getHub().getTypeID(); + assert typeID > lastTypeID; + lastTypeID++; + while (lastTypeID < typeID) { + indexEncodingBuffer.putS4(NO_METHOD_METADATA); + lastTypeID++; + } + long index = dataEncodingBuffer.getBytesWritten(); + indexEncodingBuffer.putS4(index); + dataEncodingBuffer.putUV(methods.size()); + for (Executable method : methods) { + Class declaringClass = method.getDeclaringClass(); + final int classIndex = encoders.sourceClasses.getIndex(declaringClass); + dataEncodingBuffer.putSV(classIndex); + + String name = method instanceof Constructor ? "" : ((Method) method).getName(); + final int nameIndex = encoders.sourceMethodNames.getIndex(name); + dataEncodingBuffer.putSV(nameIndex); + + dataEncodingBuffer.putUV(method.getModifiers()); + + Class[] parameterTypes = method.getParameterTypes(); + dataEncodingBuffer.putUV(parameterTypes.length); + for (Class parameterType : parameterTypes) { + final int paramClassIndex = encoders.sourceClasses.getIndex(parameterType); + dataEncodingBuffer.putSV(paramClassIndex); + } + + Class returnType = method instanceof Constructor ? void.class : ((Method) method).getReturnType(); + final int returnTypeIndex = encoders.sourceClasses.getIndex(returnType); + dataEncodingBuffer.putSV(returnTypeIndex); + + /* Only include types that are in the image (i.e. that can actually be thrown) */ + Class[] exceptionTypes = filterTypes(method.getExceptionTypes()); + dataEncodingBuffer.putUV(exceptionTypes.length); + for (Class exceptionClazz : exceptionTypes) { + final int exceptionClassIndex = encoders.sourceClasses.getIndex(exceptionClazz); + dataEncodingBuffer.putSV(exceptionClassIndex); + } + + final int signatureIndex = encoders.sourceMethodNames.getIndex(getSignature(method)); + dataEncodingBuffer.putSV(signatureIndex); + + try { + byte[] annotations = encodeAnnotations(GuardedAnnotationAccess.getDeclaredAnnotations(method)); + dataEncodingBuffer.putUV(annotations.length); + for (byte b : annotations) { + dataEncodingBuffer.putS1(b); + } + + byte[] parameterAnnotations = encodeParameterAnnotations(method.getParameterAnnotations()); + dataEncodingBuffer.putUV(parameterAnnotations.length); + for (byte b : parameterAnnotations) { + dataEncodingBuffer.putS1(b); + } + + byte[] typeAnnotations = encodeTypeAnnotations((TypeAnnotation[]) parseAllTypeAnnotations.invoke(null, method)); + dataEncodingBuffer.putUV(typeAnnotations.length); + for (byte b : typeAnnotations) { + dataEncodingBuffer.putS1(b); + } + + boolean parameterDataPresent = (boolean) hasRealParameterData.invoke(method); + dataEncodingBuffer.putU1(parameterDataPresent ? 1 : 0); + if (parameterDataPresent) { + Parameter[] parameters = method.getParameters(); + dataEncodingBuffer.putUV(parameters.length); + for (Parameter parameter : parameters) { + final int parameterNameIndex = encoders.sourceMethodNames.getIndex(parameter.getName()); + dataEncodingBuffer.putSV(parameterNameIndex); + dataEncodingBuffer.putS4(parameter.getModifiers()); + } + } + } catch (IllegalAccessException | InvocationTargetException e) { + throw shouldNotReachHere(); + } + } + } + while (lastTypeID < ImageSingletons.lookup(DynamicHubSupport.class).getMaxTypeId()) { + indexEncodingBuffer.putS4(NO_METHOD_METADATA); + lastTypeID++; + } + methodDataEncoding = new byte[TypeConversion.asS4(dataEncodingBuffer.getBytesWritten())]; + dataEncodingBuffer.toArray(methodDataEncoding); + methodDataIndexEncoding = new byte[TypeConversion.asS4(indexEncodingBuffer.getBytesWritten())]; + indexEncodingBuffer.toArray(methodDataIndexEncoding); + } + + private Class[] filterTypes(Class[] types) { + List> filteredTypes = new ArrayList<>(); + for (Class type : types) { + if (encoders.sourceClasses.contains(type)) { + filteredTypes.add(type); + } + } + return filteredTypes.toArray(new Class[0]); + } + + private static final Method getMethodSignature = ReflectionUtil.lookupMethod(Method.class, "getGenericSignature"); + private static final Method getConstructorSignature = ReflectionUtil.lookupMethod(Constructor.class, "getSignature"); + + private static String getSignature(Executable method) { + try { + return (String) (method instanceof Method ? getMethodSignature.invoke(method) : getConstructorSignature.invoke(method)); + } catch (IllegalAccessException | InvocationTargetException e) { + throw shouldNotReachHere(); + } + } + + /** + * The following methods encode annotations attached to a method or parameter in a format based + * on the one used internally by the JDK ({@link sun.reflect.annotation.AnnotationParser}). The + * format we use differs from that one on a few points, based on the fact that the JDK encoding + * is based on constant pool indices, which are not available in that form at runtime. + * + * Class and String values are represented by their index in the source metadata encoders + * instead of their constant pool indices. Additionally, Class objects are encoded directly + * instead of through their type signature. Primitive values are written directly into the + * encoding. This means that our encoding can be of a different length from the JDK one. + * + * We use a modified version of the ConstantPool and AnnotationParser classes to decode the + * data, since those are not used in their original functions at runtime. (see + * {@link com.oracle.svm.core.jdk.Target_jdk_internal_reflect_ConstantPool}) + */ + byte[] encodeAnnotations(Annotation[] annotations) throws InvocationTargetException, IllegalAccessException { + UnsafeArrayTypeWriter buf = UnsafeArrayTypeWriter.create(ByteArrayReader.supportsUnalignedMemoryAccess()); + + Annotation[] filteredAnnotations = filterAnnotations(annotations); + buf.putU2(filteredAnnotations.length); + for (Annotation annotation : filteredAnnotations) { + encodeAnnotation(buf, annotation); + } + + return buf.toArray(); + } + + byte[] encodeParameterAnnotations(Annotation[][] annotations) throws InvocationTargetException, IllegalAccessException { + UnsafeArrayTypeWriter buf = UnsafeArrayTypeWriter.create(ByteArrayReader.supportsUnalignedMemoryAccess()); + + buf.putU1(annotations.length); + for (Annotation[] parameterAnnotations : annotations) { + Annotation[] filteredParameterAnnotations = filterAnnotations(parameterAnnotations); + buf.putU2(filteredParameterAnnotations.length); + for (Annotation parameterAnnotation : filteredParameterAnnotations) { + encodeAnnotation(buf, parameterAnnotation); + } + } + + return buf.toArray(); + } + + void encodeAnnotation(UnsafeArrayTypeWriter buf, Annotation annotation) throws InvocationTargetException, IllegalAccessException { + buf.putS4(encoders.sourceClasses.getIndex(annotation.annotationType())); + AnnotationType type = AnnotationType.getInstance(annotation.annotationType()); + buf.putU2(type.members().size()); + for (Map.Entry entry : type.members().entrySet()) { + String memberName = entry.getKey(); + Method valueAccessor = entry.getValue(); + buf.putS4(encoders.sourceMethodNames.getIndex(memberName)); + encodeValue(buf, valueAccessor.invoke(annotation), type.memberTypes().get(memberName)); + } + } + + void encodeValue(UnsafeArrayTypeWriter buf, Object value, Class type) throws InvocationTargetException, IllegalAccessException { + buf.putU1(tag(type)); + if (type.isAnnotation()) { + encodeAnnotation(buf, (Annotation) value); + } else if (type.isEnum()) { + buf.putS4(encoders.sourceClasses.getIndex(type)); + buf.putS4(encoders.sourceMethodNames.getIndex(((Enum) value).name())); + } else if (type.isArray()) { + encodeArray(buf, value, type.getComponentType()); + } else if (type == Class.class) { + buf.putS4(encoders.sourceClasses.getIndex((Class) value)); + } else if (type == String.class) { + buf.putS4(encoders.sourceMethodNames.getIndex((String) value)); + } else if (type.isPrimitive() || Wrapper.isWrapperType(type)) { + Wrapper wrapper = type.isPrimitive() ? Wrapper.forPrimitiveType(type) : Wrapper.forWrapperType(type); + switch (wrapper) { + case BOOLEAN: + buf.putU1((boolean) value ? 1 : 0); + break; + case BYTE: + buf.putS1((byte) value); + break; + case SHORT: + buf.putS2((short) value); + break; + case CHAR: + buf.putU2((char) value); + break; + case INT: + buf.putS4((int) value); + break; + case LONG: + buf.putS8((long) value); + break; + case FLOAT: + buf.putS4(Float.floatToRawIntBits((float) value)); + break; + case DOUBLE: + buf.putS8(Double.doubleToRawLongBits((double) value)); + break; + default: + throw shouldNotReachHere(); + } + } else { + throw shouldNotReachHere(); + } + } + + void encodeArray(UnsafeArrayTypeWriter buf, Object value, Class componentType) throws InvocationTargetException, IllegalAccessException { + if (!componentType.isPrimitive()) { + Object[] array = (Object[]) value; + buf.putU2(array.length); + for (Object val : array) { + encodeValue(buf, val, componentType); + } + } else if (componentType == boolean.class) { + boolean[] array = (boolean[]) value; + buf.putU2(array.length); + for (boolean val : array) { + encodeValue(buf, val, componentType); + } + } else if (componentType == byte.class) { + byte[] array = (byte[]) value; + buf.putU2(array.length); + for (byte val : array) { + encodeValue(buf, val, componentType); + } + } else if (componentType == short.class) { + short[] array = (short[]) value; + buf.putU2(array.length); + for (short val : array) { + encodeValue(buf, val, componentType); + } + } else if (componentType == char.class) { + char[] array = (char[]) value; + buf.putU2(array.length); + for (char val : array) { + encodeValue(buf, val, componentType); + } + } else if (componentType == int.class) { + int[] array = (int[]) value; + buf.putU2(array.length); + for (int val : array) { + encodeValue(buf, val, componentType); + } + } else if (componentType == long.class) { + long[] array = (long[]) value; + buf.putU2(array.length); + for (long val : array) { + encodeValue(buf, val, componentType); + } + } else if (componentType == float.class) { + float[] array = (float[]) value; + buf.putU2(array.length); + for (float val : array) { + encodeValue(buf, val, componentType); + } + } else if (componentType == double.class) { + double[] array = (double[]) value; + buf.putU2(array.length); + for (double val : array) { + encodeValue(buf, val, componentType); + } + } + } + + byte tag(Class type) { + if (type.isAnnotation()) { + return '@'; + } else if (type.isEnum()) { + return 'e'; + } else if (type.isArray()) { + return '['; + } else if (type == Class.class) { + return 'c'; + } else if (type == String.class) { + return 's'; + } else if (type.isPrimitive()) { + return (byte) Wrapper.forPrimitiveType(type).basicTypeChar(); + } else if (Wrapper.isWrapperType(type)) { + return (byte) Wrapper.forWrapperType(type).basicTypeChar(); + } else { + throw shouldNotReachHere(); + } + } + + private Annotation[] filterAnnotations(Annotation[] annotations) { + List filteredAnnotations = new ArrayList<>(); + for (Annotation annotation : annotations) { + Class annotationClass = annotation.annotationType(); + if (supportedValue(annotationClass, annotation, null)) { + filteredAnnotations.add(annotation); + } + } + return filteredAnnotations.toArray(new Annotation[0]); + } + + private void registerStrings(Annotation... annotations) { + for (Annotation annotation : annotations) { + List stringValues = new ArrayList<>(); + if (supportedValue(annotation.annotationType(), annotation, stringValues)) { + for (String stringValue : stringValues) { + encoders.sourceMethodNames.addObject(stringValue); + } + } + } + } + + @SuppressWarnings("unchecked") + private boolean supportedValue(Class type, Object value, List stringValues) { + if (type.isAnnotation()) { + Annotation annotation = (Annotation) value; + if (!encoders.sourceClasses.contains(annotation.annotationType())) { + return false; + } + AnnotationType annotationType = AnnotationType.getInstance((Class) type); + for (Map.Entry> entry : annotationType.memberTypes().entrySet()) { + String valueName = entry.getKey(); + Class valueType = entry.getValue(); + try { + Method getAnnotationValue = annotationType.members().get(valueName); + getAnnotationValue.setAccessible(true); + Object annotationValue = getAnnotationValue.invoke(annotation); + if (!supportedValue(valueType, annotationValue, stringValues)) { + return false; + } + } catch (IllegalAccessException | InvocationTargetException e) { + return false; + } + } + } else if (type.isArray()) { + boolean supported = true; + Class componentType = type.getComponentType(); + if (!componentType.isPrimitive()) { + for (Object val : (Object[]) value) { + supported &= supportedValue(componentType, val, stringValues); + } + } + return supported; + } else if (type == Class.class) { + return encoders.sourceClasses.contains((Class) value); + } else if (type == String.class) { + if (stringValues != null) { + stringValues.add((String) value); + } + } else if (type.isEnum()) { + if (stringValues != null) { + stringValues.add(((Enum) value).name()); + } + return encoders.sourceClasses.contains(type); + } + return true; + } + + byte[] encodeTypeAnnotations(TypeAnnotation[] annotations) throws InvocationTargetException, IllegalAccessException { + UnsafeArrayTypeWriter buf = UnsafeArrayTypeWriter.create(ByteArrayReader.supportsUnalignedMemoryAccess()); + + buf.putU2(annotations.length); + for (TypeAnnotation typeAnnotation : annotations) { + encodeTypeAnnotation(buf, typeAnnotation); + } + + return buf.toArray(); + } + + void encodeTypeAnnotation(UnsafeArrayTypeWriter buf, TypeAnnotation typeAnnotation) throws InvocationTargetException, IllegalAccessException { + encodeTargetInfo(buf, typeAnnotation.getTargetInfo()); + encodeLocationInfo(buf, typeAnnotation.getLocationInfo()); + // Checkstyle: allow direct annotation access + encodeAnnotation(buf, typeAnnotation.getAnnotation()); + // Checkstyle: disallow direct annotation access + } + + private static final byte CLASS_TYPE_PARAMETER = 0x00; + private static final byte METHOD_TYPE_PARAMETER = 0x01; + private static final byte CLASS_EXTENDS = 0x10; + private static final byte CLASS_TYPE_PARAMETER_BOUND = 0x11; + private static final byte METHOD_TYPE_PARAMETER_BOUND = 0x12; + private static final byte FIELD = 0x13; + private static final byte METHOD_RETURN = 0x14; + private static final byte METHOD_RECEIVER = 0x15; + private static final byte METHOD_FORMAL_PARAMETER = 0x16; + private static final byte THROWS = 0x17; + + void encodeTargetInfo(UnsafeArrayTypeWriter buf, TypeAnnotation.TypeAnnotationTargetInfo targetInfo) { + switch (targetInfo.getTarget()) { + case CLASS_TYPE_PARAMETER: + buf.putU1(CLASS_TYPE_PARAMETER); + buf.putU1(targetInfo.getCount()); + break; + case METHOD_TYPE_PARAMETER: + buf.putU1(METHOD_TYPE_PARAMETER); + buf.putU1(targetInfo.getCount()); + break; + case CLASS_EXTENDS: + buf.putU1(CLASS_EXTENDS); + buf.putS2(-1); + break; + case CLASS_IMPLEMENTS: + buf.putU1(CLASS_EXTENDS); + buf.putS2(targetInfo.getCount()); + break; + case CLASS_TYPE_PARAMETER_BOUND: + buf.putU1(CLASS_TYPE_PARAMETER_BOUND); + buf.putU1(targetInfo.getCount()); + buf.putU1(targetInfo.getSecondaryIndex()); + break; + case METHOD_TYPE_PARAMETER_BOUND: + buf.putU1(METHOD_TYPE_PARAMETER_BOUND); + buf.putU1(targetInfo.getCount()); + buf.putU1(targetInfo.getSecondaryIndex()); + break; + case FIELD: + buf.putU1(FIELD); + break; + case METHOD_RETURN: + buf.putU1(METHOD_RETURN); + break; + case METHOD_RECEIVER: + buf.putU1(METHOD_RECEIVER); + break; + case METHOD_FORMAL_PARAMETER: + buf.putU1(METHOD_FORMAL_PARAMETER); + buf.putU1(targetInfo.getCount()); + break; + case THROWS: + buf.putU1(THROWS); + buf.putU2(targetInfo.getCount()); + break; + } + } + + private static final Field locationInfoDepth = ReflectionUtil.lookupField(TypeAnnotation.LocationInfo.class, "depth"); + private static final Field locationInfoLocations = ReflectionUtil.lookupField(TypeAnnotation.LocationInfo.class, "locations"); + + void encodeLocationInfo(UnsafeArrayTypeWriter buf, TypeAnnotation.LocationInfo locationInfo) throws IllegalAccessException { + int depth = (int) locationInfoDepth.get(locationInfo); + buf.putU1(depth); + TypeAnnotation.LocationInfo.Location[] locations = (TypeAnnotation.LocationInfo.Location[]) locationInfoLocations.get(locationInfo); + for (TypeAnnotation.LocationInfo.Location location : locations) { + buf.putS1(location.tag); + buf.putU1(location.index); + } + + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/MethodMetadataEncoding.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/MethodMetadataEncoding.java new file mode 100644 index 000000000000..db1a5963a7c8 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/MethodMetadataEncoding.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021, 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.code; + +// Checkstyle: allow reflection + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.annotate.UnknownObjectField; + +public class MethodMetadataEncoding { + @UnknownObjectField(types = {byte[].class}) private byte[] methodsEncoding; + @UnknownObjectField(types = {byte[].class}) private byte[] indexEncoding; + + public byte[] getMethodsEncoding() { + return methodsEncoding; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setMethodsEncoding(byte[] methodsEncoding) { + this.methodsEncoding = methodsEncoding; + } + + public byte[] getIndexEncoding() { + return indexEncoding; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setIndexEncoding(byte[] indexEncoding) { + this.indexEncoding = indexEncoding; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java index 3bf6fba985c3..5af838f649de 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java @@ -52,7 +52,8 @@ public final class ReflectionConfigurationParser extends ConfigurationParser private final boolean allowIncompleteClasspath; private static final List OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS = Arrays.asList("allDeclaredConstructors", "allPublicConstructors", "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields", - "allDeclaredClasses", "allPublicClasses", "methods", "fields", CONDITIONAL_KEY); + "allDeclaredClasses", "allPublicClasses", "methods", "queriedMethods", "fields", CONDITIONAL_KEY, + "queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods"); public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate delegate) { this(delegate, false, true); @@ -105,22 +106,22 @@ private void parseClass(Map data) { switch (name) { case "allDeclaredConstructors": if (asBoolean(value, "allDeclaredConstructors")) { - delegate.registerDeclaredConstructors(clazz); + delegate.registerDeclaredConstructors(false, clazz); } break; case "allPublicConstructors": if (asBoolean(value, "allPublicConstructors")) { - delegate.registerPublicConstructors(clazz); + delegate.registerPublicConstructors(false, clazz); } break; case "allDeclaredMethods": if (asBoolean(value, "allDeclaredMethods")) { - delegate.registerDeclaredMethods(clazz); + delegate.registerDeclaredMethods(false, clazz); } break; case "allPublicMethods": if (asBoolean(value, "allPublicMethods")) { - delegate.registerPublicMethods(clazz); + delegate.registerPublicMethods(false, clazz); } break; case "allDeclaredFields": @@ -143,8 +144,31 @@ private void parseClass(Map data) { delegate.registerPublicClasses(clazz); } break; + case "queryAllDeclaredConstructors": + if (asBoolean(value, "queryAllDeclaredConstructors")) { + delegate.registerDeclaredConstructors(true, clazz); + } + break; + case "queryAllPublicConstructors": + if (asBoolean(value, "queryAllPublicConstructors")) { + delegate.registerPublicConstructors(true, clazz); + } + break; + case "queryAllDeclaredMethods": + if (asBoolean(value, "queryAllDeclaredMethods")) { + delegate.registerDeclaredMethods(true, clazz); + } + break; + case "queryAllPublicMethods": + if (asBoolean(value, "queryAllPublicMethods")) { + delegate.registerPublicMethods(true, clazz); + } + break; case "methods": - parseMethods(asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz); + parseMethods(false, asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz); + break; + case "queriedMethods": + parseMethods(true, asList(value, "Attribute 'queriedMethods' must be an array of method descriptors"), clazz); break; case "fields": parseFields(asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz); @@ -176,13 +200,13 @@ private void parseField(Map data, T clazz) { } } - private void parseMethods(List methods, T clazz) { + private void parseMethods(boolean queriedOnly, List methods, T clazz) { for (Object method : methods) { - parseMethod(asMap(method, "Elements of 'methods' array must be method descriptor objects"), clazz); + parseMethod(queriedOnly, asMap(method, "Elements of 'methods' array must be method descriptor objects"), clazz); } } - private void parseMethod(Map data, T clazz) { + private void parseMethod(boolean queriedOnly, Map data, T clazz) { checkAttributes(data, "reflection method descriptor object", Collections.singleton("name"), Collections.singleton("parameterTypes")); String methodName = asString(data.get("name"), "name"); List methodParameterTypes = null; @@ -198,9 +222,9 @@ private void parseMethod(Map data, T clazz) { if (methodParameterTypes != null) { try { if (isConstructor) { - delegate.registerConstructor(clazz, methodParameterTypes); + delegate.registerConstructor(queriedOnly, clazz, methodParameterTypes); } else { - delegate.registerMethod(clazz, methodName, methodParameterTypes); + delegate.registerMethod(queriedOnly, clazz, methodName, methodParameterTypes); } } catch (NoSuchMethodException e) { handleError("Method " + formatMethod(clazz, methodName, methodParameterTypes) + " not found."); @@ -211,9 +235,9 @@ private void parseMethod(Map data, T clazz) { try { boolean found; if (isConstructor) { - found = delegate.registerAllConstructors(clazz); + found = delegate.registerAllConstructors(queriedOnly, clazz); } else { - found = delegate.registerAllMethodsWithName(clazz, methodName); + found = delegate.registerAllMethodsWithName(queriedOnly, clazz, methodName); } if (!found) { throw new JSONParserException("Method " + formatMethod(clazz, methodName) + " not found"); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java index 9a4782d8a31f..530950dfb595 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java @@ -46,23 +46,23 @@ public interface ReflectionConfigurationParserDelegate { void registerDeclaredFields(T type); - void registerPublicMethods(T type); + void registerPublicMethods(boolean queriedOnly, T type); - void registerDeclaredMethods(T type); + void registerDeclaredMethods(boolean queriedOnly, T type); - void registerPublicConstructors(T type); + void registerPublicConstructors(boolean queriedOnly, T type); - void registerDeclaredConstructors(T type); + void registerDeclaredConstructors(boolean queriedOnly, T type); void registerField(T type, String fieldName, boolean allowWrite) throws NoSuchFieldException; - boolean registerAllMethodsWithName(T type, String methodName); + boolean registerAllMethodsWithName(boolean queriedOnly, T type, String methodName); - void registerMethod(T type, String methodName, List methodParameterTypes) throws NoSuchMethodException; + void registerMethod(boolean queriedOnly, T type, String methodName, List methodParameterTypes) throws NoSuchMethodException; - void registerConstructor(T type, List methodParameterTypes) throws NoSuchMethodException; + void registerConstructor(boolean queriedOnly, T type, List methodParameterTypes) throws NoSuchMethodException; - boolean registerAllConstructors(T type); + boolean registerAllConstructors(boolean queriedOnly, T type); String getTypeName(T type); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index 481d0b1669b0..d83b17f47a47 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -48,12 +48,13 @@ import java.security.CodeSource; import java.security.ProtectionDomain; import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.StringJoiner; -import com.oracle.svm.core.jdk.Target_java_lang_Module; import org.graalvm.compiler.core.common.NumUtil; import org.graalvm.compiler.core.common.SuppressFBWarnings; import org.graalvm.compiler.serviceprovider.JavaVersionUtil; @@ -76,12 +77,15 @@ import com.oracle.svm.core.annotate.UnknownObjectField; import com.oracle.svm.core.classinitialization.ClassInitializationInfo; import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode; +import com.oracle.svm.core.code.CodeInfoDecoder; import com.oracle.svm.core.jdk.JDK11OrLater; import com.oracle.svm.core.jdk.JDK15OrLater; import com.oracle.svm.core.jdk.JDK16OrLater; import com.oracle.svm.core.jdk.JDK8OrEarlier; import com.oracle.svm.core.jdk.Package_jdk_internal_reflect; import com.oracle.svm.core.jdk.Resources; +import com.oracle.svm.core.jdk.Target_java_lang_Module; +import com.oracle.svm.core.jdk.Target_jdk_internal_reflect_ConstantPool; import com.oracle.svm.core.jdk.Target_jdk_internal_reflect_Reflection; import com.oracle.svm.core.meta.SharedType; import com.oracle.svm.core.util.LazyFinalReference; @@ -1134,7 +1138,7 @@ private Method getMethod(@SuppressWarnings("hiding") String name, Class... pa * The original code of getMethods() does a recursive search to avoid creating objects for * all public methods. We prepare them during the image build and can just iterate here. */ - Method method = searchMethods(rd.publicMethods, name, parameterTypes); + Method method = searchMethods(companion.get().getCompleteReflectionData().publicMethods, name, parameterTypes); if (method == null) { throw new NoSuchMethodException(describeMethod(getName() + "." + name + "(", parameterTypes, ")")); } @@ -1180,7 +1184,7 @@ private Class[] getClasses() { @Substitute private Constructor[] privateGetDeclaredConstructors(boolean publicOnly) { - return publicOnly ? rd.publicConstructors : rd.declaredConstructors; + return publicOnly ? companion.get().getCompleteReflectionData().publicConstructors : companion.get().getCompleteReflectionData().declaredConstructors; } @Substitute @@ -1190,7 +1194,75 @@ private Field[] privateGetDeclaredFields(boolean publicOnly) { @Substitute private Method[] privateGetDeclaredMethods(boolean publicOnly) { - return publicOnly ? rd.declaredPublicMethods : rd.declaredMethods; + return publicOnly ? companion.get().getCompleteReflectionData().declaredPublicMethods : companion.get().getCompleteReflectionData().declaredMethods; + } + + ReflectionData loadReflectionMetadata() { + Executable[] data = CodeInfoDecoder.getMethodMetadata(this); + + List newDeclaredMethods = new ArrayList<>(Arrays.asList(rd.declaredMethods)); + List newPublicMethods = new ArrayList<>(Arrays.asList(rd.publicMethods)); + List> newDeclaredConstructors = new ArrayList<>(Arrays.asList(rd.declaredConstructors)); + List> newPublicConstructors = new ArrayList<>(Arrays.asList(rd.publicConstructors)); + List newDeclaredPublicMethods = new ArrayList<>(Arrays.asList(rd.declaredPublicMethods)); + + outer: for (Executable method : data) { + if (method instanceof Constructor) { + Constructor c = (Constructor) method; + for (Constructor c2 : rd.declaredConstructors) { + if (Arrays.equals(c.getParameterTypes(), c2.getParameterTypes())) { + continue outer; + } + } + newDeclaredConstructors.add(c); + if (Modifier.isPublic(c.getModifiers())) { + newPublicConstructors.add(c); + } + } else { + Method m = (Method) method; + for (Method m2 : rd.declaredMethods) { + if (m.getName().equals(m2.getName()) && Arrays.equals(m.getParameterTypes(), m2.getParameterTypes())) { + continue outer; + } + } + newDeclaredMethods.add(m); + if (Modifier.isPublic(m.getModifiers())) { + newPublicMethods.add(m); + newDeclaredPublicMethods.add(m); + } + } + } + + /* Recursively add public superclass methods to the public methods list */ + if (superHub != null) { + addInheritedPublicMethods(newPublicMethods, superHub); + } + for (DynamicHub superintfc : getInterfaces()) { + addInheritedPublicMethods(newPublicMethods, superintfc); + } + + return new ReflectionData(rd.declaredFields, rd.publicFields, rd.publicUnhiddenFields, newDeclaredMethods.toArray(new Method[0]), newPublicMethods.toArray(new Method[0]), + newDeclaredConstructors.toArray(new Constructor[0]), newPublicConstructors.toArray(new Constructor[0]), rd.nullaryConstructor, rd.declaredPublicFields, + newDeclaredPublicMethods.toArray(new Method[0]), rd.declaredClasses, rd.publicClasses, rd.enclosingMethodOrConstructor, rd.recordComponents); + } + + private void addInheritedPublicMethods(List newPublicMethods, DynamicHub parentHub) { + outer: for (Method m : parentHub.companion.get().getCompleteReflectionData().publicMethods) { + if (!isInterface() && parentHub.isInterface() && Modifier.isStatic(m.getModifiers())) { + continue; + } + for (Method m2 : newPublicMethods) { + if (m.getName().equals(m2.getName()) && Arrays.equals(m.getParameterTypes(), m2.getParameterTypes())) { + if (m.getDeclaringClass() != m2.getDeclaringClass() && m2.getDeclaringClass().isAssignableFrom(m.getDeclaringClass())) { + /* Need to store the more specific method */ + newPublicMethods.remove(m2); + newPublicMethods.add(m); + } + continue outer; + } + } + newPublicMethods.add(m); + } } @Substitute @@ -1207,7 +1279,7 @@ private Field[] privateGetPublicFieldsJDK11OrLater() { @Substitute private Method[] privateGetPublicMethods() { - return rd.publicMethods; + return companion.get().getCompleteReflectionData().publicMethods; } @KeepOriginal @@ -1665,10 +1737,6 @@ public static Target_jdk_internal_reflect_ReflectionFactory getReflectionFactory } } -@TargetClass(classNameProvider = Package_jdk_internal_reflect.class, className = "ConstantPool") -final class Target_jdk_internal_reflect_ConstantPool { -} - @TargetClass(className = "java.lang.reflect.RecordComponent", onlyWith = JDK16OrLater.class) final class Target_java_lang_reflect_RecordComponent { } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java index 7ef741d66a49..e5ef466c8ea8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHubCompanion.java @@ -28,6 +28,7 @@ import java.security.ProtectionDomain; +import com.oracle.svm.core.hub.DynamicHub.ReflectionData; import com.oracle.svm.core.util.VMError; /** An optional, non-immutable companion to a {@link DynamicHub} instance. */ @@ -37,6 +38,7 @@ public final class DynamicHubCompanion { private String packageName; private Object classLoader = NO_CLASS_LOADER; private ProtectionDomain protectionDomain; + private ReflectionData completeReflectionData; public DynamicHubCompanion(DynamicHub hub) { this.hub = hub; @@ -75,4 +77,11 @@ public void setProtectionDomain(ProtectionDomain domain) { VMError.guarantee(protectionDomain == null && domain != null); protectionDomain = domain; } + + public ReflectionData getCompleteReflectionData() { + if (completeReflectionData == null) { + completeReflectionData = hub.loadReflectionMetadata(); + } + return completeReflectionData; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/SunReflectTypeSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/SunReflectTypeSubstitutions.java index 22bd852bedf2..a436573a69d8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/SunReflectTypeSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/SunReflectTypeSubstitutions.java @@ -38,7 +38,6 @@ import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; -import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.annotate.Inject; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.RecomputeFieldValue.CustomFieldValueComputer; @@ -48,11 +47,9 @@ import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.jdk.JDK11OrLater; import com.oracle.svm.core.jdk.JDK8OrEarlier; -import com.oracle.svm.core.util.VMError; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaField; -import sun.reflect.generics.factory.GenericsFactory; import sun.reflect.generics.reflectiveObjects.TypeVariableImpl; import sun.reflect.generics.reflectiveObjects.WildcardTypeImpl; import sun.reflect.generics.tree.FieldTypeSignature; @@ -77,11 +74,6 @@ final class Target_sun_reflect_generics_reflectiveObjects_TypeVariableImpl { @Alias @RecomputeFieldValue(kind = Kind.Custom, declClass = TypeVariableBoundsComputer.class) // private Object[] boundsJDK11OrLater; - /* The bounds value is cached. The boundASTs field is not used at run time. */ - @Delete // - @TargetElement(onlyWith = JDK8OrEarlier.class) // - private FieldTypeSignature[] boundASTs; - @Alias GenericDeclaration genericDeclaration; @Inject @RecomputeFieldValue(kind = Kind.Custom, declClass = TypeVariableAnnotationsComputer.class) // @@ -90,13 +82,6 @@ final class Target_sun_reflect_generics_reflectiveObjects_TypeVariableImpl { @Inject @RecomputeFieldValue(kind = Kind.Custom, declClass = TypeVariableAnnotatedBoundsComputer.class) // AnnotatedType[] annotatedBounds; - @Substitute - @SuppressWarnings("unused") - private Target_sun_reflect_generics_reflectiveObjects_TypeVariableImpl(GenericDeclaration decl, String n, FieldTypeSignature[] bs, GenericsFactory f) { - throw VMError.shouldNotReachHere("sun.reflect.generics.reflectiveObjects.TypeVariableImpl constructor was removed. " + - "All the TypeVariableImpl objects should be allocated at image build time and cached in the native image heap."); - } - @Substitute public Type[] getBounds() { Type[] result = JavaVersionUtil.JAVA_SPEC <= 8 ? boundsJDK8OrEarlier : (Type[]) boundsJDK11OrLater; @@ -191,22 +176,6 @@ final class Target_sun_reflect_generics_reflectiveObjects_WildcardTypeImpl { @TargetElement(name = "lowerBounds", onlyWith = JDK11OrLater.class) // private Object[] lowerBoundsJDK11OrLater; - /* The upperBounds value is cached. The upperBoundASTs field is not used at run time. */ - @Delete // - @TargetElement(onlyWith = JDK8OrEarlier.class) // - private FieldTypeSignature[] upperBoundASTs; - /* The lowerBounds value is cached. The lowerBoundASTs field is not used at run time. */ - @Delete // - @TargetElement(onlyWith = JDK8OrEarlier.class) // - private FieldTypeSignature[] lowerBoundASTs; - - @Substitute - @SuppressWarnings("unused") - private Target_sun_reflect_generics_reflectiveObjects_WildcardTypeImpl(FieldTypeSignature[] ubs, FieldTypeSignature[] lbs, GenericsFactory f) { - throw VMError.shouldNotReachHere("sun.reflect.generics.reflectiveObjects.WildcardTypeImpl constructor was removed." + - "All the WildcardTypeImpl objects should be allocated at image build time and cached in the native image heap."); - } - @Substitute public Type[] getUpperBounds() { if (JavaVersionUtil.JAVA_SPEC <= 8) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_reflect_ConstantPool.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_reflect_ConstantPool.java new file mode 100644 index 000000000000..de75409e10b3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_reflect_ConstantPool.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2021, 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.jdk; + +import static com.oracle.svm.core.util.VMError.unimplemented; + +// Checkstyle: stop +import java.lang.reflect.Field; +import java.lang.reflect.Member; +// Checkstyle: resume + +import com.oracle.svm.core.annotate.Delete; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.annotate.TargetElement; +import com.oracle.svm.core.c.NonmovableArrays; +import com.oracle.svm.core.code.CodeInfoAccess; +import com.oracle.svm.core.code.CodeInfoTable; + +@SuppressWarnings({"unused", "static-method", "hiding"}) +@TargetClass(classNameProvider = Package_jdk_internal_reflect.class, className = "ConstantPool") +public final class Target_jdk_internal_reflect_ConstantPool { + // Number of entries in this constant pool (= maximum valid constant pool index) + @Substitute + public int getSize() { + return Integer.MAX_VALUE; + } + + @Substitute + public Class getClassAt(int index) { + return NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceClasses(CodeInfoTable.getImageCodeInfo()), index); + } + + @Substitute + public Class getClassAtIfLoaded(int index) { + throw unimplemented(); + } + + // Returns a class reference index for a method or a field. + @Substitute + @TargetElement(onlyWith = JDK11OrLater.class) + public int getClassRefIndexAt(int index) { + throw unimplemented(); + } + + // Returns either a Method or Constructor. + // Static initializers are returned as Method objects. + @Substitute + public Member getMethodAt(int index) { + throw unimplemented(); + } + + @Substitute + public Member getMethodAtIfLoaded(int index) { + throw unimplemented(); + } + + @Substitute + public Field getFieldAt(int index) { + throw unimplemented(); + } + + @Substitute + public Field getFieldAtIfLoaded(int index) { + throw unimplemented(); + } + + // Fetches the class name, member (field, method or interface + // method) name, and type descriptor as an array of three Strings + @Substitute + @TargetElement(onlyWith = JDK11OrLater.class) + public String[] getMemberRefInfoAt(int index) { + throw unimplemented(); + } + + // Returns a name and type reference index for a method, a field or an invokedynamic. + @Substitute + @TargetElement(onlyWith = JDK11OrLater.class) + public int getNameAndTypeRefIndexAt(int index) { + throw unimplemented(); + } + + // Fetches the name and type from name_and_type index as an array of two Strings + @Substitute + @TargetElement(onlyWith = JDK11OrLater.class) + public String[] getNameAndTypeRefInfoAt(int index) { + throw unimplemented(); + } + + @Substitute + public int getIntAt(int index) { + throw unimplemented(); + } + + @Substitute + public long getLongAt(int index) { + throw unimplemented(); + } + + @Substitute + public float getFloatAt(int index) { + throw unimplemented(); + } + + @Substitute + public double getDoubleAt(int index) { + throw unimplemented(); + } + + @Substitute + public String getStringAt(int index) { + return NonmovableArrays.getObject(CodeInfoAccess.getFrameInfoSourceMethodNames(CodeInfoTable.getImageCodeInfo()), index); + } + + @Substitute + public String getUTF8At(int index) { + return getStringAt(index); + } + + @Substitute + @TargetElement(onlyWith = JDK11OrLater.class) + public Target_jdk_internal_reflect_ConstantPool_Tag getTagAt(int index) { + throw unimplemented(); + } + + @Delete private Object constantPoolOop; + + @Delete + private native int getSize0(Object constantPoolOop); + + @Delete + private native Class getClassAt0(Object constantPoolOop, int index); + + @Delete + private native Class getClassAtIfLoaded0(Object constantPoolOop, int index); + + @Delete + @TargetElement(onlyWith = JDK11OrLater.class) + private native int getClassRefIndexAt0(Object constantPoolOop, int index); + + @Delete + private native Member getMethodAt0(Object constantPoolOop, int index); + + @Delete + private native Member getMethodAtIfLoaded0(Object constantPoolOop, int index); + + @Delete + private native Field getFieldAt0(Object constantPoolOop, int index); + + @Delete + private native Field getFieldAtIfLoaded0(Object constantPoolOop, int index); + + @Delete + @TargetElement(onlyWith = JDK11OrLater.class) + private native String[] getMemberRefInfoAt0(Object constantPoolOop, int index); + + @Delete + @TargetElement(onlyWith = JDK11OrLater.class) + private native int getNameAndTypeRefIndexAt0(Object constantPoolOop, int index); + + @Delete + @TargetElement(onlyWith = JDK11OrLater.class) + private native String[] getNameAndTypeRefInfoAt0(Object constantPoolOop, int index); + + @Delete + private native int getIntAt0(Object constantPoolOop, int index); + + @Delete + private native long getLongAt0(Object constantPoolOop, int index); + + @Delete + private native float getFloatAt0(Object constantPoolOop, int index); + + @Delete + private native double getDoubleAt0(Object constantPoolOop, int index); + + @Delete + private native String getStringAt0(Object constantPoolOop, int index); + + @Delete + private native String getUTF8At0(Object constantPoolOop, int index); + + @Delete + @TargetElement(onlyWith = JDK11OrLater.class) + private native byte getTagAt0(Object constantPoolOop, int index); +} + +@TargetClass(classNameProvider = Package_jdk_internal_reflect.class, className = "ConstantPool", innerClass = "Tag", onlyWith = JDK11OrLater.class) +final class Target_jdk_internal_reflect_ConstantPool_Tag { +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIRuntimeAccess.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIRuntimeAccess.java index 9488f1aeca24..7a573e8ace68 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIRuntimeAccess.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/JNIRuntimeAccess.java @@ -53,7 +53,7 @@ public static void register(Class... classes) { } public static void register(Executable... methods) { - getSupport().register(ConfigurationCondition.alwaysTrue(), methods); + getSupport().register(ConfigurationCondition.alwaysTrue(), false, methods); } public static void register(Field... fields) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/RuntimeReflectionConstructors.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/RuntimeReflectionConstructors.java new file mode 100644 index 000000000000..70144b88b2a6 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/RuntimeReflectionConstructors.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2012, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.reflect; + +// Checkstyle: stop +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +// Checkstyle: resume + +public interface RuntimeReflectionConstructors { + Method newMethod(Class declaringClass, + String name, + Class[] parameterTypes, + Class returnType, + Class[] checkedExceptions, + int modifiers, + String signature, + byte[] annotations, + byte[] parameterAnnotations, + byte[] annotationDefault, + byte[] typeAnnotations, + String[] parameterNames, + int[] parameterModifiers); + + Constructor newConstructor(Class declaringClass, + Class[] parameterTypes, + Class[] checkedExceptions, + int modifiers, + String signature, + byte[] annotations, + byte[] parameterAnnotations, + byte[] typeAnnotations, + String[] parameterNames, + int[] parameterModifiers); +} diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFeature.java b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFeature.java index 40dbb859bd46..4bbca94734b4 100644 --- a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFeature.java +++ b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFeature.java @@ -377,7 +377,7 @@ private static void registerJNIConfiguration(JNIRuntimeAccess.JNIRuntimeAccessib try { if ("".equals(methodName)) { Constructor cons = clazz.getDeclaredConstructor(parameters); - registry.register(condition, cons); + registry.register(condition, false, cons); if (Throwable.class.isAssignableFrom(clazz) && !Modifier.isAbstract(clazz.getModifiers())) { if (usedInTranslatedException(parameters)) { RuntimeReflection.register(clazz); @@ -385,7 +385,7 @@ private static void registerJNIConfiguration(JNIRuntimeAccess.JNIRuntimeAccessib } } } else { - registry.register(condition, clazz.getDeclaredMethod(methodName, parameters)); + registry.register(condition, false, clazz.getDeclaredMethod(methodName, parameters)); } } catch (NoSuchMethodException e) { throw source.error("Method %s.%s%s not found: %e", clazz.getTypeName(), methodName, descriptor, e); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/AnnotationTypeFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/AnnotationTypeFeature.java index 73f53b089671..440189475480 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/AnnotationTypeFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/AnnotationTypeFeature.java @@ -31,8 +31,8 @@ import java.util.stream.Stream; import org.graalvm.collections.EconomicSet; -import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; @@ -58,24 +58,25 @@ public void afterRegistration(AfterRegistrationAccess access) { } @Override + @SuppressWarnings("unchecked") public void duringAnalysis(DuringAnalysisAccess access) { DuringAnalysisAccessImpl accessImpl = (DuringAnalysisAccessImpl) access; AnalysisUniverse universe = accessImpl.getUniverse(); - /* - * JDK implementation of repeatable annotations always instantiates an array of a requested - * annotation. We need to mark arrays of all reachable annotations as in heap. - */ universe.getTypes().stream() .filter(AnalysisType::isAnnotation) .filter(AnalysisType::isReachable) .map(type -> universe.lookup(type.getWrapped()).getArrayClass()) .filter(annotationArray -> !annotationArray.isInstantiated()) .forEach(annotationArray -> { + /* + * JDK implementation of repeatable annotations always instantiates an + * array of a requested annotation. We need to mark arrays of all + * reachable annotations as in heap. + */ accessImpl.registerAsInHeap(annotationArray); access.requireAnalysisIteration(); }); - Stream allElements = Stream.concat(Stream.concat(universe.getFields().stream(), universe.getMethods().stream()), universe.getTypes().stream()); Stream newElements = allElements.filter(visitedElements::add); newElements.forEach(this::reportAnnotation); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/CustomSubstitutionMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/CustomSubstitutionMethod.java index 2f15dfad58f0..7f3dd9c347cd 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/CustomSubstitutionMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/CustomSubstitutionMethod.java @@ -246,4 +246,9 @@ public SpeculationLog getSpeculationLog() { public Executable getJavaMethod() { return OriginalMethodProvider.getJavaMethod(GraalAccess.getOriginalSnippetReflection(), original); } + + @Override + public boolean hasJavaMethod() { + return OriginalMethodProvider.hasJavaMethod(GraalAccess.getOriginalSnippetReflection(), original); + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java index 281b3b7c3f2c..9e82b114b9a8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java @@ -98,23 +98,23 @@ public void registerDeclaredFields(ConditionalElement> type) { } @Override - public void registerPublicMethods(ConditionalElement> type) { - registry.register(type.getCondition(), type.getElement().getMethods()); + public void registerPublicMethods(boolean queriedOnly, ConditionalElement> type) { + registry.register(type.getCondition(), queriedOnly, type.getElement().getMethods()); } @Override - public void registerDeclaredMethods(ConditionalElement> type) { - registry.register(type.getCondition(), type.getElement().getDeclaredMethods()); + public void registerDeclaredMethods(boolean queriedOnly, ConditionalElement> type) { + registry.register(type.getCondition(), queriedOnly, type.getElement().getDeclaredMethods()); } @Override - public void registerPublicConstructors(ConditionalElement> type) { - registry.register(type.getCondition(), type.getElement().getConstructors()); + public void registerPublicConstructors(boolean queriedOnly, ConditionalElement> type) { + registry.register(type.getCondition(), queriedOnly, type.getElement().getConstructors()); } @Override - public void registerDeclaredConstructors(ConditionalElement> type) { - registry.register(type.getCondition(), type.getElement().getDeclaredConstructors()); + public void registerDeclaredConstructors(boolean queriedOnly, ConditionalElement> type) { + registry.register(type.getCondition(), queriedOnly, type.getElement().getDeclaredConstructors()); } @Override @@ -123,12 +123,12 @@ public void registerField(ConditionalElement> type, String fieldName, b } @Override - public boolean registerAllMethodsWithName(ConditionalElement> type, String methodName) { + public boolean registerAllMethodsWithName(boolean queriedOnly, ConditionalElement> type, String methodName) { boolean found = false; Executable[] methods = type.getElement().getDeclaredMethods(); for (Executable method : methods) { if (method.getName().equals(methodName)) { - registry.register(type.getCondition(), method); + registerExecutable(type.getCondition(), queriedOnly, method); found = true; } } @@ -136,16 +136,14 @@ public boolean registerAllMethodsWithName(ConditionalElement> type, Str } @Override - public boolean registerAllConstructors(ConditionalElement> type) { + public boolean registerAllConstructors(boolean queriedOnly, ConditionalElement> type) { Executable[] methods = type.getElement().getDeclaredConstructors(); - for (Executable method : methods) { - registry.register(type.getCondition(), method); - } + registerExecutable(type.getCondition(), queriedOnly, methods); return methods.length > 0; } @Override - public void registerMethod(ConditionalElement> type, String methodName, List>> methodParameterTypes) throws NoSuchMethodException { + public void registerMethod(boolean queriedOnly, ConditionalElement> type, String methodName, List>> methodParameterTypes) throws NoSuchMethodException { Class[] parameterTypesArray = getParameterTypes(methodParameterTypes); Method method; try { @@ -165,13 +163,13 @@ public void registerMethod(ConditionalElement> type, String methodName, throw e; } } - registry.register(type.getCondition(), method); + registerExecutable(type.getCondition(), queriedOnly, method); } @Override - public void registerConstructor(ConditionalElement> type, List>> methodParameterTypes) throws NoSuchMethodException { + public void registerConstructor(boolean queriedOnly, ConditionalElement> type, List>> methodParameterTypes) throws NoSuchMethodException { Class[] parameterTypesArray = getParameterTypes(methodParameterTypes); - registry.register(type.getCondition(), type.getElement().getDeclaredConstructor(parameterTypesArray)); + registerExecutable(type.getCondition(), queriedOnly, type.getElement().getDeclaredConstructor(parameterTypesArray)); } private static Class[] getParameterTypes(List>> methodParameterTypes) { @@ -180,6 +178,10 @@ private static Class[] getParameterTypes(List>> m .toArray(Class[]::new); } + private void registerExecutable(ConfigurationCondition condition, boolean queriedOnly, Executable... executable) { + registry.register(condition, queriedOnly, executable); + } + @Override public String getTypeName(ConditionalElement> type) { return type.getElement().getTypeName(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java index 39cf6c77553c..8f43ebfeb1d8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java @@ -26,6 +26,7 @@ import static com.oracle.svm.core.util.VMError.shouldNotReachHere; +import java.lang.reflect.Executable; import java.nio.ByteBuffer; import java.nio.file.Path; import java.util.ArrayList; @@ -40,7 +41,6 @@ import java.util.concurrent.ForkJoinPool; import java.util.stream.Collectors; -import com.oracle.svm.core.option.HostedOptionValues; import org.graalvm.compiler.code.CompilationResult; import org.graalvm.compiler.code.DataSection; import org.graalvm.compiler.debug.DebugContext; @@ -48,12 +48,14 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.c.function.CFunctionPointer; +import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.objectfile.ObjectFile; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.code.CodeInfo; import com.oracle.svm.core.code.CodeInfoAccess; import com.oracle.svm.core.code.CodeInfoEncoder; @@ -69,6 +71,7 @@ import com.oracle.svm.core.graal.code.SubstrateDataBuilder; import com.oracle.svm.core.meta.SubstrateObjectConstant; import com.oracle.svm.core.option.HostedOptionKey; +import com.oracle.svm.core.option.HostedOptionValues; import com.oracle.svm.core.util.Counter; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.NativeImageOptions; @@ -219,6 +222,27 @@ public void buildRuntimeMetadata(CFunctionPointer firstMethod, UnsignedWord code codeInfoEncoder.addMethod(method, compilation, method.getCodeAddressOffset()); } + for (HostedType type : imageHeap.getUniverse().getTypes()) { + if (type.getWrapped().isReachable()) { + codeInfoEncoder.prepareMetadataForClass(type.getJavaClass()); + } + } + + if (SubstrateOptions.ConfigureReflectionMetadata.getValue()) { + for (Executable queriedMethod : ImageSingletons.lookup(RuntimeReflectionSupport.class).getQueriedOnlyMethods()) { + HostedMethod method = imageHeap.getMetaAccess().optionalLookupJavaMethod(queriedMethod); + if (method != null) { + codeInfoEncoder.prepareMetadataForMethod(method, queriedMethod); + } + } + } else { + for (HostedMethod method : imageHeap.getUniverse().getMethods()) { + if (method.getWrapped().isReachable() && method.hasJavaMethod()) { + codeInfoEncoder.prepareMetadataForMethod(method, method.getJavaMethod()); + } + } + } + if (NativeImageOptions.PrintMethodHistogram.getValue()) { System.out.println("encoded deopt entry points ; " + frameInfoCustomization.numDeoptEntryPoints); System.out.println("encoded during call entry points ; " + frameInfoCustomization.numDuringCallEntryPoints); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java index 83cd96802e80..260c4ff004ca 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java @@ -496,4 +496,9 @@ public int compareTo(HostedMethod other) { public Executable getJavaMethod() { return OriginalMethodProvider.getJavaMethod(getDeclaringClass().universe.getSnippetReflection(), wrapped); } + + @Override + public boolean hasJavaMethod() { + return OriginalMethodProvider.hasJavaMethod(getDeclaringClass().universe.getSnippetReflection(), wrapped); + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotatedMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotatedMethod.java index 65e896929d3c..efb3761b19f9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotatedMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotatedMethod.java @@ -284,4 +284,9 @@ public SpeculationLog getSpeculationLog() { public Executable getJavaMethod() { return OriginalMethodProvider.getJavaMethod(GraalAccess.getOriginalSnippetReflection(), original); } + + @Override + public boolean hasJavaMethod() { + return OriginalMethodProvider.hasJavaMethod(GraalAccess.getOriginalSnippetReflection(), original); + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionMethod.java index a0c7a4a1e5d0..b28fc46cb0df 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionMethod.java @@ -296,4 +296,9 @@ public SpeculationLog getSpeculationLog() { public Executable getJavaMethod() { return OriginalMethodProvider.getJavaMethod(GraalAccess.getOriginalSnippetReflection(), original); } + + @Override + public boolean hasJavaMethod() { + return OriginalMethodProvider.hasJavaMethod(GraalAccess.getOriginalSnippetReflection(), original); + } } diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java index ccf1b7fb023d..4adb85db222a 100644 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java @@ -137,7 +137,7 @@ public void register(ConfigurationCondition condition, Class... classes) { } @Override - public void register(ConfigurationCondition condition, Executable... methods) { + public void register(ConfigurationCondition condition, boolean queriedOnly, Executable... methods) { abortIfSealed(); registerConditionalConfiguration(condition, () -> newMethods.addAll(Arrays.asList(methods))); } diff --git a/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java b/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java index 59496f73a095..1d743a41d801 100644 --- a/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java +++ b/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java @@ -253,10 +253,12 @@ public static boolean testException(JNIEnvironment localEnv) { return false; } - public static JNIObjectHandle handleException(JNIEnvironment localEnv) { + public static JNIObjectHandle handleException(JNIEnvironment localEnv, boolean clear) { if (jniFunctions().getExceptionCheck().invoke(localEnv)) { JNIObjectHandle exception = jniFunctions().getExceptionOccurred().invoke(localEnv); - jniFunctions().getExceptionClear().invoke(localEnv); + if (clear) { + jniFunctions().getExceptionClear().invoke(localEnv); + } return exception; } return nullHandle(); diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java index 0c6725d824f6..661ea1dd667e 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java @@ -26,11 +26,18 @@ //Checkstyle: allow reflection +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -41,22 +48,32 @@ import java.util.function.Predicate; import java.util.stream.Collectors; +import org.graalvm.compiler.debug.GraalError; +import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; +import org.graalvm.util.GuardedAnnotationAccess; +import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.hub.AnnotationTypeSupport; import com.oracle.svm.core.hub.ClassForNameSupport; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.jdk.RecordSupport; +import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.ConditionalConfigurationRegistry; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.FeatureAccessImpl; -import com.oracle.svm.hosted.ConditionalConfigurationRegistry; import com.oracle.svm.hosted.substitute.SubstitutionReflectivityFilter; import com.oracle.svm.util.ReflectionUtil; +import sun.reflect.annotation.TypeAnnotation; +import sun.reflect.annotation.TypeAnnotationParser; + public class ReflectionDataBuilder extends ConditionalConfigurationRegistry implements RuntimeReflectionSupport { public static final Field[] EMPTY_FIELDS = new Field[0]; @@ -71,6 +88,7 @@ public class ReflectionDataBuilder extends ConditionalConfigurationRegistry impl private final Set> reflectionClasses = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Set reflectionMethods = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Set reflectionFields = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private Set queriedMethods; private final Set> processedClasses = new HashSet<>(); @@ -79,6 +97,7 @@ public class ReflectionDataBuilder extends ConditionalConfigurationRegistry impl public ReflectionDataBuilder(FeatureAccessImpl access) { arrayReflectionData = getArrayReflectionData(); accessors = new ReflectionDataAccessors(access); + queriedMethods = SubstrateOptions.ConfigureReflectionMetadata.getValue() ? ConcurrentHashMap.newKeySet() : null; } private static DynamicHub.ReflectionData getArrayReflectionData() { @@ -123,14 +142,18 @@ private void registerClasses(Class[] classes) { } @Override - public void register(ConfigurationCondition condition, Executable... methods) { + public void register(ConfigurationCondition condition, boolean queriedOnly, Executable... methods) { checkNotSealed(); - registerConditionalConfiguration(condition, () -> registerMethods(methods)); + if (queriedOnly && !SubstrateOptions.ConfigureReflectionMetadata.getValue()) { + throw UserError.abort("Found manual reflection metadata configuration. Please use --configure-reflection-metadata to enable this behavior."); + } + registerConditionalConfiguration(condition, () -> registerMethods(queriedOnly, methods)); } - private void registerMethods(Executable[] methods) { + private void registerMethods(boolean queriedOnly, Executable[] methods) { for (Executable method : methods) { - if (reflectionMethods.add(method)) { + boolean added = queriedOnly ? queriedMethods.add(method) : reflectionMethods.add(method); + if (added) { modifiedClasses.add(method.getDeclaringClass()); } } @@ -161,6 +184,7 @@ protected void duringAnalysis(DuringAnalysisAccess a) { DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; processReachableTypes(access); processRegisteredElements(access); + processMethodMetadata(access); } /* @@ -205,6 +229,126 @@ private void processReachableTypes(DuringAnalysisAccessImpl access) { } } + protected void processMethodMetadata(DuringAnalysisAccessImpl access) { + if (SubstrateOptions.ConfigureReflectionMetadata.getValue()) { + /* Trigger creation of the AnalysisMethod objects needed to store metadata */ + Set newQueriedMethods = new HashSet<>(); + for (Executable method : queriedMethods) { + if (!SubstitutionReflectivityFilter.shouldExclude(method, access.getMetaAccess(), access.getUniverse())) { + access.getMetaAccess().lookupJavaMethod(method); + registerMetadataForReflection(access, method); + newQueriedMethods.add(method); + } + } + queriedMethods = newQueriedMethods; + } else { + for (AnalysisMethod method : access.getUniverse().getMethods()) { + /* + * Methods generated by Graal and class initialization methods have no reflection + * method. + */ + if (method.isReachable() && method.hasJavaMethod()) { + registerMetadataForReflection(access, method.getJavaMethod()); + } + } + } + } + + private static final Method parseAllTypeAnnotations = ReflectionUtil.lookupMethod(TypeAnnotationParser.class, "parseAllTypeAnnotations", AnnotatedElement.class); + + private void registerMetadataForReflection(DuringAnalysisAccessImpl access, Executable reflectMethod) { + /* + * Reflection signature parsing will try to instantiate classes via Class.forName(). + */ + makeTypeReachable(access, reflectMethod.getDeclaringClass(), false); + for (TypeVariable type : reflectMethod.getTypeParameters()) { + makeTypeReachable(access, type, true); + } + for (Type paramType : reflectMethod.getGenericParameterTypes()) { + makeTypeReachable(access, paramType, true); + } + if (reflectMethod instanceof Method) { + makeTypeReachable(access, ((Method) reflectMethod).getGenericReturnType(), true); + } + for (Type exceptionType : reflectMethod.getGenericExceptionTypes()) { + makeTypeReachable(access, exceptionType, true); + } + + /* + * Enable runtime parsing of annotations + */ + for (Annotation annotation : GuardedAnnotationAccess.getDeclaredAnnotations(reflectMethod)) { + makeTypeReachable(access, annotation.annotationType(), false); + } + for (Annotation[] parameterAnnotations : reflectMethod.getParameterAnnotations()) { + for (Annotation parameterAnnotation : parameterAnnotations) { + makeTypeReachable(access, parameterAnnotation.annotationType(), false); + } + } + try { + for (TypeAnnotation typeAnnotation : (TypeAnnotation[]) parseAllTypeAnnotations.invoke(null, reflectMethod)) { + // Checkstyle: allow direct annotation access + makeTypeReachable(access, typeAnnotation.getAnnotation().annotationType(), false); + // Checkstyle: disallow direct annotation access + } + } catch (IllegalAccessException | InvocationTargetException e) { + throw GraalError.shouldNotReachHere(); + } + } + + private static final Set seenTypes = new HashSet<>(); + + @SuppressWarnings("unchecked") + private void makeTypeReachable(DuringAnalysisAccessImpl access, Type type, boolean needsClassForName) { + if (type == null || seenTypes.contains(type)) { + return; + } + seenTypes.add(type); + if (type instanceof Class) { + Class clazz = (Class) type; + if (access.getMetaAccess().lookupJavaType(clazz).registerAsReachable()) { + access.requireAnalysisIteration(); + } + + if (needsClassForName) { + if (ClassForNameSupport.forNameOrNull(clazz.getName(), null) == null) { + access.requireAnalysisIteration(); + } + ClassForNameSupport.registerClass(clazz); + } + + if (clazz.isAnnotation()) { + /* + * Parsing annotation data in reflection classes requires being able to instantiate + * all annotation types at runtime. + */ + ImageSingletons.lookup(AnnotationTypeSupport.class).createInstance((Class) clazz); + ImageSingletons.lookup(DynamicProxyRegistry.class).addProxyClass(clazz); + } + } else if (type instanceof TypeVariable) { + for (Type bound : ((TypeVariable) type).getBounds()) { + makeTypeReachable(access, bound, needsClassForName); + } + } else if (type instanceof GenericArrayType) { + makeTypeReachable(access, ((GenericArrayType) type).getGenericComponentType(), needsClassForName); + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + for (Type actualType : parameterizedType.getActualTypeArguments()) { + makeTypeReachable(access, actualType, needsClassForName); + } + makeTypeReachable(access, parameterizedType.getRawType(), needsClassForName); + makeTypeReachable(access, parameterizedType.getOwnerType(), needsClassForName); + } else if (type instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type; + for (Type lowerBound : wildcardType.getLowerBounds()) { + makeTypeReachable(access, lowerBound, needsClassForName); + } + for (Type upperBound : wildcardType.getUpperBounds()) { + makeTypeReachable(access, upperBound, needsClassForName); + } + } + } + private void processRegisteredElements(DuringAnalysisAccessImpl access) { if (modifiedClasses.isEmpty()) { return; @@ -451,6 +595,11 @@ private static Class[] filterClasses(Object classes, Set> filter, Du return result.toArray(EMPTY_CLASSES); } + @Override + public Set getQueriedOnlyMethods() { + return queriedMethods; + } + static final class ReflectionDataAccessors { private final Method reflectionDataMethod; private final Field declaredFieldsField; diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java index 8c27c5ccee5d..827c4ea7d274 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java @@ -53,6 +53,7 @@ import com.oracle.svm.core.configure.ReflectionConfigurationParser; import com.oracle.svm.core.graal.GraalFeature; import com.oracle.svm.core.reflect.ReflectionAccessorHolder; +import com.oracle.svm.core.reflect.RuntimeReflectionConstructors; import com.oracle.svm.core.reflect.SubstrateConstructorAccessor; import com.oracle.svm.core.reflect.SubstrateMethodAccessor; import com.oracle.svm.core.reflect.SubstrateReflectionAccessorFactory; @@ -67,6 +68,7 @@ import com.oracle.svm.hosted.meta.MethodPointer; import com.oracle.svm.hosted.snippets.ReflectionPlugins; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; +import com.oracle.svm.reflect.target.RuntimeReflectionConstructorsImpl; import com.oracle.svm.util.ModuleSupport; import com.oracle.svm.util.ReflectionUtil; @@ -172,6 +174,7 @@ public void afterRegistration(AfterRegistrationAccess access) { reflectionData = new ReflectionDataBuilder((FeatureAccessImpl) access); ImageSingletons.add(RuntimeReflectionSupport.class, reflectionData); + ImageSingletons.add(RuntimeReflectionConstructors.class, new RuntimeReflectionConstructorsImpl()); } @Override diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/DynamicProxySupport.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/DynamicProxySupport.java index ea678cc54f92..45767b80586b 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/DynamicProxySupport.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/DynamicProxySupport.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.graalvm.compiler.debug.GraalError; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.hosted.RuntimeReflection; @@ -41,6 +42,7 @@ import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.NativeImageOptions; import com.oracle.svm.util.ClassUtil; import com.oracle.svm.util.ReflectionUtil; @@ -79,7 +81,7 @@ public String toString() { } private final ClassLoader classLoader; - private final Map> proxyCache; + private final Map proxyCache; public DynamicProxySupport(ClassLoader classLoader) { this.classLoader = classLoader; @@ -96,7 +98,16 @@ public void addProxyClass(Class... interfaces) { final Class[] intfs = interfaces.clone(); ProxyCacheKey key = new ProxyCacheKey(intfs); proxyCache.computeIfAbsent(key, k -> { - Class clazz = getJdkProxyClass(classLoader, intfs); + Class clazz; + try { + clazz = getJdkProxyClass(classLoader, intfs); + } catch (Throwable e) { + if (NativeImageOptions.AllowIncompleteClasspath.getValue()) { + return e; + } else { + throw e; + } + } /* * Treat the proxy as a predefined class so that we can set its class loader to the @@ -130,13 +141,17 @@ public void addProxyClass(Class... interfaces) { @Override public Class getProxyClass(ClassLoader loader, Class... interfaces) { ProxyCacheKey key = new ProxyCacheKey(interfaces); - Class clazz = proxyCache.get(key); - if (clazz == null) { + Object clazzOrError = proxyCache.get(key); + if (clazzOrError == null) { throw VMError.unsupportedFeature("Proxy class defined by interfaces " + Arrays.toString(interfaces) + " not found. " + "Generating proxy classes at runtime is not supported. " + "Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. " + "To define proxy classes use " + proxyConfigFilesOption + " and " + proxyConfigResourcesOption + " options."); } + if (clazzOrError instanceof Throwable) { + throw new GraalError((Throwable) clazzOrError); + } + Class clazz = (Class) clazzOrError; if (!DynamicHub.fromClass(clazz).isLoaded()) { /* * NOTE: we might race with another thread in loading this proxy class. diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/hosted/DynamicProxyFeature.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/hosted/DynamicProxyFeature.java index d32ddb8c634b..a49324dbf209 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/hosted/DynamicProxyFeature.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/hosted/DynamicProxyFeature.java @@ -47,6 +47,7 @@ @AutomaticFeature public final class DynamicProxyFeature implements Feature { private int loadedConfigurations; + private DynamicProxySupport dynamicProxySupport; @Override public List> getRequiredFeatures() { @@ -58,7 +59,7 @@ public void duringSetup(DuringSetupAccess a) { DuringSetupAccessImpl access = (DuringSetupAccessImpl) a; ImageClassLoader imageClassLoader = access.getImageClassLoader(); - DynamicProxySupport dynamicProxySupport = new DynamicProxySupport(imageClassLoader.getClassLoader()); + dynamicProxySupport = new DynamicProxySupport(imageClassLoader.getClassLoader()); ImageSingletons.add(DynamicProxyRegistry.class, dynamicProxySupport); ConfigurationTypeResolver typeResolver = new ConfigurationTypeResolver("resource configuration", imageClassLoader, NativeImageOptions.AllowIncompleteClasspath.getValue()); ProxyRegistry proxyRegistry = new ProxyRegistry(typeResolver, dynamicProxySupport, imageClassLoader); diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/RuntimeReflectionConstructorsImpl.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/RuntimeReflectionConstructorsImpl.java new file mode 100644 index 000000000000..e6a1c33372e1 --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/RuntimeReflectionConstructorsImpl.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2021, 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.reflect.target; + +// Checkstyle: stop +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +// Checkstyle: resume + +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.reflect.RuntimeReflectionConstructors; + +public class RuntimeReflectionConstructorsImpl implements RuntimeReflectionConstructors { + @Override + public Method newMethod(Class declaringClass, String name, Class[] parameterTypes, Class returnType, Class[] checkedExceptions, int modifiers, String signature, + byte[] annotations, byte[] parameterAnnotations, byte[] annotationDefault, byte[] typeAnnotations, String[] parameterNames, int[] parameterModifiers) { + Target_java_lang_reflect_Method method = new Target_java_lang_reflect_Method(); + method.constructor(declaringClass, name, parameterTypes, returnType, checkedExceptions, modifiers, -1, signature, annotations, parameterAnnotations, annotationDefault); + fillParameters(SubstrateUtil.cast(method, Target_java_lang_reflect_Executable.class), parameterNames, parameterModifiers); + SubstrateUtil.cast(method, Target_java_lang_reflect_Executable.class).typeAnnotations = typeAnnotations; + return SubstrateUtil.cast(method, Method.class); + } + + @Override + public Constructor newConstructor(Class declaringClass, Class[] parameterTypes, Class[] checkedExceptions, int modifiers, String signature, + byte[] annotations, byte[] parameterAnnotations, byte[] typeAnnotations, String[] parameterNames, int[] parameterModifiers) { + Target_java_lang_reflect_Constructor cons = new Target_java_lang_reflect_Constructor(); + cons.constructor(declaringClass, parameterTypes, checkedExceptions, modifiers, -1, signature, annotations, parameterAnnotations); + fillParameters(SubstrateUtil.cast(cons, Target_java_lang_reflect_Executable.class), parameterNames, parameterModifiers); + SubstrateUtil.cast(cons, Target_java_lang_reflect_Executable.class).typeAnnotations = typeAnnotations; + return SubstrateUtil.cast(cons, Constructor.class); + } + + private static void fillParameters(Target_java_lang_reflect_Executable executable, String[] parameterNames, int[] parameterModifiers) { + if (parameterNames != null && parameterModifiers != null) { + executable.hasRealParameterData = true; + assert parameterNames.length == parameterModifiers.length; + executable.parameters = new Target_java_lang_reflect_Parameter[parameterNames.length]; + for (int i = 0; i < parameterNames.length; ++i) { + executable.parameters[i] = new Target_java_lang_reflect_Parameter(); + executable.parameters[i].constructor(parameterNames[i], parameterModifiers[i], executable, i); + } + } else { + executable.hasRealParameterData = false; + } + } +} diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_AccessibleObject.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_AccessibleObject.java index a4adafcb1316..ddb9b80dbb93 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_AccessibleObject.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_AccessibleObject.java @@ -29,15 +29,26 @@ // Checkstyle: resume import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; +import com.oracle.svm.core.jdk.JDK11OrEarlier; import com.oracle.svm.core.jdk.JDK11OrLater; +import com.oracle.svm.core.jdk.JDK16OrLater; @TargetClass(value = AccessibleObject.class) public final class Target_java_lang_reflect_AccessibleObject { @Alias // public boolean override; + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // + @TargetElement(onlyWith = JDK11OrEarlier.class) // + volatile Object securityCheckCache; + + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // + @TargetElement(onlyWith = JDK16OrLater.class) // + volatile Object accessCheckCache; + @Alias // @TargetElement(onlyWith = JDK11OrLater.class) native Target_java_lang_reflect_AccessibleObject getRoot(); diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Constructor.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Constructor.java index 73bb029b0eca..0e7699d128bd 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Constructor.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Constructor.java @@ -26,11 +26,15 @@ // Checkstyle: allow reflection +import static com.oracle.svm.core.annotate.TargetElement.CONSTRUCTOR_NAME; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedType; import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; -import org.graalvm.compiler.serviceprovider.JavaVersionUtil; - +import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Inject; import com.oracle.svm.core.annotate.RecomputeFieldValue; @@ -38,11 +42,14 @@ import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.annotate.TargetElement; +import com.oracle.svm.core.code.CodeInfoDecoder; import com.oracle.svm.core.util.VMError; import com.oracle.svm.reflect.hosted.ExecutableAccessorComputer; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaField; +import sun.reflect.annotation.TypeAnnotation; import sun.reflect.generics.repository.ConstructorRepository; @TargetClass(value = Constructor.class) @@ -50,6 +57,14 @@ public final class Target_java_lang_reflect_Constructor { @Alias ConstructorRepository genericInfo; + @Alias private Class[] parameterTypes; + + @Alias @RecomputeFieldValue(kind = Kind.Reset)// + private byte[] annotations; + + @Alias @RecomputeFieldValue(kind = Kind.Reset)// + private byte[] parameterAnnotations; + @Alias // @RecomputeFieldValue(kind = Kind.Custom, declClass = ExecutableAccessorComputer.class) // Target_jdk_internal_reflect_ConstructorAccessor constructorAccessor; @@ -57,23 +72,76 @@ public final class Target_java_lang_reflect_Constructor { @Inject @RecomputeFieldValue(kind = Kind.Custom, declClass = ConstructorAnnotatedReceiverTypeComputer.class) // AnnotatedType annotatedReceiverType; + @Alias + @TargetElement(name = CONSTRUCTOR_NAME) + @SuppressWarnings("hiding") + public native void constructor(Class declaringClass, + Class[] parameterTypes, + Class[] checkedExceptions, + int modifiers, + int slot, + String signature, + byte[] annotations, + byte[] parameterAnnotations); + @Alias native Target_java_lang_reflect_Constructor copy(); @Substitute Target_jdk_internal_reflect_ConstructorAccessor acquireConstructorAccessor() { if (constructorAccessor == null) { - throw VMError.unsupportedFeature("Runtime reflection is not supported."); + throw VMError.unsupportedFeature("Runtime reflection is not supported for " + this); } return constructorAccessor; } + @Alias // + public native Class getDeclaringClass(); + + @Substitute + public Annotation[][] getParameterAnnotations() { + Target_java_lang_reflect_Executable self = SubstrateUtil.cast(this, Target_java_lang_reflect_Executable.class); + Target_java_lang_reflect_Executable holder = ReflectionHelper.getHolder(self); + if (holder.parameterAnnotations != null) { + return holder.parameterAnnotations; + } + return self.sharedGetParameterAnnotations(parameterTypes, parameterAnnotations); + } + @Substitute public AnnotatedType getAnnotatedReceiverType() { Target_java_lang_reflect_Constructor holder = ReflectionHelper.getHolder(this); - return JavaVersionUtil.JAVA_SPEC == 8 - ? ReflectionHelper.requireNonNull(holder.annotatedReceiverType, "Annotated receiver type must be computed during native image generation") - : holder.annotatedReceiverType; // can be null (JDK-8044629) + if (holder.annotatedReceiverType != null) { + return holder.annotatedReceiverType; + } else { + Class thisDeclClass = getDeclaringClass(); + Class enclosingClass = thisDeclClass.getEnclosingClass(); + + if (enclosingClass == null) { + // A Constructor for a top-level class + return null; + } + + Class outerDeclaringClass = thisDeclClass.getDeclaringClass(); + if (outerDeclaringClass == null) { + // A constructor for a local or anonymous class + return null; + } + + // Either static nested or inner class + if (Modifier.isStatic(thisDeclClass.getModifiers())) { + // static nested + return null; + } + + // A Constructor for an inner class + return Target_sun_reflect_annotation_TypeAnnotationParser.buildAnnotatedType(SubstrateUtil.cast(holder, Target_java_lang_reflect_Executable.class).typeAnnotations, + CodeInfoDecoder.getMetadataPseudoConstantPool(), + SubstrateUtil.cast(this, AnnotatedElement.class), + thisDeclClass, + enclosingClass, + TypeAnnotation.TypeAnnotationTarget.METHOD_RECEIVER); + } } /** diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Executable.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Executable.java index fa6372619509..05ffa71e154e 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Executable.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Executable.java @@ -27,13 +27,15 @@ // Checkstyle: allow reflection import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedType; import java.lang.reflect.Executable; -import java.lang.reflect.Parameter; +import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.Map; import java.util.function.Supplier; +import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Inject; import com.oracle.svm.core.annotate.RecomputeFieldValue; @@ -42,6 +44,7 @@ import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; +import com.oracle.svm.core.code.CodeInfoDecoder; import com.oracle.svm.core.jdk.JDK8OrEarlier; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.VMError; @@ -50,6 +53,7 @@ import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaField; +import sun.reflect.annotation.TypeAnnotation; @TargetClass(value = Executable.class) public final class Target_java_lang_reflect_Executable { @@ -59,7 +63,10 @@ public final class Target_java_lang_reflect_Executable { * {@link ReflectionObjectReplacer}. */ @Alias // - Parameter[] parameters; + Target_java_lang_reflect_Parameter[] parameters; + + @Alias // + boolean hasRealParameterData; /** * The declaredAnnotations field doesn't need a value recomputation. Its value is pre-loaded in @@ -68,6 +75,9 @@ public final class Target_java_lang_reflect_Executable { @Alias // Map, Annotation> declaredAnnotations; + @Inject @RecomputeFieldValue(kind = Kind.Reset) // + byte[] typeAnnotations; + @Inject @RecomputeFieldValue(kind = Kind.Custom, declClass = ParameterAnnotationsComputer.class) // Annotation[][] parameterAnnotations; @@ -87,57 +97,137 @@ public final class Target_java_lang_reflect_Executable { @TargetElement(onlyWith = JDK8OrEarlier.class) native Target_java_lang_reflect_Executable getRoot(); + @Alias // + public native int getModifiers(); + + @Alias // + native byte[] getAnnotationBytes(); + + @Alias // + public native Class getDeclaringClass(); + + @Alias // + native Type[] getAllGenericParameterTypes(); + + @Alias // + public native Type[] getGenericExceptionTypes(); + + @Alias // + private native Target_java_lang_reflect_Parameter[] synthesizeAllParams(); + @Substitute - private Parameter[] privateGetParameters() { + private Target_java_lang_reflect_Parameter[] privateGetParameters() { Target_java_lang_reflect_Executable holder = ReflectionHelper.getHolder(this); - return ReflectionHelper.requireNonNull(holder.parameters, "Parameters must be computed during native image generation"); + if (holder.parameters != null) { + return holder.parameters; + } else { + assert !hasRealParameterData; + holder.parameters = synthesizeAllParams(); + return holder.parameters; + } } @Substitute Map, Annotation> declaredAnnotations() { - Target_java_lang_reflect_Executable holder = ReflectionHelper.getHolder(this); - return ReflectionHelper.requireNonNull(holder.declaredAnnotations, "Annotations must be computed during native image generation"); + Map, Annotation> declAnnos; + if ((declAnnos = declaredAnnotations) == null) { + // Checkstyle: stop + synchronized (this) { + if ((declAnnos = declaredAnnotations) == null) { + Target_java_lang_reflect_Executable holder = ReflectionHelper.getHolder(this); + declAnnos = Target_sun_reflect_annotation_AnnotationParser.parseAnnotations( + holder.getAnnotationBytes(), + CodeInfoDecoder.getMetadataPseudoConstantPool(), + holder.getDeclaringClass()); + declaredAnnotations = declAnnos; + } + } + // Checkstyle: resume + } + return declAnnos; } - @Substitute - @SuppressWarnings("unused") - Annotation[][] sharedGetParameterAnnotations(Class[] parameterTypes, byte[] annotations) { - Target_java_lang_reflect_Executable holder = ReflectionHelper.getHolder(this); - return ReflectionHelper.requireNonNull(holder.parameterAnnotations, "Parameter annotations must be computed during native image generation"); - } + @Alias + native Annotation[][] sharedGetParameterAnnotations(Class[] parameterTypes, byte[] annotations); @Substitute @SuppressWarnings({"unused", "hiding", "static-method"}) Annotation[][] parseParameterAnnotations(byte[] parameterAnnotations) { - throw VMError.unsupportedFeature("Parameter annotations parsing is not available at run time."); + return Target_sun_reflect_annotation_AnnotationParser.parseParameterAnnotations( + parameterAnnotations, + CodeInfoDecoder.getMetadataPseudoConstantPool(), + getDeclaringClass()); } @Substitute public AnnotatedType getAnnotatedReceiverType() { Target_java_lang_reflect_Executable holder = ReflectionHelper.getHolder(this); - /* The annotatedReceiverType can be null. */ - return (AnnotatedType) AnnotatedTypeEncoder.decodeAnnotationTypes(holder.annotatedReceiverType); + if (holder.annotatedReceiverType != null) { + return (AnnotatedType) AnnotatedTypeEncoder.decodeAnnotationTypes(holder.annotatedReceiverType); + } else { + if (Modifier.isStatic(this.getModifiers())) { + return null; + } + AnnotatedType annotatedRecvType = Target_sun_reflect_annotation_TypeAnnotationParser.buildAnnotatedType(typeAnnotations, + CodeInfoDecoder.getMetadataPseudoConstantPool(), + SubstrateUtil.cast(this, AnnotatedElement.class), + getDeclaringClass(), + getDeclaringClass(), + TypeAnnotation.TypeAnnotationTarget.METHOD_RECEIVER); + holder.annotatedReceiverType = annotatedRecvType; + return annotatedRecvType; + } } @Substitute public AnnotatedType[] getAnnotatedParameterTypes() { Target_java_lang_reflect_Executable holder = ReflectionHelper.getHolder(this); - return (AnnotatedType[]) ReflectionHelper.requireNonNull(AnnotatedTypeEncoder.decodeAnnotationTypes(holder.annotatedParameterTypes), - "Annotated parameter types must be computed during native image generation"); + if (holder.annotatedParameterTypes != null) { + return (AnnotatedType[]) AnnotatedTypeEncoder.decodeAnnotationTypes(holder.annotatedParameterTypes); + } else { + AnnotatedType[] annotatedParamTypes = Target_sun_reflect_annotation_TypeAnnotationParser.buildAnnotatedTypes(typeAnnotations, + CodeInfoDecoder.getMetadataPseudoConstantPool(), + SubstrateUtil.cast(this, AnnotatedElement.class), + getDeclaringClass(), + getAllGenericParameterTypes(), + TypeAnnotation.TypeAnnotationTarget.METHOD_FORMAL_PARAMETER); + holder.annotatedParameterTypes = annotatedParamTypes; + return annotatedParamTypes; + } } @Substitute public AnnotatedType getAnnotatedReturnType0(@SuppressWarnings("unused") Type returnType) { Target_java_lang_reflect_Executable holder = ReflectionHelper.getHolder(this); - return (AnnotatedType) ReflectionHelper.requireNonNull(AnnotatedTypeEncoder.decodeAnnotationTypes(holder.annotatedReturnType), - "Annotated return type must be computed during native image generation"); + if (holder.annotatedReturnType != null) { + return (AnnotatedType) AnnotatedTypeEncoder.decodeAnnotationTypes(holder.annotatedReturnType); + } else { + AnnotatedType annotatedRetType = Target_sun_reflect_annotation_TypeAnnotationParser.buildAnnotatedType(typeAnnotations, + CodeInfoDecoder.getMetadataPseudoConstantPool(), + SubstrateUtil.cast(this, AnnotatedElement.class), + getDeclaringClass(), + returnType, + TypeAnnotation.TypeAnnotationTarget.METHOD_RETURN); + holder.annotatedReturnType = annotatedRetType; + return annotatedRetType; + } } @Substitute public AnnotatedType[] getAnnotatedExceptionTypes() { Target_java_lang_reflect_Executable holder = ReflectionHelper.getHolder(this); - return (AnnotatedType[]) ReflectionHelper.requireNonNull(AnnotatedTypeEncoder.decodeAnnotationTypes(holder.annotatedExceptionTypes), - "Annotated exception types must be computed during native image generation"); + if (holder.annotatedExceptionTypes != null) { + return (AnnotatedType[]) AnnotatedTypeEncoder.decodeAnnotationTypes(holder.annotatedExceptionTypes); + } else { + AnnotatedType[] annotatedExcTypes = Target_sun_reflect_annotation_TypeAnnotationParser.buildAnnotatedTypes(typeAnnotations, + CodeInfoDecoder.getMetadataPseudoConstantPool(), + SubstrateUtil.cast(this, AnnotatedElement.class), + getDeclaringClass(), + getGenericExceptionTypes(), + TypeAnnotation.TypeAnnotationTarget.THROWS); + holder.annotatedExceptionTypes = annotatedExcTypes; + return annotatedExcTypes; + } } public static final class ParameterAnnotationsComputer implements CustomFieldValueComputer { diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Method.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Method.java index d999d9a99c2f..47a1b67b8e0a 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Method.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Method.java @@ -26,8 +26,12 @@ // Checkstyle: allow reflection +import static com.oracle.svm.core.annotate.TargetElement.CONSTRUCTOR_NAME; + +import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Inject; import com.oracle.svm.core.annotate.RecomputeFieldValue; @@ -35,6 +39,7 @@ import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.util.VMError; import com.oracle.svm.reflect.hosted.ExecutableAccessorComputer; @@ -47,6 +52,14 @@ public final class Target_java_lang_reflect_Method { @Alias MethodRepository genericInfo; + @Alias private Class[] parameterTypes; + + @Alias @RecomputeFieldValue(kind = Kind.Reset)// + private byte[] annotations; + + @Alias @RecomputeFieldValue(kind = Kind.Reset)// + private byte[] parameterAnnotations; + @Alias // @RecomputeFieldValue(kind = Kind.Custom, declClass = ExecutableAccessorComputer.class) // Target_jdk_internal_reflect_MethodAccessor methodAccessor; @@ -54,17 +67,42 @@ public final class Target_java_lang_reflect_Method { @Inject @RecomputeFieldValue(kind = Kind.Custom, declClass = DefaultValueComputer.class) // Object defaultValue; + @Alias + @TargetElement(name = CONSTRUCTOR_NAME) + @SuppressWarnings("hiding") + public native void constructor(Class declaringClass, + String name, + Class[] parameterTypes, + Class returnType, + Class[] checkedExceptions, + int modifiers, + int slot, + String signature, + byte[] annotations, + byte[] parameterAnnotations, + byte[] annotationDefault); + @Alias native Target_java_lang_reflect_Method copy(); @Substitute public Target_jdk_internal_reflect_MethodAccessor acquireMethodAccessor() { if (methodAccessor == null) { - throw VMError.unsupportedFeature("Runtime reflection is not supported."); + throw VMError.unsupportedFeature("Runtime reflection is not supported for " + this); } return methodAccessor; } + @Substitute + public Annotation[][] getParameterAnnotations() { + Target_java_lang_reflect_Executable self = SubstrateUtil.cast(this, Target_java_lang_reflect_Executable.class); + Target_java_lang_reflect_Executable holder = ReflectionHelper.getHolder(self); + if (holder.parameterAnnotations != null) { + return holder.parameterAnnotations; + } + return self.sharedGetParameterAnnotations(parameterTypes, parameterAnnotations); + } + @Substitute public Object getDefaultValue() { Target_java_lang_reflect_Method holder = ReflectionHelper.getHolder(this); diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Parameter.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Parameter.java new file mode 100644 index 000000000000..cc74d34a0c13 --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_Parameter.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021, 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.reflect.target; + +// Checkstyle: allow reflection + +import java.lang.reflect.Parameter; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.annotate.TargetElement; + +@TargetClass(Parameter.class) +public final class Target_java_lang_reflect_Parameter { + @Alias // + @TargetElement(name = TargetElement.CONSTRUCTOR_NAME) + public native void constructor(String name, + int modifiers, + Target_java_lang_reflect_Executable executable, + int index); +} diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_ReflectAccess.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_ReflectAccess.java index b7dd97571d2b..44c84733cc2a 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_ReflectAccess.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_java_lang_reflect_ReflectAccess.java @@ -26,6 +26,7 @@ // Checkstyle: allow reflection +import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; @@ -44,6 +45,8 @@ public final class Target_java_lang_reflect_ReflectAccess { public Target_java_lang_reflect_Method copyMethod(Target_java_lang_reflect_Method method) { Target_java_lang_reflect_Method copy = method.copy(); copy.genericInfo = method.genericInfo; + Util_java_lang_reflect_ReflectAccess.copyExecutable(SubstrateUtil.cast(copy, Target_java_lang_reflect_Executable.class), + SubstrateUtil.cast(method, Target_java_lang_reflect_Executable.class)); return copy; } @@ -60,6 +63,21 @@ public Target_java_lang_reflect_Field copyField(Target_java_lang_reflect_Field f public Target_java_lang_reflect_Constructor copyConstructor(Target_java_lang_reflect_Constructor constructor) { Target_java_lang_reflect_Constructor copy = constructor.copy(); copy.genericInfo = constructor.genericInfo; + Util_java_lang_reflect_ReflectAccess.copyExecutable(SubstrateUtil.cast(copy, Target_java_lang_reflect_Executable.class), + SubstrateUtil.cast(constructor, Target_java_lang_reflect_Executable.class)); return copy; } } + +class Util_java_lang_reflect_ReflectAccess { + static void copyExecutable(Target_java_lang_reflect_Executable copy, Target_java_lang_reflect_Executable executable) { + copy.parameters = executable.parameters; + copy.declaredAnnotations = executable.declaredAnnotations; + copy.parameterAnnotations = executable.parameterAnnotations; + copy.typeAnnotations = executable.typeAnnotations; + copy.annotatedReceiverType = executable.annotatedReceiverType; + copy.annotatedReturnType = executable.annotatedReturnType; + copy.annotatedParameterTypes = executable.annotatedParameterTypes; + copy.annotatedExceptionTypes = executable.annotatedExceptionTypes; + } +} diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_sun_reflect_annotation_AnnotationParser.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_sun_reflect_annotation_AnnotationParser.java new file mode 100644 index 000000000000..8dd4d31bd7d0 --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_sun_reflect_annotation_AnnotationParser.java @@ -0,0 +1,518 @@ +/* + * Copyright (c) 2021, 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.reflect.target; + +import java.lang.annotation.Annotation; +import java.lang.annotation.AnnotationFormatError; +import java.lang.annotation.RetentionPolicy; +// Checkstyle: stop +import java.lang.reflect.Method; +// Checkstyle: resume +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.annotate.TargetElement; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.jdk.Target_jdk_internal_reflect_ConstantPool; +import com.oracle.svm.core.util.VMError; + +// Checkstyle: stop +import sun.reflect.annotation.AnnotationParser; +import sun.reflect.annotation.AnnotationType; +import sun.reflect.annotation.EnumConstantNotPresentExceptionProxy; +import sun.reflect.annotation.ExceptionProxy; +// Checkstyle: resume + +/** + * Substitutions in this class are required to adapt the JDK encoding for annotations to our + * modified version of it. See {@link com.oracle.svm.core.code.CodeInfoEncoder} for a description of + * the changes and the rationale behind them. + */ +@TargetClass(AnnotationParser.class) +@SuppressWarnings("unused") +public final class Target_sun_reflect_annotation_AnnotationParser { + @Alias + public static native Map, Annotation> parseAnnotations( + byte[] rawAnnotations, + Target_jdk_internal_reflect_ConstantPool constPool, + Class container); + + @Substitute + private static Map, Annotation> parseAnnotations2( + byte[] rawAnnotations, + Target_jdk_internal_reflect_ConstantPool constPool, + Class container, + Class[] selectAnnotationClasses) { + Map, Annotation> result = new LinkedHashMap, Annotation>(); + ByteBuffer buf = ByteBuffer.wrap(rawAnnotations); + buf.order(ConfigurationValues.getTarget().arch.getByteOrder()); + int numAnnotations = buf.getShort() & 0xFFFF; + for (int i = 0; i < numAnnotations; i++) { + Annotation a = parseAnnotation2(buf, constPool, container, false, selectAnnotationClasses); + if (a != null) { + Class klass = a.annotationType(); + if (AnnotationType.getInstance(klass).retention() == RetentionPolicy.RUNTIME && + result.put(klass, a) != null) { + throw new AnnotationFormatError( + "Duplicate annotation for class: " + klass + ": " + a); + } + } + } + return result; + } + + @Alias + public static native Annotation[][] parseParameterAnnotations( + byte[] rawAnnotations, + Target_jdk_internal_reflect_ConstantPool constPool, + Class container); + + @Substitute + private static Annotation[][] parseParameterAnnotations2( + byte[] rawAnnotations, + Target_jdk_internal_reflect_ConstantPool constPool, + Class container) { + ByteBuffer buf = ByteBuffer.wrap(rawAnnotations); + buf.order(ConfigurationValues.getTarget().arch.getByteOrder()); + int numParameters = buf.get() & 0xFF; + Annotation[][] result = new Annotation[numParameters][]; + + for (int i = 0; i < numParameters; i++) { + int numAnnotations = buf.getShort() & 0xFFFF; + List annotations = new ArrayList(numAnnotations); + for (int j = 0; j < numAnnotations; j++) { + Annotation a = parseAnnotation(buf, constPool, container, false); + if (a != null) { + AnnotationType type = AnnotationType.getInstance( + a.annotationType()); + if (type.retention() == RetentionPolicy.RUNTIME) { + annotations.add(a); + } + } + } + result[i] = annotations.toArray(EMPTY_ANNOTATIONS_ARRAY); + } + return result; + } + + // Checkstyle: stop + @Alias// + private static Annotation[] EMPTY_ANNOTATIONS_ARRAY; + // Checkstyle: resume + + @Alias + static native Annotation parseAnnotation(ByteBuffer buf, + Target_jdk_internal_reflect_ConstantPool constPool, + Class container, + boolean exceptionOnMissingAnnotationClass); + + @Substitute + @SuppressWarnings("unchecked") + private static Annotation parseAnnotation2(ByteBuffer buf, + Target_jdk_internal_reflect_ConstantPool constPool, + Class container, + boolean exceptionOnMissingAnnotationClass, + Class[] selectAnnotationClasses) { + int typeIndex = buf.getInt(); + Class annotationClass; + try { + annotationClass = (Class) constPool.getClassAt(typeIndex); + } catch (Throwable e) { + if (exceptionOnMissingAnnotationClass) { + throw new TypeNotPresentException("[unknown]", e); + } + skipAnnotation(buf, false); + return null; + } + + if (selectAnnotationClasses != null && !contains(selectAnnotationClasses, annotationClass)) { + skipAnnotation(buf, false); + return null; + } + AnnotationType type; + try { + type = AnnotationType.getInstance(annotationClass); + } catch (IllegalArgumentException e) { + skipAnnotation(buf, false); + return null; + } + if (type == null) { + skipAnnotation(buf, false); + return null; + } + + Map> memberTypes = type.memberTypes(); + Map memberValues = new LinkedHashMap<>(type.memberDefaults()); + + int numMembers = buf.getShort() & 0xFFFF; + for (int i = 0; i < numMembers; i++) { + int memberNameIndex = buf.getInt(); + String memberName = constPool.getUTF8At(memberNameIndex); + Class memberType = memberTypes.get(memberName); + + if (memberType == null) { + // Member is no longer present in annotation type; ignore it + skipMemberValue(buf); + } else { + Object value = parseMemberValue(memberType, buf, constPool, container); + if (value instanceof Target_sun_reflect_annotation_AnnotationTypeMismatchExceptionProxy) { + ((Target_sun_reflect_annotation_AnnotationTypeMismatchExceptionProxy) value).setMember(type.members().get(memberName)); + } + memberValues.put(memberName, value); + } + } + return annotationForMap(annotationClass, memberValues); + } + + @Alias + public static native Object parseMemberValue(Class memberType, + ByteBuffer buf, + Target_jdk_internal_reflect_ConstantPool constPool, + Class container); + + @Substitute + private static Object parseClassValue(ByteBuffer buf, + Target_jdk_internal_reflect_ConstantPool constPool, + Class container) { + int classIndex = buf.getInt(); + try { + return constPool.getClassAt(classIndex); + } catch (Throwable t) { + throw VMError.shouldNotReachHere(); + } + } + + @Substitute + @SuppressWarnings({"unchecked", "rawtypes"}) + private static Object parseEnumValue(Class enumType, ByteBuffer buf, + Target_jdk_internal_reflect_ConstantPool constPool, + Class container) { + int typeIndex = buf.getInt(); + int constNameIndex = buf.getInt(); + String constName = constPool.getUTF8At(constNameIndex); + + if (enumType != constPool.getClassAt(typeIndex)) { + Target_sun_reflect_annotation_AnnotationTypeMismatchExceptionProxy e = new Target_sun_reflect_annotation_AnnotationTypeMismatchExceptionProxy(); + e.constructor(enumType.getTypeName() + "." + constName); + return e; + } + + try { + return Enum.valueOf(enumType, constName); + } catch (IllegalArgumentException e) { + return new EnumConstantNotPresentExceptionProxy((Class>) enumType, constName); + } + } + + @Substitute + private static Object parseConst(int tag, + ByteBuffer buf, Target_jdk_internal_reflect_ConstantPool constPool) { + switch (tag) { + case 'B': + return buf.get(); + case 'C': + return buf.getChar(); + case 'D': + return buf.getDouble(); + case 'F': + return buf.getFloat(); + case 'I': + return buf.getInt(); + case 'J': + return buf.getLong(); + case 'S': + return buf.getShort(); + case 'Z': + byte value = buf.get(); + assert value == 1 || value == 0; + return value == 1; + case 's': + return constPool.getUTF8At(buf.getInt()); + default: + throw new AnnotationFormatError( + "Invalid member-value tag in annotation: " + tag); + } + } + + @Substitute + private static Object parseByteArray(int length, + ByteBuffer buf, Target_jdk_internal_reflect_ConstantPool constPool) { + byte[] result = new byte[length]; + boolean typeMismatch = false; + int tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 'B') { + result[i] = buf.get(); + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + @Substitute + private static Object parseCharArray(int length, + ByteBuffer buf, Target_jdk_internal_reflect_ConstantPool constPool) { + char[] result = new char[length]; + boolean typeMismatch = false; + byte tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 'C') { + result[i] = buf.getChar(); + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + @Substitute + private static Object parseDoubleArray(int length, + ByteBuffer buf, Target_jdk_internal_reflect_ConstantPool constPool) { + double[] result = new double[length]; + boolean typeMismatch = false; + int tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 'D') { + result[i] = buf.getDouble(); + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + @Substitute + private static Object parseFloatArray(int length, + ByteBuffer buf, Target_jdk_internal_reflect_ConstantPool constPool) { + float[] result = new float[length]; + boolean typeMismatch = false; + int tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 'F') { + result[i] = buf.getFloat(); + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + @Substitute + private static Object parseIntArray(int length, + ByteBuffer buf, Target_jdk_internal_reflect_ConstantPool constPool) { + int[] result = new int[length]; + boolean typeMismatch = false; + int tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 'I') { + result[i] = buf.getInt(); + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + @Substitute + private static Object parseLongArray(int length, + ByteBuffer buf, Target_jdk_internal_reflect_ConstantPool constPool) { + long[] result = new long[length]; + boolean typeMismatch = false; + int tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 'J') { + result[i] = buf.getLong(); + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + @Substitute + private static Object parseShortArray(int length, + ByteBuffer buf, Target_jdk_internal_reflect_ConstantPool constPool) { + short[] result = new short[length]; + boolean typeMismatch = false; + int tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 'S') { + result[i] = buf.getShort(); + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + @Substitute + private static Object parseBooleanArray(int length, + ByteBuffer buf, Target_jdk_internal_reflect_ConstantPool constPool) { + boolean[] result = new boolean[length]; + boolean typeMismatch = false; + int tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 'Z') { + byte value = buf.get(); + if (value != 0 && value != 1) { + skipMemberValue(tag, buf); + typeMismatch = true; + } + result[i] = value == 1; + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + @Substitute + private static Object parseStringArray(int length, + ByteBuffer buf, Target_jdk_internal_reflect_ConstantPool constPool) { + String[] result = new String[length]; + boolean typeMismatch = false; + int tag = 0; + + for (int i = 0; i < length; i++) { + tag = buf.get(); + if (tag == 's') { + int index = buf.getInt(); + result[i] = constPool.getUTF8At(index); + } else { + skipMemberValue(tag, buf); + typeMismatch = true; + } + } + return typeMismatch ? exceptionProxy(tag) : result; + } + + @Substitute + private static void skipAnnotation(ByteBuffer buf, boolean complete) { + if (complete) { + buf.getInt(); // Skip type index + } + int numMembers = buf.getShort() & 0xFFFF; + for (int i = 0; i < numMembers; i++) { + buf.getInt(); // Skip memberNameIndex + skipMemberValue(buf); + } + } + + @Alias + private static native void skipMemberValue(ByteBuffer buf); + + @Substitute + private static void skipMemberValue(int tag, ByteBuffer buf) { + switch (tag) { + case 'e': // Enum value + buf.getLong(); // (Two ints, actually.) + break; + case '@': + skipAnnotation(buf, true); + break; + case '[': + skipArray(buf); + break; + case 'c': + case 's': + // Class, or String + buf.getInt(); + break; + default: + // primitive + switch (tag) { + case 'Z': + case 'B': + buf.get(); + break; + case 'S': + case 'C': + buf.getShort(); + break; + case 'I': + case 'F': + buf.getInt(); + break; + case 'J': + case 'D': + buf.getLong(); + break; + } + } + } + + @Alias + private static native void skipArray(ByteBuffer buf); + + @Alias + public static native Annotation annotationForMap(Class type, Map memberValues); + + @Alias + private static native ExceptionProxy exceptionProxy(int tag); + + @Alias + private static native boolean contains(Object[] array, Object element); +} + +@TargetClass(className = "sun.reflect.annotation.AnnotationTypeMismatchExceptionProxy") +final class Target_sun_reflect_annotation_AnnotationTypeMismatchExceptionProxy { + @Alias + @TargetElement(name = TargetElement.CONSTRUCTOR_NAME) + native void constructor(String foundType); + + @Alias + native Target_sun_reflect_annotation_AnnotationTypeMismatchExceptionProxy setMember(Method member); +} diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_sun_reflect_annotation_TypeAnnotationParser.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_sun_reflect_annotation_TypeAnnotationParser.java new file mode 100644 index 000000000000..d786dc9b21d9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/target/Target_sun_reflect_annotation_TypeAnnotationParser.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2021, 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.reflect.target; + +// Checkstyle: allow reflection + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Type; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.jdk.Target_jdk_internal_reflect_ConstantPool; + +import sun.reflect.annotation.TypeAnnotation; +import sun.reflect.annotation.TypeAnnotationParser; + +@TargetClass(TypeAnnotationParser.class) +public final class Target_sun_reflect_annotation_TypeAnnotationParser { + @Alias + public static native AnnotatedType buildAnnotatedType(byte[] rawAnnotations, + Target_jdk_internal_reflect_ConstantPool cp, + AnnotatedElement decl, + Class container, + Type type, + TypeAnnotation.TypeAnnotationTarget filter); + + @Alias + public static native AnnotatedType[] buildAnnotatedTypes(byte[] rawAnnotations, + Target_jdk_internal_reflect_ConstantPool cp, + AnnotatedElement decl, + Class container, + Type[] types, + TypeAnnotation.TypeAnnotationTarget filter); + + @Alias + // Checkstyle: stop + private static TypeAnnotation[] EMPTY_TYPE_ANNOTATION_ARRAY; + // Checkstyle: resume + + @Substitute + private static TypeAnnotation[] parseTypeAnnotations(byte[] rawAnnotations, + Target_jdk_internal_reflect_ConstantPool cp, + AnnotatedElement baseDecl, + Class container) { + if (rawAnnotations == null) { + return EMPTY_TYPE_ANNOTATION_ARRAY; + } + + ByteBuffer buf = ByteBuffer.wrap(rawAnnotations); + buf.order(ConfigurationValues.getTarget().arch.getByteOrder()); + int annotationCount = buf.getShort() & 0xFFFF; + List typeAnnotations = new ArrayList<>(annotationCount); + + // Parse each TypeAnnotation + for (int i = 0; i < annotationCount; i++) { + TypeAnnotation ta = parseTypeAnnotation(buf, cp, baseDecl, container); + if (ta != null) { + typeAnnotations.add(ta); + } + } + + return typeAnnotations.toArray(EMPTY_TYPE_ANNOTATION_ARRAY); + } + + @Alias // + private static native TypeAnnotation parseTypeAnnotation(ByteBuffer buf, + Target_jdk_internal_reflect_ConstantPool cp, + AnnotatedElement baseDecl, + Class container); +} diff --git a/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/PermissionsFeature.java b/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/PermissionsFeature.java index 0a721658e47c..86ca19b15d05 100644 --- a/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/PermissionsFeature.java +++ b/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/PermissionsFeature.java @@ -292,30 +292,32 @@ private boolean callGraphImpl( debugContext.log(DebugContext.VERY_DETAILED_LEVEL, "Entered method: %s.", mName); for (InvokeTypeFlow invoke : m.getTypeFlow().getInvokes()) { for (AnalysisMethod callee : invoke.getCallees()) { - Set parents = visited.get(callee); - String calleeName = getMethodName(callee); - debugContext.log(DebugContext.VERY_DETAILED_LEVEL, "Callee: %s, new: %b.", calleeName, parents == null); - if (parents == null) { - parents = new HashSet<>(); - visited.put(callee, parents); - if (targets.contains(callee)) { + if (callee.isInvoked()) { + Set parents = visited.get(callee); + String calleeName = getMethodName(callee); + debugContext.log(DebugContext.VERY_DETAILED_LEVEL, "Callee: %s, new: %b.", calleeName, parents == null); + if (parents == null) { + parents = new HashSet<>(); + visited.put(callee, parents); + if (targets.contains(callee)) { + parents.add(m); + callPathContainsTarget = true; + continue; + } + boolean add = callGraphImpl(callee, targets, visited, path, debugContext); + if (add) { + parents.add(m); + debugContext.log(DebugContext.VERY_DETAILED_LEVEL, "Added callee: %s for %s.", calleeName, mName); + } + callPathContainsTarget |= add; + } else if (!isBacktrace(callee, path) || isBackTraceOverLanguageMethod(callee, path)) { parents.add(m); + debugContext.log(DebugContext.VERY_DETAILED_LEVEL, "Added backtrace callee: %s for %s.", calleeName, mName); callPathContainsTarget = true; - continue; - } - boolean add = callGraphImpl(callee, targets, visited, path, debugContext); - if (add) { - parents.add(m); - debugContext.log(DebugContext.VERY_DETAILED_LEVEL, "Added callee: %s for %s.", calleeName, mName); - } - callPathContainsTarget |= add; - } else if (!isBacktrace(callee, path) || isBackTraceOverLanguageMethod(callee, path)) { - parents.add(m); - debugContext.log(DebugContext.VERY_DETAILED_LEVEL, "Added backtrace callee: %s for %s.", calleeName, mName); - callPathContainsTarget = true; - } else { - if (debugContext.isLogEnabled(DebugContext.VERY_DETAILED_LEVEL)) { - debugContext.log(DebugContext.VERY_DETAILED_LEVEL, "Ignoring backtrace callee: %s for %s.", calleeName, mName); + } else { + if (debugContext.isLogEnabled(DebugContext.VERY_DETAILED_LEVEL)) { + debugContext.log(DebugContext.VERY_DETAILED_LEVEL, "Ignoring backtrace callee: %s for %s.", calleeName, mName); + } } } } diff --git a/vm/mx.vm/mx_vm_benchmark.py b/vm/mx.vm/mx_vm_benchmark.py index 2726017bb6c3..c4f1a44f76bd 100644 --- a/vm/mx.vm/mx_vm_benchmark.py +++ b/vm/mx.vm/mx_vm_benchmark.py @@ -162,6 +162,7 @@ def __init__(self, vm, bm_suite, args): self.base_image_build_args += ['-H:ConfigurationFileDirectories=' + self.config_dir] self.base_image_build_args += ['-H:+PrintAnalysisStatistics', '-H:AnalysisStatisticsFile=' + self.analysis_report_path] self.base_image_build_args += ['-H:+CollectImageBuildStatistics', '-H:ImageBuildStatisticsFile=' + self.image_build_report_path] + self.base_image_build_args += ['-H:+ConfigureReflectionMetadata'] if vm.is_llvm: self.base_image_build_args += ['-H:CompilerBackend=llvm', '-H:Features=org.graalvm.home.HomeFinderFeature', '-H:DeadlockWatchdogInterval=0'] if vm.gc: