Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[GR-49526] Introduce Runtime Checks for typeReachable and Change the Name to typeReached #7480

Closed
vjovanov opened this issue Sep 22, 2023 · 2 comments
Assignees
Labels

Comments

@vjovanov
Copy link
Member

vjovanov commented Sep 22, 2023

TL;DR

Native Image uses static analysis to determine if types are registered for reflective access. This is hard to explain in terms of standard programming practices (e.g., lambda calculus and the type theory) and can lead to unpredictable errors. For example:

  1. Accidentally rewriting the program so a smaller number of elements is reached can lead to incorrect behavior in an unrelated part of the program.
  2. An improvement to static analysis can start considering a smaller set of types as reachable and thus change the behavior of existing programs.

In JSON, reachability-based registration is defined with the typeReachable conditions, and in the Feature API with reachability handlers for types, methods, and fields.

To overcome the issues with reachability we need to introduce runtime checks to verify that types have been "reached" at run time and deprecate the behavior of typeReachable. In JSON metadata we would express this condition with typeReached:

{
  "condition": { "typeReached": "app.MyApp" },
  "name": "app.ReflectivelyAccessedType"
}

Type initialized is equivalent to typeReachable with an extra runtime check at every reflective access to see if the class (e.g., app.MyApp) was initialized. If the type is not accessible the reflective access will throw a MissingReflectionRegistrationError with a special message that says what type initialization was missing to make this access possible.

The org.graalvm.nativeimage.hosted.RuntimeReflection, org.graalvm.nativeimage.hosted.RuntimeSerialization, and org.graalvm.nativeimage.hosted.RuntimeResourceAccess from the feature API need to get an additional parameter that describes the condition.

Goals

  1. Stop depending on static analysis for program correctness: Only use static analysis to reduce the image size.
  2. Change reflection registration so the semantics of Native Image to be replicated in environments without static analysis.

Non-Goals

  1. Make programs slower to execute due to runtime marking of types that were initialized.
  2. Make configuration harder.

Interactions with the Rest of the System

The biggest change is that all types that appear in typeReached conditions must have additional conditional checks. These checks will be invisible to the user, but they might introduce a small performance overhead. From the experience with class initialization, this overhead can be greatly reduced with optimization passes.

Native Image Agent Support

In the agent with the conditional mode, we will simply output the typeReached conditions. Since these conditions come from the stack, that means that the type was certainly initialized.

Backward Compatibility

The value of "name" in reflect-config.json will continue to have the implicit meaning that the class can be fetched with Class.forName.

The new element will be output by the agent and described in all documentation as the default. The reachability metadata will be migrated to the new format immediately. The previous versions of GraalVM must be modified to accept typeInitialized but treat it as if it is typeReachable.

typeReachable should be supported until the majority of the ecosystem adopts the new semantics

@vjovanov vjovanov self-assigned this Sep 22, 2023
@fniephaus
Copy link
Member

From the community workshop: maybe we should consider calling it typeReached because interfaces cannot be initialized. To avoid confusing it with reachability, maybe typeAccessed or typeUsed are better alternatives?

@vjovanov vjovanov changed the title Introduce Runtime Checks for typeReachable and Call it typeInitialized Introduce Runtime Checks for typeReachable and Change the Name Sep 26, 2023
@vjovanov vjovanov closed this as completed Nov 7, 2023
@vjovanov vjovanov reopened this Nov 7, 2023
graalvmbot pushed a commit that referenced this issue Apr 17, 2024
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.
graalvmbot pushed a commit that referenced this issue Apr 17, 2024
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.
graalvmbot pushed a commit that referenced this issue Apr 22, 2024
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 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.
graalvmbot pushed a commit that referenced this issue Apr 23, 2024
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 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.
@vjovanov vjovanov changed the title Introduce Runtime Checks for typeReachable and Change the Name Introduce Runtime Checks for typeReachable and Change the Name to typeReached Apr 29, 2024
@spavlusieva spavlusieva changed the title Introduce Runtime Checks for typeReachable and Change the Name to typeReached [GR-49526] Introduce Runtime Checks for typeReachable and Change the Name to typeReached Apr 29, 2024
@vjovanov
Copy link
Member Author

This has been fixed in GraalVM for JDK 23. Documentation can be found here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Released
Development

No branches or pull requests

2 participants