diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 9542ed34d5bec..cff0d62317cbe 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -18,6 +18,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-30433) Disallow the deprecated environment variable USE_NATIVE_IMAGE_JAVA_PLATFORM_MODULE_SYSTEM=false. * (GR-49655) Experimental support for parts of the [Foreign Function & Memory API](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/ForeignInterface.md) (part of "Project Panama", [JEP 454](https://openjdk.org/jeps/454)) on AMD64. Must be enabled with `-H:+ForeignAPISupport` (requiring `-H:+UnlockExperimentalVMOptions`). * (GR-46407) Correctly rethrow build-time linkage errors at run-time for registered reflection queries. +* (GR-50529) Native Image now throws a specific error when trying to access unregistered resource bundles instead of failing on a subsequent reflection or resource query. ## GraalVM for JDK 21 (Internal Version 23.1.0) * (GR-35746) Lower the default aligned chunk size from 1 MB to 512 KB for the serial and epsilon GCs, reducing memory usage and image size in many cases. 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 3e8b5be55a58c..4fbd3796e3515 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 @@ -687,33 +687,48 @@ private static boolean getBundleImpl(JNIEnvironment jni, JNIObjectHandle thread, return true; } - /* - * Bundles.putBundleInCache is the single point through which all bundles queried through - * sun.util.resources.Bundles go - */ - private static boolean putBundleInCache(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { + private static boolean loadBundleOf(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { JNIObjectHandle callerClass = state.getDirectCallerClass(); - JNIObjectHandle cacheKey = getObjectArgument(thread, 0); - JNIObjectHandle baseName = Support.callObjectMethod(jni, cacheKey, agent.handles().sunUtilResourcesBundlesCacheKeyGetName); - if (clearException(jni)) { - baseName = nullHandle(); - } - JNIObjectHandle locale = Support.callObjectMethod(jni, cacheKey, agent.handles().sunUtilResourcesBundlesCacheKeyGetLocale); - if (clearException(jni)) { - locale = nullHandle(); - } + JNIObjectHandle baseName = getObjectArgument(thread, 0); + JNIObjectHandle locale = getObjectArgument(thread, 1); traceReflectBreakpoint(jni, nullHandle(), nullHandle(), callerClass, bp.specification.methodName, true, state.getFullStackTraceOrNull(), Tracer.UNKNOWN_VALUE, Tracer.UNKNOWN_VALUE, fromJniString(jni, baseName), readLocaleTag(jni, locale), Tracer.UNKNOWN_VALUE); return true; } private static String readLocaleTag(JNIEnvironment jni, JNIObjectHandle locale) { - JNIObjectHandle languageTag = Support.callObjectMethod(jni, locale, agent.handles().getJavaUtilLocaleToLanguageTag(jni)); + JNIObjectHandle languageTag = Support.callObjectMethod(jni, locale, agent.handles().javaUtilLocaleToLanguageTag); if (clearException(jni)) { /*- return root locale */ return ""; } - return fromJniString(jni, languageTag); + + JNIObjectHandle reconstructedLocale = Support.callStaticObjectMethodL(jni, agent.handles().javaUtilLocale, agent.handles().javaUtilLocaleForLanguageTag, languageTag); + if (clearException(jni)) { + reconstructedLocale = nullHandle(); + } + if (Support.callBooleanMethodL(jni, locale, agent.handles().javaUtilLocaleEquals, reconstructedLocale)) { + return fromJniString(jni, languageTag); + } else { + /* + * Ill-formed locale, we do our best to return a unique description of the locale. + * Locale.toLanguageTag simply ignores ill-formed locale elements, which creates + * confusion when trying to determine whether a specific locale was registered. + */ + String language = getElementString(jni, locale, agent.handles().javaUtilLocaleGetLanguage); + String country = getElementString(jni, locale, agent.handles().javaUtilLocaleGetCountry); + String variant = getElementString(jni, locale, agent.handles().javaUtilLocaleGetVariant); + + return String.join("-", language, country, variant); + } + } + + private static String getElementString(JNIEnvironment jni, JNIObjectHandle object, JNIMethodId getter) { + JNIObjectHandle valueHandle = Support.callObjectMethod(jni, object, getter); + if (clearException(jni)) { + valueHandle = nullHandle(); + } + return valueHandle.notEqual(nullHandle()) ? fromJniString(jni, valueHandle) : ""; } private static boolean loadClass(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { @@ -1553,8 +1568,8 @@ private interface BreakpointHandler { "getBundleImpl", "(Ljava/lang/Module;Ljava/lang/Module;Ljava/lang/String;Ljava/util/Locale;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle;", BreakpointInterceptor::getBundleImpl), - brk("sun/util/resources/Bundles", "putBundleInCache", "(Lsun/util/resources/Bundles$CacheKey;Ljava/util/ResourceBundle;)Ljava/util/ResourceBundle;", - BreakpointInterceptor::putBundleInCache), + brk("sun/util/resources/Bundles", "loadBundleOf", "(Ljava/lang/String;Ljava/util/Locale;Lsun/util/resources/Bundles$Strategy;)Ljava/util/ResourceBundle;", + BreakpointInterceptor::loadBundleOf), // In Java 9+, these are Java methods that call private methods optionalBrk("jdk/internal/misc/Unsafe", "objectFieldOffset", "(Ljava/lang/Class;Ljava/lang/String;)J", BreakpointInterceptor::objectFieldOffsetByName), diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java index defd3436ffeef..f359198b125e7 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java @@ -80,15 +80,16 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { private JNIObjectHandle javaLangReflectProxy = WordFactory.nullPointer(); private JNIMethodId javaLangReflectProxyIsProxyClass = WordFactory.nullPointer(); - private JNIMethodId javaUtilLocaleToLanguageTag; - private JNIFieldId javaUtilResourceBundleParentField; - private JNIMethodId javaUtilResourceBundleGetLocale; + final JNIObjectHandle javaUtilLocale; + final JNIMethodId javaUtilLocaleGetLanguage; + final JNIMethodId javaUtilLocaleGetCountry; + final JNIMethodId javaUtilLocaleGetVariant; + final JNIMethodId javaUtilLocaleForLanguageTag; + final JNIMethodId javaUtilLocaleToLanguageTag; + final JNIMethodId javaUtilLocaleEquals; final JNIFieldId javaLangInvokeSerializedLambdaCapturingClass; - final JNIMethodId sunUtilResourcesBundlesCacheKeyGetName; - final JNIMethodId sunUtilResourcesBundlesCacheKeyGetLocale; - final JNIMethodId javaLangModuleGetName; NativeImageAgentJNIHandleSet(JNIEnvironment env) { @@ -128,12 +129,16 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { JNIObjectHandle serializedLambda = findClass(env, "java/lang/invoke/SerializedLambda"); javaLangInvokeSerializedLambdaCapturingClass = getFieldId(env, serializedLambda, "capturingClass", "Ljava/lang/Class;", false); - JNIObjectHandle sunUtilResourcesBundlesCacheKey = findClass(env, "sun/util/resources/Bundles$CacheKey"); - sunUtilResourcesBundlesCacheKeyGetName = getMethodId(env, sunUtilResourcesBundlesCacheKey, "getName", "()Ljava/lang/String;", false); - sunUtilResourcesBundlesCacheKeyGetLocale = getMethodId(env, sunUtilResourcesBundlesCacheKey, "getLocale", "()Ljava/util/Locale;", false); - JNIObjectHandle javaLangModule = findClass(env, "java/lang/Module"); javaLangModuleGetName = getMethodId(env, javaLangModule, "getName", "()Ljava/lang/String;", false); + + javaUtilLocale = newClassGlobalRef(env, "java/util/Locale"); + javaUtilLocaleGetLanguage = getMethodId(env, javaUtilLocale, "getLanguage", "()Ljava/lang/String;", false); + javaUtilLocaleGetCountry = getMethodId(env, javaUtilLocale, "getCountry", "()Ljava/lang/String;", false); + javaUtilLocaleGetVariant = getMethodId(env, javaUtilLocale, "getVariant", "()Ljava/lang/String;", false); + javaUtilLocaleForLanguageTag = getMethodId(env, javaUtilLocale, "forLanguageTag", "(Ljava/lang/String;)Ljava/util/Locale;", true); + javaUtilLocaleEquals = getMethodId(env, javaUtilLocale, "equals", "(Ljava/lang/Object;)Z", false); + javaUtilLocaleToLanguageTag = getMethodId(env, javaUtilLocale, "toLanguageTag", "()Ljava/lang/String;", false); } JNIMethodId getJavaLangReflectExecutableGetParameterTypes(JNIEnvironment env) { @@ -239,28 +244,4 @@ JNIMethodId getJavaLangReflectProxyIsProxyClass(JNIEnvironment env) { } return javaLangReflectProxyIsProxyClass; } - - public JNIMethodId getJavaUtilLocaleToLanguageTag(JNIEnvironment env) { - if (javaUtilLocaleToLanguageTag.isNull()) { - JNIObjectHandle javaUtilLocale = findClass(env, "java/util/Locale"); - javaUtilLocaleToLanguageTag = getMethodId(env, javaUtilLocale, "toLanguageTag", "()Ljava/lang/String;", false); - } - return javaUtilLocaleToLanguageTag; - } - - public JNIFieldId getJavaUtilResourceBundleParentField(JNIEnvironment env) { - if (javaUtilResourceBundleParentField.isNull()) { - JNIObjectHandle javaUtilResourceBundle = findClass(env, "java/util/ResourceBundle"); - javaUtilResourceBundleParentField = getFieldId(env, javaUtilResourceBundle, "parent", "Ljava/util/ResourceBundle;", false); - } - return javaUtilResourceBundleParentField; - } - - public JNIMethodId getJavaUtilResourceBundleGetLocale(JNIEnvironment env) { - if (javaUtilResourceBundleGetLocale.isNull()) { - JNIObjectHandle javaUtilResourceBundle = findClass(env, "java/util/ResourceBundle"); - javaUtilResourceBundleGetLocale = getMethodId(env, javaUtilResourceBundle, "getLocale", "()Ljava/util/Locale;", false); - } - return javaUtilResourceBundleGetLocale; - } } 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 9b022a77dd45b..31e5719d0ba01 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 @@ -85,6 +85,8 @@ public final class AccessAdvisor { internalCallerFilter.addOrGetChildren("java.util.**", ConfigurationFilter.Inclusion.Exclude); internalCallerFilter.addOrGetChildren("java.util.concurrent.atomic.*", ConfigurationFilter.Inclusion.Include); // Atomic*FieldUpdater internalCallerFilter.addOrGetChildren("java.util.Collections", ConfigurationFilter.Inclusion.Include); // java.util.Collections.zeroLengthArray + // LogRecord.readObject looks up resource bundles + internalCallerFilter.addOrGetChildren("java.util.logging.LogRecord", ConfigurationFilter.Inclusion.Include); internalCallerFilter.addOrGetChildren("java.util.random.*", ConfigurationFilter.Inclusion.Include); // RandomGeneratorFactory$$Lambda /* * ForkJoinTask.getThrowableException calls Class.getConstructors and 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 9d8f66401581d..2276a2b421c1b 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 @@ -257,7 +257,7 @@ public void processEntry(EconomicMap entry, ConfigurationSet configur break; } - case "putBundleInCache": + case "loadBundleOf": case "getBundleImpl": { expectSize(args, 5); String baseName = (String) args.get(2); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationSupport.java index fe2cb8dd27202..514bb53b261bd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationSupport.java @@ -27,13 +27,16 @@ import static com.oracle.svm.util.StringUtil.toDotSeparated; import static com.oracle.svm.util.StringUtil.toSlashSeparated; +import static sun.util.locale.provider.LocaleProviderAdapter.Type.CLDR; import java.lang.reflect.Constructor; import java.nio.charset.Charset; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.IllformedLocaleException; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; @@ -43,6 +46,7 @@ import java.util.function.Function; import java.util.stream.Collectors; +import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -55,8 +59,11 @@ import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.jdk.Resources; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.debug.GraalError; +import sun.util.locale.provider.LocaleProviderAdapter; +import sun.util.locale.provider.ResourceBundleBasedAdapter; import sun.util.resources.Bundles; /** @@ -79,6 +86,8 @@ public class LocalizationSupport { public final Charset defaultCharset; + private final EconomicMap> registeredBundles = EconomicMap.create(); + public LocalizationSupport(Locale defaultLocale, Set locales, Charset defaultCharset) { this.defaultLocale = defaultLocale; this.allLocales = locales.toArray(new Locale[0]); @@ -186,10 +195,22 @@ public void registerRequiredReflectionAndResourcesForBundle(String baseName, Col } } - private void registerRequiredReflectionAndResourcesForBundleAndLocale(String baseName, Locale baseLocale) { - for (Locale locale : control.getCandidateLocales(baseName, baseLocale)) { + public void registerRequiredReflectionAndResourcesForBundleAndLocale(String baseName, Locale baseLocale) { + /* + * Bundles in the sun.(text|util).resources.cldr packages are loaded with an alternative + * strategy which tries parent aliases defined in CLDRBaseLocaleDataMetaInfo.parentLocales. + */ + List candidateLocales = isCLDRBundle(baseName) + ? ((ResourceBundleBasedAdapter) LocaleProviderAdapter.forType(CLDR)).getCandidateLocales(baseName, baseLocale) + : control.getCandidateLocales(baseName, baseLocale); + + for (Locale locale : candidateLocales) { String bundleWithLocale = control.toBundleName(baseName, locale); RuntimeReflection.registerClassLookup(bundleWithLocale); + Class bundleClass = ReflectionUtil.lookupClass(true, bundleWithLocale); + if (bundleClass != null) { + registerNullaryConstructor(bundleClass); + } Resources.singleton().registerNegativeQuery(bundleWithLocale.replace('.', '/') + ".properties"); String otherBundleName = Bundles.toOtherBundleName(baseName, bundleWithLocale, locale); if (!otherBundleName.equals(bundleWithLocale)) { @@ -198,6 +219,10 @@ private void registerRequiredReflectionAndResourcesForBundleAndLocale(String bas } } + private boolean isCLDRBundle(String baseName) { + return baseName.startsWith(CLDR.getUtilResourcesPackage()) || baseName.startsWith(CLDR.getTextResourcesPackage()); + } + /** * Template method for subclasses to perform additional tasks. */ @@ -273,4 +298,26 @@ private void registerNullaryConstructor(Class bundleClass) { } RuntimeReflection.register(nullaryConstructor); } + + @Platforms(Platform.HOSTED_ONLY.class) + public void registerBundleLookup(String baseName, Locale locale) { + registeredBundles.putIfAbsent(baseName, new HashSet<>()); + registeredBundles.get(baseName).add(locale); + } + + public boolean isRegisteredBundleLookup(String baseName, Locale locale, Object controlOrStrategy) { + if (baseName == null || locale == null || controlOrStrategy == null) { + /* Those cases will throw a NullPointerException before any lookup */ + return true; + } + if (registeredBundles.get(baseName, Collections.emptySet()).contains(locale)) { + return true; + } + /* + * We cannot guarantee whether non-canonicalizable locales were present in the + * configuration, so we do not throw in that case. The exception thrown when handling the + * locale will be enough for debugging. + */ + return !Locale.forLanguageTag(locale.toLanguageTag()).equals(locale); + } } 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 9d406330db8b4..b1aa6a008d128 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 @@ -24,7 +24,10 @@ */ package com.oracle.svm.core.jdk.localization.substitutions; +import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION; + import java.util.Locale; +import java.util.Objects; import java.util.ResourceBundle; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -38,6 +41,9 @@ import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.jdk.localization.LocalizationSupport; import com.oracle.svm.core.jdk.localization.substitutions.modes.OptimizedLocaleMode; +import com.oracle.svm.core.jdk.resources.MissingResourceRegistrationUtils; + +import jdk.internal.loader.BootLoader; @TargetClass(java.util.ResourceBundle.class) @SuppressWarnings({"unused"}) @@ -98,4 +104,64 @@ private static ResourceBundle getBundle(String baseName, @SuppressWarnings("unus private static ResourceBundle getBundle(String baseName, Locale targetLocale, @SuppressWarnings("unused") Module module) { return ImageSingletons.lookup(LocalizationSupport.class).asOptimizedSupport().getCached(baseName, targetLocale); } + + @Substitute + private static ResourceBundle getBundleImpl(String baseName, + Locale locale, + Class caller, + ClassLoader loader, + ResourceBundle.Control control) { + Module callerModule = getCallerModule(caller); + + // get resource bundles for a named module only if loader is the module's class loader + if (callerModule.isNamed() && loader == getLoader(callerModule)) { + if (!ImageSingletons.lookup(LocalizationSupport.class).isRegisteredBundleLookup(baseName, locale, control)) { + MissingResourceRegistrationUtils.missingResourceBundle(baseName, locale); + } + return getBundleImpl(callerModule, callerModule, baseName, locale, control); + } + + // find resource bundles from unnamed module of given class loader + // Java agent can add to the bootclasspath e.g. via + // java.lang.instrument.Instrumentation and load classes in unnamed module. + // It may call RB::getBundle that will end up here with loader == null. + Module unnamedModule = loader != null + ? loader.getUnnamedModule() + : BootLoader.getUnnamedModule(); + + if (!ImageSingletons.lookup(LocalizationSupport.class).isRegisteredBundleLookup(baseName, locale, control)) { + MissingResourceRegistrationUtils.missingResourceBundle(baseName, locale); + } + return getBundleImpl(callerModule, unnamedModule, baseName, locale, control); + } + + @Substitute + private static ResourceBundle getBundleFromModule(Class caller, + Module module, + String baseName, + Locale locale, + ResourceBundle.Control control) { + Objects.requireNonNull(module); + Module callerModule = getCallerModule(caller); + if (callerModule != module) { + @SuppressWarnings("removal") + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(GET_CLASSLOADER_PERMISSION); + } + } + if (!ImageSingletons.lookup(LocalizationSupport.class).isRegisteredBundleLookup(baseName, locale, control)) { + MissingResourceRegistrationUtils.missingResourceBundle(baseName, locale); + } + return getBundleImpl(callerModule, module, baseName, locale, control); + } + + @Alias + private static native Module getCallerModule(Class caller); + + @Alias + private static native ClassLoader getLoader(Module module); + + @Alias + private static native ResourceBundle getBundleImpl(Module callerModule, Module module, String baseName, Locale locale, ResourceBundle.Control control); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_sun_util_resources_Bundles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_sun_util_resources_Bundles.java index eb85216b565ce..c4aa8699075e9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_sun_util_resources_Bundles.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_sun_util_resources_Bundles.java @@ -37,7 +37,9 @@ import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.jdk.localization.LocalizationSupport; +import com.oracle.svm.core.jdk.localization.substitutions.modes.JvmLocaleMode; import com.oracle.svm.core.jdk.localization.substitutions.modes.OptimizedLocaleMode; +import com.oracle.svm.core.jdk.resources.MissingResourceRegistrationUtils; import sun.util.resources.Bundles.Strategy; @@ -48,9 +50,21 @@ final class Target_sun_util_resources_Bundles { @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// private static ConcurrentMap cacheList = new ConcurrentHashMap<>(); - @TargetElement(onlyWith = OptimizedLocaleMode.class) + @TargetElement(name = "loadBundleOf", onlyWith = OptimizedLocaleMode.class) @Substitute - private static ResourceBundle loadBundleOf(String baseName, Locale targetLocale, Strategy strategy) { + private static ResourceBundle loadBundleOfOptimized(String baseName, Locale targetLocale, Strategy strategy) { return ImageSingletons.lookup(LocalizationSupport.class).asOptimizedSupport().getCached(baseName, targetLocale); } + + @TargetElement(onlyWith = JvmLocaleMode.class) + @Alias + private static native ResourceBundle loadBundleOf(String baseName, Locale targetLocale, Strategy strategy); + + @Substitute + public static ResourceBundle of(String baseName, Locale locale, Strategy strategy) { + if (!ImageSingletons.lookup(LocalizationSupport.class).isRegisteredBundleLookup(baseName, locale, strategy)) { + MissingResourceRegistrationUtils.missingResourceBundle(baseName, locale); + } + return ImageSingletons.lookup(LocalizationSupport.class).jvmMode() ? loadBundleOf(baseName, locale, strategy) : loadBundleOfOptimized(baseName, locale, strategy); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/MissingResourceRegistrationUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/MissingResourceRegistrationUtils.java index 2b39b8f59159b..3d4d00b35b518 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/MissingResourceRegistrationUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/MissingResourceRegistrationUtils.java @@ -28,6 +28,7 @@ import java.nio.file.Files; import java.nio.file.spi.FileSystemProvider; +import java.util.Locale; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; @@ -40,13 +41,22 @@ public final class MissingResourceRegistrationUtils { public static void missingResource(String resourcePath) { - MissingResourceRegistrationError exception = new MissingResourceRegistrationError(errorMessage(resourcePath), resourcePath); + MissingResourceRegistrationError exception = new MissingResourceRegistrationError( + errorMessage("resource at path", resourcePath), + resourcePath); report(exception); } - private static String errorMessage(String resourcePath) { + public static void missingResourceBundle(String baseName, Locale locale) { + MissingResourceRegistrationError exception = new MissingResourceRegistrationError( + errorMessage("resource bundle with name and locale", baseName + ", " + locale), + baseName + "_" + locale); + report(exception); + } + + private static String errorMessage(String type, String resourcePath) { /* Can't use multi-line strings as they pull in format and bloat "Hello, World!" */ - return "The program tried to access the resource at path " + + return "The program tried to access the " + type + System.lineSeparator() + System.lineSeparator() + ERROR_EMPHASIS_INDENT + resourcePath + diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java index d21e21c7e5fba..9e48f381a3747 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java @@ -506,6 +506,11 @@ protected void addResourceBundles() { try { bundle = localeDataBundleGetter.apply(localeData, locale); } catch (MissingResourceException e) { + /* + * Locale data bundle class names do not contain underscores + */ + String baseName = e.getClassName().split("_")[0]; + prepareNegativeBundle(baseName, locale); continue; /* No bundle for this `locale`. */ } if (bundle instanceof ParallelListResourceBundle) { @@ -578,10 +583,6 @@ public void prepareBundle(String baseName) { @Platforms(Platform.HOSTED_ONLY.class) public void prepareBundle(String baseName, Collection wantedLocales) { - if (baseName.isEmpty()) { - return; - } - prepareBundleInternal(baseName, wantedLocales); String alternativeBundleName = null; @@ -599,6 +600,7 @@ public void prepareBundle(String baseName, Collection wantedLocales) { private void prepareBundleInternal(String baseName, Collection wantedLocales) { boolean somethingFound = false; for (Locale locale : wantedLocales) { + support.registerBundleLookup(baseName, locale); List resourceBundle; try { resourceBundle = ImageSingletons.lookup(ClassLoaderSupport.class).getResourceBundle(baseName, locale); @@ -653,18 +655,15 @@ private void prepareBundleInternal(String baseName, Collection wantedLoc @Platforms(Platform.HOSTED_ONLY.class) protected void prepareNegativeBundle(String baseName, Locale locale) { - String bundleName = support.control.toBundleName(baseName, locale); - RuntimeReflection.registerClassLookup(bundleName); - Resources.singleton().registerNegativeQuery(support.getResultingPattern(baseName, locale) + ".properties"); - String otherBundleName = Bundles.toOtherBundleName(baseName, bundleName, locale); - if (!otherBundleName.equals(bundleName)) { - RuntimeReflection.registerClassLookup(otherBundleName); - } + support.registerBundleLookup(baseName, locale); + support.registerRequiredReflectionAndResourcesForBundleAndLocale(baseName, locale); } @Platforms(Platform.HOSTED_ONLY.class) protected void prepareBundle(ResourceBundle bundle, Locale locale) { - prepareBundle(bundle.getBaseBundleName(), bundle, locale); + String baseName = bundle.getBaseBundleName(); + support.registerBundleLookup(baseName, locale); + prepareBundle(baseName, bundle, locale); } @Platforms(Platform.HOSTED_ONLY.class) diff --git a/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java b/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java index 911d9989e4f36..60534cfe78ce3 100644 --- a/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java +++ b/substratevm/src/com.oracle.svm.jvmtiagentbase/src/com/oracle/svm/jvmtiagentbase/Support.java @@ -384,6 +384,12 @@ public static boolean callBooleanMethod(JNIEnvironment env, JNIObjectHandle obj, return jniFunctions().getCallBooleanMethodA().invoke(env, obj, method, nullPointer()); } + public static boolean callBooleanMethodL(JNIEnvironment env, JNIObjectHandle obj, JNIMethodId method, JNIObjectHandle l0) { + JNIValue args = StackValue.get(1, JNIValue.class); + args.addressOf(0).setObject(l0); + return jniFunctions().getCallBooleanMethodA().invoke(env, obj, method, args); + } + public static long callLongMethodL(JNIEnvironment env, JNIObjectHandle obj, JNIMethodId method, JNIObjectHandle l0) { JNIValue args = StackValue.get(1, JNIValue.class); args.addressOf(0).setObject(l0);