oracle / graal

GraalVM compiles Java applications into native executables that start instantly, scale fast, and use fewer compute resources 🚀
https://www.graalvm.org
Other
20.28k stars 1.63k forks source link

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

Closed vjovanov closed 2 weeks ago

vjovanov commented 1 year ago

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

fniephaus commented 1 year ago

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 commented 2 weeks ago

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