diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java index 48337db3e49f..89bf0274feb2 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AbstractAnalysisEngine.java @@ -47,6 +47,8 @@ import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.meta.HostedProviders; import com.oracle.graal.pointsto.reports.StatisticsPrinter; +import com.oracle.graal.pointsto.reports.causality.CausalityExport; +import com.oracle.graal.pointsto.reports.causality.events.CausalityEvent; import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.graal.pointsto.util.CompletionExecutor; import com.oracle.graal.pointsto.util.Timer; @@ -328,7 +330,30 @@ protected void schedule(Runnable task) { @Override public final void postTask(CompletionExecutor.DebugContextRunnable task) { - executor.execute(task); + CausalityEvent inheritedCause = CausalityExport.getCause(); + if (inheritedCause == null) { + executor.execute(task); + } else { + // This branch would always be correct, but slower. + executor.execute(new CompletionExecutor.DebugContextRunnable() { + @Override + public void run(DebugContext debug) { + try (var ignored = CausalityExport.setCause(inheritedCause)) { + task.run(debug); + } + } + + @Override + public DebugContext getDebug(OptionValues opts, List factories) { + return task.getDebug(opts, factories); + } + + @Override + public DebugContext.Description getDescription() { + return task.getDescription(); + } + }); + } } public void postTask(final Runnable task) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/causality/events/CausalityEvents.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/causality/events/CausalityEvents.java index ce38df332f31..0b18663bdc98 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/causality/events/CausalityEvents.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/causality/events/CausalityEvents.java @@ -241,6 +241,7 @@ private static EventFactory2 factory(BiFunction JNIRegistration = factory(JNIRegistration::new); public static final EventFactory ReflectionRegistration = factory(ReflectionRegistration::new); public static final EventFactory ReflectionObjectInHeap = factory(ReflectionObjectInHeap::new); + public static final EventFactory DeferredTask = factory(DeferredTask::new); public static final CausalityEvent AutomaticFeatureRegistration = new RootEvent(EventKinds.AutomaticFeatureRegistration); public static final CausalityEvent UserEnabledFeatureRegistration = new RootEvent(EventKinds.UserRequestedFeatureRegistration); public static final CausalityEvent InitialRegistration = new RootEvent(EventKinds.InitialRegistrations); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/causality/events/DeferredTask.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/causality/events/DeferredTask.java new file mode 100644 index 000000000000..71557c9c97a5 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/causality/events/DeferredTask.java @@ -0,0 +1,26 @@ +package com.oracle.graal.pointsto.reports.causality.events; + +import java.util.Objects; + +public final class DeferredTask extends CausalityEvent { + private final Object task; + + public DeferredTask(Object task) { + this.task = task; + } + + @Override + public String toString() { + return Objects.toIdentityString(task) + typeDescriptor().suffix; + } + + @Override + public boolean essential() { + return false; + } + + @Override + public EventKinds typeDescriptor() { + return EventKinds.DeferredTask; + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/causality/events/EventKinds.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/causality/events/EventKinds.java index 7a55bc697bc3..ae18fe2b7bbc 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/causality/events/EventKinds.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/causality/events/EventKinds.java @@ -57,6 +57,8 @@ public enum EventKinds { SubtypeReachableNotificationCallback("Subtype Reachable Callback"), SubtypeReachableNotificationCallbackInvocation("Subtype Reachable Callback Invocation"), + DeferredTask("Deferred Task"), + AutomaticFeatureRegistration("Automatic Feature Registration"), UserRequestedFeatureRegistration("User-Requested Feature Registration"), InitialRegistrations("Initial Registrations"), diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java index f444c716f164..f2bbe079f11f 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java @@ -67,7 +67,7 @@ protected void registerConditionalConfiguration(ConfigurationCondition condition if (beforeAnalysisAccess == null) { Collection handlers = pendingReachabilityHandlers.computeIfAbsent(condition.getType(), key -> new ConcurrentLinkedQueue<>()); CausalityExport.registerEvent(CausalityEvents.ConfigurationCondition.create(condition.getType())); - handlers.add(() -> consumer.accept(runtimeCondition)); + handlers.add(() -> consumer.accept(runtimeCondition)); } else { beforeAnalysisAccess.registerReachabilityHandler(access -> consumer.accept(runtimeCondition), condition.getType()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index 8396bf4b1030..19ad76a40994 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -169,7 +169,9 @@ public void duringSetup(AnalysisMetaAccess analysisMetaAccess, AnalysisUniverse this.metaAccess = analysisMetaAccess; this.universe = analysisUniverse; for (var conditionalTask : pendingConditionalTasks) { - registerConditionalConfiguration(conditionalTask.condition, (cnd) -> universe.getBigbang().postTask(debug -> conditionalTask.task.accept(cnd))); + try (var ignored = CausalityExport.overwriteCause(CausalityEvents.DeferredTask.create(conditionalTask))) { + registerConditionalConfiguration(conditionalTask.condition, (cnd) -> universe.getBigbang().postTask(debug -> conditionalTask.task.accept(cnd))); + } } pendingConditionalTasks.clear(); } @@ -186,7 +188,9 @@ private void runConditionalInAnalysisTask(ConfigurationCondition condition, Cons if (universe != null) { registerConditionalConfiguration(condition, (cnd) -> universe.getBigbang().postTask(debug -> task.accept(cnd))); } else { - pendingConditionalTasks.add(new ConditionalTask(condition, task)); + var conditionalTask = new ConditionalTask(condition, task); + CausalityExport.registerEvent(CausalityEvents.DeferredTask.create(conditionalTask)); + pendingConditionalTasks.add(conditionalTask); VMError.guarantee(universe == null, "There shouldn't be a race condition on Feature.duringSetup."); } } @@ -203,9 +207,8 @@ private boolean isQueryFlagSet(Class clazz, int flag) { @SuppressWarnings("try") public void register(ConfigurationCondition condition, boolean unsafeInstantiated, Class clazz) { Objects.requireNonNull(clazz, () -> nullErrorMessage("class")); - CausalityExport.registerEvent(CausalityEvents.ReflectionRegistration.create(clazz)); runConditionalInAnalysisTask(condition, (cnd) -> { - try (var ignored = CausalityExport.setCause(CausalityEvents.ReflectionRegistration.create(clazz))) { + try (var ignored = CausalityExport.pushCause(CausalityEvents.ReflectionRegistration.create(clazz))) { registerClass(cnd, clazz, unsafeInstantiated, true); } }); @@ -366,9 +369,6 @@ public void registerAllSignersQuery(ConfigurationCondition condition, Class c @Override public void register(ConfigurationCondition condition, boolean queriedOnly, Executable... executables) { requireNonNull(executables, "executable"); - for (Executable executable : executables) { - CausalityExport.registerEvent(CausalityEvents.ReflectionRegistration.create(executable)); - } runConditionalInAnalysisTask(condition, (cnd) -> registerMethods(cnd, queriedOnly, executables)); } @@ -431,9 +431,7 @@ public void registerAllDeclaredConstructorsQuery(ConfigurationCondition conditio @SuppressWarnings("try") private void registerMethods(ConfigurationCondition cnd, boolean queriedOnly, Executable[] reflectExecutables) { for (Executable reflectExecutable : reflectExecutables) { - try (var ignored = CausalityExport.setCause(CausalityEvents.ReflectionRegistration.create(reflectExecutable))) { - registerMethod(cnd, queriedOnly, reflectExecutable); - } + registerMethod(cnd, queriedOnly, reflectExecutable); } } @@ -441,75 +439,76 @@ private void registerMethod(ConfigurationCondition cnd, boolean queriedOnly, Exe if (SubstitutionReflectivityFilter.shouldExclude(reflectExecutable, metaAccess, universe)) { return; } + try (var ignored = CausalityExport.pushCause(CausalityEvents.ReflectionRegistration.create(reflectExecutable))) { + AnalysisMethod analysisMethod = metaAccess.lookupJavaMethod(reflectExecutable); + AnalysisType declaringType = analysisMethod.getDeclaringClass(); + var classMethods = registeredMethods.computeIfAbsent(declaringType, t -> new ConcurrentHashMap<>()); + var shouldRegisterReachabilityHandler = classMethods.isEmpty(); - AnalysisMethod analysisMethod = metaAccess.lookupJavaMethod(reflectExecutable); - AnalysisType declaringType = analysisMethod.getDeclaringClass(); - var classMethods = registeredMethods.computeIfAbsent(declaringType, t -> new ConcurrentHashMap<>()); - var shouldRegisterReachabilityHandler = classMethods.isEmpty(); - - boolean registered = false; - ConditionalRuntimeValue conditionalValue = classMethods.get(analysisMethod); - if (conditionalValue == null) { - var newConditionalValue = new ConditionalRuntimeValue<>(RuntimeConditionSet.emptySet(), reflectExecutable); - conditionalValue = classMethods.putIfAbsent(analysisMethod, newConditionalValue); + boolean registered = false; + ConditionalRuntimeValue conditionalValue = classMethods.get(analysisMethod); if (conditionalValue == null) { - conditionalValue = newConditionalValue; - registered = true; + var newConditionalValue = new ConditionalRuntimeValue<>(RuntimeConditionSet.emptySet(), reflectExecutable); + conditionalValue = classMethods.putIfAbsent(analysisMethod, newConditionalValue); + if (conditionalValue == null) { + conditionalValue = newConditionalValue; + registered = true; + } + } + if (!queriedOnly) { + /* queryOnly methods are conditioned by the type itself */ + conditionalValue.getConditions().addCondition(cnd); } - } - if (!queriedOnly) { - /* queryOnly methods are conditioned by the type itself */ - conditionalValue.getConditions().addCondition(cnd); - } - if (registered) { - registerTypesForMethod(analysisMethod, reflectExecutable); - Class declaringClass = declaringType.getJavaClass(); + if (registered) { + registerTypesForMethod(analysisMethod, reflectExecutable); + Class declaringClass = declaringType.getJavaClass(); - /* - * The image needs to know about subtypes shadowing methods registered for reflection to - * ensure the correctness of run-time reflection queries. - */ - if (shouldRegisterReachabilityHandler) { - analysisAccess.registerSubtypeReachabilityHandler( - (access, subType) -> universe.getBigbang() - .postTask(debug -> checkSubtypeForOverridingMethods(metaAccess.lookupJavaType(subType), registeredMethods.get(declaringType).keySet())), - declaringClass); - } else { /* - * We need to perform the check for already reachable subtypes since the - * reachability handler was already called for them. + * The image needs to know about subtypes shadowing methods registered for reflection to + * ensure the correctness of run-time reflection queries. */ - for (AnalysisType subtype : AnalysisUniverse.reachableSubtypes(declaringType)) { - universe.getBigbang().postTask(debug -> checkSubtypeForOverridingMethods(subtype, Collections.singleton(analysisMethod))); + if (shouldRegisterReachabilityHandler) { + analysisAccess.registerSubtypeReachabilityHandler( + (access, subType) -> universe.getBigbang() + .postTask(debug -> checkSubtypeForOverridingMethods(metaAccess.lookupJavaType(subType), registeredMethods.get(declaringType).keySet())), + declaringClass); + } else { + /* + * We need to perform the check for already reachable subtypes since the + * reachability handler was already called for them. + */ + for (AnalysisType subtype : AnalysisUniverse.reachableSubtypes(declaringType)) { + universe.getBigbang().postTask(debug -> checkSubtypeForOverridingMethods(subtype, Collections.singleton(analysisMethod))); + } } - } - if (declaringType.isAnnotation() && !analysisMethod.isConstructor()) { - processAnnotationMethod(queriedOnly, (Method) reflectExecutable); + if (declaringType.isAnnotation() && !analysisMethod.isConstructor()) { + processAnnotationMethod(queriedOnly, (Method) reflectExecutable); + } + + if (!throwMissingRegistrationErrors() && declaringClass.isRecord()) { + pendingRecordClasses.computeIfPresent(declaringClass, (clazz, unregisteredAccessors) -> { + if (unregisteredAccessors.remove(reflectExecutable) && unregisteredAccessors.isEmpty()) { + registerRecordComponents(declaringClass); + } + return unregisteredAccessors; + }); + } } - if (!throwMissingRegistrationErrors() && declaringClass.isRecord()) { - pendingRecordClasses.computeIfPresent(declaringClass, (clazz, unregisteredAccessors) -> { - if (unregisteredAccessors.remove(reflectExecutable) && unregisteredAccessors.isEmpty()) { - registerRecordComponents(declaringClass); - } - return unregisteredAccessors; + /* + * We need to run this even if the method has already been registered, in case it was only + * registered as queried. + */ + if (!queriedOnly) { + methodAccessors.computeIfAbsent(analysisMethod, aMethod -> { + SubstrateAccessor accessor = ImageSingletons.lookup(ReflectionFeature.class).getOrCreateAccessor(reflectExecutable); + universe.getHeapScanner().rescanObject(accessor); + return accessor; }); } } - - /* - * We need to run this even if the method has already been registered, in case it was only - * registered as queried. - */ - if (!queriedOnly) { - methodAccessors.computeIfAbsent(analysisMethod, aMethod -> { - SubstrateAccessor accessor = ImageSingletons.lookup(ReflectionFeature.class).getOrCreateAccessor(reflectExecutable); - universe.getHeapScanner().rescanObject(accessor); - return accessor; - }); - } } @Override @@ -541,9 +540,6 @@ public void registerConstructorLookup(ConfigurationCondition condition, Class @Override public void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields) { requireNonNull(fields, "field"); - for (Field field : fields) { - CausalityExport.registerEvent(CausalityEvents.ReflectionRegistration.create(field)); - } runConditionalInAnalysisTask(condition, (cnd) -> registerFields(cnd, false, fields)); } @@ -585,9 +581,7 @@ public void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, boo @SuppressWarnings("try") private void registerFields(ConfigurationCondition cnd, boolean queriedOnly, Field[] reflectFields) { for (Field reflectField : reflectFields) { - try (var ignored = CausalityExport.setCause(CausalityEvents.ReflectionRegistration.create(reflectField))) { - registerField(cnd, queriedOnly, reflectField); - } + registerField(cnd, queriedOnly, reflectField); } } @@ -595,53 +589,54 @@ private void registerField(ConfigurationCondition cnd, boolean queriedOnly, Fiel if (SubstitutionReflectivityFilter.shouldExclude(reflectField, metaAccess, universe)) { return; } + try (var ignored = CausalityExport.pushCause(CausalityEvents.ReflectionRegistration.create(reflectField))) { + AnalysisField analysisField = metaAccess.lookupJavaField(reflectField); + AnalysisType declaringClass = analysisField.getDeclaringClass(); + + var classFields = registeredFields.computeIfAbsent(declaringClass, t -> new ConcurrentHashMap<>()); + boolean exists = classFields.containsKey(analysisField); + boolean shouldRegisterReachabilityHandler = classFields.isEmpty(); + var cndValue = classFields.computeIfAbsent(analysisField, f -> new ConditionalRuntimeValue<>(RuntimeConditionSet.emptySet(), reflectField)); + if (!queriedOnly) { + /* queryOnly methods are conditioned by the type itself */ + cndValue.getConditions().addCondition(cnd); + } - AnalysisField analysisField = metaAccess.lookupJavaField(reflectField); - AnalysisType declaringClass = analysisField.getDeclaringClass(); - - var classFields = registeredFields.computeIfAbsent(declaringClass, t -> new ConcurrentHashMap<>()); - boolean exists = classFields.containsKey(analysisField); - boolean shouldRegisterReachabilityHandler = classFields.isEmpty(); - var cndValue = classFields.computeIfAbsent(analysisField, f -> new ConditionalRuntimeValue<>(RuntimeConditionSet.emptySet(), reflectField)); - if (!queriedOnly) { - /* queryOnly methods are conditioned by the type itself */ - cndValue.getConditions().addCondition(cnd); - } - - if (!exists) { - registerTypesForField(analysisField, reflectField, true); + if (!exists) { + registerTypesForField(analysisField, reflectField, true); - /* - * The image needs to know about subtypes shadowing fields registered for reflection to - * ensure the correctness of run-time reflection queries. - */ - if (shouldRegisterReachabilityHandler) { - analysisAccess.registerSubtypeReachabilityHandler( - (access, subType) -> universe.getBigbang() - .postTask(debug -> checkSubtypeForOverridingFields(metaAccess.lookupJavaType(subType), - registeredFields.get(declaringClass).keySet())), - declaringClass.getJavaClass()); - } else { /* - * We need to perform the check for already reachable subtypes since the - * reachability handler was already called for them. + * The image needs to know about subtypes shadowing fields registered for reflection to + * ensure the correctness of run-time reflection queries. */ - for (AnalysisType subtype : AnalysisUniverse.reachableSubtypes(declaringClass)) { - universe.getBigbang().postTask(debug -> checkSubtypeForOverridingFields(subtype, Collections.singleton(analysisField))); + if (shouldRegisterReachabilityHandler) { + analysisAccess.registerSubtypeReachabilityHandler( + (access, subType) -> universe.getBigbang() + .postTask(debug -> checkSubtypeForOverridingFields(metaAccess.lookupJavaType(subType), + registeredFields.get(declaringClass).keySet())), + declaringClass.getJavaClass()); + } else { + /* + * We need to perform the check for already reachable subtypes since the + * reachability handler was already called for them. + */ + for (AnalysisType subtype : AnalysisUniverse.reachableSubtypes(declaringClass)) { + universe.getBigbang().postTask(debug -> checkSubtypeForOverridingFields(subtype, Collections.singleton(analysisField))); + } } - } - if (declaringClass.isAnnotation()) { - processAnnotationField(cnd, reflectField); + if (declaringClass.isAnnotation()) { + processAnnotationField(cnd, reflectField); + } } - } - /* - * We need to run this even if the method has already been registered, in case it was only - * registered as queried. - */ - if (!queriedOnly) { - registerTypesForField(analysisField, reflectField, false); + /* + * We need to run this even if the method has already been registered, in case it was only + * registered as queried. + */ + if (!queriedOnly) { + registerTypesForField(analysisField, reflectField, false); + } } }