Skip to content

Commit 5c63f6c

Browse files
committed
Output call tree analysis for use in graph database
* Methods and virtual methods are represented with graph nodes. * Direct, virtual and overriden-by relationships have been mapped. * Bytecode indexes are part of the relationships. * A method can interact with others multiple times, with each bytecode index indicating the origin of the call with the origin method.
1 parent 4ec9f16 commit 5c63f6c

File tree

4 files changed

+395
-12
lines changed

4 files changed

+395
-12
lines changed
Lines changed: 379 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
1+
/*
2+
* Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.graal.pointsto.reports;
26+
27+
import com.oracle.graal.pointsto.BigBang;
28+
import com.oracle.graal.pointsto.meta.AnalysisMethod;
29+
import com.oracle.graal.pointsto.reports.CallTreePrinter.InvokeNode;
30+
import com.oracle.graal.pointsto.reports.CallTreePrinter.MethodNode;
31+
import com.oracle.graal.pointsto.reports.CallTreePrinter.MethodNodeReference;
32+
import com.oracle.graal.pointsto.reports.CallTreePrinter.Node;
33+
import jdk.vm.ci.meta.JavaKind;
34+
import jdk.vm.ci.meta.ResolvedJavaMethod;
35+
import jdk.vm.ci.meta.ResolvedJavaType;
36+
37+
import java.io.File;
38+
import java.io.PrintWriter;
39+
import java.util.ArrayList;
40+
import java.util.Arrays;
41+
import java.util.Collection;
42+
import java.util.HashMap;
43+
import java.util.HashSet;
44+
import java.util.Iterator;
45+
import java.util.List;
46+
import java.util.Map;
47+
import java.util.Objects;
48+
import java.util.Set;
49+
import java.util.concurrent.atomic.AtomicInteger;
50+
import java.util.regex.Matcher;
51+
import java.util.regex.Pattern;
52+
import java.util.stream.Collectors;
53+
import java.util.stream.Stream;
54+
55+
public class CallTreeCypher {
56+
57+
private static final AtomicInteger virtualNodeId = new AtomicInteger(-1);
58+
public static final Pattern DISPLAY_PATTERN = Pattern.compile(
59+
"\\b[a-zA-Z]|[A-Z]|\\.");
60+
61+
public static void print(BigBang bigbang, String path, String reportName) {
62+
// Re-initialize method ids back to 0 to better diagnose disparities
63+
MethodNode.methodId = 0;
64+
65+
CallTreePrinter printer = new CallTreePrinter(bigbang);
66+
printer.buildCallTree();
67+
68+
// Set virtual node at next available method id
69+
virtualNodeId.set(MethodNode.methodId);
70+
71+
printAll(printer.methodToNode, path, reportName);
72+
}
73+
74+
private static void printAll(Map<AnalysisMethod, MethodNode> methodToNode, String path, String reportName) {
75+
Set<Integer> entryPointIds = new HashSet<>();
76+
Set<MethodNode> nonVirtualNodes = new HashSet<>();
77+
Map<List<String>, Integer> virtualNodes = new HashMap<>();
78+
79+
Map<Integer, Set<BciEndEdge>> directEdges = new HashMap<>();
80+
Map<Integer, Set<BciEndEdge>> virtualEdges = new HashMap<>();
81+
Map<Integer, Set<Integer>> overridenByEdges = new HashMap<>();
82+
83+
final Iterator<MethodNode> iterator = methodToNode.values().stream().filter(n -> n.isEntryPoint).iterator();
84+
while (iterator.hasNext()) {
85+
final MethodNode node = iterator.next();
86+
entryPointIds.add(node.id);
87+
walkNodes(node, directEdges, virtualEdges, overridenByEdges, virtualNodes, nonVirtualNodes);
88+
}
89+
90+
final String vmFileName = ReportUtils.report("call tree for vm entry point", path + File.separatorChar + "reports", "csv_call_tree_vm_" + reportName, "csv",
91+
CallTreeCypher::printVMEntryPoint);
92+
93+
final String methodsFileName = ReportUtils.report("call tree for methods", path + File.separatorChar + "reports", "csv_call_tree_methods_" + reportName, "csv",
94+
writer -> printMethodNodes(methodToNode.values(), writer));
95+
96+
final String virtualMethodsFileName = ReportUtils.report("call tree for virtual methods", path + File.separatorChar + "reports", "csv_call_tree_virtual_methods_" + reportName, "csv",
97+
writer -> printVirtualNodes(virtualNodes, writer));
98+
99+
final String entryPointsFileName = ReportUtils.report("call tree for entry points", path + File.separatorChar + "reports", "csv_call_tree_entry_points_" + reportName, "csv",
100+
writer -> printEntryPointIds(entryPointIds, writer));
101+
102+
final String directEdgesFileName = ReportUtils.report("call tree for direct edges", path + File.separatorChar + "reports", "csv_call_tree_direct_edges_" + reportName, "csv",
103+
writer -> printBciEdges(directEdges, writer));
104+
105+
final String overridenByEdgesFileName = ReportUtils.report("call tree for overriden by edges", path + File.separatorChar + "reports", "csv_call_tree_override_by_edges_" + reportName, "csv",
106+
writer -> printNonBciEdges(overridenByEdges, writer));
107+
108+
final String virtualEdgesFileName = ReportUtils.report("call tree for virtual edges", path + File.separatorChar + "reports", "csv_call_tree_virtual_edges_" + reportName, "csv",
109+
writer -> printBciEdges(virtualEdges, writer));
110+
111+
ReportUtils.report("call tree cypher", path + File.separatorChar + "reports", "cypher_call_tree_" + reportName, "cypher",
112+
writer -> printCypher(vmFileName, methodsFileName, virtualMethodsFileName, entryPointsFileName, directEdgesFileName, overridenByEdgesFileName, virtualEdgesFileName, writer));
113+
}
114+
115+
private static void printCypher(String vmFileName, String methodsFileName, String virtualMethodsFileName, String entryPointsFileName, String directEdgesFileName, String overridenByEdgesFileName,
116+
String virtualEdgesFileName, PrintWriter writer) {
117+
writer.println("CREATE CONSTRAINT unique_vm_id ON (v:VM) ASSERT v.vmId IS UNIQUE;");
118+
writer.println("CREATE CONSTRAINT unique_method_id ON (m:Method) ASSERT m.methodId IS UNIQUE;");
119+
writer.println("");
120+
writer.println(String.format("LOAD CSV WITH HEADERS FROM 'file:///%s' AS row", vmFileName));
121+
writer.println("MERGE (v:VM {vmId: row.Id, name: row.Name})");
122+
writer.println("RETURN count(v);");
123+
writer.println("");
124+
writer.println(String.format("LOAD CSV WITH HEADERS FROM 'file:///%s' AS row", methodsFileName));
125+
writer.println("MERGE (m:Method {methodId: row.Id, name: row.Name, type: row.Type, parameters: row.Parameters, return: row.Return, display: row.Display})");
126+
writer.println("RETURN count(m);");
127+
writer.println("");
128+
writer.println(String.format("LOAD CSV WITH HEADERS FROM 'file:///%s' AS row", virtualMethodsFileName));
129+
writer.println("MERGE (m:Method {methodId: row.Id, name: row.Name, type: row.Type, parameters: row.Parameters, return: row.Return, display: row.Display})");
130+
writer.println("RETURN count(m);");
131+
writer.println("");
132+
writer.println(String.format("LOAD CSV WITH HEADERS FROM 'file:///%s' AS row", entryPointsFileName));
133+
writer.println("MATCH (m:Method {methodId: row.Id})");
134+
writer.println("MATCH (v:VM {vmId: '0'})");
135+
writer.println("MERGE (v)-[:ENTRY]->(m)");
136+
writer.println("RETURN count(*);");
137+
writer.println("");
138+
writer.println(String.format("LOAD CSV WITH HEADERS FROM 'file:///%s' AS row", directEdgesFileName));
139+
writer.println("MATCH (m1:Method {methodId: row.StartId})");
140+
writer.println("MATCH (m2:Method {methodId: row.EndId})");
141+
writer.println("MERGE (m1)-[:DIRECT {bci: row.BytecodeIndexes}]->(m2)");
142+
writer.println("RETURN count(*);");
143+
writer.println("");
144+
writer.println(String.format("LOAD CSV WITH HEADERS FROM 'file:///%s' AS row", overridenByEdgesFileName));
145+
writer.println("MATCH (m1:Method {methodId: row.StartId})");
146+
writer.println("MATCH (m2:Method {methodId: row.EndId})");
147+
writer.println("MERGE (m1)-[:OVERRIDEN_BY]->(m2)");
148+
writer.println("RETURN count(*);");
149+
writer.println("");
150+
writer.println(String.format("LOAD CSV WITH HEADERS FROM 'file:///%s' AS row", virtualEdgesFileName));
151+
writer.println("MATCH (m1:Method {methodId: row.StartId})");
152+
writer.println("MATCH (m2:Method {methodId: row.EndId})");
153+
writer.println("MERGE (m1)-[:VIRTUAL {bci: row.BytecodeIndexes}]->(m2)");
154+
writer.println("RETURN count(*);");
155+
}
156+
157+
private static void printVMEntryPoint(PrintWriter writer) {
158+
writer.println(convertToCSV("Id", "Name"));
159+
writer.println(convertToCSV("0", "VM"));
160+
}
161+
162+
private static void walkNodes(MethodNode methodNode, Map<Integer, Set<BciEndEdge>> directEdges, Map<Integer, Set<BciEndEdge>> virtualEdges, Map<Integer, Set<Integer>> overridenByEdges,
163+
Map<List<String>, Integer> virtualNodes, Set<MethodNode> nonVirtualNodes) {
164+
for (InvokeNode invoke : methodNode.invokes) {
165+
if (invoke.isDirectInvoke) {
166+
if (invoke.callees.size() > 0) {
167+
Node calleeNode = invoke.callees.get(0);
168+
addDirectEdge(methodNode.id, invoke, calleeNode, directEdges, nonVirtualNodes);
169+
if (calleeNode instanceof MethodNode) {
170+
walkNodes((MethodNode) calleeNode, directEdges, virtualEdges, overridenByEdges, virtualNodes, nonVirtualNodes);
171+
}
172+
}
173+
} else {
174+
final int nodeId = addVirtualNode(invoke, virtualNodes);
175+
addVirtualMethodEdge(methodNode.id, invoke, nodeId, virtualEdges);
176+
for (Node calleeNode : invoke.callees) {
177+
addOverridenByEdge(nodeId, calleeNode, overridenByEdges, nonVirtualNodes);
178+
if (calleeNode instanceof MethodNode) {
179+
walkNodes((MethodNode) calleeNode, directEdges, virtualEdges, overridenByEdges, virtualNodes, nonVirtualNodes);
180+
}
181+
}
182+
}
183+
}
184+
}
185+
186+
private static void printMethodNodes(Collection<MethodNode> methods, PrintWriter writer) {
187+
writer.println(convertToCSV("Id", "Name", "Type", "Parameters", "Return", "Display"));
188+
methods.stream()
189+
.map(CallTreeCypher::methodNodeInfo)
190+
.map(CallTreeCypher::convertToCSV)
191+
.forEach(writer::println);
192+
}
193+
194+
private static List<String> methodNodeInfo(MethodNode method) {
195+
return resolvedJavaMethodInfo(method.id, method.method);
196+
}
197+
198+
private static int addVirtualNode(InvokeNode node, Map<List<String>, Integer> virtualNodes) {
199+
final List<String> virtualMethodInfo = virtualMethodInfo(node.targetMethod);
200+
return virtualNodes.computeIfAbsent(virtualMethodInfo, k -> CallTreeCypher.virtualNodeId.getAndIncrement());
201+
}
202+
203+
private static void addVirtualMethodEdge(int startId, InvokeNode invoke, int endId, Map<Integer, Set<BciEndEdge>> edges) {
204+
Set<BciEndEdge> nodeEdges = edges.computeIfAbsent(startId, k -> new HashSet<>());
205+
nodeEdges.add(new BciEndEdge(endId, bytecodeIndexes(invoke)));
206+
}
207+
208+
private static void addDirectEdge(int nodeId, InvokeNode invoke, Node calleeNode, Map<Integer, Set<BciEndEdge>> edges, Set<MethodNode> nodes) {
209+
Set<BciEndEdge> nodeEdges = edges.computeIfAbsent(nodeId, k -> new HashSet<>());
210+
MethodNode methodNode = calleeNode instanceof MethodNode
211+
? (MethodNode) calleeNode
212+
: ((MethodNodeReference) calleeNode).methodNode;
213+
nodes.add(methodNode);
214+
nodeEdges.add(new BciEndEdge(methodNode.id, bytecodeIndexes(invoke)));
215+
}
216+
217+
private static List<Integer> bytecodeIndexes(InvokeNode node) {
218+
return Stream.of(node.sourceReferences)
219+
.map(source -> source.bci)
220+
.collect(Collectors.toList());
221+
}
222+
223+
private static void printVirtualNodes(Map<List<String>, Integer> virtualNodes, PrintWriter writer) {
224+
writer.println(convertToCSV("Id", "Name", "Type", "Parameters", "Return", "Display"));
225+
virtualNodes.entrySet().stream()
226+
.map(CallTreeCypher::virtualMethodAndIdInfo)
227+
.map(CallTreeCypher::convertToCSV)
228+
.forEach(writer::println);
229+
}
230+
231+
private static List<String> virtualMethodAndIdInfo(Map.Entry<List<String>, Integer> entry) {
232+
final List<String> methodInfo = entry.getKey();
233+
final List<String> result = new ArrayList<>(methodInfo.size() + 1);
234+
result.add(String.valueOf(entry.getValue()));
235+
for (int i = 1; i < methodInfo.size(); i++) {
236+
result.add(i, methodInfo.get(i));
237+
}
238+
return result;
239+
}
240+
241+
private static void printEntryPointIds(Set<Integer> entryPoints, PrintWriter writer) {
242+
writer.println(convertToCSV("Id"));
243+
entryPoints.forEach(writer::println);
244+
}
245+
246+
private static void addOverridenByEdge(int nodeId, Node calleeNode, Map<Integer, Set<Integer>> edges, Set<MethodNode> nodes) {
247+
Set<Integer> nodeEdges = edges.computeIfAbsent(nodeId, k -> new HashSet<>());
248+
MethodNode methodNode = calleeNode instanceof MethodNode
249+
? (MethodNode) calleeNode
250+
: ((MethodNodeReference) calleeNode).methodNode;
251+
nodes.add(methodNode);
252+
nodeEdges.add(methodNode.id);
253+
}
254+
255+
private static void printBciEdges(Map<Integer, Set<BciEndEdge>> edges, PrintWriter writer) {
256+
final Set<BciEdge> idEdges = edges.entrySet().stream()
257+
.flatMap(entry -> entry.getValue().stream().map(endId -> new BciEdge(entry.getKey(), endId)))
258+
.collect(Collectors.toSet());
259+
260+
writer.println(convertToCSV("StartId", "EndId", "BytecodeIndexes"));
261+
idEdges.stream()
262+
.map(edge -> convertToCSV(String.valueOf(edge.startId), String.valueOf(edge.endEdge.id), showBytecodeIndexes(edge.endEdge.bytecodeIndexes)))
263+
.forEach(writer::println);
264+
}
265+
266+
private static String showBytecodeIndexes(List<Integer> bytecodeIndexes) {
267+
return bytecodeIndexes.stream()
268+
.map(String::valueOf)
269+
.collect(Collectors.joining("->"));
270+
}
271+
272+
private static void printNonBciEdges(Map<Integer, Set<Integer>> edges, PrintWriter writer) {
273+
final Set<NonBciEdge> idEdges = edges.entrySet().stream()
274+
.flatMap(entry -> entry.getValue().stream().map(endId -> new NonBciEdge(entry.getKey(), endId)))
275+
.collect(Collectors.toSet());
276+
277+
writer.println(convertToCSV("StartId", "EndId"));
278+
idEdges.stream()
279+
.map(edge -> convertToCSV(String.valueOf(edge.startId), String.valueOf(edge.endId)))
280+
.forEach(writer::println);
281+
}
282+
283+
private static List<String> virtualMethodInfo(AnalysisMethod method) {
284+
return resolvedJavaMethodInfo(null, method);
285+
}
286+
287+
private static List<String> resolvedJavaMethodInfo(Integer id, ResolvedJavaMethod method) {
288+
// TODO method parameter types are opaque, but could in the future be split out and link
289+
// together
290+
// e.g. each method could BELONG to a type, and a method could have PARAMETER relationships
291+
// with N types
292+
// see https://neo4j.com/developer/guide-import-csv/#_converting_data_values_with_load_csv
293+
// for examples
294+
final String parameters = method.getSignature().getParameterCount(false) > 0
295+
? method.format("%P").replace(",", "")
296+
: "empty";
297+
298+
return Arrays.asList(
299+
id == null ? null : Integer.toString(id),
300+
method.getName(),
301+
method.getDeclaringClass().toJavaName(true),
302+
parameters,
303+
method.getSignature().getReturnType(null).toJavaName(true),
304+
display(method));
305+
}
306+
307+
private static String display(ResolvedJavaMethod method) {
308+
final ResolvedJavaType type = method.getDeclaringClass();
309+
final String typeName = type.toJavaName(true);
310+
if (type.getJavaKind() == JavaKind.Object) {
311+
List<String> matchResults = new ArrayList<>();
312+
Matcher matcher = DISPLAY_PATTERN.matcher(typeName);
313+
while (matcher.find()) {
314+
matchResults.add(matcher.toMatchResult().group());
315+
}
316+
317+
return String.join("", matchResults) + "." + method.getName();
318+
}
319+
320+
return typeName + "." + method.getName();
321+
}
322+
323+
private static String convertToCSV(String... data) {
324+
return String.join(",", data);
325+
}
326+
327+
private static String convertToCSV(List<String> data) {
328+
return String.join(",", data);
329+
}
330+
331+
private static final class NonBciEdge {
332+
333+
final int startId;
334+
final int endId;
335+
336+
private NonBciEdge(int startId, int endId) {
337+
this.startId = startId;
338+
this.endId = endId;
339+
}
340+
}
341+
342+
private static final class BciEdge {
343+
final int startId;
344+
final BciEndEdge endEdge;
345+
346+
private BciEdge(int startId, BciEndEdge endEdge) {
347+
this.startId = startId;
348+
this.endEdge = endEdge;
349+
}
350+
}
351+
352+
private static final class BciEndEdge {
353+
final int id;
354+
final List<Integer> bytecodeIndexes;
355+
356+
private BciEndEdge(int id, List<Integer> bytecodeIndexes) {
357+
this.id = id;
358+
this.bytecodeIndexes = bytecodeIndexes;
359+
}
360+
361+
@Override
362+
public boolean equals(Object o) {
363+
if (this == o) {
364+
return true;
365+
}
366+
if (o == null || getClass() != o.getClass()) {
367+
return false;
368+
}
369+
BciEndEdge endEdge = (BciEndEdge) o;
370+
return id == endEdge.id &&
371+
bytecodeIndexes.equals(endEdge.bytecodeIndexes);
372+
}
373+
374+
@Override
375+
public int hashCode() {
376+
return Objects.hash(id, bytecodeIndexes);
377+
}
378+
}
379+
}

0 commit comments

Comments
 (0)