From e163fd3173d77f0b729d9e2a78a2876ad91e1aeb Mon Sep 17 00:00:00 2001 From: Christian Wimmer Date: Mon, 26 Feb 2024 12:32:02 -0800 Subject: [PATCH] Remove the old class initialization strategy --- .../RuntimeClassInitializationSupport.java | 20 +- substratevm/CHANGELOG.md | 1 + substratevm/mx.substratevm/mx_substratevm.py | 28 +- .../svm/core/jdk/JNIRegistrationUtil.java | 4 +- .../svm/core/jdk/JavaNetHttpFeature.java | 2 +- .../oracle/svm/core/jdk/RecomputedFields.java | 2 +- .../svm/hosted/NativeImageGenerator.java | 2 +- .../src/com/oracle/svm/hosted/SVMHost.java | 69 --- .../svm/hosted/SecurityServicesFeature.java | 57 +-- ...ostedUsagesClassInitializationSupport.java | 228 --------- .../ClassInitializationFeature.java | 109 ++-- .../ClassInitializationOptions.java | 61 +-- .../ClassInitializationSupport.java | 188 +++++-- .../EarlyClassInitializerAnalysis.java | 316 ------------ .../hosted/classinitialization/InitKind.java | 63 --- .../ProvenSafeClassInitializationSupport.java | 465 ------------------ .../TypeInitializerGraph.java | 263 ---------- .../hosted/jdk/JDKInitializationFeature.java | 60 +-- .../svm/hosted/jdk/JDKRegistrations.java | 14 +- .../hosted/jdk/JNIRegistrationJavaNet.java | 6 +- .../hosted/jdk/JNIRegistrationJavaNio.java | 22 +- .../jdk/JNIRegistrationManagementExt.java | 2 +- .../svm/hosted/jdk/JNIRegistrationPrefs.java | 4 +- .../hosted/jdk/JNIRegistrationsJavaZip.java | 6 +- .../test/clinit/TestClassInitialization.java | 363 +++++--------- .../native-image.properties | 2 +- 26 files changed, 466 insertions(+), 1891 deletions(-) delete mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/AllowAllHostedUsagesClassInitializationSupport.java delete mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/EarlyClassInitializerAnalysis.java delete mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ProvenSafeClassInitializationSupport.java delete mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/TypeInitializerGraph.java diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeClassInitializationSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeClassInitializationSupport.java index 5458f1f1e758..508dba7cdbe6 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeClassInitializationSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeClassInitializationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -46,11 +46,25 @@ public interface RuntimeClassInitializationSupport { void initializeAtBuildTime(String name, String reason); - void rerunInitialization(String name, String reason); + @Deprecated + default void rerunInitialization(String name, String reason) { + /* + * There is no more difference between initializing a class at run-time and re-running the + * class initializer at run time. + */ + initializeAtRunTime(name, reason); + } void initializeAtRunTime(Class aClass, String reason); - void rerunInitialization(Class aClass, String reason); + @Deprecated + default void rerunInitialization(Class aClass, String reason) { + /* + * There is no more difference between initializing a class at run-time and re-running the + * class initializer at run time. + */ + initializeAtRunTime(aClass, reason); + } void initializeAtBuildTime(Class aClass, String reason); } diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index f61bbea50cf3..dae152d89b27 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -3,6 +3,7 @@ This changelog summarizes major changes to GraalVM Native Image. ## GraalVM for JDK 23 (Internal Version 24.1.0) +* (GR-51520) The old class initialization strategy, which was deprecated in GraalVM for JDK 22, is removed. The option `StrictImageHeap` no longer has any effect. * (GR-51106) Fields that are accessed via a `VarHandle` or `MethodHandle` are no longer marked as "unsafe accessed" when the `VarHandle`/`MethodHandle` can be fully intrinsified. * (GR-49996) Ensure explicitly set image name (e.g., via `-o imagename`) is not accidentally overwritten by `-jar jarfile` option. * (GR-48683) Together with Red Hat, we added partial support for the JFR event `OldObjectSample`. diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 31002de1aacf..81cdbd7469e9 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -1549,11 +1549,7 @@ def cinterfacetutorial(args): @mx.command(suite.name, 'clinittest', 'Runs the ') def clinittest(args): - def build_and_test_clinittest_images(native_image, args=None): - build_and_test_clinittest_image(native_image, args, True) - build_and_test_clinittest_image(native_image, args, False) - - def build_and_test_clinittest_image(native_image, args, new_class_init_policy): + def build_and_test_clinittest_image(native_image, args): args = [] if args is None else args test_cp = classpath('com.oracle.svm.test') build_dir = join(svmbuild_dir(), 'clinittest') @@ -1563,11 +1559,6 @@ def build_and_test_clinittest_image(native_image, args, new_class_init_policy): mx.rmtree(build_dir) mx.ensure_dir_exists(build_dir) - if new_class_init_policy: - policy_args = svm_experimental_options(['-H:+SimulateClassInitializer']) + ['--features=com.oracle.svm.test.clinit.TestClassInitializationFeatureNewPolicyFeature'] - else: - policy_args = svm_experimental_options(['-H:-StrictImageHeap', '-H:-SimulateClassInitializer']) + ['--features=com.oracle.svm.test.clinit.TestClassInitializationFeatureOldPolicyFeature'] - # Build and run the example binary_path = join(build_dir, 'clinittest') native_image([ @@ -1577,9 +1568,10 @@ def build_and_test_clinittest_image(native_image, args, new_class_init_policy): '-o', binary_path, '-H:+ReportExceptionStackTraces', '-H:Class=com.oracle.svm.test.clinit.TestClassInitialization', + '--features=com.oracle.svm.test.clinit.TestClassInitializationFeature', ] + svm_experimental_options([ '-H:+PrintClassInitialization', - ]) + policy_args + args) + ]) + args) mx.run([binary_path]) # Check the reports for initialized classes @@ -1593,16 +1585,8 @@ def checkLine(line, marker, init_kind, msg, wrongly_initialized_lines): "Classes marked with " + marker + " must have init kind " + init_kind + " and message " + msg)] with open(classes_file) as f: for line in f: - if new_class_init_policy: - checkLine(line, "MustBeSafeEarly", "SIMULATED", "classes are initialized at run time by default", wrongly_initialized_lines) - checkLine(line, "MustBeSafeLate", "SIMULATED", "classes are initialized at run time by default", wrongly_initialized_lines) - checkLine(line, "MustBeSimulated", "SIMULATED", "classes are initialized at run time by default", wrongly_initialized_lines) - checkLine(line, "MustBeDelayed", "RUN_TIME", "classes are initialized at run time by default", wrongly_initialized_lines) - else: - checkLine(line, "MustBeSafeEarly", "BUILD_TIME", "class proven as side-effect free before analysis", wrongly_initialized_lines) - checkLine(line, "MustBeSafeLate", "BUILD_TIME", "class proven as side-effect free after analysis", wrongly_initialized_lines) - checkLine(line, "MustBeSimulated", "RUN_TIME", "classes are initialized at run time by default", wrongly_initialized_lines) - checkLine(line, "MustBeDelayed", "RUN_TIME", "classes are initialized at run time by default", wrongly_initialized_lines) + checkLine(line, "MustBeSimulated", "SIMULATED", "classes are initialized at run time by default", wrongly_initialized_lines) + checkLine(line, "MustBeDelayed", "RUN_TIME", "classes are initialized at run time by default", wrongly_initialized_lines) if len(wrongly_initialized_lines) > 0: msg = "" @@ -1615,7 +1599,7 @@ def checkLine(line, marker, init_kind, msg, wrongly_initialized_lines): check_class_initialization(all_classes_file) - native_image_context_run(build_and_test_clinittest_images, args) + native_image_context_run(build_and_test_clinittest_image, args) class SubstrateJvmFuncsFallbacksBuilder(mx.Project): diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JNIRegistrationUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JNIRegistrationUtil.java index 9dde925b45cc..26090951f48e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JNIRegistrationUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JNIRegistrationUtil.java @@ -65,10 +65,10 @@ protected static boolean isWindows() { return Platform.includedIn(Platform.WINDOWS.class); } - protected static void rerunClassInit(FeatureAccess access, String... classNames) { + protected static void initializeAtRunTime(FeatureAccess access, String... classNames) { RuntimeClassInitializationSupport classInitSupport = ImageSingletons.lookup(RuntimeClassInitializationSupport.class); for (String className : classNames) { - classInitSupport.rerunInitialization(clazz(access, className), "for JDK native code support via JNI"); + classInitSupport.initializeAtRunTime(clazz(access, className), "for JDK native code support via JNI"); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaNetHttpFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaNetHttpFeature.java index 59aa86fb65a7..7691f8d08206 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaNetHttpFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaNetHttpFeature.java @@ -56,7 +56,7 @@ public void afterRegistration(AfterRegistrationAccess access) { public void duringSetup(DuringSetupAccess access) { RuntimeClassInitializationSupport rci = ImageSingletons.lookup(RuntimeClassInitializationSupport.class); rci.initializeAtRunTime("jdk.internal.net.http", "for reading properties at run time"); - rci.rerunInitialization("jdk.internal.net.http.websocket.OpeningHandshake", "contains a SecureRandom reference"); + rci.initializeAtRunTime("jdk.internal.net.http.websocket.OpeningHandshake", "contains a SecureRandom reference"); } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecomputedFields.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecomputedFields.java index 7f151ca7be15..dd179f73e248 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecomputedFields.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecomputedFields.java @@ -296,7 +296,7 @@ private void processFieldUpdater(Object updater) { class InnocuousForkJoinWorkerThreadFeature implements InternalFeature { @Override public void duringSetup(DuringSetupAccess access) { - ImageSingletons.lookup(RuntimeClassInitializationSupport.class).rerunInitialization(access.findClassByName("java.util.concurrent.ForkJoinWorkerThread$InnocuousForkJoinWorkerThread"), + ImageSingletons.lookup(RuntimeClassInitializationSupport.class).initializeAtRunTime(access.findClassByName("java.util.concurrent.ForkJoinWorkerThread$InnocuousForkJoinWorkerThread"), "innocuousThreadGroup must be initialized at run time"); } } 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 b7b11eef8e0c..c7c8a484bd53 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 @@ -896,7 +896,7 @@ protected void setupNativeImage(OptionValues options, Map containsStackValueNode = new ConcurrentHashMap<>(); - private final ConcurrentMap classInitializerSideEffect = new ConcurrentHashMap<>(); - private final ConcurrentMap> initializedClasses = new ConcurrentHashMap<>(); private final ConcurrentMap analysisTrivialMethods = new ConcurrentHashMap<>(); private final Set finalFieldsInitializedOutsideOfConstructor = ConcurrentHashMap.newKeySet(); @@ -649,53 +640,6 @@ public void methodBeforeTypeFlowCreationHook(BigBang bb, AnalysisMethod method, } else if (n instanceof ReachabilityRegistrationNode node) { bb.postTask(debug -> node.getRegistrationTask().ensureDone()); } - checkClassInitializerSideEffect(method, n); - } - } - - /** - * Classes are only safe for automatic initialization if the class initializer has no side - * effect on other classes and cannot be influenced by other classes. Otherwise there would be - * observable side effects. For example, if a class initializer of class A writes a static field - * B.f in class B, then someone could rely on reading the old value of B.f before triggering - * initialization of A. Similarly, if a class initializer of class A reads a static field B.f, - * then an early automatic initialization of class A could read a non-yet-set value of B.f. - * - * Note that it is not necessary to disallow instance field accesses: Objects allocated by the - * class initializer itself can always be accessed because they are independent from other - * initializers; all other objects must be loaded transitively from a static field. - * - * Currently, we are conservative and mark all methods that access static fields as unsafe for - * automatic class initialization (unless the class initializer itself accesses a static field - * of its own class - the common way of initializing static fields). The check could be relaxed - * by tracking the call chain, i.e., allowing static field accesses when the root method of the - * call chain is the class initializer. But this does not fit well into the current approach - * where each method has a `Safety` flag. - */ - private void checkClassInitializerSideEffect(AnalysisMethod method, Node n) { - if (n instanceof AccessFieldNode) { - ResolvedJavaField field = ((AccessFieldNode) n).field(); - if (field.isStatic() && (!method.isClassInitializer() || !field.getDeclaringClass().equals(method.getDeclaringClass()))) { - classInitializerSideEffect.put(method, true); - } - } else if (n instanceof UnsafeAccessNode || n instanceof VMThreadLocalAccess) { - /* - * Unsafe memory access nodes are rare, so it does not pay off to check what kind of - * field they are accessing. - * - * Methods that access a thread-local value cannot be initialized at image build time - * because such values are not available yet. - */ - classInitializerSideEffect.put(method, true); - } else if (n instanceof EnsureClassInitializedNode) { - ResolvedJavaType type = ((EnsureClassInitializedNode) n).constantTypeOrNull(getProviders(method.getMultiMethodKey()).getConstantReflection()); - if (type != null) { - initializedClasses.computeIfAbsent(method, k -> new HashSet<>()).add((AnalysisType) type); - } else { - classInitializerSideEffect.put(method, true); - } - } else if (n instanceof AccessMonitorNode) { - classInitializerSideEffect.put(method, true); } } @@ -714,19 +658,6 @@ public boolean containsStackValueNode(AnalysisMethod method) { return containsStackValueNode.containsKey(method); } - public boolean hasClassInitializerSideEffect(AnalysisMethod method) { - return classInitializerSideEffect.containsKey(method); - } - - public Set getInitializedClasses(AnalysisMethod method) { - Set result = initializedClasses.get(method); - if (result != null) { - return result; - } else { - return Collections.emptySet(); - } - } - public boolean isAnalysisTrivialMethod(AnalysisMethod method) { return analysisTrivialMethods.containsKey(method); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 01ecb7fe153a..add0a32c5ed4 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -234,63 +234,50 @@ public void duringSetup(DuringSetupAccess a) { RuntimeClassInitializationSupport rci = ImageSingletons.lookup(RuntimeClassInitializationSupport.class); /* * The SecureRandom implementations open the /dev/random and /dev/urandom files which are - * used as sources for entropy. These files are opened in the static initializers. That's - * why we rerun the static initializers at runtime. We cannot completely delay the static - * initializers execution to runtime because the SecureRandom classes are needed by the - * native image generator too, e.g., by Files.createTempDirectory(). + * used as sources for entropy. These files are opened in the static initializers. */ - rci.rerunInitialization(NativePRNG.class, "for substitutions"); - rci.rerunInitialization(NativePRNG.Blocking.class, "for substitutions"); - rci.rerunInitialization(NativePRNG.NonBlocking.class, "for substitutions"); + rci.initializeAtRunTime(NativePRNG.class, "for substitutions"); + rci.initializeAtRunTime(NativePRNG.Blocking.class, "for substitutions"); + rci.initializeAtRunTime(NativePRNG.NonBlocking.class, "for substitutions"); - rci.rerunInitialization(clazz(access, "sun.security.provider.SeedGenerator"), "for substitutions"); - rci.rerunInitialization(clazz(access, "sun.security.provider.SecureRandom$SeederHolder"), "for substitutions"); + rci.initializeAtRunTime(clazz(access, "sun.security.provider.SeedGenerator"), "for substitutions"); + rci.initializeAtRunTime(clazz(access, "sun.security.provider.SecureRandom$SeederHolder"), "for substitutions"); /* * sun.security.provider.AbstractDrbg$SeederHolder has a static final EntropySource seeder - * field that needs to be re-initialized at run time because it captures the result of + * field that needs to be initialized at run time because it captures the result of * SeedGenerator.getSystemEntropy(). */ - rci.rerunInitialization(clazz(access, "sun.security.provider.AbstractDrbg$SeederHolder"), "for substitutions"); + rci.initializeAtRunTime(clazz(access, "sun.security.provider.AbstractDrbg$SeederHolder"), "for substitutions"); if (isMscapiModulePresent) { /* PRNG. creates a Cleaner (see JDK-8210476), which starts its thread. */ - rci.rerunInitialization(clazz(access, "sun.security.mscapi.PRNG"), "for substitutions"); + rci.initializeAtRunTime(clazz(access, "sun.security.mscapi.PRNG"), "for substitutions"); } - rci.rerunInitialization(clazz(access, "sun.security.provider.FileInputStreamPool"), "for substitutions"); + rci.initializeAtRunTime(clazz(access, "sun.security.provider.FileInputStreamPool"), "for substitutions"); /* java.util.UUID$Holder has a static final SecureRandom field. */ - rci.rerunInitialization(clazz(access, "java.util.UUID$Holder"), "for substitutions"); + rci.initializeAtRunTime(clazz(access, "java.util.UUID$Holder"), "for substitutions"); - /* - * The classes below have a static final SecureRandom field. Note that if the classes are - * not found as reachable by the analysis registering them for class initialization rerun - * doesn't have any effect. - */ - rci.rerunInitialization(clazz(access, "sun.security.jca.JCAUtil$CachedSecureRandomHolder"), "for substitutions"); - rci.rerunInitialization(clazz(access, "com.sun.crypto.provider.SunJCE$SecureRandomHolder"), "for substitutions"); - optionalClazz(access, "sun.security.krb5.Confounder").ifPresent(clazz -> rci.rerunInitialization(clazz, "for substitutions")); - optionalClazz(access, "sun.security.krb5.Config").ifPresent(clazz -> rci.rerunInitialization(clazz, "Reset the value of lazily initialized field sun.security.krb5.Config#singleton")); + /* The classes below have a static final SecureRandom field. */ + rci.initializeAtRunTime(clazz(access, "sun.security.jca.JCAUtil$CachedSecureRandomHolder"), "for substitutions"); + rci.initializeAtRunTime(clazz(access, "com.sun.crypto.provider.SunJCE$SecureRandomHolder"), "for substitutions"); + optionalClazz(access, "sun.security.krb5.Confounder").ifPresent(clazz -> rci.initializeAtRunTime(clazz, "for substitutions")); + optionalClazz(access, "sun.security.krb5.Config").ifPresent(clazz -> rci.initializeAtRunTime(clazz, "Reset the value of lazily initialized field sun.security.krb5.Config#singleton")); - rci.rerunInitialization(clazz(access, "sun.security.jca.JCAUtil"), "JCAUtil.def holds a SecureRandom."); + rci.initializeAtRunTime(clazz(access, "sun.security.jca.JCAUtil"), "JCAUtil.def holds a SecureRandom."); /* * When SSLContextImpl$DefaultManagersHolder sets-up the TrustManager in its initializer it * gets the value of the -Djavax.net.ssl.trustStore and -Djavax.net.ssl.trustStorePassword - * properties from the build machine. Re-runing its initialization at run time is required - * to use the run time provided values. + * properties from the build machine. Running its initialization at run time is required to + * use the run time provided values. */ - rci.rerunInitialization(clazz(access, "sun.security.ssl.SSLContextImpl$DefaultManagersHolder"), "for reading properties at run time"); + rci.initializeAtRunTime(clazz(access, "sun.security.ssl.SSLContextImpl$DefaultManagersHolder"), "for reading properties at run time"); /* * SSL debug logging enabled by javax.net.debug system property is setup during the class - * initialization of either sun.security.ssl.Debug or sun.security.ssl.SSLLogger. (In JDK 8 - * this was implemented in sun.security.ssl.Debug, the logic was moved to - * sun.security.ssl.SSLLogger in JDK11 but not yet backported to all JDKs. See JDK-8196584 - * for details.) We cannot prevent these classes from being initialized at image build time, - * so we have to reinitialize them at run time to honour the run time passed value for the - * javax.net.debug system property. + * initialization. */ - optionalClazz(access, "sun.security.ssl.Debug").ifPresent(c -> rci.rerunInitialization(c, "for reading properties at run time")); - optionalClazz(access, "sun.security.ssl.SSLLogger").ifPresent(c -> rci.rerunInitialization(c, "for reading properties at run time")); + rci.initializeAtRunTime(clazz(access, "sun.security.ssl.SSLLogger"), "for reading properties at run time"); } @Override 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 deleted file mode 100644 index f9b73e77c941..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/AllowAllHostedUsagesClassInitializationSupport.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * 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 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.graal.compiler.java.LambdaUtils; -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."); - forceInitializeHosted(aClass, 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."); - 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); - if (!clazz.isInterface()) { - /* - * Initialization of an interface does not trigger initialization of superinterfaces. - * Regardless whether any of the involved interfaces declare default methods. - */ - 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)))) { - /* - * To simplify class initialization configuration for proxy and lambda types, - * registering all of their implemented interfaces as "initialize at build time" is - * equivalent to registering the proxy/lambda type itself. This is safe because we know - * that proxy/lambda types themselves have no problematic code in the class initializer - * (they are generated classes). - * - * Note that we must look at all interfaces, including transitive dependencies. - */ - boolean allInterfacesSpecifiedAsBuildTime = true; - for (Class iface : allInterfaces(clazz)) { - if (specifiedInitKindFor(iface) != InitKind.BUILD_TIME) { - allInterfacesSpecifiedAsBuildTime = false; - break; - } - } - if (allInterfacesSpecifiedAsBuildTime) { - forceInitializeHosted(clazz, "proxy/lambda classes with all interfaces explicitly marked as --initialize-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/ClassInitializationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java index 3ee6460175dd..796df3373e1e 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 @@ -25,12 +25,10 @@ package com.oracle.svm.hosted.classinitialization; import static com.oracle.svm.hosted.classinitialization.InitKind.BUILD_TIME; -import static com.oracle.svm.hosted.classinitialization.InitKind.RERUN; import static com.oracle.svm.hosted.classinitialization.InitKind.RUN_TIME; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -38,14 +36,11 @@ import java.util.stream.Collectors; import java.util.stream.StreamSupport; -import org.graalvm.collections.Pair; import org.graalvm.nativeimage.impl.clinit.ClassInitializationTracking; 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; @@ -63,6 +58,7 @@ import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.FeatureImpl.AfterAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; +import com.oracle.svm.util.LogUtils; import jdk.graal.compiler.graph.Node; import jdk.graal.compiler.java.LambdaUtils; @@ -74,26 +70,56 @@ public class ClassInitializationFeature implements InternalFeature { private static final String NATIVE_IMAGE_CLASS_REASON = "Native Image classes are always initialized at build time"; private ClassInitializationSupport classInitializationSupport; - private AnalysisUniverse universe; - private AnalysisMetaAccess metaAccess; public static void processClassInitializationOptions(ClassInitializationSupport initializationSupport) { initializeNativeImagePackagesAtBuildTime(initializationSupport); ClassInitializationOptions.ClassInitialization.getValue().getValuesWithOrigins().forEach(entry -> { - for (String info : entry.getLeft().split(",")) { - boolean noMatches = Arrays.stream(InitKind.values()).noneMatch(v -> info.endsWith(v.suffix())); - OptionOrigin origin = entry.getRight(); - if (noMatches) { - throw UserError.abort("Element in class initialization configuration must end in %s, %s, or %s. Found: %s (from %s)", - RUN_TIME.suffix(), RERUN.suffix(), BUILD_TIME.suffix(), info, origin); - } - - Pair elementType = InitKind.strip(info); - elementType.getRight().stringConsumer(initializationSupport, origin).accept(elementType.getLeft()); + for (String optionValue : entry.getLeft().split(",")) { + processClassInitializationOption(initializationSupport, optionValue, entry.getRight()); } }); } + private static void processClassInitializationOption(ClassInitializationSupport initializationSupport, String optionValue, OptionOrigin origin) { + boolean initializeAtRunTime; + if (optionValue.endsWith(ClassInitializationOptions.SUFFIX_BUILD_TIME)) { + initializeAtRunTime = false; + } else if (optionValue.endsWith(ClassInitializationOptions.SUFFIX_RUN_TIME)) { + initializeAtRunTime = true; + } else if (optionValue.endsWith(ClassInitializationOptions.SEPARATOR + "rerun")) { + /* + * There is no more difference between initializing a class at run-time and re-running + * the class initializer at run time. But we still want to support it on the command + * line, in order to not break backward compatibility. + */ + LogUtils.warning("Re-running class initializer is deprecated. It is equivalent with registering the class for initialization at run time. Found: %s (from %s)", optionValue, origin); + initializeAtRunTime = true; + } else { + throw UserError.abort("Element in class initialization configuration must end in %s, or %s. Found: %s (from %s)", + ClassInitializationOptions.SUFFIX_BUILD_TIME, ClassInitializationOptions.SUFFIX_RUN_TIME, optionValue, origin); + } + + String name = optionValue.substring(0, optionValue.lastIndexOf(ClassInitializationOptions.SEPARATOR)); + String reason = "from " + origin + " with '" + name + "'"; + if (initializeAtRunTime) { + initializationSupport.initializeAtRunTime(name, reason); + } else { + if (name.equals("") && !origin.commandLineLike()) { + String msg = "--initialize-at-build-time without arguments is not allowed." + System.lineSeparator() + + "Origin of the option: " + origin + System.lineSeparator() + + "The reason for deprecation is that --initalize-at-build-time does not compose, i.e., a single library can make assumptions that the whole classpath can be safely initialized at build time;" + + " that assumption is often incorrect."; + if (ClassInitializationOptions.AllowDeprecatedInitializeAllClassesAtBuildTime.getValue()) { + LogUtils.warning(msg); + } else { + throw UserError.abort("%s%nAs a workaround, %s allows turning this error into a warning. Note that this option is deprecated and will be removed in a future version.", + msg, SubstrateOptionsParser.commandArgument(ClassInitializationOptions.AllowDeprecatedInitializeAllClassesAtBuildTime, "+")); + } + } + initializationSupport.initializeAtBuildTime(name, reason); + } + } + private static void initializeNativeImagePackagesAtBuildTime(ClassInitializationSupport initializationSupport) { initializationSupport.initializeAtBuildTime("com.oracle.svm", NATIVE_IMAGE_CLASS_REASON); initializationSupport.initializeAtBuildTime("com.oracle.graal", NATIVE_IMAGE_CLASS_REASON); @@ -115,8 +141,6 @@ public void duringSetup(DuringSetupAccess a) { FeatureImpl.DuringSetupAccessImpl access = (FeatureImpl.DuringSetupAccessImpl) a; classInitializationSupport = access.getHostVM().getClassInitializationSupport(); access.registerObjectReplacer(this::checkImageHeapInstance); - universe = ((FeatureImpl.DuringSetupAccessImpl) a).getBigBang().getUniverse(); - metaAccess = ((FeatureImpl.DuringSetupAccessImpl) a).getBigBang().getMetaAccess(); } private Object checkImageHeapInstance(Object obj) { @@ -167,16 +191,14 @@ private Object checkImageHeapInstance(Object obj) { } }); - if (ClassInitializationOptions.StrictImageHeap.getValue()) { - msg += System.lineSeparator(); - msg += """ - If you are seeing this message after upgrading to a new GraalVM release, this means that some objects ended up in the image heap without their type being marked with --initialize-at-build-time. - To fix this, include %s in your configuration. If the classes do not originate from your code, it is advised to update all library or framework dependencies to the latest version before addressing this error. - """ - .replaceAll("\n", System.lineSeparator()) - .formatted(SubstrateOptionsParser.commandArgument(ClassInitializationOptions.ClassInitialization, proxyOrLambda ? proxyLambdaInterfaceCSV : typeName, - "initialize-at-build-time", true, false)); - } + msg += System.lineSeparator(); + msg += """ + If you are seeing this message after upgrading to a new GraalVM release, this means that some objects ended up in the image heap without their type being marked with --initialize-at-build-time. + To fix this, include %s in your configuration. If the classes do not originate from your code, it is advised to update all library or framework dependencies to the latest version before addressing this error. + """ + .replaceAll("\n", System.lineSeparator()) + .formatted(SubstrateOptionsParser.commandArgument(ClassInitializationOptions.ClassInitialization, proxyOrLambda ? proxyLambdaInterfaceCSV : typeName, + "initialize-at-build-time", true, false)); msg += System.lineSeparator() + "The following detailed trace displays from which field in the code the object was reached."; throw new UnsupportedFeatureException(msg); @@ -205,16 +227,6 @@ public void registerLowerings(RuntimeConfiguration runtimeConfig, OptionValues o EnsureClassInitializedSnippets.registerLowerings(options, providers, lowerings); } - @Override - public void duringAnalysis(DuringAnalysisAccess access) { - /* - * Check early and often during static analysis if any class that must not have been - * initialized during image building got initialized. We want to fail as early as possible, - * even though we cannot pinpoint the exact time and reason why initialization happened. - */ - classInitializationSupport.checkDelayedInitialization(); - } - /** * Initializes classes that can be proven safe and prints class initialization statistics. */ @@ -223,13 +235,6 @@ public void duringAnalysis(DuringAnalysisAccess access) { public void afterAnalysis(AfterAnalysisAccess a) { AfterAnalysisAccessImpl access = (AfterAnalysisAccessImpl) a; try (Timer.StopTimer ignored = TimerCollection.createTimerAndStart(TimerCollection.Registry.CLINIT)) { - assert classInitializationSupport.checkDelayedInitialization(); - - if (SimulateClassInitializerSupport.singleton().isEnabled()) { - /* Simulation of class initializer replaces the "late initialization". */ - } else { - classInitializationSupport.doLateInitialization(universe, metaAccess); - } if (ClassInitializationOptions.PrintClassInitialization.getValue()) { reportClassInitializationInfo(access, SubstrateOptions.reportsPath()); @@ -272,7 +277,6 @@ private void reportClassInitializationInfo(AfterAnalysisAccessImpl access, Strin ReportUtils.report("class initialization report", path, "class_initialization_report", "csv", writer -> { writer.println("Class Name, Initialization Kind, Reason for Initialization"); reportKind(access, writer, BUILD_TIME); - reportKind(access, writer, RERUN); reportKind(access, writer, RUN_TIME); }); } @@ -306,18 +310,9 @@ private static void reportTrackedClassInitializationTraces(String path) { writer -> initializedClasses.forEach((k, v) -> { writer.println(k.getName()); writer.println("---------------------------------------------"); - writer.println(ProvenSafeClassInitializationSupport.getTraceString(v)); + writer.println(ClassInitializationSupport.getTraceString(v)); writer.println(); })); } } - - @Override - public void afterImageWrite(AfterImageWriteAccess a) { - /* - * This is the final time to check if any class that must not have been initialized during - * image building got initialized. - */ - classInitializationSupport.checkDelayedInitialization(); - } } 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 45cc0fb82f4b..3f840822b452 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 @@ -24,73 +24,61 @@ */ package com.oracle.svm.hosted.classinitialization; -import static com.oracle.svm.hosted.classinitialization.InitKind.BUILD_TIME; -import static com.oracle.svm.hosted.classinitialization.InitKind.RERUN; -import static com.oracle.svm.hosted.classinitialization.InitKind.RUN_TIME; -import static com.oracle.svm.hosted.classinitialization.InitKind.SEPARATOR; - -import java.util.Locale; import java.util.function.Function; import com.oracle.svm.core.option.APIOption; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.LocatableMultiOptionValue; -import com.oracle.svm.util.LogUtils; import jdk.graal.compiler.options.Option; import jdk.graal.compiler.options.OptionType; public final class ClassInitializationOptions { + public static final String SEPARATOR = ":"; + public static final String SUFFIX_BUILD_TIME = SEPARATOR + "build_time"; + public static final String SUFFIX_RUN_TIME = SEPARATOR + "run_time"; + private static class InitializationValueTransformer implements Function { - private final String val; + private final String suffix; - InitializationValueTransformer(String val) { - this.val = val; + InitializationValueTransformer(String suffix) { + this.suffix = suffix; } @Override public Object apply(Object o) { String[] elements = o.toString().split(","); if (elements.length == 0) { - return SEPARATOR + val; + return suffix; } String[] results = new String[elements.length]; for (int i = 0; i < elements.length; i++) { - results[i] = elements[i] + SEPARATOR + val; + results[i] = elements[i] + suffix; } return String.join(",", results); } } - private static class InitializationValueDelay extends InitializationValueTransformer { - InitializationValueDelay() { - super(RUN_TIME.name().toLowerCase(Locale.ROOT)); - } - } - - private static class InitializationValueRerun extends InitializationValueTransformer { - InitializationValueRerun() { - super(RERUN.name().toLowerCase(Locale.ROOT)); + private static class InitializationValueRunTime extends InitializationValueTransformer { + InitializationValueRunTime() { + super(SUFFIX_RUN_TIME); } } - private static class InitializationValueEager extends InitializationValueTransformer { - InitializationValueEager() { - super(BUILD_TIME.name().toLowerCase(Locale.ROOT)); + private static class InitializationValueBuildTime extends InitializationValueTransformer { + InitializationValueBuildTime() { + super(SUFFIX_BUILD_TIME); } } - @APIOption(name = "initialize-at-run-time", valueTransformer = InitializationValueDelay.class, defaultValue = "", // + @APIOption(name = "initialize-at-run-time", valueTransformer = InitializationValueRunTime.class, defaultValue = "", // customHelp = "A comma-separated list of packages and classes (and implicitly all of their subclasses) that must be initialized at runtime and not during image building. An empty string is currently not supported.")// - @APIOption(name = "initialize-at-build-time", valueTransformer = InitializationValueEager.class, defaultValue = "", // + @APIOption(name = "initialize-at-build-time", valueTransformer = InitializationValueBuildTime.class, defaultValue = "", // customHelp = "A comma-separated list of packages and classes (and implicitly all of their superclasses) that are initialized during image generation. An empty string designates all packages.")// - @APIOption(name = "delay-class-initialization-to-runtime", valueTransformer = InitializationValueDelay.class, deprecated = "Use --initialize-at-run-time.", // - defaultValue = "", customHelp = "A comma-separated list of classes (and implicitly all of their subclasses) that are initialized at runtime and not during image building")// - @APIOption(name = "rerun-class-initialization-at-runtime", valueTransformer = InitializationValueRerun.class, // - deprecated = "Currently there is no replacement for this option. Try using --initialize-at-run-time or use the non-API option -H:ClassInitialization directly.", // - defaultValue = "", customHelp = "A comma-separated list of classes (and implicitly all of their subclasses) that are initialized both at runtime and during image building") // - @Option(help = "A comma-separated list of classes appended with their initialization strategy (':build_time', ':rerun', or ':run_time')", type = OptionType.User)// + @APIOption(name = "delay-class-initialization-to-runtime", valueTransformer = InitializationValueRunTime.class, deprecated = "Use --initialize-at-run-time.", defaultValue = "")// + @APIOption(name = "rerun-class-initialization-at-runtime", valueTransformer = InitializationValueRunTime.class, deprecated = "Equivalent to --initialize-at-run-time.", defaultValue = "") // + @Option(help = "A comma-separated list of classes appended with their initialization strategy ('" + SUFFIX_BUILD_TIME + "' or '" + SUFFIX_RUN_TIME + "')", type = OptionType.User)// public static final HostedOptionKey ClassInitialization = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.build()); @Option(help = "Instead of abort, only warn if --initialize-at-build-time= is used.", type = OptionType.Debug, // @@ -104,13 +92,8 @@ private static class InitializationValueEager extends InitializationValueTransfo public static final HostedOptionKey AssertInitializationSpecifiedForAllClasses = new HostedOptionKey<>(false); @APIOption(name = "strict-image-heap", deprecated = "'--strict-image-heap' is now the default. You can remove the option.") // - @Option(help = "Enable the strict image heap mode that allows all classes to be used at build-time but also requires types of all objects in the heap to be explicitly marked for build-time initialization.", // - type = OptionType.User, deprecated = true, deprecationMessage = "This option was introduced to simplify migration to GraalVM 24.0 and will be removed in a future release") // - public static final HostedOptionKey StrictImageHeap = new HostedOptionKey<>(true, k -> { - if (k.hasBeenSet() && Boolean.FALSE.equals(k.getValue())) { - LogUtils.warning("The non-strict image heap mode should be avoided as it is deprecated and marked for removal."); - } - }); + @Option(help = "Deprecated, option no longer has any effect.", deprecated = true, deprecationMessage = "It no longer has any effect, and no replacement is available")// + static final HostedOptionKey StrictImageHeap = new HostedOptionKey<>(true); @Option(help = "Simulate the effects of class initializer at image build time, to avoid class initialization at run time.", type = OptionType.Expert)// public static final HostedOptionKey SimulateClassInitializer = new HostedOptionKey<>(true); 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 1cc927af9261..7de1e7a98399 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 @@ -37,22 +37,21 @@ import java.util.stream.Collectors; import org.graalvm.collections.EconomicSet; -import jdk.graal.compiler.java.LambdaUtils; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; import org.graalvm.nativeimage.impl.clinit.ClassInitializationTracking; 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.svm.core.SubstrateOptions; import com.oracle.svm.core.option.LocatableMultiOptionValue; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.UserError; +import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.LinkAtBuildTimeSupport; +import jdk.graal.compiler.java.LambdaUtils; import jdk.internal.misc.Unsafe; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaType; @@ -61,7 +60,7 @@ * The core class for deciding whether a class should be initialized during image building or class * initialization should be delayed to runtime. */ -public abstract class ClassInitializationSupport implements RuntimeClassInitializationSupport { +public class ClassInitializationSupport implements RuntimeClassInitializationSupport { /** * Setup for class initialization: configured through features and command line input. It @@ -87,18 +86,11 @@ public abstract class ClassInitializationSupport implements RuntimeClassInitiali */ final MetaAccessProvider metaAccess; - public static ClassInitializationSupport create(MetaAccessProvider metaAccess, ImageClassLoader loader) { - if (!ClassInitializationOptions.StrictImageHeap.getValue()) { - return new ProvenSafeClassInitializationSupport(metaAccess, loader); - } - return new AllowAllHostedUsagesClassInitializationSupport(metaAccess, loader); - } - public static ClassInitializationSupport singleton() { return (ClassInitializationSupport) ImageSingletons.lookup(RuntimeClassInitializationSupport.class); } - ClassInitializationSupport(MetaAccessProvider metaAccess, ImageClassLoader loader) { + public ClassInitializationSupport(MetaAccessProvider metaAccess, ImageClassLoader loader) { this.metaAccess = metaAccess; this.loader = loader; } @@ -202,6 +194,12 @@ private static String instructionsToInitializeAtRuntime(Class clazz) { " to explicitly request initialization of this class at run time."; } + @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 initializeAtRunTime(String name, String reason) { UserError.guarantee(!configurationSealed, "The class initialization configuration can be changed only before the phase analysis."); @@ -214,6 +212,12 @@ public void initializeAtRunTime(String name, String reason) { } } + @Override + public void initializeAtBuildTime(Class aClass, String reason) { + UserError.guarantee(!configurationSealed, "The class initialization configuration can be changed only before the phase analysis."); + forceInitializeHosted(aClass, 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."); @@ -227,18 +231,6 @@ public void initializeAtBuildTime(String name, String reason) { } } - @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(LocatableMultiOptionValue.Strings option, Class clazz) { return option.values().contains(clazz.getName()); } @@ -298,20 +290,152 @@ static String getTraceString(StackTraceElement[] trace) { * Initializes the class during image building, and reports an error if the user requested to * delay initialization to runtime. */ - public abstract void forceInitializeHosted(Class clazz, String reason, boolean allowInitializationErrors); + 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); - abstract InitKind computeInitKindAndMaybeInitializeClass(Class clazz); + forceInitializeHosted(clazz.getSuperclass(), "super type of " + clazz.getTypeName(), allowInitializationErrors); + if (!clazz.isInterface()) { + /* + * Initialization of an interface does not trigger initialization of superinterfaces. + * Regardless whether any of the involved interfaces declare default methods. + */ + forceInitializeInterfaces(clazz.getInterfaces(), "super type of " + clazz.getTypeName()); + } + } - abstract String reasonForClass(Class clazz); + 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()); + } + } + + InitKind computeInitKindAndMaybeInitializeClass(Class clazz) { + return computeInitKindAndMaybeInitializeClass(clazz, true); + } /** - * 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. + * 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. */ - abstract boolean checkDelayedInitialization(); + InitKind computeInitKindAndMaybeInitializeClass(Class clazz, boolean memoize) { + InitKind existing = classInitKinds.get(clazz); + if (existing != null) { + return existing; + } - abstract void doLateInitialization(AnalysisUniverse universe, AnalysisMetaAccess aMetaAccess); + 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)))) { + /* + * To simplify class initialization configuration for proxy and lambda types, + * registering all of their implemented interfaces as "initialize at build time" is + * equivalent to registering the proxy/lambda type itself. This is safe because we know + * that proxy/lambda types themselves have no problematic code in the class initializer + * (they are generated classes). + * + * Note that we must look at all interfaces, including transitive dependencies. + */ + boolean allInterfacesSpecifiedAsBuildTime = true; + for (Class iface : allInterfaces(clazz)) { + if (specifiedInitKindFor(iface) != InitKind.BUILD_TIME) { + allInterfacesSpecifiedAsBuildTime = false; + break; + } + } + if (allInterfacesSpecifiedAsBuildTime) { + forceInitializeHosted(clazz, "proxy/lambda classes with all interfaces explicitly marked as --initialize-at-build-time are also initialized at build time", false); + return InitKind.BUILD_TIME; + } + } + + InitKind result = superResult.max(clazzResult); + + if (memoize) { + if (!(result == InitKind.RUN_TIME)) { + 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; + } + + String reasonForClass(Class clazz) { + InitKind initKind = classInitKinds.get(clazz); + String reason = classInitializationConfiguration.lookupReason(clazz.getTypeName()); + if (initKind == InitKind.RUN_TIME) { + 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"); + } + } public static EconomicSet> allInterfaces(Class clazz) { EconomicSet> result = EconomicSet.create(); 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 deleted file mode 100644 index fc8dc951f1f4..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/EarlyClassInitializerAnalysis.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright (c) 2020, 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 static jdk.graal.compiler.nodes.graphbuilderconf.InlineInvokePlugin.InlineInfo.createStandardInlineInfo; - -import java.lang.reflect.Proxy; -import java.util.HashSet; -import java.util.Set; -import java.util.function.Supplier; - -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport; - -import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; -import com.oracle.graal.pointsto.phases.NoClassInitializationPlugin; -import com.oracle.graal.pointsto.util.GraalAccess; -import com.oracle.svm.core.ParsingReason; -import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode; -import com.oracle.svm.core.graal.thread.VMThreadLocalAccess; -import com.oracle.svm.core.option.HostedOptionValues; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.FallbackFeature; -import com.oracle.svm.hosted.phases.EarlyConstantFoldLoadFieldPlugin; -import com.oracle.svm.hosted.snippets.ReflectionPlugins; -import com.oracle.svm.hosted.snippets.SubstrateGraphBuilderPlugins; - -import jdk.graal.compiler.core.common.GraalBailoutException; -import jdk.graal.compiler.debug.DebugContext; -import jdk.graal.compiler.debug.DebugContext.Builder; -import jdk.graal.compiler.graph.Graph; -import jdk.graal.compiler.graph.Node; -import jdk.graal.compiler.java.BytecodeParser; -import jdk.graal.compiler.java.GraphBuilderPhase; -import jdk.graal.compiler.nodes.FrameState; -import jdk.graal.compiler.nodes.GraphState.GuardsStage; -import jdk.graal.compiler.nodes.GraphState.StageFlag; -import jdk.graal.compiler.nodes.Invoke; -import jdk.graal.compiler.nodes.StructuredGraph; -import jdk.graal.compiler.nodes.ValueNode; -import jdk.graal.compiler.nodes.extended.UnsafeAccessNode; -import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; -import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration.Plugins; -import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; -import jdk.graal.compiler.nodes.graphbuilderconf.InlineInvokePlugin; -import jdk.graal.compiler.nodes.graphbuilderconf.InvocationPlugins; -import jdk.graal.compiler.nodes.java.AccessFieldNode; -import jdk.graal.compiler.nodes.java.AccessMonitorNode; -import jdk.graal.compiler.nodes.java.NewArrayNode; -import jdk.graal.compiler.nodes.java.NewMultiArrayNode; -import jdk.graal.compiler.options.OptionValues; -import jdk.graal.compiler.phases.OptimisticOptimizations; -import jdk.graal.compiler.phases.tiers.HighTierContext; -import jdk.graal.compiler.phases.util.Providers; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.ResolvedJavaField; -import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaType; - -/** - * A simple analysis of class initializers to allow early class initialization before static - * analysis for classes with simple class initializers. - * - * This analysis runs before the call tree is completely built, so the scope is limited to the class - * initializer itself, and methods that can be inlined into the class initializer. Method inlining - * is an easy way to increase the scope. Two simple tests are used to determine if a class - * initializer is side-effect free: - * - * 1) No method calls remain after parsing. This automatically precludes any virtual calls (only - * direct calls can be inlined during parsing) and any calls to native methods. - * - * 2) No reads and writes of static fields apart from static fields of the class that is going to be - * initialized. This ensures that there are no side effects. Note that we do not need to check for - * instance fields: since no static fields are read, it is guaranteed that only instance fields of - * newly allocated objects are accessed. - * - * To avoid parsing a large class initializer graph just to find out that the class cannot be - * 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 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 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 ProvenSafeClassInitializationSupport classInitializationSupport; - private final Providers originalProviders; - private final HighTierContext context; - - EarlyClassInitializerAnalysis(ProvenSafeClassInitializationSupport classInitializationSupport) { - this.classInitializationSupport = classInitializationSupport; - - originalProviders = GraalAccess.getOriginalProviders(); - context = new HighTierContext(originalProviders, null, OptimisticOptimizations.NONE); - } - - @SuppressWarnings("try") - boolean canInitializeWithoutSideEffects(Class clazz, Set> existingAnalyzedClasses) { - if (Proxy.isProxyClass(clazz)) { - /* - * The checks below consider proxy class initialization as of JDK 19 to have side - * effects because it accesses Class.classLoader and System.allowSecurityManager, but - * these are not actual side effects, so we override these checks for proxy classes so - * that they can still be initialized at build time. (GR-40009) - */ - return true; - } - ResolvedJavaType type = originalProviders.getMetaAccess().lookupJavaType(clazz); - assert type.getSuperclass() == null || type.getSuperclass().isInitialized() : "This analysis assumes that the superclass was successfully analyzed and initialized beforehand: " + - type.toJavaName(true); - - ResolvedJavaMethod clinit = type.getClassInitializer(); - if (clinit == null) { - /* No class initializer, so the class can trivially be initialized. */ - return true; - } else if (clinit.getCode() == null) { - /* - * Happens e.g. when linking of the class failed. Note that we really need to check for - * getCode(), because getCodeSize() still returns a value > 0 for such methods. - */ - return false; - } - - Set> analyzedClasses = existingAnalyzedClasses; - if (analyzedClasses == null) { - analyzedClasses = new HashSet<>(); - } else if (analyzedClasses.contains(clazz)) { - /* Cyclic dependency of class initializers. */ - return false; - } - analyzedClasses.add(clazz); - - OptionValues options = HostedOptionValues.singleton(); - DebugContext debug = new Builder(options).build(); - try (DebugContext.Scope s = debug.scope("EarlyClassInitializerAnalysis", clinit)) { - return canInitializeWithoutSideEffects(clinit, analyzedClasses, options, debug); - } catch (Throwable ex) { - throw debug.handle(ex); - } - } - - @SuppressWarnings("try") - private boolean canInitializeWithoutSideEffects(ResolvedJavaMethod clinit, Set> analyzedClasses, OptionValues options, DebugContext debug) { - InvocationPlugins invocationPlugins = new InvocationPlugins(); - Plugins plugins = new Plugins(invocationPlugins); - plugins.appendInlineInvokePlugin(new AbortOnRecursiveInliningPlugin()); - AbortOnUnitializedClassPlugin classInitializationPlugin = new AbortOnUnitializedClassPlugin(analyzedClasses); - plugins.setClassInitializationPlugin(classInitializationPlugin); - plugins.appendNodePlugin(new EarlyConstantFoldLoadFieldPlugin(originalProviders.getMetaAccess())); - - SubstrateGraphBuilderPlugins.registerClassDesiredAssertionStatusPlugin(invocationPlugins); - FallbackFeature fallbackFeature = ImageSingletons.contains(FallbackFeature.class) ? ImageSingletons.lookup(FallbackFeature.class) : null; - ReflectionPlugins.registerInvocationPlugins(classInitializationSupport.loader, null, classInitializationPlugin, invocationPlugins, null, - ParsingReason.EarlyClassInitializerAnalysis, fallbackFeature); - - GraphBuilderConfiguration graphBuilderConfig = GraphBuilderConfiguration.getDefault(plugins).withEagerResolving(true); - - StructuredGraph graph = new StructuredGraph.Builder(options, debug) - .method(clinit) - .recordInlinedMethods(false) - .build(); - graph.getGraphState().setGuardsStage(GuardsStage.FIXED_DEOPTS); - graph.getGraphState().setAfterStage(StageFlag.GUARD_LOWERING); - GraphBuilderPhase.Instance builderPhase = new ClassInitializerGraphBuilderPhase(context, graphBuilderConfig, context.getOptimisticOptimizations()); - - try (Graph.NodeEventScope nes = graph.trackNodeEvents(new AbortOnDisallowedNode())) { - builderPhase.apply(graph, context); - /* - * If parsing is not aborted by a ClassInitializerHasSideEffectsException, it does not - * have any side effect. - */ - return true; - - } catch (ClassInitializerHasSideEffectsException ex) { - return false; - } catch (BytecodeParser.BytecodeParserError ex) { - if (ex.getCause() instanceof ClassInitializerHasSideEffectsException) { - return false; - } - throw ex; - } - } - - /** - * Parsing is aborted if any non-initialized class is encountered (apart from the class that is - * analyzed itself). - */ - final class AbortOnUnitializedClassPlugin extends NoClassInitializationPlugin { - - private final Set> analyzedClasses; - - AbortOnUnitializedClassPlugin(Set> analyzedClasses) { - this.analyzedClasses = analyzedClasses; - } - - @Override - public boolean apply(GraphBuilderContext b, ResolvedJavaType type, Supplier frameState) { - ResolvedJavaMethod clinitMethod = b.getGraph().method(); - if (!EnsureClassInitializedNode.needsRuntimeInitialization(clinitMethod.getDeclaringClass(), type)) { - return false; - } - if (classInitializationSupport.computeInitKindAndMaybeInitializeClass(OriginalClassProvider.getJavaClass(type), true, analyzedClasses) != InitKind.RUN_TIME) { - assert type.isInitialized() : "Type must be initialized now"; - return false; - } - throw new ClassInitializerHasSideEffectsException("Reference of class that is not initialized: " + type.toJavaName(true)); - } - } - -} - -final class ClassInitializerHasSideEffectsException extends GraalBailoutException { - private static final long serialVersionUID = 1L; - - ClassInitializerHasSideEffectsException(String message) { - super(message); - } - - @Override - public synchronized Throwable fillInStackTrace() { - /* Exception is used to abort parsing, stack trace is not necessary. */ - return this; - } -} - -final class AbortOnRecursiveInliningPlugin implements InlineInvokePlugin { - @Override - public InlineInfo shouldInlineInvoke(GraphBuilderContext b, ResolvedJavaMethod original, ValueNode[] arguments) { - if (b.recursiveInliningDepth(original) > 0) { - return InlineInfo.DO_NOT_INLINE_WITH_EXCEPTION; - } - if (original.getCode() == null) { - return InlineInfo.DO_NOT_INLINE_WITH_EXCEPTION; - } - - /* - * We do not restrict inlining based on method size or inlining depth. Since parsing is - * aborted as soon as a forbidden node is added, we do not expect the graph size to grow out - * of control. - */ - return createStandardInlineInfo(original); - } -} - -final class AbortOnDisallowedNode extends Graph.NodeEventListener { - @Override - public void nodeAdded(Node node) { - if (node instanceof Invoke) { - throw new ClassInitializerHasSideEffectsException("Non-inlined invoke of method: " + ((Invoke) node).getTargetMethod().format("%H.%n(%p)")); - - } else if (node instanceof AccessFieldNode) { - ResolvedJavaField field = ((AccessFieldNode) node).field(); - ResolvedJavaMethod clinit = ((StructuredGraph) node.graph()).method(); - if (field.isStatic() && !field.getDeclaringClass().equals(clinit.getDeclaringClass())) { - throw new ClassInitializerHasSideEffectsException("Access of static field from a different class: " + field.format("%H.%n")); - } - } else if (node instanceof VMThreadLocalAccess) { - throw new ClassInitializerHasSideEffectsException("Access of thread-local value"); - } else if (node instanceof UnsafeAccessNode) { - throw VMError.shouldNotReachHere("Intrinsification of Unsafe methods is not enabled during bytecode parsing"); - - } else if (node instanceof NewArrayNode) { - checkArrayAllocationLength(((NewArrayNode) node).length()); - } else if (node instanceof NewMultiArrayNode) { - var dimensions = ((NewMultiArrayNode) node).dimensions(); - for (var dimension : dimensions) { - checkArrayAllocationLength(dimension); - } - } else if (node instanceof AccessMonitorNode) { - throw new ClassInitializerHasSideEffectsException("Synchronization"); - } - } - - private static void checkArrayAllocationLength(ValueNode lengthNode) { - JavaConstant lengthConstant = lengthNode.asJavaConstant(); - if (lengthConstant != null) { - int length = lengthConstant.asInt(); - if (length < 0 || length > 100_000) { - /* - * Ensure that also the late class initialization after static analysis does not - * attempt to initialize. - */ - Class clazz = OriginalClassProvider.getJavaClass(lengthNode.graph().method().getDeclaringClass()); - ((ProvenSafeClassInitializationSupport) ImageSingletons.lookup(RuntimeClassInitializationSupport.class)).mustNotBeProvenSafe.add(clazz); - - throw new ClassInitializerHasSideEffectsException("Allocation of too large array in class initializer"); - } - } - } -} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/InitKind.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/InitKind.java index 6aa8f33a8d36..7e25d7c017ff 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/InitKind.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/InitKind.java @@ -24,18 +24,6 @@ */ package com.oracle.svm.hosted.classinitialization; -import java.util.Arrays; -import java.util.Locale; -import java.util.Optional; -import java.util.function.Consumer; - -import org.graalvm.collections.Pair; - -import com.oracle.svm.core.option.OptionOrigin; -import com.oracle.svm.core.option.SubstrateOptionsParser; -import com.oracle.svm.core.util.UserError; -import com.oracle.svm.util.LogUtils; - /** * The initialization kind for a class. The order of the enum values matters, {@link #max} depends * on it. @@ -43,61 +31,10 @@ enum InitKind { /** Class is initialized during image building, so it is already initialized at runtime. */ BUILD_TIME, - /** Class is initialized both at runtime and during image building. */ - RERUN, /** Class should be initialized at runtime and not during image building. */ RUN_TIME; InitKind max(InitKind other) { return this.ordinal() > other.ordinal() ? this : other; } - - InitKind min(InitKind other) { - return this.ordinal() < other.ordinal() ? this : other; - } - - boolean isRunTime() { - return this.equals(RUN_TIME); - } - - public static final String SEPARATOR = ":"; - - String suffix() { - return SEPARATOR + name().toLowerCase(Locale.ROOT); - } - - Consumer stringConsumer(ClassInitializationSupport support, OptionOrigin origin) { - if (this == RUN_TIME) { - return name -> support.initializeAtRunTime(name, reason(origin, name)); - } else if (this == RERUN) { - return name -> support.rerunInitialization(name, reason(origin, name)); - } else { - return name -> { - if (name.equals("") && !origin.commandLineLike()) { - String msg = "--initialize-at-build-time without arguments is not allowed." + System.lineSeparator() + - "Origin of the option: " + origin + System.lineSeparator() + - "The reason for deprecation is that --initalize-at-build-time does not compose, i.e., a single library can make assumptions that the whole classpath can be safely initialized at build time;" + - " that assumption is often incorrect."; - if (ClassInitializationOptions.AllowDeprecatedInitializeAllClassesAtBuildTime.getValue()) { - LogUtils.warning(msg); - } else { - throw UserError.abort("%s%nAs a workaround, %s allows turning this error into a warning. Note that this option is deprecated and will be removed in a future version.", msg, - SubstrateOptionsParser.commandArgument(ClassInitializationOptions.AllowDeprecatedInitializeAllClassesAtBuildTime, "+")); - } - } - support.initializeAtBuildTime(name, reason(origin, name)); - }; - } - } - - private static String reason(OptionOrigin origin, String name) { - return "from " + origin + " with '" + name + "'"; - } - - static Pair strip(String input) { - Optional it = Arrays.stream(values()).filter(x -> input.endsWith(x.suffix())).findAny(); - assert it.isPresent(); - return Pair.create(input.substring(0, input.length() - it.get().suffix().length()), it.get()); - } - } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ProvenSafeClassInitializationSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ProvenSafeClassInitializationSupport.java deleted file mode 100644 index 89cceba7c7bc..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ProvenSafeClassInitializationSupport.java +++ /dev/null @@ -1,465 +0,0 @@ -/* - * Copyright (c) 2019, 2020, 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 static com.oracle.svm.core.SubstrateOptions.TraceClassInitialization; -import static com.oracle.svm.hosted.classinitialization.InitKind.RUN_TIME; - -import java.lang.reflect.Field; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - -import org.graalvm.nativeimage.impl.clinit.ClassInitializationTracking; - -import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; -import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.graal.pointsto.meta.AnalysisUniverse; -import com.oracle.graal.pointsto.reports.ReportUtils; -import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.classinitialization.ClassInitializationInfo; -import com.oracle.svm.core.hub.DynamicHub; -import com.oracle.svm.core.option.SubstrateOptionsParser; -import com.oracle.svm.core.util.UserError; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.ImageClassLoader; -import com.oracle.svm.hosted.SVMHost; -import com.oracle.svm.util.ReflectionUtil; - -import jdk.internal.misc.Unsafe; -import jdk.vm.ci.meta.MetaAccessProvider; - -/** - * The core class for deciding whether a class should be initialized during image building or class - * initialization should be delayed to runtime. - */ -class ProvenSafeClassInitializationSupport extends ClassInitializationSupport { - - private static final Field dynamicHubClassInitializationInfoField = ReflectionUtil.lookupField(DynamicHub.class, "classInitializationInfo"); - - private final EarlyClassInitializerAnalysis earlyClassInitializerAnalysis; - private final Set> provenSafeEarly = ConcurrentHashMap.newKeySet(); - private Set> provenSafeLate = ConcurrentHashMap.newKeySet(); - final Set> mustNotBeProvenSafe = ConcurrentHashMap.newKeySet(); - - ProvenSafeClassInitializationSupport(MetaAccessProvider metaAccess, ImageClassLoader loader) { - super(metaAccess, loader); - this.earlyClassInitializerAnalysis = new EarlyClassInitializerAnalysis(this); - } - - @Override - InitKind computeInitKindAndMaybeInitializeClass(Class clazz) { - return computeInitKindAndMaybeInitializeClass(clazz, true, null); - } - - boolean canBeProvenSafe(Class clazz) { - if (mustNotBeProvenSafe.contains(clazz)) { - return false; - } - InitKind initKind = specifiedInitKindFor(clazz); - return initKind == null || (initKind.isRunTime() && !isStrictlyDefined(clazz)); - } - - @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); - setSubclassesAsRunTime(clazz); - checkEagerInitialization(clazz); - - if (!Unsafe.getUnsafe().shouldBeInitialized(clazz)) { - throw UserError.abort("The class %1$s has already been initialized (%2$s); it is too late to register %1$s for build-time initialization. %3$s", - clazz.getTypeName(), reason, - classInitializationErrorMessage(clazz, "Try avoiding this conflict by avoiding to initialize the class that caused initialization of " + clazz.getTypeName() + - " or by not marking " + clazz.getTypeName() + " for build-time initialization.")); - } - /* - * Propagate possible existing RUN_TIME registration from a superclass, so that we can check - * for user errors below. - */ - computeInitKindAndMaybeInitializeClass(clazz, false, null); - - InitKind previousKind = classInitKinds.put(clazz, InitKind.RUN_TIME); - if (previousKind == InitKind.BUILD_TIME) { - throw UserError.abort("Class is already initialized, so it is too late to register delaying class initialization: %s for reason: %s", clazz.getTypeName(), reason); - } else if (previousKind == InitKind.RERUN) { - throw UserError.abort("Class is registered both for delaying and rerunning the class initializer: %s for reason: %s", clazz.getTypeName(), reason); - } - } - - private static boolean isClassInitializationTracked(Class clazz) { - return TraceClassInitialization.hasBeenSet() && isClassListedInStringOption(TraceClassInitialization.getValue(), clazz); - } - - private static String classInitializationErrorMessage(Class clazz, String action) { - Map, StackTraceElement[]> initializedClasses = ClassInitializationTracking.initializedClasses; - if (!isClassInitializationTracked(clazz)) { - return "To see why " + clazz.getName() + " got initialized use " + SubstrateOptionsParser.commandArgument(TraceClassInitialization, clazz.getName()); - } else if (initializedClasses.containsKey(clazz)) { - - StackTraceElement[] trace = initializedClasses.get(clazz); - String culprit = null; - for (StackTraceElement stackTraceElement : trace) { - if (stackTraceElement.getMethodName().equals("")) { - culprit = stackTraceElement.getClassName(); - } - } - String initializationTrace = getTraceString(initializedClasses.get(clazz)); - if (culprit != null) { - return culprit + " caused initialization of this class with the following trace: \n" + initializationTrace; - } else { - return clazz.getTypeName() + " has been initialized through the following trace:\n" + initializationTrace; - } - } else { - return clazz.getTypeName() + " has been initialized without the native-image initialization instrumentation and the stack trace can't be tracked. " + action; - } - } - - @Override - String reasonForClass(Class clazz) { - InitKind initKind = classInitKinds.get(clazz); - String reason = classInitializationConfiguration.lookupReason(clazz.getTypeName()); - if (initKind == InitKind.BUILD_TIME && provenSafeEarly.contains(clazz)) { - return "class proven as side-effect free before analysis"; - } else if (initKind == InitKind.BUILD_TIME && provenSafeLate.contains(clazz)) { - return "class proven as side-effect free after analysis"; - } else 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 rerunInitialization(Class clazz, String reason) { - UserError.guarantee(!configurationSealed, "The class initialization configuration can be changed only before the phase analysis."); - classInitializationConfiguration.insert(clazz.getTypeName(), InitKind.RERUN, reason, true); - checkEagerInitialization(clazz); - - try { - Unsafe.getUnsafe().ensureClassInitialized(clazz); - } catch (Throwable ex) { - throw UserError.abort(ex, "Class initialization failed for %s. The class is requested for re-running (reason: %s)", clazz.getTypeName(), reason); - } - - /* - * Propagate possible existing RUN_TIME registration from a superclass, so that we can check - * for user errors below. - */ - computeInitKindAndMaybeInitializeClass(clazz, false, null); - - InitKind previousKind = classInitKinds.put(clazz, InitKind.RERUN); - if (previousKind != null) { - if (previousKind == InitKind.BUILD_TIME) { - throw UserError.abort("The information that the class should be initialized during image building has already been used, " + - "so it is too late to register the class initializer of %s for re-running. The reason for re-run request is %s", - clazz.getTypeName(), reason); - } else if (previousKind.isRunTime()) { - throw UserError.abort("Class or a superclass is already registered for delaying the class initializer, " + - "so it is too late to register the class initializer of %s for re-running. The reason for re-run request is %s", - clazz.getTypeName(), reason); - } - } - } - - @Override - public void initializeAtBuildTime(Class aClass, String reason) { - UserError.guarantee(!configurationSealed, "The class initialization configuration can be changed only before the phase analysis."); - classInitializationConfiguration.insert(aClass.getTypeName(), InitKind.BUILD_TIME, reason, true); - forceInitializeHosted(aClass, reason, false); - } - - private void setSubclassesAsRunTime(Class clazz) { - if (clazz.isInterface() && !metaAccess.lookupJavaType(clazz).declaresDefaultMethods()) { - /* - * An interface that does not declare a default method is independent from a class - * initialization point of view, i.e., it is not initialized when a class implementing - * that interface is initialized. - */ - return; - } - loader.findSubclasses(clazz, false).stream() - .filter(c -> !c.equals(clazz)) - .filter(c -> !(c.isInterface() && !metaAccess.lookupJavaType(c).declaresDefaultMethods())) - .forEach(c -> classInitializationConfiguration.insert(c.getTypeName(), InitKind.RUN_TIME, "subtype of " + clazz.getTypeName(), true)); - } - - @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() { - /* - * 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 - * being used itself, e.g., a class initializer can write a static field in another class. - */ - Set> illegalyInitialized = new HashSet<>(); - for (Map.Entry, InitKind> entry : classInitKinds.entrySet()) { - if (entry.getValue().isRunTime() && !Unsafe.getUnsafe().shouldBeInitialized(entry.getKey())) { - illegalyInitialized.add(entry.getKey()); - } - } - - if (illegalyInitialized.size() > 0) { - StringBuilder detailedMessage = new StringBuilder("Classes that should be initialized at run time got initialized during image building:\n "); - illegalyInitialized.forEach(c -> { - InitKind specifiedKind = specifiedInitKindFor(c); - /* not specified by the user so it is an accident => try to fix it */ - if (specifiedKind == null) { - detailedMessage.append(c.getTypeName()).append(" was unintentionally initialized at build time. "); - detailedMessage.append(classInitializationErrorMessage(c, - "Try marking this class for build-time initialization with " + SubstrateOptionsParser.commandArgument(ClassInitializationOptions.ClassInitialization, - c.getTypeName(), "initialize-at-build-time"))) - .append("\n"); - } else { - assert specifiedKind.isRunTime() : "Specified kind must be the same as actual kind for type " + c.getTypeName(); - String reason = classInitializationConfiguration.lookupReason(c.getTypeName()); - detailedMessage.append(c.getTypeName()).append(" the class was requested to be initialized at run time (").append(reason).append("). ") - .append(classInitializationErrorMessage(c, "Try avoiding to initialize the class that caused initialization of " + c.getTypeName())) - .append("\n"); - } - }); - - String traceClassInitArguments = illegalyInitialized.stream().filter(c -> !isClassInitializationTracked(c)).map(Class::getName).collect(Collectors.joining(",")); - if (!"".equals(traceClassInitArguments)) { - detailedMessage.append("To see how the classes got initialized, use ").append(SubstrateOptionsParser.commandArgument(TraceClassInitialization, traceClassInitArguments)); - } - - throw UserError.abort("%s", detailedMessage); - } - return true; - } - - private static void checkEagerInitialization(Class clazz) { - if (clazz.isPrimitive() || clazz.isArray()) { - throw UserError.abort("Primitive types and array classes are initialized at build time because initialization is side-effect free. " + - "It is not possible (and also not useful) to register them for run time initialization. Culprit: %s", clazz.getTypeName()); - } - } - - /** - * 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, Set> earlyClassInitializerAnalyzedClasses) { - 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; - } - - if (clazz.getTypeName().contains("$$StringConcat")) { - forceInitializeHosted(clazz, "string concatenation classes 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, earlyClassInitializerAnalyzedClasses)); - } - superResult = superResult.max(processInterfaces(clazz, memoize, earlyClassInitializerAnalyzedClasses)); - - if (memoize && superResult != InitKind.RUN_TIME && clazzResult == InitKind.RUN_TIME && canBeProvenSafe(clazz)) { - /* - * Check if the class initializer is side-effect free using a simple intraprocedural - * analysis. - */ - if (earlyClassInitializerAnalysis.canInitializeWithoutSideEffects(clazz, earlyClassInitializerAnalyzedClasses)) { - /* - * Note that even if the class initializer is side-effect free, running it can still - * fail with an exception. In that case we ignore the exception and initialize the - * class at run time (at which time the same exception is probably thrown again). - */ - clazzResult = ensureClassInitialized(clazz, true); - if (clazzResult == InitKind.BUILD_TIME) { - addProvenEarly(clazz); - } - } - } - - InitKind result = superResult.max(clazzResult); - - if (memoize) { - if (!result.isRunTime()) { - result = result.max(ensureClassInitialized(clazz, false)); - } - - /* - * Unfortunately, the computation of canInitializeWithoutSideEffects is not completely - * deterministic: Consider a class A whose class initializer depends on class B. Assume - * class B has no other dependencies and can therefore be initialized at build time. - * When class A is analyzed after class B has been initialized, it can also be - * initialized at build time. But when class A is analyzed before class B has been - * initialized, it cannot. Since two threads can analyze class A at the same time (there - * is no per-class locking) and another thread can initialize B at the same time, we can - * have a conflicting initialization status. In that case, BUILD_TIME must win over - * RUN_TIME because one thread has already initialized class A. - */ - result = classInitKinds.merge(clazz, result, InitKind::min); - } - return result; - } - - private InitKind processInterfaces(Class clazz, boolean memoizeEager, Set> earlyClassInitializerAnalyzedClasses) { - /* - * 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, earlyClassInitializerAnalyzedClasses)); - } 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, earlyClassInitializerAnalyzedClasses)); - } - } - return result; - } - - void addProvenEarly(Class clazz) { - provenSafeEarly.add(clazz); - } - - @Override - 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(this::canBeProvenSafe) - .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 (maybeInitializeAtBuildTime(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 deleted file mode 100644 index a7b06e24e683..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/TypeInitializerGraph.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (c) 2019, 2019, 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.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; - -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.InvokeInfo; -import com.oracle.svm.core.annotate.Delete; -import com.oracle.svm.core.annotate.Substitute; -import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode; -import com.oracle.svm.hosted.SVMHost; -import com.oracle.svm.hosted.phases.SubstrateClassInitializationPlugin; -import com.oracle.svm.hosted.substitute.SubstitutionMethod; -import com.oracle.svm.hosted.substitute.SubstitutionType; - -import jdk.vm.ci.meta.ResolvedJavaType; -import org.graalvm.nativeimage.AnnotationAccess; - -/** - * Keeps a type-hierarchy dependency graph for {@link AnalysisType}s from {@code universe}. Each - * type carries the information if it {@link Safety#SAFE} or {@link Safety#UNSAFE} to execute during - * native-image generation. - * - * The algorithm assigns all types ( {@link #initialTypeInitializerSafety}) and all methods ( - * {@link #initialMethodSafety}) with their initial safety. - * - * Then the information about unsafety is iteratively propagated through the graph in - * {@link #computeInitializerSafety}. - * - * NOTE: the dependency between methods and type initializers is maintained by the - * {@link SubstrateClassInitializationPlugin} that emits {@link EnsureClassInitializedNode} for - * every load, store, call, and instantiation in the bytecode. These dependencies are collected in - * {@link SVMHost#getInitializedClasses}. - */ -final class TypeInitializerGraph { - private final SVMHost hostVM; - private final ProvenSafeClassInitializationSupport classInitializationSupport; - - private enum Safety { - SAFE, - UNSAFE, - } - - private final Map types = new HashMap<>(); - private final Map> dependencies = new HashMap<>(); - - private final Map methodSafety = new HashMap<>(); - private final Collection methods; - - TypeInitializerGraph(ProvenSafeClassInitializationSupport classInitializationSupport, AnalysisUniverse universe) { - hostVM = ((SVMHost) universe.hostVM()); - this.classInitializationSupport = classInitializationSupport; - - universe.getTypes().forEach(this::addInitializer); - universe.getTypes().forEach(this::addInitializerDependencies); - /* initialize all methods with original safety data */ - methods = universe.getMethods(); - methods.stream().filter(AnalysisMethod::isImplementationInvoked).forEach(m -> methodSafety.put(m, initialMethodSafety(m))); - } - - /** - * Iteratively propagate information about unsafety through the methods ( - * {@link #updateMethodSafety}) and the initializer graph ( - * {@link #updateTypeInitializerSafety()}). - */ - void computeInitializerSafety() { - boolean newPromotions; - do { - AtomicBoolean methodSafetyChanged = new AtomicBoolean(false); - methods.stream().filter(m -> methodSafety.get(m) == Safety.SAFE) - .forEach(m -> { - if (updateMethodSafety(m)) { - methodSafetyChanged.set(true); - } - }); - newPromotions = methodSafetyChanged.get() || updateTypeInitializerSafety(); - } while (newPromotions); - } - - /** - * A type initializer is initially unsafe only if it was marked by the user as such. - */ - private Safety initialTypeInitializerSafety(AnalysisType t) { - return classInitializationSupport.specifiedInitKindFor(t.getJavaClass()) == InitKind.BUILD_TIME || classInitializationSupport.canBeProvenSafe(t.getJavaClass()) - ? Safety.SAFE - : Safety.UNSAFE; - } - - boolean isUnsafe(AnalysisType type) { - return types.get(type) == Safety.UNSAFE; - } - - public void setUnsafe(AnalysisType t) { - types.put(t, Safety.UNSAFE); - } - - private boolean updateTypeInitializerSafety() { - Set unsafeOrProcessedTypes = types.entrySet().stream() - .filter(t -> t.getValue() == Safety.UNSAFE) - .map(Map.Entry::getKey) - .collect(Collectors.toSet()); - - return types.keySet().stream() - .map(t -> tryPromoteToUnsafe(t, unsafeOrProcessedTypes)) - .reduce(false, (lhs, rhs) -> lhs || rhs); - } - - private void addInitializerDependencies(AnalysisType t) { - addInterfaceDependencies(t, t.getInterfaces()); - if (t.getSuperclass() != null) { - addDependency(t, t.getSuperclass()); - } - } - - private void addInterfaceDependencies(AnalysisType t, AnalysisType[] interfaces) { - for (AnalysisType anInterface : interfaces) { - if (anInterface.declaresDefaultMethods()) { - addDependency(t, anInterface); - } - addInterfaceDependencies(t, anInterface.getInterfaces()); - } - } - - private void addDependency(AnalysisType dependent, AnalysisType dependee) { - dependencies.get(dependent).add(dependee); - } - - /** - * Method is considered initially unsafe if (1) it is a substituted method, or (2) if any of the - * invokes are unsafe {@link TypeInitializerGraph#isInvokeInitiallyUnsafe}. - * - * Substituted methods are unsafe because their execution at image-build time would initialize - * types unknown to points-to analysis (which sees only the substituted version. - */ - private Safety initialMethodSafety(AnalysisMethod m) { - for (var invoke : m.getInvokes()) { - if (isInvokeInitiallyUnsafe(invoke)) { - return Safety.UNSAFE; - } - } - return hostVM.hasClassInitializerSideEffect(m) || - isSubstitutedMethod(m) ? Safety.UNSAFE : Safety.SAFE; - } - - private boolean isSubstitutedMethod(AnalysisMethod m) { - return classInitializationSupport.maybeInitializeAtBuildTime(m.getDeclaringClass()) && m.getWrapped() instanceof SubstitutionMethod; - } - - /** - * Unsafe invokes (1) call native methods, and/or (2) can't be statically bound. - */ - private static boolean isInvokeInitiallyUnsafe(InvokeInfo i) { - return i.getTargetMethod().isNative() || - !i.canBeStaticallyBound(); - } - - /** - * Type is promoted to unsafe when it is not already unsafe and it (1) depends on an unsafe - * type, or (2) its class initializer was promoted to unsafe. - * - * @return if pomotion to unsafe happened - */ - private boolean tryPromoteToUnsafe(AnalysisType type, Set unsafeOrProcessed) { - if (unsafeOrProcessed.contains(type)) { - return false; - } else { - unsafeOrProcessed.add(type); - if ((type.getClassInitializer() != null && methodSafety.get(type.getClassInitializer()) == Safety.UNSAFE) || - dependencies.get(type).stream().anyMatch(t -> types.get(t) == Safety.UNSAFE || tryPromoteToUnsafe(t, unsafeOrProcessed))) { - setUnsafe(type); - return true; - } else { - return false; - } - } - } - - /** - * A method is unsafe if (1) any of it's invokes are unsafe or (2) the method depends on an - * unsafe class initializer. - */ - private boolean updateMethodSafety(AnalysisMethod m) { - assert methodSafety.get(m) == Safety.SAFE; - for (var invoke : m.getInvokes()) { - if (isInvokeUnsafeIterative(invoke)) { - methodSafety.put(m, Safety.UNSAFE); - return true; - } - } - if (hostVM.getInitializedClasses(m).stream().anyMatch(this::isUnsafe)) { - methodSafety.put(m, Safety.UNSAFE); - return true; - } - return false; - } - - /** - * Invoke becomes unsafe if it calls other unsafe methods. - */ - private boolean isInvokeUnsafeIterative(InvokeInfo i) { - /* - * Note that even though (for now) we only process invokes that can be statically bound, we - * cannot just take the target method of the type flow: the static analysis can - * de-virtualize the target method to a method overridden in a subclass. So we must look at - * the actual callees of the type flow, even though we know that there is at most one callee - * returned. - */ - for (AnalysisMethod callee : i.getOriginalCallees()) { - if (methodSafety.get(callee) == Safety.UNSAFE) { - return true; - } - } - return false; - } - - private void addInitializer(AnalysisType t) { - ResolvedJavaType rt = t.getWrapped(); - boolean isSubstituted = false; - if (rt instanceof SubstitutionType) { - SubstitutionType substitutionType = (SubstitutionType) rt; - isSubstituted = AnnotationAccess.isAnnotationPresent(substitutionType, Substitute.class) || AnnotationAccess.isAnnotationPresent(substitutionType, Delete.class); - } - types.put(t, isSubstituted ? Safety.UNSAFE : initialTypeInitializerSafety(t)); - dependencies.put(t, new HashSet<>()); - } - - Set getDependencies(AnalysisType type) { - return Collections.unmodifiableSet(dependencies.get(type)); - } - -} 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 3bbebb681741..c3d1b2420cf0 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 @@ -57,7 +57,7 @@ public void afterRegistration(AfterRegistrationAccess access) { rci.initializeAtBuildTime("java.text", JDK_CLASS_REASON); rci.initializeAtBuildTime("java.time", JDK_CLASS_REASON); rci.initializeAtBuildTime("java.util", JDK_CLASS_REASON); - rci.rerunInitialization("java.util.concurrent.SubmissionPublisher", "Executor service must be recomputed"); + rci.initializeAtRunTime("java.util.concurrent.SubmissionPublisher", "Executor service must be recomputed"); rci.initializeAtBuildTime("javax.annotation.processing", JDK_CLASS_REASON); rci.initializeAtBuildTime("javax.lang.model", JDK_CLASS_REASON); @@ -90,25 +90,25 @@ public void afterRegistration(AfterRegistrationAccess access) { rci.initializeAtBuildTime("sun.nio", JDK_CLASS_REASON); if (Platform.includedIn(Platform.WINDOWS.class)) { - rci.rerunInitialization("sun.nio.ch.PipeImpl", "Contains SecureRandom reference, therefore can't be included in the image heap"); + rci.initializeAtRunTime("sun.nio.ch.PipeImpl", "Contains SecureRandom reference, therefore can't be included in the image heap"); } - rci.rerunInitialization("sun.net.PortConfig", "Calls PortConfig.getLower0() and PortConfig.getUpper0()"); + rci.initializeAtRunTime("sun.net.PortConfig", "Calls PortConfig.getLower0() and PortConfig.getUpper0()"); /* * In the cases that java.io.ObjectInputFilter$Config#serialFilter field is needed, this - * class needs to be reinitialized. Field is initialized in the static block of the Config - * class in runtime, so we need to reinitialize class in runtime. This change also makes us + * class needs to be initialized. Field is initialized in the static block of the Config + * class in runtime, so we need to initialize class at run time. This change also makes us * create substitution for jdkSerialFilterFactory in the * com.oracle.svm.core.jdk.Target_jdk_internal_util_StaticProperty. */ - rci.rerunInitialization("java.io.ObjectInputFilter$Config", "Field filter have to be initialized at runtime"); + rci.initializeAtRunTime("java.io.ObjectInputFilter$Config", "Field filter have to be initialized at runtime"); - rci.rerunInitialization("sun.nio.ch.DevPollArrayWrapper", "Calls IOUtil.fdLimit()"); - rci.rerunInitialization("sun.nio.ch.EPoll", "Calls EPoll.eventSize(), EPoll.eventsOffset() and EPoll.dataOffset()"); - rci.rerunInitialization("sun.nio.ch.EPollSelectorImpl", "Calls IOUtil.fdLimit()"); - rci.rerunInitialization("sun.nio.ch.EventPortSelectorImpl", "Calls IOUtil.fdLimit()"); - rci.rerunInitialization("sun.nio.fs.LinuxWatchService$Poller", "LinuxWatchService.eventSize() and LinuxWatchService.eventOffsets()"); + rci.initializeAtRunTime("sun.nio.ch.DevPollArrayWrapper", "Calls IOUtil.fdLimit()"); + rci.initializeAtRunTime("sun.nio.ch.EPoll", "Calls EPoll.eventSize(), EPoll.eventsOffset() and EPoll.dataOffset()"); + rci.initializeAtRunTime("sun.nio.ch.EPollSelectorImpl", "Calls IOUtil.fdLimit()"); + rci.initializeAtRunTime("sun.nio.ch.EventPortSelectorImpl", "Calls IOUtil.fdLimit()"); + rci.initializeAtRunTime("sun.nio.fs.LinuxWatchService$Poller", "LinuxWatchService.eventSize() and LinuxWatchService.eventOffsets()"); rci.initializeAtBuildTime("sun.reflect", JDK_CLASS_REASON); rci.initializeAtBuildTime("sun.security.mscapi", JDK_CLASS_REASON); @@ -172,24 +172,26 @@ public void afterRegistration(AfterRegistrationAccess access) { rci.initializeAtBuildTime("sun.rmi.transport.GC", "Loaded an unneeded library (rmi) in static initializer."); rci.initializeAtBuildTime("sun.rmi.transport.GC$LatencyLock", "Loaded an unneeded library (rmi) in static initializer."); - rci.rerunInitialization("com.sun.jndi.dns.DnsClient", "Contains Random references, therefore can't be included in the image heap."); - rci.rerunInitialization("sun.net.www.protocol.http.DigestAuthentication$Parameters", "Contains Random references, therefore can't be included in the image heap."); - rci.rerunInitialization("sun.security.krb5.KrbServiceLocator", "Contains Random references, therefore can't be included in the image heap."); - rci.rerunInitialization("com.sun.jndi.ldap.ServiceLocator", "Contains Random references, therefore can't be included in the image heap."); + rci.initializeAtRunTime("com.sun.jndi.dns.DnsClient", "Contains Random references, therefore can't be included in the image heap."); + rci.initializeAtRunTime("sun.net.www.protocol.http.DigestAuthentication$Parameters", "Contains Random references, therefore can't be included in the image heap."); + rci.initializeAtRunTime("sun.security.krb5.KrbServiceLocator", "Contains Random references, therefore can't be included in the image heap."); + rci.initializeAtRunTime("com.sun.jndi.ldap.ServiceLocator", "Contains Random references, therefore can't be included in the image heap."); - // The random number provider classes should be reinitialized at runtime to reset their - // values properly. Otherwise the numbers generated will be fixed for each generated image. - rci.rerunInitialization("java.lang.Math$RandomNumberGeneratorHolder", "Contains random seeds"); - rci.rerunInitialization("java.lang.StrictMath$RandomNumberGeneratorHolder", "Contains random seeds"); + /* + * The random number provider classes should be initialized at run time to reset their + * values properly. Otherwise the numbers generated will be fixed for each generated image. + */ + rci.initializeAtRunTime("java.lang.Math$RandomNumberGeneratorHolder", "Contains random seeds"); + rci.initializeAtRunTime("java.lang.StrictMath$RandomNumberGeneratorHolder", "Contains random seeds"); - rci.rerunInitialization("jdk.internal.misc.InnocuousThread", "Contains a thread group INNOCUOUSTHREADGROUP."); + rci.initializeAtRunTime("jdk.internal.misc.InnocuousThread", "Contains a thread group INNOCUOUSTHREADGROUP."); - rci.rerunInitialization("sun.nio.ch.Poller", "Contains an InnocuousThread."); - rci.rerunInitialization("jdk.internal.jimage", "Pulls in direct byte buffers"); + rci.initializeAtRunTime("sun.nio.ch.Poller", "Contains an InnocuousThread."); + rci.initializeAtRunTime("jdk.internal.jimage", "Pulls in direct byte buffers"); - rci.rerunInitialization("sun.net.www.protocol.jrt.JavaRuntimeURLConnection", "Pulls in jimage reader"); + rci.initializeAtRunTime("sun.net.www.protocol.jrt.JavaRuntimeURLConnection", "Pulls in jimage reader"); - rci.rerunInitialization("sun.launcher.LauncherHelper", "Pulls in jimage reader"); + rci.initializeAtRunTime("sun.launcher.LauncherHelper", "Pulls in jimage reader"); rci.initializeAtRunTime("jdk.internal.foreign.abi.fallback.LibFallback$NativeConstants", "Fails build-time initialization"); rci.initializeAtRunTime("jdk.internal.foreign.abi.fallback.FFIType", "Fails build-time initialization"); @@ -198,13 +200,13 @@ public void afterRegistration(AfterRegistrationAccess access) { rci.initializeAtRunTime("com.sun.org.apache.xml.internal.serialize.HTMLdtd", "Fails build-time initialization"); - rci.rerunInitialization("sun.security.ssl.SSLContextImpl$DefaultSSLContextHolder", "Stores secure random"); - rci.rerunInitialization("sun.security.ssl.SSLSocketFactoryImpl", "Stores secure random"); - rci.rerunInitialization("sun.security.provider.certpath.ssl.SSLServerCertStore", "Stores secure random"); + 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"); - rci.rerunInitialization("jdk.internal.foreign.SystemLookup$WindowsFallbackSymbols", "Does not work on non-Windows modular images"); + rci.initializeAtRunTime("jdk.internal.foreign.SystemLookup$WindowsFallbackSymbols", "Does not work on non-Windows modular images"); - rci.rerunInitialization("jdk.internal.logger.LoggerFinderLoader", "Contains a static field with a FilePermission value"); + rci.initializeAtRunTime("jdk.internal.logger.LoggerFinderLoader", "Contains a static field with a FilePermission value"); /* * The local class Holder in FallbackLinker#getInstance fails the build time initialization diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKRegistrations.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKRegistrations.java index 26173dab50b5..33289d653864 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKRegistrations.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKRegistrations.java @@ -46,34 +46,34 @@ class JDKRegistrations extends JNIRegistrationUtil implements InternalFeature { */ @Override public void duringSetup(DuringSetupAccess a) { - rerunClassInit(a, "java.io.RandomAccessFile", "java.lang.ProcessEnvironment", "java.io.File$TempDirectory", "java.nio.file.TempFileHelper", "java.lang.Terminator"); - rerunClassInit(a, "java.lang.ProcessImpl", "java.lang.ProcessHandleImpl", "java.lang.ProcessHandleImpl$Info", "java.io.FilePermission"); + initializeAtRunTime(a, "java.io.RandomAccessFile", "java.lang.ProcessEnvironment", "java.io.File$TempDirectory", "java.nio.file.TempFileHelper", "java.lang.Terminator"); + initializeAtRunTime(a, "java.lang.ProcessImpl", "java.lang.ProcessHandleImpl", "java.lang.ProcessHandleImpl$Info", "java.io.FilePermission"); /* * The class initializer queries and caches state (like "is a tty") - some state on JDK 17 * and even more after JDK 17. */ - rerunClassInit(a, "java.io.Console"); + initializeAtRunTime(a, "java.io.Console"); /* * Holds system and user library paths derived from the `java.library.path` and * `sun.boot.library.path` system properties. */ - rerunClassInit(a, "jdk.internal.loader.NativeLibraries$LibraryPaths"); + initializeAtRunTime(a, "jdk.internal.loader.NativeLibraries$LibraryPaths"); /* * Contains lots of state that is only available at run time: loads a native library, stores * a `Random` object and the temporary directory in a static final field. */ - rerunClassInit(a, "sun.nio.ch.UnixDomainSockets"); + initializeAtRunTime(a, "sun.nio.ch.UnixDomainSockets"); - rerunClassInit(a, "java.util.concurrent.ThreadLocalRandom$ThreadLocalRandomProxy"); + initializeAtRunTime(a, "java.util.concurrent.ThreadLocalRandom$ThreadLocalRandomProxy"); /* * Re-initialize the registered shutdown hooks, because any hooks registered during native * image construction must not survive into the running image. Both classes have only static * members and do not allow instantiation. */ - rerunClassInit(a, "java.lang.ApplicationShutdownHooks", "java.io.DeleteOnExitHook"); + initializeAtRunTime(a, "java.lang.ApplicationShutdownHooks", "java.io.DeleteOnExitHook"); /* Trigger initialization of java.net.URLConnection.fileNameMap. */ java.net.URLConnection.getFileNameMap(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationJavaNet.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationJavaNet.java index 47137829113e..24d423df9ebc 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationJavaNet.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationJavaNet.java @@ -50,16 +50,16 @@ class JNIRegistrationJavaNet extends JNIRegistrationUtil implements InternalFeat public void duringSetup(DuringSetupAccess a) { /* jdk.net.ExtendedSocketOptions is only available if the jdk.net module is loaded. */ this.hasPlatformSocketOptions = a.findClassByName("jdk.net.ExtendedSocketOptions$PlatformSocketOptions") != null; - rerunClassInit(a, "java.net.DatagramPacket", "java.net.InetAddress", "java.net.NetworkInterface", + initializeAtRunTime(a, "java.net.DatagramPacket", "java.net.InetAddress", "java.net.NetworkInterface", /* Stores a default SSLContext in a static field. */ "javax.net.ssl.SSLContext"); if (this.hasPlatformSocketOptions) { - rerunClassInit(a, "jdk.net.ExtendedSocketOptions", "jdk.net.ExtendedSocketOptions$PlatformSocketOptions", "sun.net.ext.ExtendedSocketOptions"); + initializeAtRunTime(a, "jdk.net.ExtendedSocketOptions", "jdk.net.ExtendedSocketOptions$PlatformSocketOptions", "sun.net.ext.ExtendedSocketOptions"); } if (isDarwin()) { /* Caches the default interface. */ - rerunClassInit(a, "java.net.DefaultInterface"); + initializeAtRunTime(a, "java.net.DefaultInterface"); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationJavaNio.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationJavaNio.java index 591b7dc9ab36..ae6935b3c5a3 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationJavaNio.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationJavaNio.java @@ -65,25 +65,25 @@ public class JNIRegistrationJavaNio extends JNIRegistrationUtil implements Inter @Override public void duringSetup(DuringSetupAccess a) { - rerunClassInit(a, "sun.nio.ch.IOUtil", "sun.nio.ch.ServerSocketChannelImpl", "sun.nio.ch.DatagramChannelImpl", "sun.nio.ch.FileChannelImpl", "sun.nio.ch.FileKey"); - rerunClassInit(a, "java.nio.file.Files$FileTypeDetectors"); - rerunClassInit(a, "sun.nio.ch.Net", "sun.nio.ch.SocketOptionRegistry$LazyInitialization"); - rerunClassInit(a, "sun.nio.ch.AsynchronousSocketChannelImpl$DefaultOptionsHolder", "sun.nio.ch.AsynchronousServerSocketChannelImpl$DefaultOptionsHolder", + initializeAtRunTime(a, "sun.nio.ch.IOUtil", "sun.nio.ch.ServerSocketChannelImpl", "sun.nio.ch.DatagramChannelImpl", "sun.nio.ch.FileChannelImpl", "sun.nio.ch.FileKey"); + initializeAtRunTime(a, "java.nio.file.Files$FileTypeDetectors"); + initializeAtRunTime(a, "sun.nio.ch.Net", "sun.nio.ch.SocketOptionRegistry$LazyInitialization"); + initializeAtRunTime(a, "sun.nio.ch.AsynchronousSocketChannelImpl$DefaultOptionsHolder", "sun.nio.ch.AsynchronousServerSocketChannelImpl$DefaultOptionsHolder", "sun.nio.ch.DatagramChannelImpl$DefaultOptionsHolder", "sun.nio.ch.ServerSocketChannelImpl$DefaultOptionsHolder", "sun.nio.ch.SocketChannelImpl$DefaultOptionsHolder"); /* Ensure that the interrupt signal handler is initialized at runtime. */ - rerunClassInit(a, "sun.nio.ch.NativeThread"); - rerunClassInit(a, "sun.nio.ch.FileDispatcherImpl", "sun.nio.ch.FileChannelImpl$Unmapper"); + initializeAtRunTime(a, "sun.nio.ch.NativeThread"); + initializeAtRunTime(a, "sun.nio.ch.FileDispatcherImpl", "sun.nio.ch.FileChannelImpl$Unmapper"); if (isPosix()) { - rerunClassInit(a, "sun.nio.ch.SimpleAsynchronousFileChannelImpl", "sun.nio.ch.SimpleAsynchronousFileChannelImpl$DefaultExecutorHolder", + initializeAtRunTime(a, "sun.nio.ch.SimpleAsynchronousFileChannelImpl", "sun.nio.ch.SimpleAsynchronousFileChannelImpl$DefaultExecutorHolder", "sun.nio.ch.SinkChannelImpl", "sun.nio.ch.SourceChannelImpl"); - rerunClassInit(a, "sun.nio.fs.UnixNativeDispatcher", "sun.nio.ch.UnixAsynchronousServerSocketChannelImpl"); + initializeAtRunTime(a, "sun.nio.fs.UnixNativeDispatcher", "sun.nio.ch.UnixAsynchronousServerSocketChannelImpl"); if (isLinux() && isJdkSctpModulePresent) { - rerunClassInit(a, "sun.nio.ch.sctp.SctpChannelImpl"); + initializeAtRunTime(a, "sun.nio.ch.sctp.SctpChannelImpl"); } } else if (isWindows()) { - rerunClassInit(a, "sun.nio.ch.WindowsAsynchronousFileChannelImpl", "sun.nio.ch.WindowsAsynchronousFileChannelImpl$DefaultIocpHolder"); - rerunClassInit(a, "sun.nio.fs.WindowsNativeDispatcher", "sun.nio.fs.WindowsSecurity", "sun.nio.ch.Iocp", + initializeAtRunTime(a, "sun.nio.ch.WindowsAsynchronousFileChannelImpl", "sun.nio.ch.WindowsAsynchronousFileChannelImpl$DefaultIocpHolder"); + initializeAtRunTime(a, "sun.nio.fs.WindowsNativeDispatcher", "sun.nio.fs.WindowsSecurity", "sun.nio.ch.Iocp", "sun.nio.ch.WindowsAsynchronousServerSocketChannelImpl", "sun.nio.ch.WindowsAsynchronousSocketChannelImpl"); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationManagementExt.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationManagementExt.java index 0be48e4f9af6..f847ec0d8f69 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationManagementExt.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationManagementExt.java @@ -44,7 +44,7 @@ public class JNIRegistrationManagementExt extends JNIRegistrationUtil implements InternalFeature { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { - rerunClassInit(access, "com.sun.management.internal.OperatingSystemImpl"); + initializeAtRunTime(access, "com.sun.management.internal.OperatingSystemImpl"); access.registerReachabilityHandler(this::linkManagementExt, clazz(access, "com.sun.management.internal.OperatingSystemImpl")); PlatformNativeLibrarySupport.singleton().addBuiltinPkgNativePrefix("com_sun_management_internal_OperatingSystemImpl"); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationPrefs.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationPrefs.java index 9283d4b389b0..fbe04be59cda 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationPrefs.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationPrefs.java @@ -66,13 +66,13 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { * ensure we pick up the loadLibrary call and properly link against the library. */ String preferencesImplementation = getPlatformPreferencesClassName(); - rerunClassInit(access, preferencesImplementation); + initializeAtRunTime(access, preferencesImplementation); ArrayList> triggers = new ArrayList<>(); triggers.add(clazz(access, preferencesImplementation)); if (isDarwin()) { String darwinSpecificClass = "java.util.prefs.MacOSXPreferencesFile"; - rerunClassInit(access, darwinSpecificClass); + initializeAtRunTime(access, darwinSpecificClass); triggers.add(clazz(access, darwinSpecificClass)); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationsJavaZip.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationsJavaZip.java index b14d0a53bb01..c82aba70728e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationsJavaZip.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JNIRegistrationsJavaZip.java @@ -38,10 +38,10 @@ class JNIRegistrationsJavaZip extends JNIRegistrationUtil implements InternalFea @Override public void duringSetup(DuringSetupAccess a) { - rerunClassInit(a, "java.util.zip.Inflater", "java.util.zip.Deflater"); + initializeAtRunTime(a, "java.util.zip.Inflater", "java.util.zip.Deflater"); /* These classes have class initializers that lazily load the zip library. */ - rerunClassInit(a, "java.util.zip.Adler32", "java.util.zip.CRC32"); - rerunClassInit(a, "sun.net.www.protocol.jar.JarFileFactory", "sun.net.www.protocol.jar.JarURLConnection"); + initializeAtRunTime(a, "java.util.zip.Adler32", "java.util.zip.CRC32"); + initializeAtRunTime(a, "sun.net.www.protocol.jar.JarFileFactory", "sun.net.www.protocol.jar.JarURLConnection"); } @Override diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/clinit/TestClassInitialization.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/clinit/TestClassInitialization.java index 75773878cfc4..3b20d98ec077 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/clinit/TestClassInitialization.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/clinit/TestClassInitialization.java @@ -41,30 +41,15 @@ import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; import com.oracle.svm.core.NeverInline; -import com.oracle.svm.hosted.classinitialization.ClassInitializationOptions; import com.oracle.svm.hosted.fieldfolding.IsStaticFinalFieldInitializedNode; import jdk.internal.misc.Unsafe; /* * The suffix of class names indicates the time when the class initializer is determined to be safe - * for execution/simulation at image build time. The tests are run in two configurations: 1) with - * the "old" class initialization strategy, which uses the "early" and "late" class initializer - * analysis to initialize classes at image build time, and 2) with the "new" class initialization - * strategy where all classes can be used at image build time, but class initializer are simulated - * to avoid explicit initialization at image build time. This leads to the following 4 suffixes: + * for simulation at image build time. * - * - MustBeSafeEarly: The early class initializer analysis (before static analysis) finds this class - * as safe for initialization at image build time. The simulation of class initializer also must - * succeed, because it is more powerful than the early analysis. - * - * - MustBeSafeLate: The late class initializer analysis (ater static analysis) finds this class as - * safe for initialization at image build time. The * simulation of class initializer also must - * succeed, because it is more powerful than the late analysis. - * - * - MustBeSimulated: Neither the early nor the late analysis finds this class as safe for - * initialization. But the simulation of class initializer succeeded, i.e., with the "new" class - * initialization strategy the class starts out as initialized at run time. + * - MustBeSimulated: The simulation of class initializer succeeded, i.e., the class starts out as initialized at run time. * * - MustBeDelayed: The class initializer has side effects, it must be executed at run time. * @@ -73,7 +58,7 @@ * log output (code in mx_substratevm.py). */ -class PureMustBeSafeEarly { +class PureMustBeSimulated { static int v; static { v = 1; @@ -84,7 +69,7 @@ class PureMustBeSafeEarly { class InitializesPureMustBeDelayed { static int v; static { - v = PureMustBeSafeEarly.v; + v = PureMustBeSimulated.v; } } @@ -106,7 +91,7 @@ static boolean alwaysTrue() { } } -class PureCallMustBeSafeEarly { +class PureCallMustBeSimulated { static int v; static { v = TestClassInitialization.pure(); @@ -155,20 +140,20 @@ class CreatesAnExceptionMustBeDelayed { class ThrowsAnExceptionUninitializedMustBeDelayed { static int v = 1; static { - if (PureMustBeSafeEarly.v == 42) { + if (PureMustBeSimulated.v == 42) { throw new RuntimeException("should fire at runtime"); } } } -interface PureInterfaceMustBeSafeEarly { +interface PureInterfaceMustBeSimulated { } class PureSubclassMustBeDelayed extends SuperClassMustBeDelayed { static int v = 1; } -class SuperClassMustBeDelayed implements PureInterfaceMustBeSafeEarly { +class SuperClassMustBeDelayed implements PureInterfaceMustBeSimulated { static { System.out.println("Delaying SuperClassMustBeDelayed"); } @@ -200,7 +185,7 @@ default int m() { } } -class PureSubclassInheritsDelayedInterfaceMustBeSafeEarly implements InterfaceNonPureMustBeDelayed { +class PureSubclassInheritsDelayedInterfaceMustBeSimulated implements InterfaceNonPureMustBeDelayed { static int v = 1; } @@ -228,7 +213,7 @@ class PureDependsOnImplicitExceptionUninitializedMustBeDelayed { } } -class StaticFieldHolderMustBeSafeEarly { +class StaticFieldHolderMustBeSimulated { /** * Other class initializers that modify {@link #a} must not run at image build time so that the * initial value 111 assigned here can be read at run time. @@ -242,7 +227,7 @@ static void setA(int value) { class StaticFieldModifer1MustBeDelayed { static { - StaticFieldHolderMustBeSafeEarly.a = 222; + StaticFieldHolderMustBeSimulated.a = 222; } static void triggerInitialization() { @@ -251,14 +236,14 @@ static void triggerInitialization() { class StaticFieldModifer2MustBeDelayed { static { - StaticFieldHolderMustBeSafeEarly.setA(333); + StaticFieldHolderMustBeSimulated.setA(333); } static void triggerInitialization() { } } -class RecursionInInitializerMustBeSafeLate { +class RecursionInInitializerMustBeSimulated { static int i = compute(200); static int compute(int n) { @@ -270,8 +255,8 @@ static int compute(int n) { } } -class UnsafeAccessMustBeSafeLate { - static UnsafeAccessMustBeSafeLate value = compute(); +class UnsafeAccessMustBeSimulated { + static UnsafeAccessMustBeSimulated value = compute(); int f01; int f02; @@ -290,8 +275,8 @@ class UnsafeAccessMustBeSafeLate { int f15; int f16; - static UnsafeAccessMustBeSafeLate compute() { - UnsafeAccessMustBeSafeLate result = new UnsafeAccessMustBeSafeLate(); + static UnsafeAccessMustBeSimulated compute() { + UnsafeAccessMustBeSimulated result = new UnsafeAccessMustBeSimulated(); /* * We are writing a random instance field, depending on the header size. But the object is * big enough so that the write is one of the fields. The unsafe write is converted to a @@ -303,14 +288,14 @@ static UnsafeAccessMustBeSafeLate compute() { } } -enum EnumMustBeSafeEarly { +enum EnumMustBeSimulated { V1(null), V2("Hello"), V3(new Object()); final Object value; - EnumMustBeSafeEarly(Object value) { + EnumMustBeSimulated(Object value) { this.value = value; } @@ -359,40 +344,20 @@ static void reference() { } /** - * Class initializer references a helper class that can be initialized early. Since the early class - * initializer analysis recursviely processes dependent classes, this class is also safe for early - * initialization. + * Cycle between this class and a helper class. */ -class ReferencesOtherPureClassMustBeSafeEarly { +class CycleMustBeSimulated { static { - HelperClassMustBeSafeEarly.foo(); + HelperClassMustBeSimulated.foo(); } static void foo() { } } -class HelperClassMustBeSafeEarly { - static void foo() { - } -} - -/** - * Cycle between this class and a helper class. Even though both classes could be initialized early, - * the early analysis bails out because analyzing cycles would be too complicated. - */ -class CycleMustBeSafeLate { +class HelperClassMustBeSimulated { static { - HelperClassMustBeSafeLate.foo(); - } - - static void foo() { - } -} - -class HelperClassMustBeSafeLate { - static { - CycleMustBeSafeLate.foo(); + CycleMustBeSimulated.foo(); } static void foo() { @@ -400,7 +365,7 @@ static void foo() { } /** Various reflection lookup methods are safe for execution at image build time. */ -class ReflectionMustBeSafeEarly { +class ReflectionMustBeSimulated { static Class c1; static Class c2; static Method m1; @@ -408,20 +373,20 @@ class ReflectionMustBeSafeEarly { static { try { - Class c1Local = Class.forName("com.oracle.svm.test.clinit.ForNameMustBeSafeEarly", true, ReflectionMustBeSafeEarly.class.getClassLoader()); + Class c1Local = Class.forName("com.oracle.svm.test.clinit.ForNameMustBeSimulated", true, ReflectionMustBeSimulated.class.getClassLoader()); c1 = c1Local; /** * Looking up a class that cannot be initialized at build time is allowed, as long as * `initialize` is `false`. */ - Class c2Local = Class.forName("com.oracle.svm.test.clinit.ForNameUninitializedMustBeDelayed", false, ReflectionMustBeSafeEarly.class.getClassLoader()); + Class c2Local = Class.forName("com.oracle.svm.test.clinit.ForNameUninitializedMustBeDelayed", false, ReflectionMustBeSimulated.class.getClassLoader()); c2 = c2Local; /* - * Calling getDeclaredMethod on the field c1 instead of the variable c1Local would not - * work, because the field load cannot be constant folded by the - * EarlyClassInitializerAnalysis. + * GR-51519: Calling getDeclaredMethod on the field c1 instead of the variable c1Local + * would not work, the ReflectionPlugins do not see through simulated image heap + * constants for the parameterTypes array yet. */ m1 = c1Local.getDeclaredMethod("foo", int.class); f2 = c2Local.getDeclaredField("field"); @@ -430,7 +395,7 @@ class ReflectionMustBeSafeEarly { * Check that reflective class lookup and the elimination of the class initialization * check also works when the class name is not constant yet during bytecode parsing. */ - if (c1Local != Class.forName(forNameMustBeSafeEarly(), true, ReflectionMustBeSafeEarly.class.getClassLoader())) { + if (c1Local != Class.forName(forNameMustBeSimulated(), true, ReflectionMustBeSimulated.class.getClassLoader())) { throw new Error("wrong class"); } @@ -439,13 +404,13 @@ class ReflectionMustBeSafeEarly { } } - private static String forNameMustBeSafeEarly() { - return "com.oracle.svm.test.clinit.ForNameMustBeSafeEarly"; + private static String forNameMustBeSimulated() { + return "com.oracle.svm.test.clinit.ForNameMustBeSimulated"; } } @SuppressWarnings("unused") -class ForNameMustBeSafeEarly { +class ForNameMustBeSimulated { static void foo(int arg) { } } @@ -466,13 +431,13 @@ class DevirtualizedCallMustBeDelayed { static final Object value = 42; } -class DevirtualizedCallSuperMustBeSafeEarly { +class DevirtualizedCallSuperMustBeSimulated { Object foo() { return -1; } } -class DevirtualizedCallSubMustBeSafeEarly extends DevirtualizedCallSuperMustBeSafeEarly { +class DevirtualizedCallSubMustBeSimulated extends DevirtualizedCallSuperMustBeSimulated { @Override Object foo() { return DevirtualizedCallMustBeDelayed.value; @@ -483,7 +448,7 @@ class DevirtualizedCallUsageMustBeDelayed { static final Object value = computeValue(); private static Object computeValue() { - DevirtualizedCallSuperMustBeSafeEarly provider = createProvider(); + DevirtualizedCallSuperMustBeSimulated provider = createProvider(); /* * The static analysis can prove that DevirtualizedCallSubMustBeDelayed.foo is the only @@ -494,8 +459,8 @@ private static Object computeValue() { return provider.foo(); } - private static DevirtualizedCallSuperMustBeSafeEarly createProvider() { - return new DevirtualizedCallSubMustBeSafeEarly(); + private static DevirtualizedCallSuperMustBeSimulated createProvider() { + return new DevirtualizedCallSubMustBeSimulated(); } } @@ -541,19 +506,19 @@ int virtualMethod() { } } -class StaticFinalFieldFoldingMustBeSafeEarly { +class StaticFinalFieldFoldingMustBeSimulated { Object f1; Object f2; Object f3; - StaticFinalFieldFoldingMustBeSafeEarly() { + StaticFinalFieldFoldingMustBeSimulated() { this.f1 = F1; this.f2 = F2; this.f3 = F3; } - static final StaticFinalFieldFoldingMustBeSafeEarly before = new StaticFinalFieldFoldingMustBeSafeEarly(); + static final StaticFinalFieldFoldingMustBeSimulated before = new StaticFinalFieldFoldingMustBeSimulated(); /** * Field value is stored in the class file attribute, so it is available even before this @@ -568,10 +533,10 @@ class StaticFinalFieldFoldingMustBeSafeEarly { /** Just a regular field. */ static final Object F3 = new String[]{"abc"}; - static final StaticFinalFieldFoldingMustBeSafeEarly after = new StaticFinalFieldFoldingMustBeSafeEarly(); + static final StaticFinalFieldFoldingMustBeSimulated after = new StaticFinalFieldFoldingMustBeSimulated(); } -class LambdaMustBeSafeLate { +class LambdaMustBeSimulated { private static final Predicate IS_AUTOMATIC = s -> s.equals("Hello"); static boolean matches(List l) { @@ -619,13 +584,13 @@ public static Object defaultValue(Class clazz) { } } -class SingleByteFieldMustBeSafeEarly { - static SingleByteFieldMustBeSafeEarly instance1 = new SingleByteFieldMustBeSafeEarly((byte) 42); - static SingleByteFieldMustBeSafeEarly instance2 = new SingleByteFieldMustBeSafeEarly((byte) -42); +class SingleByteFieldMustBeSimulated { + static SingleByteFieldMustBeSimulated instance1 = new SingleByteFieldMustBeSimulated((byte) 42); + static SingleByteFieldMustBeSimulated instance2 = new SingleByteFieldMustBeSimulated((byte) -42); byte b; - SingleByteFieldMustBeSafeEarly(byte b) { + SingleByteFieldMustBeSimulated(byte b) { this.b = b; } } @@ -758,23 +723,20 @@ static int add() { } } -abstract class TestClassInitializationFeature implements Feature { +class TestClassInitializationFeature implements Feature { - private void checkClasses(boolean checkSafeEarly, boolean checkSafeLate) { - System.out.println("=== Checking initialization state of classes: checkSafeEarly=" + checkSafeEarly + ", checkSafeLate=" + checkSafeLate); + private static void checkClasses() { + System.out.println("=== Checking initialization state of classes"); List errors = new ArrayList<>(); for (Class checkedClass : TestClassInitialization.checkedClasses) { - boolean nameHasSafeEarly = checkedClass.getName().contains("MustBeSafeEarly"); - boolean nameHasSafeLate = checkedClass.getName().contains("MustBeSafeLate"); boolean nameHasSimulated = checkedClass.getName().contains("MustBeSimulated"); boolean nameHasDelayed = checkedClass.getName().contains("MustBeDelayed"); - if ((nameHasSafeEarly ? 1 : 0) + (nameHasSafeLate ? 1 : 0) + (nameHasSimulated ? 1 : 0) + (nameHasDelayed ? 1 : 0) != 1) { - errors.add(checkedClass.getName() + ": Wrongly named class: nameHasSafeEarly=" + nameHasSafeEarly + ", nameHasSafeLate=" + nameHasSafeLate + - ", nameHasSimulated=" + nameHasSimulated + ", nameHasDelayed=" + nameHasDelayed); - } else { - checkClass(checkedClass, checkSafeEarly, checkSafeLate, errors, nameHasSafeEarly, nameHasSafeLate, nameHasSimulated, nameHasDelayed); + if ((nameHasSimulated ? 1 : 0) + (nameHasDelayed ? 1 : 0) != 1) { + errors.add(checkedClass.getName() + ": Wrongly named class: nameHasSimulated=" + nameHasSimulated + ", nameHasDelayed=" + nameHasDelayed); + } else if (!Unsafe.getUnsafe().shouldBeInitialized(checkedClass)) { + errors.add(checkedClass.getName() + ": Class already initialized at image build time"); } } @@ -783,9 +745,6 @@ private void checkClasses(boolean checkSafeEarly, boolean checkSafeLate) { } } - abstract void checkClass(Class checkedClass, boolean checkSafeEarly, boolean checkSafeLate, List errors, - boolean nameHasSafeEarly, boolean nameHasSafeLate, boolean nameHasSimulated, boolean nameHasDelayed); - @Override public void afterRegistration(AfterRegistrationAccess access) { /* We need to access the checkedClasses array both at image build time and run time. */ @@ -803,30 +762,23 @@ public void afterRegistration(AfterRegistrationAccess access) { assertArraysEqual(new Object[]{Test1_I1.class, Test1_I3.class, Test1_A.class}, InitializationOrder.initializationOrder.toArray()); /* - * The old class initialization policy is wrong regarding interfaces, but we do not want to - * change that now because it will be deleted soon. + * Initialization of an interface does not trigger initialization of superinterfaces. + * Regardless whether any of the involved interfaces declare default methods. */ - if (TestClassInitialization.simulationEnabled) { - - /* - * Initialization of an interface does not trigger initialization of superinterfaces. - * Regardless whether any of the involved interfaces declare default methods. - */ - InitializationOrder.initializationOrder.clear(); - assertNotInitialized(Test2_I1.class, Test2_I2.class, Test2_I3.class, Test2_I4.class); - RuntimeClassInitialization.initializeAtBuildTime(Test2_I4.class); - assertNotInitialized(Test2_I1.class, Test2_I2.class, Test2_I3.class); - assertInitialized(Test2_I4.class); - assertArraysEqual(new Object[]{Test2_I4.class}, InitializationOrder.initializationOrder.toArray()); - RuntimeClassInitialization.initializeAtBuildTime(Test2_I3.class); - assertNotInitialized(Test2_I1.class, Test2_I2.class); - assertInitialized(Test2_I3.class, Test2_I4.class); - assertArraysEqual(new Object[]{Test2_I4.class, Test2_I3.class}, InitializationOrder.initializationOrder.toArray()); - RuntimeClassInitialization.initializeAtBuildTime(Test2_I2.class); - assertNotInitialized(Test2_I1.class); - assertInitialized(Test2_I2.class, Test2_I3.class, Test2_I4.class); - assertArraysEqual(new Object[]{Test2_I4.class, Test2_I3.class, Test2_I2.class}, InitializationOrder.initializationOrder.toArray()); - } + InitializationOrder.initializationOrder.clear(); + assertNotInitialized(Test2_I1.class, Test2_I2.class, Test2_I3.class, Test2_I4.class); + RuntimeClassInitialization.initializeAtBuildTime(Test2_I4.class); + assertNotInitialized(Test2_I1.class, Test2_I2.class, Test2_I3.class); + assertInitialized(Test2_I4.class); + assertArraysEqual(new Object[]{Test2_I4.class}, InitializationOrder.initializationOrder.toArray()); + RuntimeClassInitialization.initializeAtBuildTime(Test2_I3.class); + assertNotInitialized(Test2_I1.class, Test2_I2.class); + assertInitialized(Test2_I3.class, Test2_I4.class); + assertArraysEqual(new Object[]{Test2_I4.class, Test2_I3.class}, InitializationOrder.initializationOrder.toArray()); + RuntimeClassInitialization.initializeAtBuildTime(Test2_I2.class); + assertNotInitialized(Test2_I1.class); + assertInitialized(Test2_I2.class, Test2_I3.class, Test2_I4.class); + assertArraysEqual(new Object[]{Test2_I4.class, Test2_I3.class, Test2_I2.class}, InitializationOrder.initializationOrder.toArray()); } private static void assertNotInitialized(Class... classes) { @@ -853,12 +805,12 @@ private static void assertArraysEqual(Object[] expected, Object[] actual) { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { - checkClasses(false, false); + checkClasses(); } @Override public void duringAnalysis(DuringAnalysisAccess access) { - checkClasses(true, false); + checkClasses(); } @Override @@ -871,114 +823,52 @@ public void afterAnalysis(AfterAnalysisAccess access) { @Override public void beforeCompilation(BeforeCompilationAccess access) { - checkClasses(true, true); + checkClasses(); } @Override public void afterImageWrite(AfterImageWriteAccess access) { - checkClasses(true, true); - } -} - -/** - * For testing with {@link ClassInitializationOptions#StrictImageHeap} set to false and simulation - * of class initializer disabled. - */ -class TestClassInitializationFeatureOldPolicyFeature extends TestClassInitializationFeature { - - @Override - public void afterRegistration(AfterRegistrationAccess access) { - super.afterRegistration(access); - - TestClassInitialization.simulationEnabled = false; - } - - @Override - void checkClass(Class checkedClass, boolean checkSafeEarly, boolean checkSafeLate, List errors, - boolean nameHasSafeEarly, boolean nameHasSafeLate, boolean nameHasSimulated, boolean nameHasDelayed) { - - boolean initialized = !Unsafe.getUnsafe().shouldBeInitialized(checkedClass); - - if (nameHasSafeEarly && initialized != checkSafeEarly) { - errors.add(checkedClass.getName() + ": Check for MustBeSafeEarly failed"); - } - if (nameHasSafeLate && initialized != checkSafeLate) { - errors.add(checkedClass.getName() + ": Check for MustBeSafeLate failed"); - } - if (nameHasSimulated && initialized) { - /* - * Class initializer simulation is disabled in this configuration, so these classes must - * be initialized at run time. - */ - errors.add(checkedClass.getName() + ": Check for MustBeSimulated failed"); - } - if (nameHasDelayed && initialized) { - errors.add(checkedClass.getName() + ": Check for MustBeDelayed failed"); - } - } -} - -/** - * For testing with {@link ClassInitializationOptions#StrictImageHeap} set to true and simulation of - * class initializer enabled. - */ -class TestClassInitializationFeatureNewPolicyFeature extends TestClassInitializationFeature { - @Override - public void afterRegistration(AfterRegistrationAccess access) { - super.afterRegistration(access); - - TestClassInitialization.simulationEnabled = true; - } - - @Override - void checkClass(Class checkedClass, boolean checkSafeEarly, boolean checkSafeLate, List errors, - boolean nameHasSafeEarly, boolean nameHasSafeLate, boolean nameHasSimulated, boolean nameHasDelayed) { - if (!Unsafe.getUnsafe().shouldBeInitialized(checkedClass)) { - errors.add(checkedClass.getName() + ": Class already initialized at image build time"); - } + checkClasses(); } } public class TestClassInitialization { - static boolean simulationEnabled; - static final Class[] checkedClasses = new Class[]{ - PureMustBeSafeEarly.class, + PureMustBeSimulated.class, NonPureMustBeDelayed.class, - PureCallMustBeSafeEarly.class, + PureCallMustBeSimulated.class, InitializesNonPureMustBeDelayed.class, SystemPropReadMustBeDelayed.class, SystemPropWriteMustBeDelayed.class, StartsAThreadMustBeDelayed.class, CreatesAnExceptionMustBeDelayed.class, ThrowsAnExceptionUninitializedMustBeDelayed.class, - PureInterfaceMustBeSafeEarly.class, + PureInterfaceMustBeSimulated.class, PureSubclassMustBeDelayed.class, SuperClassMustBeDelayed.class, InterfaceNonPureMustBeDelayed.class, InterfaceNonPureDefaultMustBeDelayed.class, - PureSubclassInheritsDelayedInterfaceMustBeSafeEarly.class, + PureSubclassInheritsDelayedInterfaceMustBeSimulated.class, PureSubclassInheritsDelayedDefaultInterfaceMustBeDelayed.class, ImplicitExceptionInInitializerUninitializedMustBeDelayed.class, PureDependsOnImplicitExceptionUninitializedMustBeDelayed.class, - StaticFieldHolderMustBeSafeEarly.class, + StaticFieldHolderMustBeSimulated.class, StaticFieldModifer1MustBeDelayed.class, StaticFieldModifer2MustBeDelayed.class, - RecursionInInitializerMustBeSafeLate.class, - UnsafeAccessMustBeSafeLate.class, - EnumMustBeSafeEarly.class, + RecursionInInitializerMustBeSimulated.class, + UnsafeAccessMustBeSimulated.class, + EnumMustBeSimulated.class, NativeMethodMustBeDelayed.class, - ReferencesOtherPureClassMustBeSafeEarly.class, HelperClassMustBeSafeEarly.class, - CycleMustBeSafeLate.class, HelperClassMustBeSafeLate.class, - ReflectionMustBeSafeEarly.class, ForNameMustBeSafeEarly.class, ForNameUninitializedMustBeDelayed.class, - DevirtualizedCallMustBeDelayed.class, DevirtualizedCallSuperMustBeSafeEarly.class, DevirtualizedCallSubMustBeSafeEarly.class, DevirtualizedCallUsageMustBeDelayed.class, + CycleMustBeSimulated.class, HelperClassMustBeSimulated.class, + ReflectionMustBeSimulated.class, ForNameMustBeSimulated.class, ForNameUninitializedMustBeDelayed.class, + DevirtualizedCallMustBeDelayed.class, DevirtualizedCallSuperMustBeSimulated.class, DevirtualizedCallSubMustBeSimulated.class, DevirtualizedCallUsageMustBeDelayed.class, LargeAllocation1MustBeDelayed.class, LargeAllocation2MustBeDelayed.class, ComplexEnumMustBeSimulated.class, - StaticFinalFieldFoldingMustBeSafeEarly.class, - LambdaMustBeSafeLate.class, + StaticFinalFieldFoldingMustBeSimulated.class, + LambdaMustBeSimulated.class, BoxingMustBeSimulated.class, - SingleByteFieldMustBeSafeEarly.class, + SingleByteFieldMustBeSimulated.class, SynchronizedMustBeSimulated.class, SynchronizedMustBeDelayed.class, }; @@ -1005,15 +895,15 @@ public static void main(String[] args) { boolean nameHasSimulated = checkedClass.getName().contains("MustBeSimulated"); boolean nameHasDelayed = checkedClass.getName().contains("MustBeDelayed"); boolean initialized = !Unsafe.getUnsafe().shouldBeInitialized(checkedClass); - if ((nameHasDelayed || (!simulationEnabled && nameHasSimulated)) == initialized) { + if (nameHasDelayed == initialized) { throw new RuntimeException("Class " + checkedClass.getName() + ": nameHasSimulated=" + nameHasSimulated + ", nameHasDelayed=" + nameHasDelayed + ", initialized=" + initialized); } } assertTrue("123123".equals(buildTimeLambda.apply("123"))); - assertSame(42, PureMustBeSafeEarly.v); - assertSame(84, PureCallMustBeSafeEarly.v); + assertSame(42, PureMustBeSimulated.v); + assertSame(84, PureCallMustBeSimulated.v); assertSame(42, InitializesPureMustBeDelayed.v); assertSame(1, NonPureMustBeDelayed.v); assertSame(1, NonPureAccessedFinal.v); @@ -1022,7 +912,7 @@ public static void main(String[] args) { assertSame(1, SystemPropWriteMustBeDelayed.v); assertSame(1, StartsAThreadMustBeDelayed.v); assertSame(1, PureSubclassMustBeDelayed.v); - assertSame(1, PureSubclassInheritsDelayedInterfaceMustBeSafeEarly.v); + assertSame(1, PureSubclassInheritsDelayedInterfaceMustBeSimulated.v); assertSame(1, PureSubclassInheritsDelayedDefaultInterfaceMustBeDelayed.v); assertSame(1, InterfaceNonPureMustBeDelayed.v); try { @@ -1045,33 +935,32 @@ public static void main(String[] args) { /* Expected. */ } - assertSame(111, StaticFieldHolderMustBeSafeEarly.a); + assertSame(111, StaticFieldHolderMustBeSimulated.a); StaticFieldModifer1MustBeDelayed.triggerInitialization(); - assertSame(222, StaticFieldHolderMustBeSafeEarly.a); + assertSame(222, StaticFieldHolderMustBeSimulated.a); StaticFieldModifer2MustBeDelayed.triggerInitialization(); - assertSame(333, StaticFieldHolderMustBeSafeEarly.a); + assertSame(333, StaticFieldHolderMustBeSimulated.a); - assertSame(20100, RecursionInInitializerMustBeSafeLate.i); + assertSame(20100, RecursionInInitializerMustBeSimulated.i); - UnsafeAccessMustBeSafeLate value = UnsafeAccessMustBeSafeLate.value; + UnsafeAccessMustBeSimulated value = UnsafeAccessMustBeSimulated.value; assertSame(1234, value.f01 + value.f02 + value.f03 + value.f04 + value.f05 + value.f06 + value.f07 + value.f08 + value.f09 + value.f10 + value.f11 + value.f12 + value.f13 + value.f14 + value.f15 + value.f16); - EnumMustBeSafeEarly[] values = EnumMustBeSafeEarly.values(); + EnumMustBeSimulated[] values = EnumMustBeSimulated.values(); assertSame(null, values[0].getValue()); assertSame("Hello", values[1].getValue()); assertSame(Object.class, values[2].getValue().getClass()); - assertSame(EnumMustBeSafeEarly.V1, stringToEnum("v1")); + assertSame(EnumMustBeSimulated.V1, stringToEnum("v1")); assertSame(42, NativeMethodMustBeDelayed.i); NativeMethodMustBeDelayed.foo(); - ReferencesOtherPureClassMustBeSafeEarly.foo(); - CycleMustBeSafeLate.foo(); + CycleMustBeSimulated.foo(); - assertSame(ForNameMustBeSafeEarly.class, ReflectionMustBeSafeEarly.c1); - assertSame(ForNameUninitializedMustBeDelayed.class, ReflectionMustBeSafeEarly.c2); - assertSame("foo", ReflectionMustBeSafeEarly.m1.getName()); - assertSame("field", ReflectionMustBeSafeEarly.f2.getName()); + assertSame(ForNameMustBeSimulated.class, ReflectionMustBeSimulated.c1); + assertSame(ForNameUninitializedMustBeDelayed.class, ReflectionMustBeSimulated.c2); + assertSame("foo", ReflectionMustBeSimulated.m1.getName()); + assertSame("field", ReflectionMustBeSimulated.f2.getName()); assertSame(42, DevirtualizedCallUsageMustBeDelayed.value); @@ -1082,15 +971,15 @@ public static void main(String[] args) { assertSame(ComplexEnumMustBeSimulated.V1, ComplexEnumMustBeSimulated.lookup.get("V1")); assertSame(42, ComplexEnumMustBeSimulated.lookup.get("V2").virtualMethod()); - assertSame("abc", StaticFinalFieldFoldingMustBeSafeEarly.before.f1); - assertSame(null, StaticFinalFieldFoldingMustBeSafeEarly.before.f2); - assertSame(null, StaticFinalFieldFoldingMustBeSafeEarly.before.f3); - assertSame("abc", StaticFinalFieldFoldingMustBeSafeEarly.after.f1); - assertSame("abc", StaticFinalFieldFoldingMustBeSafeEarly.after.f2); - assertSame(1, ((Object[]) StaticFinalFieldFoldingMustBeSafeEarly.after.f3).length); + assertSame("abc", StaticFinalFieldFoldingMustBeSimulated.before.f1); + assertSame(null, StaticFinalFieldFoldingMustBeSimulated.before.f2); + assertSame(null, StaticFinalFieldFoldingMustBeSimulated.before.f3); + assertSame("abc", StaticFinalFieldFoldingMustBeSimulated.after.f1); + assertSame("abc", StaticFinalFieldFoldingMustBeSimulated.after.f2); + assertSame(1, ((Object[]) StaticFinalFieldFoldingMustBeSimulated.after.f3).length); - assertSame(true, LambdaMustBeSafeLate.matches(List.of("1", "2", "3", "Hello", "4"))); - assertSame(false, LambdaMustBeSafeLate.matches(List.of("1", "2", "3", "4"))); + assertSame(true, LambdaMustBeSimulated.matches(List.of("1", "2", "3", "Hello", "4"))); + assertSame(false, LambdaMustBeSimulated.matches(List.of("1", "2", "3", "4"))); assertSame(83, BoxingMustBeSimulated.sum); assertSame(Character.class, BoxingMustBeSimulated.defaultValue(char.class).getClass()); @@ -1104,20 +993,20 @@ public static void main(String[] args) { * The unsafe field offset lookup is constant folded at image build time, which also * registers the field as unsafe accessed. */ - long bOffset = Unsafe.getUnsafe().objectFieldOffset(SingleByteFieldMustBeSafeEarly.class, "b"); + long bOffset = Unsafe.getUnsafe().objectFieldOffset(SingleByteFieldMustBeSimulated.class, "b"); assertTrue(bOffset % 4 == 0); /* * Check that for sub-int values, the padding after the value is not touched by the image * heap writer. */ - assertSame(42, readRawByte(SingleByteFieldMustBeSafeEarly.instance1, bOffset + 0)); - assertSame(0, readRawByte(SingleByteFieldMustBeSafeEarly.instance1, bOffset + 1)); - assertSame(0, readRawByte(SingleByteFieldMustBeSafeEarly.instance1, bOffset + 2)); - assertSame(0, readRawByte(SingleByteFieldMustBeSafeEarly.instance1, bOffset + 3)); - assertSame(-42, readRawByte(SingleByteFieldMustBeSafeEarly.instance2, bOffset + 0)); - assertSame(0, readRawByte(SingleByteFieldMustBeSafeEarly.instance2, bOffset + 1)); - assertSame(0, readRawByte(SingleByteFieldMustBeSafeEarly.instance2, bOffset + 2)); - assertSame(0, readRawByte(SingleByteFieldMustBeSafeEarly.instance2, bOffset + 3)); + assertSame(42, readRawByte(SingleByteFieldMustBeSimulated.instance1, bOffset + 0)); + assertSame(0, readRawByte(SingleByteFieldMustBeSimulated.instance1, bOffset + 1)); + assertSame(0, readRawByte(SingleByteFieldMustBeSimulated.instance1, bOffset + 2)); + assertSame(0, readRawByte(SingleByteFieldMustBeSimulated.instance1, bOffset + 3)); + assertSame(-42, readRawByte(SingleByteFieldMustBeSimulated.instance2, bOffset + 0)); + assertSame(0, readRawByte(SingleByteFieldMustBeSimulated.instance2, bOffset + 1)); + assertSame(0, readRawByte(SingleByteFieldMustBeSimulated.instance2, bOffset + 2)); + assertSame(0, readRawByte(SingleByteFieldMustBeSimulated.instance2, bOffset + 3)); assertSame(42, SynchronizedMustBeSimulated.vector.size()); assertSame(42, SynchronizedMustBeDelayed.synchronizedMethod()); @@ -1136,11 +1025,11 @@ static int readRawByte(Object o, long offset) { return Unsafe.getUnsafe().getByte(o, offset); } - private static EnumMustBeSafeEarly stringToEnum(String name) { - if (EnumMustBeSafeEarly.V1.name().equalsIgnoreCase(name)) { - return EnumMustBeSafeEarly.V1; + private static EnumMustBeSimulated stringToEnum(String name) { + if (EnumMustBeSimulated.V1.name().equalsIgnoreCase(name)) { + return EnumMustBeSimulated.V1; } else { - return EnumMustBeSafeEarly.V2; + return EnumMustBeSimulated.V2; } } diff --git a/truffle/src/org.graalvm.shadowed.com.ibm.icu/src/META-INF/native-image/org.graalvm.shadowed.icu4j/native-image.properties b/truffle/src/org.graalvm.shadowed.com.ibm.icu/src/META-INF/native-image/org.graalvm.shadowed.icu4j/native-image.properties index 2bfbd9163073..b551cc8c9ce2 100644 --- a/truffle/src/org.graalvm.shadowed.com.ibm.icu/src/META-INF/native-image/org.graalvm.shadowed.icu4j/native-image.properties +++ b/truffle/src/org.graalvm.shadowed.com.ibm.icu/src/META-INF/native-image/org.graalvm.shadowed.icu4j/native-image.properties @@ -1 +1 @@ -Args = --rerun-class-initialization-at-runtime=org.graalvm.shadowed.com.ibm.icu +Args=--initialize-at-run-time=org.graalvm.shadowed.com.ibm.icu