From 6677b4cd90b3d916529c43e19f69452b18e9bb5c Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Sat, 29 Nov 2025 07:41:57 +0100 Subject: [PATCH 1/5] Future default class-for-name-respects-class-loader --- .../svm/core/FutureDefaultsOptions.java | 92 +++++++++++-------- .../svm/core/doc-files/FutureDefaultsHelp.txt | 6 +- .../svm/core/hub/ClassForNameSupport.java | 1 + 3 files changed, 59 insertions(+), 40 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java index d1aa2ba86b42..7d0f20106baa 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java @@ -24,17 +24,7 @@ */ package com.oracle.svm.core; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import org.graalvm.nativeimage.ImageInfo; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; - +import com.oracle.svm.core.hub.ClassForNameSupport; import com.oracle.svm.core.option.APIOption; import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; import com.oracle.svm.core.option.HostedOptionKey; @@ -43,9 +33,20 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.LogUtils; import com.oracle.svm.util.StringUtil; - import jdk.graal.compiler.options.Option; +import jdk.graal.compiler.options.OptionKey; import jdk.graal.compiler.options.OptionType; +import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.ImageInfo; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; /** * Enables the --future-defaults=value flag that is used for evolution of Native Image semantics. @@ -81,13 +82,14 @@ public class FutureDefaultsOptions { private static final String RUN_TIME_INITIALIZE_SECURITY_PROVIDERS = "run-time-initialize-security-providers"; private static final String RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS = "run-time-initialize-file-system-providers"; private static final String RUN_TIME_INITIALIZE_RESOURCE_BUNDLES = "run-time-initialize-resource-bundles"; - private static final List ALL_FUTURE_DEFAULTS = List.of(RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS, RUN_TIME_INITIALIZE_SECURITY_PROVIDERS, RUN_TIME_INITIALIZE_RESOURCE_BUNDLES); + private static final String CLASS_FOR_NAME_RESPECTS_CLASS_LOADER = "class-for-name-respects-class-loader"; + private static final List ALL_FUTURE_DEFAULTS = List.of(CLASS_FOR_NAME_RESPECTS_CLASS_LOADER, RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS, RUN_TIME_INITIALIZE_SECURITY_PROVIDERS, RUN_TIME_INITIALIZE_RESOURCE_BUNDLES); private static final String COMPLETE_REFLECTION_TYPES = "complete-reflection-types"; private static final List RETIRED_FUTURE_DEFAULTS = List.of(COMPLETE_REFLECTION_TYPES); - public static final String RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS_REASON = "Initialize JDK classes at run time (--" + OPTION_NAME + " includes " + RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS + - ")"; + public static final String RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS_REASON = "Initialize JDK classes at run time (--" + OPTION_NAME + " includes " + CLASS_FOR_NAME_RESPECTS_CLASS_LOADER + + ")"; public static final String RUN_TIME_INITIALIZE_SECURITY_PROVIDERS_REASON = "Initialize JDK classes at run time (--" + OPTION_NAME + " includes " + RUN_TIME_INITIALIZE_SECURITY_PROVIDERS + ")"; public static final String RUN_TIME_INITIALIZE_RESOURCE_BUNDLES_REASON = "Initialize JDK classes at run time (--" + OPTION_NAME + " includes " + RUN_TIME_INITIALIZE_RESOURCE_BUNDLES + ")"; private static final String DEFAULT_NAME = ""; @@ -107,12 +109,21 @@ private static LinkedHashSet getAllValues() { @APIOption(name = OPTION_NAME, defaultValue = DEFAULT_NAME) // @Option(help = "file:doc-files/FutureDefaultsHelp.txt", type = OptionType.User) // static final HostedOptionKey FutureDefaults = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()) { + @Override + protected void onValueUpdate(EconomicMap, Object> values, AccumulatingLocatableMultiOptionValue.Strings oldValue, AccumulatingLocatableMultiOptionValue.Strings newValue) { + super.onValueUpdate(values, oldValue, newValue); + /* temporary simple pwdctest, will do full parsing */ + if (newValue.values().contains("all") || newValue.values().contains(CLASS_FOR_NAME_RESPECTS_CLASS_LOADER)) { + ClassForNameSupport.Options.ClassForNameRespectsClassLoader.update(values, true); + } + } + }; private static String getOptionHelpText() { Objects.requireNonNull(FutureDefaultsOptions.FutureDefaults.getDescriptor(), "This must be called after the options are processed."); return FutureDefaultsOptions.FutureDefaults.getDescriptor().getHelp().stream() - .collect(Collectors.joining(System.lineSeparator())); + .collect(Collectors.joining(System.lineSeparator())); } private static void verifyOptionDescription() { @@ -124,7 +135,14 @@ private static void verifyOptionDescription() { } } if (!optionHelpText.contains(futureDefaultsAllValues())) { - throw VMError.shouldNotReachHere("Must mention all options in a comma-separated sequence: " + futureDefaultsAllValues()); + throw VMError.shouldNotReachHere("Must mention all options in a comma-separated in the exact order: " + futureDefaultsAllValues()); + } + + /* Ensure retired future-defaults are not mentioned in user-facing help text */ + for (String retired : RETIRED_FUTURE_DEFAULTS) { + if (optionHelpText.contains("'" + retired + "'")) { + throw VMError.shouldNotReachHere("Must not mention retired options in the help text. Retired option: " + retired); + } } } @@ -139,35 +157,35 @@ public static void parseAndVerifyOptions() { String value = valueWithOrigin.value(); if (DEFAULT_NAME.equals(value)) { throw UserError.abort("The '%s' from %s is forbidden. It can only contain: %s.%n%nUsage:%n%n%s", - SubstrateOptionsParser.commandArgument(FutureDefaults, DEFAULT_NAME), - valueWithOrigin.origin(), - futureDefaultsAllValues(), - getOptionHelpText()); + SubstrateOptionsParser.commandArgument(FutureDefaults, DEFAULT_NAME), + valueWithOrigin.origin(), + futureDefaultsAllValues(), + getOptionHelpText()); } if (RETIRED_FUTURE_DEFAULTS.contains(value)) { LogUtils.warning("The '%s' option from %s contains the value '%s' which is enabled by default in this GraalVM release (%s) and can be removed.", - SubstrateOptionsParser.commandArgument(FutureDefaults, value), - valueWithOrigin.origin(), - value, - VM.getVersion()); + SubstrateOptionsParser.commandArgument(FutureDefaults, value), + valueWithOrigin.origin(), + value, + VM.getVersion()); return; } if (!getAllValues().contains(value)) { throw UserError.abort("The '%s' option from %s contains invalid value '%s'. It can only contain: %s.%n%nUsage:%n%n%s", - SubstrateOptionsParser.commandArgument(FutureDefaults, value), - valueWithOrigin.origin(), - value, - futureDefaultsAllValues(), - getOptionHelpText()); + SubstrateOptionsParser.commandArgument(FutureDefaults, value), + valueWithOrigin.origin(), + value, + futureDefaultsAllValues(), + getOptionHelpText()); } if (value.equals(NONE_NAME)) { if (!valueWithOrigin.origin().commandLineLike()) { throw UserError.abort("The '%s' option can only be used from the command line. Detected usage from %s.", - SubstrateOptionsParser.commandArgument(FutureDefaults, NONE_NAME), - valueWithOrigin.origin()); + SubstrateOptionsParser.commandArgument(FutureDefaults, NONE_NAME), + valueWithOrigin.origin()); } futureDefaults.clear(); } @@ -183,16 +201,16 @@ public static void parseAndVerifyOptions() { /* Set build-time properties for user features */ for (String futureDefault : getFutureDefaults()) { - setSystemProperty(futureDefault, true); + setSystemProperty(futureDefault); } for (String retiredFutureDefault : RETIRED_FUTURE_DEFAULTS) { - setSystemProperty(retiredFutureDefault, true); + setSystemProperty(retiredFutureDefault); } } - private static void setSystemProperty(String futureDefault, boolean value) { - System.setProperty(FutureDefaultsOptions.SYSTEM_PROPERTY_PREFIX + futureDefault, Boolean.toString(value)); + private static void setSystemProperty(String futureDefault) { + System.setProperty(FutureDefaultsOptions.SYSTEM_PROPERTY_PREFIX + futureDefault, Boolean.toString(true)); } public static Set getFutureDefaults() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/FutureDefaultsHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/FutureDefaultsHelp.txt index e47a1018e1f8..a8e0763cbc49 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/FutureDefaultsHelp.txt +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/doc-files/FutureDefaultsHelp.txt @@ -1,4 +1,4 @@ -Enable options that are planned to become defaults in future releases. Comma-separated list can contain 'all', 'none', 'run-time-initialize-jdk', 'run-time-initialize-file-system-providers', 'run-time-initialize-security-providers', 'run-time-initialize-resource-bundles', 'complete-reflection-types'. The preferred usage is '--future-defaults=all'. +Enable options that are planned to become defaults in future releases. Comma-separated list can contain 'all', 'none', 'run-time-initialize-jdk', 'class-for-name-respects-class-loader', 'run-time-initialize-file-system-providers', 'run-time-initialize-security-providers', 'run-time-initialize-resource-bundles'. The preferred usage is '--future-defaults=all'. The meaning of each possible option is as follows: 'all' - is the preferred option, and it enables all other behaviors. @@ -7,10 +7,10 @@ The meaning of each possible option is as follows: 'run-time-initialize-jdk' - enables all behaviors related to run-time initialization of the JDK: ['run-time-initialize-security-providers', 'run-time-initialize-file-system-providers', 'run-time-initialize-resource-bundles']. - 'complete-reflection-types' - reflective registration of a type, via metadata files or the Feature API, always includes all type metadata. Now, all registered types behave the same as types defined in 'reachability-metadata.json'. + 'class-for-name-respects-class-loader' - `Class.forName` and similar respect their class loader argument. 'run-time-initialize-security-providers' - shifts away from build-time initialization for 'java.security.Provider'. Unless you store 'java.security.Provider'-related classes in the image heap, this option should not affect you. In case this option breaks your build, follow the suggestions in the error messages. 'run-time-initialize-file-system-providers' - shifts away from build-time initialization for 'java.nio.file.spi.FileSystemProvider'. Unless you store 'FileSystemProvider'-related classes in the image heap, this option should not affect you. In case this option breaks your build, follow the suggestions in the error messages. - 'run-time-initialize-resource-bundles' - shifts away from build-time initialization for 'java.util.ResourceBundle'. Unless you store 'ResourceBundle'-related classes in the image heap, this option should not affect you. In case this option breaks your build, follow the suggestions in the error messages. \ No newline at end of file + 'run-time-initialize-resource-bundles' - shifts away from build-time initialization for 'java.util.ResourceBundle'. Unless you store 'ResourceBundle'-related classes in the image heap, this option should not affect you. In case this option breaks your build, follow the suggestions in the error messages. 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 025b9c3f7c96..22b6e4b013c3 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 @@ -38,6 +38,7 @@ import java.util.function.BooleanSupplier; import java.util.function.Consumer; +import com.oracle.svm.core.FutureDefaultsOptions; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.EconomicSet; import org.graalvm.nativeimage.Platform; From e5104935836cefbc80b90f7cb43bc3060849988b Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 4 Dec 2025 17:46:57 +0100 Subject: [PATCH 2/5] Fixes for the new future default --- .../svm/core/FutureDefaultsOptions.java | 63 ++++++++++--------- .../svm/core/hub/ClassForNameSupport.java | 4 +- .../core/hub/registry/ClassRegistries.java | 3 + 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java index 7d0f20106baa..c118f588444c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java @@ -24,6 +24,18 @@ */ package com.oracle.svm.core; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.ImageInfo; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + import com.oracle.svm.core.hub.ClassForNameSupport; import com.oracle.svm.core.option.APIOption; import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; @@ -33,20 +45,10 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.LogUtils; import com.oracle.svm.util.StringUtil; + import jdk.graal.compiler.options.Option; import jdk.graal.compiler.options.OptionKey; import jdk.graal.compiler.options.OptionType; -import org.graalvm.collections.EconomicMap; -import org.graalvm.nativeimage.ImageInfo; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; - -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; /** * Enables the --future-defaults=value flag that is used for evolution of Native Image semantics. @@ -83,13 +85,14 @@ public class FutureDefaultsOptions { private static final String RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS = "run-time-initialize-file-system-providers"; private static final String RUN_TIME_INITIALIZE_RESOURCE_BUNDLES = "run-time-initialize-resource-bundles"; private static final String CLASS_FOR_NAME_RESPECTS_CLASS_LOADER = "class-for-name-respects-class-loader"; - private static final List ALL_FUTURE_DEFAULTS = List.of(CLASS_FOR_NAME_RESPECTS_CLASS_LOADER, RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS, RUN_TIME_INITIALIZE_SECURITY_PROVIDERS, RUN_TIME_INITIALIZE_RESOURCE_BUNDLES); + private static final List ALL_FUTURE_DEFAULTS = List.of(CLASS_FOR_NAME_RESPECTS_CLASS_LOADER, RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS, RUN_TIME_INITIALIZE_SECURITY_PROVIDERS, + RUN_TIME_INITIALIZE_RESOURCE_BUNDLES); private static final String COMPLETE_REFLECTION_TYPES = "complete-reflection-types"; private static final List RETIRED_FUTURE_DEFAULTS = List.of(COMPLETE_REFLECTION_TYPES); public static final String RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS_REASON = "Initialize JDK classes at run time (--" + OPTION_NAME + " includes " + CLASS_FOR_NAME_RESPECTS_CLASS_LOADER + - ")"; + ")"; public static final String RUN_TIME_INITIALIZE_SECURITY_PROVIDERS_REASON = "Initialize JDK classes at run time (--" + OPTION_NAME + " includes " + RUN_TIME_INITIALIZE_SECURITY_PROVIDERS + ")"; public static final String RUN_TIME_INITIALIZE_RESOURCE_BUNDLES_REASON = "Initialize JDK classes at run time (--" + OPTION_NAME + " includes " + RUN_TIME_INITIALIZE_RESOURCE_BUNDLES + ")"; private static final String DEFAULT_NAME = ""; @@ -109,7 +112,7 @@ private static LinkedHashSet getAllValues() { @APIOption(name = OPTION_NAME, defaultValue = DEFAULT_NAME) // @Option(help = "file:doc-files/FutureDefaultsHelp.txt", type = OptionType.User) // static final HostedOptionKey FutureDefaults = new HostedOptionKey<>( - AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()) { + AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()) { @Override protected void onValueUpdate(EconomicMap, Object> values, AccumulatingLocatableMultiOptionValue.Strings oldValue, AccumulatingLocatableMultiOptionValue.Strings newValue) { super.onValueUpdate(values, oldValue, newValue); @@ -123,7 +126,7 @@ protected void onValueUpdate(EconomicMap, Object> values, Accumulat private static String getOptionHelpText() { Objects.requireNonNull(FutureDefaultsOptions.FutureDefaults.getDescriptor(), "This must be called after the options are processed."); return FutureDefaultsOptions.FutureDefaults.getDescriptor().getHelp().stream() - .collect(Collectors.joining(System.lineSeparator())); + .collect(Collectors.joining(System.lineSeparator())); } private static void verifyOptionDescription() { @@ -157,35 +160,35 @@ public static void parseAndVerifyOptions() { String value = valueWithOrigin.value(); if (DEFAULT_NAME.equals(value)) { throw UserError.abort("The '%s' from %s is forbidden. It can only contain: %s.%n%nUsage:%n%n%s", - SubstrateOptionsParser.commandArgument(FutureDefaults, DEFAULT_NAME), - valueWithOrigin.origin(), - futureDefaultsAllValues(), - getOptionHelpText()); + SubstrateOptionsParser.commandArgument(FutureDefaults, DEFAULT_NAME), + valueWithOrigin.origin(), + futureDefaultsAllValues(), + getOptionHelpText()); } if (RETIRED_FUTURE_DEFAULTS.contains(value)) { LogUtils.warning("The '%s' option from %s contains the value '%s' which is enabled by default in this GraalVM release (%s) and can be removed.", - SubstrateOptionsParser.commandArgument(FutureDefaults, value), - valueWithOrigin.origin(), - value, - VM.getVersion()); + SubstrateOptionsParser.commandArgument(FutureDefaults, value), + valueWithOrigin.origin(), + value, + VM.getVersion()); return; } if (!getAllValues().contains(value)) { throw UserError.abort("The '%s' option from %s contains invalid value '%s'. It can only contain: %s.%n%nUsage:%n%n%s", - SubstrateOptionsParser.commandArgument(FutureDefaults, value), - valueWithOrigin.origin(), - value, - futureDefaultsAllValues(), - getOptionHelpText()); + SubstrateOptionsParser.commandArgument(FutureDefaults, value), + valueWithOrigin.origin(), + value, + futureDefaultsAllValues(), + getOptionHelpText()); } if (value.equals(NONE_NAME)) { if (!valueWithOrigin.origin().commandLineLike()) { throw UserError.abort("The '%s' option can only be used from the command line. Detected usage from %s.", - SubstrateOptionsParser.commandArgument(FutureDefaults, NONE_NAME), - valueWithOrigin.origin()); + SubstrateOptionsParser.commandArgument(FutureDefaults, NONE_NAME), + valueWithOrigin.origin()); } futureDefaults.clear(); } 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 22b6e4b013c3..12c62d377702 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 @@ -38,7 +38,6 @@ import java.util.function.BooleanSupplier; import java.util.function.Consumer; -import com.oracle.svm.core.FutureDefaultsOptions; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.EconomicSet; import org.graalvm.nativeimage.Platform; @@ -483,6 +482,9 @@ public static boolean isUnsafeAllocatedPreserved(Class jClass) { } public static boolean isRegisteredClass(String className) { + if (!ClassNameSupport.isValidReflectionName(className)) { + return true; + } if (respectClassLoader()) { RuntimeDynamicAccessMetadata dynamicAccessMetadata = getDynamicAccessMetadataForName(className); if (dynamicAccessMetadata == null) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/ClassRegistries.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/ClassRegistries.java index 9a787d935bbe..beba554b0f1d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/ClassRegistries.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/ClassRegistries.java @@ -197,6 +197,9 @@ private Class resolve(String name, ClassLoader loader) throws ClassNotFoundEx arrayDimensions++; } if (arrayDimensions == name.length()) { + if (loader == null) { + return null; + } throw new ClassNotFoundException(name); } Class elementalResult; From e16e487bee1f77e5705a342abd6c402318b5fb99 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Tue, 9 Dec 2025 15:45:43 +0100 Subject: [PATCH 3/5] Test with array definition --- .../svm/core/reflect/MissingReflectionRegistrationUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java index f8a55e9afb68..e04f3c94eff5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java @@ -44,12 +44,13 @@ public final class MissingReflectionRegistrationUtils extends MissingRegistrationUtils { - public static void reportClassAccess(String className) { + public static MissingReflectionRegistrationError reportClassAccess(String className) { String json = elementToJSON(namedConfigurationType(className)); MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError( reflectionError("access the class", quote(className), json), Class.class, null, className, null); report(exception); + return exception; } public static void reportUnsafeAllocation(Class clazz) { From f7d3b527aeebc6f00960f6d3bf7b4a26eaddf90f Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Wed, 10 Dec 2025 08:39:38 +0100 Subject: [PATCH 4/5] Make the class definition error more clear --- .../src/com/oracle/svm/core/hub/RuntimeClassLoading.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeClassLoading.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeClassLoading.java index ea5b3374966b..529c3c6a377e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeClassLoading.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeClassLoading.java @@ -165,7 +165,8 @@ public static Class defineClass(ClassLoader loader, String expectedName, byte public static RuntimeException throwNoBytecodeClasses(String className) { assert !PredefinedClassesSupport.hasBytecodeClasses() && !RuntimeClassLoading.isSupported(); throw VMError.unsupportedFeature( - "Classes cannot be defined at runtime by default when using ahead-of-time Native Image compilation. Tried to define class '" + className + "'" + System.lineSeparator() + + "Classes cannot be defined at runtime by default when using ahead-of-time Native Image compilation. Tried to define class:" + System.lineSeparator() + System.lineSeparator() + + " " + className + System.lineSeparator() + System.lineSeparator() + DEFINITION_NOT_SUPPORTED_MESSAGE); } From afadf192f906ead5ebab1946d42f8a78af2ca604 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 11 Dec 2025 14:53:04 +0100 Subject: [PATCH 5/5] Future defaults done right --- .../svm/core/FutureDefaultsOptions.java | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java index c118f588444c..b5062422abe5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java @@ -30,6 +30,7 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.ImageInfo; @@ -40,6 +41,7 @@ import com.oracle.svm.core.option.APIOption; import com.oracle.svm.core.option.AccumulatingLocatableMultiOptionValue; import com.oracle.svm.core.option.HostedOptionKey; +import com.oracle.svm.core.option.LocatableMultiOptionValue; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; @@ -116,8 +118,7 @@ private static LinkedHashSet getAllValues() { @Override protected void onValueUpdate(EconomicMap, Object> values, AccumulatingLocatableMultiOptionValue.Strings oldValue, AccumulatingLocatableMultiOptionValue.Strings newValue) { super.onValueUpdate(values, oldValue, newValue); - /* temporary simple pwdctest, will do full parsing */ - if (newValue.values().contains("all") || newValue.values().contains(CLASS_FOR_NAME_RESPECTS_CLASS_LOADER)) { + if (computeFutureDefaults(newValue.getValuesWithOrigins()).contains(CLASS_FOR_NAME_RESPECTS_CLASS_LOADER)) { ClassForNameSupport.Options.ClassForNameRespectsClassLoader.update(values, true); } } @@ -154,8 +155,20 @@ private static void verifyOptionDescription() { @Platforms(Platform.HOSTED_ONLY.class) public static void parseAndVerifyOptions() { verifyOptionDescription(); - futureDefaults = new LinkedHashSet<>(getAllValues().size()); var valuesWithOrigin = FutureDefaults.getValue().getValuesWithOrigins(); + futureDefaults = computeFutureDefaults(valuesWithOrigin); + /* Set build-time properties for user features */ + for (String futureDefault : getFutureDefaults()) { + setSystemProperty(futureDefault); + } + + for (String retiredFutureDefault : RETIRED_FUTURE_DEFAULTS) { + setSystemProperty(retiredFutureDefault); + } + } + + private static LinkedHashSet computeFutureDefaults(Stream> valuesWithOrigin) { + LinkedHashSet result = new LinkedHashSet<>(); valuesWithOrigin.forEach(valueWithOrigin -> { String value = valueWithOrigin.value(); if (DEFAULT_NAME.equals(value)) { @@ -190,26 +203,18 @@ public static void parseAndVerifyOptions() { SubstrateOptionsParser.commandArgument(FutureDefaults, NONE_NAME), valueWithOrigin.origin()); } - futureDefaults.clear(); + result.clear(); } if (value.equals(ALL_NAME)) { - futureDefaults.addAll(ALL_FUTURE_DEFAULTS); + result.addAll(ALL_FUTURE_DEFAULTS); } else if (value.equals(RUN_TIME_INITIALIZE_JDK)) { - futureDefaults.addAll(List.of(RUN_TIME_INITIALIZE_SECURITY_PROVIDERS, RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS, RUN_TIME_INITIALIZE_RESOURCE_BUNDLES)); + result.addAll(List.of(RUN_TIME_INITIALIZE_SECURITY_PROVIDERS, RUN_TIME_INITIALIZE_FILE_SYSTEM_PROVIDERS, RUN_TIME_INITIALIZE_RESOURCE_BUNDLES)); } else { - futureDefaults.add(value); + result.add(value); } }); - - /* Set build-time properties for user features */ - for (String futureDefault : getFutureDefaults()) { - setSystemProperty(futureDefault); - } - - for (String retiredFutureDefault : RETIRED_FUTURE_DEFAULTS) { - setSystemProperty(retiredFutureDefault); - } + return result; } private static void setSystemProperty(String futureDefault) {