Skip to content
Merged
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
54 changes: 51 additions & 3 deletions docs/reference-manual/native-image/StaticAnalysisReports.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ The points-to analysis produces two kinds of reports: an analysis call tree and
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.
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.

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.

## Call tree
The call tree is a a breadth-first tree reduction of the call graph as seen by the points-to analysis.
The points-to analysis eliminates calls to methods that it determines cannot be reachable at runtime, based on the analyzed receiver types.
Expand Down Expand Up @@ -158,7 +160,46 @@ Roots suppression/expansion:
- `-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`
- `-H:ImageObjectTreeExpandRoots=*` - force the expansion of all roots, including those suppressed by default

### Report Files
## Reachability Report

In diagnosing code size or security problems, the developer often has the need to know why certain code element (type/method/field) is reachable.
Reachability reports are designed for the purpose.
There are three options for diagnosing the reachability reasons for types, methods, and fields respectively:

- `-H:AbortOnTypeReachable=<pattern>`
- `-H:AbortOnMethodReachable=<pattern>`
- `-H:AbortOnFieldReachable=<pattern>`

For each option, the right-hand side specifies the pattern of the code element to be diagnosed.

- The syntax for specifying types and fields is the same as that of suppression/expansion (See documentation for `-H:ImageObjectTreeSuppressTypes` above).
- The syntax for specifying methods is the same as that of method filters (See documentation for `-Dgraal.MethodFilter`).

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.
Here is an example of the reachability report for `-H:AbortOnTypeReachable=java.io.File`:

```
Type java.io.File is marked as allocated
at virtual method com.oracle.svm.core.jdk.NativeLibrarySupport.loadLibraryRelative(NativeLibrarySupport.java:105), implementation invoked
├── at virtual method com.oracle.svm.core.jdk.JNIPlatformNativeLibrarySupport.loadJavaLibrary(JNIPlatformNativeLibrarySupport.java:44), implementation invoked
│ ├── at virtual method com.oracle.svm.core.posix.PosixNativeLibrarySupport.loadJavaLibrary(PosixNativeLibraryFeature.java:117), implementation invoked
│ │ ├── at virtual method com.oracle.svm.core.posix.PosixNativeLibrarySupport.initializeBuiltinLibraries(PosixNativeLibraryFeature.java:98), implementation invoked
│ │ │ ├── at static method com.oracle.svm.core.graal.snippets.CEntryPointSnippets.initializeIsolate(CEntryPointSnippets.java:346), implementation invoked
│ │ │ │ str: static root method
│ │ │ └── type com.oracle.svm.core.posix.PosixNativeLibrarySupport is marked as in-heap
│ │ │ scanning root com.oracle.svm.core.posix.PosixNativeLibrarySupport@4839bf0d: com.oracle.svm.core.posix.PosixNativeLibrarySupport@4839bf0d embedded in
│ │ │ org.graalvm.nativeimage.ImageSingletons.lookup(ImageSingletons.java)
│ │ │ at static method org.graalvm.nativeimage.ImageSingletons.lookup(Class), intrinsified
│ │ │ at static method com.oracle.svm.core.graal.snippets.CEntryPointSnippets.createIsolate(CEntryPointSnippets.java:209), implementation invoked
│ │ └── type com.oracle.svm.core.posix.PosixNativeLibrarySupport is marked as in-heap
│ └── type com.oracle.svm.core.jdk.JNIPlatformNativeLibrarySupport is reachable
└── type com.oracle.svm.core.jdk.NativeLibrarySupport is marked as in-heap
scanning root com.oracle.svm.core.jdk.NativeLibrarySupport@6e06bbea: com.oracle.svm.core.jdk.NativeLibrarySupport@6e06bbea embedded in
org.graalvm.nativeimage.ImageSingletons.lookup(ImageSingletons.java)
at static method org.graalvm.nativeimage.ImageSingletons.lookup(Class), intrinsified
```

## Report Files

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

### Further Reading
The reachability reports are also located in the reports directory.
They follow the same naming convention:

- Type reachability report: `trace_types_<binary_name>_<date_time>.txt`
- Method reachability report: `trace_methods_<binary_name>_<date_time>.txt`
- Field reachability report: `trace_fields_<binary_name>_<date_time>.txt`

## Further Reading

* [Hosted and Runtime Options](HostedvsRuntimeOptions.md)
* [Hosted and Runtime Options](HostedvsRuntimeOptions.md)
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
*/
public class ObjectScanner {

private static final String INDENTATION_AFTER_NEWLINE = " ";

protected final BigBang bb;
private final ReusableSet scannedObjects;
private final CompletionExecutor executor;
Expand Down Expand Up @@ -104,7 +106,8 @@ public void scanBootImageHeapRoots(Comparator<AnalysisField> fieldComparator, Co
// scan the constant nodes
Map<JavaConstant, BytecodePosition> embeddedRoots = bb.getUniverse().getEmbeddedRoots();
if (embeddedRootComparator != null) {
embeddedRoots.entrySet().stream().sorted(Map.Entry.comparingByValue(embeddedRootComparator))
embeddedRoots.entrySet().stream()
.sorted(Map.Entry.comparingByValue(embeddedRootComparator))
.forEach(entry -> execute(() -> scanEmbeddedRoot(entry.getKey(), entry.getValue())));
} else {
embeddedRoots.forEach((key, value) -> execute(() -> scanEmbeddedRoot(key, value)));
Expand Down Expand Up @@ -311,11 +314,11 @@ public static AnalysisMethod buildObjectBacktrace(BigBang bb, ScanReason reason,
public static AnalysisMethod buildObjectBacktrace(BigBang bb, ScanReason reason, StringBuilder objectBacktrace, String header) {
ScanReason cur = reason;
objectBacktrace.append(header);
objectBacktrace.append(System.lineSeparator()).append(indent).append(asString(bb, cur));
objectBacktrace.append(System.lineSeparator()).append(indent).append(cur.toString(bb));
ScanReason rootReason = cur;
cur = cur.previous;
while (cur != null) {
objectBacktrace.append(System.lineSeparator()).append(indent).append(asString(bb, cur));
objectBacktrace.append(System.lineSeparator()).append(indent).append(cur.toString(bb));
rootReason = cur.previous;
cur = cur.previous;
}
Expand All @@ -327,39 +330,6 @@ public static AnalysisMethod buildObjectBacktrace(BigBang bb, ScanReason reason,
return null;
}

static String asString(BigBang bb, ScanReason reason) {
if (reason instanceof MethodParsing) {
MethodParsing mp = (MethodParsing) reason;
String str = String.format("parsing method %s reachable via the parsing context", mp.getMethod().asStackTraceElement(0));
str += ReportUtils.parsingContext(mp.getMethod(), indent + indent);
return str;
} else if (reason instanceof FieldConstantFold) {
FieldConstantFold fieldFold = (FieldConstantFold) reason;
StackTraceElement location = fieldFold.parsedMethod.asStackTraceElement(fieldFold.bci);
if (fieldFold.field.isStatic()) {
return "trying to constant fold static field " + reason + "\n at " + location;
} else {
/* Instance field scans must have a receiver, hence the 'of'. */
return "trying to constant fold field " + reason + " of constant \n " + asString(bb, reason.constant) + "\n at " + location;
}
} else if (reason instanceof FieldScan) {
FieldScan fieldScan = (FieldScan) reason;
if (fieldScan.field.isStatic()) {
return "reading static field " + reason + "\n at " + fieldScan.location();
} else {
/* Instance field scans must have a receiver, hence the 'of'. */
return "reading field " + reason + " of constant \n " + asString(bb, reason.constant);
// + "\n at " + location;
}
} else if (reason instanceof EmbeddedRootScan) {
return "scanning root " + asString(bb, reason.constant) + " embedded in \n " + reason;
} else if (reason instanceof ArrayScan) {
return "indexing into array " + asString(bb, reason.constant);
} else {
return reason.toString();
}
}

public static String asString(BigBang bb, JavaConstant constant) {
return asString(bb, constant, true);
}
Expand Down Expand Up @@ -483,6 +453,11 @@ protected ScanReason(ScanReason previous, JavaConstant constant) {
public ScanReason getPrevious() {
return previous;
}

@SuppressWarnings("unused")
public String toString(BigBang bb) {
return toString();
}
}

public static class OtherReason extends ScanReason {
Expand Down Expand Up @@ -542,6 +517,16 @@ public String location() {
}
}

@Override
public String toString(BigBang bb) {
if (field.isStatic()) {
return "reading static field " + field.format("%H.%n") + System.lineSeparator() + " at " + location();
} else {
/* Instance field scans must have a receiver, hence the 'of'. */
return "reading field " + field.format("%H.%n") + " of constant " + System.lineSeparator() + INDENTATION_AFTER_NEWLINE + asString(bb, constant);
}
}

@Override
public String toString() {
return field.format("%H.%n");
Expand All @@ -560,6 +545,18 @@ public FieldConstantFold(AnalysisField field, AnalysisMethod parsedMethod, int b
this.bci = bci;
}

@Override
public String toString(BigBang bb) {
StackTraceElement location = parsedMethod.asStackTraceElement(bci);
if (field.isStatic()) {
return "trying to constant fold static field " + field.format("%H.%n") + System.lineSeparator() + " at " + location;
} else {
/* Instance field scans must have a receiver, hence the 'of'. */
return "trying to constant fold field " + field.format("%H.%n") + " of constant " + System.lineSeparator() +
INDENTATION_AFTER_NEWLINE + asString(bb, constant) + System.lineSeparator() + " at " + location;
}
}

@Override
public String toString() {
return field.format("%H.%n");
Expand All @@ -584,8 +581,8 @@ public AnalysisMethod getMethod() {

@Override
public String toString() {
String str = String.format("Parsing method %s %n", method.asStackTraceElement(0));
str += "Parsing context:" + ReportUtils.parsingContext(method);
String str = String.format("parsing method %s reachable via the parsing context", method.asStackTraceElement(0));
str += ReportUtils.parsingContext(method, indent + indent);
return str;
}
}
Expand All @@ -598,6 +595,11 @@ public ArrayScan(AnalysisType arrayType, JavaConstant array, ScanReason previous
this.arrayType = arrayType;
}

@Override
public String toString(BigBang bb) {
return "indexing into array " + asString(bb, constant);
}

@Override
public String toString() {
return arrayType.toJavaName(true);
Expand All @@ -620,6 +622,11 @@ public AnalysisMethod getMethod() {
return (AnalysisMethod) position.getMethod();
}

@Override
public String toString(BigBang bb) {
return "scanning root " + asString(bb, constant) + " embedded in " + System.lineSeparator() + INDENTATION_AFTER_NEWLINE + position.getMethod().asStackTraceElement(position.getBCI());
}

@Override
public String toString() {
return position.getMethod().asStackTraceElement(position.getBCI()).toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ public AnalysisMethod addRootMethod(AnalysisMethod aMethod, boolean invokeSpecia
Consumer<PointsToAnalysisMethod> triggerStaticMethodFlow = (pointsToMethod) -> {
postTask(() -> {
pointsToMethod.registerAsDirectRootMethod();
pointsToMethod.registerAsImplementationInvoked("root method");
pointsToMethod.registerAsImplementationInvoked("static root method");
MethodFlowsGraphInfo flowInfo = analysisPolicy.staticRootMethodGraph(this, pointsToMethod);
for (int idx = 0; idx < paramCount; idx++) {
AnalysisType declaredParamType = (AnalysisType) signature.getParameterType(idx, declaringClass);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,10 +309,11 @@ protected static void registerUsedElements(PointsToAnalysis bb, StructuredGraph

} else if (n instanceof ConstantNode) {
ConstantNode cn = (ConstantNode) n;
if (cn.hasUsages() && cn.isJavaConstant() && cn.asJavaConstant().getJavaKind() == JavaKind.Object && cn.asJavaConstant().isNonNull()) {
JavaConstant root = cn.asJavaConstant();
if (cn.hasUsages() && cn.isJavaConstant() && root.getJavaKind() == JavaKind.Object && root.isNonNull()) {
assert StampTool.isExactType(cn);
AnalysisType type = (AnalysisType) StampTool.typeOrNull(cn, bb.getMetaAccess());
type.registerAsInHeap(AbstractAnalysisEngine.sourcePosition(cn));
type.registerAsInHeap(new EmbeddedRootScan(AbstractAnalysisEngine.sourcePosition(cn), root));
if (registerEmbeddedRoots && !ignoreConstant(bb, cn)) {
registerEmbeddedRoot(bb, cn);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ private boolean doAddObserver(PointsToAnalysis bb, TypeFlow<?> observer) {
* An observer is linked even if it is already saturated itself, hence no
* 'observer.isSaturated()' check is performed here. For observers the saturation state is
* that of the values flowing through and not that of the objects they observe.
*
*
* Some observers may need to continue to observe the state of their receiver object until
* the receiver object saturates itself, e.g., instance field stores, other observers may
* deregister themselves from observing the receiver object when they saturate, e.g.,
Expand Down Expand Up @@ -562,7 +562,7 @@ public TypeState declaredTypeFilter(PointsToAnalysis bb, TypeState newState, boo
*
* Places where interface types need to be filtered: method parameters, method return values,
* and field loads (including unsafe memory loads).
*
*
* Places where interface types need not be filtered: array element loads (because all array
* stores have an array store check).
*/
Expand Down Expand Up @@ -704,10 +704,10 @@ public void onObservedSaturated(@SuppressWarnings("unused") PointsToAnalysis bb,
* approximation, e.g., the flow of the receiver type for a special invoke operation or of the
* field declaring class for a field access operation. By default the observers don't use the
* null state of the observed, therefore the non-null type flow is used.
*
*
* The overloaded {@link #replacedObservedWith(PointsToAnalysis, TypeFlow)} can be used for
* replacing the observed with a custom type flow.
*
*
*/
public void replaceObservedWith(PointsToAnalysis bb, AnalysisType newObservedType) {
replacedObservedWith(bb, newObservedType.getTypeFlow(bb, false));
Expand Down
Loading