Skip to content

Commit c3d4e1c

Browse files
cstancuOracleLabsAutomation
authored andcommitted
[GR-80706] Refactor field injection and correct conservative summaries.
PullRequest: graal/25748
2 parents 907c780 + 07aa2ab commit c3d4e1c

17 files changed

Lines changed: 179 additions & 139 deletions

File tree

‎substratevm/docs/architecture/PARTIALLY-OPEN-WORLD-ANALYSIS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ It can result from a precision cutoff or from the need to represent values suppl
8888
**Recovering a finite type approximation** means replacing such an incomplete set with an explicit set that covers all permitted runtime types at that point.
8989
One mechanism uses an **all-instantiated flow**, which collects types registered as instantiated and assignable to a particular bound.
9090
Recovery based solely on that bound requires both a complete assignable-type hierarchy and registration of every type that may occur, including types allocated only by extensions.
91-
A successful checked cast can provide the bound, but does not by itself establish either condition.
91+
A successful checked cast or an explicit guarantee from a value's producer can establish the bound, but does not by itself establish either condition.
9292
An open bound alone cannot justify enumerating all possible types; a particular value can nevertheless have a precise type established by its analyzed allocation.
9393

9494
### Array Bounds

‎substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -115,10 +115,6 @@ default boolean isCallAllowed(PointsToAnalysis bb, AnalysisMethod caller, Analys
115115
default void onFieldAccessed(AnalysisField field) {
116116
}
117117

118-
@SuppressWarnings("unused")
119-
default void injectFieldTypes(AnalysisField aField, List<AnalysisType> customTypes, boolean canBeNull) {
120-
}
121-
122118
@SuppressWarnings("unused")
123119
default void onTypeInstantiated(AnalysisType type) {
124120
}

‎substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java‎

Lines changed: 4 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -267,7 +267,7 @@ public void forceUnsafeUpdate() {
267267

268268
@Override
269269
public void registerAsJNIAccessed(AnalysisField field, boolean writable) {
270-
field.injectDeclaredType();
270+
field.injectConservativeFieldValues();
271271
}
272272

273273
/**
@@ -509,7 +509,7 @@ public AnalysisType addRootClass(AnalysisType type, boolean addFields, boolean a
509509
if (addFields) {
510510
field.registerAsAccessed("field of root class");
511511
}
512-
field.injectDeclaredType();
512+
field.injectConservativeFieldValues();
513513
}
514514
if (type.getSuperclass() != null) {
515515
addRootClass(type.getSuperclass(), addFields, addArrayClass);
@@ -530,7 +530,7 @@ public AnalysisType addRootField(Class<?> clazz, String fieldName) {
530530
@Override
531531
public AnalysisType addRootField(AnalysisField field) {
532532
field.registerAsAccessed((field.isStatic() ? "static" : "instance") + " root field");
533-
field.injectDeclaredType();
533+
field.injectConservativeFieldValues();
534534
return field.getType();
535535
}
536536

@@ -659,58 +659,6 @@ public void onTypeInstantiated(AnalysisType type) {
659659
});
660660
}
661661

662-
/**
663-
* Injects custom types into a reference field's initial flow. This utility is used for:
664-
* <ul>
665-
* <li>root fields - whose state is forced to be that of the declared type</li>
666-
* <li>unsafe accessed fields - whose state is conservatively also the declared type</li>
667-
* <li>lazily computed fields - for which writes are not visible during analysis</li>
668-
* </ul>
669-
* <p>
670-
* For instance fields, injection alone does not propagate state to the sink. This requires a
671-
* receiver-specific field flow created and connected by analyzing a field load or store, or by
672-
* heap scanning. A retained load creates these connections only when its receiver flow contains
673-
* an analyzed object. Once connected, the field flow propagates both existing and future
674-
* initial-flow values to the sink. Static reference fields share their initial and sink flows.
675-
*/
676-
@Override
677-
public void injectFieldTypes(AnalysisField aField, List<AnalysisType> customTypes, boolean canBeNull) {
678-
assert aField.getStorageKind().isObject();
679-
var ptaField = (PointsToAnalysisField) aField;
680-
681-
/* Link the field with all declared types. */
682-
for (AnalysisType type : customTypes) {
683-
if (type.isPrimitive() || type.isWordType()) {
684-
continue;
685-
}
686-
type.getTypeFlow(this, canBeNull).addUse(this, ptaField.getInitialFlow());
687-
688-
if (type.isArray()) {
689-
AnalysisType fieldComponentType = type.getComponentType();
690-
if (!(fieldComponentType.isPrimitive() || fieldComponentType.isWordType())) {
691-
/*
692-
* Write the component type abstract object into the field array elements type
693-
* flow, i.e., the array elements type flow of the abstract object of the field
694-
* declared type.
695-
*
696-
* This is required so that the index loads from this array return all the
697-
* possible objects that can be stored in the array.
698-
*/
699-
TypeFlow<?> elementsFlow = type.getContextInsensitiveAnalysisObject().getArrayElementsFlow(this, true);
700-
fieldComponentType.getTypeFlow(this, false).addUse(this, elementsFlow);
701-
702-
/*
703-
* In the current implementation it is not necessary to do it recursively for
704-
* multidimensional arrays since we don't model individual array elements, so
705-
* from the point of view of the static analysis the field's array elements
706-
* value is non-null (in the case of an n-dimensional array that value is
707-
* another array, n-1 dimensional).
708-
*/
709-
}
710-
}
711-
}
712-
}
713-
714662
public static class ConstantObjectsProfiler {
715663

716664
static final ConcurrentHashMap<AnalysisType, MyInteger> constantTypes = new ConcurrentHashMap<>(ESTIMATED_NUMBER_OF_TYPES);

‎substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldTypeFlow.java‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ public TypeFlow<AnalysisField> copy(PointsToAnalysis bb, MethodFlowsGraph method
7171
@Override
7272
public boolean canSaturate(PointsToAnalysis bb) {
7373
/*
74-
* Fields with a closed declared type don't saturate; when their input saturates they are
75-
* injected their declared type, and they track all its instantiated subtypes.
74+
* Object fields with a closed effective value bound retain finite type information. A
75+
* saturated input is replaced with all instantiated types assignable to that bound.
7676
*/
7777
return !bb.getExtensionPolicy().isTypeClosed(declaredType);
7878
}
@@ -82,12 +82,11 @@ protected void onInputSaturated(PointsToAnalysis bb, TypeFlow<?> input) {
8282
/* The field input, which is usually a field-store, is saturated. */
8383
if (bb.getExtensionPolicy().isTypeClosed(declaredType)) {
8484
/*
85-
* If the field's declared type is closed, conservatively assume that the field state
86-
* can contain any subtype of its declared type or any primitive value for primitive
87-
* fields. Even if a store is not visible to the analysis, i.e., it is from the
88-
* open-world, the complete hierarchy of the field's declared type is visible.
85+
* Recover all instantiated types under the effective bound, or any primitive value.
86+
* The effective bound drops unchecked interface constraints from the declaration.
87+
* A saturated reference input does not establish an additional array-element write.
8988
*/
90-
source.injectDeclaredType();
89+
source.injectFieldValueBound();
9190
} else {
9291
/* Otherwise, propagate saturation stamp through the field flow. */
9392
super.onInputSaturated(bb, input);

‎substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2131,10 +2131,10 @@ protected void processLoadField(ValueNode node, PointsToAnalysisField field, Val
21312131
private void processOpenWorldField(PointsToAnalysisField field) {
21322132
if (!bb.getExtensionPolicy().isFieldClosed(field)) {
21332133
/*
2134-
* Open fields can be written from the open world and may contain state not seen by the
2135-
* analysis. This is equivalent with assuming any of their input is saturated.
2134+
* External writes may supply arrays whose elements are not covered by analyzed stores
2135+
* or heap scanning. Summarize these contents as well as the field references.
21362136
*/
2137-
field.getInitialFlow().onInputSaturated(bb, null);
2137+
field.injectConservativeFieldValues();
21382138
}
21392139
}
21402140

@@ -2143,9 +2143,9 @@ protected void processStoreField(ValueNode node, PointsToAnalysisField field, Va
21432143

21442144
if (!bb.getExtensionPolicy().isFieldClosed(field)) {
21452145
/*
2146-
* Open fields can be written from the open world and may contain state not seen by the
2147-
* analysis. We don't track their writes, instead we eagerly saturate their loads (See
2148-
* processLoadField).
2146+
* Open fields use conservative summaries for external writes. Instead of tracking
2147+
* individual field stores, processLoadField installs these summaries when the field is
2148+
* read.
21492149
*/
21502150
return;
21512151
}

‎substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ private void onStaticFieldRead(AnalysisField field) {
175175
*
176176
* GR-52421: the field state needs to be serialized from the base layer analysis
177177
*/
178-
field.injectDeclaredType();
178+
field.injectConservativeFieldValues();
179179
} else if (isValueAvailable(field, null)) {
180180
JavaConstant fieldValue = readStaticFieldValue(field);
181181
if (fieldValue instanceof ImageHeapConstant imageHeapConstant && field.isFinal()) {

‎substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java‎

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,50 @@ public boolean registerAsWritten(Object reason) {
264264
});
265265
}
266266

267-
public void injectDeclaredType() {
267+
/**
268+
* Supplies conservative field summaries for roots, conservative unsafe writes, and hosted
269+
* values unavailable during analysis.
270+
* <p>
271+
* In points-to analysis, uses the effective field bound after dropping unchecked interface
272+
* constraints and permits null. Closed bounds supply all instantiated assignable types,
273+
* including types instantiated later during analysis. Open bounds require saturation, which may
274+
* lose bound and nullability precision. Finite reference coverage requires every possible value
275+
* type to be registered as instantiated; injection does not perform that registration. Tracked
276+
* primitive fields receive unconstrained values.
277+
* <p>
278+
* Also summarizes immediate reference-array elements, permitting null independently of the
279+
* array reference, including arrays held through an effective Object bound. Further array
280+
* dimensions require separate coverage. Injection does not change write permissions.
281+
* <p>
282+
* Reference values enter the initial flow. Propagation to an instance field's sink requires a
283+
* receiver-specific field flow created and connected by analyzing a field load or store, or by
284+
* heap scanning. A retained load creates these connections only when its receiver flow contains
285+
* an analyzed object. Once connected, both existing and future initial-flow values reach the
286+
* sink. Static reference fields share their initial and sink flows.
287+
*/
288+
public void injectConservativeFieldValues() {
289+
/* By default, nothing to do. */
290+
}
291+
292+
/**
293+
* Summarizes unavailable reference values using bounds and nullability guaranteed by the
294+
* producer. The bounds must cover all summarized non-null values and be assignable to the effective
295+
* field bound. They may supply stricter guarantees, including interface constraints that the
296+
* field declaration does not enforce.
297+
* <p>
298+
* Saturation, type registration, array element summaries, and propagation follow
299+
* {@link #injectConservativeFieldValues()}. {@code canBeNull} applies to field references;
300+
* reference-array elements remain nullable. An empty list contributes null when
301+
* {@code canBeNull} is true, and no values otherwise.
302+
*/
303+
public abstract void injectConservativeFieldValues(List<AnalysisType> customBounds, boolean canBeNull);
304+
305+
/**
306+
* Injects field values without summarizing array contents. Uses the effective bound, nullability,
307+
* and propagation rules of {@link #injectConservativeFieldValues()}. Use when array contents are
308+
* already covered by analyzed stores, heap scanning, or another summary.
309+
*/
310+
public void injectFieldValueBound() {
268311
/* By default, nothing to do. */
269312
}
270313

‎substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/PointsToAnalysisField.java‎

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.oracle.graal.pointsto.flow.FieldTypeFlow;
3434
import com.oracle.graal.pointsto.flow.StoreFieldTypeFlow;
3535
import com.oracle.graal.pointsto.flow.StoreFieldTypeFlow.StoreInstanceFieldTypeFlow;
36+
import com.oracle.graal.pointsto.flow.TypeFlow;
3637
import com.oracle.graal.pointsto.typestate.TypeState;
3738
import com.oracle.graal.pointsto.util.AtomicUtils;
3839

@@ -116,22 +117,72 @@ public FieldTypeFlow getStaticFieldFlow() {
116117
}
117118

118119
@Override
119-
public void injectDeclaredType() {
120-
var bb = getUniverse().getBigbang();
120+
public void injectConservativeFieldValues() {
121+
if (getStorageKind().isObject()) {
122+
injectConservativeFieldValues(List.of(initialFlow.getDeclaredType()), true);
123+
} else {
124+
injectFieldValueBound();
125+
}
126+
}
127+
128+
@Override
129+
public void injectConservativeFieldValues(List<AnalysisType> customBounds, boolean canBeNull) {
130+
assert getStorageKind().isObject();
131+
var bb = (PointsToAnalysis) getUniverse().getBigbang();
132+
var effectiveBound = initialFlow.getDeclaredType();
133+
if (canBeNull) {
134+
initialFlow.addState(bb, TypeState.forNull());
135+
}
136+
for (AnalysisType valueBound : customBounds) {
137+
assert !valueBound.isPrimitive() && !valueBound.isWordType() : valueBound;
138+
assert effectiveBound.isAssignableFrom(valueBound) : "Custom bound must be assignable to the effective field bound: field: " + this +
139+
" | effective bound: " + effectiveBound + " | custom bound: " + valueBound;
140+
injectBoundValues(bb, valueBound, initialFlow, false);
141+
injectArrayContents(bb, valueBound);
142+
}
143+
}
144+
145+
@Override
146+
public void injectFieldValueBound() {
147+
var bb = (PointsToAnalysis) getUniverse().getBigbang();
121148
if (!bb.isSupportedJavaKind(getStorageKind())) {
122-
/*
123-
* If this JavaKind is not tracked by the analysis, there is no point in inserting any
124-
* state into the field.
125-
*/
149+
/* There is no point in inserting state for a kind not tracked by the analysis. */
126150
return;
127151
}
128152
if (getStorageKind().isObject()) {
129-
bb.injectFieldTypes(this, List.of(initialFlow.getDeclaredType()), true);
153+
injectBoundValues(bb, initialFlow.getDeclaredType(), initialFlow, true);
130154
} else {
131155
this.saturatePrimitiveField();
132156
}
133157
}
134158

159+
private static void injectBoundValues(PointsToAnalysis bb, AnalysisType valueBound, TypeFlow<?> flow, boolean canBeNull) {
160+
if (bb.getExtensionPolicy().isTypeClosed(valueBound)) {
161+
// A closed bound has a complete assignable-type hierarchy.
162+
valueBound.getTypeFlow(bb, canBeNull).addUse(bb, flow);
163+
} else {
164+
// An open bound must include values whose runtime types are not yet known.
165+
bb.getPreSaturatedSourceTypeFlow().addUse(bb, flow);
166+
}
167+
}
168+
169+
/**
170+
* Summarizes immediate reference-array contents admitted by the bound, using its array type or
171+
* Object[] for a supertype such as Object, Cloneable, or Serializable. Further dimensions require
172+
* separate coverage (GR-80797).
173+
*/
174+
private static void injectArrayContents(PointsToAnalysis bb, AnalysisType valueBound) {
175+
AnalysisType arrayType = valueBound.isArray() ? valueBound : bb.getObjectType().getArrayClass();
176+
if (!valueBound.isAssignableFrom(arrayType)) {
177+
return;
178+
}
179+
AnalysisType componentType = arrayType.getComponentType();
180+
if (!componentType.isPrimitive() && !componentType.isWordType()) {
181+
var elements = arrayType.getContextInsensitiveAnalysisObject().getArrayElementsFlow(bb, true);
182+
injectBoundValues(bb, componentType, elements, true);
183+
}
184+
}
185+
135186
@Override
136187
public void cleanupAfterAnalysis() {
137188
super.cleanupAfterAnalysis();
@@ -152,13 +203,7 @@ public boolean registerAsUnsafeAccessed(Object reason) {
152203
* classification and the saturation cutoff, so ordinary reads account for unknown
153204
* runtime types.
154205
*/
155-
if (getStorageKind().isObject() && !bb.getExtensionPolicy().isTypeClosed(initialFlow.getDeclaredType())) {
156-
// An open reference bound must account for unknown extension types.
157-
bb.getPreSaturatedSourceTypeFlow().addUse(bb, initialFlow);
158-
} else {
159-
// Retain declared-type injection for closed reference bounds and primitive values.
160-
injectDeclaredType();
161-
}
206+
injectConservativeFieldValues();
162207
}
163208
if (fieldType.getStorageKind().isPrimitive()) {
164209
/*

‎substratevm/src/com.oracle.graal.reachability/src/com/oracle/graal/reachability/ReachabilityAnalysisEngine.java‎

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -28,7 +28,6 @@
2828
import java.util.ArrayDeque;
2929
import java.util.Collections;
3030
import java.util.Deque;
31-
import java.util.List;
3231
import java.util.Set;
3332

3433
import org.graalvm.collections.EconomicSet;
@@ -156,15 +155,6 @@ public AnalysisType addRootField(AnalysisField analysisField) {
156155
return analysisField.getType();
157156
}
158157

159-
@Override
160-
public void injectFieldTypes(AnalysisField aField, List<AnalysisType> customTypes, boolean canBeNull) {
161-
assert aField.getStorageKind().isObject();
162-
aField.registerAsAccessed("@UnknownObjectField annotated field.");
163-
for (AnalysisType declaredType : customTypes) {
164-
declaredType.registerAsReachable("injected field types for unknown annotated field " + aField.format("%H.%n"));
165-
}
166-
}
167-
168158
@Override
169159
public AnalysisMethod addRootMethod(AnalysisMethod m, boolean invokeSpecial, Object reason, MethodVariant.MethodVariantKey... otherRoots) {
170160
assert otherRoots.length == 0 : otherRoots;

0 commit comments

Comments
 (0)