From e8eed176d1086377ea1d1535f4c300fee5522f8d Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Mon, 28 Apr 2025 15:00:51 +0200 Subject: [PATCH 1/2] Make missing registration errors subclass LinkageError --- .../org.graalvm.nativeimage/snapshot.sigtest | 11 +++- .../MissingJNIRegistrationError.java | 4 +- .../MissingReflectionRegistrationError.java | 4 +- substratevm/CHANGELOG.md | 1 + .../svm/core/MissingRegistrationUtils.java | 23 +++++++- .../Target_java_lang_invoke_MemberName.java | 57 ++++++++++++++++++- .../Target_java_util_ResourceBundle.java | 16 +++++- ..._java_lang_invoke_MethodHandleNatives.java | 4 +- ...MissingSerializationRegistrationError.java | 2 +- .../AnnotationSubstitutionProcessor.java | 4 +- 10 files changed, 111 insertions(+), 15 deletions(-) diff --git a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest index 5c15c4d96208..24cb6cb5bc57 100644 --- a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest +++ b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest @@ -39,6 +39,13 @@ cons public init(java.lang.Throwable) supr java.lang.Throwable hfds serialVersionUID +CLSS public java.lang.LinkageError +cons public init() +cons public init(java.lang.String) +cons public init(java.lang.String,java.lang.Throwable) +supr java.lang.Error +hfds serialVersionUID + CLSS public java.lang.Exception cons protected init(java.lang.String,java.lang.Throwable,boolean,boolean) cons public init() @@ -230,7 +237,7 @@ meth public java.lang.Class getDeclaringClass() meth public java.lang.Class getElementType() meth public java.lang.String getElementName() meth public java.lang.String getSignature() -supr java.lang.Error +supr java.lang.LinkageError hfds declaringClass,elementName,elementType,serialVersionUID,signature CLSS public final org.graalvm.nativeimage.MissingReflectionRegistrationError @@ -239,7 +246,7 @@ meth public java.lang.Class getDeclaringClass() meth public java.lang.Class getElementType() meth public java.lang.Class[] getParameterTypes() meth public java.lang.String getElementName() -supr java.lang.Error +supr java.lang.LinkageError hfds declaringClass,elementName,elementType,parameterTypes,serialVersionUID CLSS public abstract interface org.graalvm.nativeimage.ObjectHandle diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/MissingJNIRegistrationError.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/MissingJNIRegistrationError.java index 0504f27ddc75..cfb15b5d082c 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/MissingJNIRegistrationError.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/MissingJNIRegistrationError.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -74,7 +74,7 @@ * * @since 24.1 */ -public final class MissingJNIRegistrationError extends Error { +public final class MissingJNIRegistrationError extends LinkageError { @Serial private static final long serialVersionUID = -8940056537864516986L; private final Class elementType; diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/MissingReflectionRegistrationError.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/MissingReflectionRegistrationError.java index 9eb745bf8af9..a2a4ee58ed67 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/MissingReflectionRegistrationError.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/MissingReflectionRegistrationError.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -91,7 +91,7 @@ * * @since 23.0 */ -public final class MissingReflectionRegistrationError extends Error { +public final class MissingReflectionRegistrationError extends LinkageError { @Serial private static final long serialVersionUID = 2764341882856270640L; private final Class elementType; diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index a25d8d923e54..ee75e9680822 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -24,6 +24,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-59869) Implemented initial optimization of Java Vector API (JEP 338) operations in native images. See the compiler changelog for more details. * (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 * (GR-47881) Remove the total number of loaded types, fields, and methods from the build output, deprecated these metrics in the build output schema, and removed already deprecated build output metrics. +* (GR-64619) Missing registration errors are now subclasses of `LinkageError` ## 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.core/src/com/oracle/svm/core/MissingRegistrationUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/MissingRegistrationUtils.java index 510cca5f604d..a21482449e04 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/MissingRegistrationUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/MissingRegistrationUtils.java @@ -28,8 +28,10 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import com.oracle.svm.core.util.ExitStatus; +import com.oracle.svm.core.util.VMError; public final class MissingRegistrationUtils { @@ -46,7 +48,7 @@ public static SubstrateOptions.ReportingMode missingRegistrationReportingMode() private static final AtomicReference> seenOutputs = new AtomicReference<>(null); public static void report(Error exception, StackTraceElement responsibleClass) { - if (responsibleClass != null && !MissingRegistrationSupport.singleton().reportMissingRegistrationErrors(responsibleClass)) { + if (missingRegistrationErrorsSuspended.get() || (responsibleClass != null && !MissingRegistrationSupport.singleton().reportMissingRegistrationErrors(responsibleClass))) { return; } switch (missingRegistrationReportingMode()) { @@ -104,6 +106,25 @@ public static void report(Error exception, StackTraceElement responsibleClass) { } } + private static final ThreadLocal missingRegistrationErrorsSuspended = ThreadLocal.withInitial(() -> false); + + /** + * Code executing inside this function will temporarily revert to throwing JDK exceptions like + * ({@code ClassNotFoundException} when encountering a situation that would normally cause a + * missing registration error. This is currently required during resource bundle lookups, where + * encountering an unregistered class can mean that the corresponding locale isn't included in + * the image, and is not a reason to abort the lookup completely. + */ + public static T runIgnoringMissingRegistrations(Supplier callback) { + VMError.guarantee(!missingRegistrationErrorsSuspended.get()); + try { + missingRegistrationErrorsSuspended.set(true); + return callback.get(); + } finally { + missingRegistrationErrorsSuspended.set(false); + } + } + private static void printLine(StringBuilder sb, Object object) { sb.append(" ").append(object).append(System.lineSeparator()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/Target_java_lang_invoke_MemberName.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/Target_java_lang_invoke_MemberName.java index 752aee45839f..ad1288c1185d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/Target_java_lang_invoke_MemberName.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/invoke/Target_java_lang_invoke_MemberName.java @@ -27,11 +27,16 @@ import java.lang.invoke.MethodType; import java.lang.reflect.Member; +import org.graalvm.nativeimage.MissingReflectionRegistrationError; + import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Inject; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.methodhandles.Target_java_lang_invoke_MethodHandleNatives; +import com.oracle.svm.core.util.BasedOnJDKFile; +import com.oracle.svm.core.util.VMError; @TargetClass(className = "java.lang.invoke.MemberName") public final class Target_java_lang_invoke_MemberName { @@ -81,7 +86,57 @@ public final class Target_java_lang_invoke_MemberName { @SuppressWarnings("static-method") @Substitute - private boolean vminfoIsConsistent() { + boolean vminfoIsConsistent() { return true; /* The substitution class doesn't use the same internals as the JDK */ } + + @Alias + @Override + protected native Target_java_lang_invoke_MemberName clone(); + + @Alias + native void ensureTypeVisible(Class refc); + + @Alias + public native boolean isResolved(); + + @Alias + native boolean referenceKindIsConsistent(); + + @Alias + native void initResolved(boolean isResolved); +} + +@TargetClass(className = "java.lang.invoke.MemberName", innerClass = "Factory") +final class Target_java_lang_invoke_MemberName_Factory { + @Substitute + @SuppressWarnings("static-method") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/b685ea54081fcf54a6567dddb49b63435a6e1ea4/src/java.base/share/classes/java/lang/invoke/MemberName.java#L937-L973") + private Target_java_lang_invoke_MemberName resolve(byte refKind, Target_java_lang_invoke_MemberName ref, Class lookupClass, int allowedModes, + boolean speculativeResolve) { + Target_java_lang_invoke_MemberName m = ref.clone(); + assert (refKind == m.getReferenceKind()); + try { + m = Target_java_lang_invoke_MethodHandleNatives.resolve(m, lookupClass, allowedModes, speculativeResolve); + if (m == null) { + VMError.guarantee(speculativeResolve, "non-speculative resolution should return member name or throw"); + return null; + } + m.ensureTypeVisible(m.getDeclaringClass()); + m.resolution = null; + } catch (ClassNotFoundException | LinkageError ex) { + if (ex instanceof MissingReflectionRegistrationError e) { + /* Bypass the LinkageError catch below */ + throw e; + } + VMError.guarantee(m != null, "speculative resolution should not throw"); + assert (!m.isResolved()); + m.resolution = ex; + return m; + } + assert (m.referenceKindIsConsistent()); + m.initResolved(true); + assert (m.vminfoIsConsistent()); + return m; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_java_util_ResourceBundle.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_java_util_ResourceBundle.java index 9bd8f8870a1f..401790ecab1d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_java_util_ResourceBundle.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_java_util_ResourceBundle.java @@ -28,9 +28,11 @@ import java.util.ResourceBundle; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.function.Supplier; import org.graalvm.nativeimage.ImageSingletons; +import com.oracle.svm.core.MissingRegistrationUtils; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.Substitute; @@ -115,7 +117,12 @@ private static ResourceBundle getBundleImpl(String baseName, if (!ImageSingletons.lookup(LocalizationSupport.class).isRegisteredBundleLookup(baseName, locale, control)) { MissingResourceRegistrationUtils.missingResourceBundle(baseName); } - return getBundleImpl(callerModule, callerModule, baseName, locale, control); + return MissingRegistrationUtils.runIgnoringMissingRegistrations(new Supplier() { + @Override + public ResourceBundle get() { + return getBundleImpl(callerModule, callerModule, baseName, locale, control); + } + }); } // find resource bundles from unnamed module of given class loader @@ -129,7 +136,12 @@ private static ResourceBundle getBundleImpl(String baseName, if (!ImageSingletons.lookup(LocalizationSupport.class).isRegisteredBundleLookup(baseName, locale, control)) { MissingResourceRegistrationUtils.missingResourceBundle(baseName); } - return getBundleImpl(callerModule, unnamedModule, baseName, locale, control); + return MissingRegistrationUtils.runIgnoringMissingRegistrations(new Supplier() { + @Override + public ResourceBundle get() { + return getBundleImpl(callerModule, unnamedModule, baseName, locale, control); + } + }); } @Alias diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives.java index 773f3acd1218..5142f84a9951 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives.java @@ -65,7 +65,7 @@ */ @SuppressWarnings("unused") @TargetClass(className = "java.lang.invoke.MethodHandleNatives") -final class Target_java_lang_invoke_MethodHandleNatives { +public final class Target_java_lang_invoke_MethodHandleNatives { /* * MemberName native constructor. We need to resolve the actual type and flags of the member and @@ -209,7 +209,7 @@ private static Object getMemberVMInfo(Target_java_lang_invoke_MemberName self) { static native String refKindName(byte refKind); @Substitute - static Target_java_lang_invoke_MemberName resolve(Target_java_lang_invoke_MemberName self, Class caller, int lookupMode, boolean speculativeResolve) + public static Target_java_lang_invoke_MemberName resolve(Target_java_lang_invoke_MemberName self, Class caller, int lookupMode, boolean speculativeResolve) throws LinkageError, ClassNotFoundException { Class declaringClass = self.getDeclaringClass(); Target_java_lang_invoke_MemberName resolved = Util_java_lang_invoke_MethodHandleNatives.resolve(self, caller, speculativeResolve); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/MissingSerializationRegistrationError.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/MissingSerializationRegistrationError.java index 0fa28f1231f5..d636779d3656 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/MissingSerializationRegistrationError.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/MissingSerializationRegistrationError.java @@ -34,7 +34,7 @@ * The purpose of this exception is to easily discover unregistered elements and to assure that all * serialization or deserialization operations have expected behavior. */ -public final class MissingSerializationRegistrationError extends Error { +public final class MissingSerializationRegistrationError extends LinkageError { @Serial private static final long serialVersionUID = 2764341882856270641L; private final Class culprit; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java index dd3aac70ba64..b2d9c4c2b546 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java @@ -396,7 +396,7 @@ private void handleAliasClass(Class annotatedClass, Class originalClass, T } private void handleMethodInAliasClass(Executable annotatedMethod, Class originalClass) { - if (skipExcludedPlatform(annotatedMethod)) { + if (skipExcludedPlatform(annotatedMethod) || annotatedMethod.isSynthetic()) { return; } @@ -482,7 +482,7 @@ private boolean skipExcludedPlatform(AnnotatedElement annotatedMethod) { } private void handleFieldInAliasClass(Field annotatedField, Class originalClass) { - if (skipExcludedPlatform(annotatedField)) { + if (skipExcludedPlatform(annotatedField) || annotatedField.isSynthetic()) { return; } From d227453b15bf689db2ae8e338cf7c6e50d81cd9a Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Wed, 7 May 2025 15:06:24 +0200 Subject: [PATCH 2/2] Include resource bundles in resource configuration --- .../graal/compiler/util/json/JsonPrinter.java | 15 +++- .../native-image/ReachabilityMetadata.md | 33 +++----- .../reachability-metadata-schema-v1.1.0.json | 80 +++++++++++++------ substratevm/CHANGELOG.md | 1 + .../svm/configure/ConfigurationParser.java | 1 + .../ResourceConfigurationParser.java | 11 +-- .../svm/configure/ResourceMetadataParser.java | 11 ++- .../configure/config/ConfigurationSet.java | 7 +- .../config/ResourceConfiguration.java | 41 +++++----- .../configure/trace/ReflectionProcessor.java | 3 +- 10 files changed, 118 insertions(+), 85 deletions(-) diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/json/JsonPrinter.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/json/JsonPrinter.java index 949554a9a9de..6e216e9b57bd 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/json/JsonPrinter.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/json/JsonPrinter.java @@ -55,7 +55,12 @@ public interface JsonPrinter { * @see JsonWriter#print(Object) */ static void printCollection(JsonWriter writer, Collection collection, Comparator comparator, JsonPrinter elementPrinter) throws IOException { - if (collection.isEmpty()) { + printCollection(writer, collection, comparator, elementPrinter, true, true); + } + + /* Utility method to allow printing multiple collections into the same array */ + static void printCollection(JsonWriter writer, Collection collection, Comparator comparator, JsonPrinter elementPrinter, boolean arrayStart, boolean arrayEnd) throws IOException { + if (collection.isEmpty() && arrayStart && arrayEnd) { writer.append("[]"); return; } @@ -66,7 +71,9 @@ static void printCollection(JsonWriter writer, Collection collection, Com ((List) ordered).sort(comparator); } - writer.appendArrayStart(); + if (arrayStart) { + writer.appendArrayStart(); + } boolean separator = false; for (T t : ordered) { if (separator) { @@ -75,6 +82,8 @@ static void printCollection(JsonWriter writer, Collection collection, Com elementPrinter.print(t, writer); separator = true; } - writer.appendArrayEnd(); + if (arrayEnd) { + writer.appendArrayEnd(); + } } } diff --git a/docs/reference-manual/native-image/ReachabilityMetadata.md b/docs/reference-manual/native-image/ReachabilityMetadata.md index d40cf3c65ac9..2710ba0316b5 100644 --- a/docs/reference-manual/native-image/ReachabilityMetadata.md +++ b/docs/reference-manual/native-image/ReachabilityMetadata.md @@ -130,8 +130,7 @@ The _reachability-metadata.json_ configuration contains a single object with one ```json { "reflection":[], - "resources":[], - "bundles":[] + "resources":[] } ``` @@ -562,13 +561,13 @@ For each registered resource you get: Java localization support (`java.util.ResourceBundle`) enables to load L10N resources and show messages localized for a specific _locale_. Native Image needs knowledge of the resource bundles that your application uses so that it can include appropriate resources and program elements to the application. -A simple bundle can be specified in the `bundles` section of _reachability-metadata.json_: +A simple bundle can be specified in the `resources` section of _reachability-metadata.json_: ```json { - "bundles": [ + "resources": [ { - "name":"your.pkg.Bundle" + "bundle": "your.pkg.Bundle" } ] } @@ -577,26 +576,15 @@ A simple bundle can be specified in the `bundles` section of _reachability-metad To request a bundle from a specific module: ```json { - "bundles": [ + "resources": [ { - "name":"app.module:module.pkg.Bundle" + "bundle": "app.module:module.pkg.Bundle" } ] } ``` -By default, resource bundles are included for all locales that are [included into the image](#locales). -Below is the example how to include only specific locales for a bundle: -```json -{ - "bundles": [ - { - "name": "specific.locales.Bundle", - "locales": ["en", "de", "sk"] - } - ] -} -``` +Resource bundles are included for all locales that are [included into the image](#locales). ### Locales @@ -737,12 +725,9 @@ See below is a sample reachability metadata configuration that you can use in _r { "module": "optional.module.of.a.resource", "glob": "path1/level*/**" - } - ], - "bundles": [ + }, { - "name": "fully.qualified.bundle.name", - "locales": ["en", "de", "other_optional_locales"] + "bundle": "fully.qualified.bundle.name" } ] } diff --git a/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json b/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json index 327b34bca255..5677283e3307 100644 --- a/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json +++ b/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json @@ -64,37 +64,69 @@ "type": "array", "default": [], "items": { - "title": "Resource that should be available", - "type": "object", - "properties": { - "reason": { - "title": "Reason for the resource's inclusion in the metadata", - "$ref": "#/$defs/reason" - }, - "condition": { - "title": "Condition under which the resource should be registered for runtime access", - "$ref": "#/$defs/condition" - }, - "module": { - "title": "Module containing the resource", - "type": "string", - "default": "" - }, - "glob": { - "title": "Resource name or pattern matching multiple resources (accepts * and ** wildcards)", - "type": "string" + "oneOf": [ + { + "title": "Resource that should be available", + "type": "object", + "properties": { + "reason": { + "title": "Reason for the resource's inclusion in the metadata", + "$ref": "#/$defs/reason" + }, + "condition": { + "title": "Condition under which the resource should be registered for runtime access", + "$ref": "#/$defs/condition" + }, + "module": { + "title": "Module containing the resource", + "type": "string", + "default": "" + }, + "glob": { + "title": "Resource name or pattern matching multiple resources (accepts * and ** wildcards)", + "type": "string" + } + }, + "required": [ + "glob" + ], + "additionalProperties": false + }, + { + "title": "Resource bundle that should be available", + "type": "object", + "properties": { + "reason": { + "title": "Reason for the resource bundle's inclusion in the metadata", + "$ref": "#/$defs/reason" + }, + "condition": { + "title": "Condition under which the resource bundle should be registered for runtime access", + "$ref": "#/$defs/condition" + }, + "module": { + "title": "Module containing the resource bundle", + "type": "string", + "default": "" + }, + "bundle": { + "title": "Resource bundle name", + "type": "string" + } + }, + "required": [ + "bundle" + ], + "additionalProperties": false } - }, - "required": [ - "glob" - ], - "additionalProperties": false + ] } }, "bundles": { "title": "Metadata to ensure resource bundles are available", "type": "array", "default": [], + "deprecated": true, "items": { "title": "Resource bundle that should be available", "type": "object", diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index f85545c2c5aa..3c391080caa5 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -27,6 +27,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-64787) Enable `--install-exit-handlers` by default for executables and deprecate the option. If shared libraries were using this flag, the same functionality can be restored by using `-H:+InstallJavaExitHandlersForSharedLibrary`. * (GR-47881) Remove the total number of loaded types, fields, and methods from the build output, deprecated these metrics in the build output schema, and removed already deprecated build output metrics. * (GR-64619) Missing registration errors are now subclasses of `LinkageError` +* (GR-63591) Resource bundle registration is now included as part of the `"resources"` section of _reachability-metadata.json_. When this is the case, the bundle name is specified using the `"bundle"` field. ## 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.configure/src/com/oracle/svm/configure/ConfigurationParser.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationParser.java index a677f0d21e7e..105196125e1c 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationParser.java @@ -72,6 +72,7 @@ public static InputStream openStream(URI uri) throws IOException { public static final String GLOBS_KEY = "globs"; public static final String MODULE_KEY = "module"; public static final String GLOB_KEY = "glob"; + public static final String BUNDLE_KEY = "bundle"; private final Map> seenUnknownAttributesByType = new HashMap<>(); private final EnumSet parserOptions; diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ResourceConfigurationParser.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ResourceConfigurationParser.java index e636ea5d43fb..dcb4936d8b8f 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ResourceConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ResourceConfigurationParser.java @@ -62,16 +62,17 @@ protected ResourceConfigurationParser(ConfigurationConditionResolver conditio protected void parseBundlesObject(Object bundlesObject) { List bundles = asList(bundlesObject, "Attribute 'bundles' must be a list of bundles"); for (Object bundle : bundles) { - parseBundle(bundle); + parseBundle(bundle, false); } } protected abstract UnresolvedConfigurationCondition parseCondition(EconomicMap condition); - private void parseBundle(Object bundle) { + protected void parseBundle(Object bundle, boolean inResourcesSection) { EconomicMap resource = asMap(bundle, "Elements of 'bundles' list must be a bundle descriptor object"); - checkAttributes(resource, "bundle descriptor object", Collections.singletonList("name"), Arrays.asList("locales", "classNames", "condition")); - String basename = asString(resource.get("name")); + String bundleNameAttribute = inResourcesSection ? BUNDLE_KEY : NAME_KEY; + checkAttributes(resource, "bundle descriptor object", Collections.singletonList(bundleNameAttribute), Arrays.asList("locales", "classNames", "condition")); + String basename = asString(resource.get(bundleNameAttribute)); TypeResult resolvedConfigurationCondition = conditionResolver.resolveCondition(parseCondition(resource)); if (!resolvedConfigurationCondition.isPresent()) { return; @@ -121,7 +122,7 @@ protected interface GlobPatternConsumer { void accept(T a, String b, String c); } - private void parseGlobEntry(Object data, GlobPatternConsumer resourceRegistry) { + protected void parseGlobEntry(Object data, GlobPatternConsumer resourceRegistry) { EconomicMap globObject = asMap(data, "Elements of 'globs' list must be a glob descriptor objects"); checkAttributes(globObject, "glob resource descriptor object", Collections.singletonList(GLOB_KEY), List.of(CONDITIONAL_KEY, MODULE_KEY)); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ResourceMetadataParser.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ResourceMetadataParser.java index 9b744d8bfc72..519f6a93c7be 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ResourceMetadataParser.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ResourceMetadataParser.java @@ -26,6 +26,7 @@ import java.net.URI; import java.util.EnumSet; +import java.util.List; import org.graalvm.collections.EconomicMap; @@ -40,7 +41,15 @@ final class ResourceMetadataParser extends ResourceConfigurationParser { public void parseAndRegister(Object json, URI origin) { Object resourcesJson = getFromGlobalFile(json, RESOURCES_KEY); if (resourcesJson != null) { - parseGlobsObject(resourcesJson, origin); + List globsAndBundles = asList(resourcesJson, "'resources' section must be a list of glob pattern or bundle descriptors"); + for (Object object : globsAndBundles) { + EconomicMap globOrBundle = asMap(object, "Elements of 'resources' list must be glob pattern or bundle descriptor objects"); + if (globOrBundle.containsKey(GLOB_KEY)) { + parseGlobEntry(object, (condition, module, glob) -> registry.addGlob(condition, module, glob, origin)); + } else if (globOrBundle.containsKey(BUNDLE_KEY)) { + parseBundle(globOrBundle, true); + } + } } Object bundlesJson = getFromGlobalFile(json, BUNDLES_KEY); if (bundlesJson != null) { 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 acafec14115f..97c3a3c4d89d 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 @@ -200,12 +200,7 @@ public static List writeConfigurationToAllPaths(Function key = new ConditionalElement<>(condition, baseName); return bundles.computeIfAbsent(key, cond -> new BundleConfiguration(condition, baseName)); @@ -342,9 +339,13 @@ public boolean anyBundleMatches(UnresolvedConfigurationCondition condition, Stri @Override public void printJson(JsonWriter writer) throws IOException { - printGlobsJson(writer, true); - writer.appendSeparator(); - printBundlesJson(writer, true); + JsonPrinter.printCollection(writer, addedGlobs, ConditionalElement.comparator(ResourceEntry.comparator()), (p, w) -> conditionalGlobElementJson(p, w, true), true, bundles.isEmpty()); + if (!bundles.isEmpty()) { + if (!addedGlobs.isEmpty()) { + writer.appendSeparator(); + } + JsonPrinter.printCollection(writer, bundles.keySet(), ConditionalElement.comparator(String::compareTo), (p, w) -> printResourceBundle(bundles.get(p), w, true), false, true); + } } @Override @@ -352,9 +353,9 @@ public void printLegacyJson(JsonWriter writer) throws IOException { writer.appendObjectStart(); printResourcesJson(writer); writer.appendSeparator(); - printBundlesJson(writer, false); + printBundlesJson(writer); writer.appendSeparator(); - printGlobsJson(writer, false); + printGlobsJson(writer); writer.appendObjectEnd(); } @@ -370,14 +371,14 @@ void printResourcesJson(JsonWriter writer) throws IOException { writer.appendObjectEnd(); } - void printBundlesJson(JsonWriter writer, boolean combinedFile) throws IOException { + void printBundlesJson(JsonWriter writer) throws IOException { writer.quote(BUNDLES_KEY).appendFieldSeparator(); - JsonPrinter.printCollection(writer, bundles.keySet(), ConditionalElement.comparator(), (p, w) -> printResourceBundle(bundles.get(p), w, combinedFile)); + JsonPrinter.printCollection(writer, bundles.keySet(), ConditionalElement.comparator(), (p, w) -> printResourceBundle(bundles.get(p), w, false)); } - void printGlobsJson(JsonWriter writer, boolean combinedFile) throws IOException { - writer.quote(combinedFile ? RESOURCES_KEY : GLOBS_KEY).appendFieldSeparator(); - JsonPrinter.printCollection(writer, addedGlobs, ConditionalElement.comparator(ResourceEntry.comparator()), (p, w) -> conditionalGlobElementJson(p, w, combinedFile)); + void printGlobsJson(JsonWriter writer) throws IOException { + writer.quote(GLOBS_KEY).appendFieldSeparator(); + JsonPrinter.printCollection(writer, addedGlobs, ConditionalElement.comparator(ResourceEntry.comparator()), (p, w) -> conditionalGlobElementJson(p, w, false)); } @Override @@ -388,12 +389,12 @@ public ConfigurationParser createParser(boolean combinedFileSchema, EnumSet w.quote(p)); } - if (!config.classNames.isEmpty()) { + if (!combinedFile && !config.classNames.isEmpty()) { writer.appendSeparator().quote("classNames").appendFieldSeparator(); JsonPrinter.printCollection(writer, config.classNames, Comparator.naturalOrder(), (String p, JsonWriter w) -> w.quote(p)); } @@ -411,7 +412,7 @@ public boolean supportsCombinedFile() { return false; } for (ResourceConfiguration.BundleConfiguration bundleConfiguration : bundles.values()) { - if (!bundleConfiguration.classNames.isEmpty()) { + if (!bundleConfiguration.classNames.isEmpty() || !bundleConfiguration.locales.isEmpty()) { return false; } } 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 acef9ca59d0f..5d1daa89bbad 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 @@ -280,9 +280,8 @@ public void processEntry(EconomicMap entry, ConfigurationSet con case "getBundleImpl": { expectSize(args, 5); String baseName = (String) args.get(2); - String queriedLocale = (String) args.get(3); if (baseName != null) { - resourceConfiguration.addBundle(condition, baseName, queriedLocale); + resourceConfiguration.addBundle(condition, baseName); } break; }