Skip to content

Commit

Permalink
Introduce a typeReached check for Class.forName
Browse files Browse the repository at this point in the history
The core implementation of #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 stored in the image heap.

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.
  • Loading branch information
vjovanov committed Apr 17, 2024
1 parent 104613f commit 9c581ed
Show file tree
Hide file tree
Showing 40 changed files with 739 additions and 225 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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 <type> for reflection"
}
},
"required": [
"typeReachable"
"oneOf": [
"typeReachable",
"typeReached"
],
"additionalProperties": false,
"type": "object"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,43 +43,51 @@
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.
* <p>
* Currently, there is only one type of condition (<code>typeReached</code>) so this is a single
* Currently, there is only one type of condition (<code>typeReached</code>) 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;
}

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;
}

public Class<?> getType() {
return type;
}

public boolean isRuntimeChecked() {
return runtimeChecked;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<UnresolvedConfigurationCondition> {
public final class UnresolvedConfigurationCondition implements Comparable<UnresolvedConfigurationCondition> {
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() {
Expand All @@ -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) {
Expand All @@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ private ConfigurationSet deduceConditionalConfiguration(Map<MethodInfo, List<Met
for (List<MethodCallNode> 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());
}
Expand Down
Loading

0 comments on commit 9c581ed

Please sign in to comment.