Skip to content

Commit 0b093d7

Browse files
committed
Include JNI reachability metadata with reflection
1 parent 6c2c4a6 commit 0b093d7

28 files changed

+578
-266
lines changed

docs/reference-manual/native-image/ReachabilityMetadata.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,7 @@ The _reachability-metadata.json_ configuration contains a single object with one
131131
{
132132
"reflection":[],
133133
"resources":[],
134-
"bundles":[],
135-
"jni":[]
134+
"bundles":[]
136135
}
137136
```
138137
@@ -376,12 +375,14 @@ jclass clazz = FindClass(env, "jni/accessed/Type");
376375
```
377376
looks up the `jni.accessed.Type` class, which can then be used to instantiate `jni.accessed.Type`, invoke its methods or access its fields.
378377

379-
The metadata entry for the above call can *only* be provided via _reachability-metadata.json_. Specify the `type` entry in the `jni` field:
378+
The metadata entry for the above call can *only* be provided via _reachability-metadata.json_. Specify
379+
the `jniAccessible` field in the `type` entry in the `reflection` section:
380380
```json
381381
{
382-
"jni":[
382+
"reflection": [
383383
{
384-
"type": "jni.accessed.Type"
384+
"type": "jni.accessed.Type",
385+
"jniAccessibleType": true
385386
}
386387
]
387388
}
@@ -393,13 +394,15 @@ To access field values, we need to provide field names:
393394
```json
394395
{
395396
"type": "jni.accessed.Type",
397+
"jniAccessible": true,
396398
"fields": [{"name": "value"}]
397399
}
398400
```
399401
To access all fields one can use the following attributes:
400402
```json
401403
{
402404
"type": "jni.accessed.Type",
405+
"jniAccessible": true,
403406
"allDeclaredFields": true,
404407
"allPublicFields": true
405408
}
@@ -410,6 +413,7 @@ To call Java methods from JNI, we must provide metadata for the method signature
410413
```json
411414
{
412415
"type": "jni.accessed.Type",
416+
"jniAccessible": true,
413417
"methods": [
414418
{"name": "<methodName1>", "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]},
415419
{"name": "<methodName2>", "parameterTypes": ["<param-type1>", "<param-typeI>", "<param-typeN>"]}
@@ -420,6 +424,7 @@ As a convenience, one can allow method invocation for groups of methods by addin
420424
```json
421425
{
422426
"type": "jni.accessed.Type",
427+
"jniAccessible": true,
423428
"allDeclaredConstructors": true,
424429
"allPublicConstructors": true,
425430
"allDeclaredMethods": true,
@@ -429,7 +434,8 @@ As a convenience, one can allow method invocation for groups of methods by addin
429434
`allDeclaredConstructors` and `allDeclaredMethods` allow calls invocations of methods declared on a given type.
430435
`allPublicConstructors` and `allPublicMethods` allow invocations of all public methods defined on a type and all of its supertypes.
431436

432-
To allocate objects of a type with `AllocObject`, the metadata must be stored in the `reflection` section:
437+
To allocate objects of a type with `AllocObject`, the `unsafeAllocated` field must be set, but the `jniAccessible` field
438+
is not required:
433439
```json
434440
{
435441
"reflection": [

docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.1.0.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,11 @@
207207
"title": "Allow objects of this class to be serialized and deserialized",
208208
"type": "boolean",
209209
"default": false
210+
},
211+
"jniAccessible": {
212+
"title": "Register the type, including all registered fields and methods, for runtime JNI access",
213+
"type": "boolean",
214+
"default": false
210215
}
211216
},
212217
"additionalProperties": false

substratevm/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This changelog summarizes major changes to GraalVM Native Image.
1616
1. `run-time-initialized-jdk` shifts away from build-time initialization of the JDK, instead initializing most of it at run time. This transition is gradual, with individual components of the JDK becoming run-time initialized in each release. This process should complete with JDK 29 when this option should not be needed anymore. Unless you store classes from the JDK in the image heap, this option should not affect you. In case this option breaks your build, follow the suggestions in the error messages.
1717
* (GR-63494) Recurring callback support is no longer enabled by default. If this feature is needed, please specify `-H:+SupportRecurringCallback` at image build-time.
1818
* (GR-60209) New syntax for configuration of the [Foreign Function & Memory API](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/ForeignInterface.md)
19+
* (GR-60238) JNI registration is now included as part of the `"reflection"` section of _reachability-metadata.json_ using the `"jniAccessible"` attribute. Registrations performed through the `"jni"` section of _reachability-metadata.json_ and through _jni-config.json_ will still be accepted and parsed correctly.
1920

2021
## GraalVM for JDK 24 (Internal Version 24.2.0)
2122
* (GR-59717) Added `DuringSetupAccess.registerObjectReachabilityHandler` to allow registering a callback that is executed when an object of a specified type is marked as reachable during heap scanning.

substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ public void testSameConfig() {
9797
ConfigurationSet config = loadTraceProcessorFromResourceDirectory(PREVIOUS_CONFIG_DIR_NAME, omittedConfig);
9898
config = config.copyAndSubtract(omittedConfig);
9999

100-
assertTrue(config.getJniConfiguration().isEmpty());
101100
assertTrue(config.getReflectionConfiguration().isEmpty());
102101
assertTrue(config.getProxyConfiguration().isEmpty());
103102
assertTrue(config.getResourceConfiguration().isEmpty());
@@ -112,7 +111,7 @@ public void testConfigDifference() {
112111
config = config.copyAndSubtract(omittedConfig);
113112

114113
doTestGeneratedTypeConfig();
115-
doTestTypeConfig(config.getJniConfiguration());
114+
doTestTypeConfig(config.getReflectionConfiguration());
116115

117116
doTestProxyConfig(config.getProxyConfiguration());
118117

@@ -242,8 +241,8 @@ class TypeMethodsWithFlagsTest {
242241
final Map<ConfigurationMethod, ConfigurationMemberDeclaration> methodsThatMustExist = new HashMap<>();
243242
final Map<ConfigurationMethod, ConfigurationMemberDeclaration> methodsThatMustNotExist = new HashMap<>();
244243

245-
final TypeConfiguration previousConfig = new TypeConfiguration("");
246-
final TypeConfiguration currentConfig = new TypeConfiguration("");
244+
final TypeConfiguration previousConfig = new TypeConfiguration();
245+
final TypeConfiguration currentConfig = new TypeConfiguration();
247246

248247
TypeMethodsWithFlagsTest(ConfigurationMemberDeclaration methodKind) {
249248
this.methodKind = methodKind;

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConditionalConfigurationParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ protected UnresolvedConfigurationCondition parseCondition(EconomicMap<String, Ob
7878
return UnresolvedConfigurationCondition.alwaysTrue();
7979
}
8080

81-
private NamedConfigurationTypeDescriptor checkConditionType(ConfigurationTypeDescriptor type) {
81+
private static NamedConfigurationTypeDescriptor checkConditionType(ConfigurationTypeDescriptor type) {
8282
if (!(type instanceof NamedConfigurationTypeDescriptor)) {
8383
failOnSchemaError("condition should be a fully qualified class name.");
8484
}

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationFile.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public enum ConfigurationFile {
3838
REFLECTION("reflect", REFLECTION_KEY, true, true),
3939
RESOURCES("resource", RESOURCES_KEY, true, true),
4040
SERIALIZATION("serialization", SERIALIZATION_KEY, true, true),
41-
JNI("jni", JNI_KEY, true, true),
41+
JNI("jni", JNI_KEY, false, true),
4242
/* Deprecated metadata categories */
4343
DYNAMIC_PROXY("proxy", null, true, false),
4444
PREDEFINED_CLASSES_NAME("predefined-classes", null, true, false),

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationParserOption.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,10 @@ public enum ConfigurationParserOption {
4747
/**
4848
* Treat the "name" entry in a legacy reflection configuration as a "type" entry.
4949
*/
50-
TREAT_ALL_NAME_ENTRIES_AS_TYPE
50+
TREAT_ALL_NAME_ENTRIES_AS_TYPE,
51+
52+
/**
53+
* Parse the given type configuration file as a JNI configuration.
54+
*/
55+
JNI_PARSER
5156
}

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/LegacyReflectionConfigurationParser.java

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -97,29 +97,30 @@ protected void parseClass(EconomicMap<String, Object> data) {
9797
T clazz = result.get();
9898
delegate.registerType(conditionResult.get(), clazz);
9999

100-
registerIfNotDefault(data, false, clazz, "allDeclaredConstructors", () -> delegate.registerDeclaredConstructors(condition, false, clazz));
101-
registerIfNotDefault(data, false, clazz, "allPublicConstructors", () -> delegate.registerPublicConstructors(condition, false, clazz));
102-
registerIfNotDefault(data, false, clazz, "allDeclaredMethods", () -> delegate.registerDeclaredMethods(condition, false, clazz));
103-
registerIfNotDefault(data, false, clazz, "allPublicMethods", () -> delegate.registerPublicMethods(condition, false, clazz));
104-
registerIfNotDefault(data, false, clazz, "allDeclaredFields", () -> delegate.registerDeclaredFields(condition, false, clazz));
105-
registerIfNotDefault(data, false, clazz, "allPublicFields", () -> delegate.registerPublicFields(condition, false, clazz));
100+
boolean jniAccessible = checkOption(ConfigurationParserOption.JNI_PARSER);
101+
registerIfNotDefault(data, false, clazz, "allDeclaredConstructors", () -> delegate.registerDeclaredConstructors(condition, false, jniAccessible, clazz));
102+
registerIfNotDefault(data, false, clazz, "allPublicConstructors", () -> delegate.registerPublicConstructors(condition, false, jniAccessible, clazz));
103+
registerIfNotDefault(data, false, clazz, "allDeclaredMethods", () -> delegate.registerDeclaredMethods(condition, false, jniAccessible, clazz));
104+
registerIfNotDefault(data, false, clazz, "allPublicMethods", () -> delegate.registerPublicMethods(condition, false, jniAccessible, clazz));
105+
registerIfNotDefault(data, false, clazz, "allDeclaredFields", () -> delegate.registerDeclaredFields(condition, false, jniAccessible, clazz));
106+
registerIfNotDefault(data, false, clazz, "allPublicFields", () -> delegate.registerPublicFields(condition, false, jniAccessible, clazz));
106107
registerIfNotDefault(data, isType, clazz, "allDeclaredClasses", () -> delegate.registerDeclaredClasses(queryCondition, clazz));
107108
registerIfNotDefault(data, isType, clazz, "allRecordComponents", () -> delegate.registerRecordComponents(queryCondition, clazz));
108109
registerIfNotDefault(data, isType, clazz, "allPermittedSubclasses", () -> delegate.registerPermittedSubclasses(queryCondition, clazz));
109110
registerIfNotDefault(data, isType, clazz, "allNestMembers", () -> delegate.registerNestMembers(queryCondition, clazz));
110111
registerIfNotDefault(data, isType, clazz, "allSigners", () -> delegate.registerSigners(queryCondition, clazz));
111112
registerIfNotDefault(data, isType, clazz, "allPublicClasses", () -> delegate.registerPublicClasses(queryCondition, clazz));
112-
registerIfNotDefault(data, isType, clazz, "queryAllDeclaredConstructors", () -> delegate.registerDeclaredConstructors(queryCondition, true, clazz));
113-
registerIfNotDefault(data, isType, clazz, "queryAllPublicConstructors", () -> delegate.registerPublicConstructors(queryCondition, true, clazz));
114-
registerIfNotDefault(data, isType, clazz, "queryAllDeclaredMethods", () -> delegate.registerDeclaredMethods(queryCondition, true, clazz));
115-
registerIfNotDefault(data, isType, clazz, "queryAllPublicMethods", () -> delegate.registerPublicMethods(queryCondition, true, clazz));
113+
registerIfNotDefault(data, isType, clazz, "queryAllDeclaredConstructors", () -> delegate.registerDeclaredConstructors(queryCondition, true, jniAccessible, clazz));
114+
registerIfNotDefault(data, isType, clazz, "queryAllPublicConstructors", () -> delegate.registerPublicConstructors(queryCondition, true, jniAccessible, clazz));
115+
registerIfNotDefault(data, isType, clazz, "queryAllDeclaredMethods", () -> delegate.registerDeclaredMethods(queryCondition, true, jniAccessible, clazz));
116+
registerIfNotDefault(data, isType, clazz, "queryAllPublicMethods", () -> delegate.registerPublicMethods(queryCondition, true, jniAccessible, clazz));
116117
if (isType) {
117118
/*
118119
* Fields cannot be registered as queried only by the user, we register them
119120
* unconditionally if the class is registered via "type".
120121
*/
121-
delegate.registerDeclaredFields(queryCondition, true, clazz);
122-
delegate.registerPublicFields(queryCondition, true, clazz);
122+
delegate.registerDeclaredFields(queryCondition, true, jniAccessible, clazz);
123+
delegate.registerPublicFields(queryCondition, true, jniAccessible, clazz);
123124
}
124125
registerIfNotDefault(data, false, clazz, "unsafeAllocated", () -> delegate.registerUnsafeAllocated(condition, clazz));
125126
MapCursor<String, Object> cursor = data.getEntries();
@@ -129,13 +130,13 @@ protected void parseClass(EconomicMap<String, Object> data) {
129130
try {
130131
switch (name) {
131132
case "methods":
132-
parseMethods(condition, false, asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz);
133+
parseMethods(condition, false, asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz, jniAccessible);
133134
break;
134135
case "queriedMethods":
135-
parseMethods(condition, true, asList(value, "Attribute 'queriedMethods' must be an array of method descriptors"), clazz);
136+
parseMethods(condition, true, asList(value, "Attribute 'queriedMethods' must be an array of method descriptors"), clazz, jniAccessible);
136137
break;
137138
case "fields":
138-
parseFields(condition, asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz);
139+
parseFields(condition, asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz, jniAccessible);
139140
break;
140141
}
141142
} catch (LinkageError e) {

0 commit comments

Comments
 (0)