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..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,14 +30,18 @@ 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; 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; +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; @@ -45,6 +49,7 @@ import com.oracle.svm.util.StringUtil; import jdk.graal.compiler.options.Option; +import jdk.graal.compiler.options.OptionKey; import jdk.graal.compiler.options.OptionType; /** @@ -81,12 +86,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 + ")"; @@ -107,7 +114,15 @@ 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); + if (computeFutureDefaults(newValue.getValuesWithOrigins()).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."); @@ -124,7 +139,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); + } } } @@ -133,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)) { @@ -169,30 +203,22 @@ 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, true); - } - - for (String retiredFutureDefault : RETIRED_FUTURE_DEFAULTS) { - setSystemProperty(retiredFutureDefault, true); - } + return result; } - 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 ab692a6721fc..edc0a1b7cf86 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 @@ -482,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/RuntimeClassLoading.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/RuntimeClassLoading.java index 9ec0f1d42b7b..0ca5961cade7 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); } 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 60fc7dd9aa39..d5e7b0f7dd3b 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; @@ -271,10 +274,12 @@ private static Class getArrayClass(String name, Class elementalResult, int if (RuntimeClassLoading.isSupported()) { RuntimeClassLoading.getOrCreateArrayHub(hub); } else { - if (throwMissingRegistrationErrors()) { - MissingReflectionRegistrationUtils.reportClassAccess(name); + if (arrayDimensions <= 255) { + /* throw only for valid arrays */ + throw MissingReflectionRegistrationUtils.reportClassAccess(name); + } else { + return null; } - return null; } } remainingDims--; 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 1d7ee8d26a1c..14214ae2c638 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) {