Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fba956c
Register resource bundles as called, not returned
loicottet Aug 22, 2023
2368a2f
Correctly handle resource include patterns
loicottet Aug 29, 2023
0fde0ab
Automatically register primitive and array classes for reflection
loicottet Aug 29, 2023
8e1bbd6
Disable interface constructor missing metadata checks
loicottet Aug 29, 2023
6f2ad97
Register class-based lookup for property resource bundles
loicottet Aug 30, 2023
c54f3c7
Correctly register annotations in annotation members
loicottet Aug 30, 2023
128b0cb
Correctly handle negative constructor lookups when bulk queries are r…
loicottet Aug 30, 2023
66aef86
Do not throw missing registration errors for non-public elements when…
loicottet Aug 30, 2023
35b1c2c
Register Bundles.of in the agent
loicottet Aug 30, 2023
728e833
Handle missing registration errors during method handle lookups
loicottet Aug 31, 2023
6089b11
Handle <init> as a method name in lookups
loicottet Aug 31, 2023
18332cc
Handle array reflection with Java names
loicottet Aug 31, 2023
217c49f
Update the access advisor for JCK tests
loicottet Aug 31, 2023
914adce
Fix ToReflectMethod to not need reflection
vjovanov Sep 1, 2023
61feac6
Correctly handle alternative bundle names in queries
loicottet Sep 1, 2023
607fb8d
Catch wrong class names in deserialization
vjovanov Sep 6, 2023
cf5f203
Remove skipped service providers from the heap
loicottet Sep 6, 2023
53537d6
Add breakpoint for Module.getResourceAsStream
loicottet Sep 6, 2023
b9f1b4f
Correctly handle multiple Class.forName registrations from different …
loicottet Sep 6, 2023
c688cfc
Correctly handle ClassLoader.findSystemClass and empty string in clas…
loicottet Sep 7, 2023
f730ded
Add identation to reflection errors
vjovanov Sep 20, 2023
246305a
Cleanup benchmark --allow-incomplete-classpath
vjovanov Sep 20, 2023
4902802
Log better the resource tests
vjovanov Sep 22, 2023
5103185
Take module into account in BuiltinClassLoader.findResourceAsStream
loicottet Oct 4, 2023
73eb48b
Initialize CharArrayWrapper at build-time for xalan
loicottet Oct 6, 2023
135005b
Include required locales by default
loicottet Oct 6, 2023
efadbaf
Reset locale formatter cache
loicottet Oct 13, 2023
031cf34
Add strict metadata docs
loicottet Oct 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions docs/reference-manual/native-image/ReachabilityMetadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,41 @@ The schema also includes further details and explanations how this configuration
]
```

## Strict Metadata Mode

Native Image's strict metadata mode helps ensure the correctness and composability of the Native Image metadata, by strengthening the metadata requirements for reflection queries.
This mode can be activated with the `-H:ThrowMissingRegistrationErrors=` option and requires the following additional registrations over the default:

### Reflection

* If a reflectively-accessed element (`Class`, `Field`, `Method`, etc.) is not present on the image class- or module-path, it still needs to be registered to ensure the correct exception (`ClassNotFoundException` or similar) is thrown.
If an element is queried at run-time without having been registered, regardless of whether it is present on the class- or module-path, this query will throw a `MissingReflectionRegistrationError`.
This change ensures that the error is not ambiguous between a non-existent element and one that was not registered for reflection in the image;
* This rationale also requires that any query that returns a collection of class members (`Class.getMethods()` or similar) has to be registered in full (with `"queryAllPublicMethods"` in this case) to succeed at run-time.
This additionally ensures that any of the registered elements can be queried individually, and non-existent elements of that type will throw the correct exception without having to be registered.
However, this means that `Class.getMethods()` does not return the subset of methods that were registered, but throws a `MissingReflectionRegistrationError` if `"queryAllPublicMethods"` is missing.

### Resources

* If a resource or resource bundle is not present on the image class- or module-path, it still needs to be registered to ensure the correct return value (`null`).
If a resource is queried at run-time without having been registered, regardless of whether it is present on the class- or module-path, this query will throw a `MissingResourceRegistrationError`.
This change ensures that the program behavior is not ambiguous between a non-existent resource and one that was not registered for run-time access;

The Native Image agent does not support custom implementations of `ResourceBundle$Control` or `Bundles$Strategy` and requires manual registrations for the reflection and resource queries that they will perform.

### Transition tools

This mode will be made the default behavior of Native Image in a future release. We encourage you to start transitioning your code as soon as possible.
The [Native Image agent](AutomaticMetadataCollection.md) outputs JSON files that conform to both the default and strict modes of operation.
The following options are useful for debugging issues during the transition to the strict mode:

* `-H:ThrowMissingRegistrationErrors=<package list>`: limits `MissingReflectionRegistrationError` to be thrown from a defined list of packages.
This is useful when using some library code that has not been ported to the new mode yet;
* `-H:MissingRegistrationReportingMode`: sets how `MissingReflectionRegistrationError` is reported:
* `Throw` is the default. The error is simply thrown as a Java exception;
* `Warn` outputs a small stack trace for every error encountered, which results in a report of all the places the tested code is going to throw when the strict mode is enabled;
* `Exit` exits the program when encountering the error. This is useful to detect blanket `catch (Throwable t) {` blocks that would otherwise silence the error.

### Further Reading

* [Metadata Collection with the Tracing Agent](AutomaticMetadataCollection.md)
Expand Down
7 changes: 7 additions & 0 deletions docs/reference-manual/native-image/Reflection.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ Code may also write non-static final fields like `String.value` in this example,
More than one configuration can be used by specifying multiple paths for `ReflectionConfigurationFiles` and separating them with `,`.
Also, `-H:ReflectionConfigurationResources` can be specified to load one or several configuration files from the build's class path, such as from a JAR file.

### Elements and queries registered by default

Querying the methods and constructor of `java.lang.Object` does not require configuration. The Java access rules still apply.
Likewise, when using the [strict metadata mode](#strict-metadata-mode), it is possible to query the public or declared fields, methods and constructors of `java.lang.Object`, primitive classes and array classes without requiring a configuration entry.
These queries return empty arrays in most cases, except for `java.lang.Object` methods and constructors and array public methods (all inherited from `java.lang.Object`). The image size impact of this inclusion is therefore minimal.
On the other hand, it is necessary to register these methods and constructors if they need to be reflectively invoked at run-time, via `Method.invoke()` or `Constructor.newInstance()`.

## Conditional Configuration

With conditional configuration, a class configuration entry is applied only if a provided `condition` is satisfied.
Expand Down
2 changes: 2 additions & 0 deletions substratevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ This changelog summarizes major changes to GraalVM Native Image.
* (GR-39407) Add support for the `NATIVE_IMAGE_OPTIONS` environment variable, which allows users and tools to pass additional arguments via the environment. Similar to `JAVA_TOOL_OPTIONS`, the value of the environment variable is prepended to the options supplied to `native-image`.
* (GR-20827): Introduce a dedicated caller-saved branch target register for software CFI implementations.
* (GR-47937) Make the lambda-class name format in Native-Image consistent with the JDK name format.
* (GR-45651) Methods, fields and constructors of `Object`, primitive classes and array classes are now registered by default for reflection.
* (GR-45651) The Native Image agent now tracks calls to `ClassLoader.findSystemClass`, `ObjectInputStream.resolveClass` and `Bundles.of`, and registers resource bundles as bundle name-locale pairs.

## GraalVM for JDK 21 (Internal Version 23.1.0)
* (GR-35746) Lower the default aligned chunk size from 1 MB to 512 KB for the serial and epsilon GCs, reducing memory usage and image size in many cases.
Expand Down
17 changes: 8 additions & 9 deletions substratevm/mx.substratevm/mx_substratevm_benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@

import os
import re
from glob import glob
import tempfile

import zipfile
from glob import glob

import mx
import mx_benchmark
import mx_sdk_benchmark
import mx_java_benchmarks
import mx_sdk_benchmark
import mx_sdk_vm_impl

_suite = mx.suite("substratevm")
Expand Down Expand Up @@ -316,12 +316,12 @@ def _empty_file():
}

_DACAPO_EXTRA_IMAGE_BUILD_ARGS = {
'h2' : ['--allow-incomplete-classpath'],
'pmd': ['--allow-incomplete-classpath'],
'h2' : [],
'pmd': [],
# org.apache.crimson.parser.Parser2 is force initialized at build-time due to non-determinism in class initialization
# order that can lead to runtime issues. See GR-26324.
'xalan': ['--report-unsupported-elements-at-runtime',
'--initialize-at-build-time=org.apache.crimson.parser.Parser2,org.apache.crimson.parser.Parser2$Catalog,org.apache.crimson.parser.Parser2$NullHandler'],
'--initialize-at-build-time=org.apache.crimson.parser.Parser2,org.apache.crimson.parser.Parser2$Catalog,org.apache.crimson.parser.Parser2$NullHandler,org.apache.xml.utils.res.CharArrayWrapper'],
# There are two main issues with fop:
# 1. LoggingFeature is enabled by default, causing the LogManager configuration to be parsed at build-time. However
# DaCapo Harness sets the `java.util.logging.config.file` property at run-time. Therefore, we set
Expand All @@ -330,11 +330,10 @@ def _empty_file():
# not exist and would fail the benchmark when assertions are enabled.
# 2. Native-image picks a different service provider than the JVM for javax.xml.transform.TransformerFactory.
# We can simply remove the jar containing that provider as it is not required for the benchmark to run.
'fop': ['--allow-incomplete-classpath',
'--report-unsupported-elements-at-runtime',
'fop': ['--report-unsupported-elements-at-runtime',
f"-Djava.util.logging.config.file={_empty_file()}",
'--initialize-at-run-time=org.apache.fop.render.rtf.rtflib.rtfdoc.RtfList'],
'batik': ['--allow-incomplete-classpath']
'batik': []
}

'''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;

import jdk.graal.compiler.core.common.NumUtil;
import org.graalvm.nativeimage.StackValue;
import org.graalvm.nativeimage.UnmanagedMemory;
import org.graalvm.nativeimage.c.function.CEntryPoint;
Expand Down Expand Up @@ -103,6 +102,8 @@
import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiInterface;
import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiLocationFormat;

import jdk.graal.compiler.core.common.NumUtil;

/**
* Intercepts events of interest via breakpoints in Java code.
* <p>
Expand Down Expand Up @@ -679,18 +680,30 @@ private static boolean getBundleImpl(JNIEnvironment jni, JNIObjectHandle thread,
callerMethod = state.getCallerMethod(3);
}
JNIObjectHandle callerClass = getMethodDeclaringClass(callerMethod);
JNIObjectHandle callerModule = getObjectArgument(thread, 0);
JNIObjectHandle module = getObjectArgument(thread, 1);
JNIObjectHandle baseName = getObjectArgument(thread, 2);
JNIObjectHandle locale = getObjectArgument(thread, 3);
JNIObjectHandle control = getObjectArgument(thread, 4);
JNIObjectHandle result = Support.callStaticObjectMethodLLLLL(jni, bp.clazz, bp.method, callerModule, module, baseName, locale, control);
BundleInfo bundleInfo = BundleInfo.NONE;
if (!clearException(jni)) {
bundleInfo = extractBundleInfo(jni, result);
}
traceReflectBreakpoint(jni, nullHandle(), nullHandle(), callerClass, "getBundleImpl", true, state.getFullStackTraceOrNull(),
Tracer.UNKNOWN_VALUE, Tracer.UNKNOWN_VALUE, fromJniString(jni, baseName), Tracer.UNKNOWN_VALUE, Tracer.UNKNOWN_VALUE, bundleInfo.classNames, bundleInfo.locales);
traceReflectBreakpoint(jni, nullHandle(), nullHandle(), callerClass, bp.specification.methodName, true, state.getFullStackTraceOrNull(),
Tracer.UNKNOWN_VALUE, Tracer.UNKNOWN_VALUE, fromJniString(jni, baseName), readLocaleTag(jni, locale), Tracer.UNKNOWN_VALUE);
return true;
}

/*
* Bundles.putBundleInCache is the single point through which all bundles queried through
* sun.util.resources.Bundles go
*/
private static boolean putBundleInCache(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) {
JNIObjectHandle callerClass = state.getDirectCallerClass();
JNIObjectHandle cacheKey = getObjectArgument(thread, 0);
JNIObjectHandle baseName = Support.callObjectMethod(jni, cacheKey, agent.handles().sunUtilResourcesBundlesCacheKeyGetName);
if (clearException(jni)) {
baseName = nullHandle();
}
JNIObjectHandle locale = Support.callObjectMethod(jni, cacheKey, agent.handles().sunUtilResourcesBundlesCacheKeyGetLocale);
if (clearException(jni)) {
locale = nullHandle();
}
traceReflectBreakpoint(jni, nullHandle(), nullHandle(), callerClass, bp.specification.methodName, true, state.getFullStackTraceOrNull(),
Tracer.UNKNOWN_VALUE, Tracer.UNKNOWN_VALUE, fromJniString(jni, baseName), readLocaleTag(jni, locale), Tracer.UNKNOWN_VALUE);
return true;
}

Expand All @@ -703,59 +716,6 @@ private static String readLocaleTag(JNIEnvironment jni, JNIObjectHandle locale)
return fromJniString(jni, languageTag);
}

private static final class BundleInfo {

static final BundleInfo NONE = new BundleInfo(new String[0], new String[0]);

final String[] classNames;
final String[] locales;

BundleInfo(String[] classNames, String[] locales) {
this.classNames = classNames;
this.locales = locales;
}
}

/**
* Traverses the bundle parent chain and collects classnames and locales of all encountered
* bundles.
*
*/
private static BundleInfo extractBundleInfo(JNIEnvironment jni, JNIObjectHandle bundle) {
List<String> locales = new ArrayList<>();
List<String> classNames = new ArrayList<>();
JNIObjectHandle curr = bundle;
while (curr.notEqual(nullHandle())) {
JNIObjectHandle locale = Support.callObjectMethod(jni, curr, agent.handles().getJavaUtilResourceBundleGetLocale(jni));
if (clearException(jni)) {
return BundleInfo.NONE;
}
String localeTag = readLocaleTag(jni, locale);
if (localeTag.equals("und")) {
/*- Root locale is serialized into "und" */
localeTag = "";
}
JNIObjectHandle clazz = Support.callObjectMethod(jni, curr, agent.handles().javaLangObjectGetClass);
if (!clearException(jni)) {
JNIObjectHandle classNameHandle = Support.callObjectMethod(jni, clazz, agent.handles().javaLangClassGetName);
if (!clearException(jni)) {
classNames.add(fromJniString(jni, classNameHandle));
locales.add(localeTag);
}
}
curr = getResourceBundleParent(jni, curr);
}
return new BundleInfo(classNames.toArray(new String[0]), locales.toArray(new String[0]));
}

private static JNIObjectHandle getResourceBundleParent(JNIEnvironment jni, JNIObjectHandle bundle) {
JNIObjectHandle parent = Support.readObjectField(jni, bundle, agent.handles().getJavaUtilResourceBundleParentField(jni));
if (!clearException(jni)) {
return parent;
}
return nullHandle();
}

private static boolean loadClass(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) {
assert experimentalClassLoaderSupport;
/*
Expand Down Expand Up @@ -787,6 +747,13 @@ private static boolean loadClass(JNIEnvironment jni, JNIObjectHandle thread, Bre
return true;
}

private static boolean findSystemClass(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) {
JNIObjectHandle callerClass = state.getDirectCallerClass();
JNIObjectHandle className = getObjectArgument(thread, 1);
traceReflectBreakpoint(jni, bp.clazz, nullHandle(), callerClass, bp.specification.methodName, true, state.getFullStackTraceOrNull(), fromJniString(jni, className));
return true;
}

private static boolean isLoadClassInvocation(JNIObjectHandle clazz, JNIMethodId method, int bci, String methodName, String signature) {
CIntPointer lengthPtr = StackValue.get(CIntPointer.class);
CCharPointerPointer bytecodesPtr = StackValue.get(CCharPointerPointer.class);
Expand Down Expand Up @@ -1012,6 +979,18 @@ private static boolean serializedLambdaReadResolve(JNIEnvironment jni, JNIObject
return true;
}

private static boolean readClassDescriptor(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) {
JNIObjectHandle desc = getObjectArgument(thread, 1);
JNIMethodId descriptor = agent.handles().getJavaIoObjectStreamClassGetName(jni);
var name = Support.callObjectMethod(jni, desc, descriptor);
if (clearException(jni)) {
name = nullHandle();
}
var className = fromJniString(jni, name);
traceSerializeBreakpoint(jni, "ObjectInputStream.readClassDescriptor", true, state.getFullStackTraceOrNull(), className, null);
return true;
}

private static boolean objectStreamClassConstructor(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) {

JNIObjectHandle serializeTargetClass = getObjectArgument(thread, 1);
Expand Down Expand Up @@ -1544,6 +1523,9 @@ private interface BreakpointHandler {
brk("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;I)Ljava/lang/Object;", BreakpointInterceptor::newArrayInstance),
brk("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;[I)Ljava/lang/Object;", BreakpointInterceptor::newArrayInstanceMulti),

brk("java/lang/ClassLoader", "findSystemClass", "(Ljava/lang/String;)Ljava/lang/Class;",
BreakpointInterceptor::findSystemClass),

brk("jdk/internal/loader/BuiltinClassLoader", "findResource", "(Ljava/lang/String;Ljava/lang/String;)Ljava/net/URL;", BreakpointInterceptor::findResource),
brk("jdk/internal/loader/BuiltinClassLoader", "findResourceAsStream", "(Ljava/lang/String;Ljava/lang/String;)Ljava/io/InputStream;", BreakpointInterceptor::findResource),
brk("jdk/internal/loader/Loader", "findResource", "(Ljava/lang/String;Ljava/lang/String;)Ljava/net/URL;", BreakpointInterceptor::findResource),
Expand All @@ -1562,6 +1544,7 @@ private interface BreakpointHandler {
"(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;", BreakpointInterceptor::newProxyInstance),

brk("java/lang/invoke/SerializedLambda", "readResolve", "()Ljava/lang/Object;", BreakpointInterceptor::serializedLambdaReadResolve),
brk("java/io/ObjectInputStream", "resolveClass", "(Ljava/io/ObjectStreamClass;)Ljava/lang/Class;", BreakpointInterceptor::readClassDescriptor),
brk("java/io/ObjectStreamClass", "<init>", "(Ljava/lang/Class;)V", BreakpointInterceptor::objectStreamClassConstructor),
brk("jdk/internal/reflect/ReflectionFactory",
"newConstructorForSerialization",
Expand All @@ -1570,6 +1553,8 @@ private interface BreakpointHandler {
"getBundleImpl",
"(Ljava/lang/Module;Ljava/lang/Module;Ljava/lang/String;Ljava/util/Locale;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle;",
BreakpointInterceptor::getBundleImpl),
brk("sun/util/resources/Bundles", "putBundleInCache", "(Lsun/util/resources/Bundles$CacheKey;Ljava/util/ResourceBundle;)Ljava/util/ResourceBundle;",
BreakpointInterceptor::putBundleInCache),

// In Java 9+, these are Java methods that call private methods
optionalBrk("jdk/internal/misc/Unsafe", "objectFieldOffset", "(Ljava/lang/Class;Ljava/lang/String;)J", BreakpointInterceptor::objectFieldOffsetByName),
Expand Down
Loading