From: Artem Dergachev Date: Tue, 13 Aug 2019 23:04:53 +0000 (+0000) Subject: [analyzer] exploded-graph-rewriter: Implement manual graph trimming. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=f2e3097678760d86edd3fe536000e4ada4fd3ed0;p=clang [analyzer] exploded-graph-rewriter: Implement manual graph trimming. When -trim-egraph is unavailable (say, when you're debugging a crash on a real-world code that takes too long to reduce), it makes sense to view the untrimmed graph up to the crashing node's predecessor, then dump the ID (or a pointer) of the node in the attached debugger, and then trim the dumped graph in order to keep only paths from the root to the node. The newly added --to flag does exactly that: $ exploded-graph-rewriter.py ExprEngine.dot --to 0x12229acd0 Multiple nodes can be specified. Stable IDs of nodes can be used instead of pointers. Differential Revision: https://reviews.llvm.org/D65345 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@368768 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/test/Analysis/exploded-graph-rewriter/trimmers.dot b/test/Analysis/exploded-graph-rewriter/trimmers.dot index 226c63911c..8bdef649e0 100644 --- a/test/Analysis/exploded-graph-rewriter/trimmers.dot +++ b/test/Analysis/exploded-graph-rewriter/trimmers.dot @@ -1,7 +1,17 @@ // RUN: %exploded_graph_rewriter %s \ -// RUN: | FileCheck %s -check-prefixes=CHECK,BASIC +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,THREE,FOUR // RUN: %exploded_graph_rewriter -s %s \ -// RUN: | FileCheck %s -check-prefixes=CHECK,SINGLE +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,NOTHREE,FOUR +// RUN: %exploded_graph_rewriter --to=0x2 %s \ +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,NOTHREE,NOFOUR +// RUN: %exploded_graph_rewriter --to 2 %s \ +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,NOTHREE,NOFOUR +// RUN: %exploded_graph_rewriter --to 2,3 %s \ +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,THREE,NOFOUR +// RUN: %exploded_graph_rewriter --to 4 %s \ +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,THREE,FOUR +// RUN: %exploded_graph_rewriter --to 4 -s %s \ +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,NOTHREE,FOUR // FIXME: Substitution doesn't seem to work on Windows. // UNSUPPORTED: system-windows @@ -22,16 +32,16 @@ Node0x4 [shape=record,label= "{{ "node_id": 4, "pointer": "0x4", "has_report": false, "is_sink": false, "program_state": null, "program_points": []}\l}"]; -// CHECK: Node0x1 -> Node0x2; Node0x1 -> Node0x2; - -// BASIC: Node0x1 -> Node0x3; -// SINGLE-NOT: Node0x1 -> Node0x3; Node0x1 -> Node0x3; - -// CHECK: Node0x2 -> Node0x4; Node0x2 -> Node0x4; - -// BASIC: Node0x3 -> Node0x4; -// SINGLE-NOT: Node0x3 -> Node0x4; Node0x3 -> Node0x4; + +// ONE: Node0x1 +// NOTONE-NOT: Node0x1 +// TWO: Node0x2 +// NOTTWO-NOT: Node0x2 +// THREE: Node0x3 +// NOTTHREE-NOT: Node0x3 +// FOUR: Node0x4 +// NOTFOUR-NOT: Node0x4 diff --git a/utils/analyzer/exploded-graph-rewriter.py b/utils/analyzer/exploded-graph-rewriter.py index 3fa8ba0c9a..d612cfc3ba 100755 --- a/utils/analyzer/exploded-graph-rewriter.py +++ b/utils/analyzer/exploded-graph-rewriter.py @@ -914,6 +914,52 @@ class SinglePathTrimmer(object): for node_id in visited_nodes} +# TargetedTrimmer keeps paths that lead to specific nodes and discards all +# other paths. Useful when you cannot use -trim-egraph (e.g. when debugging +# a crash). +class TargetedTrimmer(object): + def __init__(self, target_nodes): + super(TargetedTrimmer, self).__init__() + self._target_nodes = target_nodes + + @staticmethod + def parse_target_node(node, graph): + if node.startswith('0x'): + ret = 'Node' + node + assert ret in graph.nodes + return ret + else: + for other_id in graph.nodes: + other = graph.nodes[other_id] + if other.node_id == int(node): + return other_id + + @staticmethod + def parse_target_nodes(target_nodes, graph): + return [TargetedTrimmer.parse_target_node(node, graph) + for node in target_nodes.split(',')] + + def trim(self, graph): + queue = self._target_nodes + visited_nodes = set() + + while len(queue) > 0: + node_id = queue.pop() + visited_nodes.add(node_id) + node = graph.nodes[node_id] + for pred_id in node.predecessors: + if pred_id not in visited_nodes: + queue.append(pred_id) + graph.nodes = {node_id: graph.nodes[node_id] + for node_id in visited_nodes} + for node_id in graph.nodes: + node = graph.nodes[node_id] + node.successors = [succ_id for succ_id in node.successors + if succ_id in visited_nodes] + node.predecessors = [succ_id for succ_id in node.predecessors + if succ_id in visited_nodes] + + #===-----------------------------------------------------------------------===# # The entry point to the script. #===-----------------------------------------------------------------------===# @@ -939,6 +985,11 @@ def main(): help='only display the leftmost path in the graph ' '(useful for trimmed graphs that still ' 'branch too much)') + parser.add_argument('--to', type=str, default=None, + help='only display execution paths from the root ' + 'to the given comma-separated list of nodes ' + 'identified by a pointer or a stable ID; ' + 'compatible with --single-path') parser.add_argument('--dark', action='store_const', dest='dark', const=True, default=False, help='dark mode') @@ -960,6 +1011,9 @@ def main(): graph.add_raw_line(raw_line) trimmers = [] + if args.to is not None: + trimmers.append(TargetedTrimmer( + TargetedTrimmer.parse_target_nodes(args.to, graph))) if args.single_path: trimmers.append(SinglePathTrimmer())