From 19009b612e860fb38e2604e238905c1a2a95bb64 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 25 Jan 2024 17:35:17 +0100 Subject: [PATCH] Introduce a typeReached check for Class.forName The core implementation of https://github.com/oracle/graal/issues/7480 This PR applies only for `Class.forName`. The other cases will be covered in the consecutive PRs. A type is reached if the type is initialized (successfuly, or unsuccesfully), or any of the type's subtypes are reached. Type is also reached if any of the subtypes is marked as `initialize-at-build-time`. The JSON elements `typeReached` and `typeReachable` are currently distinguished as different elements and can not be merged. This is due to the possible restriction in semantics if we merge different reflection descriptors with a condition that is different only in a runtime check. The agent still outputs `typeReachable` as the implementation is not finished for all elements. Implementation notes: * The `ClassInitializationInfo` is contains the extra field `typeReached`. * The `ClassInitializationInfo` is not a singleton for build-time initialized classes that require tracking for being reached. This is required as the field `reached` can be mutated at runtime. Review entry points: 1) For the runtime computation of reached and build-time metadata construction: substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java 2) For the usage of the mechanism in `Class.forName`: substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java 3) For parsing the JSON files: substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java 4) For interactions with optimizations related to class initalization follow the `EnsureClassInitializedNode`: substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/EnsureClassInitializedNode.java. --- .../config-condition-schema-v1.0.0.json | 12 +- .../impl/ConfigurationCondition.java | 26 ++- .../UnresolvedConfigurationCondition.java | 47 +++-- .../ConfigurationConditionPrintable.java | 2 +- .../ConditionalConfigurationComputer.java | 2 +- .../ClassInitializationInfo.java | 184 ++++++++++++++++-- .../EnsureClassInitializedNode.java | 13 +- .../EnsureClassInitializedSnippets.java | 12 +- .../TypeReachedProvider.java | 38 ++++ .../configure/ConditionalRuntimeValue.java | 88 +++++++++ .../ConfigurationConditionResolver.java | 4 +- .../core/configure/ConfigurationFiles.java | 5 + .../core/configure/ConfigurationParser.java | 60 +++++- .../ReflectionConfigurationParser.java | 29 +-- .../SerializationConfigurationParser.java | 26 +-- .../svm/core/hub/ClassForNameSupport.java | 98 ++++++---- .../com/oracle/svm/core/hub/DynamicHub.java | 6 +- .../svm/core/jdk/JavaLangSubstitutions.java | 4 +- .../jdk/Target_java_lang_ClassLoader.java | 2 +- .../RuntimeCompiledMethodSupport.java | 3 +- .../SubstrateRuntimeConfigurationBuilder.java | 2 +- .../ConditionalConfigurationRegistry.java | 2 +- .../svm/hosted/NativeImageGenerator.java | 6 +- .../oracle/svm/hosted/ProgressReporter.java | 2 +- .../src/com/oracle/svm/hosted/SVMHost.java | 2 +- .../AnalysisConstantReflectionProvider.java | 13 +- .../analysis/DynamicHubInitializer.java | 15 +- .../ClassInitializationSupport.java | 80 +++++++- .../SimulateClassInitializerGraphDecoder.java | 6 +- .../SimulateClassInitializerSupport.java | 4 + .../HostedRuntimeConfigurationBuilder.java | 2 +- .../UninterruptibleAnnotationChecker.java | 13 +- .../HostedConstantReflectionProvider.java | 5 +- .../svm/hosted/meta/UniverseBuilder.java | 45 +++++ .../svm/hosted/phases/HostedGraphKit.java | 5 +- .../InlineBeforeAnalysisGraphDecoderImpl.java | 3 +- .../SubstrateClassInitializationPlugin.java | 10 +- .../reflect/NativeImageConditionResolver.java | 12 +- .../hosted/reflect/ReflectionDataBuilder.java | 79 ++++---- .../svm/hosted/reflect/ReflectionFeature.java | 2 +- 40 files changed, 744 insertions(+), 225 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/TypeReachedProvider.java create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalRuntimeValue.java diff --git a/docs/reference-manual/native-image/assets/config-condition-schema-v1.0.0.json b/docs/reference-manual/native-image/assets/config-condition-schema-v1.0.0.json index 05f2bc350e7f..9525c76c810c 100644 --- a/docs/reference-manual/native-image/assets/config-condition-schema-v1.0.0.json +++ b/docs/reference-manual/native-image/assets/config-condition-schema-v1.0.0.json @@ -4,12 +4,18 @@ "title": "JSON schema for the conditions used in GraalVM Native Image configuration files", "properties": { "typeReachable": { - "type": "string", + "deprecated": true, + "$ref": "config-type-schema-v1.0.0.json", "title": "Fully qualified name of a class that must be reachable in order to register the type for reflection" + }, + "typeReached": { + "$ref": "config-type-schema-v1.0.0.json", + "title": "Fully qualified name of a class that must be reached in order to register the type for reflection" } }, - "required": [ - "typeReachable" + "oneOf": [ + "typeReachable", + "typeReached" ], "additionalProperties": false, "type": "object" diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ConfigurationCondition.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ConfigurationCondition.java index 24ee15609829..f2ed67daf0bc 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ConfigurationCondition.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ConfigurationCondition.java @@ -43,17 +43,17 @@ import java.util.Objects; /** - * A condition that describes if a reflectively accessed element in Native Image is visible by the + * A condition that describes if a reflectively-accessed element in Native Image is visible by the * user. *

- * Currently, there is only one type of condition (typeReached) so this is a single + * Currently, there is only one type of condition (typeReached) so this is a final * class instead of the class hierarchy. The {@link ConfigurationCondition#type} represents the * {@link Class<>} that needs to be reached by analysis in order for an element to be visible. */ public final class ConfigurationCondition { /* Cached to save space: it is used as a marker for all non-conditional elements */ - private static final ConfigurationCondition JAVA_LANG_OBJECT_REACHED = new ConfigurationCondition(Object.class); + private static final ConfigurationCondition JAVA_LANG_OBJECT_REACHED = new ConfigurationCondition(Object.class, true); public static ConfigurationCondition alwaysTrue() { return JAVA_LANG_OBJECT_REACHED; @@ -61,18 +61,22 @@ public static ConfigurationCondition alwaysTrue() { private final Class type; - public static ConfigurationCondition create(Class type) { + private final boolean runtimeChecked; + + public static ConfigurationCondition create(Class type, boolean runtimeChecked) { + Objects.requireNonNull(type); if (JAVA_LANG_OBJECT_REACHED.getType().equals(type)) { return JAVA_LANG_OBJECT_REACHED; } - return new ConfigurationCondition(type); + return new ConfigurationCondition(type, runtimeChecked); } public boolean isAlwaysTrue() { return ConfigurationCondition.alwaysTrue().equals(this); } - private ConfigurationCondition(Class type) { + private ConfigurationCondition(Class type, boolean runtimeChecked) { + this.runtimeChecked = runtimeChecked; this.type = type; } @@ -80,6 +84,10 @@ public Class getType() { return type; } + public boolean isRuntimeChecked() { + return runtimeChecked; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -88,13 +96,13 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - ConfigurationCondition condition = (ConfigurationCondition) o; - return Objects.equals(type, condition.type); + ConfigurationCondition that = (ConfigurationCondition) o; + return runtimeChecked == that.runtimeChecked && Objects.equals(type, that.type); } @Override public int hashCode() { - return Objects.hash(type); + return Objects.hash(type, runtimeChecked); } } diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/UnresolvedConfigurationCondition.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/UnresolvedConfigurationCondition.java index 9dda1db6b546..ffaf597dcdbe 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/UnresolvedConfigurationCondition.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/UnresolvedConfigurationCondition.java @@ -43,22 +43,27 @@ import java.util.Objects; /** - * This is an unresolved version of the {@link ConfigurationCondition} used only during parsing. + * Represents a {@link ConfigurationCondition} during parsing before it is resolved in a context of + * the classpath. */ -public class UnresolvedConfigurationCondition implements Comparable { +public final class UnresolvedConfigurationCondition implements Comparable { + private static final UnresolvedConfigurationCondition JAVA_LANG_OBJECT_REACHED = new UnresolvedConfigurationCondition(Object.class.getTypeName(), true); + public static final String TYPE_REACHED_KEY = "typeReached"; + public static final String TYPE_REACHABLE_KEY = "typeReachable"; private final String typeName; - private static final UnresolvedConfigurationCondition JAVA_LANG_OBJECT_REACHED = new UnresolvedConfigurationCondition(Object.class.getTypeName()); + private final boolean runtimeChecked; - public static UnresolvedConfigurationCondition create(String typeName) { + public static UnresolvedConfigurationCondition create(String typeName, boolean runtimeChecked) { Objects.requireNonNull(typeName); if (JAVA_LANG_OBJECT_REACHED.getTypeName().equals(typeName)) { return JAVA_LANG_OBJECT_REACHED; } - return new UnresolvedConfigurationCondition(typeName); + return new UnresolvedConfigurationCondition(typeName, runtimeChecked); } - protected UnresolvedConfigurationCondition(String typeName) { + private UnresolvedConfigurationCondition(String typeName, boolean runtimeChecked) { this.typeName = typeName; + this.runtimeChecked = runtimeChecked; } public static UnresolvedConfigurationCondition alwaysTrue() { @@ -69,6 +74,14 @@ public String getTypeName() { return typeName; } + public boolean isRuntimeChecked() { + return runtimeChecked; + } + + public boolean isAlwaysTrue() { + return typeName.equals(JAVA_LANG_OBJECT_REACHED.getTypeName()); + } + @Override public boolean equals(Object o) { if (this == o) { @@ -77,26 +90,28 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - UnresolvedConfigurationCondition condition = (UnresolvedConfigurationCondition) o; - return Objects.equals(typeName, condition.typeName); + UnresolvedConfigurationCondition that = (UnresolvedConfigurationCondition) o; + return runtimeChecked == that.runtimeChecked && Objects.equals(typeName, that.typeName); } @Override public int hashCode() { - return Objects.hash(typeName); + return Objects.hash(typeName, runtimeChecked); } @Override - public String toString() { - return "[\"typeReachable\": \"" + typeName + "\"" + "]"; + public int compareTo(UnresolvedConfigurationCondition o) { + int res = Boolean.compare(runtimeChecked, o.runtimeChecked); + if (res != 0) { + return res; + } + return typeName.compareTo(o.typeName); } @Override - public int compareTo(UnresolvedConfigurationCondition c) { - return this.typeName.compareTo(c.typeName); + public String toString() { + var field = runtimeChecked ? TYPE_REACHED_KEY : TYPE_REACHABLE_KEY; + return "[" + field + ": \"" + typeName + "\"" + "]"; } - public boolean isAlwaysTrue() { - return typeName.equals(JAVA_LANG_OBJECT_REACHED.getTypeName()); - } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationConditionPrintable.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationConditionPrintable.java index 9d2ab29abaa1..7d826854b05e 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationConditionPrintable.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationConditionPrintable.java @@ -25,7 +25,7 @@ package com.oracle.svm.configure.config; import static com.oracle.svm.core.configure.ConfigurationParser.CONDITIONAL_KEY; -import static com.oracle.svm.core.configure.ConfigurationParser.TYPE_REACHABLE_KEY; +import static org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition.TYPE_REACHABLE_KEY; import java.io.IOException; diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationComputer.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationComputer.java index dbb6d2e2a829..523067b2d60e 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationComputer.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationComputer.java @@ -171,7 +171,7 @@ private ConfigurationSet deduceConditionalConfiguration(Map value : methodCallNodes.values()) { for (MethodCallNode node : value) { String className = node.methodInfo.getJavaDeclaringClassName(); - UnresolvedConfigurationCondition condition = UnresolvedConfigurationCondition.create(className); + UnresolvedConfigurationCondition condition = UnresolvedConfigurationCondition.create(className, false); var resolveCondition = ConfigurationConditionResolver.identityResolver().resolveCondition(condition); addConfigurationWithCondition(configurationSet, node.configuration, resolveCondition.get()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java index f13625d036df..48282e9b6ffd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/ClassInitializationInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2018, 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. * * This code is free software; you can redistribute it and/or modify it @@ -50,30 +50,65 @@ /** * Information about the runtime class initialization state of a {@link DynamicHub class}, and - * {@link #initialize implementation} of class initialization according to the Java VM - * specification. - * + * {@link #slowPath(ClassInitializationInfo, DynamicHub)} implementation} of class initialization + * according to the Java VM specification. + *

* The information is not directly stored in {@link DynamicHub} because 1) the class initialization * state is mutable while {@link DynamicHub} must be immutable, and 2) few classes require * initialization at runtime so factoring out the information reduces image size. + *

+ * The {@link #typeReached} for all super types must have a value whose ordinal is greater or equal + * to its own value. Concretely, {@link TypeReached#REACHED} types must have all super types + * {@link TypeReached#REACHED} or {@link TypeReached#UNTRACKED}, and {@link TypeReached#UNTRACKED} + * types' super types must be {@link TypeReached#UNTRACKED}. This is verified in + * com.oracle.svm.hosted.meta.UniverseBuilder#checkHierarchyForTypeReachedConstraints. */ @InternalVMMethod public final class ClassInitializationInfo { - /** - * Singleton for classes that are already initialized during image building and do not need - * class initialization at runtime, but have {@code } methods. + /* + * The singletons are here to reduce image size for build-time initialized classes that are + * UNTRACKED for type reached. */ - public static final ClassInitializationInfo INITIALIZED_INFO_SINGLETON = new ClassInitializationInfo(InitState.FullyInitialized, true); + private static final ClassInitializationInfo NO_INITIALIZER_NO_TRACKING = new ClassInitializationInfo(InitState.FullyInitialized, false, true, false); + private static final ClassInitializationInfo INITIALIZED_NO_TRACKING = new ClassInitializationInfo(InitState.FullyInitialized, true, true, false); + private static final ClassInitializationInfo FAILED_NO_TRACKING = new ClassInitializationInfo(InitState.InitializationError, false); + + public static ClassInitializationInfo forFailedInfo(boolean typeReachedTracked) { + if (typeReachedTracked) { + return new ClassInitializationInfo(InitState.InitializationError, typeReachedTracked); + } else { + return FAILED_NO_TRACKING; + } + } /** * Singleton for classes that are already initialized during image building and do not need * class initialization at runtime, and don't have {@code } methods. */ - public static final ClassInitializationInfo NO_INITIALIZER_INFO_SINGLETON = new ClassInitializationInfo(InitState.FullyInitialized, false); + public static ClassInitializationInfo forNoInitializerInfo(boolean typeReachedTracked) { + if (typeReachedTracked) { + return new ClassInitializationInfo(InitState.FullyInitialized, false, true, typeReachedTracked); + } else { + return NO_INITIALIZER_NO_TRACKING; + } + } + + /** + * For classes that are already initialized during image building and do not need class + * initialization at runtime, but have {@code } methods. + */ + public static ClassInitializationInfo forInitializedInfo(boolean typeReachedTracked) { + if (typeReachedTracked) { + return new ClassInitializationInfo(InitState.FullyInitialized, true, true, typeReachedTracked); + } else { + return INITIALIZED_NO_TRACKING; + } + } - /** Singleton for classes that failed to link during image building. */ - public static final ClassInitializationInfo FAILED_INFO_SINGLETON = new ClassInitializationInfo(InitState.InitializationError); + public boolean requiresSlowPath() { + return slowPathRequired; + } enum InitState { /** @@ -89,6 +124,12 @@ enum InitState { InitializationError } + public enum TypeReached { + NOT_REACHED, + REACHED, + UNTRACKED, + } + interface ClassInitializerFunctionPointer extends CFunctionPointer { @InvokeJavaFunctionPointer void invoke(); @@ -100,16 +141,28 @@ interface ClassInitializerFunctionPointer extends CFunctionPointer { */ private final FunctionPointerHolder classInitializer; + /** + * Marks that this class has been reached at run time. + */ + private TypeReached typeReached; + /** * The current initialization state. */ private InitState initState; + + /** + * Requires one less check after lowering {@link EnsureClassInitializedNode}. true + * if class is not build-time initialized or typeReached != TypeReached.UNTRACKED. + */ + private boolean slowPathRequired; + /** * The thread that is currently initializing the class. We use the platform thread instead of a * potential virtual thread because initializers like that of {@code sun.nio.ch.Poller} can * switch to the carrier thread and encounter the class that is being initialized again and * would wait for its initialization in the virtual thread to complete and therefore deadlock. - * + *

* We also pin the virtual thread because it must not continue initialization on a different * platform thread, and also because if the platform thread switches to a different virtual * thread which encounters the class being initialized, it would wrongly be considered reentrant @@ -133,25 +186,48 @@ interface ClassInitializerFunctionPointer extends CFunctionPointer { * at native image's build time or run time. */ private boolean hasInitializer; + private boolean buildTimeInitialized; + + public boolean isTypeReached() { + assert typeReached != TypeReached.UNTRACKED : "We should never emit a check for untracked types as this was known at build time"; + return typeReached == TypeReached.REACHED; + } @Platforms(Platform.HOSTED_ONLY.class) - private ClassInitializationInfo(InitState initState, boolean hasInitializer) { - this(initState); + public TypeReached getTypeReached() { + return typeReached; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void setTypeReached() { + VMError.guarantee(typeReached != TypeReached.UNTRACKED, "Must not modify untracked types as nodes for checks have already been omitted."); + typeReached = TypeReached.REACHED; + slowPathRequired = initState != InitState.FullyInitialized; + } + + @Platforms(Platform.HOSTED_ONLY.class) + private ClassInitializationInfo(InitState initState, boolean hasInitializer, boolean buildTimeInitialized, boolean typeReachedTracked) { + this(initState, typeReachedTracked); this.hasInitializer = hasInitializer; + this.buildTimeInitialized = buildTimeInitialized; } @Platforms(Platform.HOSTED_ONLY.class) - private ClassInitializationInfo(InitState initState) { + private ClassInitializationInfo(InitState initState, boolean typeReachedTracked) { this.classInitializer = null; + this.typeReached = typeReachedTracked ? TypeReached.NOT_REACHED : TypeReached.UNTRACKED; this.initState = initState; + this.slowPathRequired = typeReached != TypeReached.UNTRACKED || initState != InitState.FullyInitialized; this.initLock = initState == InitState.FullyInitialized ? null : new ReentrantLock(); this.hasInitializer = true; } @Platforms(Platform.HOSTED_ONLY.class) - public ClassInitializationInfo(CFunctionPointer classInitializer) { + public ClassInitializationInfo(CFunctionPointer classInitializer, boolean typeReachedTracked) { this.classInitializer = classInitializer == null || classInitializer.isNull() ? null : new FunctionPointerHolder(classInitializer); this.initState = InitState.Linked; + this.typeReached = typeReachedTracked ? TypeReached.NOT_REACHED : TypeReached.UNTRACKED; + this.slowPathRequired = true; this.initLock = new ReentrantLock(); this.hasInitializer = classInitializer != null; } @@ -164,6 +240,14 @@ public boolean isInitialized() { return initState == InitState.FullyInitialized; } + public boolean isInitializationError() { + return initState == InitState.InitializationError; + } + + public boolean isBuildTimeInitialized() { + return buildTimeInitialized; + } + private boolean isBeingInitialized() { return initState == InitState.BeingInitialized; } @@ -176,17 +260,74 @@ private boolean isReentrantInitialization(IsolateThread thread) { return thread.equal(initThread); } + /** + * Marks the hierarchy of hub as reached. + *

+ * Locking is not needed as the whole type hierarchy (up until + * TypeReached.UNTRACKED types) is marked as reached every time we enter the class + * initialization slow path. The hierarchy of a type can be marked as reached multiple times in + * following cases: + *

    + *
  • Every time we reach type initialization when the type is in the + * {@link InitState#InitializationError} state
  • + *
  • Multiple times by different threads while the type is being initialized by one + * thread.
  • + *
+ * + */ + private static void markReached(DynamicHub hub) { + var current = hub; + do { + ClassInitializationInfo clinitInfo = current.getClassInitializationInfo(); + if (clinitInfo.typeReached == TypeReached.UNTRACKED) { + break; + } + clinitInfo.typeReached = TypeReached.REACHED; + if (clinitInfo.isInitialized()) { + clinitInfo.slowPathRequired = false; + } + reachInterfaces(current); + + current = current.getSuperHub(); + } while (current != null); + } + + private static void reachInterfaces(DynamicHub hub) { + for (DynamicHub superInterface : hub.getInterfaces()) { + if (superInterface.getClassInitializationInfo().typeReached == TypeReached.REACHED) { + return; + } + + if (hub.getClassInitializationInfo().typeReached != TypeReached.UNTRACKED) { + superInterface.getClassInitializationInfo().typeReached = TypeReached.REACHED; + } + reachInterfaces(superInterface); + } + } + /** * Perform class initialization. This is the slow-path that should only be called after checking * {@link #isInitialized}. - * - * Steps refer to the JVM specification for class initialization: - * https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5 + *

+ * Steps refer to the + * JVM + * specification for class initialization. */ @SubstrateForeignCallTarget(stubCallingConvention = true) - private static void initialize(ClassInitializationInfo info, DynamicHub hub) { + private static void slowPath(ClassInitializationInfo info, DynamicHub hub) { IsolateThread self = CurrentIsolate.getCurrentThread(); + /* + * Types are marked as reached before any initialization is performed. Reason: the results + * should be visible in class initializers of the whole hierarchy as they could use + * reflection. + */ + markReached(hub); + + if (info.isInitialized()) { + return; + } + /* * GR-43118: If a predefined class is not loaded, and the caller class is loaded, set the * classloader of the initialized class to the class loader of the caller class. @@ -386,6 +527,9 @@ private void setInitializationStateAndNotify(InitState state) { initLock.lock(); try { this.initState = state; + if (initState == InitState.FullyInitialized) { + this.slowPathRequired = false; + } this.initThread = WordFactory.nullPointer(); /* Make sure previous stores are all done, notably the initState. */ Unsafe.getUnsafe().storeFence(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/EnsureClassInitializedNode.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/EnsureClassInitializedNode.java index 4ff3cfc76e73..abaf9fdebb56 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/EnsureClassInitializedNode.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/EnsureClassInitializedNode.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.core.classinitialization; +import org.graalvm.word.LocationIdentity; + import jdk.graal.compiler.core.common.type.StampFactory; import jdk.graal.compiler.graph.Node; import jdk.graal.compiler.graph.Node.NodeIntrinsicFactory; @@ -42,8 +44,6 @@ import jdk.graal.compiler.nodes.spi.CanonicalizerTool; import jdk.graal.compiler.nodes.spi.Lowerable; import jdk.graal.compiler.nodes.type.StampTool; -import org.graalvm.word.LocationIdentity; - import jdk.vm.ci.meta.ConstantReflectionProvider; import jdk.vm.ci.meta.ResolvedJavaType; @@ -109,9 +109,12 @@ public ResolvedJavaType constantTypeOrNull(ConstantReflectionProvider constantRe public Node canonical(CanonicalizerTool tool) { ResolvedJavaType type = constantTypeOrNull(tool.getConstantReflection()); if (type != null) { - for (FrameState cur = stateAfter; cur != null; cur = cur.outerFrameState()) { - if (!needsRuntimeInitialization(cur.getMethod().getDeclaringClass(), type)) { - return null; + TypeReachedProvider typeReachedProvider = (TypeReachedProvider) tool.getConstantReflection(); + if (!typeReachedProvider.initializationCheckRequired(type)) { + for (FrameState cur = stateAfter; cur != null; cur = cur.outerFrameState()) { + if (!needsRuntimeInitialization(cur.getMethod().getDeclaringClass(), type)) { + return null; + } } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/EnsureClassInitializedSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/EnsureClassInitializedSnippets.java index 460afaf9e0bd..7bb257ed0a6d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/EnsureClassInitializedSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/EnsureClassInitializedSnippets.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, 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 @@ -54,10 +54,10 @@ import jdk.graal.compiler.replacements.Snippets; public final class EnsureClassInitializedSnippets extends SubstrateTemplates implements Snippets { - private static final SubstrateForeignCallDescriptor INITIALIZE = SnippetRuntime.findForeignCall(ClassInitializationInfo.class, "initialize", HAS_SIDE_EFFECT, LocationIdentity.any()); + private static final SubstrateForeignCallDescriptor SLOW_PATH = SnippetRuntime.findForeignCall(ClassInitializationInfo.class, "slowPath", HAS_SIDE_EFFECT, LocationIdentity.any()); public static final SubstrateForeignCallDescriptor[] FOREIGN_CALLS = new SubstrateForeignCallDescriptor[]{ - INITIALIZE, + SLOW_PATH, }; /** @@ -73,13 +73,13 @@ private static void ensureClassIsInitializedSnippet(@Snippet.NonNullParameter Dy */ ClassInitializationInfo infoNonNull = (ClassInitializationInfo) PiNode.piCastNonNull(info, SnippetAnchorNode.anchor()); - if (BranchProbabilityNode.probability(BranchProbabilityNode.EXTREMELY_SLOW_PATH_PROBABILITY, !infoNonNull.isInitialized())) { - callInitialize(INITIALIZE, infoNonNull, DynamicHub.toClass(hub)); + if (BranchProbabilityNode.probability(BranchProbabilityNode.EXTREMELY_SLOW_PATH_PROBABILITY, infoNonNull.requiresSlowPath())) { + callSlowPath(SLOW_PATH, infoNonNull, DynamicHub.toClass(hub)); } } @NodeIntrinsic(value = ForeignCallWithExceptionNode.class) - private static native void callInitialize(@ConstantNodeParameter ForeignCallDescriptor descriptor, ClassInitializationInfo info, Class clazz); + private static native void callSlowPath(@ConstantNodeParameter ForeignCallDescriptor descriptor, ClassInitializationInfo info, Class clazz); @SuppressWarnings("unused") public static void registerLowerings(OptionValues options, Providers providers, Map, NodeLoweringProvider> lowerings) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/TypeReachedProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/TypeReachedProvider.java new file mode 100644 index 000000000000..1ea21454bf7a --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/classinitialization/TypeReachedProvider.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.classinitialization; + +import jdk.vm.ci.meta.ResolvedJavaType; + +/** + * Used to determine if EnsureClassInitializedNode is required for type-reached + * checking. + */ +public interface TypeReachedProvider { + /** + * Check whether a type needs a class-initialization node to mark it as reached. + */ + boolean initializationCheckRequired(ResolvedJavaType type); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalRuntimeValue.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalRuntimeValue.java new file mode 100644 index 000000000000..b9f080d84140 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalRuntimeValue.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.util.Set; +import java.util.function.Predicate; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.util.VMError; + +/** + * A image-heap stored {@link ConditionalRuntimeValue#value} that is guarded by run-time computed + * {@link ConditionalRuntimeValue#conditions}. + *

+ * {@link ConditionalRuntimeValue#conditions} are stored as an array to save space in the image + * heap. This is subject to further optimizations. + * + * @param type of the stored value. + */ +public final class ConditionalRuntimeValue { + private final Class[] conditions; + private boolean satisfied; + volatile T value; + + @Platforms(Platform.HOSTED_ONLY.class) + public ConditionalRuntimeValue(Set> conditions, T value) { + if (!conditions.isEmpty()) { + this.conditions = conditions.toArray(Class[]::new); + } else { + this.conditions = null; + satisfied = true; + } + + VMError.guarantee(conditions.stream().noneMatch(c -> c.equals(Object.class)), "java.lang.Object must not be in conditions as it is always true."); + this.value = value; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public T getValueUnconditionally() { + return value; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public Set> getConditions() { + return conditions == null ? Set.of() : Set.of(conditions); + } + + public T getValue(Predicate> conditionSatisfied) { + if (satisfied) { + return value; + } else { + for (Class element : conditions) { + if (conditionSatisfied.test(element)) { + satisfied = true; + break; + } + } + if (satisfied) { + return value; + } + } + return null; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationConditionResolver.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationConditionResolver.java index 435eeab05b1c..bfbba32b23cc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationConditionResolver.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationConditionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, 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 @@ -39,7 +39,7 @@ public TypeResult resolveCondition(UnresolvedC @Override public UnresolvedConfigurationCondition alwaysTrue() { - return UnresolvedConfigurationCondition.create("java.lang.Object"); + return UnresolvedConfigurationCondition.alwaysTrue(); } }; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java index 4597c9d8595e..695a99266ae0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java @@ -125,6 +125,11 @@ public static final class Options { @Option(help = "When configuration files do not match their schema, abort the image build instead of emitting a warning.")// public static final HostedOptionKey StrictConfiguration = new HostedOptionKey<>(false); + @Option(help = "Testing flag: the typeReachable condition is treated as typeReached so the semantics of programs can change.")// + public static final HostedOptionKey TreatAllReachableConditionsAsReached = new HostedOptionKey<>(false); + + @Option(help = "Testing flag: every type is considered as it participates in a typeReachable condition.")// + public static final HostedOptionKey TreatAllUserSpaceTypesAsTrackedForTypeReached = new HostedOptionKey<>(false); @Option(help = "Warn when reflection and JNI configuration files have elements that could not be found on the classpath or modulepath.", type = OptionType.Expert)// public static final HostedOptionKey WarnAboutMissingReflectionOrJNIMetadataElements = new HostedOptionKey<>(false); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java index 370b680d1a6c..68aa4015b216 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java @@ -24,6 +24,10 @@ */ package com.oracle.svm.core.configure; +import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TreatAllReachableConditionsAsReached; +import static org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition.TYPE_REACHABLE_KEY; +import static org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition.TYPE_REACHED_KEY; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -37,6 +41,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import org.graalvm.collections.EconomicMap; @@ -61,9 +66,8 @@ public static InputStream openStream(URI uri) throws IOException { } public static final String CONDITIONAL_KEY = "condition"; - public static final String TYPE_REACHABLE_KEY = "typeReachable"; - - public static final String TYPE_REACHED_KEY = "typeReached"; + public static final String NAME_KEY = "name"; + public static final String TYPE_KEY = "type"; private final Map> seenUnknownAttributesByType = new HashMap<>(); private final boolean strictSchema; @@ -155,7 +159,7 @@ protected void checkHasExactlyOneAttribute(EconomicMap map, Stri */ protected void warnOrFailOnSchemaError(String message) { if (strictSchema) { - throw new JSONParserException(message); + failOnSchemaError(message); } else { LogUtils.warning(message); } @@ -204,15 +208,53 @@ protected UnresolvedConfigurationCondition parseCondition(EconomicMap conditionObject = asMap(conditionData, "Attribute 'condition' must be an object"); + if (conditionObject.containsKey(TYPE_REACHABLE_KEY) && conditionObject.containsKey(TYPE_REACHED_KEY)) { + failOnSchemaError("condition can not have both '" + TYPE_REACHED_KEY + "' and '" + TYPE_REACHABLE_KEY + "' set."); + } - Object conditionType = conditionObject.get(TYPE_REACHABLE_KEY); - if (conditionType instanceof String) { - return UnresolvedConfigurationCondition.create((String) conditionType); - } else { - warnOrFailOnSchemaError("'" + TYPE_REACHABLE_KEY + "' should be of type string"); + if (conditionObject.containsKey(TYPE_REACHED_KEY)) { + Object object = conditionObject.get(TYPE_REACHED_KEY); + var condition = parseTypeContents(object); + if (condition.isPresent()) { + return UnresolvedConfigurationCondition.create(condition.get(), true); + } + } else if (conditionObject.containsKey(TYPE_REACHABLE_KEY)) { + Object object = conditionObject.get(TYPE_REACHABLE_KEY); + var condition = parseTypeContents(object); + if (condition.isPresent()) { + return UnresolvedConfigurationCondition.create(condition.get(), TreatAllReachableConditionsAsReached.getValue()); + } } } return UnresolvedConfigurationCondition.alwaysTrue(); } + private static JSONParserException failOnSchemaError(String message) { + throw new JSONParserException(message); + } + + protected static Optional parseType(EconomicMap data) { + Object typeObject = data.get(TYPE_KEY); + Object name = data.get(NAME_KEY); + if (typeObject != null) { + return parseTypeContents(typeObject); + } else if (name != null) { + return Optional.of(asString(name)); + } else { + throw failOnSchemaError("must have type or name specified for an element"); + } + } + + protected static Optional parseTypeContents(Object typeObject) { + if (typeObject instanceof String stringValue) { + return Optional.of(stringValue); + } else { + /* + * We return if we find a future version of a type descriptor (as a JSON object) instead + * of failing parsing. + */ + asMap(typeObject, "type descriptor should be a string or object"); + return Optional.empty(); + } + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java index 95edc68f319e..4068ff1497a5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java @@ -29,6 +29,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.graalvm.collections.EconomicMap; @@ -83,27 +84,9 @@ private void parseClass(EconomicMap data) { return; } - String className; - Object typeObject = data.get("type"); - /* - * Classes registered using the old ("class") syntax will require elements (fields, methods, - * constructors, ...) to be registered for runtime queries, whereas the new ("type") syntax - * will automatically register all elements as queried. - */ - if (typeObject != null) { - if (typeObject instanceof String stringValue) { - className = stringValue; - } else { - /* - * We warn if we find a future version of a type descriptor (as a JSON object) - * instead of failing parsing. - */ - asMap(typeObject, "type descriptor should be a string or object"); - handleMissingElement("Unsupported type descriptor of type object"); - return; - } - } else { - className = asString(data.get("name"), "class name should be a string"); + Optional className = parseType(data); + if (className.isEmpty()) { + return; } /* @@ -111,9 +94,9 @@ private void parseClass(EconomicMap data) { * allow getDeclaredMethods() and similar bulk queries at run time. */ C condition = conditionResult.get(); - TypeResult result = delegate.resolveType(condition, className, true, false); + TypeResult result = delegate.resolveType(condition, className.get(), true, false); if (!result.isPresent()) { - handleMissingElement("Could not resolve class " + className + " for reflection configuration.", result.getException()); + handleMissingElement("Could not resolve class " + className.get() + " for reflection configuration.", result.getException()); return; } T clazz = result.get(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java index 8f8e3361428e..0441d9202757 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java @@ -29,6 +29,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Optional; import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; @@ -38,8 +39,6 @@ public class SerializationConfigurationParser extends ConfigurationParser { - public static final String NAME_KEY = "name"; - public static final String TYPE_KEY = "type"; public static final String CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY = "customTargetConstructorClass"; private static final String SERIALIZATION_TYPES_KEY = "types"; private static final String LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY = "lambdaCapturingTypes"; @@ -107,29 +106,18 @@ private void parseSerializationDescriptorObject(EconomicMap data return; } - String targetSerializationClass; - Object typeObject = data.get(TYPE_KEY); - if (typeObject != null) { - if (typeObject instanceof String stringValue) { - targetSerializationClass = stringValue; - } else { - /* - * We return if we find a future version of a type descriptor (as a JSON object) - * instead of failing parsing. - */ - asMap(typeObject, "type descriptor should be a string or object"); - return; - } - } else { - targetSerializationClass = asString(data.get(NAME_KEY)); + Optional targetSerializationClass; + targetSerializationClass = parseType(data); + if (targetSerializationClass.isEmpty()) { + return; } if (lambdaCapturingType) { - serializationSupport.registerLambdaCapturingClass(condition.get(), targetSerializationClass); + serializationSupport.registerLambdaCapturingClass(condition.get(), targetSerializationClass.get()); } else { Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY); String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null; - serializationSupport.registerWithTargetConstructorClass(condition.get(), targetSerializationClass, customTargetConstructorClass); + serializationSupport.registerWithTargetConstructorClass(condition.get(), targetSerializationClass.get(), customTargetConstructorClass); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java index 56fc69ef51e3..b3e1566495a7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/ClassForNameSupport.java @@ -26,91 +26,123 @@ import static com.oracle.svm.core.MissingRegistrationUtils.throwMissingRegistrationErrors; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.impl.ConfigurationCondition; +import com.oracle.svm.core.configure.ConditionalRuntimeValue; import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; import com.oracle.svm.core.util.ImageHeapMap; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.util.ReflectionUtil; @AutomaticallyRegisteredImageSingleton public final class ClassForNameSupport { - static ClassForNameSupport singleton() { + public static ClassForNameSupport singleton() { return ImageSingletons.lookup(ClassForNameSupport.class); } /** The map used to collect registered classes. */ - private final EconomicMap knownClasses = ImageHeapMap.create(); + private final EconomicMap> knownClasses = ImageHeapMap.create(); private static final Object NEGATIVE_QUERY = new Object(); @Platforms(Platform.HOSTED_ONLY.class) - public static void registerClass(Class clazz) { + public void registerClass(Class clazz) { + registerClass(ConfigurationCondition.alwaysTrue(), clazz); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public void registerClass(ConfigurationCondition condition, Class clazz) { assert !clazz.isPrimitive() : "primitive classes cannot be looked up by name"; if (PredefinedClassesSupport.isPredefined(clazz)) { return; // must be defined at runtime before it can be looked up } String name = clazz.getName(); - Object currentValue = singleton().knownClasses.get(name); - /* - * If the class is already registered as negative, it means that it exists but is not - * accessible through the builder class loader, and it was already registered by name (as - * negative query) before this point. In that case, we update the map to contain the actual - * class. - */ - VMError.guarantee(currentValue == null || currentValue == clazz || currentValue instanceof Throwable || - (currentValue == NEGATIVE_QUERY && ReflectionUtil.lookupClass(true, name) == null), - "Invalid Class.forName value for %s: %s", name, currentValue); - - if (currentValue == NEGATIVE_QUERY) { - singleton().knownClasses.put(name, clazz); - } else { + ConditionalRuntimeValue exisingEntry = knownClasses.get(name); + Object currentValue = exisingEntry == null ? null : exisingEntry.getValueUnconditionally(); + + if (currentValue == null || // never seen + currentValue == NEGATIVE_QUERY || + currentValue == clazz) { + currentValue = clazz; + var cond = updateConditionalValue(exisingEntry, currentValue, condition); + knownClasses.put(name, cond); + } else if (currentValue instanceof Throwable) { // failed at linking time + var cond = updateConditionalValue(exisingEntry, currentValue, condition); /* * If the class has already been seen as throwing an error, we don't overwrite this - * error + * error. Nevertheless, we have to update the set of conditionals to be correct. */ - singleton().knownClasses.putIfAbsent(name, clazz); + knownClasses.put(name, cond); + } else { + throw VMError.shouldNotReachHere(""" + Invalid Class.forName value for %s: %s + If the class is already registered as negative, it means that it exists but is not + accessible through the builder class loader, and it was already registered by name (as + negative query) before this point. In that case, we update the map to contain the actual + class. + """, name, currentValue); + } + } + + private static ConditionalRuntimeValue updateConditionalValue(ConditionalRuntimeValue existingConditionalValue, Object newValue, + ConfigurationCondition additionalCondition) { + Set> resConditions = Set.of(); + if (!additionalCondition.isAlwaysTrue()) { + Class conditionClass = additionalCondition.getType(); + if (existingConditionalValue != null) { + Set> conditions = existingConditionalValue.getConditions(); + resConditions = Stream.concat(conditions.stream(), Stream.of(conditionClass)) + .collect(Collectors.toUnmodifiableSet()); + } else { + resConditions = Set.of(conditionClass); + } } + return new ConditionalRuntimeValue<>(resConditions, newValue); } @Platforms(Platform.HOSTED_ONLY.class) - public static void registerExceptionForClass(String className, Throwable t) { - Object currentValue = singleton().knownClasses.get(className); - VMError.guarantee(currentValue == null || currentValue.getClass() == t.getClass()); - singleton().knownClasses.put(className, t); + public void registerExceptionForClass(ConfigurationCondition condition, String className, Throwable t) { + Set> typeSet = condition.isAlwaysTrue() ? Set.of() : Set.of(condition.getType()); + knownClasses.put(className, new ConditionalRuntimeValue<>(typeSet, t)); } @Platforms(Platform.HOSTED_ONLY.class) - public static void registerNegativeQuery(String className) { + public void registerNegativeQuery(ConfigurationCondition condition, String className) { /* * If the class is not accessible by the builder class loader, but was already registered * through registerClass(Class), we don't overwrite the actual class or exception. */ - singleton().knownClasses.putIfAbsent(className, NEGATIVE_QUERY); + Set> typeSet = condition.isAlwaysTrue() ? Set.of() : Set.of(condition.getType()); + knownClasses.putIfAbsent(className, new ConditionalRuntimeValue<>(typeSet, NEGATIVE_QUERY)); } - public static Class forNameOrNull(String className, ClassLoader classLoader) { + public Class forNameOrNull(String className, ClassLoader classLoader) { try { return forName(className, classLoader, true); } catch (ClassNotFoundException e) { - throw VMError.shouldNotReachHere("ClassForNameSupport.forNameOrNull should not throw", e); + throw VMError.shouldNotReachHere("ClassForNameSupport#forNameOrNull should not throw", e); } } - public static Class forName(String className, ClassLoader classLoader) throws ClassNotFoundException { + public Class forName(String className, ClassLoader classLoader) throws ClassNotFoundException { return forName(className, classLoader, false); } - private static Class forName(String className, ClassLoader classLoader, boolean returnNullOnException) throws ClassNotFoundException { + private Class forName(String className, ClassLoader classLoader, boolean returnNullOnException) throws ClassNotFoundException { if (className == null) { return null; } - Object result = singleton().knownClasses.get(className); + var conditional = knownClasses.get(className); + Object result = conditional == null ? null : conditional.getValue(cls -> DynamicHub.fromClass(cls).isReached()); if (result == NEGATIVE_QUERY || className.endsWith("[]")) { /* Querying array classes with their "TypeName[]" name always throws */ result = new ClassNotFoundException(className); @@ -146,7 +178,7 @@ private static Class forName(String className, ClassLoader classLoader, boole throw VMError.shouldNotReachHere("Class.forName result should be Class, ClassNotFoundException or Error: " + result); } - public static int count() { - return singleton().knownClasses.size(); + public int count() { + return knownClasses.size(); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index 838e72de5405..b1381625abe5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -1464,7 +1464,7 @@ private static Class forName(String name, boolean initialize, ClassLoader loa } Class result; try { - result = ClassForNameSupport.forName(name, loader); + result = ClassForNameSupport.singleton().forName(name, loader); } catch (ClassNotFoundException e) { if (loader != null && PredefinedClassesSupport.hasBytecodeClasses()) { result = loader.loadClass(name); // may throw @@ -1919,6 +1919,10 @@ public Object getJfrEventConfiguration() { return companion.getJfrEventConfiguration(); } + public boolean isReached() { + return classInitializationInfo.isTypeReached(); + } + private static class ReflectionDataAccessors { @SuppressWarnings("unused") private static SoftReference> getReflectionData(DynamicHub that) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java index 69859b423964..22f6047210a7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java @@ -620,14 +620,14 @@ public static Stream packages() { @Substitute private static Class loadClassOrNull(String name) { - return ClassForNameSupport.forNameOrNull(name, null); + return ClassForNameSupport.singleton().forNameOrNull(name, null); } @SuppressWarnings("unused") @Substitute private static Class loadClass(Module module, String name) { /* The module system is not supported for now, therefore the module parameter is ignored. */ - return ClassForNameSupport.forNameOrNull(name, null); + return ClassForNameSupport.singleton().forNameOrNull(name, null); } @SuppressWarnings("unused") diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java index 6eb82ccebdec..5b0e047de818 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java @@ -175,7 +175,7 @@ Class loadClass(Module module, String name) { @Substitute // @SuppressWarnings({"unused"}) // private Class findLoadedClass0(String name) { - return ClassForNameSupport.forNameOrNull(name, SubstrateUtil.cast(this, ClassLoader.class)); + return ClassForNameSupport.singleton().forNameOrNull(name, SubstrateUtil.cast(this, ClassLoader.class)); } /** diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompiledMethodSupport.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompiledMethodSupport.java index f04c82e3a20d..6994b98652d5 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompiledMethodSupport.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/RuntimeCompiledMethodSupport.java @@ -350,9 +350,8 @@ public boolean isFinalField(ResolvedJavaField f, ConstantFieldTool tool) { static class RuntimeCompilationReflectionProvider extends AnalysisConstantReflectionProvider { - @SuppressWarnings("unused") RuntimeCompilationReflectionProvider(BigBang bb, ClassInitializationSupport classInitializationSupport) { - super(bb.getUniverse(), bb.getMetaAccess()); + super(bb.getUniverse(), bb.getMetaAccess(), classInitializationSupport); } @Override diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateRuntimeConfigurationBuilder.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateRuntimeConfigurationBuilder.java index 5ced65ffd1b1..dd257bb08192 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateRuntimeConfigurationBuilder.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateRuntimeConfigurationBuilder.java @@ -84,7 +84,7 @@ protected Providers createProviders(CodeCacheProvider codeCache, ConstantReflect @Override protected ConstantReflectionProvider createConstantReflectionProvider() { - return new AnalysisConstantReflectionProvider(aUniverse, metaAccess); + return new AnalysisConstantReflectionProvider(aUniverse, metaAccess, classInitializationSupport); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java index 371cba683816..f8bd664cacea 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java @@ -45,7 +45,7 @@ protected void registerConditionalConfiguration(ConfigurationCondition condition consumer.accept(condition); } else { Collection handlers = pendingReachabilityHandlers.computeIfAbsent(condition.getType(), key -> new ConcurrentLinkedQueue<>()); - ConfigurationCondition runtimeCondition = ConfigurationCondition.alwaysTrue(); + ConfigurationCondition runtimeCondition = condition.isRuntimeChecked() ? condition : ConfigurationCondition.alwaysTrue(); handlers.add(() -> consumer.accept(runtimeCondition)); } 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 bdec60153acf..681180fd5142 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 @@ -983,7 +983,7 @@ protected void setupNativeImage(OptionValues options, Map, InitKind> classInitKinds = new ConcurrentHashMap<>(); + /** + * We need always-reached types to avoid users injecting class initialization checks in our VM + * implementation and hot paths and to prevent users from making the whole class hierarchy + * require initialization nodes. + */ + @SuppressWarnings("DataFlowIssue")// + private static final Set> alwaysReachedTypes = Set.of( + Object.class, Class.class, String.class, + Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Boolean.class, + Enum.class, Cloneable.class, Formattable.class, Throwable.class, Serializable.class, AutoCloseable.class, Runnable.class, + Iterable.class, Collection.class, Set.class, List.class, Map.class, + System.class, Thread.class, + Reference.class, SoftReference.class, StackWalker.class, ReferenceQueue.class); + + final Set> typesRequiringReachability = ConcurrentHashMap.newKeySet(); + boolean configurationSealed; final ImageClassLoader loader; @@ -133,7 +156,7 @@ Set> classesWithKind(InitKind kind) { /** * Returns true if the provided type is initialized at image build time. - * + *

* If the return value is true, then the class is also guaranteed to be initialized already. * This means that calling this method might trigger class initialization, i.e., execute * arbitrary user code. @@ -144,7 +167,7 @@ public boolean maybeInitializeAtBuildTime(ResolvedJavaType type) { /** * Returns true if the provided type is initialized at image build time. - * + *

* If the return value is true, then the class is also guaranteed to be initialized already. * This means that calling this method might trigger class initialization, i.e., execute * arbitrary user code. @@ -328,7 +351,7 @@ InitKind computeInitKindAndMaybeInitializeClass(Class clazz) { * 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) { @@ -450,4 +473,55 @@ private static void addAllInterfaces(Class clazz, EconomicSet> resul } } } + + public void addForTypeReachedTracking(Class clazz) { + if (!isAlwaysReached(clazz)) { + UserError.guarantee(!configurationSealed, "It is not possible to register types for reachability tracking after the analysis has started."); + typesRequiringReachability.add(clazz); + } + } + + public boolean isAlwaysReached(Class jClass) { + Set systemModules = Set.of("org.graalvm.nativeimage.builder", "org.graalvm.nativeimage", "org.graalvm.nativeimage.base", "com.oracle.svm.svm_enterprise", + "org.graalvm.word", "jdk.internal.vm.ci", "jdk.graal.compiler", "com.oracle.graal.graal_enterprise"); + Set jdkModules = Set.of("java.base", "jdk.management", "java.management", "org.graalvm.collections"); + + String classModuleName = jClass.getModule().getName(); + boolean alwaysReachedModule = classModuleName != null && (systemModules.contains(classModuleName) || jdkModules.contains(classModuleName)); + return jClass.isPrimitive() || + jClass.isArray() || + alwaysReachedModule || + alwaysReachedTypes.contains(jClass); + } + + /** + * If any type in the type hierarchy was marked as "type reached", we have to track + * initialization for all its subtypes. Otherwise, marking the supertype as reached could be + * missed when the initializer of the subtype is computed at build time. + */ + public boolean requiresInitializationNodeForTypeReached(ResolvedJavaType type) { + if (type == null) { + return false; + } + var jClass = OriginalClassProvider.getJavaClass(type); + if (isAlwaysReached(jClass)) { + return false; + } + + if (TreatAllUserSpaceTypesAsTrackedForTypeReached.getValue()) { + return true; + } + + if (typesRequiringReachability.contains(jClass) || + requiresInitializationNodeForTypeReached(type.getSuperclass())) { + return true; + } + + for (ResolvedJavaType anInterface : type.getInterfaces()) { + if (requiresInitializationNodeForTypeReached(anInterface)) { + return true; + } + } + return false; + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerGraphDecoder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerGraphDecoder.java index bc5a2dc3e32f..bac1bcdfaec7 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerGraphDecoder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerGraphDecoder.java @@ -64,6 +64,7 @@ import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode; import com.oracle.svm.core.config.ObjectLayout; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.ameta.AnalysisConstantReflectionProvider; import com.oracle.svm.hosted.classinitialization.SimulateClassInitializerPolicy.SimulateClassInitializerInlineScope; import com.oracle.svm.hosted.fieldfolding.IsStaticFinalFieldInitializedNode; import com.oracle.svm.hosted.fieldfolding.MarkStaticFinalFieldInitializedNode; @@ -374,9 +375,10 @@ protected boolean handleArrayCopy(ImageHeapArray source, int sourcePos, ImageHea } private Node handleEnsureClassInitializedNode(EnsureClassInitializedNode node) { - var classInitType = (AnalysisType) node.constantTypeOrNull(providers.getConstantReflection()); + var aConstantReflection = (AnalysisConstantReflectionProvider) providers.getConstantReflection(); + var classInitType = (AnalysisType) node.constantTypeOrNull(aConstantReflection); if (classInitType != null) { - if (support.trySimulateClassInitializer(graph.getDebug(), classInitType, clusterMember)) { + if (support.trySimulateClassInitializer(graph.getDebug(), classInitType, clusterMember) && !aConstantReflection.initializationCheckRequired(classInitType)) { /* Class is already simulated initialized, no need for a run-time check. */ return null; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerSupport.java index 205699b3bce8..57b481c722d6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/SimulateClassInitializerSupport.java @@ -607,4 +607,8 @@ private static String reasonToString(HostedProviders providers, Object reason) { return String.valueOf(reason); } } + + public ClassInitializationSupport getClassInitializationSupport() { + return classInitializationSupport; + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/HostedRuntimeConfigurationBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/HostedRuntimeConfigurationBuilder.java index fa3e9a3269b7..1b448c8f6e77 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/HostedRuntimeConfigurationBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/HostedRuntimeConfigurationBuilder.java @@ -79,7 +79,7 @@ protected Providers createProviders(CodeCacheProvider codeCache, ConstantReflect @Override protected ConstantReflectionProvider createConstantReflectionProvider() { - return new HostedConstantReflectionProvider(universe, (HostedMetaAccess) metaAccess); + return new HostedConstantReflectionProvider(universe, (HostedMetaAccess) metaAccess, classInitializationSupport); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/UninterruptibleAnnotationChecker.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/UninterruptibleAnnotationChecker.java index cf38b9a86f0b..f5ccebf38230 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/UninterruptibleAnnotationChecker.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/UninterruptibleAnnotationChecker.java @@ -43,12 +43,14 @@ import jdk.graal.compiler.graph.Node; import jdk.graal.compiler.nodes.StructuredGraph; +import jdk.graal.compiler.nodes.ValueNode; import jdk.graal.compiler.nodes.java.AbstractNewObjectNode; import jdk.graal.compiler.nodes.java.MonitorEnterNode; import jdk.graal.compiler.nodes.java.NewMultiArrayNode; import jdk.graal.compiler.nodes.virtual.CommitAllocationNode; import jdk.graal.compiler.options.Option; import jdk.graal.compiler.options.OptionsParser; +import jdk.vm.ci.meta.ConstantReflectionProvider; import jdk.vm.ci.meta.ResolvedJavaMethod; /** Checks that {@linkplain Uninterruptible} has been used consistently. */ @@ -69,9 +71,9 @@ private static UninterruptibleAnnotationChecker singleton() { UninterruptibleAnnotationChecker() { } - public static void checkAfterParsing(ResolvedJavaMethod method, StructuredGraph graph) { + public static void checkAfterParsing(ResolvedJavaMethod method, StructuredGraph graph, ConstantReflectionProvider constantReflectionProvider) { if (Uninterruptible.Utils.isUninterruptible(method) && graph != null) { - singleton().checkGraph(method, graph); + singleton().checkGraph(method, graph, constantReflectionProvider); } } @@ -252,7 +254,7 @@ private void checkCallers(HostedMethod caller, Uninterruptible callerAnnotation, } } - private void checkGraph(ResolvedJavaMethod method, StructuredGraph graph) { + private void checkGraph(ResolvedJavaMethod method, StructuredGraph graph, ConstantReflectionProvider constantReflectionProvider) { Uninterruptible annotation = Uninterruptible.Utils.getAnnotation(method); for (Node node : graph.getNodes()) { if (isAllocationNode(node)) { @@ -265,7 +267,10 @@ private void checkGraph(ResolvedJavaMethod method, StructuredGraph graph) { * It is therefore safe to have class initialization nodes in methods that are * annotated with calleeMustBe = false. */ - violations.add("Uninterruptible method " + method.format("%H.%n(%p)") + " is not allowed to do class initialization."); + ValueNode hub = ((EnsureClassInitializedNode) node).getHub(); + + var culprit = hub.isConstant() ? constantReflectionProvider.asJavaType(hub.asConstant()).toClassName() : "unknown"; + violations.add("Uninterruptible method " + method.format("%H.%n(%p)") + " is not allowed to do class initialization. Initialized type: " + culprit); } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedConstantReflectionProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedConstantReflectionProvider.java index 5a1d4dcb3552..d9d162ee83a3 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedConstantReflectionProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedConstantReflectionProvider.java @@ -31,6 +31,7 @@ import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.hosted.ameta.AnalysisConstantReflectionProvider; +import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; import jdk.vm.ci.meta.Constant; import jdk.vm.ci.meta.JavaConstant; @@ -46,8 +47,8 @@ public class HostedConstantReflectionProvider extends AnalysisConstantReflection private final HostedMemoryAccessProvider hMemoryAccess; @SuppressWarnings("this-escape") - public HostedConstantReflectionProvider(HostedUniverse hUniverse, HostedMetaAccess hMetaAccess) { - super(hUniverse.getBigBang().getUniverse(), hUniverse.getBigBang().getMetaAccess()); + public HostedConstantReflectionProvider(HostedUniverse hUniverse, HostedMetaAccess hMetaAccess, ClassInitializationSupport classInitializationSupport) { + super(hUniverse.getBigBang().getUniverse(), hUniverse.getBigBang().getMetaAccess(), classInitializationSupport); this.hUniverse = hUniverse; this.hMetaAccess = hMetaAccess; this.hMemoryAccess = new HostedMemoryAccessProvider(hMetaAccess, this); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java index f0d64d6ca8f1..b727c3492de8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java @@ -44,6 +44,7 @@ import java.util.function.Function; import java.util.stream.Collectors; +import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.function.CEntryPointLiteral; import org.graalvm.nativeimage.c.function.CFunction; @@ -68,6 +69,7 @@ import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.c.BoxedRelocatedPointer; import com.oracle.svm.core.c.function.CFunctionOptions; +import com.oracle.svm.core.classinitialization.ClassInitializationInfo; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.config.ObjectLayout; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; @@ -146,6 +148,9 @@ public void build(DebugContext debug) { for (AnalysisType aType : aUniverse.getTypes()) { makeType(aType); } + for (AnalysisType aType : aUniverse.getTypes()) { + checkHierarchyForTypeReachedConstraints(aType); + } for (AnalysisField aField : aUniverse.getFields()) { makeField(aField); } @@ -278,9 +283,49 @@ private HostedType makeType(AnalysisType aType) { " -> " + hTypeChecked + " @ " + Integer.toHexString(System.identityHashCode(hTypeChecked)) + " / " + aTypeChecked + " @ " + Integer.toHexString(System.identityHashCode(aTypeChecked))); } + + /* + * Mark all types whose subtype is marked as --initialize-at-build-time types as reached. We + * need this as interfaces without default methods are not transitively initialized at build + * time by their subtypes. + */ + if (hType.wrapped.isReachable() && + ClassInitializationSupport.singleton().maybeInitializeAtBuildTime(hostedJavaClass) && + hub.getClassInitializationInfo().getTypeReached() == ClassInitializationInfo.TypeReached.NOT_REACHED) { + hType.wrapped.forAllSuperTypes(t -> { + var superHub = hUniverse.hostVM().dynamicHub(t); + if (superHub.getClassInitializationInfo().getTypeReached() == ClassInitializationInfo.TypeReached.NOT_REACHED) { + superHub.getClassInitializationInfo().setTypeReached(); + } + }); + } return hType; } + /** + * The {@link ClassInitializationInfo#getTypeReached()} for each super-type hub must have a + * value whose ordinal is greater or equal to its own value. + */ + private void checkHierarchyForTypeReachedConstraints(AnalysisType type) { + if (type.isReachable()) { + var hub = hUniverse.hostVM().dynamicHub(type); + if (type.getSuperclass() != null) { + checkSuperHub(hub, hub.getSuperHub()); + } + + for (AnalysisType superInterface : type.getInterfaces()) { + checkSuperHub(hub, hUniverse.hostVM().dynamicHub(superInterface)); + } + } + } + + private static void checkSuperHub(DynamicHub hub, DynamicHub superTypeHub) { + ClassInitializationInfo.TypeReached typeReached = hub.getClassInitializationInfo().getTypeReached(); + ClassInitializationInfo.TypeReached superTypeReached = superTypeHub.getClassInitializationInfo().getTypeReached(); + VMError.guarantee(superTypeReached.ordinal() >= typeReached.ordinal(), + "Super type of a type must have type reached >= than the type: %s is %s but %s is %s", hub.getName(), typeReached, superTypeHub.getName(), superTypeReached); + } + /* * Normally types need to be compared with equals, and there is a gate check enforcing this. * Using a separate method hides the comparison from the checker. diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphKit.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphKit.java index 84ffed0d53e9..2ee25aa64573 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphKit.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/HostedGraphKit.java @@ -35,6 +35,7 @@ import com.oracle.svm.core.graal.code.SubstrateCompilationIdentifier; import com.oracle.svm.core.graal.replacements.SubstrateGraphKit; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; import com.oracle.svm.hosted.code.SubstrateCompilationDirectives; import jdk.graal.compiler.core.common.type.StampFactory; @@ -73,7 +74,9 @@ public AnalysisMetaAccess getMetaAccess() { } public void emitEnsureInitializedCall(ResolvedJavaType type) { - if (EnsureClassInitializedNode.needsRuntimeInitialization(graph.method().getDeclaringClass(), type)) { + boolean requiresInitializationForTypeReached = ClassInitializationSupport.singleton().requiresInitializationNodeForTypeReached(type); + if (requiresInitializationForTypeReached || + EnsureClassInitializedNode.needsRuntimeInitialization(graph.method().getDeclaringClass(), type)) { ValueNode hub = createConstant(getConstantReflection().asJavaClass(type), JavaKind.Object); appendWithUnwind(new EnsureClassInitializedNode(hub)); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisGraphDecoderImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisGraphDecoderImpl.java index d9ee3c359e51..be4a6399b242 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisGraphDecoderImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/InlineBeforeAnalysisGraphDecoderImpl.java @@ -32,6 +32,7 @@ import com.oracle.graal.pointsto.phases.InlineBeforeAnalysisPolicy; import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode; import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; +import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; import com.oracle.svm.hosted.classinitialization.SimulateClassInitializerSupport; import com.oracle.svm.hosted.fieldfolding.IsStaticFinalFieldInitializedNode; @@ -83,7 +84,7 @@ private Node handleEnsureClassInitializedNode(EnsureClassInitializedNode node) { */ simulateClassInitializerSupport.trySimulateClassInitializer(bb, type); } - if (simulateClassInitializerSupport.isClassInitializerSimulated(type)) { + if (simulateClassInitializerSupport.isClassInitializerSimulated(type) && !ClassInitializationSupport.singleton().requiresInitializationNodeForTypeReached(type)) { return null; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SubstrateClassInitializationPlugin.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SubstrateClassInitializationPlugin.java index bc4dcd1fe2ea..5b8caada369c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SubstrateClassInitializationPlugin.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SubstrateClassInitializationPlugin.java @@ -28,7 +28,9 @@ import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode; import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; +import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; import jdk.graal.compiler.nodes.ConstantNode; import jdk.graal.compiler.nodes.FrameState; import jdk.graal.compiler.nodes.ValueNode; @@ -58,8 +60,12 @@ public void loadReferencedType(GraphBuilderContext builder, ConstantPool constan @Override public boolean apply(GraphBuilderContext builder, ResolvedJavaType type, Supplier frameState) { - if (EnsureClassInitializedNode.needsRuntimeInitialization(builder.getMethod().getDeclaringClass(), type)) { - emitEnsureClassInitialized(builder, builder.getSnippetReflection().forObject(host.dynamicHub(type)), frameState.get()); + var requiredForTypeReached = ClassInitializationSupport.singleton().requiresInitializationNodeForTypeReached(type); + if (requiredForTypeReached || + EnsureClassInitializedNode.needsRuntimeInitialization(builder.getMethod().getDeclaringClass(), type)) { + assert !type.isArray() : "Array types must not have initialization nodes: " + type.getName(); + SnippetReflectionProvider snippetReflection = builder.getSnippetReflection(); + emitEnsureClassInitialized(builder, snippetReflection.forObject(host.dynamicHub(type)), frameState.get()); return true; } return false; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/NativeImageConditionResolver.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/NativeImageConditionResolver.java index 99736dc84a7d..cdf0ae9c0624 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/NativeImageConditionResolver.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/NativeImageConditionResolver.java @@ -46,7 +46,17 @@ public NativeImageConditionResolver(ImageClassLoader classLoader, ClassInitializ public TypeResult resolveCondition(UnresolvedConfigurationCondition unresolvedCondition) { String canonicalizedName = RegistryAdapter.canonicalizeTypeName(unresolvedCondition.getTypeName()); TypeResult> clazz = classLoader.findClass(canonicalizedName); - return clazz.map(ConfigurationCondition::create); + return clazz.map(type -> { + /* + * We don't want to track always reached types: we convert them into build-time + * reachability checks. + */ + var runtimeChecked = !classInitializationSupport.isAlwaysReached(type) && unresolvedCondition.isRuntimeChecked(); + if (runtimeChecked) { + classInitializationSupport.addForTypeReachedTracking(type); + } + return ConfigurationCondition.create(type, runtimeChecked); + }); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index ff60d493eda0..f525f9bedf1a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -61,6 +61,7 @@ import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; import java.util.stream.Collectors; import org.graalvm.nativeimage.ImageSingletons; @@ -141,7 +142,7 @@ public class ReflectionDataBuilder extends ConditionalConfigurationRegistry impl private final Map> processedTypes = new ConcurrentHashMap<>(); private final Map, Set> pendingRecordClasses; - record ConditionalTask(ConfigurationCondition condition, Runnable task) { + record ConditionalTask(ConfigurationCondition condition, Consumer task) { } private final Set pendingConditionalTasks = ConcurrentHashMap.newKeySet(); @@ -160,7 +161,7 @@ public void duringSetup(AnalysisMetaAccess analysisMetaAccess, AnalysisUniverse this.metaAccess = analysisMetaAccess; this.universe = analysisUniverse; for (var conditionalTask : pendingConditionalTasks) { - registerConditionalConfiguration(conditionalTask.condition, (cnd) -> universe.getBigbang().postTask(debug -> conditionalTask.task.run())); + registerConditionalConfiguration(conditionalTask.condition, (cnd) -> universe.getBigbang().postTask(debug -> conditionalTask.task.accept(cnd))); } pendingConditionalTasks.clear(); } @@ -169,13 +170,13 @@ public void beforeAnalysis(BeforeAnalysisAccessImpl beforeAnalysisAccess) { this.analysisAccess = beforeAnalysisAccess; } - private void runConditionalInAnalysisTask(ConfigurationCondition condition, Runnable task) { + private void runConditionalInAnalysisTask(ConfigurationCondition condition, Consumer task) { if (sealed) { throw UserError.abort("Too late to add classes, methods, and fields for reflective access. Registration must happen in a Feature before the analysis has finished."); } if (universe != null) { - registerConditionalConfiguration(condition, (cnd) -> universe.getBigbang().postTask(debug -> task.run())); + registerConditionalConfiguration(condition, (cnd) -> universe.getBigbang().postTask(debug -> task.accept(cnd))); } else { pendingConditionalTasks.add(new ConditionalTask(condition, task)); VMError.guarantee(universe == null, "There shouldn't be a race condition on Feature.duringSetup."); @@ -189,17 +190,17 @@ private void setQueryFlag(Class clazz, int flag) { @Override public void register(ConfigurationCondition condition, boolean unsafeInstantiated, Class clazz) { Objects.requireNonNull(clazz, () -> nullErrorMessage("class")); - runConditionalInAnalysisTask(condition, () -> registerClass(clazz, unsafeInstantiated, true)); + runConditionalInAnalysisTask(condition, (cnd) -> registerClass(cnd, clazz, unsafeInstantiated, true)); } @Override public void registerAllClassesQuery(ConfigurationCondition condition, Class clazz) { - runConditionalInAnalysisTask(condition, () -> { + runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_CLASSES_FLAG); try { for (Class innerClass : clazz.getClasses()) { innerClasses.computeIfAbsent(innerClass.getDeclaringClass(), c -> ConcurrentHashMap.newKeySet()).add(innerClass); - registerClass(innerClass, false, !MissingRegistrationUtils.throwMissingRegistrationErrors()); + registerClass(cnd, innerClass, false, !MissingRegistrationUtils.throwMissingRegistrationErrors()); } } catch (LinkageError e) { registerLinkageError(clazz, e, classLookupExceptions); @@ -209,12 +210,12 @@ public void registerAllClassesQuery(ConfigurationCondition condition, Class c @Override public void registerAllDeclaredClassesQuery(ConfigurationCondition condition, Class clazz) { - runConditionalInAnalysisTask(condition, () -> { + runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_DECLARED_CLASSES_FLAG); try { for (Class innerClass : clazz.getDeclaredClasses()) { innerClasses.computeIfAbsent(clazz, c -> ConcurrentHashMap.newKeySet()).add(innerClass); - registerClass(innerClass, false, !MissingRegistrationUtils.throwMissingRegistrationErrors()); + registerClass(cnd, innerClass, false, !MissingRegistrationUtils.throwMissingRegistrationErrors()); } } catch (LinkageError e) { registerLinkageError(clazz, e, classLookupExceptions); @@ -222,7 +223,7 @@ public void registerAllDeclaredClassesQuery(ConfigurationCondition condition, Cl }); } - private void registerClass(Class clazz, boolean unsafeInstantiated, boolean allowForName) { + private void registerClass(ConfigurationCondition condition, Class clazz, boolean unsafeInstantiated, boolean allowForName) { if (shouldExcludeClass(clazz)) { return; } @@ -234,7 +235,7 @@ private void registerClass(Class clazz, boolean unsafeInstantiated, boolean a } if (allowForName) { - ClassForNameSupport.registerClass(clazz); + ClassForNameSupport.singleton().registerClass(condition, clazz); if (!MissingRegistrationUtils.throwMissingRegistrationErrors()) { /* @@ -258,25 +259,25 @@ private void registerClass(Class clazz, boolean unsafeInstantiated, boolean a @Override public void registerClassLookupException(ConfigurationCondition condition, String typeName, Throwable t) { - runConditionalInAnalysisTask(condition, () -> ClassForNameSupport.registerExceptionForClass(typeName, t)); + runConditionalInAnalysisTask(condition, (cnd) -> ClassForNameSupport.singleton().registerExceptionForClass(cnd, typeName, t)); } @Override public void registerClassLookup(ConfigurationCondition condition, String typeName) { - runConditionalInAnalysisTask(condition, () -> { + runConditionalInAnalysisTask(condition, (cnd) -> { try { - registerClass(Class.forName(typeName, false, ClassLoader.getSystemClassLoader()), false, true); + registerClass(cnd, Class.forName(typeName, false, ClassLoader.getSystemClassLoader()), false, true); } catch (ClassNotFoundException e) { - ClassForNameSupport.registerNegativeQuery(typeName); + ClassForNameSupport.singleton().registerNegativeQuery(cnd, typeName); } catch (Throwable t) { - ClassForNameSupport.registerExceptionForClass(typeName, t); + ClassForNameSupport.singleton().registerExceptionForClass(cnd, typeName, t); } }); } @Override public void registerAllRecordComponentsQuery(ConfigurationCondition condition, Class clazz) { - runConditionalInAnalysisTask(condition, () -> { + runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_RECORD_COMPONENTS_FLAG); registerRecordComponents(clazz); }); @@ -284,11 +285,11 @@ public void registerAllRecordComponentsQuery(ConfigurationCondition condition, C @Override public void registerAllPermittedSubclassesQuery(ConfigurationCondition condition, Class clazz) { - runConditionalInAnalysisTask(condition, () -> { + runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_PERMITTED_SUBCLASSES_FLAG); if (clazz.isSealed()) { for (Class permittedSubclass : clazz.getPermittedSubclasses()) { - registerClass(permittedSubclass, false, false); + registerClass(condition, permittedSubclass, false, false); } } }); @@ -296,11 +297,11 @@ public void registerAllPermittedSubclassesQuery(ConfigurationCondition condition @Override public void registerAllNestMembersQuery(ConfigurationCondition condition, Class clazz) { - runConditionalInAnalysisTask(condition, () -> { + runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_NEST_MEMBERS_FLAG); for (Class nestMember : clazz.getNestMembers()) { if (nestMember != clazz) { - registerClass(nestMember, false, false); + registerClass(condition, nestMember, false, false); } } }); @@ -308,7 +309,7 @@ public void registerAllNestMembersQuery(ConfigurationCondition condition, Class< @Override public void registerAllSignersQuery(ConfigurationCondition condition, Class clazz) { - runConditionalInAnalysisTask(condition, () -> { + runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_SIGNERS_FLAG); Object[] signers = clazz.getSigners(); if (signers != null) { @@ -322,12 +323,12 @@ public void registerAllSignersQuery(ConfigurationCondition condition, Class c @Override public void register(ConfigurationCondition condition, boolean queriedOnly, Executable... executables) { requireNonNull(executables, "executable"); - runConditionalInAnalysisTask(condition, () -> registerMethods(queriedOnly, executables)); + runConditionalInAnalysisTask(condition, (cnd) -> registerMethods(queriedOnly, executables)); } @Override public void registerAllMethodsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { - runConditionalInAnalysisTask(condition, () -> { + runConditionalInAnalysisTask(condition, (cnd) -> { for (Class current = clazz; current != null; current = current.getSuperclass()) { setQueryFlag(current, ALL_METHODS_FLAG); } @@ -341,7 +342,7 @@ public void registerAllMethodsQuery(ConfigurationCondition condition, boolean qu @Override public void registerAllDeclaredMethodsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { - runConditionalInAnalysisTask(condition, () -> { + runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_DECLARED_METHODS_FLAG); try { registerMethods(queriedOnly, clazz.getDeclaredMethods()); @@ -353,7 +354,7 @@ public void registerAllDeclaredMethodsQuery(ConfigurationCondition condition, bo @Override public void registerAllConstructorsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { - runConditionalInAnalysisTask(condition, () -> { + runConditionalInAnalysisTask(condition, (cnd) -> { for (Class current = clazz; current != null; current = current.getSuperclass()) { setQueryFlag(current, ALL_CONSTRUCTORS_FLAG); } @@ -367,7 +368,7 @@ public void registerAllConstructorsQuery(ConfigurationCondition condition, boole @Override public void registerAllDeclaredConstructorsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { - runConditionalInAnalysisTask(condition, () -> { + runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_DECLARED_CONSTRUCTORS_FLAG); try { registerMethods(queriedOnly, clazz.getDeclaredConstructors()); @@ -430,7 +431,7 @@ private void registerMethod(boolean queriedOnly, Executable reflectExecutable) { @Override public void registerMethodLookup(ConfigurationCondition condition, Class declaringClass, String methodName, Class... parameterTypes) { - runConditionalInAnalysisTask(condition, () -> { + runConditionalInAnalysisTask(condition, (cnd) -> { try { registerMethod(true, declaringClass.getDeclaredMethod(methodName, parameterTypes)); } catch (NoSuchMethodException e) { @@ -442,7 +443,7 @@ public void registerMethodLookup(ConfigurationCondition condition, Class decl @Override public void registerConstructorLookup(ConfigurationCondition condition, Class declaringClass, Class... parameterTypes) { - runConditionalInAnalysisTask(condition, () -> { + runConditionalInAnalysisTask(condition, (cnd) -> { try { registerMethod(true, declaringClass.getDeclaredConstructor(parameterTypes)); } catch (NoSuchMethodException e) { @@ -455,7 +456,7 @@ public void registerConstructorLookup(ConfigurationCondition condition, Class @Override public void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields) { requireNonNull(fields, "field"); - runConditionalInAnalysisTask(condition, () -> registerFields(false, fields)); + runConditionalInAnalysisTask(condition, (cnd) -> registerFields(false, fields)); } @Override @@ -464,7 +465,7 @@ public void registerAllFieldsQuery(ConfigurationCondition condition, Class cl } private void registerAllFieldsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { - runConditionalInAnalysisTask(condition, () -> { + runConditionalInAnalysisTask(condition, (cnd) -> { for (Class current = clazz; current != null; current = current.getSuperclass()) { setQueryFlag(current, ALL_FIELDS_FLAG); } @@ -482,7 +483,7 @@ public void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, Cla } private void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { - runConditionalInAnalysisTask(condition, () -> { + runConditionalInAnalysisTask(condition, (cnd) -> { setQueryFlag(clazz, ALL_DECLARED_FIELDS_FLAG); try { registerFields(queriedOnly, clazz.getDeclaredFields()); @@ -532,7 +533,7 @@ private void registerField(boolean queriedOnly, Field reflectField) { @Override public void registerFieldLookup(ConfigurationCondition condition, Class declaringClass, String fieldName) { - runConditionalInAnalysisTask(condition, () -> { + runConditionalInAnalysisTask(condition, (cnd) -> { try { registerField(false, declaringClass.getDeclaredField(fieldName)); } catch (NoSuchFieldException e) { @@ -549,7 +550,7 @@ private void processAnnotationMethod(boolean queriedOnly, Method method) { Class annotationClass = method.getDeclaringClass(); Class proxyClass = Proxy.getProxyClass(annotationClass.getClassLoader(), annotationClass); try { - var condition = ConfigurationCondition.create(proxyClass); + var condition = ConfigurationCondition.create(proxyClass, true); register(condition, queriedOnly, proxyClass.getDeclaredMethod(method.getName(), method.getParameterTypes())); } catch (NoSuchMethodException e) { /* @@ -563,7 +564,7 @@ private void processAnnotationField(Field field) { Class annotationClass = field.getDeclaringClass(); Class proxyClass = Proxy.getProxyClass(annotationClass.getClassLoader(), annotationClass); try { - var condition = ConfigurationCondition.create(proxyClass); + var condition = ConfigurationCondition.create(proxyClass, true); register(condition, false, proxyClass.getDeclaredField(field.getName())); } catch (NoSuchFieldException e) { /* @@ -597,12 +598,12 @@ private void checkSubtypeForOverridingField(AnalysisField field, AnalysisType su * {@link Class#getDeclaredMethods()} internally, instead of * {@link AnalysisType#resolveConcreteMethod(ResolvedJavaMethod)} which gives different results * in at least two scenarios: - * + *

* 1) When resolving a static method, resolveConcreteMethod does not return a subclass method * with the same signature, since they are actually fully distinct methods. However, these * methods need to be included in the hiding list because them showing up in a reflection query * would be wrong. - * + *

* 2) When resolving an interface method from an abstract class, resolveConcreteMethod returns * an undeclared method with the abstract subclass as declaring class, which is not the * reflection API behavior. @@ -794,7 +795,7 @@ private void registerTypesForGenericSignature(Type type, int dimension) { /* * Reflection signature parsing will try to instantiate classes via Class.forName(). */ - ClassForNameSupport.registerClass(clazz); + ClassForNameSupport.singleton().registerClass(clazz); } else if (type instanceof TypeVariable) { /* Bounds are reified lazily. */ registerTypesForGenericSignature(queryGenericInfo(((TypeVariable) type)::getBounds), dimension); @@ -1178,7 +1179,7 @@ private static String nullErrorMessage(String kind) { public static class TestBackdoor { public static void registerField(ReflectionDataBuilder reflectionDataBuilder, boolean queriedOnly, Field field) { - reflectionDataBuilder.runConditionalInAnalysisTask(ConfigurationCondition.alwaysTrue(), () -> reflectionDataBuilder.registerField(queriedOnly, field)); + reflectionDataBuilder.runConditionalInAnalysisTask(ConfigurationCondition.alwaysTrue(), (cnd) -> reflectionDataBuilder.registerField(queriedOnly, field)); } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java index 28400f4109e7..1317ba6ed320 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java @@ -277,7 +277,7 @@ public void duringSetup(DuringSetupAccess a) { /* Primitive classes cannot be accessed through Class.forName() */ for (Class primitiveClass : PRIMITIVE_CLASSES) { - ClassForNameSupport.registerNegativeQuery(primitiveClass.getName()); + ClassForNameSupport.singleton().registerNegativeQuery(ConfigurationCondition.alwaysTrue(), primitiveClass.getName()); } access.registerObjectReachableCallback(SubstrateAccessor.class, ReflectionFeature::onAccessorReachable);