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 9b032e46f43d..eab7b903a71a 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 @@ -49,6 +49,7 @@ import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_NATIVE_METHOD_BIND; import static org.graalvm.word.WordFactory.nullPointer; +import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; @@ -80,6 +81,7 @@ import com.oracle.svm.agent.stackaccess.EagerlyLoadedJavaStackAccess; import com.oracle.svm.agent.stackaccess.InterceptedState; import com.oracle.svm.agent.tracing.core.Tracer; +import com.oracle.svm.configure.ClassNameSupport; import com.oracle.svm.configure.LambdaConfigurationTypeDescriptor; import com.oracle.svm.configure.NamedConfigurationTypeDescriptor; import com.oracle.svm.configure.ProxyConfigurationTypeDescriptor; @@ -263,8 +265,17 @@ static Object getTypeDescriptor(JNIEnvironment env, JNIObjectHandle clazz) { } private static boolean forName(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { - JNIObjectHandle callerClass = state.getDirectCallerClass(); JNIObjectHandle name = getObjectArgument(thread, 0); + return handleForName(jni, bp, state, name); + } + + private static boolean forNameWithModule(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { + JNIObjectHandle name = getObjectArgument(thread, 1); + return handleForName(jni, bp, state, name); + } + + private static boolean handleForName(JNIEnvironment jni, Breakpoint bp, InterceptedState state, JNIObjectHandle name) { + JNIObjectHandle callerClass = state.getDirectCallerClass(); String className = fromJniString(jni, name); if (className == null) { return true; /* No point in tracing this. */ @@ -273,6 +284,14 @@ private static boolean forName(JNIEnvironment jni, JNIObjectHandle thread, Break return true; } + private static boolean arrayType(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { + JNIObjectHandle callerClass = state.getDirectCallerClass(); + JNIObjectHandle componentType = getReceiver(thread); + String arrayTypeName = ClassNameSupport.getArrayReflectionName(getClassNameOrNull(jni, componentType)); + traceReflectBreakpoint(jni, bp.clazz, nullHandle(), callerClass, "forName", null, state.getFullStackTraceOrNull(), arrayTypeName); + return true; + } + private static boolean getFields(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { return handleGetFields(jni, thread, bp, state); } @@ -543,6 +562,34 @@ private static boolean getEnclosingMethod(JNIEnvironment jni, JNIObjectHandle th return true; } + private static boolean accessField(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) { + JNIObjectHandle field = getReceiver(thread); + return accessFieldInternal(jni, state, field); + } + + private static boolean accessFieldUnsafe(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) { + JNIObjectHandle field = getObjectArgument(thread, 1); + return accessFieldInternal(jni, state, field); + } + + private static boolean accessFieldInternal(JNIEnvironment jni, InterceptedState state, JNIObjectHandle field) { + JNIObjectHandle callerClass = state.getDirectCallerClass(); + + JNIObjectHandle declaring = Support.callObjectMethod(jni, field, agent.handles().javaLangReflectMemberGetDeclaringClass); + if (clearException(jni)) { + declaring = nullHandle(); + } + + JNIObjectHandle nameHandle = Support.callObjectMethod(jni, field, agent.handles().javaLangReflectMemberGetName); + if (clearException(jni)) { + nameHandle = nullHandle(); + } + String name = fromJniString(jni, nameHandle); + + traceReflectBreakpoint(jni, declaring, declaring, callerClass, "accessField", declaring.notEqual(nullHandle()), state.getFullStackTraceOrNull(), name); + return true; + } + private static boolean invokeMethod(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { return handleInvokeMethod(jni, thread, bp, state, true); } @@ -582,6 +629,27 @@ private static boolean handleInvokeMethod(JNIEnvironment jni, JNIObjectHandle th JNIObjectHandle clazz = getObjectArgument(thread, 1); traceReflectBreakpoint(jni, clazz, nullHandle(), callerClass, "newInstance", clazz.notEqual(nullHandle()), state.getFullStackTraceOrNull()); } + + /* + * Calling Field.get/set and associated methods through Method.invoke should register the + * field for reflective access + */ + if (isInvoke && isFieldAccess(jni, declaring, name)) { + JNIObjectHandle field = getObjectArgument(thread, 1); + + JNIObjectHandle clazz = Support.callObjectMethod(jni, field, agent.handles().javaLangReflectMemberGetDeclaringClass); + if (clearException(jni)) { + clazz = nullHandle(); + } + + JNIObjectHandle fieldNameHandle = Support.callObjectMethod(jni, field, agent.handles().javaLangReflectMemberGetName); + if (clearException(jni)) { + clazz = nullHandle(); + } + String fieldName = fromJniString(jni, fieldNameHandle); + + traceReflectBreakpoint(jni, clazz, nullHandle(), callerClass, "accessField", clazz.notEqual(nullHandle()), state.getFullStackTraceOrNull(), fieldName); + } return true; } @@ -597,6 +665,24 @@ private static boolean isClassNewInstance(JNIEnvironment jni, JNIObjectHandle de return "java.lang.Class".equals(className); } + /** + * A call is a field access iff it is a call to a method from {@link Field} whose name starts + * with "get" or "set". + */ + private static boolean isFieldAccess(JNIEnvironment jni, JNIObjectHandle declaring, String name) { + if (declaring.equal(nullHandle()) || name == null || (!name.startsWith("get") && !name.startsWith("set"))) { + return false; + } + + JNIObjectHandle classNameHandle = Support.callObjectMethod(jni, declaring, agent.handles().javaLangClassGetName); + if (clearException(jni)) { + classNameHandle = nullHandle(); + } + String className = fromJniString(jni, classNameHandle); + + return Field.class.getName().equals(className); + } + private static boolean invokeConstructor(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { return handleInvokeConstructor(jni, bp, state, getReceiver(thread)); } @@ -1650,6 +1736,7 @@ private interface BreakpointHandler { private static final BreakpointSpecification[] BREAKPOINT_SPECIFICATIONS = { brk("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", BreakpointInterceptor::forName), brk("java/lang/Class", "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;", BreakpointInterceptor::forName), + brk("java/lang/Class", "forName", "(Ljava/lang/Module;Ljava/lang/String;)Ljava/lang/Class;", BreakpointInterceptor::forNameWithModule), brk("java/lang/Class", "getFields", "()[Ljava/lang/reflect/Field;", BreakpointInterceptor::getFields), brk("java/lang/Class", "getClasses", "()[Ljava/lang/Class;", BreakpointInterceptor::getClasses), @@ -1673,6 +1760,7 @@ private interface BreakpointHandler { brk("java/lang/Class", "getDeclaredConstructor", "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;", BreakpointInterceptor::getConstructor), brk("java/lang/Class", "newInstance", "()Ljava/lang/Object;", BreakpointInterceptor::newInstance), + brk("java/lang/Class", "arrayType", "()Ljava/lang/Class;", BreakpointInterceptor::arrayType), 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), @@ -1712,6 +1800,7 @@ private interface BreakpointHandler { optionalBrk("jdk/internal/misc/Unsafe", "objectFieldOffset", "(Ljava/lang/Class;Ljava/lang/String;)J", BreakpointInterceptor::objectFieldOffsetByName), brk("sun/misc/Unsafe", "allocateInstance", "(Ljava/lang/Class;)Ljava/lang/Object;", BreakpointInterceptor::allocateInstance), + brk("sun/misc/Unsafe", "objectFieldOffset", "(Ljava/lang/reflect/Field;)J", BreakpointInterceptor::accessFieldUnsafe), optionalBrk("java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", @@ -1762,6 +1851,9 @@ private interface BreakpointHandler { optionalBrk("java/lang/invoke/MethodHandles$Lookup", "unreflectSetter", "(Ljava/lang/reflect/Field;)Ljava/lang/invoke/MethodHandle;", BreakpointInterceptor::unreflectField), + optionalBrk("java/lang/invoke/MethodHandles$Lookup", "unreflectVarHandle", + "(Ljava/lang/reflect/Field;)Ljava/lang/invoke/VarHandle;", + BreakpointInterceptor::unreflectField), optionalBrk("java/lang/invoke/MethodHandleProxies", "asInterfaceInstance", "(Ljava/lang/Class;Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;", BreakpointInterceptor::asInterfaceInstance), @@ -1810,6 +1902,24 @@ private static boolean allocateInstance(JNIEnvironment jni, JNIObjectHandle thre 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/reflect/Field", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", BreakpointInterceptor::accessField), + brk("java/lang/reflect/Field", "set", "(Ljava/lang/Object;Ljava/lang/Object;)V", BreakpointInterceptor::accessField), + brk("java/lang/reflect/Field", "getBoolean", "(Ljava/lang/Object;)Z", BreakpointInterceptor::accessField), + brk("java/lang/reflect/Field", "setBoolean", "(Ljava/lang/Object;Z)V", BreakpointInterceptor::accessField), + brk("java/lang/reflect/Field", "getByte", "(Ljava/lang/Object;)B", BreakpointInterceptor::accessField), + brk("java/lang/reflect/Field", "setByte", "(Ljava/lang/Object;B)V", BreakpointInterceptor::accessField), + brk("java/lang/reflect/Field", "getShort", "(Ljava/lang/Object;)S", BreakpointInterceptor::accessField), + brk("java/lang/reflect/Field", "setShort", "(Ljava/lang/Object;S)V", BreakpointInterceptor::accessField), + brk("java/lang/reflect/Field", "getChar", "(Ljava/lang/Object;)C", BreakpointInterceptor::accessField), + brk("java/lang/reflect/Field", "setChar", "(Ljava/lang/Object;C)V", BreakpointInterceptor::accessField), + brk("java/lang/reflect/Field", "getInt", "(Ljava/lang/Object;)I", BreakpointInterceptor::accessField), + brk("java/lang/reflect/Field", "setInt", "(Ljava/lang/Object;I)V", BreakpointInterceptor::accessField), + brk("java/lang/reflect/Field", "getLong", "(Ljava/lang/Object;)J", BreakpointInterceptor::accessField), + brk("java/lang/reflect/Field", "setLong", "(Ljava/lang/Object;J)V", BreakpointInterceptor::accessField), + brk("java/lang/reflect/Field", "getFloat", "(Ljava/lang/Object;)F", BreakpointInterceptor::accessField), + brk("java/lang/reflect/Field", "setFloat", "(Ljava/lang/Object;F)V", BreakpointInterceptor::accessField), + brk("java/lang/reflect/Field", "getDouble", "(Ljava/lang/Object;)D", BreakpointInterceptor::accessField), + brk("java/lang/reflect/Field", "setDouble", "(Ljava/lang/Object;D)V", BreakpointInterceptor::accessField), }; private static final BreakpointSpecification[] CLASS_PREDEFINITION_BREAKPOINT_SPECIFICATIONS = { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ClassNameSupport.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ClassNameSupport.java index a7d69f5152cb..a2f83bfbe040 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ClassNameSupport.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ClassNameSupport.java @@ -124,6 +124,13 @@ private static String reflectionNameToJNINameUnchecked(String reflectionName) { return reflectionName.replace('.', '/'); } + public static String getArrayReflectionName(String componentReflectionName) { + if (!isValidReflectionName(componentReflectionName)) { + return componentReflectionName; + } + return "[" + (wrappingArrayDimension(componentReflectionName) > 0 ? componentReflectionName : typeNameToArrayElementType(componentReflectionName)); + } + private static String arrayElementTypeToTypeName(String arrayElementType, int startIndex) { char typeChar = arrayElementType.charAt(startIndex); return switch (typeChar) { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java index 5601fe4dd397..80cfff7547fc 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java @@ -113,6 +113,8 @@ public final class AccessAdvisor { // BytecodeDescriptor calls Class.forName internalCallerFilter.addOrGetChildren("sun.invoke.util.BytecodeDescriptor", ConfigurationFilter.Inclusion.Include); internalCallerFilter.addOrGetChildren("sun.launcher.**", ConfigurationFilter.Inclusion.Exclude); + // LoggingMXBeanAccess calls Class.forName + internalCallerFilter.addOrGetChildren("sun.management.ManagementFactoryHelper$LoggingMXBeanAccess", ConfigurationFilter.Inclusion.Exclude); internalCallerFilter.addOrGetChildren("sun.misc.**", ConfigurationFilter.Inclusion.Exclude); internalCallerFilter.addOrGetChildren("sun.net.**", ConfigurationFilter.Inclusion.Exclude); // Uses constructor reflection on exceptions 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 9de762cdef69..2fb620094ad0 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 @@ -40,7 +40,6 @@ 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; import com.oracle.svm.configure.config.ResourceConfiguration; import com.oracle.svm.configure.config.SignatureUtil; import com.oracle.svm.configure.config.TypeConfiguration; @@ -127,68 +126,52 @@ public void processEntry(EconomicMap entry, ConfigurationSet con ConfigurationMemberAccessibility accessibility = ConfigurationMemberAccessibility.QUERIED; ConfigurationTypeDescriptor clazzOrDeclaringClass = entry.containsKey("declaring_class") ? descriptorForClass(entry.get("declaring_class")) : clazz; switch (function) { - case "getDeclaredFields": { - configuration.getOrCreateType(condition, clazz).setAllDeclaredFields(ConfigurationMemberAccessibility.ACCESSED); - break; - } - case "getFields": { - configuration.getOrCreateType(condition, clazz).setAllPublicFields(ConfigurationMemberAccessibility.ACCESSED); + case "getDeclaredFields": + case "getFields": + case "getDeclaredMethods": + case "getMethods": + case "getDeclaredConstructors": + case "getConstructors": + case "getDeclaredClasses": + case "getClasses": + case "getRecordComponents": + case "getPermittedSubclasses": + case "getNestMembers": + case "getSigners": { + configuration.getOrCreateType(condition, clazz); break; } - case "getDeclaredMethods": { - configuration.getOrCreateType(condition, clazz).setAllDeclaredMethods(accessibility); - break; - } - case "asInterfaceInstance": - accessibility = ConfigurationMemberAccessibility.ACCESSED; - // fallthrough - case "getMethods": { - configuration.getOrCreateType(condition, clazz).setAllPublicMethods(accessibility); + case "getDeclaredField": + case "getField": + case "getDeclaredMethod": + case "getMethod": + case "getDeclaredConstructor": + case "getConstructor": { + configuration.getOrCreateType(condition, clazz); + if (!clazzOrDeclaringClass.equals(clazz)) { + configuration.getOrCreateType(condition, clazz); + } break; } - case "getDeclaredConstructors": { - configuration.getOrCreateType(condition, clazz).setAllDeclaredConstructors(accessibility); - break; - } - case "getConstructors": { - configuration.getOrCreateType(condition, clazz).setAllPublicConstructors(accessibility); + case "accessField": { + expectSize(args, 1); + String name = (String) args.get(0); + configuration.getOrCreateType(condition, clazz).addField(name, ConfigurationMemberDeclaration.DECLARED, false); break; } - case "getDeclaredClasses": { - configuration.getOrCreateType(condition, clazz).setAllDeclaredClasses(); - break; - } - case "getRecordComponents": { - configuration.getOrCreateType(condition, clazz).setAllRecordComponents(); - break; - } - case "getPermittedSubclasses": { - configuration.getOrCreateType(condition, clazz).setAllPermittedSubclasses(); - break; - } - case "getNestMembers": { - configuration.getOrCreateType(condition, clazz).setAllNestMembers(); - break; - } - case "getSigners": { - configuration.getOrCreateType(condition, clazz).setAllSigners(); - break; - } - case "getClasses": { - configuration.getOrCreateType(condition, clazz).setAllPublicClasses(); + case "asInterfaceInstance": { + accessibility = ConfigurationMemberAccessibility.ACCESSED; + configuration.getOrCreateType(condition, clazz).setAllPublicMethods(accessibility); break; } case "objectFieldOffset": case "findFieldHandle": - case "unreflectField": - case "getDeclaredField": + case "unreflectField": { declaration = "findFieldHandle".equals(function) ? ConfigurationMemberDeclaration.PRESENT : ConfigurationMemberDeclaration.DECLARED; - // fall through - case "getField": { configuration.getOrCreateType(condition, clazzOrDeclaringClass).addField(singleElement(args), declaration, false); if (!clazzOrDeclaringClass.equals(clazz)) { configuration.getOrCreateType(condition, clazz); @@ -196,49 +179,33 @@ public void processEntry(EconomicMap entry, ConfigurationSet con break; } - case "getDeclaredMethod": case "findMethodHandle": - case "invokeMethod": - declaration = "getDeclaredMethod".equals(function) ? ConfigurationMemberDeclaration.DECLARED : ConfigurationMemberDeclaration.PRESENT; - // fall through - case "getMethod": { + case "invokeMethod": { expectSize(args, 2); - accessibility = (!trackReflectionMetadata || function.equals("invokeMethod") || function.equals("findMethodHandle")) - ? ConfigurationMemberAccessibility.ACCESSED - : ConfigurationMemberAccessibility.QUERIED; 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(); } - if (accessibility == ConfigurationMemberAccessibility.ACCESSED) { - configuration.getOrCreateType(condition, clazzOrDeclaringClass).addMethod(name, SignatureUtil.toInternalSignature(parameterTypes), declaration, accessibility); - } - if (accessibility == ConfigurationMemberAccessibility.QUERIED || (!trackReflectionMetadata && !clazzOrDeclaringClass.equals(clazz))) { + configuration.getOrCreateType(condition, clazzOrDeclaringClass).addMethod(name, SignatureUtil.toInternalSignature(parameterTypes), ConfigurationMemberDeclaration.PRESENT, + ConfigurationMemberAccessibility.ACCESSED); + if (!trackReflectionMetadata && !clazzOrDeclaringClass.equals(clazz)) { configuration.getOrCreateType(condition, clazz); } break; } - case "getDeclaredConstructor": case "findConstructorHandle": - case "invokeConstructor": - declaration = "getDeclaredConstructor".equals(function) ? ConfigurationMemberDeclaration.DECLARED : ConfigurationMemberDeclaration.PRESENT; - // fall through - case "getConstructor": { - accessibility = (!trackReflectionMetadata || function.equals("invokeConstructor") || function.equals("findConstructorHandle")) - ? ConfigurationMemberAccessibility.ACCESSED - : ConfigurationMemberAccessibility.QUERIED; + case "invokeConstructor": { + declaration = ConfigurationMemberDeclaration.PRESENT; + accessibility = ConfigurationMemberAccessibility.ACCESSED; 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"; - ConfigurationType configurationType = configuration.getOrCreateType(condition, clazzOrDeclaringClass); - if (accessibility == ConfigurationMemberAccessibility.ACCESSED) { - configurationType.addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, signature, declaration, accessibility); - } + configuration.getOrCreateType(condition, clazzOrDeclaringClass).addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, signature, declaration, accessibility); break; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeMetadataDecoderImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeMetadataDecoderImpl.java index d87d1d92bbf0..7aba20e7de88 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeMetadataDecoderImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeMetadataDecoderImpl.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.code; -import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Field; @@ -49,6 +48,7 @@ import com.oracle.svm.core.reflect.RuntimeMetadataDecoder; import com.oracle.svm.core.reflect.target.ReflectionObjectFactory; import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_Executable; +import com.oracle.svm.core.snippets.KnownIntrinsics; import com.oracle.svm.core.util.ByteArrayReader; import jdk.graal.compiler.core.common.util.UnsafeArrayTypeReader; @@ -695,7 +695,7 @@ private static T[] decodeArray(UnsafeArrayTypeReader buf, Class elementTy if (isErrorIndex(length)) { decodeAndThrowError(length, layerId); } - T[] result = (T[]) Array.newInstance(elementType, length); + T[] result = (T[]) KnownIntrinsics.unvalidatedNewArray(elementType, length); int valueCount = 0; for (int i = 0; i < length; ++i) { T element = elementDecoder.apply(i); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java index 3db63d93410a..22a70fa78421 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java @@ -357,18 +357,7 @@ private static Class forName(String className, ClassLoader classLoader, boole if (className == null) { return null; } - Object result = null; - for (var singleton : layeredSingletons()) { - Object newResult = singleton.forName0(className, classLoader); - result = newResult != null ? newResult : result; - /* - * The class might have been registered in a shared layer but was not yet available. In - * that case, the extension layers need to be checked too. - */ - if (result != null && result != NEGATIVE_QUERY) { - break; - } - } + Object result = queryResultFor(className, classLoader); // Note: for non-predefined classes, we (currently) don't need to check the provided loader // TODO rewrite stack traces (GR-42813) if (result instanceof Class) { @@ -397,6 +386,22 @@ private static Class forName(String className, ClassLoader classLoader, boole throw VMError.shouldNotReachHere("Class.forName result should be Class, ClassNotFoundException or Error: " + result); } + private static Object queryResultFor(String className, ClassLoader classLoader) { + Object result = null; + for (var singleton : layeredSingletons()) { + Object newResult = singleton.forName0(className, classLoader); + result = newResult != null ? newResult : result; + /* + * The class might have been registered in a shared layer but was not yet available. In + * that case, the extension layers need to be checked too. + */ + if (result != null && result != NEGATIVE_QUERY) { + break; + } + } + return result; + } + private Object forName0(String className, ClassLoader classLoader) { if (className.endsWith("[]")) { /* Querying array classes with their "TypeName[]" name always throws */ @@ -451,12 +456,15 @@ public static RuntimeConditionSet getConditionFor(Class jClass) { } public static boolean isRegisteredClass(String className) { - assert respectClassLoader(); - RuntimeConditionSet conditionSet = getConditionForName(className); - if (conditionSet == null) { - return false; + if (respectClassLoader()) { + RuntimeConditionSet conditionSet = getConditionForName(className); + if (conditionSet == null) { + return false; + } + return conditionSet.satisfied(); + } else { + return queryResultFor(className, null) != null; } - return conditionSet.satisfied(); } @Platforms(Platform.HOSTED_ONLY.class) 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 123bad962417..c06ef55d3906 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 @@ -90,6 +90,7 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import com.oracle.svm.configure.ClassNameSupport; import com.oracle.svm.configure.config.SignatureUtil; import com.oracle.svm.core.BuildPhaseProvider.AfterHostedUniverse; import com.oracle.svm.core.BuildPhaseProvider.CompileQueueFinished; @@ -1976,7 +1977,7 @@ public DynamicHub arrayType() { if (MetadataTracer.enabled()) { MetadataTracer.singleton().traceReflectionArrayType(toClass(this)); } - if (companion.arrayHub == null) { + if (companion.arrayHub == null || (throwMissingRegistrationErrors() && !ClassForNameSupport.isRegisteredClass(ClassNameSupport.getArrayReflectionName(getName())))) { MissingReflectionRegistrationUtils.reportClassAccess(getTypeName() + "[]"); } return companion.arrayHub; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/DynamicAccessDetectionPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/DynamicAccessDetectionPhase.java index 562c386a37d3..2fa66b1cc642 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/DynamicAccessDetectionPhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/DynamicAccessDetectionPhase.java @@ -24,20 +24,6 @@ */ package com.oracle.svm.hosted.phases; -import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; -import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.svm.hosted.DynamicAccessDetectionFeature; -import jdk.graal.compiler.graph.NodeSourcePosition; -import jdk.graal.compiler.nodes.StructuredGraph; -import jdk.graal.compiler.nodes.java.MethodCallTargetNode; -import jdk.graal.compiler.nodes.spi.CoreProviders; -import jdk.graal.compiler.phases.BasePhase; -import jdk.vm.ci.meta.JavaType; -import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.Signature; -import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.EconomicSet; - import java.io.File; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -67,6 +53,22 @@ import java.util.Objects; import java.util.Set; +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.EconomicSet; + +import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.svm.hosted.DynamicAccessDetectionFeature; + +import jdk.graal.compiler.graph.NodeSourcePosition; +import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.graal.compiler.nodes.java.MethodCallTargetNode; +import jdk.graal.compiler.nodes.spi.CoreProviders; +import jdk.graal.compiler.phases.BasePhase; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.Signature; + /** * This phase detects usages of dynamic access calls that might require metadata in reached parts of * the project. It does so by analyzing the specified class path entries, modules or packages and @@ -123,6 +125,25 @@ public record MethodInfo(DynamicAccessKind accessKind, String signature) { new MethodSignature("getSigners"), new MethodSignature("arrayType"), new MethodSignature("newInstance"))); + reflectionMethodSignatures.put(Field.class, Set.of( + new MethodSignature("get", Object.class), + new MethodSignature("set", Object.class, Object.class), + new MethodSignature("getBoolean", Object.class), + new MethodSignature("setBoolean", Object.class, boolean.class), + new MethodSignature("getByte", Object.class), + new MethodSignature("setByte", Object.class, byte.class), + new MethodSignature("getShort", Object.class), + new MethodSignature("setShort", Object.class, short.class), + new MethodSignature("getChar", Object.class), + new MethodSignature("setChar", Object.class, char.class), + new MethodSignature("getInt", Object.class), + new MethodSignature("setInt", Object.class, int.class), + new MethodSignature("getLong", Object.class), + new MethodSignature("setLong", Object.class, long.class), + new MethodSignature("getFloat", Object.class), + new MethodSignature("setFloat", Object.class, float.class), + new MethodSignature("getDouble", Object.class), + new MethodSignature("setDouble", Object.class, double.class))); reflectionMethodSignatures.put(Method.class, Set.of( new MethodSignature("invoke", Object.class, Object[].class))); reflectionMethodSignatures.put(MethodHandles.Lookup.class, Set.of( @@ -244,18 +265,22 @@ private static MethodInfo getMethodInfo(ResolvedJavaMethod method) { if (reflectionMethodSignatures.containsKey(declaringClass) && reflectionMethodSignatures.get(declaringClass).contains(methodSignature)) { - return new MethodInfo(DynamicAccessKind.Reflection, declaringClass.getName() + "#" + methodSignature); + return new MethodInfo(DynamicAccessKind.Reflection, getClassName(declaringClass) + "#" + methodSignature); } else if (resourceMethodSignatures.containsKey(declaringClass) && resourceMethodSignatures.get(declaringClass).contains(methodSignature)) { - return new MethodInfo(DynamicAccessKind.Resource, declaringClass.getName() + "#" + methodSignature); + return new MethodInfo(DynamicAccessKind.Resource, getClassName(declaringClass) + "#" + methodSignature); } else if (foreignMethodSignatures.containsKey(declaringClass) && foreignMethodSignatures.get(declaringClass).contains(methodSignature)) { - return new MethodInfo(DynamicAccessKind.Foreign, declaringClass.getName() + "#" + methodSignature); + return new MethodInfo(DynamicAccessKind.Foreign, getClassName(declaringClass) + "#" + methodSignature); } return null; } + private static String getClassName(Class clazz) { + return clazz.getName().replace('$', '.'); + } + /** * Returns the class path entry, module or package name of the caller class if it is included in * the value specified by the option, otherwise returns null.