-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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-40106] Reporting missing metadata in Native Image by throwing special exceptions #5171
Comments
7 tasks
bclozel
added a commit
to spring-projects/spring-framework
that referenced
this issue
Mar 29, 2023
Prior to this commit, reflection hints registered for beans was selectively applied to only consider the methods that we'll actually need reflection on at runtime. This would rely on an undocumented behavior of GraalVM Native where calling `getDeclaredMethods` on a type would only return known metadata at runtime, ignoring the ones that were not registered during native compilation. As of oracle/graal#5171, this behavior is now fixed in GraalVM and aligns with the JVM behavior: all methods will be returned. This means that if during native compilation, introspection was not registered for the type a new `MissingReflectionMetadataException` will be raised. As a follow up of #29205, this commit contributes the "introspection on declared method" reflection hint for all registered beans. Closes gh-29246
vjovanov
modified the milestones:
GraalVM for JDK 17 / JDK 20 (June 13, 2023),
GraalVM for JDK 21 (September 19, 2023)
Jun 21, 2023
vjovanov
modified the milestones:
GraalVM for JDK 21 (September 19, 2023),
GraalVM for JDK 22 (March 19, 2024)
Sep 22, 2023
vjovanov
modified the milestones:
GraalVM for JDK 22 (March 19, 2024),
GraalVM for JDK 23 (September 17, 2024)
Dec 7, 2023
spavlusieva
changed the title
Reporting missing metadata in Native Image by throwing special exceptions
[GR-40106] Reporting missing metadata in Native Image by throwing special exceptions
Apr 29, 2024
This ticket was fixed in GraalVM for JDK 23. Documentation can be found here. |
github-project-automation
bot
moved this from In Progress
to Done
in GraalVM Community Roadmap
Sep 17, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TL;DR
Libraries often rely on catching exceptions from the reflection API and they have a mechanism to recover from them. Native Image does not distinguish between missing reachability metadata and exceptions that are regularly thrown by the reflection API. Therefore, missing metadata can lead to program misbehavior that is hard to diagnose and leads to unnecessary debugging. A good example of such behavior is the
ClassNotFoundException
that is thrown when a class is not on the classpath, or when the metadata for a class is not present.We propose that each call that requires metadata, fails with a special exception (e.g.,
MissingReflectionMetadataException
) when the metadata entry is not present. When the-XX:ExitOnMissingMetadata
the same program will terminate withSystem#exit
to allow for easier debugging.Note this is a breaking change and it can cause existing libraries that recovered from reflection API exceptions to function differently.
Goals
The main goal is to allow Native Image users to easily and early catch metadata exceptions. The users can debug their programs with
-XX:ExitOnMissingMetadata
to guarantee that all metadata entries are correct.Detailed Description
Native Image is based on a closed-world assumption. This means that any element (
Class
,Executable
orField
) accessed reflectively by the program has to be included in the reachability metadata ahead of time.Native Image currently throws a
ClassNotFoundException
,NoSuchMethodException
or similar when trying to query an element in the following cases:LinkageError
at build time. The element was then dropped by Native Image, which means theLinkageError
is lost and not thrown at run time. This is not compliant with the Java specification.LinkageError
. This is also not compliant with the Java specification.The situation is similar with resource and serialization metadata.
This accumulation of very different situations under the same run-time error, two of which are not compliant with the Java specification and one of which is only by chance, are prone to create confusion.
Here is an example to illustrate the problem:
Main.java (compiled with Java 11)
QueriedClass.java (compiled with Java 17)
BackupClass.java (compiled with Java 11)
On HotSpot, running
java Main "QueriedClass" "BackupClass"
results in anUnsupportedClassVersionError
on the firstClass.forName
call, and the program then successfully queriesBackupClass
and exits.After compiling the code with Native Image, running
main "QueriedClass" "BackupClass"
fails with aClassNotFoundException
on the firstClass.forName
call (either becauseQueriedClass
is not included in the metadata or because it triggered theUnsupportedClassVersionError
at build time and was therefore dropped from the image).In this case, the Native Image behavior is not the same as the HotSpot behavior. It is also unclear, based on the thrown exception, whether
QueriedClass
indeed doesn't exist or was only omitted from the metadata.Note: the issue of incorrect exceptions being thrown by
Class.forName
has recently been patched in Native Image, however this example is both simple and symptomatic of the larger problem.New missing metadata exception
Elements that are queried at run-time without being registered in the metadata will now throw a Native Image-specific exception. This will ensure that a metadata issue does not get confused with a Java-level issue, as is the case currently.
For example, a
ClassNotFoundException
will now only be thrown in cases where the JDK would also throw such an error.There will be an exception for each concerned type of metadata, to enable differentiated catching of those exceptions. Thus, we will introduce a
MissingReflectionMetadataException
, aMissingResourceMetadataException
and aMissingSerializationMetadataException
.In the example, omitting either
QueriedClass
orBackupClass
from the metadata will now throw aMissingReflectionMetadataException
when the class is queried, hinting at exactly what the problem is and how to fix it.Missing metadata error messages
The new missing metadata exceptions will throw the corresponding error messages:
Method <qualified class name>#<methodName>(<signature>) has not been registered for reflection. To ensure this element is accessible at run time, you need to add it to the reflection metadata.
Resource at path <resource path> has not been registered as reachable. To ensure this resource is available at run time, you need to add it to the resource metadata.
Class <qualified class name> has not been registered for serialization. To ensure this element is serializable at run time, you need to add it to the serialization metadata.
Rethrowing of build-time linkage errors at run time
As part of our effort to be fully Java-compliant, reflection, resource and serialization queries will now throw the correct exception at run time, even when the element triggered an exception at build time (e.g. a
LinkageError
).This behavior will only occur for elements present in the corresponding metadata. If that is not the case, Native Image will throw the appropriate
MissingMetadataException
instead.This means that, assuming both
QueriedClass
andBackupClass
are included in the reflection metadata, the introductory example will now have the same behavior on HotSpot and Native Image.If the
--link-at-build-time
option is specified for the queried element, theLinkageError
will instead be thrown immediately by the builder.Native Image agent modifications
The Native Image agent currently registers the arguments to reflection, resource or serialization queries only if these queries do not throw an exception.
In the example, the agent would currently only register
BackupClass
for reflection, since queryingQueriedClass
results in anUnsupportedClassVersionError
.The Native Image agent will be modified to also register elements that cause an exception when queried. As a result, no
MissingMetadataException
should be thrown when the agent is (correctly) used.The agent currently has to run each intercepted call itself to check whether its execution triggers an exception, and drop the element from the metadata if it is the case. As every intercepted call will now create a metadata entry regardless of its result, this repeated call will not be necessary anymore, which should improve agent run times.
Stack trace rewriting
The exception being rethrown at run time will have a composite stack trace:
The part of the trace located before the problematic call will be the location where the exception happened at run time. This is already the case, and ensures that users know where the exception happened;
The part of the trace located after the problematic call will be patched with the corresponding part of the stack trace of the exception caught at build time. This ensures that users know why the exception happened.
To ensure that the correct parts of the build-time stack trace are patched into the run-time stack trace, exceptions will be caught at build-time from the lowest possible level, usually from JDK native methods. This also ensures that the image size impact of this change stays minimal.
In the case of our introductory example, the produced stack trace will look as follows:
The part of the trace marked as
base
is the trace that would currently be part of the thrown exception, describing where the exception occurred.The part of the trace marked as
patched
is extracted from the exception caught at build time. This includes the exception type and its cause, which is itself not patched.Impact on existing applications
Manual metadata
For projects using manual (or programmatically computed) metadata files, the presented changes mean that more elements need to be added to the metadata to ensure a correct execution.
While this one-time update is certainly time-consuming, we believe that the benefits of this approach in terms of understandability (any element queried should be included regardless of the result of that query) and precision (it eliminates ambiguous error reporting and accidental specification compliance) justify it.
In practical terms, it will be possible to simply follow the instructions given by
MissingMetadataException
error messages to patch the metadata.Agent-computed metadata
Projects using agent-computed metadata will not have to worry about changes to their metadata generation.
These projects may still encounter issues linked with the behavior changes described in this proposal. However, these changes are fixing cases where Native Image does not conform to Java specifications, and as such cannot be avoided. As a result, more programs will be able to work on both HotSpot and Native Image without modification.
The new missing metadata exceptions may hinder efforts to fix those issues. The next section introduces an option that will help separate metadata issues from wrong program behavior.
Hard exit option
The current discrepancies between HotSpot and Native Image behaviors may have triggered users to modify the introductory example in the following way, replacing the specific
LinkageError
with a blanketThrowable
catch to ensure both results are the same:The changes presented in this proposal make this change unnecessary, however this
Throwable
catch will now also catch anyMissingReflectionMetadataException
that is thrown ifQueriedClass
is absent from the metadata.To enable debugging this type of problem, and to ensure that a metadata problem can always be distinguished from another exception, the option
-XX:ExitOnMissingMetadata
will cause the program to exit without the possibility of recovery instead of throwing one of the new missing metadata exceptions.This will facilitate the debugging of code expecting the previous behavior in the case of missing metadata, and the discovery of metadata issues in code containing blanket
catch
blocks which may silence them.To facilitate diagnosis, the exception type, message, cause and stack trace will still be displayed before exiting.
Implementation
Build-time caching of exceptions
An unreproducible exception is an exception that cannot be reproduced at run time by Native Image, such as a
ClassFormatError
, since it has no concept of a class file.The Native Image builder will run the problematic calls at build-time on all registered reflection, resource and serialization metadata, and cache any unreproducible exception.
Other exceptions, such as
IllegalAccessError
, do not suffer from this problem and as such, do not need to be cached. However, the problematic element will now be included in the image instead of being ignored by the Native Image builder.Reflection
An unreproducible error can occur during the following operations:
Class.forName0
is the only static method here. We will handleClass.forName
errors by enhancing the currently used structure mapping class names toDynamicHub
objects to be able to alternatively containThrowable
objects, which can then be thrown as needed at run-time.All the other methods are instance methods whose results can be encoded in the existing reflection metadata byte arrays (most of them actually already are). We will enhance the current encoding to allow negative values for those results, which would mean that the call should throw instead of returning a result, and lookup the exception to throw in a new encoder, parallel to the existing class, string and object encoders.
This is possible for all methods, both those returning a single element (e.g.
getDeclaringClass0
) or a set number of elements (e.g.getEnclosingMethod0
), where the first element encoded is a class index, and the others returning an array, where the size of the array is encoded first. Both those values are expected to be positive integers, and therefore enable the use of the negative values for exceptions.Care should be taken for the
NO_DATA
value, which is used to represent null values and is currently encoded as-1
. Shifting the exception object index to avoid collisions with it should be enough to solve the problem.Resources
Querying resources can throw an
IOException
at build-time. This exception will be thrown at run time when needed. This exception will be caught while callingClassLoader.get[System]Resource[s]
at build-time, and stored forResourcesHelper.nameToResource[s]
to re-throw when invoked with the matching name. We will use a simple map for that purpose, similar to the solution found forClass.forName
.Serialization
Serialization exceptions can occur in three places:
SerializedLambda.readResolve
,ObjectStreamClass.<init>
andReflectionFactory.newConstructorForSerialization
. In each case, the potential serialization-specific exceptions can be reproduced in Native Image, and don't require specific build-time support. The exceptions that require build-time support are reflection exceptions, which are already covered above.The text was updated successfully, but these errors were encountered: