From 7fc30ab18cbdae2d7d32cfcae61818c17ab02657 Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Tue, 13 May 2025 10:50:44 +0200 Subject: [PATCH] Do not require negative queries for impossible class names --- substratevm/CHANGELOG.md | 1 + .../oracle/svm/agent/JniCallInterceptor.java | 5 ++++- .../oracle/svm/configure/ClassNameSupport.java | 8 +++++++- .../svm/configure/config/ConfigurationSet.java | 8 ++++++-- .../svm/configure/trace/JniProcessor.java | 18 ++++++++++-------- .../configure/trace/ReflectionProcessor.java | 4 +++- .../svm/core/hub/ClassForNameSupport.java | 13 +------------ .../jni/access/JNIReflectionDictionary.java | 13 ++++--------- .../svm/hosted/jni/JNIAccessFeature.java | 5 +++-- 9 files changed, 39 insertions(+), 36 deletions(-) diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 1885bc3f30e5..73772928d596 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -20,6 +20,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-64584) Experimental option `-H:+RelativeCodePointers` to significantly reduce relocation entries in position-independent executables and shared libraries. * (GR-60238) JNI registration is now included as part of the `"reflection"` section of _reachability-metadata.json_ using the `"jniAccessible"` attribute. Registrations performed through the `"jni"` section of _reachability-metadata.json_ and through _jni-config.json_ will still be accepted and parsed correctly. * (GR-65008) Remove the sequential reachability handler. The only remaining variant is the concurrent reachability handler, which has been the default implementation since its introduction. Additionally, remove the `-H:-RunReachabilityHandlersConcurrently` option which was introduced to simplify migration and has been deprecated since Version 24.0.0. +* (GR-63268) Reflection and JNI queries do not require metadata entries to throw the expected JDK exception when querying a class that doesn't exist under `--exact-reachability-metadata` if the query cannot possibly be a valid class name ## GraalVM for JDK 24 (Internal Version 24.2.0) * (GR-59717) Added `DuringSetupAccess.registerObjectReachabilityHandler` to allow registering a callback that is executed when an object of a specified type is marked as reachable during heap scanning. diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/JniCallInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/JniCallInterceptor.java index 030a943980f7..43ea95289287 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/JniCallInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/JniCallInterceptor.java @@ -137,7 +137,10 @@ private static JNIObjectHandle findClass(JNIEnvironment env, CCharPointer name) result = nullHandle(); } if (shouldTrace()) { - traceCall(env, "FindClass", nullHandle(), nullHandle(), callerClass, name.notEqual(nullHandle()), state, fromCString(name)); + String className = fromCString(name); + if (className != null) { + traceCall(env, "FindClass", nullHandle(), nullHandle(), callerClass, name.notEqual(nullHandle()), state, className); + } } return result; } 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 4a29faa0b648..a7d69f5152cb 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 @@ -182,7 +182,13 @@ private static boolean isValidFullyQualifiedClassName(String name, int startInde return false; } lastPackageSeparatorIndex = i; - } else if (!Character.isJavaIdentifierPart(current)) { + } else if (current == '.' || current == ';' || current == '[' || current == '/') { + /* + * Some special characters are allowed in class files while not being permitted as + * code identifiers (e.g. '+', '-', ','). + * + * @see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.2.2 + */ return false; } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java index 1b41df51fe2c..bbaaef82e35d 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java @@ -151,7 +151,6 @@ public static List writeConfigurationToAllPaths(Function writeConfigurationToAllPaths(Function writeConfigurationToAllPaths(Function entry, ConfigurationSet configurat // Special: FindClass and DefineClass take the class in question as a string argument if (function.equals("FindClass") || function.equals("DefineClass")) { String jniName = singleElement(args); - ConfigurationTypeDescriptor type = NamedConfigurationTypeDescriptor.fromJNIName(jniName); - LazyValue reflectionName = lazyValue(ClassNameSupport.jniNameToReflectionName(jniName)); - if (!advisor.shouldIgnore(reflectionName, callerClassLazyValue, entry)) { - if (function.equals("FindClass")) { - if (!advisor.shouldIgnoreJniLookup(function, reflectionName, lazyNull(), lazyNull(), callerClassLazyValue, entry)) { - configurationSet.getReflectionConfiguration().getOrCreateType(condition, type).setJniAccessible(); + if (ClassNameSupport.isValidJNIName(jniName)) { + ConfigurationTypeDescriptor type = NamedConfigurationTypeDescriptor.fromJNIName(jniName); + LazyValue reflectionName = lazyValue(ClassNameSupport.jniNameToReflectionName(jniName)); + if (!advisor.shouldIgnore(reflectionName, callerClassLazyValue, entry)) { + if (function.equals("FindClass")) { + if (!advisor.shouldIgnoreJniLookup(function, reflectionName, lazyNull(), lazyNull(), callerClassLazyValue, entry)) { + configurationSet.getReflectionConfiguration().getOrCreateType(condition, type).setJniAccessible(); + } + } else if (!AccessAdvisor.PROXY_CLASS_NAME_PATTERN.matcher(jniName).matches()) { // DefineClass + LogUtils.warning("Unsupported JNI function DefineClass used to load class " + jniName); } - } else if (!AccessAdvisor.PROXY_CLASS_NAME_PATTERN.matcher(jniName).matches()) { // DefineClass - LogUtils.warning("Unsupported JNI function DefineClass used to load class " + jniName); } } return; 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 3b5af48d334e..acef9ca59d0f 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 @@ -32,6 +32,7 @@ import org.graalvm.collections.EconomicMap; +import com.oracle.svm.configure.ClassNameSupport; import com.oracle.svm.configure.ConfigurationTypeDescriptor; import com.oracle.svm.configure.NamedConfigurationTypeDescriptor; import com.oracle.svm.configure.ProxyConfigurationTypeDescriptor; @@ -101,7 +102,8 @@ public void processEntry(EconomicMap entry, ConfigurationSet con name = MetaUtil.internalNameToJava(MetaUtil.toInternalName(name), true, true); } if (!advisor.shouldIgnore(lazyValue(name), lazyValue(callerClass), entry) && - !(isLoadClass && advisor.shouldIgnoreLoadClass(lazyValue(name), lazyValue(callerClass), entry))) { + !(isLoadClass && advisor.shouldIgnoreLoadClass(lazyValue(name), lazyValue(callerClass), entry)) && + ClassNameSupport.isValidReflectionName(name)) { configuration.getOrCreateType(condition, NamedConfigurationTypeDescriptor.fromReflectionName(name)); } return; 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 c5efadad75ae..11a37b95f001 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 @@ -246,18 +246,7 @@ private Object forName0(String className, ClassLoader classLoader) { result = PredefinedClassesSupport.getLoadedForNameOrNull(className, classLoader); } if (result == null && !ClassNameSupport.isValidReflectionName(className)) { - if (result == null && ClassNameSupport.isValidJNIName(className)) { - var jniAlias = knownClasses.get(ClassNameSupport.jniNameToReflectionName(className)); - if (jniAlias != null && jniAlias.getValue() != null) { - result = NEGATIVE_QUERY; - } - } - if (result == null && ClassNameSupport.isValidTypeName(className)) { - var typeAlias = knownClasses.get(ClassNameSupport.typeNameToReflectionName(className)); - if (typeAlias != null && typeAlias.getValue() != null) { - result = NEGATIVE_QUERY; - } - } + result = NEGATIVE_QUERY; } return result == NEGATIVE_QUERY ? new ClassNotFoundException(className) : result; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIReflectionDictionary.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIReflectionDictionary.java index c81efd2b99f5..2021f81dfbfa 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIReflectionDictionary.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIReflectionDictionary.java @@ -180,14 +180,9 @@ public static Class getClassObjectByName(CharSequence name) { for (var dictionary : layeredSingletons()) { JNIAccessibleClass clazz = dictionary.classesByName.get(name); if (clazz == null && !ClassNameSupport.isValidJNIName(name.toString())) { - if (clazz == null && ClassNameSupport.isValidReflectionName(name.toString()) && dictionary.classesByName.containsKey(ClassNameSupport.reflectionNameToJNIName(name.toString()))) { - clazz = NEGATIVE_CLASS_LOOKUP; - } - if (clazz == null && ClassNameSupport.isValidTypeName(name.toString()) && dictionary.classesByName.containsKey(ClassNameSupport.typeNameToJNIName(name.toString()))) { - clazz = NEGATIVE_CLASS_LOOKUP; - } + clazz = NEGATIVE_CLASS_LOOKUP; } - clazz = checkClass(clazz, name); + clazz = checkClass(clazz, name.toString()); if (clazz != null) { return clazz.getClassObject(); } @@ -196,9 +191,9 @@ public static Class getClassObjectByName(CharSequence name) { return null; } - private static JNIAccessibleClass checkClass(JNIAccessibleClass clazz, CharSequence name) { + private static JNIAccessibleClass checkClass(JNIAccessibleClass clazz, String name) { if (throwMissingRegistrationErrors() && clazz == null) { - MissingJNIRegistrationUtils.forClass(name.toString()); + MissingJNIRegistrationUtils.forClass(name); } else if (clazz != null && clazz.isNegative()) { return null; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java index 0d4be8aa5399..e63b7b408d10 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java @@ -257,10 +257,11 @@ private void registerFields(boolean finalIsWritable, Field[] fields) { } @Override - public void registerClassLookup(ConfigurationCondition condition, String jniName) { + public void registerClassLookup(ConfigurationCondition condition, String reflectionName) { try { - register(condition, false, Class.forName(ClassNameSupport.jniNameToReflectionName(jniName))); + register(condition, false, Class.forName(reflectionName)); } catch (ClassNotFoundException e) { + String jniName = ClassNameSupport.reflectionNameToJNIName(reflectionName); newNegativeClassLookups.add(jniName); } }