Skip to content

Commit eab2a6f

Browse files
committed
[GR-50176] Implemention of the typeReached conditional for reflection.
PullRequest: graal/16125
2 parents 3c7f0c3 + 01c61f0 commit eab2a6f

File tree

37 files changed

+680
-276
lines changed

37 files changed

+680
-276
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -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.RuntimeProxyCreationSupport;
4748

4849
/**
@@ -61,7 +62,7 @@ public final class RuntimeProxyCreation {
6162
* @since 22.3
6263
*/
6364
public static void register(Class<?>... interfaces) {
64-
ImageSingletons.lookup(RuntimeProxyCreationSupport.class).addProxyClass(interfaces);
65+
ImageSingletons.lookup(RuntimeProxyCreationSupport.class).addProxyClass(ConfigurationCondition.alwaysTrue(), interfaces);
6566
}
6667

6768
private RuntimeProxyCreation() {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -41,5 +41,5 @@
4141
package org.graalvm.nativeimage.impl;
4242

4343
public interface RuntimeProxyCreationSupport {
44-
void addProxyClass(Class<?>... interfaces);
44+
void addProxyClass(ConfigurationCondition condition, Class<?>... interfaces);
4545
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ public void printJson(JsonWriter writer) throws IOException {
150150

151151
@Override
152152
public ConfigurationParser createParser() {
153-
return new ReflectionConfigurationParser<>(ConfigurationConditionResolver.identityResolver(), new ParserConfigurationAdapter(this), true, false);
153+
return new ReflectionConfigurationParser<>(ConfigurationConditionResolver.identityResolver(), new ParserConfigurationAdapter(this), true, false, false);
154154
}
155155

156156
@Override

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/RuntimeMetadataDecoderImpl.java

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141

4242
import com.oracle.svm.core.SubstrateUtil;
4343
import com.oracle.svm.core.c.NonmovableArrays;
44+
import com.oracle.svm.core.configure.RuntimeConditionSet;
4445
import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton;
4546
import com.oracle.svm.core.hub.DynamicHub;
4647
import com.oracle.svm.core.reflect.RuntimeMetadataDecoder;
@@ -320,14 +321,16 @@ private static Object decodeField(UnsafeArrayTypeReader buf, Class<?> declaringC
320321
int modifiers = buf.getUVInt();
321322
boolean inHeap = (modifiers & IN_HEAP_FLAG_MASK) != 0;
322323
boolean complete = (modifiers & COMPLETE_FLAG_MASK) != 0;
324+
325+
RuntimeConditionSet conditions = decodeConditions(buf);
323326
if (inHeap) {
324327
Field field = (Field) decodeObject(buf);
325328
if (publicOnly && !Modifier.isPublic(field.getModifiers())) {
326329
/*
327330
* Generate negative copy of the field. Finding a non-public field when looking for
328331
* a public one should not result in a missing registration exception.
329332
*/
330-
return ReflectionObjectFactory.newField(declaringClass, field.getName(), Object.class, field.getModifiers() | NEGATIVE_FLAG_MASK, false,
333+
return ReflectionObjectFactory.newField(conditions, declaringClass, field.getName(), Object.class, field.getModifiers() | NEGATIVE_FLAG_MASK, false,
331334
null, null, ReflectionObjectFactory.FIELD_OFFSET_NONE, null, null);
332335
}
333336
if (reflectOnly) {
@@ -356,7 +359,8 @@ private static Object decodeField(UnsafeArrayTypeReader buf, Class<?> declaringC
356359
if (!reflectOnly) {
357360
return new FieldDescriptor(declaringClass, name);
358361
}
359-
return ReflectionObjectFactory.newField(declaringClass, name, negative ? Object.class : type, modifiers, false, null, null, ReflectionObjectFactory.FIELD_OFFSET_NONE, null, null);
362+
return ReflectionObjectFactory.newField(conditions, declaringClass, name, negative ? Object.class : type, modifiers, false, null, null, ReflectionObjectFactory.FIELD_OFFSET_NONE, null,
363+
null);
360364
}
361365
boolean trustedFinal = buf.getU1() == 1;
362366
String signature = decodeOtherString(buf);
@@ -368,10 +372,15 @@ private static Object decodeField(UnsafeArrayTypeReader buf, Class<?> declaringC
368372
modifiers |= NEGATIVE_FLAG_MASK;
369373
}
370374

371-
Field reflectField = ReflectionObjectFactory.newField(declaringClass, name, type, modifiers, trustedFinal, signature, annotations, offset, deletedReason, typeAnnotations);
375+
Field reflectField = ReflectionObjectFactory.newField(conditions, declaringClass, name, type, modifiers, trustedFinal, signature, annotations, offset, deletedReason, typeAnnotations);
372376
return reflectOnly ? reflectField : new FieldDescriptor(reflectField);
373377
}
374378

379+
private static RuntimeConditionSet decodeConditions(UnsafeArrayTypeReader buf) {
380+
var conditionTypes = decodeArray(buf, Class.class, i -> decodeType(buf));
381+
return RuntimeConditionSet.createDecoded(conditionTypes);
382+
}
383+
375384
/**
376385
* Complete method encoding.
377386
*
@@ -479,6 +488,7 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class<?> decla
479488
int modifiers = buf.getUVInt();
480489
boolean inHeap = (modifiers & IN_HEAP_FLAG_MASK) != 0;
481490
boolean complete = (modifiers & COMPLETE_FLAG_MASK) != 0;
491+
RuntimeConditionSet conditions = decodeConditions(buf);
482492
if (inHeap) {
483493
Executable executable = (Executable) decodeObject(buf);
484494
if (publicOnly && !Modifier.isPublic(executable.getModifiers())) {
@@ -487,10 +497,11 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class<?> decla
487497
* looking for a public one should not result in a missing registration exception.
488498
*/
489499
if (isMethod) {
490-
executable = ReflectionObjectFactory.newMethod(declaringClass, executable.getName(), executable.getParameterTypes(), Object.class, null, modifiers | NEGATIVE_FLAG_MASK,
500+
executable = ReflectionObjectFactory.newMethod(conditions, declaringClass, executable.getName(), executable.getParameterTypes(), Object.class, null, modifiers | NEGATIVE_FLAG_MASK,
491501
null, null, null, null, null, null, null);
492502
} else {
493-
executable = ReflectionObjectFactory.newConstructor(declaringClass, executable.getParameterTypes(), null, modifiers | NEGATIVE_FLAG_MASK, null, null, null, null, null, null);
503+
executable = ReflectionObjectFactory.newConstructor(conditions, declaringClass, executable.getParameterTypes(), null, modifiers | NEGATIVE_FLAG_MASK, null, null, null, null, null,
504+
null);
494505
}
495506
}
496507
if (reflectOnly) {
@@ -532,13 +543,13 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class<?> decla
532543
if (!reflectOnly) {
533544
return new MethodDescriptor(declaringClass, name, (String[]) parameterTypes);
534545
}
535-
return ReflectionObjectFactory.newMethod(declaringClass, name, (Class<?>[]) parameterTypes, negative ? Object.class : returnType, null, modifiers,
546+
return ReflectionObjectFactory.newMethod(conditions, declaringClass, name, (Class<?>[]) parameterTypes, negative ? Object.class : returnType, null, modifiers,
536547
null, null, null, null, null, null, null);
537548
} else {
538549
if (!reflectOnly) {
539550
return new ConstructorDescriptor(declaringClass, (String[]) parameterTypes);
540551
}
541-
return ReflectionObjectFactory.newConstructor(declaringClass, (Class<?>[]) parameterTypes, null, modifiers, null, null, null, null, null, null);
552+
return ReflectionObjectFactory.newConstructor(conditions, declaringClass, (Class<?>[]) parameterTypes, null, modifiers, null, null, null, null, null, null);
542553
}
543554
}
544555
Class<?>[] exceptionTypes = decodeArray(buf, Class.class, (i) -> decodeType(buf));
@@ -555,14 +566,14 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class<?> decla
555566

556567
Target_java_lang_reflect_Executable executable;
557568
if (isMethod) {
558-
Method method = ReflectionObjectFactory.newMethod(declaringClass, name, (Class<?>[]) parameterTypes, returnType, exceptionTypes, modifiers,
569+
Method method = ReflectionObjectFactory.newMethod(conditions, declaringClass, name, (Class<?>[]) parameterTypes, returnType, exceptionTypes, modifiers,
559570
signature, annotations, parameterAnnotations, annotationDefault, accessor, reflectParameters, typeAnnotations);
560571
if (!reflectOnly) {
561572
return new MethodDescriptor(method);
562573
}
563574
executable = SubstrateUtil.cast(method, Target_java_lang_reflect_Executable.class);
564575
} else {
565-
Constructor<?> constructor = ReflectionObjectFactory.newConstructor(declaringClass, (Class<?>[]) parameterTypes, exceptionTypes,
576+
Constructor<?> constructor = ReflectionObjectFactory.newConstructor(conditions, declaringClass, (Class<?>[]) parameterTypes, exceptionTypes,
566577
modifiers, signature, annotations, parameterAnnotations, accessor, reflectParameters, typeAnnotations);
567578
if (!reflectOnly) {
568579
return new ConstructorDescriptor(constructor);

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConditionalRuntimeValue.java

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,9 @@
2424
*/
2525
package com.oracle.svm.core.configure;
2626

27-
import java.util.Set;
28-
import java.util.function.Predicate;
29-
3027
import org.graalvm.nativeimage.Platform;
3128
import org.graalvm.nativeimage.Platforms;
3229

33-
import com.oracle.svm.core.util.VMError;
34-
3530
/**
3631
* A image-heap stored {@link ConditionalRuntimeValue#value} that is guarded by run-time computed
3732
* {@link ConditionalRuntimeValue#conditions}.
@@ -42,20 +37,11 @@
4237
* @param <T> type of the stored value.
4338
*/
4439
public final class ConditionalRuntimeValue<T> {
45-
private final Class<?>[] conditions;
46-
private boolean satisfied;
40+
RuntimeConditionSet conditions;
4741
volatile T value;
4842

49-
@Platforms(Platform.HOSTED_ONLY.class)
50-
public ConditionalRuntimeValue(Set<Class<?>> conditions, T value) {
51-
if (!conditions.isEmpty()) {
52-
this.conditions = conditions.toArray(Class[]::new);
53-
} else {
54-
this.conditions = null;
55-
satisfied = true;
56-
}
57-
58-
VMError.guarantee(conditions.stream().noneMatch(c -> c.equals(Object.class)), "java.lang.Object must not be in conditions as it is always true.");
43+
public ConditionalRuntimeValue(RuntimeConditionSet conditions, T value) {
44+
this.conditions = conditions;
5945
this.value = value;
6046
}
6147

@@ -64,25 +50,20 @@ public T getValueUnconditionally() {
6450
return value;
6551
}
6652

67-
@Platforms(Platform.HOSTED_ONLY.class)
68-
public Set<Class<?>> getConditions() {
69-
return conditions == null ? Set.of() : Set.of(conditions);
53+
public RuntimeConditionSet getConditions() {
54+
return conditions;
7055
}
7156

72-
public T getValue(Predicate<Class<?>> conditionSatisfied) {
73-
if (satisfied) {
57+
public T getValue() {
58+
if (conditions.satisfied()) {
7459
return value;
7560
} else {
76-
for (Class<?> element : conditions) {
77-
if (conditionSatisfied.test(element)) {
78-
satisfied = true;
79-
break;
80-
}
81-
}
82-
if (satisfied) {
83-
return value;
84-
}
61+
return null;
8562
}
86-
return null;
63+
}
64+
65+
@Platforms(Platform.HOSTED_ONLY.class)
66+
public void updateValue(T newValue) {
67+
this.value = newValue;
8768
}
8869
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,20 @@ public static final class Options {
126126
public static final HostedOptionKey<Boolean> StrictConfiguration = new HostedOptionKey<>(false);
127127

128128
@Option(help = "Testing flag: the typeReachable condition is treated as typeReached so the semantics of programs can change.")//
129-
public static final HostedOptionKey<Boolean> TreatAllReachableConditionsAsReached = new HostedOptionKey<>(false);
129+
public static final HostedOptionKey<Boolean> TreatAllTypeReachableConditionsAsTypeReached = new HostedOptionKey<>(false);
130+
131+
@Option(help = "Testing flag: the 'name' is treated as 'type' reflection configuration.")//
132+
public static final HostedOptionKey<Boolean> TreatAllNameEntriesAsType = new HostedOptionKey<>(false);
133+
134+
@Option(help = "Testing flag: the 'typeReached' condition is always satisfied however it prints the stack trace where it would not be satisfied.")//
135+
public static final HostedOptionKey<Boolean> TrackUnsatisfiedTypeReachedConditions = new HostedOptionKey<>(false);
136+
137+
@Option(help = "Testing flag: print 'typeReached' conditions that are used on interfaces at build time.")//
138+
public static final HostedOptionKey<Boolean> TrackTypeReachedOnInterfaces = new HostedOptionKey<>(false);
130139

131140
@Option(help = "Testing flag: every type is considered as it participates in a typeReachable condition.")//
132141
public static final HostedOptionKey<Boolean> TreatAllUserSpaceTypesAsTrackedForTypeReached = new HostedOptionKey<>(false);
142+
133143
@Option(help = "Warn when reflection and JNI configuration files have elements that could not be found on the classpath or modulepath.", type = OptionType.Expert)//
134144
public static final HostedOptionKey<Boolean> WarnAboutMissingReflectionOrJNIMetadataElements = new HostedOptionKey<>(false);
135145
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
*/
2525
package com.oracle.svm.core.configure;
2626

27-
import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TreatAllReachableConditionsAsReached;
27+
import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TreatAllTypeReachableConditionsAsTypeReached;
2828
import static org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition.TYPE_REACHABLE_KEY;
2929
import static org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition.TYPE_REACHED_KEY;
3030

@@ -205,7 +205,7 @@ protected static long asLong(Object value, String propertyName) {
205205
throw new JSONParserException("Invalid long value '" + value + "' for element '" + propertyName + "'");
206206
}
207207

208-
protected UnresolvedConfigurationCondition parseCondition(EconomicMap<String, Object> data) {
208+
protected UnresolvedConfigurationCondition parseCondition(EconomicMap<String, Object> data, boolean runtimeCondition) {
209209
Object conditionData = data.get(CONDITIONAL_KEY);
210210
if (conditionData != null) {
211211
EconomicMap<String, Object> conditionObject = asMap(conditionData, "Attribute 'condition' must be an object");
@@ -214,18 +214,24 @@ protected UnresolvedConfigurationCondition parseCondition(EconomicMap<String, Ob
214214
}
215215

216216
if (conditionObject.containsKey(TYPE_REACHED_KEY)) {
217+
if (!runtimeCondition) {
218+
failOnSchemaError("'" + TYPE_REACHED_KEY + "' condition cannot be used in older schemas. Please migrate the file to the latest schema.");
219+
}
217220
Object object = conditionObject.get(TYPE_REACHED_KEY);
218221
var condition = parseTypeContents(object);
219222
if (condition.isPresent()) {
220223
String className = ((NamedConfigurationTypeDescriptor) condition.get()).name();
221224
return UnresolvedConfigurationCondition.create(className, true);
222225
}
223226
} else if (conditionObject.containsKey(TYPE_REACHABLE_KEY)) {
227+
if (runtimeCondition && !TreatAllTypeReachableConditionsAsTypeReached.getValue()) {
228+
failOnSchemaError("'" + TYPE_REACHABLE_KEY + "' condition can not be used with the latest schema. Please use '" + TYPE_REACHED_KEY + "'.");
229+
}
224230
Object object = conditionObject.get(TYPE_REACHABLE_KEY);
225231
var condition = parseTypeContents(object);
226232
if (condition.isPresent()) {
227233
String className = ((NamedConfigurationTypeDescriptor) condition.get()).name();
228-
return UnresolvedConfigurationCondition.create(className, TreatAllReachableConditionsAsReached.getValue());
234+
return UnresolvedConfigurationCondition.create(className, TreatAllTypeReachableConditionsAsTypeReached.getValue());
229235
}
230236
}
231237
}
@@ -236,21 +242,21 @@ private static JSONParserException failOnSchemaError(String message) {
236242
throw new JSONParserException(message);
237243
}
238244

239-
protected static Optional<ConfigurationTypeDescriptor> parseType(EconomicMap<String, Object> data) {
245+
protected static Optional<ConfigurationTypeDescriptor> parseTypeOrName(EconomicMap<String, Object> data, boolean treatAllNameEntriesAsType) {
240246
Object typeObject = data.get(TYPE_KEY);
241247
Object name = data.get(NAME_KEY);
242248
if (typeObject != null) {
243249
return parseTypeContents(typeObject);
244250
} else if (name != null) {
245-
return Optional.of(new NamedConfigurationTypeDescriptor(asString(name)));
251+
return Optional.of(new NamedConfigurationTypeDescriptor(asString(name), treatAllNameEntriesAsType));
246252
} else {
247253
throw failOnSchemaError("must have type or name specified for an element");
248254
}
249255
}
250256

251257
protected static Optional<ConfigurationTypeDescriptor> parseTypeContents(Object typeObject) {
252258
if (typeObject instanceof String stringValue) {
253-
return Optional.of(new NamedConfigurationTypeDescriptor(stringValue));
259+
return Optional.of(new NamedConfigurationTypeDescriptor(stringValue, true));
254260
} else {
255261
EconomicMap<String, Object> type = asMap(typeObject, "type descriptor should be a string or object");
256262
if (type.containsKey(PROXY_KEY)) {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationTypeDescriptor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,6 @@ static String checkQualifiedJavaName(String javaName) {
7272
assert javaName.indexOf('/') == -1 || javaName.indexOf('/') > javaName.lastIndexOf('.') : "Requires qualified Java name, not internal representation: %s".formatted(javaName);
7373
return canonicalizeTypeName(javaName);
7474
}
75+
76+
boolean definedAsType();
7577
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,20 @@
3030

3131
import com.oracle.svm.core.util.json.JsonWriter;
3232

33-
public record NamedConfigurationTypeDescriptor(String name) implements ConfigurationTypeDescriptor {
33+
public record NamedConfigurationTypeDescriptor(String name, boolean definedAsType) implements ConfigurationTypeDescriptor {
3434

3535
public NamedConfigurationTypeDescriptor(String name) {
36+
this(name, false);
37+
}
38+
39+
public NamedConfigurationTypeDescriptor(String name, boolean definedAsType) {
3640
this.name = ConfigurationTypeDescriptor.checkQualifiedJavaName(name);
41+
this.definedAsType = definedAsType;
42+
}
43+
44+
@Override
45+
public boolean definedAsType() {
46+
return definedAsType;
3747
}
3848

3949
@Override

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ private void parseInterfaceList(C condition, List<?> data) {
9090

9191
private void parseWithConditionalConfig(EconomicMap<String, Object> proxyConfigObject) {
9292
checkAttributes(proxyConfigObject, "proxy descriptor object", Collections.singleton("interfaces"), Collections.singletonList(CONDITIONAL_KEY));
93-
UnresolvedConfigurationCondition condition = parseCondition(proxyConfigObject);
93+
UnresolvedConfigurationCondition condition = parseCondition(proxyConfigObject, false);
9494
TypeResult<C> resolvedCondition = conditionResolver.resolveCondition(condition);
9595
if (resolvedCondition.isPresent()) {
9696
parseInterfaceList(resolvedCondition.get(), asList(proxyConfigObject.get("interfaces"), "The interfaces property must be an array of fully qualified interface names"));

0 commit comments

Comments
 (0)