From 03bc71dd4c236d2b7f29892c9106c90ca75d31a1 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Thu, 19 Dec 2024 14:20:25 +0100 Subject: [PATCH 1/2] Various fixes for SystemProperties. --- .../posix/PosixProcessPropertiesSupport.java | 3 +- .../WindowsProcessPropertiesSupport.java | 3 +- .../core/jdk/FileSystemProviderSupport.java | 23 +- .../svm/core/jdk/JVMCISubstitutions.java | 2 +- .../svm/core/jdk/JavaLangSubstitutions.java | 16 +- .../svm/core/jdk/SystemPropertiesSupport.java | 380 +++++++++--------- .../core/jdk/Target_jdk_internal_misc_VM.java | 2 +- ...rget_jdk_internal_util_StaticProperty.java | 294 +++++++++----- .../svm/core/jdk/UserSystemProperty.java | 47 +++ .../properties/RuntimePropertyParser.java | 2 +- .../{include => src}/locale_str.h | 10 +- 11 files changed, 469 insertions(+), 313 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UserSystemProperty.java rename substratevm/src/com.oracle.svm.native.libchelper/{include => src}/locale_str.h (97%) diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixProcessPropertiesSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixProcessPropertiesSupport.java index 12b87ac0b2c3..786c168d93e0 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixProcessPropertiesSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixProcessPropertiesSupport.java @@ -33,7 +33,6 @@ import java.util.Map.Entry; import java.util.stream.Collectors; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.nativeimage.c.type.CTypeConversion.CCharPointerHolder; @@ -47,6 +46,8 @@ import com.oracle.svm.core.posix.headers.Stdlib; import com.oracle.svm.core.posix.headers.Unistd; +import jdk.graal.compiler.word.Word; + public abstract class PosixProcessPropertiesSupport extends BaseProcessPropertiesSupport { @Override diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsProcessPropertiesSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsProcessPropertiesSupport.java index 057ba03622d4..22abf4be4ba6 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsProcessPropertiesSupport.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsProcessPropertiesSupport.java @@ -32,7 +32,6 @@ import java.util.List; import java.util.Map; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.nativeimage.impl.ProcessPropertiesSupport; @@ -48,6 +47,8 @@ import com.oracle.svm.core.windows.headers.WinBase.HANDLE; import com.oracle.svm.core.windows.headers.WindowsLibC.WCharPointer; +import jdk.graal.compiler.word.Word; + @AutomaticallyRegisteredImageSingleton(ProcessPropertiesSupport.class) public class WindowsProcessPropertiesSupport extends BaseProcessPropertiesSupport { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/FileSystemProviderSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/FileSystemProviderSupport.java index 85b0dc32eb18..f3ce00798851 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/FileSystemProviderSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/FileSystemProviderSupport.java @@ -30,7 +30,6 @@ import java.util.Collections; import java.util.List; -import jdk.graal.compiler.options.Option; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -49,6 +48,9 @@ import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.options.Option; +import jdk.internal.util.StaticProperty; + public final class FileSystemProviderSupport { public static class Options { @@ -312,7 +314,7 @@ private static synchronized void reinitialize(Target_sun_nio_fs_UnixFileSystem t * Note that the `System.getProperty("user.dir")` value is always used when re-initializing * a UnixFileSystem, which is not the case with the WindowsFileSystem (JDK-8066709). */ - that.originalConstructor(that.provider, System.getProperty("user.dir")); + that.originalConstructor(that.provider, System.getProperty(UserSystemProperty.DIR)); /* * Now the object is completely re-initialized and can be used by any thread without @@ -391,7 +393,7 @@ private static synchronized void reinitialize(Target_sun_nio_fs_WindowsFileSyste return; } that.needsReinitialization = NeedsReinitializationProvider.STATUS_IN_REINITIALIZATION; - that.originalConstructor(that.provider, SystemPropertiesSupport.singleton().userDir()); + that.originalConstructor(that.provider, SystemPropertiesSupport.singleton().getInitialProperty(UserSystemProperty.DIR)); that.needsReinitialization = NeedsReinitializationProvider.STATUS_REINITIALIZED; } } @@ -415,13 +417,14 @@ final class Target_java_io_FileSystem { class UserDirAccessors { @SuppressWarnings("unused") static String getUserDir(Target_java_io_FileSystem that) { - /* - * Note that on Windows, we normalize the property value (JDK-8198997) and do not use the - * `StaticProperty.userDir()` like the rest (JDK-8066709). - */ - return Platform.includedIn(Platform.WINDOWS.class) - ? that.normalize(System.getProperty("user.dir")) - : SystemPropertiesSupport.singleton().userDir(); + if (Platform.includedIn(Platform.WINDOWS.class)) { + /* + * Note that on Windows, we normalize the property value (JDK-8198997) and do not use + * the `StaticProperty.userDir()` like the rest (JDK-8066709). + */ + return that.normalize(System.getProperty(UserSystemProperty.DIR)); + } + return StaticProperty.userDir(); } @SuppressWarnings("unused") diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JVMCISubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JVMCISubstitutions.java index 4ff7a612b638..768328a0a731 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JVMCISubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JVMCISubstitutions.java @@ -65,7 +65,7 @@ final class Target_jdk_vm_ci_services_Services { */ @Substitute public static Map getSavedProperties() { - return SystemPropertiesSupport.singleton().getSavedProperties(); + return SystemPropertiesSupport.singleton().getInitialProperties(); } @Delete // diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java index e748403a552d..d3bd762683d9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java @@ -386,38 +386,38 @@ private static int identityHashCode(Object obj) { @Substitute private static Properties getProperties() { - return SystemPropertiesSupport.singleton().getProperties(); + return SystemPropertiesSupport.singleton().getCurrentProperties(); } @Substitute private static void setProperties(Properties props) { - SystemPropertiesSupport.singleton().setProperties(props); + SystemPropertiesSupport.singleton().setCurrentProperties(props); } @Substitute public static String setProperty(String key, String value) { checkKey(key); - return SystemPropertiesSupport.singleton().setProperty(key, value); + return SystemPropertiesSupport.singleton().setCurrentProperty(key, value); } @Substitute @NeverInlineTrivial("Used in 'java.home' access analysis: AnalyzeJavaHomeAccessPhase") private static String getProperty(String key) { checkKey(key); - return SystemPropertiesSupport.singleton().getProperty(key); + return SystemPropertiesSupport.singleton().getCurrentProperty(key); } @Substitute public static String clearProperty(String key) { checkKey(key); - return SystemPropertiesSupport.singleton().clearProperty(key); + return SystemPropertiesSupport.singleton().clearCurrentProperty(key); } @Substitute @NeverInlineTrivial("Used in 'java.home' access analysis: AnalyzeJavaHomeAccessPhase") private static String getProperty(String key, String def) { - String result = getProperty(key); - return result != null ? result : def; + checkKey(key); + return SystemPropertiesSupport.singleton().getCurrentProperty(key, def); } @Alias @@ -444,7 +444,7 @@ private static String getProperty(String key, String def) { private static void setSecurityManager(SecurityManager sm) { if (sm != null) { /* Read the property collected at isolate creation as that is what happens on the JVM */ - String smp = SystemPropertiesSupport.singleton().getSavedProperties().get("java.security.manager"); + String smp = SystemPropertiesSupport.singleton().getInitialProperty("java.security.manager"); if (smp != null && !smp.equals("disallow")) { throw new SecurityException("Setting the SecurityManager is not supported by Native Image"); } else { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemPropertiesSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemPropertiesSupport.java index 1bff154f0bf3..0a7cb99d97c3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemPropertiesSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemPropertiesSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,12 +26,15 @@ import static java.util.Locale.Category.DISPLAY; import static java.util.Locale.Category.FORMAT; +import static jdk.graal.compiler.nodes.extended.MembarNode.FenceKind.STORE_STORE; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import org.graalvm.nativeimage.ImageInfo; @@ -43,6 +46,7 @@ import org.graalvm.nativeimage.impl.RuntimeSystemPropertiesSupport; import com.oracle.svm.core.LibCHelper; +import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.VM; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.headers.LibCSupport; @@ -50,15 +54,16 @@ import jdk.graal.compiler.api.replacements.Fold; import jdk.graal.compiler.debug.GraalError; +import jdk.graal.compiler.nodes.extended.MembarNode; /** * This class maintains the system properties at run time. * - * Some of the standard system properties can just be taken from the image generator: - * {@link #HOSTED_PROPERTIES}. Other important system properties need to be computed at run time. - * However, we want to do the computation lazily to reduce the startup cost. For example, getting - * the current working directory is quite expensive. We initialize such a property either when it is - * explicitly accessed, or when all properties are accessed. + * Some of the standard system properties can just be taken from the image generator, see + * {@link #HOSTED_PROPERTIES}. Other system properties need to be computed at run time. However, we + * want to do the computation lazily to reduce the startup cost. For example, getting the current + * working directory is quite expensive. We initialize such a property either when it is explicitly + * accessed, or when all properties are accessed. */ public abstract class SystemPropertiesSupport implements RuntimeSystemPropertiesSupport { @@ -68,12 +73,18 @@ public abstract class SystemPropertiesSupport implements RuntimeSystemProperties "java.version.date", ImageInfo.PROPERTY_IMAGE_KIND_KEY, /* - * We do not support cross-compilation for now. Separator might also be cached + * We do not support cross-compilation for now. Separators might also be cached * in other classes, so changing them would be tricky. */ - "line.separator", "path.separator", "file.separator", + "line.separator", + "path.separator", + "file.separator", /* For our convenience for now. */ - "file.encoding", "sun.jnu.encoding", "native.encoding", "stdout.encoding", "stderr.encoding", + "file.encoding", + "sun.jnu.encoding", + "native.encoding", + "stdout.encoding", + "stderr.encoding", "java.class.version", "java.runtime.version", "java.specification.name", @@ -91,22 +102,19 @@ public abstract class SystemPropertiesSupport implements RuntimeSystemProperties private static final int VARIANT_POSITION = COUNTRY_POSITION + 1; private static final int EXTENSION_POSITION = VARIANT_POSITION + 1; - /** System properties that are lazily computed at run time on first access. */ - private final Map> lazyRuntimeValues; - - private Properties properties; - + /** System properties that are computed at run time on first access. */ + private final Map lazySystemProperties = new HashMap<>(); /** - * Initial value of the system properties after parsing command line options at run time. - * Changes by the application using {@link System#setProperties} do not affect this map. + * Initial system property values after parsing command line options at run time. Changes by the + * application (e.g., via {@link System#setProperties}) do not affect this map. Note that this + * map must not contain any null values (see usages on the libgraal-side). */ - final Map savedProperties; + private final Map initialProperties = new ConcurrentHashMap<>(); + /** Read-only wrapper for the initial system property values. */ + private final Map readOnlyInitialProperties = Collections.unmodifiableMap(initialProperties); - private final Map readOnlySavedProperties; - private final String hostOS = System.getProperty("os.name"); - // needed as fallback for platforms that don't implement osNameValue - - private volatile boolean fullyInitialized; + private Properties currentProperties = new Properties(); + private boolean allPropertiesInitialized; @Fold public static SystemPropertiesSupport singleton() { @@ -116,15 +124,10 @@ public static SystemPropertiesSupport singleton() { @Platforms(Platform.HOSTED_ONLY.class) @SuppressWarnings("this-escape") protected SystemPropertiesSupport() { - properties = new Properties(); - savedProperties = new HashMap<>(); - readOnlySavedProperties = Collections.unmodifiableMap(savedProperties); - for (String key : HOSTED_PROPERTIES) { String value = System.getProperty(key); if (value != null) { - properties.put(key, value); - savedProperties.put(key, value); + initializeProperty(key, value); } } @@ -147,188 +150,195 @@ protected SystemPropertiesSupport() { initializeProperty(ImageInfo.PROPERTY_IMAGE_CODE_KEY, ImageInfo.PROPERTY_IMAGE_CODE_VALUE_RUNTIME); - lazyRuntimeValues = new HashMap<>(); - lazyRuntimeValues.put("user.name", this::userName); - lazyRuntimeValues.put("user.home", this::userHome); - lazyRuntimeValues.put("user.dir", this::userDir); - lazyRuntimeValues.put("java.io.tmpdir", this::javaIoTmpDir); - lazyRuntimeValues.put("java.library.path", this::javaLibraryPath); - lazyRuntimeValues.put("os.version", this::osVersionValue); - lazyRuntimeValues.put(UserSystemProperty.USER_LANGUAGE, () -> postProcessLocale(UserSystemProperty.USER_LANGUAGE, parseLocale(DISPLAY).language(), null)); - lazyRuntimeValues.put(UserSystemProperty.USER_LANGUAGE_DISPLAY, () -> postProcessLocale(UserSystemProperty.USER_LANGUAGE, parseLocale(DISPLAY).language(), DISPLAY)); - lazyRuntimeValues.put(UserSystemProperty.USER_LANGUAGE_FORMAT, () -> postProcessLocale(UserSystemProperty.USER_LANGUAGE, parseLocale(FORMAT).language(), FORMAT)); - lazyRuntimeValues.put(UserSystemProperty.USER_SCRIPT, () -> postProcessLocale(UserSystemProperty.USER_SCRIPT, parseLocale(DISPLAY).script(), null)); - lazyRuntimeValues.put(UserSystemProperty.USER_SCRIPT_DISPLAY, () -> postProcessLocale(UserSystemProperty.USER_SCRIPT, parseLocale(DISPLAY).script(), DISPLAY)); - lazyRuntimeValues.put(UserSystemProperty.USER_SCRIPT_FORMAT, () -> postProcessLocale(UserSystemProperty.USER_SCRIPT, parseLocale(FORMAT).script(), FORMAT)); - lazyRuntimeValues.put(UserSystemProperty.USER_COUNTRY, () -> postProcessLocale(UserSystemProperty.USER_COUNTRY, parseLocale(DISPLAY).country(), null)); - lazyRuntimeValues.put(UserSystemProperty.USER_COUNTRY_DISPLAY, () -> postProcessLocale(UserSystemProperty.USER_COUNTRY, parseLocale(DISPLAY).country(), DISPLAY)); - lazyRuntimeValues.put(UserSystemProperty.USER_COUNTRY_FORMAT, () -> postProcessLocale(UserSystemProperty.USER_COUNTRY, parseLocale(FORMAT).country(), FORMAT)); - lazyRuntimeValues.put(UserSystemProperty.USER_VARIANT, () -> postProcessLocale(UserSystemProperty.USER_VARIANT, parseLocale(DISPLAY).variant(), null)); - lazyRuntimeValues.put(UserSystemProperty.USER_VARIANT_DISPLAY, () -> postProcessLocale(UserSystemProperty.USER_VARIANT, parseLocale(DISPLAY).variant(), DISPLAY)); - lazyRuntimeValues.put(UserSystemProperty.USER_VARIANT_FORMAT, () -> postProcessLocale(UserSystemProperty.USER_VARIANT, parseLocale(FORMAT).variant(), FORMAT)); - lazyRuntimeValues.put(UserSystemProperty.USER_EXTENSIONS, () -> postProcessLocale(UserSystemProperty.USER_EXTENSIONS, parseLocale(DISPLAY).extensions(), null)); - lazyRuntimeValues.put(UserSystemProperty.USER_EXTENSIONS_DISPLAY, () -> postProcessLocale(UserSystemProperty.USER_EXTENSIONS, parseLocale(DISPLAY).extensions(), DISPLAY)); - lazyRuntimeValues.put(UserSystemProperty.USER_EXTENSIONS_FORMAT, () -> postProcessLocale(UserSystemProperty.USER_EXTENSIONS, parseLocale(FORMAT).extensions(), FORMAT)); + ArrayList lazyProperties = new ArrayList<>(); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.NAME, this::userNameValue)); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.HOME, this::userHomeValue)); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.DIR, this::userDirValue)); + lazyProperties.add(new LazySystemProperty("java.io.tmpdir", this::javaIoTmpdirValue)); + lazyProperties.add(new LazySystemProperty("java.library.path", this::javaLibraryPathValue)); + lazyProperties.add(new LazySystemProperty("os.version", this::osVersionValue)); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.LANGUAGE, () -> postProcessLocale(UserSystemProperty.LANGUAGE, parseLocale(DISPLAY).language(), null))); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.LANGUAGE_DISPLAY, () -> postProcessLocale(UserSystemProperty.LANGUAGE, parseLocale(DISPLAY).language(), DISPLAY))); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.LANGUAGE_FORMAT, () -> postProcessLocale(UserSystemProperty.LANGUAGE, parseLocale(FORMAT).language(), FORMAT))); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.SCRIPT, () -> postProcessLocale(UserSystemProperty.SCRIPT, parseLocale(DISPLAY).script(), null))); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.SCRIPT_DISPLAY, () -> postProcessLocale(UserSystemProperty.SCRIPT, parseLocale(DISPLAY).script(), DISPLAY))); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.SCRIPT_FORMAT, () -> postProcessLocale(UserSystemProperty.SCRIPT, parseLocale(FORMAT).script(), FORMAT))); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.COUNTRY, () -> postProcessLocale(UserSystemProperty.COUNTRY, parseLocale(DISPLAY).country(), null))); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.COUNTRY_DISPLAY, () -> postProcessLocale(UserSystemProperty.COUNTRY, parseLocale(DISPLAY).country(), DISPLAY))); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.COUNTRY_FORMAT, () -> postProcessLocale(UserSystemProperty.COUNTRY, parseLocale(FORMAT).country(), FORMAT))); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.VARIANT, () -> postProcessLocale(UserSystemProperty.VARIANT, parseLocale(FORMAT).country(), FORMAT))); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.VARIANT_DISPLAY, () -> postProcessLocale(UserSystemProperty.VARIANT, parseLocale(DISPLAY).variant(), DISPLAY))); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.VARIANT_FORMAT, () -> postProcessLocale(UserSystemProperty.VARIANT, parseLocale(FORMAT).variant(), FORMAT))); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.EXTENSIONS, () -> postProcessLocale(UserSystemProperty.EXTENSIONS, parseLocale(DISPLAY).extensions(), null))); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.EXTENSIONS_DISPLAY, () -> postProcessLocale(UserSystemProperty.EXTENSIONS, parseLocale(DISPLAY).extensions(), DISPLAY))); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.EXTENSIONS_FORMAT, () -> postProcessLocale(UserSystemProperty.EXTENSIONS, parseLocale(FORMAT).extensions(), FORMAT))); String targetName = System.getProperty("svm.targetName"); if (targetName != null) { initializeProperty("os.name", targetName); } else { - lazyRuntimeValues.put("os.name", this::osNameValue); + lazyProperties.add(new LazySystemProperty("os.name", this::osNameValue)); } String targetArch = System.getProperty("svm.targetArch"); if (targetArch != null) { initializeProperty("os.arch", targetArch); + } else if (Platform.includedIn(Platform.DARWIN.class)) { + /* On Darwin, we need to use the hosted value to be consistent with HotSpot. */ + initializeProperty("os.arch", System.getProperty("os.arch")); } else { initializeProperty("os.arch", ImageSingletons.lookup(Platform.class).getArchitecture()); } - } - private void ensureFullyInitialized() { - if (!fullyInitialized) { - for (String key : lazyRuntimeValues.keySet()) { - initializeLazyValue(key); - } - fullyInitialized = true; + /* Register all lazy properties. */ + for (LazySystemProperty property : lazyProperties) { + assert !initialProperties.containsKey(property.getKey()); + assert !currentProperties.containsKey(property.getKey()); + + lazySystemProperties.put(property.getKey(), property); } } - public Map getSavedProperties() { - ensureFullyInitialized(); - return readOnlySavedProperties; - } + /** + * Initializes a system property at build-time or during VM startup from external input (e.g., + * command line arguments). + */ + @Override + public void initializeProperty(String key, String value) { + VMError.guarantee(key != null, "The key for a system property must not be null."); + VMError.guarantee(value != null, "System property must have a non-null value."); + + initialProperties.put(key, value); + currentProperties.setProperty(key, value); - public Properties getProperties() { /* - * We do not know what the user is going to do with the returned Properties object, so we - * need to do a full initialization. + * If there is a lazy system property, then mark it as initialized to ensure that the lazy + * initialization code is not executed at run-time. */ - ensureFullyInitialized(); - return properties; + LazySystemProperty property = lazySystemProperties.get(key); + if (property != null) { + property.markAsInitialized(); + } + } + + public Map getInitialProperties() { + ensureAllPropertiesInitialized(); + return readOnlyInitialProperties; } - protected String getProperty(String key) { - initializeLazyValue(key); - return properties.getProperty(key); + public String getInitialProperty(String key) { + return getInitialProperty(key, true); } - protected String getSavedProperty(String key, String defaultValue) { - initializeLazyValue(key); - String value = savedProperties.get(key); + public String getInitialProperty(String key, boolean initializeLazyProperty) { + if (initializeLazyProperty) { + ensurePropertyInitialized(key); + } + return initialProperties.get(key); + } + + public String getInitialProperty(String key, String defaultValue) { + String value = getInitialProperty(key); return value != null ? value : defaultValue; } - public void setProperties(Properties props) { - // Flush lazy values into savedProperties - ensureFullyInitialized(); + public Properties getCurrentProperties() { + ensureAllPropertiesInitialized(); + return currentProperties; + } + + public void setCurrentProperties(Properties props) { + ensureAllPropertiesInitialized(); if (props == null) { + /* Reset to initial values. */ Properties newProps = new Properties(); - for (Map.Entry e : savedProperties.entrySet()) { - newProps.setProperty(e.getKey(), e.getValue()); + for (Map.Entry e : initialProperties.entrySet()) { + String value = e.getValue(); + newProps.setProperty(e.getKey(), value); } - properties = newProps; + currentProperties = newProps; } else { - properties = props; + currentProperties = props; } } - /** - * Initializes a property at startup from external input (e.g., command line arguments). This - * must only be called while the runtime is single threaded. - */ - @Override - public void initializeProperty(String key, String value) { - initializeProperty(key, value, true); + protected String getCurrentProperty(String key) { + ensurePropertyInitialized(key); + return currentProperties.getProperty(key); } - public void initializeProperty(String key, String value, boolean strict) { - String prevValue = savedProperties.put(key, value); - if (strict && prevValue != null && !prevValue.equals(value)) { - VMError.shouldNotReachHere("System property " + key + " is initialized to " + value + " but was previously initialized to " + prevValue + "."); - } - properties.setProperty(key, value); + protected String getCurrentProperty(String key, String defaultValue) { + String value = getCurrentProperty(key); + return value != null ? value : defaultValue; } - public String setProperty(String key, String value) { - /* - * The return value of setProperty is the previous value of the key, so we need to ensure - * that a lazy value for that property was computed. - */ - initializeLazyValue(key); - return (String) properties.setProperty(key, value); + public String setCurrentProperty(String key, String value) { + ensurePropertyInitialized(key); + return (String) currentProperties.setProperty(key, value); } - public String clearProperty(String key) { - initializeLazyValue(key); - return (String) properties.remove(key); + public String clearCurrentProperty(String key) { + ensurePropertyInitialized(key); + return (String) currentProperties.remove(key); } - private void initializeLazyValue(String key) { - if (!fullyInitialized && lazyRuntimeValues.containsKey(key) && properties.get(key) == null) { - /* - * Hashtable.putIfAbsent has the correct synchronization to guard against concurrent - * manual updates of the same property key. - */ - String value = lazyRuntimeValues.get(key).get(); - setRawProperty(key, value); + private void ensureAllPropertiesInitialized() { + if (!allPropertiesInitialized) { + initializeAllProperties(); } } - private void setRawProperty(String key, String value) { - if (value != null && properties.putIfAbsent(key, value) == null) { - synchronized (savedProperties) { - savedProperties.put(key, value); - } + private synchronized void initializeAllProperties() { + if (allPropertiesInitialized) { + return; } - } - private String cachedUserName; - - String userName() { - if (cachedUserName == null) { - cachedUserName = userNameValue(); + for (var entry : lazySystemProperties.entrySet()) { + LazySystemProperty property = entry.getValue(); + initializeProperty(property); } - return cachedUserName; - } - private String cachedUserHome; - - String userHome() { - if (cachedUserHome == null) { - cachedUserHome = userHomeValue(); - } - return cachedUserHome; + /* + * No memory barrier is needed because the loop above already emits one STORE_STORE barrier + * per initialized system property. + */ + allPropertiesInitialized = true; } - private String cachedUserDir; + private void ensurePropertyInitialized(String key) { + if (allPropertiesInitialized) { + return; + } - String userDir() { - if (cachedUserDir == null) { - cachedUserDir = userDirValue(); + LazySystemProperty property = lazySystemProperties.get(key); + if (property != null) { + ensureInitialized(property); } - return cachedUserDir; } - private String cachedJavaIoTmpdir; - - String javaIoTmpDir() { - if (cachedJavaIoTmpdir == null) { - cachedJavaIoTmpdir = javaIoTmpdirValue(); + private void ensureInitialized(LazySystemProperty property) { + if (!property.isInitialized()) { + initializeProperty(property); } - return cachedJavaIoTmpdir; } - private String cachedJavaLibraryPath; + private synchronized void initializeProperty(LazySystemProperty property) { + if (property.isInitialized()) { + return; + } - String javaLibraryPath() { - if (cachedJavaLibraryPath == null) { - cachedJavaLibraryPath = javaLibraryPathValue(); + String key = property.getKey(); + String value = property.computeValue(); + if (value != null) { + currentProperties.put(key, value); + initialProperties.put(key, value); } - return cachedJavaLibraryPath; + + /* Publish the value. */ + property.markAsInitialized(); } - // Platform-specific subclasses compute the actual system property values lazily at run time. + // Platform-specific subclasses compute the actual values lazily at run time. protected abstract String userNameValue(); @@ -336,29 +346,25 @@ String javaLibraryPath() { protected abstract String userDirValue(); + protected abstract String osNameValue(); + + protected abstract String osVersionValue(); + protected String javaIoTmpdirValue() { return tmpdirValue(); } + /* Should be removed, see GR-61420. */ protected String tmpdirValue() { - throw VMError.intentionallyUnimplemented(); + throw VMError.shouldNotReachHere("Subclasses must either implement javaIoTmpdirValue() or tmpdirValue()."); } + /* Should be removed, see GR-61420. */ protected String javaLibraryPathValue() { - /* Default implementation. */ + /* Fallback for platforms that don't implement this method. */ return ""; } - protected String osNameValue() { - /* - * Fallback for systems that don't implement osNameValue in their SystemPropertiesSupport - * implementation. - */ - return hostOS; - } - - protected abstract String osVersionValue(); - public record LocaleEncoding(String language, String script, String country, String variant, String extensions) { private LocaleEncoding(CCharPointerPointer properties) { this(fromCStringArray(properties, LANGUAGE_POSITION), @@ -407,7 +413,7 @@ private String postProcessLocale(String base, String value, Locale.Category cate /* user.xxx property */ String baseValue = null; if (value != null) { - setRawProperty(base, value); + initializeProperty(base, value); baseValue = value; } return baseValue; @@ -415,9 +421,9 @@ private String postProcessLocale(String base, String value, Locale.Category cate switch (category) { case DISPLAY, FORMAT -> { /* user.xxx.(display|format) property */ - String baseValue = getProperty(base); + String baseValue = getCurrentProperty(base); if (baseValue == null && value != null) { - setRawProperty(base + '.' + category.name().toLowerCase(Locale.ROOT), value); + initializeProperty(base + '.' + category.name().toLowerCase(Locale.ROOT), value); return value; } return null; @@ -426,22 +432,36 @@ private String postProcessLocale(String base, String value, Locale.Category cate } } - public static class UserSystemProperty { - public static final String USER_LANGUAGE = "user.language"; - public static final String USER_LANGUAGE_DISPLAY = USER_LANGUAGE + ".display"; - public static final String USER_LANGUAGE_FORMAT = USER_LANGUAGE + ".format"; - public static final String USER_SCRIPT = "user.script"; - public static final String USER_SCRIPT_DISPLAY = USER_SCRIPT + ".display"; - public static final String USER_SCRIPT_FORMAT = USER_SCRIPT + ".format"; - public static final String USER_COUNTRY = "user.country"; - public static final String USER_COUNTRY_DISPLAY = USER_COUNTRY + ".display"; - public static final String USER_COUNTRY_FORMAT = USER_COUNTRY + ".format"; - public static final String USER_VARIANT = "user.variant"; - public static final String USER_VARIANT_DISPLAY = USER_VARIANT + ".display"; - public static final String USER_VARIANT_FORMAT = USER_VARIANT + ".format"; - public static final String USER_EXTENSIONS = "user.extensions"; - public static final String USER_EXTENSIONS_DISPLAY = USER_EXTENSIONS + ".display"; - public static final String USER_EXTENSIONS_FORMAT = USER_EXTENSIONS + ".format"; - public static final String USER_REGION = "user.region"; + private static class LazySystemProperty { + private final String key; + private final Supplier supplier; + + private boolean initialized; + + @Platforms(Platform.HOSTED_ONLY.class) + LazySystemProperty(String key, Supplier supplier) { + this.key = key; + this.supplier = supplier; + } + + public String getKey() { + return key; + } + + public boolean isInitialized() { + return initialized; + } + + public String computeValue() { + return supplier.get(); + } + + public void markAsInitialized() { + if (!SubstrateUtil.HOSTED) { + /* Ensure that other threads see consistent values once 'initialized' is true. */ + MembarNode.memoryBarrier(STORE_STORE); + } + initialized = true; + } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_misc_VM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_misc_VM.java index ae7b5fee71f8..a1ef24a0eefc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_misc_VM.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_misc_VM.java @@ -47,7 +47,7 @@ public final class Target_jdk_internal_misc_VM { @Substitute public static String getSavedProperty(String name) { - return SystemPropertiesSupport.singleton().getSavedProperties().get(name); + return SystemPropertiesSupport.singleton().getInitialProperty(name); } @Substitute diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_util_StaticProperty.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_util_StaticProperty.java index 16747c29576d..8152a2cd5ab5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_util_StaticProperty.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_util_StaticProperty.java @@ -24,211 +24,303 @@ */ package com.oracle.svm.core.jdk; -import java.util.function.BooleanSupplier; - import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Delete; +import com.oracle.svm.core.annotate.KeepOriginal; 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.annotate.TargetElement; -import com.oracle.svm.util.ReflectionUtil; -import jdk.internal.util.StaticProperty; +import jdk.graal.compiler.serviceprovider.JavaVersionUtil; /** * This class provides JDK-internal access to values that are also available via system properties. - * However, it must not return values changes by the user. We do not want to query the values during - * VM startup, because doing that is expensive. So we perform lazy initialization by calling the - * same methods also used to initialize the system properties. + * However, it must not return values changed by the user. We do not want to query the values during + * VM startup, because doing that is expensive. So we perform lazy initialization by accessing the + * corresponding system properties. + *

+ * We {@link Substitute substitute} the whole class so that it is possible to use a custom static + * constructor at run-time. + *

+ * Note for updating: use {@link Delete} for static fields that should be unreachable (e.g, because + * we substituted an accessor and the field is therefore unused). Use {@link Alias} for static + * fields that can be initialized in our custom static constructor. Use {@link Substitute} for + * methods that access expensive lazily initialized system properties (see + * {@link SystemPropertiesSupport} for a list of all lazily initialized properties). Use + * {@link KeepOriginal} for methods that we don't want to substitute. */ @Substitute @TargetClass(jdk.internal.util.StaticProperty.class) -@SuppressWarnings("unused") +@SuppressWarnings({"unused", "FieldCanBeLocal"}) final class Target_jdk_internal_util_StaticProperty { - // Checkstyle: stop + @Delete// + private static String JAVA_HOME; + + @Delete// + private static String USER_HOME; + + @Delete// + private static String USER_DIR; + + @Delete// + private static String USER_NAME; + + @Delete// + private static String JAVA_LIBRARY_PATH; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// + private static String SUN_BOOT_LIBRARY_PATH; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// + private static String JDK_SERIAL_FILTER; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// + private static String JDK_SERIAL_FILTER_FACTORY; + + @Delete// + private static String JAVA_IO_TMPDIR; + @Alias// - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// + private static String NATIVE_ENCODING; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// + private static String FILE_ENCODING; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// + private static String JAVA_PROPERTIES_DATE; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// + private static String SUN_JNU_ENCODING; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// + private static String JAVA_LOCALE_USE_OLD_ISO_CODES; + + @Delete// + @TargetElement(onlyWith = JDKLatest.class)// + private static String OS_NAME; + + @Alias// + @TargetElement(onlyWith = JDKLatest.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// + private static String OS_ARCH; + + @Delete// + @TargetElement(onlyWith = JDKLatest.class)// + private static String OS_VERSION; + + @Alias// + @TargetElement(onlyWith = JDKLatest.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// public static String USER_LANGUAGE; @Alias// - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + @TargetElement(onlyWith = JDKLatest.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// public static String USER_LANGUAGE_DISPLAY; @Alias// - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + @TargetElement(onlyWith = JDKLatest.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// public static String USER_LANGUAGE_FORMAT; @Alias// - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + @TargetElement(onlyWith = JDKLatest.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// public static String USER_SCRIPT; @Alias// - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + @TargetElement(onlyWith = JDKLatest.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// public static String USER_SCRIPT_DISPLAY; @Alias// - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + @TargetElement(onlyWith = JDKLatest.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// public static String USER_SCRIPT_FORMAT; @Alias// - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + @TargetElement(onlyWith = JDKLatest.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// public static String USER_COUNTRY; @Alias// - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + @TargetElement(onlyWith = JDKLatest.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// public static String USER_COUNTRY_DISPLAY; @Alias// - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + @TargetElement(onlyWith = JDKLatest.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// public static String USER_COUNTRY_FORMAT; @Alias// - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + @TargetElement(onlyWith = JDKLatest.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// public static String USER_VARIANT; @Alias// - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + @TargetElement(onlyWith = JDKLatest.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// public static String USER_VARIANT_DISPLAY; @Alias// - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + @TargetElement(onlyWith = JDKLatest.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// public static String USER_VARIANT_FORMAT; @Alias// - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + @TargetElement(onlyWith = JDKLatest.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// public static String USER_EXTENSIONS; @Alias// - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + @TargetElement(onlyWith = JDKLatest.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// public static String USER_EXTENSIONS_DISPLAY; @Alias// - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + @TargetElement(onlyWith = JDKLatest.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// public static String USER_EXTENSIONS_FORMAT; @Alias// - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + @TargetElement(onlyWith = JDKLatest.class)// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// public static String USER_REGION; // Checkstyle: resume + /* + * This static constructor is executed at run-time. Be careful that it only initializes lazy + * system properties that are reasonably cheap to initialize (e.g., everything related to + * locale). + */ static { if (!SubstrateUtil.HOSTED) { - USER_LANGUAGE = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_LANGUAGE, "en"); - USER_LANGUAGE_DISPLAY = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_LANGUAGE_DISPLAY, USER_LANGUAGE); - USER_LANGUAGE_FORMAT = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_SCRIPT_FORMAT, USER_LANGUAGE); - USER_SCRIPT = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_SCRIPT, ""); - USER_SCRIPT_DISPLAY = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_SCRIPT_DISPLAY, USER_SCRIPT); - USER_SCRIPT_FORMAT = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_SCRIPT_FORMAT, USER_SCRIPT); - USER_COUNTRY = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_COUNTRY, ""); - USER_COUNTRY_DISPLAY = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_COUNTRY_DISPLAY, USER_COUNTRY); - USER_COUNTRY_FORMAT = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_COUNTRY_FORMAT, USER_COUNTRY); - USER_VARIANT = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_VARIANT, ""); - USER_VARIANT_DISPLAY = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_VARIANT_DISPLAY, USER_VARIANT); - USER_VARIANT_FORMAT = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_VARIANT_FORMAT, USER_VARIANT); - USER_EXTENSIONS = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_EXTENSIONS, ""); - USER_EXTENSIONS_DISPLAY = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_EXTENSIONS_DISPLAY, USER_EXTENSIONS); - USER_EXTENSIONS_FORMAT = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_EXTENSIONS_FORMAT, USER_EXTENSIONS); - USER_REGION = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_REGION, ""); + SystemPropertiesSupport p = SystemPropertiesSupport.singleton(); + SUN_BOOT_LIBRARY_PATH = p.getInitialProperty("sun.boot.library.path", ""); + JDK_SERIAL_FILTER = p.getInitialProperty("jdk.serialFilter"); + JDK_SERIAL_FILTER_FACTORY = p.getInitialProperty("jdk.serialFilterFactory"); + NATIVE_ENCODING = p.getInitialProperty("native.encoding"); + FILE_ENCODING = p.getInitialProperty("file.encoding"); + JAVA_PROPERTIES_DATE = p.getInitialProperty("java.properties.date"); + SUN_JNU_ENCODING = p.getInitialProperty("sun.jnu.encoding"); + JAVA_LOCALE_USE_OLD_ISO_CODES = p.getInitialProperty("java.locale.useOldISOCodes", ""); + + if (JavaVersionUtil.JAVA_SPEC > 21) { + OS_ARCH = p.getInitialProperty("os.arch"); + + USER_LANGUAGE = p.getInitialProperty(UserSystemProperty.LANGUAGE, "en"); + USER_LANGUAGE_DISPLAY = p.getInitialProperty(UserSystemProperty.LANGUAGE_DISPLAY, USER_LANGUAGE); + USER_LANGUAGE_FORMAT = p.getInitialProperty(UserSystemProperty.LANGUAGE_FORMAT, USER_LANGUAGE); + // for compatibility, check for old user.region property + USER_REGION = p.getInitialProperty(UserSystemProperty.REGION, ""); + if (!USER_REGION.isEmpty()) { + // region can be of form country, country_variant, or _variant + int i = USER_REGION.indexOf('_'); + if (i >= 0) { + USER_COUNTRY = USER_REGION.substring(0, i); + USER_VARIANT = USER_REGION.substring(i + 1); + } else { + USER_COUNTRY = USER_REGION; + USER_VARIANT = ""; + } + USER_SCRIPT = ""; + } else { + USER_SCRIPT = p.getInitialProperty(UserSystemProperty.SCRIPT, ""); + USER_COUNTRY = p.getInitialProperty(UserSystemProperty.COUNTRY, ""); + USER_VARIANT = p.getInitialProperty(UserSystemProperty.VARIANT, ""); + } + USER_SCRIPT_DISPLAY = p.getInitialProperty(UserSystemProperty.SCRIPT_DISPLAY, USER_SCRIPT); + USER_SCRIPT_FORMAT = p.getInitialProperty(UserSystemProperty.SCRIPT_FORMAT, USER_SCRIPT); + USER_COUNTRY_DISPLAY = p.getInitialProperty(UserSystemProperty.COUNTRY_DISPLAY, USER_COUNTRY); + USER_COUNTRY_FORMAT = p.getInitialProperty(UserSystemProperty.COUNTRY_FORMAT, USER_COUNTRY); + USER_VARIANT_DISPLAY = p.getInitialProperty(UserSystemProperty.VARIANT_DISPLAY, USER_VARIANT); + USER_VARIANT_FORMAT = p.getInitialProperty(UserSystemProperty.VARIANT_FORMAT, USER_VARIANT); + USER_EXTENSIONS = p.getInitialProperty(UserSystemProperty.EXTENSIONS, ""); + USER_EXTENSIONS_DISPLAY = p.getInitialProperty(UserSystemProperty.EXTENSIONS_DISPLAY, USER_EXTENSIONS); + USER_EXTENSIONS_FORMAT = p.getInitialProperty(UserSystemProperty.EXTENSIONS_FORMAT, USER_EXTENSIONS); + } } } @Substitute private static String javaHome() { - /* Native images do not have a Java home directory. */ - return null; + return SystemPropertiesSupport.singleton().getInitialProperty("java.home"); } @Substitute private static String userHome() { - return SystemPropertiesSupport.singleton().userHome(); + return SystemPropertiesSupport.singleton().getInitialProperty(UserSystemProperty.HOME); } @Substitute private static String userDir() { - return SystemPropertiesSupport.singleton().userDir(); + return SystemPropertiesSupport.singleton().getInitialProperty(UserSystemProperty.DIR); } @Substitute private static String userName() { - return SystemPropertiesSupport.singleton().userName(); - } - - @Substitute - private static String javaIoTmpDir() { - return SystemPropertiesSupport.singleton().javaIoTmpDir(); + return SystemPropertiesSupport.singleton().getInitialProperty(UserSystemProperty.NAME); } @Substitute private static String javaLibraryPath() { - return SystemPropertiesSupport.singleton().javaLibraryPath(); + return SystemPropertiesSupport.singleton().getInitialProperty("java.library.path", ""); } @Substitute - private static String sunBootLibraryPath() { - String value = SystemPropertiesSupport.singleton().savedProperties.get("sun.boot.library.path"); - return value == null ? "" : value; - } - - @Substitute - private static String jdkSerialFilter() { - return SystemPropertiesSupport.singleton().savedProperties.get("jdk.serialFilter"); + private static String javaIoTmpDir() { + return SystemPropertiesSupport.singleton().getInitialProperty("java.io.tmpdir"); } - @Substitute - @TargetElement(onlyWith = StaticPropertyJdkSerialFilterFactoryAvailable.class) - private static String jdkSerialFilterFactory() { - return SystemPropertiesSupport.singleton().savedProperties.get("jdk.serialFilterFactory"); - } + @KeepOriginal + public static native String sunBootLibraryPath(); - private abstract static class StaticPropertyMethodAvailable implements BooleanSupplier { + @KeepOriginal + public static native String jdkSerialFilter(); - private final String methodName; + @KeepOriginal + public static native String jdkSerialFilterFactory(); - protected StaticPropertyMethodAvailable(String methodName) { - this.methodName = methodName; - } + @KeepOriginal + public static native String nativeEncoding(); - @Override - public boolean getAsBoolean() { - return ReflectionUtil.lookupMethod(true, StaticProperty.class, methodName) != null; - } - } + @KeepOriginal + public static native String fileEncoding(); - /* - * Method jdkSerialFilterFactory is present in some versions of the JDK11 and not in the other. - * It is always present in the JDK17. We need to check if this method should be substituted by - * checking if it exists in the running JDK version. - */ - private static class StaticPropertyJdkSerialFilterFactoryAvailable extends StaticPropertyMethodAvailable { - protected StaticPropertyJdkSerialFilterFactoryAvailable() { - super("jdkSerialFilterFactory"); - } - } + @KeepOriginal + public static native String javaPropertiesDate(); - @Substitute - public static String nativeEncoding() { - return SystemPropertiesSupport.singleton().savedProperties.get("native.encoding"); - } + @KeepOriginal + public static native String jnuEncoding(); - @Substitute - public static String fileEncoding() { - return SystemPropertiesSupport.singleton().savedProperties.get("file.encoding"); - } + @KeepOriginal + public static native String javaLocaleUseOldISOCodes(); @Substitute - public static String javaPropertiesDate() { - return SystemPropertiesSupport.singleton().savedProperties.getOrDefault("java.properties.date", null); + @TargetElement(onlyWith = JDKLatest.class)// + public static String osName() { + return SystemPropertiesSupport.singleton().getInitialProperty("os.name"); } - @Substitute - public static String jnuEncoding() { - return SystemPropertiesSupport.singleton().savedProperties.get("sun.jnu.encoding"); - } + @KeepOriginal + @TargetElement(onlyWith = JDKLatest.class)// + public static native String osArch(); @Substitute - public static String javaLocaleUseOldISOCodes() { - return SystemPropertiesSupport.singleton().savedProperties.getOrDefault("java.locale.useOldISOCodes", ""); + @TargetElement(onlyWith = JDKLatest.class)// + public static String osVersion() { + return SystemPropertiesSupport.singleton().getInitialProperty("os.version"); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UserSystemProperty.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UserSystemProperty.java new file mode 100644 index 000000000000..dc54e39201f2 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/UserSystemProperty.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.jdk; + +public class UserSystemProperty { + public static final String NAME = "user.name"; + public static final String HOME = "user.home"; + public static final String DIR = "user.dir"; + public static final String LANGUAGE = "user.language"; + public static final String LANGUAGE_DISPLAY = "user.language.display"; + public static final String LANGUAGE_FORMAT = "user.language.format"; + public static final String SCRIPT = "user.script"; + public static final String SCRIPT_DISPLAY = "user.script.display"; + public static final String SCRIPT_FORMAT = "user.script.format"; + public static final String COUNTRY = "user.country"; + public static final String COUNTRY_DISPLAY = "user.country.display"; + public static final String COUNTRY_FORMAT = "user.country.format"; + public static final String VARIANT = "user.variant"; + public static final String VARIANT_DISPLAY = "user.variant.display"; + public static final String VARIANT_FORMAT = "user.variant.format"; + public static final String EXTENSIONS = "user.extensions"; + public static final String EXTENSIONS_DISPLAY = "user.extensions.display"; + public static final String EXTENSIONS_FORMAT = "user.extensions.format"; + public static final String REGION = "user.region"; +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/properties/RuntimePropertyParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/properties/RuntimePropertyParser.java index ab1ea72ca143..4ed029d743fe 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/properties/RuntimePropertyParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/properties/RuntimePropertyParser.java @@ -56,7 +56,7 @@ public static String[] parse(String[] args) { } MapCursor cursor = properties.getEntries(); while (cursor.advance()) { - SystemPropertiesSupport.singleton().initializeProperty(cursor.getKey(), cursor.getValue(), false); + SystemPropertiesSupport.singleton().initializeProperty(cursor.getKey(), cursor.getValue()); } if (newIdx == args.length) { /* We can be allocation free and just return the original arguments. */ diff --git a/substratevm/src/com.oracle.svm.native.libchelper/include/locale_str.h b/substratevm/src/com.oracle.svm.native.libchelper/src/locale_str.h similarity index 97% rename from substratevm/src/com.oracle.svm.native.libchelper/include/locale_str.h rename to substratevm/src/com.oracle.svm.native.libchelper/src/locale_str.h index d1373f30cb9f..3bf3c3cd9d4a 100644 --- a/substratevm/src/com.oracle.svm.native.libchelper/include/locale_str.h +++ b/substratevm/src/com.oracle.svm.native.libchelper/src/locale_str.h @@ -23,14 +23,6 @@ * questions. */ -typedef struct { - char* language; - char* script; - char* country; - char* variant; - char* extensions; -} locale_props_t; - /* * Mappings from partial locale names to full locale names */ @@ -239,4 +231,4 @@ static char *country_names[] = { static char *variant_names[] = { "nynorsk", "NY", "", "", -}; \ No newline at end of file +}; From a344a3e27e1e7aff8237daa676be80651cb184b6 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Tue, 21 Jan 2025 09:53:34 +0100 Subject: [PATCH 2/2] Various fixes for locales. --- .../org.graalvm.nativeimage/snapshot.sigtest | 1 + .../nativeimage/ProcessProperties.java | 6 +- .../impl/ProcessPropertiesSupport.java | 7 +- substratevm/mx.substratevm/mx_substratevm.py | 3 + .../posix/PosixProcessPropertiesSupport.java | 3 + .../com/oracle/svm/core/posix/PosixUtils.java | 4 +- .../WindowsProcessPropertiesSupport.java | 3 + .../src/com/oracle/svm/core/LibCHelper.java | 16 -- .../com/oracle/svm/core/SubstrateOptions.java | 3 + .../svm/core/c/locale/LocaleCHelper.java | 96 +++++++++ .../oracle/svm/core/c/locale/LocaleData.java | 31 +++ .../svm/core/c/locale/LocaleDirectives.java | 39 ++++ .../svm/core/c/locale/LocaleSupport.java | 202 ++++++++++++++++++ .../graal/snippets/CEntryPointSnippets.java | 17 +- .../svm/core/jdk/SystemPropertiesSupport.java | 109 ++-------- .../include/svm_locale.h | 41 ++++ .../src/locale.c | 130 +++++------ .../svm/test/ProcessPropertiesTest.java | 7 +- 18 files changed, 529 insertions(+), 189 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/locale/LocaleCHelper.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/locale/LocaleData.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/locale/LocaleDirectives.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/locale/LocaleSupport.java create mode 100644 substratevm/src/com.oracle.svm.native.libchelper/include/svm_locale.h diff --git a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest index ddb5a9414cd4..b5d3a2ec5316 100644 --- a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest +++ b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest @@ -453,6 +453,7 @@ meth public static java.lang.String getExecutableName() meth public static java.lang.String getObjectFile(java.lang.String) meth public static java.lang.String getObjectFile(org.graalvm.nativeimage.c.function.CEntryPointLiteral) meth public static java.lang.String setLocale(java.lang.String,java.lang.String) + anno 0 java.lang.Deprecated(boolean forRemoval=false, java.lang.String since="25.0") meth public static long getProcessID() meth public static long getProcessID(java.lang.Process) meth public static void exec(java.nio.file.Path,java.lang.String[],java.util.Map) diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ProcessProperties.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ProcessProperties.java index 2b84f5bbee11..5fa4f3c25d2d 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ProcessProperties.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ProcessProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -145,7 +145,11 @@ public static String getObjectFile(CEntryPointLiteral symbol) { * Set the program locale. * * @since 19.0 + * @deprecated in 25.0 without replacement. This method is inherently unsafe because + * {@code setlocale(...)} is not thread-safe on the OS level. */ + @Deprecated(since = "25.0") + @SuppressWarnings("deprecation") public static String setLocale(String category, String locale) { return ImageSingletons.lookup(ProcessPropertiesSupport.class).setLocale(category, locale); } diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ProcessPropertiesSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ProcessPropertiesSupport.java index fec57ff274d6..496e81fe9218 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ProcessPropertiesSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ProcessPropertiesSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 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 @@ -64,6 +64,11 @@ default String getObjectFile(PointerBase symbolAddress) { return null; } + /** + * @deprecated in 25.0 without replacement. This method is inherently unsafe because + * {@code setlocale(...)} is not thread-safe on the OS level. + */ + @Deprecated String setLocale(String category, String locale); boolean destroy(long processID); diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index ff740f68a193..9d3d8f1d1d67 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -1572,6 +1572,9 @@ def prevent_build_path_in_libgraal(): # Reduce image size by outlining all write barriers. # Benchmarking showed no performance degradation. '-H:+OutlineWriteBarriers', + + # Libgraal must not change the process-wide locale settings. + '-H:-UseSystemLocale', ] + ([ # Force page size to support libgraal on AArch64 machines with a page size up to 64K. '-H:PageSize=64K' diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixProcessPropertiesSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixProcessPropertiesSupport.java index 786c168d93e0..e3e01beaaf97 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixProcessPropertiesSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixProcessPropertiesSupport.java @@ -39,6 +39,7 @@ import org.graalvm.word.PointerBase; import com.oracle.svm.core.BaseProcessPropertiesSupport; +import com.oracle.svm.core.c.locale.LocaleSupport; import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; import com.oracle.svm.core.memory.UntrackedNullableNativeMemory; import com.oracle.svm.core.posix.headers.Dlfcn; @@ -108,7 +109,9 @@ public String getObjectFile(PointerBase symbolAddress) { } } + /** This method is unsafe and should not be used, see {@link LocaleSupport}. */ @Override + @SuppressWarnings("deprecation") public String setLocale(String category, String locale) { return PosixUtils.setLocale(category, locale); } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixUtils.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixUtils.java index ffb232eb456e..016b9f276f8d 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixUtils.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixUtils.java @@ -46,6 +46,7 @@ import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.c.libc.GLibC; import com.oracle.svm.core.c.libc.LibCBase; +import com.oracle.svm.core.c.locale.LocaleSupport; import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; import com.oracle.svm.core.headers.LibC; import com.oracle.svm.core.hub.DynamicHub; @@ -70,12 +71,13 @@ import jdk.graal.compiler.word.Word; public class PosixUtils { + /** This method is unsafe and should not be used, see {@link LocaleSupport}. */ static String setLocale(String category, String locale) { int intCategory = getCategory(category); - return setLocale(intCategory, locale); } + /** This method is unsafe and should not be used, see {@link LocaleSupport}. */ private static String setLocale(int category, String locale) { if (locale == null) { CCharPointer cstrResult = Locale.setlocale(category, Word.nullPointer()); diff --git a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsProcessPropertiesSupport.java b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsProcessPropertiesSupport.java index 22abf4be4ba6..bfd411171d32 100644 --- a/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsProcessPropertiesSupport.java +++ b/substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsProcessPropertiesSupport.java @@ -38,6 +38,7 @@ import org.graalvm.word.PointerBase; import com.oracle.svm.core.BaseProcessPropertiesSupport; +import com.oracle.svm.core.c.locale.LocaleSupport; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.graal.stackvalue.UnsafeStackValue; import com.oracle.svm.core.util.VMError; @@ -133,7 +134,9 @@ private static String getModulePath(WinBase.HMODULE module) { return WindowsSystemPropertiesSupport.toJavaString(path, length); } + /** This method is unsafe and should not be used, see {@link LocaleSupport}. */ @Override + @SuppressWarnings("deprecation") public String setLocale(String category, String locale) { throw VMError.intentionallyUnimplemented(); // ExcludeFromJacocoGeneratedReport } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/LibCHelper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/LibCHelper.java index 6a2803c84c4d..c91ae4d9ca5f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/LibCHelper.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/LibCHelper.java @@ -30,8 +30,6 @@ import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CCharPointerPointer; -import com.oracle.svm.core.util.BasedOnJDKFile; - @CLibrary(value = "libchelper", requireStatic = true, dependsOn = "java") public class LibCHelper { @CFunction(transition = Transition.NO_TRANSITION) @@ -41,18 +39,4 @@ public class LibCHelper { // Checkstyle: stop public static native CCharPointer SVM_FindJavaTZmd(CCharPointer tzMappings, int length); // Checkstyle: start - - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+13/src/java.base/unix/native/libjava/locale_str.h") - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+13/src/java.base/windows/native/libjava/locale_str.h") - public static class Locale { - @CFunction(transition = Transition.TO_NATIVE) - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+17/src/java.base/unix/native/libjava/java_props_md.c#L93-L357") - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+24/src/java.base/windows/native/libjava/java_props_md.c#L321-L713") - public static native CCharPointerPointer parseDisplayLocale(); - - @CFunction(transition = Transition.TO_NATIVE) - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+17/src/java.base/unix/native/libjava/java_props_md.c#L93-L357") - @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+24/src/java.base/windows/native/libjava/java_props_md.c#L321-L713") - public static native CCharPointerPointer parseFormatLocale(); - } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index f4346f18611f..944efd9089ed 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -1145,6 +1145,9 @@ public Boolean getValue(OptionValues values) { } }; + @Option(help = "Determines if the system locale should be used at run-time. If this is disabled, the locale 'en-US' will be used instead.", stability = OptionStability.EXPERIMENTAL, type = Expert)// + public static final HostedOptionKey UseSystemLocale = new HostedOptionKey<>(true); + @Option(help = "Dump heap to file (see HeapDumpPath) the first time the image throws java.lang.OutOfMemoryError because it ran out of Java heap.")// public static final RuntimeOptionKey HeapDumpOnOutOfMemoryError = new RuntimeOptionKey<>(false); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/locale/LocaleCHelper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/locale/LocaleCHelper.java new file mode 100644 index 000000000000..8f7e7f1974cc --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/locale/LocaleCHelper.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.c.locale; + +import static org.graalvm.nativeimage.c.function.CFunction.Transition.NO_TRANSITION; + +import org.graalvm.nativeimage.c.CContext; +import org.graalvm.nativeimage.c.constant.CConstant; +import org.graalvm.nativeimage.c.function.CFunction; +import org.graalvm.nativeimage.c.function.CLibrary; +import org.graalvm.nativeimage.c.struct.CField; +import org.graalvm.nativeimage.c.struct.CStruct; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.word.PointerBase; + +import com.oracle.svm.core.util.BasedOnJDKFile; + +@CContext(LocaleDirectives.class) +@CLibrary(value = "libchelper", requireStatic = true, dependsOn = "java") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+13/src/java.base/unix/native/libjava/locale_str.h") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+13/src/java.base/windows/native/libjava/locale_str.h") +class LocaleCHelper { + // Checkstyle: stop + @CConstant + static native int SVM_LOCALE_INITIALIZATION_SUCCEEDED(); + + @CConstant + static native int SVM_LOCALE_INITIALIZATION_OUT_OF_MEMORY(); + // Checkstyle: resume + + /** + * This method changes the process-wide locale settings and should therefore only be called + * during early startup. Calling it at a later point in time is unsafe and may result in + * crashes. + * + * @return {@link #SVM_LOCALE_INITIALIZATION_SUCCEEDED} or + * {@link #SVM_LOCALE_INITIALIZATION_OUT_OF_MEMORY}. + */ + @CFunction(value = "svm_initialize_locale", transition = NO_TRANSITION) + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+17/src/java.base/unix/native/libjava/java_props_md.c#L71-L357") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+17/src/java.base/unix/native/libjava/java_props_md.c#L436-L460") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+24/src/java.base/windows/native/libjava/java_props_md.c#L254-L713") + static native int initializeLocale(); + + @CFunction(value = "svm_get_locale", transition = NO_TRANSITION) + static native LocaleProps getLocale(); + + @CStruct(value = "svm_locale_props_t") + interface LocaleProps extends PointerBase { + @CField(value = "display_language") + CCharPointer displayLanguage(); + + @CField(value = "display_script") + CCharPointer displayScript(); + + @CField(value = "display_country") + CCharPointer displayCountry(); + + @CField(value = "display_variant") + CCharPointer displayVariant(); + + @CField(value = "format_language") + CCharPointer formatLanguage(); + + @CField(value = "format_script") + CCharPointer formatScript(); + + @CField(value = "format_country") + CCharPointer formatCountry(); + + @CField(value = "format_variant") + CCharPointer formatVariant(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/locale/LocaleData.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/locale/LocaleData.java new file mode 100644 index 000000000000..807438259ebf --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/locale/LocaleData.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.c.locale; + +public record LocaleData(String country, String displayCountry, String formatCountry, + String language, String displayLanguage, String formatLanguage, + String script, String displayScript, String formatScript, + String variant, String displayVariant, String formatVariant) { +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/locale/LocaleDirectives.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/locale/LocaleDirectives.java new file mode 100644 index 000000000000..3ebb80866723 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/locale/LocaleDirectives.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.c.locale; + +import java.util.Collections; +import java.util.List; + +import org.graalvm.nativeimage.c.CContext; + +import com.oracle.svm.core.c.ProjectHeaderFile; + +class LocaleDirectives implements CContext.Directives { + @Override + public List getHeaderFiles() { + return Collections.singletonList(ProjectHeaderFile.resolve("com.oracle.svm.native.libchelper", "include/svm_locale.h")); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/locale/LocaleSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/locale/LocaleSupport.java new file mode 100644 index 000000000000..d94ba805e736 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/c/locale/LocaleSupport.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.c.locale; + +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.c.type.CTypeConversion; +import org.graalvm.word.LocationIdentity; +import org.graalvm.word.Pointer; +import org.graalvm.word.UnsignedWord; + +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.c.CGlobalData; +import com.oracle.svm.core.c.CGlobalDataFactory; +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.headers.LibC; +import com.oracle.svm.core.jdk.SystemPropertiesSupport; +import com.oracle.svm.core.jdk.UserSystemProperty; +import com.oracle.svm.core.util.BasedOnJDKFile; +import com.oracle.svm.core.util.VMError; + +import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.nodes.PauseNode; +import jdk.graal.compiler.word.Word; + +/** + * The locale is a process-wide setting. This class uses {@link LocaleCHelper C code} to initialize + * the system-specific locale at run-time. For this, the C code needs to change the locale multiple + * times, which is a dangerous operation as it is not necessarily multi-threading safe. Therefore, + * the locale initialization needs to be executed once per process during early startup. + *

+ * In some cases, such as libgraal, this C code must not be executed as it may interfere with other + * threads that are already running in the same process. If the option + * {@link SubstrateOptions#UseSystemLocale} is disabled, the locale 'en-US' is used instead of the + * system-specific locale. + *

+ * Note that the JavaDoc of {@link java.util.Locale} explains commonly used terms such as script, + * display, format, variant, and extensions. + */ +@AutomaticallyRegisteredImageSingleton +public class LocaleSupport { + private static final CGlobalData STATE = CGlobalDataFactory.createWord(State.UNINITIALIZED); + + private LocaleData locale; + + @Fold + public static LocaleSupport singleton() { + return ImageSingletons.lookup(LocaleSupport.class); + } + + @Fold + static boolean isSystemSpecificLocaleSupported() { + return LibC.isSupported() && SubstrateOptions.UseSystemLocale.getValue(); + } + + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + public static void initialize() { + if (!isSystemSpecificLocaleSupported()) { + return; + } + + Pointer statePtr = STATE.get(); + UnsignedWord value = statePtr.compareAndSwapWord(0, State.UNINITIALIZED, State.INITIALIZING, LocationIdentity.ANY_LOCATION); + if (value == State.UNINITIALIZED) { + int result = LocaleCHelper.initializeLocale(); + if (result == LocaleCHelper.SVM_LOCALE_INITIALIZATION_SUCCEEDED()) { + statePtr.writeWordVolatile(0, State.SUCCESS); + } else if (result == LocaleCHelper.SVM_LOCALE_INITIALIZATION_OUT_OF_MEMORY()) { + statePtr.writeWordVolatile(0, State.OUT_OF_MEMORY); + } else { + throw VMError.shouldNotReachHere("LocaleCHelper.initializeLocale() returned an unexpected result."); + } + } else { + while (value == State.INITIALIZING) { + PauseNode.pause(); + value = statePtr.readWordVolatile(0, LocationIdentity.ANY_LOCATION); + } + } + } + + public static void checkForError() { + if (!isSystemSpecificLocaleSupported()) { + return; + } + + UnsignedWord state = STATE.get().readWord(0); + if (state != State.SUCCESS) { + if (state == State.OUT_OF_MEMORY) { + throw new OutOfMemoryError("Not enough native memory to initialize the locale support."); + } + throw VMError.shouldNotReachHere("Locale support had an unexpected state after initialization."); + } + } + + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+5/src/java.base/share/classes/jdk/internal/util/SystemProps.java#L131-L141") + public synchronized LocaleData getLocale() { + /* This method is only called a few times, so need to optimize the locking. */ + if (locale != null) { + return locale; + } + + if (!isSystemSpecificLocaleSupported()) { + String country = "US"; + String language = "en"; + locale = new LocaleData(country, null, null, language, null, null, null, null, null, null, null, null); + return locale; + } + + assert STATE.get().readWord(0) == State.SUCCESS; + + /* Convert all C values to Java strings. */ + LocaleCHelper.LocaleProps props = LocaleCHelper.getLocale(); + String defaultCountry = CTypeConversion.toJavaString(props.displayCountry()); + String defaultCountryDisplay = CTypeConversion.toJavaString(props.displayCountry()); + String defaultCountryFormat = CTypeConversion.toJavaString(props.formatCountry()); + String defaultLanguage = CTypeConversion.toJavaString(props.displayLanguage()); + String defaultLanguageDisplay = CTypeConversion.toJavaString(props.displayLanguage()); + String defaultLanguageFormat = CTypeConversion.toJavaString(props.formatLanguage()); + String defaultScript = CTypeConversion.toJavaString(props.displayScript()); + String defaultScriptDisplay = CTypeConversion.toJavaString(props.displayScript()); + String defaultScriptFormat = CTypeConversion.toJavaString(props.formatScript()); + String defaultVariant = CTypeConversion.toJavaString(props.displayVariant()); + String defaultVariantDisplay = CTypeConversion.toJavaString(props.displayVariant()); + String defaultVariantFormat = CTypeConversion.toJavaString(props.formatVariant()); + + /* The code below is similar to the JDK class SystemProps. */ + LocaleAspect country = getLocaleAspect(UserSystemProperty.COUNTRY, UserSystemProperty.COUNTRY_DISPLAY, UserSystemProperty.COUNTRY_FORMAT, + defaultCountry, defaultCountryDisplay, defaultCountryFormat); + LocaleAspect language = getLocaleAspect(UserSystemProperty.LANGUAGE, UserSystemProperty.LANGUAGE_DISPLAY, UserSystemProperty.LANGUAGE_FORMAT, + defaultLanguage, defaultLanguageDisplay, defaultLanguageFormat); + LocaleAspect script = getLocaleAspect(UserSystemProperty.SCRIPT, UserSystemProperty.SCRIPT_DISPLAY, UserSystemProperty.SCRIPT_FORMAT, + defaultScript, defaultScriptDisplay, defaultScriptFormat); + LocaleAspect variant = getLocaleAspect(UserSystemProperty.VARIANT, UserSystemProperty.VARIANT_DISPLAY, UserSystemProperty.VARIANT_FORMAT, + defaultVariant, defaultVariantDisplay, defaultVariantFormat); + + locale = new LocaleData(country.base, country.display, country.format, language.base, language.display, language.format, script.base, script.display, script.format, variant.base, + variant.display, variant.format); + return locale; + } + + /** + * If a locale system property is set at image build-time or as a command-line option at + * run-time (-D...), then we don't want to use the default value that the C code determined. + */ + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+5/src/java.base/share/classes/jdk/internal/util/SystemProps.java#L178-L209") + private static LocaleAspect getLocaleAspect(String baseKey, String displayKey, String formatKey, + String defaultBase, String defaultDisplay, String defaultFormat) { + /* + * This method is only called once, when the first locale-specific lazy system property is + * initialized. All locale system properties are initialized lazily, so we can be sure that + * the accessed system properties below only have an initial value if they were set at image + * build-time or from a command-line option during early VM startup. + */ + String base = SystemPropertiesSupport.singleton().getInitialProperty(baseKey, false); + String display = SystemPropertiesSupport.singleton().getInitialProperty(displayKey, false); + String format = SystemPropertiesSupport.singleton().getInitialProperty(formatKey, false); + if (base == null) { + base = defaultBase; + if (display == null && defaultDisplay != null && !defaultDisplay.equals(base)) { + display = defaultDisplay; + } + if (format == null && defaultFormat != null && !defaultFormat.equals(base)) { + format = defaultFormat; + } + } + return new LocaleAspect(base, display, format); + } + + private record LocaleAspect(String base, String display, String format) { + } + + private static final class State { + static final UnsignedWord UNINITIALIZED = Word.unsigned(0); + static final UnsignedWord INITIALIZING = Word.unsigned(1); + static final UnsignedWord SUCCESS = Word.unsigned(2); + static final UnsignedWord OUT_OF_MEMORY = Word.unsigned(3); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java index 204644fef217..996aa1a01ddb 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/CEntryPointSnippets.java @@ -67,6 +67,7 @@ import com.oracle.svm.core.c.function.CEntryPointCreateIsolateParameters; import com.oracle.svm.core.c.function.CEntryPointErrors; import com.oracle.svm.core.c.function.CEntryPointNativeFunctions; +import com.oracle.svm.core.c.locale.LocaleSupport; import com.oracle.svm.core.code.CodeInfoTable; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.container.Container; @@ -291,6 +292,8 @@ private static int createIsolate(CEntryPointCreateIsolateParameters providedPara layeredPatchHeapRelativeRelocations(); } + LocaleSupport.initialize(); + CEntryPointCreateIsolateParameters parameters = providedParameters; if (parameters.isNull() || parameters.version() < 1) { parameters = StackValue.get(CEntryPointCreateIsolateParameters.class); @@ -386,6 +389,11 @@ private static int initializeIsolateInterruptibly0(CEntryPointCreateIsolateParam @NeverInline("GR-24649") private static int initializeIsolateInterruptibly1(CEntryPointCreateIsolateParameters parameters) { + /* Initialize the isolate id (the id is needed for isolate teardown). */ + long initStateAddr = FIRST_ISOLATE_INIT_STATE.get().rawValue(); + boolean firstIsolate = Unsafe.getUnsafe().compareAndSetInt(null, initStateAddr, FirstIsolateInitStates.UNINITIALIZED, FirstIsolateInitStates.IN_PROGRESS); + Isolates.assignIsolateId(firstIsolate); + /* * Initialize the physical memory size. This must be done as early as possible because we * must not trigger GC before PhysicalMemory is initialized. @@ -400,11 +408,12 @@ private static int initializeIsolateInterruptibly1(CEntryPointCreateIsolateParam VMOperationControl.startVMOperationThread(); } - long initStateAddr = FIRST_ISOLATE_INIT_STATE.get().rawValue(); - boolean firstIsolate = Unsafe.getUnsafe().compareAndSetInt(null, initStateAddr, FirstIsolateInitStates.UNINITIALIZED, FirstIsolateInitStates.IN_PROGRESS); - - Isolates.assignIsolateId(firstIsolate); + /* + * Before this point, only limited error handling is possible because the isolate is not + * sufficiently initialized (i.e., we can't tear it down properly). + */ Isolates.assignStartTime(); + LocaleSupport.checkForError(); if (!firstIsolate) { int state = Unsafe.getUnsafe().getInt(initStateAddr); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemPropertiesSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemPropertiesSupport.java index 0a7cb99d97c3..11acad8fd26c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemPropertiesSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemPropertiesSupport.java @@ -24,8 +24,6 @@ */ package com.oracle.svm.core.jdk; -import static java.util.Locale.Category.DISPLAY; -import static java.util.Locale.Category.FORMAT; import static jdk.graal.compiler.nodes.extended.MembarNode.FenceKind.STORE_STORE; import java.util.ArrayList; @@ -41,19 +39,15 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.nativeimage.c.type.CCharPointerPointer; -import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.nativeimage.impl.RuntimeSystemPropertiesSupport; -import com.oracle.svm.core.LibCHelper; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.VM; +import com.oracle.svm.core.c.locale.LocaleSupport; import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.headers.LibCSupport; import com.oracle.svm.core.util.VMError; import jdk.graal.compiler.api.replacements.Fold; -import jdk.graal.compiler.debug.GraalError; import jdk.graal.compiler.nodes.extended.MembarNode; /** @@ -95,13 +89,6 @@ public abstract class SystemPropertiesSupport implements RuntimeSystemProperties "java.vm.specification.version" }; - /* The list of field positions in locale_props_t (see locale_str.h). */ - private static final int LANGUAGE_POSITION = 0; - private static final int SCRIPT_POSITION = LANGUAGE_POSITION + 1; - private static final int COUNTRY_POSITION = SCRIPT_POSITION + 1; - private static final int VARIANT_POSITION = COUNTRY_POSITION + 1; - private static final int EXTENSION_POSITION = VARIANT_POSITION + 1; - /** System properties that are computed at run time on first access. */ private final Map lazySystemProperties = new HashMap<>(); /** @@ -157,21 +144,18 @@ protected SystemPropertiesSupport() { lazyProperties.add(new LazySystemProperty("java.io.tmpdir", this::javaIoTmpdirValue)); lazyProperties.add(new LazySystemProperty("java.library.path", this::javaLibraryPathValue)); lazyProperties.add(new LazySystemProperty("os.version", this::osVersionValue)); - lazyProperties.add(new LazySystemProperty(UserSystemProperty.LANGUAGE, () -> postProcessLocale(UserSystemProperty.LANGUAGE, parseLocale(DISPLAY).language(), null))); - lazyProperties.add(new LazySystemProperty(UserSystemProperty.LANGUAGE_DISPLAY, () -> postProcessLocale(UserSystemProperty.LANGUAGE, parseLocale(DISPLAY).language(), DISPLAY))); - lazyProperties.add(new LazySystemProperty(UserSystemProperty.LANGUAGE_FORMAT, () -> postProcessLocale(UserSystemProperty.LANGUAGE, parseLocale(FORMAT).language(), FORMAT))); - lazyProperties.add(new LazySystemProperty(UserSystemProperty.SCRIPT, () -> postProcessLocale(UserSystemProperty.SCRIPT, parseLocale(DISPLAY).script(), null))); - lazyProperties.add(new LazySystemProperty(UserSystemProperty.SCRIPT_DISPLAY, () -> postProcessLocale(UserSystemProperty.SCRIPT, parseLocale(DISPLAY).script(), DISPLAY))); - lazyProperties.add(new LazySystemProperty(UserSystemProperty.SCRIPT_FORMAT, () -> postProcessLocale(UserSystemProperty.SCRIPT, parseLocale(FORMAT).script(), FORMAT))); - lazyProperties.add(new LazySystemProperty(UserSystemProperty.COUNTRY, () -> postProcessLocale(UserSystemProperty.COUNTRY, parseLocale(DISPLAY).country(), null))); - lazyProperties.add(new LazySystemProperty(UserSystemProperty.COUNTRY_DISPLAY, () -> postProcessLocale(UserSystemProperty.COUNTRY, parseLocale(DISPLAY).country(), DISPLAY))); - lazyProperties.add(new LazySystemProperty(UserSystemProperty.COUNTRY_FORMAT, () -> postProcessLocale(UserSystemProperty.COUNTRY, parseLocale(FORMAT).country(), FORMAT))); - lazyProperties.add(new LazySystemProperty(UserSystemProperty.VARIANT, () -> postProcessLocale(UserSystemProperty.VARIANT, parseLocale(FORMAT).country(), FORMAT))); - lazyProperties.add(new LazySystemProperty(UserSystemProperty.VARIANT_DISPLAY, () -> postProcessLocale(UserSystemProperty.VARIANT, parseLocale(DISPLAY).variant(), DISPLAY))); - lazyProperties.add(new LazySystemProperty(UserSystemProperty.VARIANT_FORMAT, () -> postProcessLocale(UserSystemProperty.VARIANT, parseLocale(FORMAT).variant(), FORMAT))); - lazyProperties.add(new LazySystemProperty(UserSystemProperty.EXTENSIONS, () -> postProcessLocale(UserSystemProperty.EXTENSIONS, parseLocale(DISPLAY).extensions(), null))); - lazyProperties.add(new LazySystemProperty(UserSystemProperty.EXTENSIONS_DISPLAY, () -> postProcessLocale(UserSystemProperty.EXTENSIONS, parseLocale(DISPLAY).extensions(), DISPLAY))); - lazyProperties.add(new LazySystemProperty(UserSystemProperty.EXTENSIONS_FORMAT, () -> postProcessLocale(UserSystemProperty.EXTENSIONS, parseLocale(FORMAT).extensions(), FORMAT))); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.LANGUAGE, () -> LocaleSupport.singleton().getLocale().language())); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.LANGUAGE_DISPLAY, () -> LocaleSupport.singleton().getLocale().displayLanguage())); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.LANGUAGE_FORMAT, () -> LocaleSupport.singleton().getLocale().formatLanguage())); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.SCRIPT, () -> LocaleSupport.singleton().getLocale().script())); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.SCRIPT_DISPLAY, () -> LocaleSupport.singleton().getLocale().displayScript())); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.SCRIPT_FORMAT, () -> LocaleSupport.singleton().getLocale().formatScript())); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.COUNTRY, () -> LocaleSupport.singleton().getLocale().country())); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.COUNTRY_DISPLAY, () -> LocaleSupport.singleton().getLocale().displayCountry())); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.COUNTRY_FORMAT, () -> LocaleSupport.singleton().getLocale().formatCountry())); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.VARIANT, () -> LocaleSupport.singleton().getLocale().variant())); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.VARIANT_DISPLAY, () -> LocaleSupport.singleton().getLocale().displayVariant())); + lazyProperties.add(new LazySystemProperty(UserSystemProperty.VARIANT_FORMAT, () -> LocaleSupport.singleton().getLocale().formatVariant())); String targetName = System.getProperty("svm.targetName"); if (targetName != null) { @@ -365,73 +349,6 @@ protected String javaLibraryPathValue() { return ""; } - public record LocaleEncoding(String language, String script, String country, String variant, String extensions) { - private LocaleEncoding(CCharPointerPointer properties) { - this(fromCStringArray(properties, LANGUAGE_POSITION), - fromCStringArray(properties, SCRIPT_POSITION), - fromCStringArray(properties, COUNTRY_POSITION), - fromCStringArray(properties, VARIANT_POSITION), - fromCStringArray(properties, EXTENSION_POSITION)); - } - - private static String fromCStringArray(CCharPointerPointer cString, int index) { - if (cString.isNull()) { - return null; - } - return CTypeConversion.toJavaString(cString.read(index)); - } - } - - private LocaleEncoding displayLocale; - - private LocaleEncoding formatLocale; - - protected LocaleEncoding parseLocale(Locale.Category category) { - if (!ImageSingletons.contains(LibCSupport.class)) { - /* If native calls are not supported, just return fixed values. */ - return new LocaleEncoding("en", "", "US", "", ""); - } - switch (category) { - case DISPLAY -> { - if (displayLocale == null) { - displayLocale = new LocaleEncoding(LibCHelper.Locale.parseDisplayLocale()); - } - return displayLocale; - } - case FORMAT -> { - if (formatLocale == null) { - formatLocale = new LocaleEncoding(LibCHelper.Locale.parseFormatLocale()); - } - return formatLocale; - } - default -> throw new GraalError("Unknown locale category: " + category + "."); - } - } - - private String postProcessLocale(String base, String value, Locale.Category category) { - if (category == null) { - /* user.xxx property */ - String baseValue = null; - if (value != null) { - initializeProperty(base, value); - baseValue = value; - } - return baseValue; - } - switch (category) { - case DISPLAY, FORMAT -> { - /* user.xxx.(display|format) property */ - String baseValue = getCurrentProperty(base); - if (baseValue == null && value != null) { - initializeProperty(base + '.' + category.name().toLowerCase(Locale.ROOT), value); - return value; - } - return null; - } - default -> throw new GraalError("Unknown locale category: " + category + "."); - } - } - private static class LazySystemProperty { private final String key; private final Supplier supplier; diff --git a/substratevm/src/com.oracle.svm.native.libchelper/include/svm_locale.h b/substratevm/src/com.oracle.svm.native.libchelper/include/svm_locale.h new file mode 100644 index 000000000000..09a701066508 --- /dev/null +++ b/substratevm/src/com.oracle.svm.native.libchelper/include/svm_locale.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +const int SVM_LOCALE_INITIALIZATION_USE_DEFAULT = 0; +const int SVM_LOCALE_INITIALIZATION_SUCCEEDED = 1; +const int SVM_LOCALE_INITIALIZATION_OUT_OF_MEMORY = 2; + +typedef struct { + char* format_language; + char* format_script; + char* format_country; + char* format_variant; + + char* display_language; + char* display_script; + char* display_country; + char* display_variant; +} svm_locale_props_t; + diff --git a/substratevm/src/com.oracle.svm.native.libchelper/src/locale.c b/substratevm/src/com.oracle.svm.native.libchelper/src/locale.c index 21b33919b87c..25889390f680 100644 --- a/substratevm/src/com.oracle.svm.native.libchelper/src/locale.c +++ b/substratevm/src/com.oracle.svm.native.libchelper/src/locale.c @@ -27,6 +27,7 @@ #include #include #include +#include "svm_locale.h" #include "locale_str.h" #ifdef _WIN64 @@ -37,6 +38,10 @@ #define SNAMESIZE 86 // max number of chars for LOCALE_SNAME is 85 #endif +/* Process-wide state. */ +static svm_locale_props_t _svm_locale_sprops = {0}; + +#ifndef _WIN64 /* Take an array of string pairs (map of key->value) and a string (key). * Examine each pair in the map to see if the first string (key) matches the * string. If so, store the second string of the pair (value) in the value and @@ -54,37 +59,19 @@ static int mapLookup(char* map[], const char* key, char** value) { return 0; } -static locale_props_t *allocateJavaProps() { - locale_props_t* sprops = malloc(sizeof(locale_props_t)); - sprops->language = NULL; - sprops->country = NULL; - sprops->variant = NULL; - sprops->script = NULL; - sprops->extensions = NULL; - return sprops; -} - /* * Copied and adapted from java.base/unix/native/libjava/java_props_md.c. We are not calling * these functions via static linking of libjava, because we only need a small subset of the * functionalities that exist in the original method. */ -static int ParseLocale(locale_props_t * sprops, int cat) { - setlocale(LC_ALL, ""); - char **std_language = &(sprops->language); - char **std_country = &(sprops->country); - char **std_variant = &(sprops->variant); - char **std_script = &(sprops->script); - char **std_extensions = &(sprops->extensions); - +static int ParseLocale(int cat, char ** std_language, char ** std_script, + char ** std_country, char ** std_variant) { char *temp = NULL; char *language = NULL, *country = NULL, *variant = NULL, *encoding = NULL; char *p, *encoding_variant, *old_temp, *old_ev; char *lc; - /* Query the locale set for the category */ - #ifdef MACOSX lc = setupMacOSXLocale(cat); // malloc'd memory, need to free #else @@ -93,7 +80,7 @@ static int ParseLocale(locale_props_t * sprops, int cat) { #ifndef __linux__ if (lc == NULL) { - return 0; + return SVM_LOCALE_INITIALIZATION_USE_DEFAULT; } temp = malloc(strlen(lc) + 1); @@ -101,8 +88,7 @@ static int ParseLocale(locale_props_t * sprops, int cat) { #ifdef MACOSX free(lc); // malloced memory #endif - // JNU_ThrowOutOfMemoryError(env, NULL); - return 0; + return SVM_LOCALE_INITIALIZATION_OUT_OF_MEMORY; } if (cat == LC_CTYPE) { @@ -129,8 +115,7 @@ static int ParseLocale(locale_props_t * sprops, int cat) { temp = malloc(strlen(lc) + 1); if (temp == NULL) { - // JNU_ThrowOutOfMemoryError(env, NULL); - return 0; + return SVM_LOCALE_INITIALIZATION_OUT_OF_MEMORY; } #endif @@ -162,8 +147,7 @@ static int ParseLocale(locale_props_t * sprops, int cat) { encoding_variant = malloc(strlen(temp)+1); if (encoding_variant == NULL) { free(temp); - // JNU_ThrowOutOfMemoryError(env, NULL); - return 0; + return SVM_LOCALE_INITIALIZATION_OUT_OF_MEMORY; } if ((p = strchr(temp, '.')) != NULL) { @@ -182,8 +166,7 @@ static int ParseLocale(locale_props_t * sprops, int cat) { if (temp == NULL) { free(old_temp); free(encoding_variant); - // JNU_ThrowOutOfMemoryError(env, NULL); - return 0; + return SVM_LOCALE_INITIALIZATION_OUT_OF_MEMORY; } strcpy(temp, p); old_ev = encoding_variant; @@ -191,8 +174,7 @@ static int ParseLocale(locale_props_t * sprops, int cat) { if (encoding_variant == NULL) { free(old_ev); free(temp); - // JNU_ThrowOutOfMemoryError(env, NULL); - return 0; + return SVM_LOCALE_INITIALIZATION_OUT_OF_MEMORY; } // check the "encoding_variant" again, if any. if ((p = strchr(temp, '.')) != NULL) { @@ -225,8 +207,7 @@ static int ParseLocale(locale_props_t * sprops, int cat) { *std_language = malloc(strlen(language)+1); if (*std_language == NULL) { free(encoding_variant); - // JNU_ThrowOutOfMemoryError(env, NULL); - return 0; + return SVM_LOCALE_INITIALIZATION_OUT_OF_MEMORY; } strcpy(*std_language, language); } @@ -238,8 +219,7 @@ static int ParseLocale(locale_props_t * sprops, int cat) { *std_country = malloc(strlen(country)+1); if (*std_country == NULL) { free(encoding_variant); - // JNU_ThrowOutOfMemoryError(env, NULL); - return 0; + return SVM_LOCALE_INITIALIZATION_OUT_OF_MEMORY; } strcpy(*std_country, country); } @@ -261,16 +241,47 @@ static int ParseLocale(locale_props_t * sprops, int cat) { free(temp); free(encoding_variant); - return 1; + return SVM_LOCALE_INITIALIZATION_SUCCEEDED; } -#ifdef _WIN64 +/* + * This method only returns an error code. If an error occurs in this method, we throw an + * exception at a later point during isolate initialization. + */ +int svm_initialize_locale() { + setlocale(LC_ALL, ""); + + int result = ParseLocale(LC_CTYPE, + &(_svm_locale_sprops.format_language), + &(_svm_locale_sprops.format_script), + &(_svm_locale_sprops.format_country), + &(_svm_locale_sprops.format_variant)); + + if (result == SVM_LOCALE_INITIALIZATION_SUCCEEDED) { + result = ParseLocale(LC_MESSAGES, + &(_svm_locale_sprops.display_language), + &(_svm_locale_sprops.display_script), + &(_svm_locale_sprops.display_country), + &(_svm_locale_sprops.display_variant)); + } + + if (result == SVM_LOCALE_INITIALIZATION_USE_DEFAULT) { + _svm_locale_sprops.display_language = "en"; + return SVM_LOCALE_INITIALIZATION_SUCCEEDED; + } + + return result; +} + +#else // _WIN64 + /* * Copied and adapted from java.base/windows/native/libjava/java_props_md.c. We are not calling * these functions via static linking of libjava, because we only need a small subset of the * functionalities that exist in the original method. */ -static int SetupI18nProps(LCID lcid, char** language, char** script, char** country, char** variant) { +static int SetupI18nProps(LCID lcid, char** language, char** script, char** country, + char** variant) { /* script */ char tmp[SNAMESIZE]; *script = malloc(PROPSIZE); @@ -330,7 +341,7 @@ static int SetupI18nProps(LCID lcid, char** language, char** script, char** coun return 1; } -void PopulateJavaProperties(locale_props_t *sprops, int isFormatLocale) { +int svm_initialize_locale() { /* * query the system for the current system default locale * (which is a Windows LCID value), @@ -351,34 +362,23 @@ void PopulateJavaProperties(locale_props_t *sprops, int isFormatLocale) { userDefaultUILCID = userDefaultLCID; } - SetupI18nProps(isFormatLocale ? userDefaultLCID : userDefaultUILCID, - &(sprops->language), - &(sprops->script), - &(sprops->country), - &(sprops->variant)); + SetupI18nProps(userDefaultLCID, + &_svm_locale_sprops.format_language, + &_svm_locale_sprops.format_script, + &_svm_locale_sprops.format_country, + &_svm_locale_sprops.format_variant); + SetupI18nProps(userDefaultUILCID, + &_svm_locale_sprops.display_language, + &_svm_locale_sprops.display_script, + &_svm_locale_sprops.display_country, + &_svm_locale_sprops.display_variant); + + /* HotSpot ignores the return value of the methods above, so we do the same. */ + return SVM_LOCALE_INITIALIZATION_SUCCEEDED; } #endif -locale_props_t* parseDisplayLocale() { - locale_props_t *sprops = allocateJavaProps(); -#ifndef _WIN64 - int result = ParseLocale(sprops, LC_MESSAGES); - if (!result) { - sprops->language = "en"; - } -#else - PopulateJavaProperties(sprops, 0); -#endif - return sprops; +svm_locale_props_t* svm_get_locale() { + return &_svm_locale_sprops; } -locale_props_t* parseFormatLocale() { - locale_props_t *sprops = allocateJavaProps(); -#ifndef _WIN64 - int result = ParseLocale(sprops, LC_CTYPE); -#else - PopulateJavaProperties(sprops, 1); -#endif - - return sprops; -} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/ProcessPropertiesTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/ProcessPropertiesTest.java index 8b4fe7b52ab2..67ee80030c64 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/ProcessPropertiesTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/ProcessPropertiesTest.java @@ -42,12 +42,9 @@ public void testGetPID() { Assert.assertTrue("Invalid pid.", pid > 0); } - /** - * Test ProcessProperties.setLocale(). See - * setLocale - * specification for details. - */ + /** {@link ProcessProperties#setLocale} is deprecated and was only ever supported on Linux. */ @Test + @SuppressWarnings("deprecation") public void testSetLocale() { /* Get the default locale. */ String before = ProcessProperties.setLocale("LC_ALL", null);