diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java index e53f70cb9b43..6861a5c02583 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/FutureDefaultsOptions.java @@ -54,6 +54,8 @@ public class FutureDefaultsOptions { private static final String NONE_NAME = "none"; private static final String RUN_TIME_INITIALIZE_JDK_NAME = "run-time-initialized-jdk"; + public static final String RUN_TIME_INITIALIZE_JDK_REASON = "Initialize JDK classes at run time (--" + OPTION_NAME + " includes " + RUN_TIME_INITIALIZE_JDK_NAME + ")"; + private static final Set ALL_VALUES = Set.of(RUN_TIME_INITIALIZE_JDK_NAME, ALL_NAME, NONE_NAME); private static String futureDefaultsAllValues() { @@ -66,7 +68,7 @@ private static String futureDefaultsAllValues() { @APIOption(name = OPTION_NAME, defaultValue = DEFAULT_NAME) // @Option(help = "file:doc-files/FutureDefaultsHelp.txt", type = OptionType.User) // - public static final HostedOptionKey FutureDefaults = new HostedOptionKey<>( + static final HostedOptionKey FutureDefaults = new HostedOptionKey<>( AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); private static EconomicSet futureDefaults; 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 008d59d433c7..a1ffb6f9d56e 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -142,7 +142,7 @@ public void afterRegistration(AfterRegistrationAccess access) { } } -@TargetClass(java.nio.file.spi.FileSystemProvider.class) +@TargetClass(value = java.nio.file.spi.FileSystemProvider.class, onlyWith = JDKInitializedAtBuildTime.class) final class Target_java_nio_file_spi_FileSystemProvider { @Substitute public static List installedProviders() { @@ -171,7 +171,7 @@ public static List installedProviders() { * c) Allow UnixFileSystem in the image heap and recompute state at run time on first acccess. This * approach is implemented here. */ -@TargetClass(className = "sun.nio.fs.UnixFileSystem") +@TargetClass(className = "sun.nio.fs.UnixFileSystem", onlyWith = JDKInitializedAtBuildTime.class) @Platforms({Platform.LINUX.class, Platform.DARWIN.class}) final class Target_sun_nio_fs_UnixFileSystem { @@ -224,12 +224,12 @@ final class Target_sun_nio_fs_UnixFileSystem { native void originalConstructor(Target_sun_nio_fs_UnixFileSystemProvider p, String dir); } -@TargetClass(className = "sun.nio.fs.UnixFileSystemProvider") +@TargetClass(className = "sun.nio.fs.UnixFileSystemProvider", onlyWith = JDKInitializedAtBuildTime.class) @Platforms({Platform.LINUX.class, Platform.DARWIN.class}) final class Target_sun_nio_fs_UnixFileSystemProvider { } -@TargetClass(className = "sun.nio.fs.UnixPath") +@TargetClass(className = "sun.nio.fs.UnixPath", onlyWith = JDKInitializedAtBuildTime.class) @Platforms({Platform.LINUX.class, Platform.DARWIN.class}) final class Target_sun_nio_fs_UnixPath { } @@ -403,7 +403,7 @@ private static synchronized void reinitialize(Target_sun_nio_fs_WindowsFileSyste } } -@TargetClass(className = "java.io.UnixFileSystem") +@TargetClass(className = "java.io.UnixFileSystem", onlyWith = JDKInitializedAtBuildTime.class) @Platforms({Platform.LINUX.class, Platform.DARWIN.class}) final class Target_java_io_UnixFileSystem { @@ -412,7 +412,7 @@ final class Target_java_io_UnixFileSystem { private String userDir; } -@TargetClass(className = "java.io.FileSystem") +@TargetClass(className = "java.io.FileSystem", onlyWith = JDKInitializedAtBuildTime.class) final class Target_java_io_FileSystem { @Alias diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKInitializedAtBuildTime.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKInitializedAtBuildTime.java new file mode 100644 index 000000000000..5633833cbfcd --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKInitializedAtBuildTime.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025, 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; + +import java.util.function.BooleanSupplier; + +import com.oracle.svm.core.FutureDefaultsOptions; + +public class JDKInitializedAtBuildTime implements BooleanSupplier { + @Override + public boolean getAsBoolean() { + return !FutureDefaultsOptions.isJDKInitializedAtRunTime(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKInitializedAtRunTime.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKInitializedAtRunTime.java new file mode 100644 index 000000000000..58cd8f77d6a9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JDKInitializedAtRunTime.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025, 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; + +import java.util.function.BooleanSupplier; + +import com.oracle.svm.core.FutureDefaultsOptions; + +public class JDKInitializedAtRunTime implements BooleanSupplier { + @Override + public boolean getAsBoolean() { + return FutureDefaultsOptions.isJDKInitializedAtRunTime(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/FileSystemProviderRuntimeInitSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/FileSystemProviderRuntimeInitSupport.java new file mode 100644 index 000000000000..da9694a9170d --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/runtimeinit/FileSystemProviderRuntimeInitSupport.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2025, 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.runtimeinit; + +import java.nio.file.FileSystem; +import java.nio.file.spi.FileSystemProvider; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.InjectAccessors; +import com.oracle.svm.core.annotate.TargetClass; +import com.oracle.svm.core.jdk.JDKInitializedAtRunTime; +import com.oracle.svm.core.util.BasedOnJDKFile; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.ReflectionUtil; + +/** + * This file contains substitutions that are required for initializing {@link FileSystemProvider} at + * image run time. Other related functionality (general and build time initialization) can be found + * in {@link com.oracle.svm.core.jdk.FileSystemProviderSupport}. + * + * @see JDKInitializedAtRunTime + * @see com.oracle.svm.core.jdk.FileSystemProviderSupport + */ +final class FileSystemProviderRuntimeInitSupport { +} + +// java.io + +@TargetClass(className = "java.io.FileSystem", onlyWith = JDKInitializedAtRunTime.class) +final class Target_java_io_FileSystem_RunTime { +} + +@TargetClass(className = "java.io.File", onlyWith = JDKInitializedAtRunTime.class) +@SuppressWarnings("unused") +final class Target_java_io_File_RunTime { + @Alias // + @InjectAccessors(DefaultFileSystemAccessor.class) // + private static Target_java_io_FileSystem_RunTime FS; +} + +@TargetClass(className = "java.io.DefaultFileSystem", onlyWith = JDKInitializedAtRunTime.class) +final class Target_java_io_DefaultFileSystem_RunTime { + @Alias + static native Target_java_io_FileSystem_RunTime getFileSystem(); +} + +/** + * Holds the default java.io file system. Initialized at run time via + * {@code JDKInitializationFeature}. This cache is needed because + * {@link Target_java_io_DefaultFileSystem_RunTime#getFileSystem()} creates a new instance for every + * time. In the JDK, this method is called only once. + */ +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+25/src/java.base/unix/classes/java/io/DefaultFileSystem.java#L39-L41") +@BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+25/src/java.base/windows/classes/java/io/DefaultFileSystem.java#L39-L41") +class DefaultFileSystemHolder { + static final Object FS; + static { + if (SubstrateUtil.HOSTED) { + /* + * Should be unused, but layered images might want to initialize it during image build. + * We set it to the real default file system instead of null to guard against + * unintentional usages. In run-time init mode, we don't allow FileSystems in the image + * heap, so we would fail the image build with an exception if it happens, which helps + * to detect problems. + */ + var defaultFileSystem = ReflectionUtil.lookupClass("java.io.DefaultFileSystem"); + var getFileSystem = ReflectionUtil.lookupMethod(defaultFileSystem, "getFileSystem"); + FS = ReflectionUtil.invokeMethod(getFileSystem, null); + } else { + FS = Target_java_io_DefaultFileSystem_RunTime.getFileSystem(); + } + } +} + +class DefaultFileSystemAccessor { + @SuppressWarnings("unused") + static Target_java_io_FileSystem_RunTime get() { + VMError.guarantee(DefaultFileSystemHolder.FS != null, "DefaultFileSystemHolder.FS is null"); + return SubstrateUtil.cast(DefaultFileSystemHolder.FS, Target_java_io_FileSystem_RunTime.class); + } +} + +// sun.nio.fs + +@TargetClass(className = "sun.nio.fs.DefaultFileSystemProvider", onlyWith = JDKInitializedAtRunTime.class) +final class Target_sun_nio_fs_DefaultFileSystemProvider_RunTime { + @Alias + static native FileSystem theFileSystem(); +} + +@TargetClass(className = "sun.nio.fs.UnixFileSystem", onlyWith = JDKInitializedAtRunTime.class) +@Platforms({Platform.LINUX.class, Platform.DARWIN.class}) +final class Target_sun_nio_fs_UnixFileSystem_RunTime { +} + +@TargetClass(className = "sun.nio.fs.UnixPath", onlyWith = JDKInitializedAtRunTime.class) +@Platforms({Platform.LINUX.class, Platform.DARWIN.class}) +final class Target_sun_nio_fs_UnixPath_RunTime { + @Alias // + @InjectAccessors(UnixFileSystemAccessor.class) // + private Target_sun_nio_fs_UnixFileSystem_RunTime fs; +} + +@SuppressWarnings("unused") +class UnixFileSystemAccessor { + static Target_sun_nio_fs_UnixFileSystem_RunTime get(Target_sun_nio_fs_UnixPath_RunTime that) { + FileSystem theFileSystem = Target_sun_nio_fs_DefaultFileSystemProvider_RunTime.theFileSystem(); + VMError.guarantee(theFileSystem != null, "DefaultFileSystemProvider.theFileSystem() is null"); + return SubstrateUtil.cast(theFileSystem, Target_sun_nio_fs_UnixFileSystem_RunTime.class); + } + + static void set(Target_sun_nio_fs_UnixPath_RunTime that, Target_sun_nio_fs_UnixFileSystem_RunTime value) { + /* + * `value` should always be DefaultFileSystemProvider.INSTANCE.theFileSystem() but we cannot + * check that here because it would introduce a class initialization cycle. + */ + } +} + +@TargetClass(className = "sun.nio.fs.WindowsFileSystem", onlyWith = JDKInitializedAtRunTime.class) +@Platforms(Platform.WINDOWS.class) +final class Target_sun_nio_fs_WindowsFileSystem_RunTime { +} + +@TargetClass(className = "sun.nio.fs.WindowsPath", onlyWith = JDKInitializedAtRunTime.class) +@Platforms(Platform.WINDOWS.class) +final class Target_sun_nio_fs_WindowsPath_RunTime { + @Alias // + @InjectAccessors(WindowsFileSystemAccessor.class) // + private Target_sun_nio_fs_WindowsFileSystem_RunTime fs; +} + +@SuppressWarnings("unused") +class WindowsFileSystemAccessor { + static Target_sun_nio_fs_WindowsFileSystem_RunTime get(Target_sun_nio_fs_WindowsPath_RunTime that) { + FileSystem theFileSystem = Target_sun_nio_fs_DefaultFileSystemProvider_RunTime.theFileSystem(); + VMError.guarantee(theFileSystem != null, "DefaultFileSystemProvider.theFileSystem() is null"); + return SubstrateUtil.cast(theFileSystem, Target_sun_nio_fs_WindowsFileSystem_RunTime.class); + } + + static void set(Target_sun_nio_fs_WindowsPath_RunTime that, Target_sun_nio_fs_WindowsFileSystem_RunTime value) { + /* + * `value` should always be DefaultFileSystemProvider.INSTANCE.theFileSystem() but we cannot + * check that here because it would introduce a class initialization cycle. + */ + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKInitializationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKInitializationFeature.java index a6f5fb32c145..86dfa00e9f2d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKInitializationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKInitializationFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -142,6 +142,42 @@ public void afterRegistration(AfterRegistrationAccess access) { rci.initializeAtBuildTime("java.awt.font.NumericShaper", "Required for sun.text.bidi.BidiBase.NumericShapings"); rci.initializeAtBuildTime("java.awt.font.JavaAWTFontAccessImpl", "Required for sun.text.bidi.BidiBase.NumericShapings"); + /* FileSystemProviders related */ + if (FutureDefaultsOptions.isJDKInitializedAtRunTime()) { + rci.initializeAtRunTime("java.nio.file.spi", FutureDefaultsOptions.RUN_TIME_INITIALIZE_JDK_REASON); + rci.initializeAtRunTime("sun.nio.fs", FutureDefaultsOptions.RUN_TIME_INITIALIZE_JDK_REASON); + + rci.initializeAtRunTime("java.nio.file.FileSystems", FutureDefaultsOptions.RUN_TIME_INITIALIZE_JDK_REASON); + rci.initializeAtRunTime("java.nio.file.FileSystems$DefaultFileSystemHolder", FutureDefaultsOptions.RUN_TIME_INITIALIZE_JDK_REASON); + + rci.initializeAtRunTime("java.util.zip.ZipFile$Source", FutureDefaultsOptions.RUN_TIME_INITIALIZE_JDK_REASON); + rci.initializeAtRunTime("java.util.zip.ZipFile$Source", FutureDefaultsOptions.RUN_TIME_INITIALIZE_JDK_REASON); + + rci.initializeAtRunTime("java.io.FileSystem", FutureDefaultsOptions.RUN_TIME_INITIALIZE_JDK_REASON); + rci.initializeAtRunTime("java.io.FileSystem$CurrentWorkingDirectoryHolder", FutureDefaultsOptions.RUN_TIME_INITIALIZE_JDK_REASON); + rci.initializeAtRunTime("java.io.UnixFileSystem", FutureDefaultsOptions.RUN_TIME_INITIALIZE_JDK_REASON); + rci.initializeAtRunTime("java.io.WindowsFileSystem", FutureDefaultsOptions.RUN_TIME_INITIALIZE_JDK_REASON); + + /* Holder for the default file system. */ + rci.initializeAtRunTime("com.oracle.svm.core.jdk.runtimeinit.DefaultFileSystemHolder", FutureDefaultsOptions.RUN_TIME_INITIALIZE_JDK_REASON); + + /* + * The following need to be build-time initialized because they can end up in the image + * heap. There are substitutions to patch the links to run-time initialized classes like + * FileSystem or FileSystemProvider. + */ + + /* + * Require explicit initializeAtBuildTime because the sun.nio.fs is registered for + * run-time initialization. + */ + rci.initializeAtBuildTime("sun.nio.fs.UnixPath", "Allow UnixPath objects in the image heap (" + FutureDefaultsOptions.RUN_TIME_INITIALIZE_JDK_REASON + ")"); + rci.initializeAtBuildTime("sun.nio.fs.WindowsPath", "Allow WindowsPath objects in the image heap (" + FutureDefaultsOptions.RUN_TIME_INITIALIZE_JDK_REASON + ")"); + + /* JrtFS support. */ + rci.initializeAtBuildTime("jdk.internal.jrtfs.SystemImage", FutureDefaultsOptions.RUN_TIME_INITIALIZE_JDK_REASON); + } + /* XML-related */ if (FutureDefaultsOptions.isJDKInitializedAtRunTime()) { // GR-50683 should remove this part diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKRegistrations.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKRegistrations.java index 33289d653864..ee1c46fa7b2c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKRegistrations.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKRegistrations.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -65,6 +65,7 @@ public void duringSetup(DuringSetupAccess a) { * a `Random` object and the temporary directory in a static final field. */ initializeAtRunTime(a, "sun.nio.ch.UnixDomainSockets"); + initializeAtRunTime(a, "sun.nio.ch.UnixDomainSockets$UnnamedHolder"); initializeAtRunTime(a, "java.util.concurrent.ThreadLocalRandom$ThreadLocalRandomProxy");