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..131961b0f2df --- /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.Predicate; + +import com.oracle.svm.core.FutureDefaultsOptions; + +public class JDKInitializedAtBuildTime implements Predicate { + @Override + public boolean test(String className) { + return !FutureDefaultsOptions.isJDKInitializedAtRunTime(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/xml/Target_jdk_xml_internal_JdkCatalog.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/xml/Target_jdk_xml_internal_JdkCatalog.java index eec7075c8d9d..11a97e628e13 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/xml/Target_jdk_xml_internal_JdkCatalog.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/xml/Target_jdk_xml_internal_JdkCatalog.java @@ -26,6 +26,7 @@ import java.util.Objects; +import com.oracle.svm.core.jdk.JDKInitializedAtBuildTime; import org.graalvm.nativeimage.hosted.FieldValueTransformer; import com.oracle.svm.core.annotate.Alias; @@ -45,7 +46,7 @@ * Ideally, we would initialize all of {@code jdk.xml} at run time, but that is too intrusive at the * current point in time (GR-50683). */ -@TargetClass(className = "jdk.xml.internal.JdkCatalog", onlyWith = JDKLatest.class) +@TargetClass(className = "jdk.xml.internal.JdkCatalog", onlyWith = {JDKLatest.class, JDKInitializedAtBuildTime.class}) public final class Target_jdk_xml_internal_JdkCatalog { @Alias // @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = JdkCatalogSupplier.class, isFinal = true) // @@ -62,7 +63,7 @@ public static void init(String resolve) { final class Target_javax_xml_catalog_Catalog { } -@TargetClass(className = "javax.xml.catalog.CatalogImpl") +@TargetClass(className = "javax.xml.catalog.CatalogImpl", onlyWith = JDKInitializedAtBuildTime.class) final class Target_javax_xml_catalog_CatalogImpl { @Alias // @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/xml/Target_jdk_xml_internal_SecuritySupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/xml/Target_jdk_xml_internal_SecuritySupport.java new file mode 100644 index 000000000000..11cb9978fe97 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/xml/Target_jdk_xml_internal_SecuritySupport.java @@ -0,0 +1,94 @@ +/* + * 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.xml; + +import java.util.Properties; + +import com.oracle.svm.core.annotate.Alias; +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.core.jdk.JDK21OrEarlier; +import com.oracle.svm.core.jdk.JDKInitializedAtBuildTime; +import com.oracle.svm.core.jdk.JDKLatest; + +import jdk.graal.compiler.serviceprovider.JavaVersionUtil; + +@TargetClass(className = "jdk.xml.internal.SecuritySupport", onlyWith = JDKInitializedAtBuildTime.class) +public final class Target_jdk_xml_internal_SecuritySupport { + + @Alias // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias, isFinal = true) // + static Properties cacheProps = new Properties(); + @Alias // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias) // + private static volatile boolean firstTime = true; + + @Substitute + public static String readConfig(String propName, boolean stax) { + // always load the default configuration file + if (firstTime) { + synchronized (cacheProps) { + if (firstTime) { + boolean found = XMLSupport.loadJaxpProperties(); + + // attempts to find stax.properties only if jaxp.properties is not available + if (stax && !found) { + XMLSupport.loadStaxProperties(); + } + + // load the custom configure on top of the default if any + String configFile = Target_jdk_xml_internal_SecuritySupport + .getSystemProperty(JavaVersionUtil.JAVA_SPEC > 21 ? Target_jdk_xml_internal_JdkConstants.CONFIG_FILE_PROPNAME : Target_jdk_xml_internal_JdkConstants.CONFIG_FILE); + if (configFile != null) { + loadProperties(configFile); + } + + firstTime = false; + } + } + } + + return cacheProps.getProperty(propName); + } + + @Alias + static native boolean loadProperties(String resourceName); + + @Alias + public static native String getSystemProperty(final String propName); +} + +@TargetClass(className = "jdk.xml.internal.JdkConstants", onlyWith = JDKInitializedAtBuildTime.class) +final class Target_jdk_xml_internal_JdkConstants { + @Alias // + @TargetElement(onlyWith = JDKLatest.class) // + public static String CONFIG_FILE_PROPNAME; + + @Alias // + @TargetElement(onlyWith = JDK21OrEarlier.class) // + public static String CONFIG_FILE; +} \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/xml/XMLSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/xml/XMLSupport.java new file mode 100644 index 000000000000..8ddb98e2cfa9 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/xml/XMLSupport.java @@ -0,0 +1,105 @@ +/* + * 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.xml; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.impl.ConfigurationCondition; +import org.graalvm.nativeimage.impl.RuntimeResourceSupport; + +import com.oracle.svm.core.jdk.Resources; +import com.oracle.svm.core.util.BasedOnJDKClass; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.ReflectionUtil; + +@BasedOnJDKClass(className = "jdk.xml.internal.SecuritySupport") +public final class XMLSupport { + + private static final Module xmlModule = ReflectionUtil.lookupClass("jdk.xml.internal.SecuritySupport").getModule(); + private static final String JAXP_PROPERTIES = "jaxp.properties"; + private static final String STAX_PROPERTIES = "stax.properties"; + + static boolean loadJaxpProperties() { + return loadProperties(JAXP_PROPERTIES); + } + + static void loadStaxProperties() { + loadProperties(STAX_PROPERTIES); + } + + private static boolean loadProperties(String resource) { + try (InputStream in = Resources.createInputStream(xmlModule, resource)) { + if (in == null) { + return false; + } + Target_jdk_xml_internal_SecuritySupport.cacheProps.load(in); + return true; + } catch (IOException e) { + // shouldn't happen, but required by method Properties.load + throw VMError.shouldNotReachHere(e); + } + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static void readProperties() { + byte[] jaxpProperty = readProperties(Paths.get(System.getProperty("java.home"), "conf", JAXP_PROPERTIES) + .toAbsolutePath().normalize()); + byte[] staxProperty = jaxpProperty != null ? null + : readProperties(Paths.get(System.getProperty("java.home"), "conf", STAX_PROPERTIES) + .toAbsolutePath().normalize()); + addResource(xmlModule, JAXP_PROPERTIES, jaxpProperty); + addResource(xmlModule, STAX_PROPERTIES, staxProperty); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static void addResource(Module module, String resourcePath, byte[] resourceContent) { + if (resourceContent != null) { + RuntimeResourceSupport.singleton().injectResource(module, resourcePath, resourceContent, "Added via " + XMLSupport.class.getName()); + } else { + Resources.currentLayer().registerNegativeQuery(module, resourcePath); + } + RuntimeResourceSupport.singleton().addCondition(ConfigurationCondition.alwaysTrue(), module, resourcePath); + } + + @Platforms(Platform.HOSTED_ONLY.class) + private static byte[] readProperties(Path path) { + File f = path.toFile(); + if (f.exists()) { + try { + return Files.readAllBytes(path); + } catch (IOException e) { + // shouldn't happen, but required by method getFileInputStream + } + } + return null; + } +} 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 1def6abd3495..9d04e2534821 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 @@ -26,11 +26,11 @@ import java.lang.reflect.Field; -import com.oracle.svm.core.FutureDefaultsOptions; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; +import com.oracle.svm.core.FutureDefaultsOptions; import com.oracle.svm.core.ParsingReason; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; @@ -53,7 +53,7 @@ @AutomaticallyRegisteredFeature public class JDKInitializationFeature implements InternalFeature { - private static final String JDK_CLASS_REASON = "Core JDK classes are initialized at build time"; + static final String JDK_CLASS_REASON = "Core JDK classes are initialized at build time"; @Override public void afterRegistration(AfterRegistrationAccess access) { @@ -72,8 +72,10 @@ public void afterRegistration(AfterRegistrationAccess access) { rci.initializeAtBuildTime("java.net", JDK_CLASS_REASON); rci.initializeAtBuildTime("java.nio", JDK_CLASS_REASON); rci.initializeAtBuildTime("java.text", JDK_CLASS_REASON); + rci.initializeAtBuildTime("java.time", JDK_CLASS_REASON); rci.initializeAtRunTime("java.time.chrono.HijrahChronology", "Reads java.home in class initializer."); + rci.initializeAtBuildTime("java.util", JDK_CLASS_REASON); rci.initializeAtRunTime("java.util.concurrent.SubmissionPublisher", "Executor service must be recomputed"); @@ -83,7 +85,6 @@ public void afterRegistration(AfterRegistrationAccess access) { rci.initializeAtBuildTime("javax.naming", JDK_CLASS_REASON); rci.initializeAtBuildTime("javax.net", JDK_CLASS_REASON); rci.initializeAtBuildTime("javax.tools", JDK_CLASS_REASON); - rci.initializeAtBuildTime("javax.xml", JDK_CLASS_REASON); rci.initializeAtBuildTime("jdk.internal", JDK_CLASS_REASON); rci.initializeAtBuildTime("jdk.jfr", "Needed for Native Image substitutions"); @@ -94,15 +95,6 @@ public void afterRegistration(AfterRegistrationAccess access) { rci.initializeAtBuildTime("jdk.net", JDK_CLASS_REASON); rci.initializeAtBuildTime("jdk.nio", JDK_CLASS_REASON); rci.initializeAtBuildTime("jdk.vm.ci", "Native Image classes are always initialized at build time"); - rci.initializeAtBuildTime("jdk.xml", JDK_CLASS_REASON); - /* - * The XML classes have cyclic class initializer dependencies, and class initialization can - * deadlock/fail when initialization is started at the "wrong part" of the cycle. - * Force-initializing the correct class of the cycle here, in addition to the - * "whole package" initialization above, breaks the cycle because it triggers immediate - * initilalization here before the static analysis is started. - */ - rci.initializeAtBuildTime("jdk.xml.internal.JdkXmlUtils", JDK_CLASS_REASON); rci.initializeAtBuildTime("sun.invoke", JDK_CLASS_REASON); rci.initializeAtBuildTime("sun.launcher", JDK_CLASS_REASON); @@ -145,11 +137,30 @@ public void afterRegistration(AfterRegistrationAccess access) { /* XML-related */ if (FutureDefaultsOptions.isJDKInitializedAtRunTime()) { - // GR-50683 should remove this part - rci.initializeAtBuildTime("com.sun.xml", JDK_CLASS_REASON); - rci.initializeAtBuildTime("com.sun.org.apache", JDK_CLASS_REASON); - rci.initializeAtBuildTime("com.sun.org.slf4j.internal", JDK_CLASS_REASON); + /* + * still initialization due to the security services that reach the XMLSecurityManager + */ + rci.initializeAtBuildTime("javax.xml.catalog.CatalogImpl", "Security providers initialized at build time: jdk.xml.internal.XMLSecurityManager"); + rci.initializeAtBuildTime("javax.xml.catalog.CatalogResolver$NotFoundAction$1", "Security providers initialized at build time: jdk.xml.internal.XMLSecurityManager"); + rci.initializeAtBuildTime("javax.xml.catalog.CatalogResolver$NotFoundAction$2", "Security providers initialized at build time: jdk.xml.internal.XMLSecurityManager"); + rci.initializeAtBuildTime("javax.xml.catalog.CatalogResolver$NotFoundAction$3", "Security providers initialized at build time: jdk.xml.internal.XMLSecurityManager"); + rci.initializeAtBuildTime("javax.xml.catalog.SystemSuffix", "Security providers initialized at build time: jdk.xml.internal.XMLSecurityManager"); + rci.initializeAtBuildTime("javax.xml.catalog.SystemEntry", "Security providers initialized at build time: jdk.xml.internal.XMLSecurityManager"); + rci.initializeAtBuildTime("javax.xml.catalog.CatalogFeatures", "Security providers initialized at build time: jdk.xml.internal.XMLSecurityManager"); + rci.initializeAtBuildTime("javax.xml.catalog.BaseEntry$CatalogEntryType", "Security providers initialized at build time: jdk.xml.internal.XMLSecurityManager"); + rci.initializeAtBuildTime("javax.xml.catalog.CatalogFeatures$State", "Security providers initialized at build time: jdk.xml.internal.XMLSecurityManager"); + rci.initializeAtBuildTime("javax.xml.catalog.PublicEntry", "Security providers initialized at build time: jdk.xml.internal.XMLSecurityManager"); } else { + rci.initializeAtBuildTime("jdk.xml", JDK_CLASS_REASON); + /* + * The XML classes have cyclic class initializer dependencies, and class initialization + * can deadlock/fail when initialization is started at the "wrong part" of the cycle. + * Force-initializing the correct class of the cycle here, in addition to the + * "whole package" initialization above, breaks the cycle because it triggers immediate + * initilalization here before the static analysis is started. + */ + rci.initializeAtBuildTime("jdk.xml.internal.JdkXmlUtils", JDK_CLASS_REASON); + rci.initializeAtBuildTime("com.sun.xml", JDK_CLASS_REASON); rci.initializeAtBuildTime("com.sun.org.apache", JDK_CLASS_REASON); rci.initializeAtBuildTime("com.sun.org.slf4j.internal", JDK_CLASS_REASON); @@ -229,8 +240,6 @@ public void afterRegistration(AfterRegistrationAccess access) { rci.initializeAtRunTime("jdk.internal.foreign.abi.fallback.FFIABI", "Fails build-time initialization"); rci.initializeAtRunTime("sun.reflect.misc.Trampoline", "Fails build-time initialization"); - rci.initializeAtRunTime("com.sun.org.apache.xml.internal.serialize.HTMLdtd", "Fails build-time initialization"); - rci.initializeAtRunTime("sun.security.ssl.SSLContextImpl$DefaultSSLContextHolder", "Stores secure random"); rci.initializeAtRunTime("sun.security.ssl.SSLSocketFactoryImpl", "Stores secure random"); rci.initializeAtRunTime("sun.security.provider.certpath.ssl.SSLServerCertStore", "Stores secure random");