From 2d5aa3125165983ab45db9e709009f802a6e24c7 Mon Sep 17 00:00:00 2001 From: Christian Wimmer Date: Wed, 17 Jan 2024 12:41:50 -0800 Subject: [PATCH] Registering a class as initialize-at-build-time should immediately trigger initialization --- .../hosted/RuntimeClassInitialization.java | 12 +- ...ostedUsagesClassInitializationSupport.java | 27 +-- .../test/clinit/TestClassInitialization.java | 154 ++++++++++++++++++ 3 files changed, 173 insertions(+), 20 deletions(-) diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeClassInitialization.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeClassInitialization.java index 5d9a0ebc98a5..e2f32a2f425d 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeClassInitialization.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeClassInitialization.java @@ -103,12 +103,20 @@ public static void initializeAtRunTime(Class... classes) { /** * Registers the provided classes as eagerly initialized during image-build time. *

- * All static initializers of {@code classes} will be executed during image-build time and - * static fields that are assigned values will be available at runtime. {@code static final} + * All static initializers of {@code classes} will be executed immediately at image-build time + * and static fields that are assigned values will be available at runtime. {@code static final} * fields will be considered as constant. *

* It is up to the user to ensure that this behavior makes sense and does not lead to wrong * application behavior. + *

+ * After this method returns, all listed classes are initialized in the VM that runs the image + * generator. Therefore, this method can be used to resolve deadlocks and cycles in class + * initializer by starting initialization with a known-good entry class. + *

+ * All superclasses and superinterfaces that are initialized before any of the listed classes + * are registered for initialization at build time too. Please look at the Java specification + * for the exact rules, especially regarding interfaces that declare default methods. * * @since 19.0 */ 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 index 3e0d94661338..f9b73e77c941 100644 --- 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 @@ -26,14 +26,13 @@ import java.lang.reflect.Proxy; -import jdk.graal.compiler.java.LambdaUtils; - import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ImageClassLoader; +import jdk.graal.compiler.java.LambdaUtils; import jdk.vm.ci.meta.MetaAccessProvider; class AllowAllHostedUsagesClassInitializationSupport extends ClassInitializationSupport { @@ -45,21 +44,7 @@ class AllowAllHostedUsagesClassInitializationSupport extends ClassInitialization @Override public void initializeAtBuildTime(Class aClass, String reason) { UserError.guarantee(!configurationSealed, "The class initialization configuration can be changed only before the phase analysis."); - Class cur = aClass; - do { - classInitializationConfiguration.insert(cur.getTypeName(), InitKind.BUILD_TIME, cur == aClass ? reason : "super type of " + aClass.getTypeName(), true); - initializeInterfacesAtBuildTime(cur.getInterfaces(), "interface of " + aClass.getTypeName()); - cur = cur.getSuperclass(); - } while (cur != null); - } - - private void initializeInterfacesAtBuildTime(Class[] interfaces, String reason) { - for (Class iface : interfaces) { - if (metaAccess.lookupJavaType(iface).declaresDefaultMethods()) { - classInitializationConfiguration.insert(iface.getTypeName(), InitKind.BUILD_TIME, reason, true); - } - initializeInterfacesAtBuildTime(iface.getInterfaces(), reason); - } + forceInitializeHosted(aClass, reason, false); } @Override @@ -103,7 +88,13 @@ public void forceInitializeHosted(Class clazz, String reason, boolean allowIn classInitKinds.put(clazz, initKind); forceInitializeHosted(clazz.getSuperclass(), "super type of " + clazz.getTypeName(), allowInitializationErrors); - forceInitializeInterfaces(clazz.getInterfaces(), "super type of " + clazz.getTypeName()); + 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) { 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 983a46edc6e9..0eb3cf8a072d 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 @@ -28,6 +28,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -663,6 +664,100 @@ static synchronized int synchronizedMethod() { } } +class InitializationOrder { + static final List> initializationOrder = Collections.synchronizedList(new ArrayList<>()); +} + +interface Test1_I1 { + default void defaultI1() { + } + + int order = add(); + + static int add() { + InitializationOrder.initializationOrder.add(Test1_I1.class); + return 42; + } +} + +interface Test1_I2 extends Test1_I1 { + int order = add(); + + static int add() { + InitializationOrder.initializationOrder.add(Test1_I2.class); + return 42; + } +} + +interface Test1_I3 extends Test1_I2 { + default void defaultI3() { + } + + int order = add(); + + static int add() { + InitializationOrder.initializationOrder.add(Test1_I3.class); + return 42; + } +} + +interface Test1_I4 extends Test1_I3 { + int order = add(); + + static int add() { + InitializationOrder.initializationOrder.add(Test1_I4.class); + return 42; + } +} + +class Test1_A implements Test1_I4 { + static { + InitializationOrder.initializationOrder.add(Test1_A.class); + } +} + +interface Test2_I1 { + default void defaultI1() { + } + + int order = add(); + + static int add() { + InitializationOrder.initializationOrder.add(Test2_I1.class); + return 42; + } +} + +interface Test2_I2 extends Test2_I1 { + int order = add(); + + static int add() { + InitializationOrder.initializationOrder.add(Test2_I2.class); + return 42; + } +} + +interface Test2_I3 extends Test2_I2 { + default void defaultI3() { + } + + int order = add(); + + static int add() { + InitializationOrder.initializationOrder.add(Test2_I3.class); + return 42; + } +} + +interface Test2_I4 extends Test2_I3 { + int order = add(); + + static int add() { + InitializationOrder.initializationOrder.add(Test2_I4.class); + return 42; + } +} + abstract class TestClassInitializationFeature implements Feature { private void checkClasses(boolean checkSafeEarly, boolean checkSafeLate) { @@ -695,6 +790,65 @@ abstract void checkClass(Class checkedClass, boolean checkSafeEarly, boolean public void afterRegistration(AfterRegistrationAccess access) { /* We need to access the checkedClasses array both at image build time and run time. */ RuntimeClassInitialization.initializeAtBuildTime(TestClassInitialization.class); + + /* + * Initialization of a class first triggers initialization of all superinterfaces that + * declared default methods. + */ + InitializationOrder.initializationOrder.clear(); + assertNotInitialized(Test1_I1.class, Test1_I2.class, Test1_I3.class, Test1_I4.class, Test1_A.class); + RuntimeClassInitialization.initializeAtBuildTime(Test1_A.class); + assertNotInitialized(Test1_I2.class, Test1_I4.class); + assertInitialized(Test1_I1.class, Test1_I3.class, Test1_A.class); + 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. + */ + 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()); + } + } + + private void assertNotInitialized(Class... classes) { + for (var clazz : classes) { + if (!Unsafe.getUnsafe().shouldBeInitialized(clazz)) { + throw new AssertionError("Already initialized: " + clazz); + } + } + } + + private void assertInitialized(Class... classes) { + for (var clazz : classes) { + if (Unsafe.getUnsafe().shouldBeInitialized(clazz)) { + throw new AssertionError("Not initialized: " + clazz); + } + } + } + + private static void assertArraysEqual(Object[] expected, Object[] actual) { + if (!Arrays.equals(expected, actual)) { + throw new RuntimeException("expected " + Arrays.toString(expected) + " but found " + Arrays.toString(actual)); + } } @Override