Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GR-51416] Registering a class as initialize-at-build-time should immediately trigger initialization. #8193

Merged
merged 1 commit into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,20 @@ public static void initializeAtRunTime(Class<?>... classes) {
/**
* Registers the provided classes as eagerly initialized during image-build time.
* <p>
* 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.
* <p>
* It is up to the user to ensure that this behavior makes sense and does not lead to wrong
* application behavior.
* <p>
* 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.
* <p>
* 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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -663,6 +664,100 @@ static synchronized int synchronizedMethod() {
}
}

class InitializationOrder {
static final List<Class<?>> 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) {
Expand Down Expand Up @@ -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
Expand Down