From d90833ad9d8a8656abe7c006203b2f981e12f58b Mon Sep 17 00:00:00 2001 From: Christian Wimmer Date: Thu, 30 Jun 2022 12:25:46 -0700 Subject: [PATCH] Add class initialization strategy that allows all classes to be used at image build time --- substratevm/mx.substratevm/suite.py | 1 + ...et_sun_security_ssl_TrustStoreManager.java | 1 + .../jdk/management/ManagementFeature.java | 4 + .../svm/hosted/NativeImageGenerator.java | 53 +-- ...ostedUsagesClassInitializationSupport.java | 219 ++++++++++ .../ClassInitializationConfiguration.java | 2 +- .../ClassInitializationFeature.java | 78 +--- .../ClassInitializationOptions.java | 3 + .../ClassInitializationSupport.java | 328 ++++++++++++++- .../ClassOrPackageConfig.java | 2 +- .../EarlyClassInitializerAnalysis.java | 14 +- .../hosted/classinitialization/InitKind.java | 2 +- ...ProvenSafeClassInitializationSupport.java} | 374 ++++-------------- .../TypeInitializerGraph.java | 8 +- .../hosted/jdk/JDKInitializationFeature.java | 3 + .../jdk/localization/LocalizationFeature.java | 7 + .../hosted/xml/XMLParsersRegistration.java | 4 +- .../svm/polyglot/scala/ScalaFeature.java | 2 + 18 files changed, 676 insertions(+), 429 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/AllowAllHostedUsagesClassInitializationSupport.java rename substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/{ConfigurableClassInitialization.java => ProvenSafeClassInitializationSupport.java} (58%) diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index cc910c84f850..47bbaa788fab 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -1174,6 +1174,7 @@ "com.oracle.truffle.api.instrumentation.TruffleInstrument.Provider", "com.oracle.svm.hosted.agent.NativeImageBytecodeInstrumentationAgentExtension", "com.oracle.svm.hosted.NativeImageClassLoaderPostProcessing", + "java.util.spi.ResourceBundleControlProvider", ], "requiresConcealed": { "jdk.internal.vm.ci": [ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_sun_security_ssl_TrustStoreManager.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_sun_security_ssl_TrustStoreManager.java index 7a7eff8263af..8ed424e858ad 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_sun_security_ssl_TrustStoreManager.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_sun_security_ssl_TrustStoreManager.java @@ -95,6 +95,7 @@ public void afterRegistration(AfterRegistrationAccess access) { * certificate files while generating X509Certificates. */ rci.initializeAtBuildTime("org.jcp.xml.dsig.internal.dom.XMLDSigRI", "Required for TrustStoreManager"); + rci.initializeAtBuildTime("org.jcp.xml.dsig.internal.dom.XMLDSigRI$ProviderService", "Required for TrustStoreManager"); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/ManagementFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/ManagementFeature.java index 814c911a90bd..3115b135ded9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/ManagementFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/ManagementFeature.java @@ -85,6 +85,10 @@ public void duringSetup(DuringSetupAccess access) { access.registerObjectReplacer(this::replaceHostedPlatformManagedObject); RuntimeClassInitialization.initializeAtBuildTime("com.sun.jmx.mbeanserver.DefaultMXBeanMappingFactory"); + RuntimeClassInitialization.initializeAtBuildTime("com.sun.jmx.mbeanserver.DefaultMXBeanMappingFactory$Mappings"); + RuntimeClassInitialization.initializeAtBuildTime("com.sun.jmx.mbeanserver.DefaultMXBeanMappingFactory$IdentityMapping"); + RuntimeClassInitialization.initializeAtBuildTime("com.sun.jmx.mbeanserver.DescriptorCache"); + RuntimeClassInitialization.initializeAtBuildTime("com.sun.jmx.remote.util.ClassLogger"); } /** diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java index 4133d7bb4a5b..72152de48cd0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java @@ -24,13 +24,14 @@ */ package com.oracle.svm.hosted; +import static com.oracle.graal.pointsto.api.PointstoOptions.UseExperimentalReachabilityAnalysis; import static com.oracle.svm.hosted.NativeImageOptions.DiagnosticsDir; import static com.oracle.svm.hosted.NativeImageOptions.DiagnosticsMode; import static org.graalvm.compiler.hotspot.JVMCIVersionCheck.OPEN_LABSJDK_RELEASE_URL_PATTERN; -import static com.oracle.graal.pointsto.api.PointstoOptions.UseExperimentalReachabilityAnalysis; import static org.graalvm.compiler.replacements.StandardGraphBuilderPlugins.registerInvocationPlugins; import java.io.IOException; +import java.lang.annotation.Annotation; import java.lang.ref.Reference; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -56,18 +57,6 @@ import java.util.function.BooleanSupplier; import java.util.stream.Collectors; -import com.oracle.graal.pointsto.ObjectScanningObserver; -import com.oracle.graal.pointsto.PointsToAnalysis; -import com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder; -import com.oracle.graal.pointsto.meta.AnalysisFactory; -import com.oracle.graal.pointsto.meta.PointsToAnalysisFactory; -import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; -import com.oracle.graal.reachability.MethodSummaryProvider; -import com.oracle.graal.reachability.ReachabilityAnalysisFactory; -import com.oracle.graal.reachability.ReachabilityObjectScanner; -import com.oracle.svm.hosted.analysis.NativeImageReachabilityAnalysisEngine; -import com.oracle.graal.pointsto.util.TimerCollection; -import com.oracle.svm.util.AnnotationExtracter; import org.graalvm.collections.EconomicSet; import org.graalvm.collections.Pair; import org.graalvm.compiler.api.replacements.Fold; @@ -143,10 +132,12 @@ import com.oracle.graal.pointsto.AnalysisObjectScanningObserver; import com.oracle.graal.pointsto.AnalysisPolicy; import com.oracle.graal.pointsto.BigBang; -import com.oracle.graal.pointsto.typestate.DefaultAnalysisPolicy; +import com.oracle.graal.pointsto.ObjectScanningObserver; +import com.oracle.graal.pointsto.PointsToAnalysis; import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; import com.oracle.graal.pointsto.flow.FormalParamTypeFlow; +import com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder; import com.oracle.graal.pointsto.flow.TypeFlow; import com.oracle.graal.pointsto.flow.context.bytecode.BytecodeSensitiveAnalysisPolicy; import com.oracle.graal.pointsto.heap.HeapSnapshotVerifier; @@ -154,17 +145,25 @@ import com.oracle.graal.pointsto.heap.ImageHeapScanner; import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor; import com.oracle.graal.pointsto.infrastructure.WrappedJavaMethod; +import com.oracle.graal.pointsto.meta.AnalysisFactory; import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.meta.HostedProviders; +import com.oracle.graal.pointsto.meta.PointsToAnalysisFactory; +import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; import com.oracle.graal.pointsto.reports.AnalysisReporter; +import com.oracle.graal.pointsto.typestate.DefaultAnalysisPolicy; import com.oracle.graal.pointsto.typestate.TypeState; import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.graal.pointsto.util.GraalAccess; import com.oracle.graal.pointsto.util.Timer.StopTimer; +import com.oracle.graal.pointsto.util.TimerCollection; +import com.oracle.graal.reachability.MethodSummaryProvider; +import com.oracle.graal.reachability.ReachabilityAnalysisFactory; +import com.oracle.graal.reachability.ReachabilityObjectScanner; import com.oracle.svm.core.BuildArtifacts; import com.oracle.svm.core.BuildArtifacts.ArtifactType; import com.oracle.svm.core.BuildPhaseProvider; @@ -241,6 +240,7 @@ import com.oracle.svm.hosted.ameta.AnalysisConstantReflectionProvider; import com.oracle.svm.hosted.analysis.Inflation; import com.oracle.svm.hosted.analysis.NativeImagePointsToAnalysis; +import com.oracle.svm.hosted.analysis.NativeImageReachabilityAnalysisEngine; import com.oracle.svm.hosted.analysis.SVMAnalysisMetaAccess; import com.oracle.svm.hosted.analysis.SubstrateUnsupportedFeatures; import com.oracle.svm.hosted.annotation.AnnotationSupport; @@ -253,7 +253,6 @@ import com.oracle.svm.hosted.cenum.CEnumCallWrapperSubstitutionProcessor; import com.oracle.svm.hosted.classinitialization.ClassInitializationFeature; import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; -import com.oracle.svm.hosted.classinitialization.ConfigurableClassInitialization; import com.oracle.svm.hosted.code.CEntryPointCallStubSupport; import com.oracle.svm.hosted.code.CEntryPointData; import com.oracle.svm.hosted.code.CFunctionSubstitutionProcessor; @@ -293,6 +292,8 @@ import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; import com.oracle.svm.hosted.substitute.DeletedFieldsPlugin; import com.oracle.svm.hosted.substitute.UnsafeAutomaticSubstitutionProcessor; +import com.oracle.svm.util.AnnotationExtracter; +import com.oracle.svm.util.ClassUtil; import com.oracle.svm.util.ImageBuildStatistics; import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.ReflectionUtil.ReflectionUtilError; @@ -828,7 +829,7 @@ private void setupNativeImage(OptionValues options, Map ImageSingletons.add(ClassLoaderSupport.class, classLoaderSupport); ImageSingletons.add(LinkAtBuildTimeSupport.class, new LinkAtBuildTimeSupport(loader, classLoaderSupport)); - ClassInitializationSupport classInitializationSupport = new ConfigurableClassInitialization(originalMetaAccess, loader); + ClassInitializationSupport classInitializationSupport = ClassInitializationSupport.create(originalMetaAccess, loader); ImageSingletons.add(RuntimeClassInitializationSupport.class, classInitializationSupport); ClassInitializationFeature.processClassInitializationOptions(classInitializationSupport); @@ -1603,7 +1604,7 @@ private void processNativeLibraryImports(NativeLibraries nativeLibs, MetaAccessP for (Method method : loader.findAnnotatedMethods(CConstant.class)) { if (LibCBase.isMethodProvidedInCurrentLibc(method)) { - classInitializationSupport.initializeAtBuildTime(method.getDeclaringClass(), "classes with " + CConstant.class.getSimpleName() + " annotations are always initialized"); + initializeAtBuildTime(method.getDeclaringClass(), classInitializationSupport, CConstant.class); nativeLibs.loadJavaMethod(metaAccess.lookupJavaMethod(method)); } } @@ -1614,38 +1615,38 @@ private void processNativeLibraryImports(NativeLibraries nativeLibs, MetaAccessP } for (Class clazz : loader.findAnnotatedClasses(CStruct.class, false)) { if (LibCBase.isTypeProvidedInCurrentLibc(clazz)) { - classInitializationSupport.initializeAtBuildTime(clazz, "classes annotated with " + CStruct.class.getSimpleName() + " are always initialized"); + initializeAtBuildTime(clazz, classInitializationSupport, CStruct.class); nativeLibs.loadJavaType(metaAccess.lookupJavaType(clazz)); } } for (Class clazz : loader.findAnnotatedClasses(RawStructure.class, false)) { if (LibCBase.isTypeProvidedInCurrentLibc(clazz)) { - classInitializationSupport.initializeAtBuildTime(clazz, "classes annotated with " + RawStructure.class.getSimpleName() + " are always initialized"); + initializeAtBuildTime(clazz, classInitializationSupport, RawStructure.class); nativeLibs.loadJavaType(metaAccess.lookupJavaType(clazz)); } } for (Class clazz : loader.findAnnotatedClasses(CPointerTo.class, false)) { if (LibCBase.isTypeProvidedInCurrentLibc(clazz)) { - classInitializationSupport.initializeAtBuildTime(clazz, "classes annotated with " + CPointerTo.class.getSimpleName() + " are always initialized"); + initializeAtBuildTime(clazz, classInitializationSupport, CPointerTo.class); nativeLibs.loadJavaType(metaAccess.lookupJavaType(clazz)); } } for (Class clazz : loader.findAnnotatedClasses(RawPointerTo.class, false)) { if (LibCBase.isTypeProvidedInCurrentLibc(clazz)) { - classInitializationSupport.initializeAtBuildTime(clazz, "classes annotated with " + RawPointerTo.class.getSimpleName() + " are always initialized"); + initializeAtBuildTime(clazz, classInitializationSupport, RawPointerTo.class); nativeLibs.loadJavaType(metaAccess.lookupJavaType(clazz)); } } for (Class clazz : loader.findAnnotatedClasses(CEnum.class, false)) { if (LibCBase.isTypeProvidedInCurrentLibc(clazz)) { ResolvedJavaType type = metaAccess.lookupJavaType(clazz); - classInitializationSupport.initializeAtBuildTime(clazz, "classes annotated with " + CEnum.class.getSimpleName() + " are always initialized"); + initializeAtBuildTime(clazz, classInitializationSupport, CEnum.class); nativeLibs.loadJavaType(type); } } for (Class clazz : loader.findAnnotatedClasses(CContext.class, false)) { if (LibCBase.isTypeProvidedInCurrentLibc(clazz)) { - classInitializationSupport.initializeAtBuildTime(clazz, "classes annotated with " + CContext.class.getSimpleName() + " are always initialized"); + initializeAtBuildTime(clazz, classInitializationSupport, CContext.class); } } nativeLibs.processCLibraryAnnotations(loader); @@ -1654,6 +1655,12 @@ private void processNativeLibraryImports(NativeLibraries nativeLibs, MetaAccessP nativeLibs.reportErrors(); } + private static void initializeAtBuildTime(Class clazz, ClassInitializationSupport classInitializationSupport, Class annotationForMessage) { + String message = "classes annotated with " + ClassUtil.getUnqualifiedName(annotationForMessage) + " are always initialized at image build time"; + classInitializationSupport.initializeAtBuildTime(clazz, message); + classInitializationSupport.forceInitializeHosted(clazz, message, false); + } + public AbstractImage getBuiltImage() { return image; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/AllowAllHostedUsagesClassInitializationSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/AllowAllHostedUsagesClassInitializationSupport.java new file mode 100644 index 000000000000..2ccb82b10545 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/AllowAllHostedUsagesClassInitializationSupport.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2022, 2022, 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.hosted.classinitialization; + +import java.lang.reflect.Proxy; + +import org.graalvm.compiler.java.LambdaUtils; + +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.ImageClassLoader; + +import jdk.vm.ci.meta.MetaAccessProvider; + +class AllowAllHostedUsagesClassInitializationSupport extends ClassInitializationSupport { + + AllowAllHostedUsagesClassInitializationSupport(MetaAccessProvider metaAccess, ImageClassLoader loader) { + super(metaAccess, loader); + } + + @Override + public void initializeAtBuildTime(Class aClass, String reason) { + UserError.guarantee(!configurationSealed, "The class initialization configuration can be changed only before the phase analysis."); + Class cur = aClass; + do { + classInitializationConfiguration.insert(cur.getTypeName(), InitKind.BUILD_TIME, cur == aClass ? reason : "super type of " + aClass.getTypeName(), true); + initializeInterfacesAtBuildTime(cur.getInterfaces(), "interface of " + aClass.getTypeName()); + cur = cur.getSuperclass(); + } while (cur != null); + } + + private void initializeInterfacesAtBuildTime(Class[] interfaces, String reason) { + for (Class iface : interfaces) { + if (metaAccess.lookupJavaType(iface).declaresDefaultMethods()) { + classInitializationConfiguration.insert(iface.getTypeName(), InitKind.BUILD_TIME, reason, true); + } + initializeInterfacesAtBuildTime(iface.getInterfaces(), reason); + } + } + + @Override + public void initializeAtRunTime(Class clazz, String reason) { + UserError.guarantee(!configurationSealed, "The class initialization configuration can be changed only before the phase analysis."); + classInitializationConfiguration.insert(clazz.getTypeName(), InitKind.RUN_TIME, reason, true); + } + + @Override + public void rerunInitialization(Class clazz, String reason) { + /* There is no more difference between RUN_TIME and RERUN. */ + initializeAtRunTime(clazz, reason); + } + + @Override + public void rerunInitialization(String name, String reason) { + /* There is no more difference between RUN_TIME and RERUN. */ + initializeAtRunTime(name, reason); + } + + @Override + String reasonForClass(Class clazz) { + InitKind initKind = classInitKinds.get(clazz); + String reason = classInitializationConfiguration.lookupReason(clazz.getTypeName()); + if (initKind.isRunTime()) { + return "classes are initialized at run time by default"; + } else if (reason != null) { + return reason; + } else { + throw VMError.shouldNotReachHere("Must be either proven or specified"); + } + } + + @Override + public void forceInitializeHosted(Class clazz, String reason, boolean allowInitializationErrors) { + if (clazz == null) { + return; + } + classInitializationConfiguration.insert(clazz.getTypeName(), InitKind.BUILD_TIME, reason, true); + InitKind initKind = ensureClassInitialized(clazz, allowInitializationErrors); + classInitKinds.put(clazz, initKind); + + forceInitializeHosted(clazz.getSuperclass(), "super type of " + clazz.getTypeName(), allowInitializationErrors); + forceInitializeInterfaces(clazz.getInterfaces(), "super type of " + clazz.getTypeName()); + } + + private void forceInitializeInterfaces(Class[] interfaces, String reason) { + for (Class iface : interfaces) { + if (metaAccess.lookupJavaType(iface).declaresDefaultMethods()) { + classInitializationConfiguration.insert(iface.getTypeName(), InitKind.BUILD_TIME, reason, true); + + ensureClassInitialized(iface, false); + classInitKinds.put(iface, InitKind.BUILD_TIME); + } + forceInitializeInterfaces(iface.getInterfaces(), "super type of " + iface.getTypeName()); + } + } + + @Override + boolean checkDelayedInitialization() { + /* Nothing to check, all classes are allowed to be initialized in the image builder VM. */ + return true; + } + + @Override + InitKind computeInitKindAndMaybeInitializeClass(Class clazz) { + return computeInitKindAndMaybeInitializeClass(clazz, true); + } + + /** + * Computes the class initialization kind of the provided class, all superclasses, and all + * interfaces that the provided class depends on (i.e., interfaces implemented by the provided + * class that declare default methods). + * + * Also defines class initialization based on a policy of the subclass. + */ + InitKind computeInitKindAndMaybeInitializeClass(Class clazz, boolean memoize) { + InitKind existing = classInitKinds.get(clazz); + if (existing != null) { + return existing; + } + + if (clazz.isPrimitive()) { + forceInitializeHosted(clazz, "primitive types are initialized at build time", false); + return InitKind.BUILD_TIME; + } + + if (clazz.isArray()) { + forceInitializeHosted(clazz, "arrays are initialized at build time", false); + return InitKind.BUILD_TIME; + } + + InitKind specifiedInitKind = specifiedInitKindFor(clazz); + InitKind clazzResult = specifiedInitKind != null ? specifiedInitKind : InitKind.RUN_TIME; + + InitKind superResult = InitKind.BUILD_TIME; + if (clazz.getSuperclass() != null) { + superResult = superResult.max(computeInitKindAndMaybeInitializeClass(clazz.getSuperclass(), memoize)); + } + superResult = superResult.max(processInterfaces(clazz, memoize)); + + if (superResult == InitKind.BUILD_TIME && (Proxy.isProxyClass(clazz) || LambdaUtils.isLambdaType(metaAccess.lookupJavaType(clazz)))) { + forceInitializeHosted(clazz, "proxy/lambda classes with interfaces initialized at build time are also initialized at build time", false); + return InitKind.BUILD_TIME; + } + + InitKind result = superResult.max(clazzResult); + + if (memoize) { + if (!result.isRunTime()) { + result = result.max(ensureClassInitialized(clazz, false)); + } + + InitKind previous = classInitKinds.putIfAbsent(clazz, result); + if (previous != null && previous != result) { + throw VMError.shouldNotReachHere("Conflicting class initialization kind: " + previous + " != " + result + " for " + clazz); + } + } + return result; + } + + private InitKind processInterfaces(Class clazz, boolean memoizeEager) { + /* + * Note that we do not call computeInitKindForClass(clazz) on purpose: if clazz is the root + * class or an interface declaring default methods, then + * computeInitKindAndMaybeInitializeClass() already calls computeInitKindForClass. If the + * interface does not declare default methods, than we must not take the InitKind of that + * interface into account, because interfaces without default methods are independent from a + * class initialization point of view. + */ + InitKind result = InitKind.BUILD_TIME; + + for (Class iface : clazz.getInterfaces()) { + if (metaAccess.lookupJavaType(iface).declaresDefaultMethods()) { + /* + * An interface that declares default methods is initialized when a class + * implementing it is initialized. So we need to inherit the InitKind from such an + * interface. + */ + result = result.max(computeInitKindAndMaybeInitializeClass(iface, memoizeEager)); + } else { + /* + * An interface that does not declare default methods is independent from a class + * that implements it, i.e., the interface can still be uninitialized even when the + * class is initialized. + */ + result = result.max(processInterfaces(iface, memoizeEager)); + } + } + return result; + } + + @Override + void doLateInitialization(AnalysisUniverse aUniverse, AnalysisMetaAccess aMetaAccess) { + /* Nothing for now. */ + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationConfiguration.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationConfiguration.java index 37c721fc15a9..d0ff1b688a3c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationConfiguration.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationConfiguration.java @@ -53,7 +53,7 @@ * Every node tracks a list of reasons for the set configuration. This list helps the users debug * conflicts in the configuration. */ -public class ClassInitializationConfiguration { +final class ClassInitializationConfiguration { private static final String ROOT_QUALIFIER = ""; private static final int MAX_NUMBER_OF_REASONS = 10; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java index 9a37f9c2904c..76cd8fdda63e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java @@ -29,14 +29,11 @@ import static com.oracle.svm.hosted.classinitialization.InitKind.RUN_TIME; import java.io.PrintWriter; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; import org.graalvm.collections.Pair; @@ -47,27 +44,23 @@ import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisMethod; -import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.reports.ReportUtils; import com.oracle.graal.pointsto.util.Timer; import com.oracle.graal.pointsto.util.TimerCollection; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.annotate.AutomaticFeature; -import com.oracle.svm.core.classinitialization.ClassInitializationInfo; import com.oracle.svm.core.classinitialization.EnsureClassInitializedSnippets; import com.oracle.svm.core.graal.InternalFeature; import com.oracle.svm.core.graal.meta.RuntimeConfiguration; import com.oracle.svm.core.graal.meta.SubstrateForeignCallsProvider; import com.oracle.svm.core.graal.snippets.NodeLoweringProvider; -import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.option.OptionOrigin; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.snippets.SnippetRuntime; import com.oracle.svm.core.util.UserError; import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; -import com.oracle.svm.hosted.SVMHost; @AutomaticFeature public class ClassInitializationFeature implements InternalFeature { @@ -76,7 +69,6 @@ public class ClassInitializationFeature implements InternalFeature { private ClassInitializationSupport classInitializationSupport; private AnalysisUniverse universe; private AnalysisMetaAccess metaAccess; - private Field dynamicHubClassInitializationInfoField; public static void processClassInitializationOptions(ClassInitializationSupport initializationSupport) { initializeNativeImagePackagesAtBuildTime(initializationSupport); @@ -121,7 +113,6 @@ public void duringSetup(DuringSetupAccess a) { access.registerObjectReplacer(this::checkImageHeapInstance); universe = ((FeatureImpl.DuringSetupAccessImpl) a).getBigBang().getUniverse(); metaAccess = ((FeatureImpl.DuringSetupAccessImpl) a).getBigBang().getMetaAccess(); - dynamicHubClassInitializationInfoField = access.findField(DynamicHub.class, "classInitializationInfo"); } private Object checkImageHeapInstance(Object obj) { @@ -181,20 +172,15 @@ public void afterAnalysis(AfterAnalysisAccess access) { try (Timer.StopTimer ignored = TimerCollection.createTimerAndStart(TimerCollection.Registry.CLINIT)) { classInitializationSupport.setUnsupportedFeatures(null); - String path = SubstrateOptions.reportsPath(); assert classInitializationSupport.checkDelayedInitialization(); - TypeInitializerGraph initGraph = new TypeInitializerGraph(universe); - initGraph.computeInitializerSafety(); + classInitializationSupport.doLateInitialization(universe, metaAccess); - classInitializationSupport.setProvenSafeLate(initializeSafeDelayedClasses(initGraph)); if (ClassInitializationOptions.PrintClassInitialization.getValue()) { - reportInitializerDependencies(universe, initGraph, path); - reportClassInitializationInfo(path); + reportClassInitializationInfo(SubstrateOptions.reportsPath()); } - if (SubstrateOptions.TraceClassInitialization.hasBeenSet()) { - reportTrackedClassInitializationTraces(path); + reportTrackedClassInitializationTraces(SubstrateOptions.reportsPath()); } if (ClassInitializationOptions.AssertInitializationSpecifiedForAllClasses.getValue()) { @@ -211,20 +197,6 @@ public void afterAnalysis(AfterAnalysisAccess access) { } } - private static void reportInitializerDependencies(AnalysisUniverse universe, TypeInitializerGraph initGraph, String path) { - ReportUtils.report("class initialization dependencies", path, "class_initialization_dependencies", "dot", writer -> { - writer.println("digraph class_initializer_dependencies {"); - universe.getTypes().stream() - .filter(ClassInitializationFeature::isRelevantForPrinting) - .forEach(t -> writer.println(quote(t.toClassName()) + "[fillcolor=" + (initGraph.isUnsafe(t) ? "red" : "green") + "]")); - universe.getTypes().stream() - .filter(ClassInitializationFeature::isRelevantForPrinting) - .forEach(t -> initGraph.getDependencies(t) - .forEach(t1 -> writer.println(quote(t.toClassName()) + " -> " + quote(t1.toClassName())))); - writer.println("}"); - }); - } - /** * Prints a file for every type of class initialization. Each file contains a list of classes * that belong to it. @@ -249,59 +221,19 @@ private void reportKind(PrintWriter writer, InitKind kind) { } private static void reportTrackedClassInitializationTraces(String path) { - Map, StackTraceElement[]> initializedClasses = ConfigurableClassInitialization.getInitializedClasses(); + Map, StackTraceElement[]> initializedClasses = ProvenSafeClassInitializationSupport.getInitializedClasses(); int size = initializedClasses.size(); if (size > 0) { ReportUtils.report(size + " class initialization trace(s) of class(es) traced by " + SubstrateOptions.TraceClassInitialization.getName(), path, "traced_class_initialization", "txt", writer -> initializedClasses.forEach((k, v) -> { writer.println(k.getName()); writer.println("---------------------------------------------"); - writer.println(ConfigurableClassInitialization.getTraceString(v)); + writer.println(ProvenSafeClassInitializationSupport.getTraceString(v)); writer.println(); })); } } - private static boolean isRelevantForPrinting(AnalysisType type) { - return !type.isPrimitive() && !type.isArray() && type.isReachable(); - } - - private static String quote(String className) { - return "\"" + className + "\""; - } - - /** - * Initializes all classes that are considered delayed by the system. Classes specified by the - * user will not be delayed. - */ - private Set> initializeSafeDelayedClasses(TypeInitializerGraph initGraph) { - Set> provenSafe = new HashSet<>(); - classInitializationSupport.setConfigurationSealed(false); - classInitializationSupport.classesWithKind(RUN_TIME).stream() - .filter(t -> metaAccess.optionalLookupJavaType(t).isPresent()) - .filter(t -> metaAccess.lookupJavaType(t).isReachable()) - .filter(t -> classInitializationSupport.canBeProvenSafe(t)) - .forEach(c -> { - AnalysisType type = metaAccess.lookupJavaType(c); - if (!initGraph.isUnsafe(type)) { - classInitializationSupport.forceInitializeHosted(c, "proven safe to initialize", true); - /* - * See if initialization worked--it can fail due to implicit - * exceptions. - */ - if (!classInitializationSupport.shouldInitializeAtRuntime(c)) { - provenSafe.add(c); - ClassInitializationInfo initializationInfo = type.getClassInitializer() == null ? ClassInitializationInfo.NO_INITIALIZER_INFO_SINGLETON - : ClassInitializationInfo.INITIALIZED_INFO_SINGLETON; - DynamicHub hub = ((SVMHost) universe.hostVM()).dynamicHub(type); - hub.setClassInitializationInfo(initializationInfo); - universe.getHeapScanner().rescanField(hub, dynamicHubClassInitializationInfoField); - } - } - }); - return provenSafe; - } - @Override public void afterImageWrite(AfterImageWriteAccess a) { /* diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationOptions.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationOptions.java index 939204fcfe42..277cb730b59f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationOptions.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationOptions.java @@ -96,4 +96,7 @@ private static class InitializationValueEager extends InitializationValueTransfo @Option(help = "Assert class initialization is specified for all classes.", type = OptionType.Debug)// public static final HostedOptionKey AssertInitializationSpecifiedForAllClasses = new HostedOptionKey<>(false); + + @Option(help = "Use new class initialization strategy that allows all classes to be used at image build time.", type = OptionType.Expert)// + public static final HostedOptionKey UseNewExperimentalClassInitialization = new HostedOptionKey<>(false); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java index 2c117ed2d657..f6bbfdd3d3ab 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java @@ -24,70 +24,356 @@ */ package com.oracle.svm.hosted.classinitialization; +import static com.oracle.svm.core.SubstrateOptions.TraceClassInitialization; +import static com.oracle.svm.core.SubstrateOptions.TraceObjectInstantiation; + +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.stream.Collectors; import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; import com.oracle.graal.pointsto.constraints.UnsupportedFeatures; +import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.graal.pointsto.reports.ReportUtils; +import com.oracle.graal.pointsto.util.GraalAccess; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.option.SubstrateOptionsParser; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.hosted.LinkAtBuildTimeSupport; +import jdk.internal.misc.Unsafe; +import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaType; /** - * Interface for the class initialization required by the native-image. + * The core class for deciding whether a class should be initialized during image building or class + * initialization should be delayed to runtime. */ -public interface ClassInitializationSupport extends RuntimeClassInitializationSupport { +public abstract class ClassInitializationSupport implements RuntimeClassInitializationSupport { /** - * Returns an init kind for {@code clazz}. + * Setup for class initialization: configured through features and command line input. It + * represents the user desires about class initialization and helps in finding configuration + * issues. + */ + final ClassInitializationConfiguration classInitializationConfiguration = new ClassInitializationConfiguration(); + + /** + * The initialization kind for all classes seen during image building. Classes are inserted into + * this map the first time information was queried and used during image building. This is the + * ground truth about what got initialized during image building. + */ + final ConcurrentMap, InitKind> classInitKinds = new ConcurrentHashMap<>(); + + /** + * These two are intentionally static to keep the reference to objects and classes that were + * initialized in the JDK. + */ + static final Map, StackTraceElement[]> initializedClasses = new ConcurrentHashMap<>(); + /** + * Instantiated objects must be traced using their identities as their hashCode may change + * during the execution. We also want two objects of the same class that have the same hash and + * are equal to be mapped as two distinct entries. + */ + static final Map instantiatedObjects = Collections.synchronizedMap(new IdentityHashMap<>()); + + boolean configurationSealed; + + final ImageClassLoader loader; + + /** + * Non-null while the static analysis is running to allow reporting of class initialization + * errors without immediately aborting image building. */ - InitKind specifiedInitKindFor(Class clazz); + UnsupportedFeatures unsupportedFeatures; + final MetaAccessProvider metaAccess; + + public static ClassInitializationSupport create(MetaAccessProvider metaAccess, ImageClassLoader loader) { + if (ClassInitializationOptions.UseNewExperimentalClassInitialization.getValue()) { + System.out.println("WARNING: using new experimental class initialization strategy. Image size and peak performance are not optimized yet!"); + return new AllowAllHostedUsagesClassInitializationSupport(metaAccess, loader); + } + return new ProvenSafeClassInitializationSupport(metaAccess, loader); + } + + ClassInitializationSupport(MetaAccessProvider metaAccess, ImageClassLoader loader) { + this.metaAccess = metaAccess; + this.loader = loader; + } + + public void setConfigurationSealed(boolean sealed) { + configurationSealed = sealed; + if (configurationSealed && ClassInitializationOptions.PrintClassInitialization.getValue()) { + List allConfigs = classInitializationConfiguration.allConfigs(); + allConfigs.sort(Comparator.comparing(ClassOrPackageConfig::getName)); + String path = Paths.get(Paths.get(SubstrateOptions.Path.getValue()).toString(), "reports").toAbsolutePath().toString(); + ReportUtils.report("class initialization configuration", path, "class_initialization_configuration", "csv", writer -> { + writer.println("Class or Package Name, Initialization Kind, Reasons"); + for (ClassOrPackageConfig config : allConfigs) { + writer.append(config.getName()).append(", ").append(config.getKind().toString()).append(", ") + .append(String.join(" and ", config.getReasons())).append(System.lineSeparator()); + } + }); + } + } + + void setUnsupportedFeatures(UnsupportedFeatures unsupportedFeatures) { + this.unsupportedFeatures = unsupportedFeatures; + } /** - * Return true if the class is allowed to be proven safe. + * Returns an init kind for {@code clazz}. */ - boolean canBeProvenSafe(Class clazz); + InitKind specifiedInitKindFor(Class clazz) { + return classInitializationConfiguration.lookupKind(clazz.getTypeName()).getLeft(); + } + + Boolean isStrictlyDefined(Class clazz) { + return classInitializationConfiguration.lookupKind(clazz.getTypeName()).getRight(); + } /** * Returns all classes of a single {@link InitKind}. */ - Set> classesWithKind(InitKind kind); + Set> classesWithKind(InitKind kind) { + return classInitKinds.entrySet().stream() + .filter(e -> e.getValue() == kind) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + } /** * Returns true if the provided type should be initialized at runtime. */ - boolean shouldInitializeAtRuntime(ResolvedJavaType type); + public boolean shouldInitializeAtRuntime(ResolvedJavaType type) { + return computeInitKindAndMaybeInitializeClass(getJavaClass(type)) != InitKind.BUILD_TIME; + } /** * Returns true if the provided class should be initialized at runtime. */ - boolean shouldInitializeAtRuntime(Class clazz); + public boolean shouldInitializeAtRuntime(Class clazz) { + return computeInitKindAndMaybeInitializeClass(clazz) != InitKind.BUILD_TIME; + } /** * Initializes the class during image building, unless initialization must be delayed to * runtime. */ - void maybeInitializeHosted(ResolvedJavaType type); + public void maybeInitializeHosted(ResolvedJavaType type) { + computeInitKindAndMaybeInitializeClass(getJavaClass(type)); + } + + /** + * Ensure class is initialized. Report class initialization errors in a user-friendly way if + * class initialization fails. + */ + InitKind ensureClassInitialized(Class clazz, boolean allowErrors) { + try { + Unsafe.getUnsafe().ensureClassInitialized(clazz); + return InitKind.BUILD_TIME; + } catch (NoClassDefFoundError ex) { + if (allowErrors || !LinkAtBuildTimeSupport.singleton().linkAtBuildTime(clazz)) { + return InitKind.RUN_TIME; + } else { + return reportInitializationError("Class initialization of " + clazz.getTypeName() + " failed. " + + LinkAtBuildTimeSupport.singleton().errorMessageFor(clazz) + " " + + instructionsToInitializeAtRuntime(clazz), clazz, ex); + } + } catch (Throwable t) { + if (allowErrors) { + return InitKind.RUN_TIME; + } else { + return reportInitializationError("Class initialization of " + clazz.getTypeName() + " failed. " + + instructionsToInitializeAtRuntime(clazz), clazz, t); + } + } + } + + private InitKind reportInitializationError(String msg, Class clazz, Throwable t) { + if (unsupportedFeatures != null) { + /* + * Report an unsupported feature during static analysis, so that we can collect multiple + * error messages without aborting analysis immediately. Returning InitKind.RUN_TIME + * ensures that analysis can continue, even though eventually an error is reported (so + * no image will be created). + */ + unsupportedFeatures.addMessage(clazz.getTypeName(), null, msg, null, t); + return InitKind.RUN_TIME; + } else { + throw UserError.abort(t, "%s", msg); + } + } + + private static String instructionsToInitializeAtRuntime(Class clazz) { + return "Use the option " + SubstrateOptionsParser.commandArgument(ClassInitializationOptions.ClassInitialization, clazz.getTypeName(), "initialize-at-run-time") + + " to explicitly request delayed initialization of this class."; + } + + static Class getJavaClass(ResolvedJavaType type) { + return OriginalClassProvider.getJavaClass(GraalAccess.getOriginalSnippetReflection(), type); + } + + @Override + public void initializeAtRunTime(String name, String reason) { + UserError.guarantee(!configurationSealed, "The class initialization configuration can be changed only before the phase analysis."); + Class clazz = loader.findClass(name).get(); + if (clazz != null) { + classInitializationConfiguration.insert(name, InitKind.RUN_TIME, reason, true); + initializeAtRunTime(clazz, reason); + } else { + classInitializationConfiguration.insert(name, InitKind.RUN_TIME, reason, false); + } + } + + @Override + public void initializeAtBuildTime(String name, String reason) { + UserError.guarantee(!configurationSealed, "The class initialization configuration can be changed only before the phase analysis."); + + Class clazz = loader.findClass(name).get(); + if (clazz != null) { + classInitializationConfiguration.insert(name, InitKind.BUILD_TIME, reason, true); + initializeAtBuildTime(clazz, reason); + } else { + classInitializationConfiguration.insert(name, InitKind.BUILD_TIME, reason, false); + } + } + + @Override + public void rerunInitialization(String name, String reason) { + UserError.guarantee(!configurationSealed, "The class initialization configuration can be changed only before the phase analysis."); + Class clazz = loader.findClass(name).get(); + if (clazz != null) { + classInitializationConfiguration.insert(name, InitKind.RERUN, reason, true); + rerunInitialization(clazz, reason); + } else { + classInitializationConfiguration.insert(name, InitKind.RERUN, reason, false); + } + } + + static boolean isClassListedInStringOption(String option, Class clazz) { + return Arrays.asList(option.split(",")).contains(clazz.getName()); + } + + private static boolean isObjectInstantiationForClassTracked(Class clazz) { + return TraceObjectInstantiation.hasBeenSet() && isClassListedInStringOption(TraceObjectInstantiation.getValue(), clazz); + } + + public String objectInstantiationTraceMessage(Object obj, String action) { + if (!isObjectInstantiationForClassTracked(obj.getClass())) { + return " To see how this object got instantiated use " + SubstrateOptionsParser.commandArgument(TraceObjectInstantiation, obj.getClass().getName()) + "."; + } else if (instantiatedObjects.containsKey(obj)) { + String culprit = null; + StackTraceElement[] trace = instantiatedObjects.get(obj); + boolean containsLambdaMetaFactory = false; + for (StackTraceElement stackTraceElement : trace) { + if (stackTraceElement.getMethodName().equals("")) { + culprit = stackTraceElement.getClassName(); + } + if (stackTraceElement.getClassName().equals("java.lang.invoke.LambdaMetafactory")) { + containsLambdaMetaFactory = true; + } + } + if (containsLambdaMetaFactory) { + return " Object was instantiated through a lambda (https://github.com/oracle/graal/issues/1218). Try marking " + obj.getClass().getTypeName() + + " for build-time initialization with " + SubstrateOptionsParser.commandArgument( + ClassInitializationOptions.ClassInitialization, obj.getClass().getTypeName(), "initialize-at-build-time") + + "."; + } else if (culprit != null) { + return " Object has been initialized by the " + culprit + " class initializer with a trace: \n " + getTraceString(instantiatedObjects.get(obj)) + ". " + action; + } else { + return " Object has been initialized through the following trace:\n" + getTraceString(instantiatedObjects.get(obj)) + ". " + action; + } + } else { + return " Object has been initialized without the native-image initialization instrumentation and the stack trace can't be tracked."; + } + } + + static String getTraceString(StackTraceElement[] trace) { + StringBuilder b = new StringBuilder(); + + for (int i = 0; i < trace.length; i++) { + StackTraceElement stackTraceElement = trace[i]; + b.append("\tat ").append(stackTraceElement.toString()).append("\n"); + } + + return b.toString(); + } + + @Override + public void reportClassInitialized(Class clazz, StackTraceElement[] stackTrace) { + assert TraceClassInitialization.hasBeenSet(); + initializedClasses.put(clazz, relevantStackTrace(stackTrace)); + /* + * We don't do early failing here. Lambdas tend to initialize many classes that should not + * be initialized, but effectively they do not change the behavior of the final image. + * + * Failing early here creates many unnecessary constraints and reduces usability. + */ + } + + @Override + public void reportObjectInstantiated(Object o, StackTraceElement[] stackTrace) { + assert TraceObjectInstantiation.hasBeenSet(); + instantiatedObjects.putIfAbsent(o, relevantStackTrace(stackTrace)); + } + + /** + * If the stack trace contains class initializaiton takes the stack up to the last + * initialization. Otherwise returns the whole stack trace. The method never returns the stack + * from the instrumented part. + * + * This method can be refined on a case-by-case basis to print nicer traces. + * + * @return a stack trace that led to erroneous situation + */ + private static StackTraceElement[] relevantStackTrace(StackTraceElement[] stack) { + ArrayList filteredStack = new ArrayList<>(); + int lastClinit = 0; + boolean containsLambdaMetaFactory = false; + for (int i = 0; i < stack.length; i++) { + StackTraceElement stackTraceElement = stack[i]; + if ("".equals(stackTraceElement.getMethodName())) { + lastClinit = i; + } + if (stackTraceElement.getClassName().equals("java.lang.invoke.LambdaMetafactory")) { + containsLambdaMetaFactory = true; + } + filteredStack.add(stackTraceElement); + } + List finalStack = lastClinit != 0 && !containsLambdaMetaFactory ? filteredStack.subList(0, lastClinit + 1) : filteredStack; + return finalStack.toArray(new StackTraceElement[0]); + } /** * Initializes the class during image building, and reports an error if the user requested to * delay initialization to runtime. */ - void forceInitializeHosted(Class clazz, String reason, boolean allowInitializationErrors); + public abstract void forceInitializeHosted(Class clazz, String reason, boolean allowInitializationErrors); + + abstract InitKind computeInitKindAndMaybeInitializeClass(Class clazz); + + abstract String reasonForClass(Class clazz); /** * Check that all registered classes are here, regardless if the AnalysisType got actually * marked as used. Class initialization can have side effects on other classes without the class * being used itself, e.g., a class initializer can write a static field in another class. */ - boolean checkDelayedInitialization(); - - void setUnsupportedFeatures(UnsupportedFeatures o); - - void setConfigurationSealed(boolean sealed); - - String objectInstantiationTraceMessage(Object obj, String action); - - String reasonForClass(Class clazz); + abstract boolean checkDelayedInitialization(); - void setProvenSafeLate(Set> initializeSafeDelayedClasses); + abstract void doLateInitialization(AnalysisUniverse universe, AnalysisMetaAccess aMetaAccess); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassOrPackageConfig.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassOrPackageConfig.java index d5c911cf77f6..07ac13e81bbf 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassOrPackageConfig.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassOrPackageConfig.java @@ -26,7 +26,7 @@ import org.graalvm.collections.EconomicSet; -public class ClassOrPackageConfig { +final class ClassOrPackageConfig { private final String name; private final EconomicSet reasons; private final InitKind kind; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/EarlyClassInitializerAnalysis.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/EarlyClassInitializerAnalysis.java index 5d39898317bb..ba693fdf3569 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/EarlyClassInitializerAnalysis.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/EarlyClassInitializerAnalysis.java @@ -90,20 +90,20 @@ * initialized anyway, the parsing is aborted using a * {@link ClassInitializerHasSideEffectsException} as soon as one of the tests fail. * - * To make the analysis inter-procedural, {@link ConfigurableClassInitialization} is used when a - * not-yet-initialized type is found. This can then lead to a recursive invocation of this early + * To make the analysis inter-procedural, {@link ProvenSafeClassInitializationSupport} is used when + * a not-yet-initialized type is found. This can then lead to a recursive invocation of this early * class initializer analysis. To avoid infinite recursion when class initializers have cyclic * dependencies, the analysis bails out when a cycle is detected. As with all analysis done by - * {@link ConfigurableClassInitialization}, there is no synchronization between threads, so the same - * class and the same dependencies can be concurrently analyzed by multiple threads. + * {@link ProvenSafeClassInitializationSupport}, there is no synchronization between threads, so the + * same class and the same dependencies can be concurrently analyzed by multiple threads. */ final class EarlyClassInitializerAnalysis { - private final ConfigurableClassInitialization classInitializationSupport; + private final ProvenSafeClassInitializationSupport classInitializationSupport; private final Providers originalProviders; private final HighTierContext context; - EarlyClassInitializerAnalysis(ConfigurableClassInitialization classInitializationSupport) { + EarlyClassInitializerAnalysis(ProvenSafeClassInitializationSupport classInitializationSupport) { this.classInitializationSupport = classInitializationSupport; originalProviders = GraalAccess.getOriginalProviders(); @@ -204,7 +204,7 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaType type, Supplier, InitKind> classInitKinds = new ConcurrentHashMap<>(); - - /** - * These two are intentionally static to keep the reference to objects and classes that were - * initialized in the JDK. - */ - private static final Map, StackTraceElement[]> initializedClasses = new ConcurrentHashMap<>(); - /** - * Instantiated objects must be traced using their identities as their hashCode may change - * during the execution. We also want two objects of the same class that have the same hash and - * are equal to be mapped as two distinct entries. - */ - private static final Map instantiatedObjects = Collections.synchronizedMap(new IdentityHashMap<>()); - - private boolean configurationSealed; - - final ImageClassLoader loader; - - /** - * Non-null while the static analysis is running to allow reporting of class initialization - * errors without immediately aborting image building. - */ - private UnsupportedFeatures unsupportedFeatures; - protected MetaAccessProvider metaAccess; + private static final Field dynamicHubClassInitializationInfoField = ReflectionUtil.lookupField(DynamicHub.class, "classInitializationInfo"); private final EarlyClassInitializerAnalysis earlyClassInitializerAnalysis; private Set> provenSafeEarly = Collections.synchronizedSet(new HashSet<>()); private Set> provenSafeLate = Collections.synchronizedSet(new HashSet<>()); - public ConfigurableClassInitialization(MetaAccessProvider metaAccess, ImageClassLoader loader) { - this.metaAccess = metaAccess; - this.loader = loader; + ProvenSafeClassInitializationSupport(MetaAccessProvider metaAccess, ImageClassLoader loader) { + super(metaAccess, loader); this.earlyClassInitializerAnalysis = new EarlyClassInitializerAnalysis(this); } @Override - public void setConfigurationSealed(boolean sealed) { - configurationSealed = sealed; - if (configurationSealed && ClassInitializationOptions.PrintClassInitialization.getValue()) { - List allConfigs = classInitializationConfiguration.allConfigs(); - allConfigs.sort(Comparator.comparing(ClassOrPackageConfig::getName)); - String path = Paths.get(Paths.get(SubstrateOptions.Path.getValue()).toString(), "reports").toAbsolutePath().toString(); - ReportUtils.report("class initialization configuration", path, "class_initialization_configuration", "csv", writer -> { - writer.println("Class or Package Name, Initialization Kind, Reasons"); - for (ClassOrPackageConfig config : allConfigs) { - writer.append(config.getName()).append(", ").append(config.getKind().toString()).append(", ") - .append(String.join(" and ", config.getReasons())).append(System.lineSeparator()); - } - }); - } - } - - @Override - public void setUnsupportedFeatures(UnsupportedFeatures unsupportedFeatures) { - this.unsupportedFeatures = unsupportedFeatures; - } - - private InitKind computeInitKindAndMaybeInitializeClass(Class clazz) { + InitKind computeInitKindAndMaybeInitializeClass(Class clazz) { return computeInitKindAndMaybeInitializeClass(clazz, true, null); } - @Override - public InitKind specifiedInitKindFor(Class clazz) { - return classInitializationConfiguration.lookupKind(clazz.getTypeName()).getLeft(); - } - - @Override - public boolean canBeProvenSafe(Class clazz) { + boolean canBeProvenSafe(Class clazz) { InitKind initKind = specifiedInitKindFor(clazz); return initKind == null || (initKind.isRunTime() && !isStrictlyDefined(clazz)); } - private Boolean isStrictlyDefined(Class clazz) { - return classInitializationConfiguration.lookupKind(clazz.getTypeName()).getRight(); - } - - @Override - public Set> classesWithKind(InitKind kind) { - return classInitKinds.entrySet().stream() - .filter(e -> e.getValue() == kind) - .map(Map.Entry::getKey) - .collect(Collectors.toSet()); - } - - @Override - public boolean shouldInitializeAtRuntime(ResolvedJavaType type) { - return computeInitKindAndMaybeInitializeClass(getJavaClass(type)) != InitKind.BUILD_TIME; - } - - @Override - public boolean shouldInitializeAtRuntime(Class clazz) { - return computeInitKindAndMaybeInitializeClass(clazz) != InitKind.BUILD_TIME; - } - - @Override - public void maybeInitializeHosted(ResolvedJavaType type) { - computeInitKindAndMaybeInitializeClass(getJavaClass(type)); - } - - /** - * Ensure class is initialized. Report class initialization errors in a user-friendly way if - * class initialization fails. - */ - private InitKind ensureClassInitialized(Class clazz, boolean allowErrors) { - try { - Unsafe.getUnsafe().ensureClassInitialized(clazz); - return InitKind.BUILD_TIME; - } catch (NoClassDefFoundError ex) { - if (allowErrors || !LinkAtBuildTimeSupport.singleton().linkAtBuildTime(clazz)) { - return InitKind.RUN_TIME; - } else { - return reportInitializationError("Class initialization of " + clazz.getTypeName() + " failed. " + - LinkAtBuildTimeSupport.singleton().errorMessageFor(clazz) + " " + - instructionsToInitializeAtRuntime(clazz), clazz, ex); - } - } catch (Throwable t) { - if (allowErrors) { - return InitKind.RUN_TIME; - } else { - return reportInitializationError("Class initialization of " + clazz.getTypeName() + " failed. " + - instructionsToInitializeAtRuntime(clazz), clazz, t); - } - } - } - - private InitKind reportInitializationError(String msg, Class clazz, Throwable t) { - if (unsupportedFeatures != null) { - /* - * Report an unsupported feature during static analysis, so that we can collect multiple - * error messages without aborting analysis immediately. Returning InitKind.RUN_TIME - * ensures that analysis can continue, even though eventually an error is reported (so - * no image will be created). - */ - unsupportedFeatures.addMessage(clazz.getTypeName(), null, msg, null, t); - return InitKind.RUN_TIME; - } else { - throw UserError.abort(t, "%s", msg); - } - } - - private static String instructionsToInitializeAtRuntime(Class clazz) { - return "Use the option " + SubstrateOptionsParser.commandArgument(ClassInitializationOptions.ClassInitialization, clazz.getTypeName(), "initialize-at-run-time") + - " to explicitly request delayed initialization of this class."; - } - - static Class getJavaClass(ResolvedJavaType type) { - return OriginalClassProvider.getJavaClass(GraalAccess.getOriginalSnippetReflection(), type); - } - - @Override - public void initializeAtRunTime(String name, String reason) { - UserError.guarantee(!configurationSealed, "The class initialization configuration can be changed only before the phase analysis."); - Class clazz = loader.findClass(name).get(); - if (clazz != null) { - classInitializationConfiguration.insert(name, InitKind.RUN_TIME, reason, true); - initializeAtRunTime(clazz, reason); - } else { - classInitializationConfiguration.insert(name, InitKind.RUN_TIME, reason, false); - } - } - - @Override - public void initializeAtBuildTime(String name, String reason) { - UserError.guarantee(!configurationSealed, "The class initialization configuration can be changed only before the phase analysis."); - - Class clazz = loader.findClass(name).get(); - if (clazz != null) { - classInitializationConfiguration.insert(name, InitKind.BUILD_TIME, reason, true); - initializeAtBuildTime(clazz, reason); - } else { - classInitializationConfiguration.insert(name, InitKind.BUILD_TIME, reason, false); - } - } - - @Override - public void rerunInitialization(String name, String reason) { - UserError.guarantee(!configurationSealed, "The class initialization configuration can be changed only before the phase analysis."); - Class clazz = loader.findClass(name).get(); - if (clazz != null) { - classInitializationConfiguration.insert(name, InitKind.RERUN, reason, true); - rerunInitialization(clazz, reason); - } else { - classInitializationConfiguration.insert(name, InitKind.RERUN, reason, false); - } - } - @Override public void initializeAtRunTime(Class clazz, String reason) { UserError.guarantee(!configurationSealed, "The class initialization configuration can be changed only before the phase analysis."); @@ -287,18 +105,10 @@ public void initializeAtRunTime(Class clazz, String reason) { } } - private static boolean isClassListedInStringOption(String option, Class clazz) { - return Arrays.asList(option.split(",")).contains(clazz.getName()); - } - private static boolean isClassInitializationTracked(Class clazz) { return TraceClassInitialization.hasBeenSet() && isClassListedInStringOption(TraceClassInitialization.getValue(), clazz); } - private static boolean isObjectInstantiationForClassTracked(Class clazz) { - return TraceObjectInstantiation.hasBeenSet() && isClassListedInStringOption(TraceObjectInstantiation.getValue(), clazz); - } - private static String classInitializationErrorMessage(Class clazz, String action) { if (!isClassInitializationTracked(clazz)) { return "To see why " + clazz.getName() + " got initialized use " + SubstrateOptionsParser.commandArgument(TraceClassInitialization, clazz.getName()); @@ -322,38 +132,7 @@ private static String classInitializationErrorMessage(Class clazz, String act } @Override - public String objectInstantiationTraceMessage(Object obj, String action) { - if (!isObjectInstantiationForClassTracked(obj.getClass())) { - return " To see how this object got instantiated use " + SubstrateOptionsParser.commandArgument(TraceObjectInstantiation, obj.getClass().getName()) + "."; - } else if (instantiatedObjects.containsKey(obj)) { - String culprit = null; - StackTraceElement[] trace = instantiatedObjects.get(obj); - boolean containsLambdaMetaFactory = false; - for (StackTraceElement stackTraceElement : trace) { - if (stackTraceElement.getMethodName().equals("")) { - culprit = stackTraceElement.getClassName(); - } - if (stackTraceElement.getClassName().equals("java.lang.invoke.LambdaMetafactory")) { - containsLambdaMetaFactory = true; - } - } - if (containsLambdaMetaFactory) { - return " Object was instantiated through a lambda (https://github.com/oracle/graal/issues/1218). Try marking " + obj.getClass().getTypeName() + - " for build-time initialization with " + SubstrateOptionsParser.commandArgument( - ClassInitializationOptions.ClassInitialization, obj.getClass().getTypeName(), "initialize-at-build-time") + - "."; - } else if (culprit != null) { - return " Object has been initialized by the " + culprit + " class initializer with a trace: \n " + getTraceString(instantiatedObjects.get(obj)) + ". " + action; - } else { - return " Object has been initialized through the following trace:\n" + getTraceString(instantiatedObjects.get(obj)) + ". " + action; - } - } else { - return " Object has been initialized without the native-image initialization instrumentation and the stack trace can't be tracked."; - } - } - - @Override - public String reasonForClass(Class clazz) { + String reasonForClass(Class clazz) { InitKind initKind = classInitKinds.get(clazz); String reason = classInitializationConfiguration.lookupReason(clazz.getTypeName()); if (initKind == InitKind.BUILD_TIME && provenSafeEarly.contains(clazz)) { @@ -377,17 +156,6 @@ public static Map, StackTraceElement[]> getInitializedClasses() { return initializedClasses; } - public static String getTraceString(StackTraceElement[] trace) { - StringBuilder b = new StringBuilder(); - - for (int i = 0; i < trace.length; i++) { - StackTraceElement stackTraceElement = trace[i]; - b.append("\tat ").append(stackTraceElement.toString()).append("\n"); - } - - return b.toString(); - } - @Override public void rerunInitialization(Class clazz, String reason) { UserError.guarantee(!configurationSealed, "The class initialization configuration can be changed only before the phase analysis."); @@ -442,51 +210,6 @@ private void setSubclassesAsRunTime(Class clazz) { .forEach(c -> classInitializationConfiguration.insert(c.getTypeName(), InitKind.RUN_TIME, "subtype of " + clazz.getTypeName(), true)); } - @Override - public void reportClassInitialized(Class clazz, StackTraceElement[] stackTrace) { - assert TraceClassInitialization.hasBeenSet(); - initializedClasses.put(clazz, relevantStackTrace(stackTrace)); - /* - * We don't do early failing here. Lambdas tend to initialize many classes that should not - * be initialized, but effectively they do not change the behavior of the final image. - * - * Failing early here creates many unnecessary constraints and reduces usability. - */ - } - - @Override - public void reportObjectInstantiated(Object o, StackTraceElement[] stackTrace) { - assert TraceObjectInstantiation.hasBeenSet(); - instantiatedObjects.putIfAbsent(o, relevantStackTrace(stackTrace)); - } - - /** - * If the stack trace contains class initializaiton takes the stack up to the last - * initialization. Otherwise returns the whole stack trace. The method never returns the stack - * from the instrumented part. - * - * This method can be refined on a case-by-case basis to print nicer traces. - * - * @return a stack trace that led to erroneous situation - */ - private static StackTraceElement[] relevantStackTrace(StackTraceElement[] stack) { - ArrayList filteredStack = new ArrayList<>(); - int lastClinit = 0; - boolean containsLambdaMetaFactory = false; - for (int i = 0; i < stack.length; i++) { - StackTraceElement stackTraceElement = stack[i]; - if ("".equals(stackTraceElement.getMethodName())) { - lastClinit = i; - } - if (stackTraceElement.getClassName().equals("java.lang.invoke.LambdaMetafactory")) { - containsLambdaMetaFactory = true; - } - filteredStack.add(stackTraceElement); - } - List finalStack = lastClinit != 0 && !containsLambdaMetaFactory ? filteredStack.subList(0, lastClinit + 1) : filteredStack; - return finalStack.toArray(new StackTraceElement[0]); - } - @Override public void forceInitializeHosted(Class clazz, String reason, boolean allowInitializationErrors) { if (clazz == null) { @@ -513,7 +236,7 @@ private void forceInitializeInterfaces(Class[] interfaces, String reason) { } @Override - public boolean checkDelayedInitialization() { + boolean checkDelayedInitialization() { /* * We check all registered classes here, regardless if the AnalysisType got actually marked * as used. Class initialization can have side effects on other classes without the class @@ -677,7 +400,66 @@ void addProvenEarly(Class clazz) { } @Override - public void setProvenSafeLate(Set> classes) { - provenSafeLate = new HashSet<>(classes); + void doLateInitialization(AnalysisUniverse aUniverse, AnalysisMetaAccess aMetaAccess) { + TypeInitializerGraph initGraph = new TypeInitializerGraph(this, aUniverse); + initGraph.computeInitializerSafety(); + provenSafeLate = initializeSafeDelayedClasses(initGraph, aUniverse, aMetaAccess); + if (ClassInitializationOptions.PrintClassInitialization.getValue()) { + reportInitializerDependencies(aUniverse, initGraph, SubstrateOptions.reportsPath()); + } + } + + private static void reportInitializerDependencies(AnalysisUniverse universe, TypeInitializerGraph initGraph, String path) { + ReportUtils.report("class initialization dependencies", path, "class_initialization_dependencies", "dot", writer -> { + writer.println("digraph class_initializer_dependencies {"); + universe.getTypes().stream() + .filter(ProvenSafeClassInitializationSupport::isRelevantForPrinting) + .forEach(t -> writer.println(quote(t.toClassName()) + "[fillcolor=" + (initGraph.isUnsafe(t) ? "red" : "green") + "]")); + universe.getTypes().stream() + .filter(ProvenSafeClassInitializationSupport::isRelevantForPrinting) + .forEach(t -> initGraph.getDependencies(t) + .forEach(t1 -> writer.println(quote(t.toClassName()) + " -> " + quote(t1.toClassName())))); + writer.println("}"); + }); + } + + private static boolean isRelevantForPrinting(AnalysisType type) { + return !type.isPrimitive() && !type.isArray() && type.isReachable(); + } + + private static String quote(String className) { + return "\"" + className + "\""; + } + + /** + * Initializes all classes that are considered delayed by the system. Classes specified by the + * user will not be delayed. + */ + private Set> initializeSafeDelayedClasses(TypeInitializerGraph initGraph, AnalysisUniverse aUniverse, AnalysisMetaAccess aMetaAccess) { + Set> provenSafe = new HashSet<>(); + setConfigurationSealed(false); + classesWithKind(RUN_TIME).stream() + .filter(t -> aMetaAccess.optionalLookupJavaType(t).isPresent()) + .filter(t -> aMetaAccess.lookupJavaType(t).isReachable()) + .filter(t -> canBeProvenSafe(t)) + .forEach(c -> { + AnalysisType type = aMetaAccess.lookupJavaType(c); + if (!initGraph.isUnsafe(type)) { + forceInitializeHosted(c, "proven safe to initialize", true); + /* + * See if initialization worked--it can fail due to implicit + * exceptions. + */ + if (!shouldInitializeAtRuntime(c)) { + provenSafe.add(c); + ClassInitializationInfo initializationInfo = type.getClassInitializer() == null ? ClassInitializationInfo.NO_INITIALIZER_INFO_SINGLETON + : ClassInitializationInfo.INITIALIZED_INFO_SINGLETON; + DynamicHub hub = ((SVMHost) aUniverse.hostVM()).dynamicHub(type); + hub.setClassInitializationInfo(initializationInfo); + aUniverse.getHeapScanner().rescanField(hub, dynamicHubClassInitializationInfoField); + } + } + }); + return provenSafe; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/TypeInitializerGraph.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/TypeInitializerGraph.java index 9a2ed78f4351..031cbe3ea797 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/TypeInitializerGraph.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/TypeInitializerGraph.java @@ -64,9 +64,9 @@ * every load, store, call, and instantiation in the bytecode. These dependencies are collected in * {@link SVMHost#getInitializedClasses}. */ -public class TypeInitializerGraph { +final class TypeInitializerGraph { private final SVMHost hostVM; - private ClassInitializationSupport classInitializationSupport; + private final ProvenSafeClassInitializationSupport classInitializationSupport; private enum Safety { SAFE, @@ -79,9 +79,9 @@ private enum Safety { private final Map methodSafety = new HashMap<>(); private final Collection methods; - TypeInitializerGraph(AnalysisUniverse universe) { + TypeInitializerGraph(ProvenSafeClassInitializationSupport classInitializationSupport, AnalysisUniverse universe) { hostVM = ((SVMHost) universe.hostVM()); - classInitializationSupport = hostVM.getClassInitializationSupport(); + this.classInitializationSupport = classInitializationSupport; universe.getTypes().forEach(this::addInitializer); universe.getTypes().forEach(this::addInitializerDependencies); 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 a0431ebfc456..fb1facc52306 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 @@ -107,10 +107,12 @@ public void afterRegistration(AfterRegistrationAccess access) { rci.initializeAtRunTime("com.sun.naming.internal.ResourceManager$AppletParameter", "Initializes AWT"); rci.initializeAtBuildTime("java.awt.font.TextAttribute", "Required for sun.text.bidi.BidiBase.NumericShapings"); 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"); /* XML-related */ 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); /* Security services */ rci.initializeAtBuildTime("com.sun.crypto.provider", JDK_CLASS_REASON); @@ -133,6 +135,7 @@ public void afterRegistration(AfterRegistrationAccess access) { rci.initializeAtBuildTime("sun.security.internal.spec", JDK_CLASS_REASON); rci.initializeAtBuildTime("sun.security.jca", JDK_CLASS_REASON); rci.initializeAtBuildTime("sun.security.jgss", JDK_CLASS_REASON); + rci.initializeAtBuildTime("org.ietf.jgss.Oid", JDK_CLASS_REASON); rci.initializeAtBuildTime("org.ietf.jgss.GSSException", JDK_CLASS_REASON); rci.initializeAtBuildTime("org.ietf.jgss.GSSName", JDK_CLASS_REASON); rci.initializeAtBuildTime("sun.security.krb5", JDK_CLASS_REASON); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java index cf8b26764e4c..8315b5a2c174 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java @@ -46,6 +46,7 @@ import java.util.MissingResourceException; import java.util.Objects; import java.util.ResourceBundle; +import java.util.ServiceLoader; import java.util.Set; import java.util.concurrent.ForkJoinPool; import java.util.function.Function; @@ -54,6 +55,7 @@ import java.util.spi.CurrencyNameProvider; import java.util.spi.LocaleNameProvider; import java.util.spi.LocaleServiceProvider; +import java.util.spi.ResourceBundleControlProvider; import java.util.spi.TimeZoneNameProvider; import org.graalvm.collections.Pair; @@ -67,6 +69,7 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; import com.oracle.svm.core.ClassLoaderSupport; import com.oracle.svm.core.annotate.AutomaticFeature; @@ -289,6 +292,10 @@ public void duringSetup(DuringSetupAccess a) { } candidatesCacheField = access.findField("java.util.ResourceBundle$Control", "CANDIDATES_CACHE"); localeObjectCacheMapField = access.findField(LocaleObjectCache.class, "map"); + + String reason = "All ResourceBundleControlProvider that are registered as services end up as objects in the image heap, and are therefore registered to be initialized at image build time"; + ServiceLoader.load(ResourceBundleControlProvider.class).stream() + .forEach(provider -> ImageSingletons.lookup(RuntimeClassInitializationSupport.class).initializeAtBuildTime(provider.type(), reason)); } /** diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/xml/XMLParsersRegistration.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/xml/XMLParsersRegistration.java index 990d89d889a5..8f5ce06e88b9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/xml/XMLParsersRegistration.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/xml/XMLParsersRegistration.java @@ -38,7 +38,7 @@ import com.oracle.svm.core.configure.ResourcesRegistry; import com.oracle.svm.core.jdk.JNIRegistrationUtil; import com.oracle.svm.hosted.FeatureImpl; -import com.oracle.svm.hosted.classinitialization.ConfigurableClassInitialization; +import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; public abstract class XMLParsersRegistration extends JNIRegistrationUtil { @@ -114,7 +114,7 @@ void registerResources() { /* * To allow register new resource bundle classes during analysis phase */ - ConfigurableClassInitialization classInitializationSupport = (ConfigurableClassInitialization) ImageSingletons.lookup(RuntimeClassInitializationSupport.class); + ClassInitializationSupport classInitializationSupport = (ClassInitializationSupport) ImageSingletons.lookup(RuntimeClassInitializationSupport.class); classInitializationSupport.setConfigurationSealed(false); ResourcesRegistry resourcesRegistry = ImageSingletons.lookup(ResourcesRegistry.class); diff --git a/substratevm/src/com.oracle.svm.polyglot/src/com/oracle/svm/polyglot/scala/ScalaFeature.java b/substratevm/src/com.oracle.svm.polyglot/src/com/oracle/svm/polyglot/scala/ScalaFeature.java index bf345e9f82c1..7a2cd0e7d7f6 100644 --- a/substratevm/src/com.oracle.svm.polyglot/src/com/oracle/svm/polyglot/scala/ScalaFeature.java +++ b/substratevm/src/com.oracle.svm.polyglot/src/com/oracle/svm/polyglot/scala/ScalaFeature.java @@ -67,6 +67,8 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { RuntimeClassInitialization.initializeAtBuildTime("scala.Symbol$"); /* Initialized through an invokedynamic in `scala.Option` */ RuntimeClassInitialization.initializeAtBuildTime("scala.runtime.LambdaDeserialize"); + RuntimeClassInitialization.initializeAtBuildTime("scala.runtime.StructuralCallSite"); + RuntimeClassInitialization.initializeAtBuildTime("scala.runtime.EmptyMethodCache"); ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, ScalaFeature.class, false, "jdk.internal.vm.compiler", "org.graalvm.compiler.nodes"); }