Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(233)

Unified Diff: tools/clang/blink_gc_plugin/process-graph.py

Issue 207913002: Global cycle detection analysis. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Review comments Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tools/clang/blink_gc_plugin/RecordInfo.cpp ('k') | tools/clang/blink_gc_plugin/tests/cycle_ptrs.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/clang/blink_gc_plugin/process-graph.py
diff --git a/tools/clang/blink_gc_plugin/process-graph.py b/tools/clang/blink_gc_plugin/process-graph.py
new file mode 100755
index 0000000000000000000000000000000000000000..155634cead84c069b05a9113f4e922936c5d4523
--- /dev/null
+++ b/tools/clang/blink_gc_plugin/process-graph.py
@@ -0,0 +1,283 @@
+#!/usr/bin/env python
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse, os, sys, json, subprocess
+
+parser = argparse.ArgumentParser(
+ description =
+ "Process the Blink points-to graph generated by the Blink GC plugin.")
+
+parser.add_argument(
+ '-', dest='use_stdin', action='store_true',
+ help='Read JSON graph files from stdin')
+
+parser.add_argument(
+ '-v', '--verbose', action='store_true',
+ help='Verbose output')
+
+parser.add_argument(
+ '-c', '--detect-cycles', action='store_true', default=True,
+ help='Detect cycles containing GC roots')
+
+parser.add_argument(
+ 'files', metavar='FILE', nargs='*', default=[],
+ help='JSON graph files or directories containing them')
+
+# Command line args after parsing.
+args = None
+
+# Map from node labels to nodes.
+graph = {}
+
+# Set of root nodes.
+roots = []
+
+# Global flag to determine exit code.
+global_reported_error = False
+
+def set_reported_error(value):
+ global global_reported_error
+ global_reported_error = value
+
+def reported_error():
+ return global_reported_error
+
+def log(msg):
+ if args.verbose:
+ print msg
+
+global_inc_copy = 0
+def inc_copy():
+ global global_inc_copy
+ global_inc_copy += 1
+
+def get_node(name):
+ return graph.setdefault(name, Node(name))
+
+# Representation of graph nodes. Basically a map of directed edges.
+class Node:
+ def __init__(self, name):
+ self.name = name
+ self.edges = {}
+ self.reset()
+ def __repr__(self):
+ return "%s(%s) %s" % (self.name, self.visited, self.edges)
+ def update_node(self, decl):
+ # Currently we don't track any node info besides its edges.
+ pass
+ def update_edge(self, e):
+ new_edge = Edge(**e)
+ edge = self.edges.get(new_edge.key)
+ if edge:
+ # If an edge exist, its kind is the strongest of the two.
+ edge.kind = max(edge.kind, new_edge.kind)
+ else:
+ self.edges[new_edge.key] = new_edge
+ def super_edges(self):
+ return [ e for e in self.edges.itervalues() if e.is_super() ]
+ def reset(self):
+ self.cost = sys.maxint
+ self.visited = False
+ self.path = None
+
+# Representation of directed graph edges.
+class Edge:
+ def __init__(self, **decl):
+ self.src = decl['src']
+ self.dst = decl['dst']
+ self.lbl = decl['lbl']
+ self.kind = decl['kind'] # 0 = weak, 1 = strong, 2 = root
+ self.loc = decl['loc']
+ # The label does not uniquely determine an edge from a node. We
+ # define the semi-unique key to be the concatenation of the
+ # label and dst name. This is sufficient to track the strongest
+ # edge to a particular type. For example, if the field A::m_f
+ # has type HashMap<WeakMember<B>, Member<B>> we will have a
+ # strong edge with key m_f#B from A to B.
+ self.key = '%s#%s' % (self.lbl, self.dst)
+ def copy(self):
+ return Edge(
+ lbl = self.lbl,
+ kind = self.kind,
+ src = self.src,
+ dst = self.dst,
+ loc = self.loc)
+ def __repr__(self):
+ return '%s (%s) => %s' % (self.src, self.lbl, self.dst)
+ def is_root(self):
+ return self.kind == 2
+ def is_weak(self):
+ return self.kind == 0
+ def keeps_alive(self):
+ return self.kind > 0
+ def is_subclass(self):
+ return self.lbl.startswith('<subclass>')
+ def is_super(self):
+ return self.lbl.startswith('<super>')
+
+def parse_file(filename):
+ obj = json.load(open(filename))
+ return obj
+
+def build_graphs_in_dir(dirname):
+ # TODO: Use plateform independent code, eg, os.walk
+ files = subprocess.check_output(
+ ['find', dirname, '-name', '*.graph.json']).split('\n')
+ log("Found %d files" % len(files))
+ for f in files:
+ f.strip()
+ if len(f) < 1:
+ continue
+ build_graph(f)
+
+def build_graph(filename):
+ for decl in parse_file(filename):
+ if decl.has_key('name'):
+ # Add/update a node entry
+ name = decl['name']
+ node = get_node(name)
+ node.update_node(decl)
+ else:
+ # Add/update an edge entry
+ name = decl['src']
+ node = get_node(name)
+ node.update_edge(decl)
+
+# Copy all non-weak edges from super classes to their subclasses.
+# This causes all fields of a super to be considered fields of a
+# derived class without tranitively relating derived classes with
+# each other. For example, if B <: A, C <: A, and for some D, D => B,
+# we don't want that to entail that D => C.
+def copy_super_edges(edge):
+ if edge.is_weak() or not edge.is_super():
+ return
+ inc_copy()
+ # Make the super-class edge weak (prohibits processing twice).
+ edge.kind = 0
+ # If the super class is not in our graph exit early.
+ super_node = graph.get(edge.dst)
+ if super_node is None: return
+ # Recursively copy all super-class edges.
+ for e in super_node.super_edges():
+ copy_super_edges(e)
+ # Copy strong super-class edges (ignoring sub-class edges) to the sub class.
+ sub_node = graph[edge.src]
+ for e in super_node.edges.itervalues():
+ if e.keeps_alive() and not e.is_subclass():
+ new_edge = e.copy()
+ new_edge.src = sub_node.name
+ new_edge.lbl = '%s <: %s' % (super_node.name, e.lbl)
+ sub_node.edges[new_edge.key] = new_edge
+ # Add a strong sub-class edge.
+ sub_edge = edge.copy()
+ sub_edge.kind = 1
+ sub_edge.lbl = '<subclass>'
+ sub_edge.src = super_node.name
+ sub_edge.dst = sub_node.name
+ super_node.edges[sub_edge.key] = sub_edge
+
+def complete_graph():
+ for node in graph.itervalues():
+ for edge in node.super_edges():
+ copy_super_edges(edge)
+ for edge in node.edges.itervalues():
+ if edge.is_root():
+ roots.append(edge)
+ log("Copied edges down <super> edges for %d graph nodes" % global_inc_copy)
+
+def reset_graph():
+ for n in graph.itervalues():
+ n.reset()
+
+def shortest_path(start, end):
+ start.cost = 0
+ minlist = [start]
+ while len(minlist) > 0:
+ minlist.sort(key=lambda n: -n.cost)
+ current = minlist.pop()
+ current.visited = True
+ if current == end or current.cost >= end.cost + 1:
+ return
+ for e in current.edges.itervalues():
+ if not e.keeps_alive():
+ continue
+ dst = graph.get(e.dst)
+ if dst is None or dst.visited:
+ continue
+ if current.cost < dst.cost:
+ dst.cost = current.cost + 1
+ dst.path = e
+ minlist.append(dst)
+
+def detect_cycles():
+ for root_edge in roots:
+ reset_graph()
+ src = graph[root_edge.src]
+ dst = graph.get(root_edge.dst)
+ if dst is None:
+ print "\nPersistent root to incomplete destination object:"
+ print root_edge
+ set_reported_error(True)
+ continue
+ # Find the shortest path from the root target (dst) to its host (src)
+ shortest_path(dst, src)
+ if src.cost < sys.maxint:
+ report_cycle(root_edge)
+
+def report_cycle(root_edge):
+ dst = graph[root_edge.dst]
+ print "\nFound a potentially leaking cycle starting from a GC root:"
+ path = []
+ edge = root_edge
+ dst.path = None
+ while edge:
+ path.append(edge)
+ edge = graph[edge.src].path
+ path.append(root_edge)
+ path.reverse()
+ # Find the max loc length for pretty printing.
+ max_loc = 0
+ for p in path:
+ if len(p.loc) > max_loc:
+ max_loc = len(p.loc)
+ for p in path[:-1]:
+ print (p.loc + ':').ljust(max_loc + 1), p
+ set_reported_error(True)
+
+def main():
+ global args
+ args = parser.parse_args()
+ if not args.detect_cycles:
+ print "Please select an operation to perform (eg, -c to detect cycles)"
+ parser.print_help()
+ return 1
+ if args.use_stdin:
+ log("Reading files from stdin")
+ for f in sys.stdin:
+ build_graph(f.strip())
+ else:
+ log("Reading files and directories from command line")
+ if len(args.files) == 0:
+ print "Please provide files or directores for building the graph"
+ parser.print_help()
+ return 1
+ for f in args.files:
+ if os.path.isdir(f):
+ log("Building graph from files in directory: " + f)
+ build_graphs_in_dir(f)
+ else:
+ log("Building graph from file: " + f)
+ build_graph(f)
+ log("Completing graph construction (%d graph nodes)" % len(graph))
+ complete_graph()
+ if args.detect_cycles:
+ log("Detecting cycles containg GC roots")
+ detect_cycles()
+ if reported_error():
+ return 1
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main())
« no previous file with comments | « tools/clang/blink_gc_plugin/RecordInfo.cpp ('k') | tools/clang/blink_gc_plugin/tests/cycle_ptrs.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698