Skip to content

Commit c1f00ee

Browse files
committed
Track run-time conditions when creating handler
1 parent 45fb238 commit c1f00ee

File tree

14 files changed

+62
-54
lines changed

14 files changed

+62
-54
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,11 @@ public int hashCode() {
105105
return Objects.hash(type, runtimeChecked);
106106
}
107107

108+
@Override
109+
public String toString() {
110+
return "ConfigurationCondition(" +
111+
"type=" + type +
112+
", runtimeChecked=" + runtimeChecked +
113+
')';
114+
}
108115
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,16 @@ public static final class Options {
125125
@Option(help = "When configuration files do not match their schema, abort the image build instead of emitting a warning.")//
126126
public static final HostedOptionKey<Boolean> StrictConfiguration = new HostedOptionKey<>(false);
127127

128-
@Option(help = "Testing flag: the typeReachable condition is treated as typeReached so the semantics of programs can change.")//
128+
@Option(help = "Testing flag: the 'typeReachable' condition is treated as typeReached so the semantics of programs can change.")//
129129
public static final HostedOptionKey<Boolean> TreatAllTypeReachableConditionsAsTypeReached = new HostedOptionKey<>(false);
130130

131-
@Option(help = "Testing flag: the 'name' is treated as 'type' reflection configuration.")//
131+
@Option(help = "Testing flag: the 'name' is treated as 'type' in reflection configuration.")//
132132
public static final HostedOptionKey<Boolean> TreatAllNameEntriesAsType = new HostedOptionKey<>(false);
133133

134134
@Option(help = "Testing flag: the 'typeReached' condition is always satisfied however it prints the stack trace where it would not be satisfied.")//
135135
public static final HostedOptionKey<Boolean> TrackUnsatisfiedTypeReachedConditions = new HostedOptionKey<>(false);
136136

137-
@Option(help = "Testing flag: print 'typeReached' conditions that are used on interfaces at build time.")//
137+
@Option(help = "Testing flag: print 'typeReached' conditions that are used on interfaces without default methods at build time.")//
138138
public static final HostedOptionKey<Boolean> TrackTypeReachedOnInterfaces = new HostedOptionKey<>(false);
139139

140140
@Option(help = "Testing flag: every type is considered as it participates in a typeReachable condition.")//

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ public ResourceStorageEntryBase getAtRuntime(String name, boolean throwOnMissing
319319
* tried on the same resource name, causing an unexpected exception if we throw directly.
320320
*/
321321
public ResourceStorageEntryBase getAtRuntime(Module module, String resourceName, boolean throwOnMissing) {
322+
VMError.guarantee(ImageInfo.inImageRuntimeCode(), "This function should be used only at runtime.");
322323
String canonicalResourceName = toCanonicalForm(resourceName);
323324
String moduleName = moduleName(module);
324325
ConditionalRuntimeValue<ResourceStorageEntryBase> entry = resources.get(createStorageKey(module, canonicalResourceName));

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationSupport.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ public void prepareBundle(String bundleName, ResourceBundle bundle, Function<Str
162162
Optional<Module> module = findModule.apply(bundleNameWithModule[0]);
163163
String finalResourceName = resourceName;
164164
module.ifPresent(m -> ImageSingletons.lookup(RuntimeResourceSupport.class).addResource(m, finalResourceName));
165-
166165
}
167166
}
168167
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationRegistry.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727

2828
public interface SerializationRegistry {
2929

30+
boolean isRegisteredForSerialization(Class<?> cl);
31+
3032
Object getSerializationConstructorAccessor(Class<?> serializationTargetClass, Class<?> targetConstructorClass);
3133

32-
boolean isRegisteredForSerialization(Class<?> aClass);
3334
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/serialize/SerializationSupport.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@
3131
import java.lang.invoke.SerializedLambda;
3232
import java.lang.reflect.Constructor;
3333
import java.lang.reflect.Modifier;
34-
import java.util.Map;
3534
import java.util.Objects;
36-
import java.util.concurrent.ConcurrentHashMap;
3735

3836
import org.graalvm.collections.EconomicMap;
3937
import org.graalvm.collections.MapCursor;
@@ -157,19 +155,27 @@ public SerializationLookupKey getKeyFromConstructorAccessorClass(Class<?> constr
157155
return null;
158156
}
159157

160-
private final Map<Class<?>, RuntimeConditionSet> classes = new ConcurrentHashMap<>();
161-
private final Map<String, RuntimeConditionSet> lambdaCapturingClasses = new ConcurrentHashMap<>();
158+
private final EconomicMap<Class<?>, RuntimeConditionSet> classes = EconomicMap.create();
159+
private final EconomicMap<String, RuntimeConditionSet> lambdaCapturingClasses = EconomicMap.create();
162160

163161
@Platforms(Platform.HOSTED_ONLY.class)
164162
public void registerSerializationTargetClass(ConfigurationCondition cnd, Class<?> serializationTargetClass) {
165-
classes.computeIfAbsent(serializationTargetClass, k -> RuntimeConditionSet.emptySet())
166-
.addCondition(cnd);
163+
synchronized (classes) {
164+
var previous = classes.putIfAbsent(serializationTargetClass, RuntimeConditionSet.createHosted(cnd));
165+
if (previous != null) {
166+
previous.addCondition(cnd);
167+
}
168+
}
167169
}
168170

169171
@Platforms(Platform.HOSTED_ONLY.class)
170172
public void registerLambdaCapturingClass(ConfigurationCondition cnd, String lambdaCapturingClass) {
171-
lambdaCapturingClasses.computeIfAbsent(lambdaCapturingClass, k -> RuntimeConditionSet.emptySet())
172-
.addCondition(cnd);
173+
synchronized (lambdaCapturingClasses) {
174+
var previousConditions = lambdaCapturingClasses.putIfAbsent(lambdaCapturingClass, RuntimeConditionSet.createHosted(cnd));
175+
if (previousConditions != null) {
176+
previousConditions.addCondition(cnd);
177+
}
178+
}
173179
}
174180

175181
@Platforms(Platform.HOSTED_ONLY.class)
@@ -205,8 +211,8 @@ public Object getSerializationConstructorAccessor(Class<?> rawDeclaringClass, Cl
205211
}
206212

207213
@Override
208-
public boolean isRegisteredForSerialization(Class<?> jClass) {
209-
var conditionSet = classes.get(jClass);
214+
public boolean isRegisteredForSerialization(Class<?> clazz) {
215+
var conditionSet = classes.get(clazz);
210216
return conditionSet != null && conditionSet.satisfied();
211217
}
212218
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,44 +34,52 @@
3434
import org.graalvm.nativeimage.hosted.Feature;
3535
import org.graalvm.nativeimage.impl.ConfigurationCondition;
3636

37+
import com.oracle.svm.core.util.VMError;
3738
import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport;
3839

3940
public abstract class ConditionalConfigurationRegistry {
41+
private Feature.BeforeAnalysisAccess beforeAnalysisAccess;
4042
private final Map<Class<?>, Collection<Runnable>> pendingReachabilityHandlers = new ConcurrentHashMap<>();
4143

4244
protected void registerConditionalConfiguration(ConfigurationCondition condition, Consumer<ConfigurationCondition> consumer) {
4345
Objects.requireNonNull(condition, "Cannot use null value as condition for conditional configuration. Please ensure that you register a non-null condition.");
4446
Objects.requireNonNull(consumer, "Cannot use null value as runnable for conditional configuration. Please ensure that you register a non-null runnable.");
47+
if (condition.isRuntimeChecked() && !condition.isAlwaysTrue()) {
48+
/*
49+
* We do this before the type is reached as the handler runs during analysis when it is
50+
* too late to register types for reached tracking. If the type is never reached, there
51+
* is no damage as subtypes will also never be reached.
52+
*/
53+
ClassInitializationSupport.singleton().addForTypeReachedTracking(condition.getType());
54+
}
4555
if (ConfigurationCondition.alwaysTrue().equals(condition)) {
4656
/* analysis optimization to include new types as early as possible */
4757
consumer.accept(ConfigurationCondition.alwaysTrue());
4858
} else {
49-
Collection<Runnable> handlers = pendingReachabilityHandlers.computeIfAbsent(condition.getType(), key -> new ConcurrentLinkedQueue<>());
5059
ConfigurationCondition runtimeCondition;
5160
if (condition.isRuntimeChecked()) {
52-
ClassInitializationSupport.singleton().addForTypeReachedTracking(condition.getType());
5361
runtimeCondition = condition;
5462
} else {
5563
runtimeCondition = ConfigurationCondition.alwaysTrue();
5664
}
65+
if (beforeAnalysisAccess == null) {
66+
Collection<Runnable> handlers = pendingReachabilityHandlers.computeIfAbsent(condition.getType(), key -> new ConcurrentLinkedQueue<>());
67+
handlers.add(() -> consumer.accept(runtimeCondition));
68+
} else {
69+
beforeAnalysisAccess.registerReachabilityHandler(access -> consumer.accept(runtimeCondition), condition.getType());
70+
}
5771

58-
handlers.add(() -> consumer.accept(runtimeCondition));
5972
}
6073

6174
}
6275

63-
public void flushConditionalConfiguration(Feature.BeforeAnalysisAccess b) {
76+
public void setAnalysisAccess(Feature.BeforeAnalysisAccess beforeAnalysisAccess) {
77+
VMError.guarantee(this.beforeAnalysisAccess == null, "Analysis access can be set only once.");
78+
this.beforeAnalysisAccess = Objects.requireNonNull(beforeAnalysisAccess);
6479
for (Map.Entry<Class<?>, Collection<Runnable>> reachabilityEntry : pendingReachabilityHandlers.entrySet()) {
65-
b.registerReachabilityHandler(access -> reachabilityEntry.getValue().forEach(Runnable::run), reachabilityEntry.getKey());
80+
this.beforeAnalysisAccess.registerReachabilityHandler(access -> reachabilityEntry.getValue().forEach(Runnable::run), reachabilityEntry.getKey());
6681
}
6782
pendingReachabilityHandlers.clear();
6883
}
6984

70-
public void flushConditionalConfiguration(Feature.DuringAnalysisAccess b) {
71-
if (!pendingReachabilityHandlers.isEmpty()) {
72-
b.requireAnalysisIteration();
73-
}
74-
flushConditionalConfiguration((Feature.BeforeAnalysisAccess) b);
75-
}
76-
7785
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ public void addGlob(ConfigurationCondition condition, String module, String glob
195195
globWorkSet.add(new ConditionalPattern(condition, resolvedGlob));
196196
}
197197

198+
@Override
198199
public void addCondition(ConfigurationCondition condition, Module module, String resourcePath) {
199200
var conditionalResource = Resources.singleton().getResourceStorage().get(createStorageKey(module, resourcePath));
200201
if (conditionalResource != null) {
@@ -436,7 +437,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
436437
try {
437438
collector.prepareProgressReporter();
438439
ImageSingletons.lookup(ClassLoaderSupport.class).collectResources(collector);
439-
collector.flushConditionalConfiguration(access);
440+
collector.setAnalysisAccess(access);
440441
} finally {
441442
collector.shutDownProgressReporter();
442443
}
@@ -450,12 +451,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
450451
resourcePatternWorkSet = Set.of();
451452
globWorkSet = Set.of();
452453

453-
resourceRegistryImpl().flushConditionalConfiguration(access);
454-
}
455-
456-
@Override
457-
public void duringAnalysis(DuringAnalysisAccess access) {
458-
resourceRegistryImpl().flushConditionalConfiguration(access);
454+
resourceRegistryImpl().setAnalysisAccess(access);
459455
}
460456

461457
private static final class ResourceCollectorImpl extends ConditionalConfigurationRegistry implements ResourceCollector {

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationSupport.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -495,11 +495,13 @@ private static void addAllInterfaces(Class<?> clazz, EconomicSet<Class<?>> resul
495495

496496
public void addForTypeReachedTracking(Class<?> clazz) {
497497
if (TrackTypeReachedOnInterfaces.getValue() && clazz.isInterface() && !metaAccess.lookupJavaType(clazz).declaresDefaultMethods()) {
498-
LogUtils.info("Detected 'typeReached' on interface type without default methods: " + clazz);
498+
LogUtils.info("Detected 'typeReached' on interface type without default methods: %s", clazz.getName());
499499
}
500500

501501
if (!isAlwaysReached(clazz)) {
502-
UserError.guarantee(!configurationSealed, "It is not possible to register types for reachability tracking after the analysis has started.");
502+
UserError.guarantee(!configurationSealed || typesRequiringReachability.contains(clazz),
503+
"It is not possible to register types for reachability tracking after the analysis has started if they were not registered before analysis started. Trying to register: %s",
504+
clazz.getName());
503505
typesRequiringReachability.add(clazz);
504506
}
505507
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -257,12 +257,7 @@ public void beforeAnalysis(BeforeAnalysisAccess arg) {
257257
registerJavaCallTrampoline(access, variant, true);
258258
}
259259

260-
/* duplicated to reduce the number of analysis iterations */
261-
getConditionalConfigurationRegistry().flushConditionalConfiguration(access);
262-
}
263-
264-
private static ConditionalConfigurationRegistry getConditionalConfigurationRegistry() {
265-
return singleton().runtimeSupport;
260+
singleton().runtimeSupport.setAnalysisAccess(access);
266261
}
267262

268263
private static void registerJavaCallTrampoline(BeforeAnalysisAccessImpl access, CallVariant variant, boolean nonVirtual) {
@@ -319,7 +314,6 @@ private boolean wereElementsAdded() {
319314

320315
@Override
321316
public void duringAnalysis(DuringAnalysisAccess a) {
322-
getConditionalConfigurationRegistry().flushConditionalConfiguration(a);
323317
DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a;
324318
if (!wereElementsAdded()) {
325319
return;

0 commit comments

Comments
 (0)