From 766be6c130827ae25d98512685e30a0a33000aa4 Mon Sep 17 00:00:00 2001 From: Loic Ottet Date: Thu, 7 Dec 2023 15:57:37 +0100 Subject: [PATCH] Throw exceptions for missing resource bundle registrations --- substratevm/CHANGELOG.md | 1 + .../svm/agent/BreakpointInterceptor.java | 46 ++++----- .../agent/NativeImageAgentJNIHandleSet.java | 49 +++------- .../svm/configure/trace/AccessAdvisor.java | 2 + .../configure/trace/ReflectionProcessor.java | 1 - ...ContentSubstitutedLocalizationSupport.java | 8 +- .../jdk/localization/LocalizationSupport.java | 93 +++++++++++++++---- .../OptimizedLocalizationSupport.java | 4 +- .../Target_java_util_ResourceBundle.java | 66 +++++++++++++ .../Target_sun_util_resources_Bundles.java | 14 ++- .../MissingResourceRegistrationUtils.java | 15 ++- .../jdk/localization/LocalizationFeature.java | 49 +++++----- .../oracle/svm/jvmtiagentbase/Support.java | 6 ++ 13 files changed, 244 insertions(+), 110 deletions(-) diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index a13df8a0bb478..6ad446a10f889 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -28,6 +28,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (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-51002) Improve intrinsification of method handles. This especially improves the performance of `equals` and `hashCode` methods for records, which use method handles that are now intrinsified. +* (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 5e72875f8fa1a..f575ac7c41760 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 @@ -690,33 +690,39 @@ 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) { - JNIObjectHandle callerClass = state.getDirectCallerClass(); - JNIObjectHandle cacheKey = getObjectArgument(thread, 0); - JNIObjectHandle baseName = Support.callObjectMethod(jni, cacheKey, agent.handles().sunUtilResourcesBundlesCacheKeyGetName); + private static String readLocaleTag(JNIEnvironment jni, JNIObjectHandle locale) { + JNIObjectHandle languageTag = Support.callObjectMethod(jni, locale, agent.handles().javaUtilLocaleToLanguageTag); if (clearException(jni)) { - baseName = nullHandle(); + /*- return root locale */ + return ""; } - JNIObjectHandle locale = Support.callObjectMethod(jni, cacheKey, agent.handles().sunUtilResourcesBundlesCacheKeyGetLocale); + + JNIObjectHandle reconstructedLocale = Support.callStaticObjectMethodL(jni, agent.handles().javaUtilLocale, agent.handles().javaUtilLocaleForLanguageTag, languageTag); if (clearException(jni)) { - locale = nullHandle(); + 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); } - 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)); + private static String getElementString(JNIEnvironment jni, JNIObjectHandle object, JNIMethodId getter) { + JNIObjectHandle valueHandle = Support.callObjectMethod(jni, object, getter); if (clearException(jni)) { - /*- return root locale */ - return ""; + valueHandle = nullHandle(); } - return fromJniString(jni, languageTag); + return valueHandle.notEqual(nullHandle()) ? fromJniString(jni, valueHandle) : ""; } private static boolean loadClass(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { @@ -1626,8 +1632,6 @@ 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), // 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 884686b4f5e0e..f6fb9635d8b97 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; private JNIMethodId javaLangInvokeCallSiteMakeSite = WordFactory.nullPointer(); @@ -132,12 +133,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) { @@ -244,30 +249,6 @@ 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; - } - public JNIMethodId getJavaLangInvokeCallSiteMakeSite(JNIEnvironment env) { if (javaLangInvokeCallSiteMakeSite.isNull()) { JNIObjectHandle javaLangInvokeCallSite = findClass(env, "java/lang/invoke/CallSite"); 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 5f63ef5e89f51..8ffba388fd2a6 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,6 @@ public void processEntry(EconomicMap entry, ConfigurationSet configur break; } - case "putBundleInCache": 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/BundleContentSubstitutedLocalizationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/BundleContentSubstitutedLocalizationSupport.java index a178d797270be..3b2a82f90db32 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/BundleContentSubstitutedLocalizationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/BundleContentSubstitutedLocalizationSupport.java @@ -174,11 +174,13 @@ public boolean isNotIncluded(String bundleName) { } @Override - public void prepareBundle(String bundleName, ResourceBundle bundle, Function> findModule, Locale locale) { - super.prepareBundle(bundleName, bundle, findModule, locale); + public void prepareBundle(String bundleName, ResourceBundle bundle, Function> findModule, Locale locale, boolean jdkLocale) { + super.prepareBundle(bundleName, bundle, findModule, locale, jdkLocale); /* Initialize ResourceBundle.keySet eagerly */ bundle.keySet(); - this.existingBundles.add(control.toBundleName(bundleName, locale)); + if (!jdkLocale) { + this.existingBundles.add(control.toBundleName(bundleName, locale)); + } } @Override 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 db339beac7105..45f8089b6507d 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 @@ -29,11 +29,13 @@ import static com.oracle.svm.util.StringUtil.toSlashSeparated; import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.nio.charset.Charset; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.IllformedLocaleException; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; @@ -43,6 +45,7 @@ import java.util.function.Function; import java.util.stream.Collectors; +import org.graalvm.collections.EconomicSet; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -55,8 +58,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; /** @@ -77,8 +83,12 @@ public class LocalizationSupport { public final ResourceBundle.Control control = ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT); + private final Bundles.Strategy strategy = getLocaleDataStrategy(); + public final Charset defaultCharset; + private final EconomicSet registeredBundles = EconomicSet.create(); + public LocalizationSupport(Locale defaultLocale, Set locales, Charset defaultCharset) { this.defaultLocale = defaultLocale; this.allLocales = locales.toArray(new Locale[0]); @@ -107,13 +117,23 @@ public Map getBundleContentOf(Object bundle) { throw VMError.unsupportedFeature("Resource bundle lookup must be loaded during native image generation: " + bundle.getClass()); } + private static Bundles.Strategy getLocaleDataStrategy() { + try { + Class localeDataStrategy = ReflectionUtil.lookupClass(false, "sun.util.resources.LocaleData$LocaleDataStrategy"); + Field strategyInstance = ReflectionUtil.lookupField(localeDataStrategy, "INSTANCE"); + return (Bundles.Strategy) strategyInstance.get(null); + } catch (IllegalAccessException e) { + throw VMError.shouldNotReachHere(e); + } + } + @Platforms(Platform.HOSTED_ONLY.class) - public void prepareBundle(String bundleName, ResourceBundle bundle, Function> findModule, Locale locale) { + public void prepareBundle(String bundleName, ResourceBundle bundle, Function> findModule, Locale locale, boolean jdkBundle) { /* * Class-based bundle lookup happens on every query, but we don't need to register the * constructor for a property resource bundle since the class lookup will fail. */ - registerRequiredReflectionAndResourcesForBundle(bundleName, Set.of(locale)); + registerRequiredReflectionAndResourcesForBundle(bundleName, Set.of(locale), jdkBundle); if (!(bundle instanceof PropertyResourceBundle)) { registerNullaryConstructor(bundle.getClass()); } @@ -171,33 +191,59 @@ private String getBundleName(String fixedBundleName, Locale locale) { } } - public void registerRequiredReflectionAndResourcesForBundle(String baseName, Collection wantedLocales) { - int i = baseName.lastIndexOf('.'); - if (i > 0) { - String name = baseName.substring(i + 1) + "Provider"; - String providerName = baseName.substring(0, i) + ".spi." + name; - ImageSingletons.lookup(RuntimeReflectionSupport.class).registerClassLookup(ConfigurationCondition.alwaysTrue(), providerName); + public void registerRequiredReflectionAndResourcesForBundle(String baseName, Collection wantedLocales, boolean jdkBundle) { + if (!jdkBundle) { + int i = baseName.lastIndexOf('.'); + if (i > 0) { + String name = baseName.substring(i + 1) + "Provider"; + String providerName = baseName.substring(0, i) + ".spi." + name; + ImageSingletons.lookup(RuntimeReflectionSupport.class).registerClassLookup(ConfigurationCondition.alwaysTrue(), providerName); + } } - ImageSingletons.lookup(RuntimeReflectionSupport.class).registerClassLookup(ConfigurationCondition.alwaysTrue(), baseName); - for (Locale locale : wantedLocales) { - registerRequiredReflectionAndResourcesForBundleAndLocale(baseName, locale); + registerRequiredReflectionAndResourcesForBundleAndLocale(baseName, locale, jdkBundle); } } - private void registerRequiredReflectionAndResourcesForBundleAndLocale(String baseName, Locale baseLocale) { - for (Locale locale : control.getCandidateLocales(baseName, baseLocale)) { - String bundleWithLocale = control.toBundleName(baseName, locale); + public void registerRequiredReflectionAndResourcesForBundleAndLocale(String baseName, Locale baseLocale, boolean jdkBundle) { + /* + * 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 = jdkBundle + ? getJDKBundleCandidateLocales(baseName, baseLocale) + : control.getCandidateLocales(baseName, baseLocale); + + for (Locale locale : candidateLocales) { + String bundleWithLocale = jdkBundle ? strategy.toBundleName(baseName, locale) : 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)) { - RuntimeReflection.registerClassLookup(otherBundleName); + + if (jdkBundle) { + String otherBundleName = Bundles.toOtherBundleName(baseName, bundleWithLocale, locale); + if (!otherBundleName.equals(bundleWithLocale)) { + RuntimeReflection.registerClassLookup(otherBundleName); + } } } } + private static List getJDKBundleCandidateLocales(String baseName, Locale baseLocale) { + /* + * LocaleDataStrategy.getCandidateLocale does some filtering of locales it knows do not have + * a bundle for the requested base name. We still want to see those locales to be able to + * register negative queries for them. + */ + LocaleProviderAdapter.Type adapterType = baseName.contains(".cldr") ? LocaleProviderAdapter.Type.CLDR : LocaleProviderAdapter.Type.JRE; + ResourceBundleBasedAdapter adapter = ((ResourceBundleBasedAdapter) LocaleProviderAdapter.forType(adapterType)); + return adapter.getCandidateLocales(baseName, baseLocale); + } + /** * Template method for subclasses to perform additional tasks. */ @@ -273,4 +319,17 @@ private static void registerNullaryConstructor(Class bundleClass) { } RuntimeReflection.register(nullaryConstructor); } + + @Platforms(Platform.HOSTED_ONLY.class) + public void registerBundleLookup(String baseName) { + registeredBundles.add(baseName); + } + + 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; + } + return registeredBundles.contains(baseName); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/OptimizedLocalizationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/OptimizedLocalizationSupport.java index 5a8a29f4a1b4c..b4d81e3f3a919 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/OptimizedLocalizationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/OptimizedLocalizationSupport.java @@ -93,7 +93,7 @@ public void prepareClassResourceBundle(String basename, Class bundleClass) { bundleLocaleField.set(bundle, locale); // override in this class does not use findModule - prepareBundle(basename, bundle, null, locale); + prepareBundle(basename, bundle, null, locale, false); } catch (ReflectionUtil.ReflectionUtilError | ReflectiveOperationException e) { throw UserError.abort(e, "Failed to instantiated bundle from class %s, reason %s", bundleClass, e.getCause().getMessage()); } @@ -101,7 +101,7 @@ public void prepareClassResourceBundle(String basename, Class bundleClass) { @Platforms(Platform.HOSTED_ONLY.class) @Override - public void prepareBundle(String bundleName, ResourceBundle bundle, Function> findModule, Locale locale) { + public void prepareBundle(String bundleName, ResourceBundle bundle, Function> findModule, Locale locale, boolean jdkBundle) { bundle.keySet(); this.resourceBundles.put(Pair.create(bundleName, locale), bundle); } 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..4af19f6951efa 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); + } + 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); + } + return getBundleImpl(callerModule, unnamedModule, baseName, locale, control); + } + + @Substitute + @SuppressWarnings({"removal", "deprecation"}) + 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) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(GET_CLASSLOADER_PERMISSION); + } + } + if (!ImageSingletons.lookup(LocalizationSupport.class).isRegisteredBundleLookup(baseName, locale, control)) { + MissingResourceRegistrationUtils.missingResourceBundle(baseName); + } + 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..a1d55f46b3e4a 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,6 +37,7 @@ 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 sun.util.resources.Bundles.Strategy; @@ -48,9 +49,18 @@ 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) { + 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..5de9822f80936 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 @@ -40,13 +40,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) { + MissingResourceRegistrationError exception = new MissingResourceRegistrationError( + errorMessage("resource bundle with name", baseName), + baseName); + 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 579688abeae58..9666a4f6c568c 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 @@ -64,14 +64,12 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.nativeimage.hosted.RuntimeReflection; import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; import com.oracle.svm.core.ClassLoaderSupport; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; -import com.oracle.svm.core.jdk.Resources; import com.oracle.svm.core.jdk.localization.BundleContentSubstitutedLocalizationSupport; import com.oracle.svm.core.jdk.localization.LocalizationSupport; import com.oracle.svm.core.jdk.localization.OptimizedLocalizationSupport; @@ -100,7 +98,6 @@ import sun.util.locale.LocaleObjectCache; import sun.util.locale.provider.LocaleProviderAdapter; import sun.util.locale.provider.ResourceBundleBasedAdapter; -import sun.util.resources.Bundles; import sun.util.resources.LocaleData; import sun.util.resources.ParallelListResourceBundle; @@ -502,13 +499,18 @@ 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, true); continue; /* No bundle for this `locale`. */ } if (bundle instanceof ParallelListResourceBundle) { /* Make sure the `bundle` content is complete. */ localeData.setSupplementary((ParallelListResourceBundle) bundle); } - prepareBundle(bundle, locale); + prepareJDKBundle(bundle, locale); } } }); @@ -556,7 +558,7 @@ public void prepareClassResourceBundle(String basename, String className) { Class bundleClass = findClassByName.apply(className); UserError.guarantee(ResourceBundle.class.isAssignableFrom(bundleClass), "%s is not a subclass of ResourceBundle", bundleClass.getName()); trace("Adding class based resource bundle: " + className + " " + bundleClass); - support.registerRequiredReflectionAndResourcesForBundle(basename, Set.of()); + support.registerRequiredReflectionAndResourcesForBundle(basename, Set.of(), false); support.prepareClassResourceBundle(basename, bundleClass); } @@ -574,10 +576,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; @@ -595,18 +593,19 @@ public void prepareBundle(String baseName, Collection wantedLocales) { private void prepareBundleInternal(String baseName, Collection wantedLocales) { boolean somethingFound = false; for (Locale locale : wantedLocales) { + support.registerBundleLookup(baseName); List resourceBundle; try { resourceBundle = ImageSingletons.lookup(ClassLoaderSupport.class).getResourceBundle(baseName, locale); } catch (MissingResourceException mre) { for (Locale candidateLocale : support.control.getCandidateLocales(baseName, locale)) { - prepareNegativeBundle(baseName, candidateLocale); + prepareNegativeBundle(baseName, candidateLocale, false); } continue; } somethingFound |= !resourceBundle.isEmpty(); for (ResourceBundle bundle : resourceBundle) { - prepareBundle(baseName, bundle, locale); + prepareBundle(baseName, bundle, locale, false); } } @@ -635,36 +634,32 @@ private void prepareBundleInternal(String baseName, Collection wantedLoc "If the bundle is part of a module, verify the bundle name is a fully qualified class name. Otherwise " + "verify the bundle path is accessible in the classpath."; trace(errorMessage); - prepareNegativeBundle(baseName, Locale.ROOT); + prepareNegativeBundle(baseName, Locale.ROOT, false); for (String language : wantedLocales.stream().map(Locale::getLanguage).collect(Collectors.toSet())) { - prepareNegativeBundle(baseName, Locale.of(language)); + prepareNegativeBundle(baseName, Locale.of(language), false); } for (Locale locale : wantedLocales) { if (!locale.getCountry().isEmpty()) { - prepareNegativeBundle(baseName, locale); + prepareNegativeBundle(baseName, locale, false); } } } } @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); - } + protected void prepareNegativeBundle(String baseName, Locale locale, boolean jdkBundle) { + support.registerBundleLookup(baseName); + support.registerRequiredReflectionAndResourcesForBundleAndLocale(baseName, locale, jdkBundle); } @Platforms(Platform.HOSTED_ONLY.class) - protected void prepareBundle(ResourceBundle bundle, Locale locale) { - prepareBundle(bundle.getBaseBundleName(), bundle, locale); + protected void prepareJDKBundle(ResourceBundle bundle, Locale locale) { + String baseName = bundle.getBaseBundleName(); + prepareBundle(baseName, bundle, locale, true); } @Platforms(Platform.HOSTED_ONLY.class) - private void prepareBundle(String bundleName, ResourceBundle bundle, Locale locale) { + private void prepareBundle(String bundleName, ResourceBundle bundle, Locale locale, boolean jdkBundle) { trace("Adding bundle " + bundleName + ", locale " + locale); /* * Ensure that the bundle contents are loaded. We need to walk the whole bundle parent chain @@ -672,14 +667,14 @@ private void prepareBundle(String bundleName, ResourceBundle bundle, Locale loca */ for (ResourceBundle cur = bundle; cur != null; cur = SharedSecrets.getJavaUtilResourceBundleAccess().getParent(cur)) { /* Register all bundles with their corresponding locales */ - support.prepareBundle(bundleName, cur, this.imageClassLoader::findModule, cur.getLocale()); + support.prepareBundle(bundleName, cur, this.imageClassLoader::findModule, cur.getLocale(), jdkBundle); } /* * Finally, register the requested bundle with requested locale (Requested might be more * specific than the actual bundle locale */ - support.prepareBundle(bundleName, bundle, this.imageClassLoader::findModule, locale); + support.prepareBundle(bundleName, bundle, this.imageClassLoader::findModule, locale, jdkBundle); } @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);