Skip to content

Commit adfb1fe

Browse files
committed
[GR-32340] Conditional configuration for Native Image.
PullRequest: graal/9388
2 parents 6697022 + 80c9ff8 commit adfb1fe

File tree

32 files changed

+665
-241
lines changed

32 files changed

+665
-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 {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.configure.config;
26+
27+
import static com.oracle.svm.core.configure.ConfigurationParser.CONDITIONAL_KEY;
28+
import static com.oracle.svm.core.configure.ConfigurationParser.TYPE_REACHABLE_KEY;
29+
30+
import java.io.IOException;
31+
32+
import org.graalvm.nativeimage.impl.ConfigurationCondition;
33+
34+
import com.oracle.svm.configure.json.JsonWriter;
35+
36+
final class ConfigurationConditionPrintable {
37+
static void printConditionAttribute(ConfigurationCondition condition, JsonWriter writer) throws IOException {
38+
if (!condition.equals(ConfigurationCondition.objectReachable())) {
39+
writer.quote(CONDITIONAL_KEY).append(":{");
40+
writer.quote(TYPE_REACHABLE_KEY).append(':').quote(condition.getTypeName());
41+
writer.append("},").newline();
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)