Skip to content

Commit 5b3b8ce

Browse files
committed
Conditional reflection and serialization config
Introducing conditions for reflection, JNI, and serialization configuration. The condition can be only a type name. For example, "condition": { "typeReachable": "org.graalvm.ReachableClass" }, will apply configuration only if type "org.graalvm.ReachableClass" is reachable. The default condition for every reflection entry is "java.lang.Object" and as such it will not be printed. The Native Image agent does not emit conditions, but will fuse conditions accordingly. Instead all config of one type, the fusion will happen by the condition and the type.
1 parent 41863b1 commit 5b3b8ce

File tree

31 files changed

+621
-241
lines changed

31 files changed

+621
-241
lines changed

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

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,15 @@ Here, `reflectconfig` is a JSON file in the following format (use `--expert-opti
107107
{ "name" : "format", "parameterTypes" : ["java.lang.String", "java.lang.Object[]"] }
108108
]
109109
},
110-
{
111-
"name" : "java.lang.String$CaseInsensitiveComparator",
112-
"methods" : [
113-
{ "name" : "compare" }
114-
]
115-
}
110+
{
111+
"name" : "java.lang.String$CaseInsensitiveComparator",
112+
"methods" : [
113+
{ "name" : "compare" }
114+
]
115+
}
116116
]
117117
```
118+
118119
The native image builder generates reflection metadata for all classes, methods, and fields referenced in that file.
119120
The `allPublicConstructors`, `allDeclaredConstructors`, `allPublicMethods`, `allDeclaredMethods`, `allPublicFields`, `allDeclaredFields`, `allPublicClasses`, and `allDeclaredClasses` attributes can be used to automatically include an entire set of members of a class.
120121

@@ -125,6 +126,28 @@ Code may also write non-static final fields like `String.value` in this example,
125126
More than one configuration can be used by specifying multiple paths for `ReflectionConfigurationFiles` and separating them with `,`.
126127
Also, `-H:ReflectionConfigurationResources` can be specified to load one or several configuration files from the native image build's class path, such as from a JAR file.
127128

129+
### Conditional Configuration
130+
131+
With conditional configuraiton, a class configuration entry is applied only if a provided `condition` is satisfied. The only currently supported condition is `typeReachable`, which enables the configuration entry if the specified type is reachable through other code. For example, to support reflective access to `sun.misc.Unsafe.theUnsafe` only when `io.netty.util.internal.PlatformDependent0` is reachable, the configuration should look like:
132+
133+
```json
134+
{
135+
"condition" : { "typeReachable" : "io.netty.util.internal.PlatformDependent0" },
136+
"name" : "sun.misc.Unsafe",
137+
"fields" : [
138+
{ "name" : "theUnsafe" }
139+
]
140+
}
141+
```
142+
143+
Conditional configuration is the *preferred* way to specify reflection configuration: if code doing a reflective access is not reachable, it is unnecessary to include its corresponding reflection entry. The consistent usage of `condition` results in *smaller binaries* and *better build times* as the image builder can selectively include reflectively accessed code.
144+
145+
If a `condition` is omitted, the element is always included. When the same `condition` is used for two distinct elements in two configuration entries, both elements will be included when the condition is satisfied. When a configuration entry should be enabled if one of several types are reachable, it is necessary to add two configuration entries: one entry for each condition.
146+
147+
When used with [assisted configuration](BuildConfiguration.md#assisted-configuration-of-native-image-builds), conditional entries of existing configuration will not be fused with agent-collected entries as agent-collected entries.
148+
149+
### Configuration with Features
150+
128151
Alternatively, a custom `Feature` implementation can register program elements before and during the analysis phase of the native image build using the `RuntimeReflection` class. For example:
129152
```java
130153
class RuntimeReflectionRegistrationFeature implements Feature {

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeReflection.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.graalvm.nativeimage.ImageSingletons;
4949
import org.graalvm.nativeimage.Platform;
5050
import org.graalvm.nativeimage.Platforms;
51+
import org.graalvm.nativeimage.impl.ConfigurationCondition;
5152
import org.graalvm.nativeimage.impl.RuntimeReflectionSupport;
5253

5354
//Checkstyle: allow reflection
@@ -68,7 +69,7 @@ public final class RuntimeReflection {
6869
* @since 19.0
6970
*/
7071
public static void register(Class<?>... classes) {
71-
ImageSingletons.lookup(RuntimeReflectionSupport.class).register(classes);
72+
ImageSingletons.lookup(RuntimeReflectionSupport.class).register(ConfigurationCondition.objectReachable(), classes);
7273
}
7374

7475
/**
@@ -79,7 +80,7 @@ public static void register(Class<?>... classes) {
7980
* @since 19.0
8081
*/
8182
public static void register(Executable... methods) {
82-
ImageSingletons.lookup(RuntimeReflectionSupport.class).register(methods);
83+
ImageSingletons.lookup(RuntimeReflectionSupport.class).register(ConfigurationCondition.objectReachable(), methods);
8384
}
8485

8586
/**
@@ -90,7 +91,7 @@ public static void register(Executable... methods) {
9091
* @since 19.0
9192
*/
9293
public static void register(Field... fields) {
93-
ImageSingletons.lookup(RuntimeReflectionSupport.class).register(false, fields);
94+
ImageSingletons.lookup(RuntimeReflectionSupport.class).register(ConfigurationCondition.objectReachable(), false, fields);
9495
}
9596

9697
/**

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeSerialization.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.graalvm.nativeimage.ImageSingletons;
4444
import org.graalvm.nativeimage.Platform;
4545
import org.graalvm.nativeimage.Platforms;
46+
import org.graalvm.nativeimage.impl.ConfigurationCondition;
4647
import org.graalvm.nativeimage.impl.RuntimeSerializationSupport;
4748

4849
/**
@@ -60,7 +61,7 @@ public final class RuntimeSerialization {
6061
* @since 21.3
6162
*/
6263
public static void register(Class<?>... classes) {
63-
ImageSingletons.lookup(RuntimeSerializationSupport.class).register(classes);
64+
ImageSingletons.lookup(RuntimeSerializationSupport.class).register(ConfigurationCondition.objectReachable(), classes);
6465
}
6566

6667
/**
@@ -75,7 +76,7 @@ public static void register(Class<?>... classes) {
7576
* @since 21.3
7677
*/
7778
public static void registerWithTargetConstructorClass(Class<?> clazz, Class<?> customTargetConstructorClazz) {
78-
ImageSingletons.lookup(RuntimeSerializationSupport.class).registerWithTargetConstructorClass(clazz, customTargetConstructorClazz);
79+
ImageSingletons.lookup(RuntimeSerializationSupport.class).registerWithTargetConstructorClass(ConfigurationCondition.objectReachable(), clazz, customTargetConstructorClazz);
7980
}
8081

8182
private RuntimeSerialization() {
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package org.graalvm.nativeimage.impl;
42+
43+
import java.util.Objects;
44+
45+
public final class ConfigurationCondition implements Comparable<ConfigurationCondition> {
46+
private final String typeName;
47+
private static final ConfigurationCondition OBJECT_REACHABLE = new ConfigurationCondition(Object.class.getTypeName());
48+
49+
public static ConfigurationCondition objectReachable() {
50+
return OBJECT_REACHABLE;
51+
}
52+
53+
public static ConfigurationCondition create(String typeReachability) {
54+
Objects.requireNonNull(typeReachability);
55+
if (OBJECT_REACHABLE.typeName.equals(typeReachability)) {
56+
return OBJECT_REACHABLE;
57+
}
58+
return new ConfigurationCondition(typeReachability);
59+
}
60+
61+
private ConfigurationCondition(String typeName) {
62+
this.typeName = typeName;
63+
}
64+
65+
public String getTypeName() {
66+
return typeName;
67+
}
68+
69+
@Override
70+
public boolean equals(Object o) {
71+
if (this == o) {
72+
return true;
73+
}
74+
if (o == null || getClass() != o.getClass()) {
75+
return false;
76+
}
77+
ConfigurationCondition condition = (ConfigurationCondition) o;
78+
return Objects.equals(typeName, condition.typeName);
79+
}
80+
81+
@Override
82+
public int hashCode() {
83+
return Objects.hash(typeName);
84+
}
85+
86+
@Override
87+
public int compareTo(ConfigurationCondition o) {
88+
return this.typeName.compareTo(o.typeName);
89+
}
90+
}

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@
4444
import java.lang.reflect.Field;
4545

4646
public interface ReflectionRegistry {
47-
void register(Class<?>... classes);
47+
void register(ConfigurationCondition condition, Class<?>... classes);
4848

49-
void register(Executable... methods);
49+
void register(ConfigurationCondition condition, Executable... methods);
5050

51-
void register(boolean finalIsWritable, Field... fields);
51+
void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields);
5252

5353
}

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeSerializationSupport.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@
4242

4343
public interface RuntimeSerializationSupport {
4444

45-
void register(Class<?>... classes);
45+
void register(ConfigurationCondition condition, Class<?>... classes);
4646

47-
void registerWithTargetConstructorClass(Class<?> clazz, Class<?> customTargetConstructorClazz);
47+
void registerWithTargetConstructorClass(ConfigurationCondition condition, Class<?> clazz, Class<?> customTargetConstructorClazz);
48+
49+
void registerWithTargetConstructorClass(ConfigurationCondition condition, String className, String customTargetConstructorClassName);
4850

49-
void registerWithTargetConstructorClass(String className, String customTargetConstructorClassName);
5051
}

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.util.function.Function;
3636
import java.util.function.Predicate;
3737

38+
import org.graalvm.nativeimage.impl.ConfigurationCondition;
3839
import org.junit.Assert;
3940
import org.junit.Test;
4041

@@ -145,8 +146,8 @@ private static void doTestTypeConfig(TypeConfiguration typeConfig) {
145146
}
146147

147148
private static void doTestExpectedMissingTypes(TypeConfiguration typeConfig) {
148-
Assert.assertNull(typeConfig.get("FlagTestA"));
149-
Assert.assertNull(typeConfig.get("FlagTestB"));
149+
Assert.assertNull(typeConfig.get(ConfigurationCondition.objectReachable(), "FlagTestA"));
150+
Assert.assertNull(typeConfig.get(ConfigurationCondition.objectReachable(), "FlagTestB"));
150151
}
151152

152153
private static void doTestTypeFlags(TypeConfiguration typeConfig) {
@@ -194,12 +195,13 @@ private static void doTestResourceConfig(ResourceConfiguration resourceConfig) {
194195
}
195196

196197
private static void doTestSerializationConfig(SerializationConfiguration serializationConfig) {
197-
Assert.assertFalse(serializationConfig.contains("seenType", null));
198-
Assert.assertTrue(serializationConfig.contains("unseenType", null));
198+
ConfigurationCondition condition = ConfigurationCondition.objectReachable();
199+
Assert.assertFalse(serializationConfig.contains(condition, "seenType", null));
200+
Assert.assertTrue(serializationConfig.contains(condition, "unseenType", null));
199201
}
200202

201203
private static ConfigurationType getConfigTypeOrFail(TypeConfiguration typeConfig, String typeName) {
202-
ConfigurationType type = typeConfig.get(typeName);
204+
ConfigurationType type = typeConfig.get(ConfigurationCondition.objectReachable(), typeName);
203205
Assert.assertNotNull(type);
204206
return type;
205207
}
@@ -259,11 +261,11 @@ Map<ConfigurationMethod, ConfigurationMemberKind> getMethodsMap(ConfigurationMem
259261
}
260262

261263
void populateConfig() {
262-
ConfigurationType oldType = new ConfigurationType(getTypeName());
264+
ConfigurationType oldType = new ConfigurationType(ConfigurationCondition.objectReachable(), getTypeName());
263265
setFlags(oldType);
264266
previousConfig.add(oldType);
265267

266-
ConfigurationType newType = new ConfigurationType(getTypeName());
268+
ConfigurationType newType = new ConfigurationType(ConfigurationCondition.objectReachable(), getTypeName());
267269
for (Map.Entry<ConfigurationMethod, ConfigurationMemberKind> methodEntry : methodsThatMustExist.entrySet()) {
268270
newType.addMethod(methodEntry.getKey().getName(), methodEntry.getKey().getInternalSignature(), methodEntry.getValue());
269271
}
@@ -294,7 +296,7 @@ String getTypeName() {
294296

295297
void doTest() {
296298
String name = getTypeName();
297-
ConfigurationType configurationType = currentConfig.get(name);
299+
ConfigurationType configurationType = currentConfig.get(ConfigurationCondition.objectReachable(), name);
298300
if (methodsThatMustExist.size() == 0) {
299301
Assert.assertNull("Generated configuration type " + name + " exists. Expected it to be cleared as it is empty.", configurationType);
300302
} else {

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,14 @@
3131
import java.util.function.BiPredicate;
3232
import java.util.function.Consumer;
3333

34+
import org.graalvm.nativeimage.impl.ConfigurationCondition;
35+
3436
import com.oracle.svm.configure.json.JsonPrintable;
3537
import com.oracle.svm.configure.json.JsonPrinter;
3638
import com.oracle.svm.configure.json.JsonWriter;
3739

3840
public class ConfigurationType implements JsonPrintable {
41+
private final ConfigurationCondition condition;
3942
private final String qualifiedJavaName;
4043

4144
private Map<String, FieldInfo> fields;
@@ -50,18 +53,21 @@ public class ConfigurationType implements JsonPrintable {
5053
private boolean allDeclaredConstructors;
5154
private boolean allPublicConstructors;
5255

53-
public ConfigurationType(String qualifiedJavaName) {
56+
public ConfigurationType(ConfigurationCondition condition, String qualifiedJavaName) {
5457
assert qualifiedJavaName.indexOf('/') == -1 : "Requires qualified Java name, not internal representation";
5558
assert !qualifiedJavaName.startsWith("[") : "Requires Java source array syntax, for example java.lang.String[]";
59+
this.condition = condition;
5660
this.qualifiedJavaName = qualifiedJavaName;
5761
}
5862

5963
public ConfigurationType(ConfigurationType other) {
6064
qualifiedJavaName = other.qualifiedJavaName;
65+
condition = other.condition;
6166
mergeWith(other);
6267
}
6368

6469
public void mergeWith(ConfigurationType other) {
70+
assert condition.equals(other.condition);
6571
assert qualifiedJavaName.equals(other.qualifiedJavaName);
6672
mergeFlagsWith(other);
6773
mergeFieldsWith(other);
@@ -134,6 +140,8 @@ private void maybeRemoveMethods(boolean hasAllDeclaredMethods, boolean hasAllPub
134140
}
135141

136142
public void removeAll(ConfigurationType other) {
143+
assert condition.equals(other.condition);
144+
assert qualifiedJavaName.equals(other.qualifiedJavaName);
137145
removeFlags(other);
138146
removeFields(other);
139147
removeMethods(other);
@@ -317,6 +325,9 @@ public void setAllPublicConstructors() {
317325
@Override
318326
public void printJson(JsonWriter writer) throws IOException {
319327
writer.append('{').indent().newline();
328+
329+
ConfigurationConditionPrintable.printConditionAttribute(condition, writer);
330+
320331
writer.quote("name").append(':').quote(qualifiedJavaName);
321332
optionallyPrintJsonBoolean(writer, haveAllDeclaredFields(), "allDeclaredFields");
322333
optionallyPrintJsonBoolean(writer, haveAllPublicFields(), "allPublicFields");
@@ -337,8 +348,8 @@ public void printJson(JsonWriter writer) throws IOException {
337348
Comparator.comparing(ConfigurationMethod::getName).thenComparing(Comparator.nullsFirst(Comparator.comparing(ConfigurationMethod::getInternalSignature))),
338349
JsonPrintable::printJson);
339350
}
340-
writer.unindent().newline();
341-
writer.append('}');
351+
352+
writer.append('}').unindent().newline();
342353
}
343354

344355
private static void printField(Map.Entry<String, FieldInfo> entry, JsonWriter w) throws IOException {
@@ -373,4 +384,8 @@ private static <T, S> Map<T, S> maybeRemove(Map<T, S> fromMap, Consumer<Map<T, S
373384
}
374385
return map;
375386
}
387+
388+
ConfigurationCondition getCondition() {
389+
return condition;
390+
}
376391
}

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,28 @@
2626

2727
import java.util.List;
2828

29+
import org.graalvm.nativeimage.impl.ConfigurationCondition;
30+
2931
import com.oracle.svm.core.TypeResult;
3032
import com.oracle.svm.core.configure.ReflectionConfigurationParserDelegate;
3133

3234
public class ParserConfigurationAdapter implements ReflectionConfigurationParserDelegate<ConfigurationType> {
3335

3436
private final TypeConfiguration configuration;
3537

36-
public ParserConfigurationAdapter(TypeConfiguration configuration) {
38+
ParserConfigurationAdapter(TypeConfiguration configuration) {
3739
this.configuration = configuration;
3840
}
3941

4042
@Override
41-
public TypeResult<ConfigurationType> resolveTypeResult(String typeName) {
42-
ConfigurationType type = configuration.get(typeName);
43-
ConfigurationType result = type != null ? type : new ConfigurationType(typeName);
43+
public TypeResult<ConfigurationCondition> resolveCondition(String typeName) {
44+
return TypeResult.forType(typeName, ConfigurationCondition.create(typeName));
45+
}
46+
47+
@Override
48+
public TypeResult<ConfigurationType> resolveType(ConfigurationCondition condition, String typeName) {
49+
ConfigurationType type = configuration.get(condition, typeName);
50+
ConfigurationType result = type != null ? type : new ConfigurationType(condition, typeName);
4451
return TypeResult.forType(typeName, result);
4552
}
4653

0 commit comments

Comments
 (0)