Skip to content

[GR-61426] Ensure that when a reachability handler callback is executed a DuringAnalysisAccess object is available. #11227

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -130,33 +130,36 @@ public void scanEmbeddedRoot(JavaConstant root, BytecodePosition position) {
public void onFieldRead(AnalysisField field) {
assert field.isRead() : field;
/* Check if the value is available before accessing it. */
AnalysisType declaringClass = field.getDeclaringClass();
if (field.isStatic()) {
FieldScan reason = new FieldScan(field);
if (!field.installableInLayer()) {
/*
* For non-installable static fields we do not scan the constant value, but instead
* inject its type state in the field flow. This will be propagated to any
* corresponding field loads.
*
* GR-52421: the field state needs to be serialized from the base layer analysis
*/
if (field.getStorageKind().isObject()) {
bb.injectFieldTypes(field, List.of(field.getType()), true);
} else if (bb.trackPrimitiveValues() && field.getStorageKind().isPrimitive()) {
((PointsToAnalysisField) field).saturatePrimitiveField();
}
} else if (isValueAvailable(field)) {
JavaConstant fieldValue = readStaticFieldValue(field);
if (fieldValue instanceof ImageHeapConstant imageHeapConstant && field.isFinal()) {
AnalysisError.guarantee(imageHeapConstant.getOrigin() != null, "The origin of the constant %s should have been registered before", imageHeapConstant);
}
markReachable(fieldValue, reason);
notifyAnalysis(field, null, fieldValue, reason);
}
postTask(() -> onStaticFieldRead(field));
} else {
/* Trigger field scanning for the already processed objects. */
postTask(() -> onInstanceFieldRead(field, declaringClass));
postTask(() -> onInstanceFieldRead(field, field.getDeclaringClass()));
}
}

private void onStaticFieldRead(AnalysisField field) {
FieldScan reason = new FieldScan(field);
if (!field.installableInLayer()) {
/*
* For non-installable static fields we do not scan the constant value, but instead
* inject its type state in the field flow. This will be propagated to any corresponding
* field loads.
*
* GR-52421: the field state needs to be serialized from the base layer analysis
*/
if (field.getStorageKind().isObject()) {
bb.injectFieldTypes(field, List.of(field.getType()), true);
} else if (bb.trackPrimitiveValues() && field.getStorageKind().isPrimitive()) {
((PointsToAnalysisField) field).saturatePrimitiveField();
}
} else if (isValueAvailable(field)) {
JavaConstant fieldValue = readStaticFieldValue(field);
if (fieldValue instanceof ImageHeapConstant imageHeapConstant && field.isFinal()) {
AnalysisError.guarantee(imageHeapConstant.getOrigin() != null, "The origin of the constant %s should have been registered before", imageHeapConstant);
}
markReachable(fieldValue, reason);
notifyAnalysis(field, null, fieldValue, reason);
}
}

Expand Down Expand Up @@ -605,7 +608,7 @@ protected void onObjectReachable(ImageHeapConstant imageHeapConstant, ScanReason
* conditions, e.g., a started Thread should never be added to the image heap, but
* the structure of the object is valid, as ensured by the validity check above.
*/
objectType.notifyObjectReachable(universe.getConcurrentAnalysisAccess(), object, reason);
objectType.notifyObjectReachable(object, reason);
} catch (UnsupportedFeatureException e) {
/* Enhance the unsupported feature message with the object trace and rethrow. */
StringBuilder backtrace = new StringBuilder();
Expand Down Expand Up @@ -890,9 +893,10 @@ protected AnalysisField lookupJavaField(String className, String fieldName) {
*
* In the (legacy) Feature.duringAnalysis state, the executor is not running and we must not
* schedule new tasks, because that would be treated as "the analysis has not finished yet". So
* in that case we execute the task directly.
* in that case we execute the task directly. A task that runs in the Feature.duringAnalysis
* stage and modifies the analysis state should itself trigger an additional analysis iteration.
*/
private void maybeRunInExecutor(CompletionExecutor.DebugContextRunnable task) {
protected void maybeRunInExecutor(CompletionExecutor.DebugContextRunnable task) {
if (bb.executorIsStarted()) {
bb.postTask(task);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -517,9 +517,9 @@ public JavaConstant getConstantValue() {
}

/**
* Ensure that all reachability handler that were present at the time the declaring type was
* marked as reachable are executed before accessing field values. This allows field value
* transformer to be installed reliably in reachability handler.
* Ensure that all reachability handlers that were present at the time the declaring type was
* marked as reachable are executed before accessing field values. This allows a field value
* transformer to be installed reliably in a reachability handler.
*/
public void beforeFieldValueAccess() {
declaringClass.registerAsReachable(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -700,8 +700,9 @@ public <T> void registerObjectReachableCallback(ObjectReachableCallback<T> callb
});
}

public <T> void notifyObjectReachable(DuringAnalysisAccess access, T object, ScanReason reason) {
ConcurrentLightHashSet.forEach(this, objectReachableCallbacksUpdater, (ObjectReachableCallback<T> c) -> c.doCallback(access, object, reason));
public <T> void notifyObjectReachable(T object, ScanReason reason) {
ConcurrentLightHashSet.forEach(this, objectReachableCallbacksUpdater,
(ObjectReachableCallback<T> c) -> c.doCallback(universe.getConcurrentAnalysisAccess(), object, reason));
}

public void registerInstantiatedCallback(Consumer<DuringAnalysisAccess> callback) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,9 @@ public void setConcurrentAnalysisAccess(DuringAnalysisAccess access) {
}

public DuringAnalysisAccess getConcurrentAnalysisAccess() {
AnalysisError.guarantee(concurrentAnalysisAccess != null, "The requested DuringAnalysisAccess object is not available. " +
"This means that an analysis task is executed too eagerly, before analysis. " +
"Make sure that all analysis tasks are posted to the analysis execution engine.");
return concurrentAnalysisAccess;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public final class BuildPhaseProvider {

private boolean featureRegistrationFinished;
private boolean setupFinished;
private boolean analysisStarted;
private boolean analysisFinished;
private boolean hostedUniverseBuilt;
private boolean readyForCompilation;
Expand Down Expand Up @@ -69,6 +70,14 @@ public static boolean isSetupFinished() {
return ImageSingletons.contains(BuildPhaseProvider.class) && singleton().setupFinished;
}

public static void markAnalysisStarted() {
singleton().analysisStarted = true;
}

public static boolean isAnalysisStarted() {
return ImageSingletons.contains(BuildPhaseProvider.class) && singleton().analysisStarted;
}

public static void markAnalysisFinished() {
singleton().analysisFinished = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,13 @@ protected void createAbstractImage(NativeImageKind k, List<HostedMethod> hostedE
@SuppressWarnings("try")
protected boolean runPointsToAnalysis(String imageName, OptionValues options, DebugContext debug) {
try (Indent ignored = debug.logAndIndent("run analysis")) {
/*
* Set the ConcurrentAnalysisAccessImpl before Feature.beforeAnalysis is executed. Some
* features may already execute some pre-analysis tasks, e.g., reading a hosted field
* value, that can trigger reachability callbacks.
*/
ConcurrentAnalysisAccessImpl concurrentConfig = new ConcurrentAnalysisAccessImpl(featureHandler, loader, bb, nativeLibraries, debug);
aUniverse.setConcurrentAnalysisAccess(concurrentConfig);
try (Indent ignored1 = debug.logAndIndent("process analysis initializers")) {
BeforeAnalysisAccessImpl config = new BeforeAnalysisAccessImpl(featureHandler, loader, bb, nativeLibraries, debug);
ServiceCatalogSupport.singleton().enableServiceCatalogMapTransformer(config);
Expand All @@ -828,8 +835,6 @@ protected boolean runPointsToAnalysis(String imageName, OptionValues options, De
try (ReporterClosable c = ProgressReporter.singleton().printAnalysis(bb.getUniverse(), nativeLibraries.getLibraries())) {
DuringAnalysisAccessImpl config = new DuringAnalysisAccessImpl(featureHandler, loader, bb, nativeLibraries, debug);
try {
ConcurrentAnalysisAccessImpl concurrentConfig = new ConcurrentAnalysisAccessImpl(featureHandler, loader, bb, nativeLibraries, debug);
aUniverse.setConcurrentAnalysisAccess(concurrentConfig);
if (ImageLayerBuildingSupport.buildingExtensionLayer()) {
/*
* All the field value transformers should be installed by this point.
Expand All @@ -838,6 +843,8 @@ protected boolean runPointsToAnalysis(String imageName, OptionValues options, De
*/
HostedImageLayerBuildingSupport.singleton().getLoader().relinkTransformedStaticFinalFieldValues();
}
/* All pre-analysis set-up is done and the fixed-point analysis can start. */
BuildPhaseProvider.markAnalysisStarted();
bb.runAnalysis(debug, (universe) -> {
try (StopTimer t2 = TimerCollection.createTimerAndStart(TimerCollection.Registry.FEATURES)) {
bb.getHostVM().notifyClassReachabilityListener(universe, config);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.svm.hosted;

import org.graalvm.nativeimage.hosted.Feature;

import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
import com.oracle.svm.core.feature.InternalFeature;
import com.oracle.svm.core.util.VMError;

/**
* Ensure that a {@link org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess} object is
* available when a reachability callback is executed for any of the reachable types.
*/
@AutomaticallyRegisteredFeature
public class ReachabilityHandlerAssertFeature implements InternalFeature {
@Override
public boolean isInConfiguration(IsInConfigurationAccess access) {
return SubstrateUtil.assertionsEnabled();
}

@Override
public void beforeAnalysis(Feature.BeforeAnalysisAccess access) {
access.registerSubtypeReachabilityHandler(this::onTypeReachable, Object.class);
}

private void onTypeReachable(DuringAnalysisAccess access, Class<?> cls) {
VMError.guarantee(access != null, "DuringAnalysisAccess object is not available when processing class %s.", cls.getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public AnnotationSubstitutionProcessor getAnnotationSubstitutionProcessor() {

@Override
public void onFieldAccessed(AnalysisField field) {
customTypeFieldHandler.handleField(field);
postTask(() -> customTypeFieldHandler.handleField(field));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import com.oracle.graal.pointsto.heap.ImageHeapScanner;
import com.oracle.graal.pointsto.meta.AnalysisField;
import com.oracle.graal.pointsto.meta.AnalysisMetaAccess;
import com.oracle.graal.pointsto.util.CompletionExecutor;
import com.oracle.svm.core.BuildPhaseProvider;
import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.hosted.ImageClassLoader;
import com.oracle.svm.hosted.ameta.AnalysisConstantReflectionProvider;
Expand Down Expand Up @@ -132,4 +134,17 @@ protected void onObjectReachable(ImageHeapConstant imageHeapConstant, ScanReason
reflectionSupport.registerHeapDynamicHub(hub, reason);
}
}

@Override
protected void maybeRunInExecutor(CompletionExecutor.DebugContextRunnable task) {
if (BuildPhaseProvider.isAnalysisStarted()) {
super.maybeRunInExecutor(task);
} else {
/*
* Before the analysis is started post all scanning tasks to the executor. They will be
* executed after the analysis starts.
*/
bb.postTask(task);
}
}
}