Skip to content

Commit 85a8ff5

Browse files
cstanculiufengyun
authored andcommitted
Reachability trace prototype.
- Make line separator system-ignorant (Thanks Fabio) - Rename -H:ThrowOnXXXReachable to -H:AbortOnXXXReachable (Thanks Doug) - Don't hardcode option name and rename Diagnosis Report to Reachability Diagnosis Report (Thanks Fabio)
1 parent 9c893ed commit 85a8ff5

File tree

16 files changed

+700
-88
lines changed

16 files changed

+700
-88
lines changed

docs/reference-manual/native-image/StaticAnalysisReports.md

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ The points-to analysis produces two kinds of reports: an analysis call tree and
1212
This information is produced by an intermediate step in the building process and represents the static analysis view of the call graph and heap object graph.
1313
These graphs are further transformed in the building process before they are compiled ahead-of-time into the binary and written into the binary heap, respectively.
1414

15+
In addition to the comprehensive report of the whole analysis universe, the points-to analysis can also produce reachability reports on why certain type/method/field is reachable.
16+
1517
## Call tree
1618
The call tree is a a breadth-first tree reduction of the call graph as seen by the points-to analysis.
1719
The points-to analysis eliminates calls to methods that it determines cannot be reachable at runtime, based on the analyzed receiver types.
@@ -158,7 +160,46 @@ Roots suppression/expansion:
158160
- `-H:ImageObjectTreeSuppressRoots=java.util.* -H:ImageObjectTreeExpandRoots=java.util.Locale` - suppress the expansion of all roots that start with `java.util.` but not `java.util.Locale`
159161
- `-H:ImageObjectTreeExpandRoots=*` - force the expansion of all roots, including those suppressed by default
160162

161-
### Report Files
163+
## Reachability Report
164+
165+
In diagnosing code size or security problems, the developer often has the need to know why certain code element (type/method/field) is reachable.
166+
Reachability reports are designed for the purpose.
167+
There are three options for diagnosing the reachability reasons for types, methods, and fields respectively:
168+
169+
- `-H:AbortOnTypeReachable=<pattern>`
170+
- `-H:AbortOnMethodReachable=<pattern>`
171+
- `-H:AbortOnFieldReachable=<pattern>`
172+
173+
For each option, the right-hand side specifies the pattern of the code element to be diagnosed.
174+
175+
- The syntax for specifying types and fields is the same as that of suppression/expansion (See documentation for `-H:ImageObjectTreeSuppressTypes` above).
176+
- The syntax for specifying methods is the same as that of method filters (See documentation for `-Dgraal.MethodFilter`).
177+
178+
When one of the option is enabled and the corresponding code element is reachable, a reachability trace will be dumped to a TXT file and Native Image will abort.
179+
Here is an example of the reachability report for `-H:AbortOnTypeReachable=java.io.File`:
180+
181+
```
182+
Type java.io.File is marked as allocated
183+
at virtual method com.oracle.svm.core.jdk.NativeLibrarySupport.loadLibraryRelative(NativeLibrarySupport.java:105), implementation invoked
184+
├── at virtual method com.oracle.svm.core.jdk.JNIPlatformNativeLibrarySupport.loadJavaLibrary(JNIPlatformNativeLibrarySupport.java:44), implementation invoked
185+
│ ├── at virtual method com.oracle.svm.core.posix.PosixNativeLibrarySupport.loadJavaLibrary(PosixNativeLibraryFeature.java:117), implementation invoked
186+
│ │ ├── at virtual method com.oracle.svm.core.posix.PosixNativeLibrarySupport.initializeBuiltinLibraries(PosixNativeLibraryFeature.java:98), implementation invoked
187+
│ │ │ ├── at static method com.oracle.svm.core.graal.snippets.CEntryPointSnippets.initializeIsolate(CEntryPointSnippets.java:346), implementation invoked
188+
│ │ │ │ str: static root method
189+
│ │ │ └── type com.oracle.svm.core.posix.PosixNativeLibrarySupport is marked as in-heap
190+
│ │ │ scanning root com.oracle.svm.core.posix.PosixNativeLibrarySupport@4839bf0d: com.oracle.svm.core.posix.PosixNativeLibrarySupport@4839bf0d embedded in
191+
│ │ │ org.graalvm.nativeimage.ImageSingletons.lookup(ImageSingletons.java)
192+
│ │ │ at static method org.graalvm.nativeimage.ImageSingletons.lookup(Class), intrinsified
193+
│ │ │ at static method com.oracle.svm.core.graal.snippets.CEntryPointSnippets.createIsolate(CEntryPointSnippets.java:209), implementation invoked
194+
│ │ └── type com.oracle.svm.core.posix.PosixNativeLibrarySupport is marked as in-heap
195+
│ └── type com.oracle.svm.core.jdk.JNIPlatformNativeLibrarySupport is reachable
196+
└── type com.oracle.svm.core.jdk.NativeLibrarySupport is marked as in-heap
197+
scanning root com.oracle.svm.core.jdk.NativeLibrarySupport@6e06bbea: com.oracle.svm.core.jdk.NativeLibrarySupport@6e06bbea embedded in
198+
org.graalvm.nativeimage.ImageSingletons.lookup(ImageSingletons.java)
199+
at static method org.graalvm.nativeimage.ImageSingletons.lookup(Class), intrinsified
200+
```
201+
202+
## Report Files
162203

163204
The reports are generated in the `reports` subdirectory, relative to the build directory.
164205
When executing the `native-image` executable the build directory defaults to the working directory and can be modified using the `-H:Path=<dir>` option.
@@ -169,6 +210,13 @@ The object tree report name has the structure: `object_tree_<binary_name>_<date_
169210
The binary name is the name of the generated binary, which can be set with the `-H:Name=<name>` option.
170211
The `<date_time>` is in the `yyyyMMdd_HHmmss` format.
171212

172-
### Further Reading
213+
The reachability reports are also located in the reports directory.
214+
They follow the same naming convention:
215+
216+
- Type reachability report: `trace_types_<binary_name>_<date_time>.txt`
217+
- Method reachability report: `trace_methods_<binary_name>_<date_time>.txt`
218+
- Field reachability report: `trace_fields_<binary_name>_<date_time>.txt`
219+
220+
## Further Reading
173221

174-
* [Hosted and Runtime Options](HostedvsRuntimeOptions.md)
222+
* [Hosted and Runtime Options](HostedvsRuntimeOptions.md)

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanner.java

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@
6363
*/
6464
public class ObjectScanner {
6565

66+
private static final String INDENTATION_AFTER_NEWLINE = " ";
67+
6668
protected final BigBang bb;
6769
private final ReusableSet scannedObjects;
6870
private final CompletionExecutor executor;
@@ -104,7 +106,8 @@ public void scanBootImageHeapRoots(Comparator<AnalysisField> fieldComparator, Co
104106
// scan the constant nodes
105107
Map<JavaConstant, BytecodePosition> embeddedRoots = bb.getUniverse().getEmbeddedRoots();
106108
if (embeddedRootComparator != null) {
107-
embeddedRoots.entrySet().stream().sorted(Map.Entry.comparingByValue(embeddedRootComparator))
109+
embeddedRoots.entrySet().stream()
110+
.sorted(Map.Entry.comparingByValue(embeddedRootComparator))
108111
.forEach(entry -> execute(() -> scanEmbeddedRoot(entry.getKey(), entry.getValue())));
109112
} else {
110113
embeddedRoots.forEach((key, value) -> execute(() -> scanEmbeddedRoot(key, value)));
@@ -311,11 +314,11 @@ public static AnalysisMethod buildObjectBacktrace(BigBang bb, ScanReason reason,
311314
public static AnalysisMethod buildObjectBacktrace(BigBang bb, ScanReason reason, StringBuilder objectBacktrace, String header) {
312315
ScanReason cur = reason;
313316
objectBacktrace.append(header);
314-
objectBacktrace.append(System.lineSeparator()).append(indent).append(asString(bb, cur));
317+
objectBacktrace.append(System.lineSeparator()).append(indent).append(cur.toString(bb));
315318
ScanReason rootReason = cur;
316319
cur = cur.previous;
317320
while (cur != null) {
318-
objectBacktrace.append(System.lineSeparator()).append(indent).append(asString(bb, cur));
321+
objectBacktrace.append(System.lineSeparator()).append(indent).append(cur.toString(bb));
319322
rootReason = cur.previous;
320323
cur = cur.previous;
321324
}
@@ -327,39 +330,6 @@ public static AnalysisMethod buildObjectBacktrace(BigBang bb, ScanReason reason,
327330
return null;
328331
}
329332

330-
static String asString(BigBang bb, ScanReason reason) {
331-
if (reason instanceof MethodParsing) {
332-
MethodParsing mp = (MethodParsing) reason;
333-
String str = String.format("parsing method %s reachable via the parsing context", mp.getMethod().asStackTraceElement(0));
334-
str += ReportUtils.parsingContext(mp.getMethod(), indent + indent);
335-
return str;
336-
} else if (reason instanceof FieldConstantFold) {
337-
FieldConstantFold fieldFold = (FieldConstantFold) reason;
338-
StackTraceElement location = fieldFold.parsedMethod.asStackTraceElement(fieldFold.bci);
339-
if (fieldFold.field.isStatic()) {
340-
return "trying to constant fold static field " + reason + "\n at " + location;
341-
} else {
342-
/* Instance field scans must have a receiver, hence the 'of'. */
343-
return "trying to constant fold field " + reason + " of constant \n " + asString(bb, reason.constant) + "\n at " + location;
344-
}
345-
} else if (reason instanceof FieldScan) {
346-
FieldScan fieldScan = (FieldScan) reason;
347-
if (fieldScan.field.isStatic()) {
348-
return "reading static field " + reason + "\n at " + fieldScan.location();
349-
} else {
350-
/* Instance field scans must have a receiver, hence the 'of'. */
351-
return "reading field " + reason + " of constant \n " + asString(bb, reason.constant);
352-
// + "\n at " + location;
353-
}
354-
} else if (reason instanceof EmbeddedRootScan) {
355-
return "scanning root " + asString(bb, reason.constant) + " embedded in \n " + reason;
356-
} else if (reason instanceof ArrayScan) {
357-
return "indexing into array " + asString(bb, reason.constant);
358-
} else {
359-
return reason.toString();
360-
}
361-
}
362-
363333
public static String asString(BigBang bb, JavaConstant constant) {
364334
return asString(bb, constant, true);
365335
}
@@ -483,6 +453,11 @@ protected ScanReason(ScanReason previous, JavaConstant constant) {
483453
public ScanReason getPrevious() {
484454
return previous;
485455
}
456+
457+
@SuppressWarnings("unused")
458+
public String toString(BigBang bb) {
459+
return toString();
460+
}
486461
}
487462

488463
public static class OtherReason extends ScanReason {
@@ -542,6 +517,16 @@ public String location() {
542517
}
543518
}
544519

520+
@Override
521+
public String toString(BigBang bb) {
522+
if (field.isStatic()) {
523+
return "reading static field " + field.format("%H.%n") + System.lineSeparator() + " at " + location();
524+
} else {
525+
/* Instance field scans must have a receiver, hence the 'of'. */
526+
return "reading field " + field.format("%H.%n") + " of constant " + System.lineSeparator() + INDENTATION_AFTER_NEWLINE + asString(bb, constant);
527+
}
528+
}
529+
545530
@Override
546531
public String toString() {
547532
return field.format("%H.%n");
@@ -560,6 +545,18 @@ public FieldConstantFold(AnalysisField field, AnalysisMethod parsedMethod, int b
560545
this.bci = bci;
561546
}
562547

548+
@Override
549+
public String toString(BigBang bb) {
550+
StackTraceElement location = parsedMethod.asStackTraceElement(bci);
551+
if (field.isStatic()) {
552+
return "trying to constant fold static field " + field.format("%H.%n") + System.lineSeparator() + " at " + location;
553+
} else {
554+
/* Instance field scans must have a receiver, hence the 'of'. */
555+
return "trying to constant fold field " + field.format("%H.%n") + " of constant " + System.lineSeparator() +
556+
INDENTATION_AFTER_NEWLINE + asString(bb, constant) + System.lineSeparator() + " at " + location;
557+
}
558+
}
559+
563560
@Override
564561
public String toString() {
565562
return field.format("%H.%n");
@@ -584,8 +581,8 @@ public AnalysisMethod getMethod() {
584581

585582
@Override
586583
public String toString() {
587-
String str = String.format("Parsing method %s %n", method.asStackTraceElement(0));
588-
str += "Parsing context:" + ReportUtils.parsingContext(method);
584+
String str = String.format("parsing method %s reachable via the parsing context", method.asStackTraceElement(0));
585+
str += ReportUtils.parsingContext(method, indent + indent);
589586
return str;
590587
}
591588
}
@@ -598,6 +595,11 @@ public ArrayScan(AnalysisType arrayType, JavaConstant array, ScanReason previous
598595
this.arrayType = arrayType;
599596
}
600597

598+
@Override
599+
public String toString(BigBang bb) {
600+
return "indexing into array " + asString(bb, constant);
601+
}
602+
601603
@Override
602604
public String toString() {
603605
return arrayType.toJavaName(true);
@@ -620,6 +622,11 @@ public AnalysisMethod getMethod() {
620622
return (AnalysisMethod) position.getMethod();
621623
}
622624

625+
@Override
626+
public String toString(BigBang bb) {
627+
return "scanning root " + asString(bb, constant) + " embedded in " + System.lineSeparator() + INDENTATION_AFTER_NEWLINE + position.getMethod().asStackTraceElement(position.getBCI());
628+
}
629+
623630
@Override
624631
public String toString() {
625632
return position.getMethod().asStackTraceElement(position.getBCI()).toString();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ public AnalysisMethod addRootMethod(AnalysisMethod aMethod, boolean invokeSpecia
314314
Consumer<PointsToAnalysisMethod> triggerStaticMethodFlow = (pointsToMethod) -> {
315315
postTask(() -> {
316316
pointsToMethod.registerAsDirectRootMethod();
317-
pointsToMethod.registerAsImplementationInvoked("root method");
317+
pointsToMethod.registerAsImplementationInvoked("static root method");
318318
MethodFlowsGraphInfo flowInfo = analysisPolicy.staticRootMethodGraph(this, pointsToMethod);
319319
for (int idx = 0; idx < paramCount; idx++) {
320320
AnalysisType declaredParamType = (AnalysisType) signature.getParameterType(idx, declaringClass);

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,10 +309,11 @@ protected static void registerUsedElements(PointsToAnalysis bb, StructuredGraph
309309

310310
} else if (n instanceof ConstantNode) {
311311
ConstantNode cn = (ConstantNode) n;
312-
if (cn.hasUsages() && cn.isJavaConstant() && cn.asJavaConstant().getJavaKind() == JavaKind.Object && cn.asJavaConstant().isNonNull()) {
312+
JavaConstant root = cn.asJavaConstant();
313+
if (cn.hasUsages() && cn.isJavaConstant() && root.getJavaKind() == JavaKind.Object && root.isNonNull()) {
313314
assert StampTool.isExactType(cn);
314315
AnalysisType type = (AnalysisType) StampTool.typeOrNull(cn, bb.getMetaAccess());
315-
type.registerAsInHeap(AbstractAnalysisEngine.sourcePosition(cn));
316+
type.registerAsInHeap(new EmbeddedRootScan(AbstractAnalysisEngine.sourcePosition(cn), root));
316317
if (registerEmbeddedRoots && !ignoreConstant(bb, cn)) {
317318
registerEmbeddedRoot(bb, cn);
318319
}

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/TypeFlow.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ private boolean doAddObserver(PointsToAnalysis bb, TypeFlow<?> observer) {
464464
* An observer is linked even if it is already saturated itself, hence no
465465
* 'observer.isSaturated()' check is performed here. For observers the saturation state is
466466
* that of the values flowing through and not that of the objects they observe.
467-
*
467+
*
468468
* Some observers may need to continue to observe the state of their receiver object until
469469
* the receiver object saturates itself, e.g., instance field stores, other observers may
470470
* deregister themselves from observing the receiver object when they saturate, e.g.,
@@ -562,7 +562,7 @@ public TypeState declaredTypeFilter(PointsToAnalysis bb, TypeState newState, boo
562562
*
563563
* Places where interface types need to be filtered: method parameters, method return values,
564564
* and field loads (including unsafe memory loads).
565-
*
565+
*
566566
* Places where interface types need not be filtered: array element loads (because all array
567567
* stores have an array store check).
568568
*/
@@ -704,10 +704,10 @@ public void onObservedSaturated(@SuppressWarnings("unused") PointsToAnalysis bb,
704704
* approximation, e.g., the flow of the receiver type for a special invoke operation or of the
705705
* field declaring class for a field access operation. By default the observers don't use the
706706
* null state of the observed, therefore the non-null type flow is used.
707-
*
707+
*
708708
* The overloaded {@link #replacedObservedWith(PointsToAnalysis, TypeFlow)} can be used for
709709
* replacing the observed with a custom type flow.
710-
*
710+
*
711711
*/
712712
public void replaceObservedWith(PointsToAnalysis bb, AnalysisType newObservedType) {
713713
replacedObservedWith(bb, newObservedType.getTypeFlow(bb, false));

0 commit comments

Comments
 (0)