1 1.1 joerg #!/usr/bin/env python 2 1.1 joerg # 3 1.1 joerg #===- exploded-graph-rewriter.py - ExplodedGraph dump tool -----*- python -*--# 4 1.1 joerg # 5 1.1 joerg # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 6 1.1 joerg # See https://llvm.org/LICENSE.txt for license information. 7 1.1 joerg # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 8 1.1 joerg # 9 1.1 joerg #===-----------------------------------------------------------------------===# 10 1.1 joerg 11 1.1 joerg 12 1.1 joerg from __future__ import print_function 13 1.1 joerg 14 1.1 joerg import argparse 15 1.1 joerg import collections 16 1.1 joerg import difflib 17 1.1 joerg import json 18 1.1 joerg import logging 19 1.1 joerg import os 20 1.1 joerg import re 21 1.1.1.2 joerg import sys 22 1.1 joerg 23 1.1 joerg 24 1.1 joerg #===-----------------------------------------------------------------------===# 25 1.1 joerg # These data structures represent a deserialized ExplodedGraph. 26 1.1 joerg #===-----------------------------------------------------------------------===# 27 1.1 joerg 28 1.1 joerg 29 1.1 joerg # A helper function for finding the difference between two dictionaries. 30 1.1 joerg def diff_dicts(curr, prev): 31 1.1 joerg removed = [k for k in prev if k not in curr or curr[k] != prev[k]] 32 1.1 joerg added = [k for k in curr if k not in prev or curr[k] != prev[k]] 33 1.1 joerg return (removed, added) 34 1.1 joerg 35 1.1 joerg 36 1.1 joerg # Represents any program state trait that is a dictionary of key-value pairs. 37 1.1.1.2 joerg class GenericMap: 38 1.1 joerg def __init__(self, items): 39 1.1 joerg self.generic_map = collections.OrderedDict(items) 40 1.1 joerg 41 1.1 joerg def diff(self, prev): 42 1.1 joerg return diff_dicts(self.generic_map, prev.generic_map) 43 1.1 joerg 44 1.1 joerg def is_different(self, prev): 45 1.1 joerg removed, added = self.diff(prev) 46 1.1 joerg return len(removed) != 0 or len(added) != 0 47 1.1 joerg 48 1.1 joerg 49 1.1 joerg # A deserialized source location. 50 1.1.1.2 joerg class SourceLocation: 51 1.1 joerg def __init__(self, json_loc): 52 1.1 joerg logging.debug('json: %s' % json_loc) 53 1.1 joerg self.line = json_loc['line'] 54 1.1 joerg self.col = json_loc['column'] 55 1.1 joerg self.filename = os.path.basename(json_loc['file']) \ 56 1.1 joerg if 'file' in json_loc else '(main file)' 57 1.1 joerg self.spelling = SourceLocation(json_loc['spelling']) \ 58 1.1 joerg if 'spelling' in json_loc else None 59 1.1 joerg 60 1.1 joerg def is_macro(self): 61 1.1 joerg return self.spelling is not None 62 1.1 joerg 63 1.1 joerg 64 1.1 joerg # A deserialized program point. 65 1.1.1.2 joerg class ProgramPoint: 66 1.1 joerg def __init__(self, json_pp): 67 1.1 joerg self.kind = json_pp['kind'] 68 1.1 joerg self.tag = json_pp['tag'] 69 1.1 joerg self.node_id = json_pp['node_id'] 70 1.1 joerg self.is_sink = bool(json_pp['is_sink']) 71 1.1 joerg self.has_report = bool(json_pp['has_report']) 72 1.1 joerg if self.kind == 'Edge': 73 1.1 joerg self.src_id = json_pp['src_id'] 74 1.1 joerg self.dst_id = json_pp['dst_id'] 75 1.1 joerg elif self.kind == 'Statement': 76 1.1 joerg logging.debug(json_pp) 77 1.1 joerg self.stmt_kind = json_pp['stmt_kind'] 78 1.1 joerg self.cast_kind = json_pp['cast_kind'] \ 79 1.1 joerg if 'cast_kind' in json_pp else None 80 1.1 joerg self.stmt_point_kind = json_pp['stmt_point_kind'] 81 1.1 joerg self.stmt_id = json_pp['stmt_id'] 82 1.1 joerg self.pointer = json_pp['pointer'] 83 1.1 joerg self.pretty = json_pp['pretty'] 84 1.1 joerg self.loc = SourceLocation(json_pp['location']) \ 85 1.1 joerg if json_pp['location'] is not None else None 86 1.1 joerg elif self.kind == 'BlockEntrance': 87 1.1 joerg self.block_id = json_pp['block_id'] 88 1.1 joerg 89 1.1 joerg 90 1.1 joerg # A single expression acting as a key in a deserialized Environment. 91 1.1.1.2 joerg class EnvironmentBindingKey: 92 1.1 joerg def __init__(self, json_ek): 93 1.1 joerg # CXXCtorInitializer is not a Stmt! 94 1.1 joerg self.stmt_id = json_ek['stmt_id'] if 'stmt_id' in json_ek \ 95 1.1 joerg else json_ek['init_id'] 96 1.1 joerg self.pretty = json_ek['pretty'] 97 1.1 joerg self.kind = json_ek['kind'] if 'kind' in json_ek else None 98 1.1 joerg 99 1.1 joerg def _key(self): 100 1.1 joerg return self.stmt_id 101 1.1 joerg 102 1.1 joerg def __eq__(self, other): 103 1.1 joerg return self._key() == other._key() 104 1.1 joerg 105 1.1 joerg def __hash__(self): 106 1.1 joerg return hash(self._key()) 107 1.1 joerg 108 1.1 joerg 109 1.1 joerg # Deserialized description of a location context. 110 1.1.1.2 joerg class LocationContext: 111 1.1 joerg def __init__(self, json_frame): 112 1.1 joerg self.lctx_id = json_frame['lctx_id'] 113 1.1 joerg self.caption = json_frame['location_context'] 114 1.1 joerg self.decl = json_frame['calling'] 115 1.1 joerg self.loc = SourceLocation(json_frame['location']) \ 116 1.1 joerg if json_frame['location'] is not None else None 117 1.1 joerg 118 1.1 joerg def _key(self): 119 1.1 joerg return self.lctx_id 120 1.1 joerg 121 1.1 joerg def __eq__(self, other): 122 1.1 joerg return self._key() == other._key() 123 1.1 joerg 124 1.1 joerg def __hash__(self): 125 1.1 joerg return hash(self._key()) 126 1.1 joerg 127 1.1 joerg 128 1.1 joerg # A group of deserialized Environment bindings that correspond to a specific 129 1.1 joerg # location context. 130 1.1.1.2 joerg class EnvironmentFrame: 131 1.1 joerg def __init__(self, json_frame): 132 1.1 joerg self.location_context = LocationContext(json_frame) 133 1.1 joerg self.bindings = collections.OrderedDict( 134 1.1 joerg [(EnvironmentBindingKey(b), 135 1.1 joerg b['value']) for b in json_frame['items']] 136 1.1 joerg if json_frame['items'] is not None else []) 137 1.1 joerg 138 1.1 joerg def diff_bindings(self, prev): 139 1.1 joerg return diff_dicts(self.bindings, prev.bindings) 140 1.1 joerg 141 1.1 joerg def is_different(self, prev): 142 1.1 joerg removed, added = self.diff_bindings(prev) 143 1.1 joerg return len(removed) != 0 or len(added) != 0 144 1.1 joerg 145 1.1 joerg 146 1.1 joerg # A deserialized Environment. This class can also hold other entities that 147 1.1 joerg # are similar to Environment, such as Objects Under Construction. 148 1.1.1.2 joerg class GenericEnvironment: 149 1.1 joerg def __init__(self, json_e): 150 1.1 joerg self.frames = [EnvironmentFrame(f) for f in json_e] 151 1.1 joerg 152 1.1 joerg def diff_frames(self, prev): 153 1.1 joerg # TODO: It's difficult to display a good diff when frame numbers shift. 154 1.1 joerg if len(self.frames) != len(prev.frames): 155 1.1 joerg return None 156 1.1 joerg 157 1.1 joerg updated = [] 158 1.1 joerg for i in range(len(self.frames)): 159 1.1 joerg f = self.frames[i] 160 1.1 joerg prev_f = prev.frames[i] 161 1.1 joerg if f.location_context == prev_f.location_context: 162 1.1 joerg if f.is_different(prev_f): 163 1.1 joerg updated.append(i) 164 1.1 joerg else: 165 1.1 joerg # We have the whole frame replaced with another frame. 166 1.1 joerg # TODO: Produce a nice diff. 167 1.1 joerg return None 168 1.1 joerg 169 1.1 joerg # TODO: Add support for added/removed. 170 1.1 joerg return updated 171 1.1 joerg 172 1.1 joerg def is_different(self, prev): 173 1.1 joerg updated = self.diff_frames(prev) 174 1.1 joerg return updated is None or len(updated) > 0 175 1.1 joerg 176 1.1 joerg 177 1.1 joerg # A single binding key in a deserialized RegionStore cluster. 178 1.1.1.2 joerg class StoreBindingKey: 179 1.1 joerg def __init__(self, json_sk): 180 1.1 joerg self.kind = json_sk['kind'] 181 1.1 joerg self.offset = json_sk['offset'] 182 1.1 joerg 183 1.1 joerg def _key(self): 184 1.1 joerg return (self.kind, self.offset) 185 1.1 joerg 186 1.1 joerg def __eq__(self, other): 187 1.1 joerg return self._key() == other._key() 188 1.1 joerg 189 1.1 joerg def __hash__(self): 190 1.1 joerg return hash(self._key()) 191 1.1 joerg 192 1.1 joerg 193 1.1 joerg # A single cluster of the deserialized RegionStore. 194 1.1.1.2 joerg class StoreCluster: 195 1.1 joerg def __init__(self, json_sc): 196 1.1 joerg self.base_region = json_sc['cluster'] 197 1.1 joerg self.bindings = collections.OrderedDict( 198 1.1 joerg [(StoreBindingKey(b), b['value']) for b in json_sc['items']]) 199 1.1 joerg 200 1.1 joerg def diff_bindings(self, prev): 201 1.1 joerg return diff_dicts(self.bindings, prev.bindings) 202 1.1 joerg 203 1.1 joerg def is_different(self, prev): 204 1.1 joerg removed, added = self.diff_bindings(prev) 205 1.1 joerg return len(removed) != 0 or len(added) != 0 206 1.1 joerg 207 1.1 joerg 208 1.1 joerg # A deserialized RegionStore. 209 1.1.1.2 joerg class Store: 210 1.1 joerg def __init__(self, json_s): 211 1.1 joerg self.ptr = json_s['pointer'] 212 1.1 joerg self.clusters = collections.OrderedDict( 213 1.1 joerg [(c['pointer'], StoreCluster(c)) for c in json_s['items']]) 214 1.1 joerg 215 1.1 joerg def diff_clusters(self, prev): 216 1.1 joerg removed = [k for k in prev.clusters if k not in self.clusters] 217 1.1 joerg added = [k for k in self.clusters if k not in prev.clusters] 218 1.1 joerg updated = [k for k in prev.clusters if k in self.clusters 219 1.1 joerg and prev.clusters[k].is_different(self.clusters[k])] 220 1.1 joerg return (removed, added, updated) 221 1.1 joerg 222 1.1 joerg def is_different(self, prev): 223 1.1 joerg removed, added, updated = self.diff_clusters(prev) 224 1.1 joerg return len(removed) != 0 or len(added) != 0 or len(updated) != 0 225 1.1 joerg 226 1.1 joerg 227 1.1 joerg # Deserialized messages from a single checker in a single program state. 228 1.1 joerg # Basically a list of raw strings. 229 1.1.1.2 joerg class CheckerLines: 230 1.1 joerg def __init__(self, json_lines): 231 1.1 joerg self.lines = json_lines 232 1.1 joerg 233 1.1 joerg def diff_lines(self, prev): 234 1.1 joerg lines = difflib.ndiff(prev.lines, self.lines) 235 1.1 joerg return [l.strip() for l in lines 236 1.1 joerg if l.startswith('+') or l.startswith('-')] 237 1.1 joerg 238 1.1 joerg def is_different(self, prev): 239 1.1 joerg return len(self.diff_lines(prev)) > 0 240 1.1 joerg 241 1.1 joerg 242 1.1 joerg # Deserialized messages of all checkers, separated by checker. 243 1.1.1.2 joerg class CheckerMessages: 244 1.1 joerg def __init__(self, json_m): 245 1.1 joerg self.items = collections.OrderedDict( 246 1.1 joerg [(m['checker'], CheckerLines(m['messages'])) for m in json_m]) 247 1.1 joerg 248 1.1 joerg def diff_messages(self, prev): 249 1.1 joerg removed = [k for k in prev.items if k not in self.items] 250 1.1 joerg added = [k for k in self.items if k not in prev.items] 251 1.1 joerg updated = [k for k in prev.items if k in self.items 252 1.1 joerg and prev.items[k].is_different(self.items[k])] 253 1.1 joerg return (removed, added, updated) 254 1.1 joerg 255 1.1 joerg def is_different(self, prev): 256 1.1 joerg removed, added, updated = self.diff_messages(prev) 257 1.1 joerg return len(removed) != 0 or len(added) != 0 or len(updated) != 0 258 1.1 joerg 259 1.1 joerg 260 1.1 joerg # A deserialized program state. 261 1.1.1.2 joerg class ProgramState: 262 1.1 joerg def __init__(self, state_id, json_ps): 263 1.1 joerg logging.debug('Adding ProgramState ' + str(state_id)) 264 1.1 joerg 265 1.1 joerg if json_ps is None: 266 1.1 joerg json_ps = { 267 1.1 joerg 'store': None, 268 1.1 joerg 'environment': None, 269 1.1 joerg 'constraints': None, 270 1.1 joerg 'dynamic_types': None, 271 1.1 joerg 'constructing_objects': None, 272 1.1 joerg 'checker_messages': None 273 1.1 joerg } 274 1.1 joerg 275 1.1 joerg self.state_id = state_id 276 1.1 joerg 277 1.1 joerg self.store = Store(json_ps['store']) \ 278 1.1 joerg if json_ps['store'] is not None else None 279 1.1 joerg 280 1.1 joerg self.environment = \ 281 1.1 joerg GenericEnvironment(json_ps['environment']['items']) \ 282 1.1 joerg if json_ps['environment'] is not None else None 283 1.1 joerg 284 1.1 joerg self.constraints = GenericMap([ 285 1.1 joerg (c['symbol'], c['range']) for c in json_ps['constraints'] 286 1.1 joerg ]) if json_ps['constraints'] is not None else None 287 1.1 joerg 288 1.1 joerg self.dynamic_types = GenericMap([ 289 1.1 joerg (t['region'], '%s%s' % (t['dyn_type'], 290 1.1 joerg ' (or a sub-class)' 291 1.1 joerg if t['sub_classable'] else '')) 292 1.1 joerg for t in json_ps['dynamic_types']]) \ 293 1.1 joerg if json_ps['dynamic_types'] is not None else None 294 1.1 joerg 295 1.1 joerg self.constructing_objects = \ 296 1.1 joerg GenericEnvironment(json_ps['constructing_objects']) \ 297 1.1 joerg if json_ps['constructing_objects'] is not None else None 298 1.1 joerg 299 1.1 joerg self.checker_messages = CheckerMessages(json_ps['checker_messages']) \ 300 1.1 joerg if json_ps['checker_messages'] is not None else None 301 1.1 joerg 302 1.1 joerg 303 1.1 joerg # A deserialized exploded graph node. Has a default constructor because it 304 1.1 joerg # may be referenced as part of an edge before its contents are deserialized, 305 1.1 joerg # and in this moment we already need a room for predecessors and successors. 306 1.1.1.2 joerg class ExplodedNode: 307 1.1 joerg def __init__(self): 308 1.1 joerg self.predecessors = [] 309 1.1 joerg self.successors = [] 310 1.1 joerg 311 1.1 joerg def construct(self, node_id, json_node): 312 1.1 joerg logging.debug('Adding ' + node_id) 313 1.1 joerg self.ptr = node_id[4:] 314 1.1 joerg self.points = [ProgramPoint(p) for p in json_node['program_points']] 315 1.1 joerg self.node_id = self.points[-1].node_id 316 1.1 joerg self.state = ProgramState(json_node['state_id'], 317 1.1 joerg json_node['program_state'] 318 1.1 joerg if json_node['program_state'] is not None else None); 319 1.1 joerg 320 1.1 joerg assert self.node_name() == node_id 321 1.1 joerg 322 1.1 joerg def node_name(self): 323 1.1 joerg return 'Node' + self.ptr 324 1.1 joerg 325 1.1 joerg 326 1.1 joerg # A deserialized ExplodedGraph. Constructed by consuming a .dot file 327 1.1 joerg # line-by-line. 328 1.1.1.2 joerg class ExplodedGraph: 329 1.1 joerg # Parse .dot files with regular expressions. 330 1.1 joerg node_re = re.compile( 331 1.1 joerg '^(Node0x[0-9a-f]*) \\[shape=record,.*label="{(.*)\\\\l}"\\];$') 332 1.1 joerg edge_re = re.compile( 333 1.1 joerg '^(Node0x[0-9a-f]*) -> (Node0x[0-9a-f]*);$') 334 1.1 joerg 335 1.1 joerg def __init__(self): 336 1.1 joerg self.nodes = collections.defaultdict(ExplodedNode) 337 1.1 joerg self.root_id = None 338 1.1 joerg self.incomplete_line = '' 339 1.1 joerg 340 1.1 joerg def add_raw_line(self, raw_line): 341 1.1 joerg if raw_line.startswith('//'): 342 1.1 joerg return 343 1.1 joerg 344 1.1 joerg # Allow line breaks by waiting for ';'. This is not valid in 345 1.1 joerg # a .dot file, but it is useful for writing tests. 346 1.1 joerg if len(raw_line) > 0 and raw_line[-1] != ';': 347 1.1 joerg self.incomplete_line += raw_line 348 1.1 joerg return 349 1.1 joerg raw_line = self.incomplete_line + raw_line 350 1.1 joerg self.incomplete_line = '' 351 1.1 joerg 352 1.1 joerg # Apply regexps one by one to see if it's a node or an edge 353 1.1 joerg # and extract contents if necessary. 354 1.1 joerg logging.debug('Line: ' + raw_line) 355 1.1 joerg result = self.edge_re.match(raw_line) 356 1.1 joerg if result is not None: 357 1.1 joerg logging.debug('Classified as edge line.') 358 1.1 joerg pred = result.group(1) 359 1.1 joerg succ = result.group(2) 360 1.1 joerg self.nodes[pred].successors.append(succ) 361 1.1 joerg self.nodes[succ].predecessors.append(pred) 362 1.1 joerg return 363 1.1 joerg result = self.node_re.match(raw_line) 364 1.1 joerg if result is not None: 365 1.1 joerg logging.debug('Classified as node line.') 366 1.1 joerg node_id = result.group(1) 367 1.1 joerg if len(self.nodes) == 0: 368 1.1 joerg self.root_id = node_id 369 1.1 joerg # Note: when writing tests you don't need to escape everything, 370 1.1 joerg # even though in a valid dot file everything is escaped. 371 1.1.1.2 joerg node_label = result.group(2).replace(' ', '') \ 372 1.1 joerg .replace('\\"', '"') \ 373 1.1 joerg .replace('\\{', '{') \ 374 1.1 joerg .replace('\\}', '}') \ 375 1.1 joerg .replace('\\\\', '\\') \ 376 1.1 joerg .replace('\\|', '|') \ 377 1.1 joerg .replace('\\<', '\\\\<') \ 378 1.1 joerg .replace('\\>', '\\\\>') \ 379 1.1 joerg .rstrip(',') 380 1.1.1.2 joerg # Handle `\l` separately because a string literal can be in code 381 1.1.1.2 joerg # like "string\\literal" with the `\l` inside. 382 1.1.1.2 joerg # Also on Windows macros __FILE__ produces specific delimiters `\` 383 1.1.1.2 joerg # and a directory or file may starts with the letter `l`. 384 1.1.1.2 joerg # Find all `\l` (like `,\l`, `}\l`, `[\l`) except `\\l`, 385 1.1.1.2 joerg # because the literal as a rule containes multiple `\` before `\l`. 386 1.1.1.2 joerg node_label = re.sub(r'(?<!\\)\\l', '', node_label) 387 1.1 joerg logging.debug(node_label) 388 1.1 joerg json_node = json.loads(node_label) 389 1.1 joerg self.nodes[node_id].construct(node_id, json_node) 390 1.1 joerg return 391 1.1 joerg logging.debug('Skipping.') 392 1.1 joerg 393 1.1 joerg 394 1.1 joerg #===-----------------------------------------------------------------------===# 395 1.1 joerg # Visitors traverse a deserialized ExplodedGraph and do different things 396 1.1 joerg # with every node and edge. 397 1.1 joerg #===-----------------------------------------------------------------------===# 398 1.1 joerg 399 1.1 joerg 400 1.1 joerg # A visitor that dumps the ExplodedGraph into a DOT file with fancy HTML-based 401 1.1 joerg # syntax highlighing. 402 1.1.1.2 joerg class DotDumpVisitor: 403 1.1 joerg def __init__(self, do_diffs, dark_mode, gray_mode, 404 1.1 joerg topo_mode, dump_dot_only): 405 1.1 joerg self._do_diffs = do_diffs 406 1.1 joerg self._dark_mode = dark_mode 407 1.1 joerg self._gray_mode = gray_mode 408 1.1 joerg self._topo_mode = topo_mode 409 1.1 joerg self._dump_dot_only = dump_dot_only 410 1.1 joerg self._output = [] 411 1.1 joerg 412 1.1 joerg def _dump_raw(self, s): 413 1.1 joerg if self._dump_dot_only: 414 1.1 joerg print(s, end='') 415 1.1 joerg else: 416 1.1 joerg self._output.append(s) 417 1.1 joerg 418 1.1 joerg def output(self): 419 1.1 joerg assert not self._dump_dot_only 420 1.1.1.2 joerg if sys.version_info[0] > 2 and sys.version_info[1] >= 5: 421 1.1.1.2 joerg return ''.join(self._output).encode() 422 1.1.1.2 joerg else: 423 1.1.1.2 joerg return ''.join(self._output) 424 1.1 joerg 425 1.1 joerg def _dump(self, s): 426 1.1 joerg s = s.replace('&', '&') \ 427 1.1 joerg .replace('{', '\\{') \ 428 1.1 joerg .replace('}', '\\}') \ 429 1.1 joerg .replace('\\<', '<') \ 430 1.1 joerg .replace('\\>', '>') \ 431 1.1 joerg .replace('|', '\\|') 432 1.1.1.2 joerg s = re.sub(r'(?<!\\)\\l', '<br />', s) 433 1.1 joerg if self._gray_mode: 434 1.1 joerg s = re.sub(r'<font color="[a-z0-9]*">', '', s) 435 1.1 joerg s = re.sub(r'</font>', '', s) 436 1.1 joerg self._dump_raw(s) 437 1.1 joerg 438 1.1 joerg @staticmethod 439 1.1 joerg def _diff_plus_minus(is_added): 440 1.1 joerg if is_added is None: 441 1.1 joerg return '' 442 1.1 joerg if is_added: 443 1.1 joerg return '<font color="forestgreen">+</font>' 444 1.1 joerg return '<font color="red">-</font>' 445 1.1 joerg 446 1.1 joerg @staticmethod 447 1.1 joerg def _short_pretty(s): 448 1.1 joerg if s is None: 449 1.1 joerg return None 450 1.1 joerg if len(s) < 20: 451 1.1 joerg return s 452 1.1 joerg left = s.find('{') 453 1.1 joerg right = s.rfind('}') 454 1.1 joerg if left == -1 or right == -1 or left >= right: 455 1.1 joerg return s 456 1.1 joerg candidate = s[0:left + 1] + ' ... ' + s[right:] 457 1.1 joerg if len(candidate) >= len(s): 458 1.1 joerg return s 459 1.1 joerg return candidate 460 1.1 joerg 461 1.1 joerg @staticmethod 462 1.1 joerg def _make_sloc(loc): 463 1.1 joerg if loc is None: 464 1.1 joerg return '<i>Invalid Source Location</i>' 465 1.1 joerg 466 1.1 joerg def make_plain_loc(loc): 467 1.1 joerg return '%s:<b>%s</b>:<b>%s</b>' \ 468 1.1 joerg % (loc.filename, loc.line, loc.col) 469 1.1 joerg 470 1.1 joerg if loc.is_macro(): 471 1.1 joerg return '%s <font color="royalblue1">' \ 472 1.1 joerg '(<i>spelling at </i> %s)</font>' \ 473 1.1 joerg % (make_plain_loc(loc), make_plain_loc(loc.spelling)) 474 1.1 joerg 475 1.1 joerg return make_plain_loc(loc) 476 1.1 joerg 477 1.1 joerg def visit_begin_graph(self, graph): 478 1.1 joerg self._graph = graph 479 1.1 joerg self._dump_raw('digraph "ExplodedGraph" {\n') 480 1.1 joerg if self._dark_mode: 481 1.1 joerg self._dump_raw('bgcolor="gray10";\n') 482 1.1 joerg self._dump_raw('label="";\n') 483 1.1 joerg 484 1.1 joerg def visit_program_point(self, p): 485 1.1 joerg if p.kind in ['Edge', 'BlockEntrance', 'BlockExit']: 486 1.1 joerg color = 'gold3' 487 1.1 joerg elif p.kind in ['PreStmtPurgeDeadSymbols', 488 1.1 joerg 'PostStmtPurgeDeadSymbols']: 489 1.1 joerg color = 'red' 490 1.1 joerg elif p.kind in ['CallEnter', 'CallExitBegin', 'CallExitEnd']: 491 1.1 joerg color = 'dodgerblue' if self._dark_mode else 'blue' 492 1.1 joerg elif p.kind in ['Statement']: 493 1.1 joerg color = 'cyan4' 494 1.1 joerg else: 495 1.1 joerg color = 'forestgreen' 496 1.1 joerg 497 1.1 joerg self._dump('<tr><td align="left">%s.</td>' % p.node_id) 498 1.1 joerg 499 1.1 joerg if p.kind == 'Statement': 500 1.1 joerg # This avoids pretty-printing huge statements such as CompoundStmt. 501 1.1 joerg # Such statements show up only at [Pre|Post]StmtPurgeDeadSymbols 502 1.1 joerg skip_pretty = 'PurgeDeadSymbols' in p.stmt_point_kind 503 1.1 joerg stmt_color = 'cyan3' 504 1.1 joerg self._dump('<td align="left" width="0">%s:</td>' 505 1.1 joerg '<td align="left" width="0"><font color="%s">' 506 1.1 joerg '%s</font> </td>' 507 1.1 joerg '<td align="left"><i>S%s</i></td>' 508 1.1 joerg '<td align="left"><font color="%s">%s</font></td>' 509 1.1 joerg '<td align="left">%s</td></tr>' 510 1.1 joerg % (self._make_sloc(p.loc), color, 511 1.1 joerg '%s (%s)' % (p.stmt_kind, p.cast_kind) 512 1.1 joerg if p.cast_kind is not None else p.stmt_kind, 513 1.1 joerg p.stmt_id, stmt_color, p.stmt_point_kind, 514 1.1 joerg self._short_pretty(p.pretty) 515 1.1 joerg if not skip_pretty else '')) 516 1.1 joerg elif p.kind == 'Edge': 517 1.1 joerg self._dump('<td width="0"></td>' 518 1.1 joerg '<td align="left" width="0">' 519 1.1 joerg '<font color="%s">%s</font></td><td align="left">' 520 1.1 joerg '[B%d] -\\> [B%d]</td></tr>' 521 1.1 joerg % (color, 'BlockEdge', p.src_id, p.dst_id)) 522 1.1 joerg elif p.kind == 'BlockEntrance': 523 1.1 joerg self._dump('<td width="0"></td>' 524 1.1 joerg '<td align="left" width="0">' 525 1.1 joerg '<font color="%s">%s</font></td>' 526 1.1 joerg '<td align="left">[B%d]</td></tr>' 527 1.1 joerg % (color, p.kind, p.block_id)) 528 1.1 joerg else: 529 1.1 joerg # TODO: Print more stuff for other kinds of points. 530 1.1 joerg self._dump('<td width="0"></td>' 531 1.1 joerg '<td align="left" width="0" colspan="2">' 532 1.1 joerg '<font color="%s">%s</font></td></tr>' 533 1.1 joerg % (color, p.kind)) 534 1.1 joerg 535 1.1 joerg if p.tag is not None: 536 1.1 joerg self._dump('<tr><td width="0"></td><td width="0"></td>' 537 1.1 joerg '<td colspan="3" align="left">' 538 1.1 joerg '<b>Tag: </b> <font color="crimson">' 539 1.1 joerg '%s</font></td></tr>' % p.tag) 540 1.1 joerg 541 1.1 joerg if p.has_report: 542 1.1 joerg self._dump('<tr><td width="0"></td><td width="0"></td>' 543 1.1 joerg '<td colspan="3" align="left">' 544 1.1 joerg '<font color="red"><b>Bug Report Attached' 545 1.1 joerg '</b></font></td></tr>') 546 1.1 joerg if p.is_sink: 547 1.1 joerg self._dump('<tr><td width="0"></td><td width="0"></td>' 548 1.1 joerg '<td colspan="3" align="left">' 549 1.1 joerg '<font color="cornflowerblue"><b>Sink Node' 550 1.1 joerg '</b></font></td></tr>') 551 1.1 joerg 552 1.1 joerg def visit_environment(self, e, prev_e=None): 553 1.1 joerg self._dump('<table border="0">') 554 1.1 joerg 555 1.1 joerg def dump_location_context(lc, is_added=None): 556 1.1 joerg self._dump('<tr><td>%s</td>' 557 1.1 joerg '<td align="left"><b>%s</b></td>' 558 1.1 joerg '<td align="left" colspan="2">' 559 1.1 joerg '<font color="gray60">%s </font>' 560 1.1 joerg '%s</td></tr>' 561 1.1 joerg % (self._diff_plus_minus(is_added), 562 1.1 joerg lc.caption, lc.decl, 563 1.1 joerg ('(%s)' % self._make_sloc(lc.loc)) 564 1.1 joerg if lc.loc is not None else '')) 565 1.1 joerg 566 1.1 joerg def dump_binding(f, b, is_added=None): 567 1.1 joerg self._dump('<tr><td>%s</td>' 568 1.1 joerg '<td align="left"><i>S%s</i></td>' 569 1.1 joerg '%s' 570 1.1 joerg '<td align="left">%s</td>' 571 1.1 joerg '<td align="left">%s</td></tr>' 572 1.1 joerg % (self._diff_plus_minus(is_added), 573 1.1 joerg b.stmt_id, 574 1.1 joerg '<td align="left"><font color="%s"><i>' 575 1.1 joerg '%s</i></font></td>' % ( 576 1.1 joerg 'lavender' if self._dark_mode else 'darkgreen', 577 1.1 joerg ('(%s)' % b.kind) if b.kind is not None else ' ' 578 1.1 joerg ), 579 1.1 joerg self._short_pretty(b.pretty), f.bindings[b])) 580 1.1 joerg 581 1.1 joerg frames_updated = e.diff_frames(prev_e) if prev_e is not None else None 582 1.1 joerg if frames_updated: 583 1.1 joerg for i in frames_updated: 584 1.1 joerg f = e.frames[i] 585 1.1 joerg prev_f = prev_e.frames[i] 586 1.1 joerg dump_location_context(f.location_context) 587 1.1 joerg bindings_removed, bindings_added = f.diff_bindings(prev_f) 588 1.1 joerg for b in bindings_removed: 589 1.1 joerg dump_binding(prev_f, b, False) 590 1.1 joerg for b in bindings_added: 591 1.1 joerg dump_binding(f, b, True) 592 1.1 joerg else: 593 1.1 joerg for f in e.frames: 594 1.1 joerg dump_location_context(f.location_context) 595 1.1 joerg for b in f.bindings: 596 1.1 joerg dump_binding(f, b) 597 1.1 joerg 598 1.1 joerg self._dump('</table>') 599 1.1 joerg 600 1.1 joerg def visit_environment_in_state(self, selector, title, s, prev_s=None): 601 1.1 joerg e = getattr(s, selector) 602 1.1 joerg prev_e = getattr(prev_s, selector) if prev_s is not None else None 603 1.1 joerg if e is None and prev_e is None: 604 1.1 joerg return 605 1.1 joerg 606 1.1 joerg self._dump('<hr /><tr><td align="left"><b>%s: </b>' % title) 607 1.1 joerg if e is None: 608 1.1 joerg self._dump('<i> Nothing!</i>') 609 1.1 joerg else: 610 1.1 joerg if prev_e is not None: 611 1.1 joerg if e.is_different(prev_e): 612 1.1 joerg self._dump('</td></tr><tr><td align="left">') 613 1.1 joerg self.visit_environment(e, prev_e) 614 1.1 joerg else: 615 1.1 joerg self._dump('<i> No changes!</i>') 616 1.1 joerg else: 617 1.1 joerg self._dump('</td></tr><tr><td align="left">') 618 1.1 joerg self.visit_environment(e) 619 1.1 joerg 620 1.1 joerg self._dump('</td></tr>') 621 1.1 joerg 622 1.1 joerg def visit_store(self, s, prev_s=None): 623 1.1 joerg self._dump('<table border="0">') 624 1.1 joerg 625 1.1 joerg def dump_binding(s, c, b, is_added=None): 626 1.1 joerg self._dump('<tr><td>%s</td>' 627 1.1 joerg '<td align="left">%s</td>' 628 1.1 joerg '<td align="left">%s</td>' 629 1.1 joerg '<td align="left">%s</td>' 630 1.1 joerg '<td align="left">%s</td></tr>' 631 1.1 joerg % (self._diff_plus_minus(is_added), 632 1.1 joerg s.clusters[c].base_region, b.offset, 633 1.1 joerg '(<i>Default</i>)' if b.kind == 'Default' 634 1.1 joerg else '', 635 1.1 joerg s.clusters[c].bindings[b])) 636 1.1 joerg 637 1.1 joerg if prev_s is not None: 638 1.1 joerg clusters_removed, clusters_added, clusters_updated = \ 639 1.1 joerg s.diff_clusters(prev_s) 640 1.1 joerg for c in clusters_removed: 641 1.1 joerg for b in prev_s.clusters[c].bindings: 642 1.1 joerg dump_binding(prev_s, c, b, False) 643 1.1 joerg for c in clusters_updated: 644 1.1 joerg bindings_removed, bindings_added = \ 645 1.1 joerg s.clusters[c].diff_bindings(prev_s.clusters[c]) 646 1.1 joerg for b in bindings_removed: 647 1.1 joerg dump_binding(prev_s, c, b, False) 648 1.1 joerg for b in bindings_added: 649 1.1 joerg dump_binding(s, c, b, True) 650 1.1 joerg for c in clusters_added: 651 1.1 joerg for b in s.clusters[c].bindings: 652 1.1 joerg dump_binding(s, c, b, True) 653 1.1 joerg else: 654 1.1 joerg for c in s.clusters: 655 1.1 joerg for b in s.clusters[c].bindings: 656 1.1 joerg dump_binding(s, c, b) 657 1.1 joerg 658 1.1 joerg self._dump('</table>') 659 1.1 joerg 660 1.1 joerg def visit_store_in_state(self, s, prev_s=None): 661 1.1 joerg st = s.store 662 1.1 joerg prev_st = prev_s.store if prev_s is not None else None 663 1.1 joerg if st is None and prev_st is None: 664 1.1 joerg return 665 1.1 joerg 666 1.1 joerg self._dump('<hr /><tr><td align="left"><b>Store: </b>') 667 1.1 joerg if st is None: 668 1.1 joerg self._dump('<i> Nothing!</i>') 669 1.1 joerg else: 670 1.1 joerg if self._dark_mode: 671 1.1 joerg self._dump(' <font color="gray30">(%s)</font>' % st.ptr) 672 1.1 joerg else: 673 1.1 joerg self._dump(' <font color="gray">(%s)</font>' % st.ptr) 674 1.1 joerg if prev_st is not None: 675 1.1 joerg if s.store.is_different(prev_st): 676 1.1 joerg self._dump('</td></tr><tr><td align="left">') 677 1.1 joerg self.visit_store(st, prev_st) 678 1.1 joerg else: 679 1.1 joerg self._dump('<i> No changes!</i>') 680 1.1 joerg else: 681 1.1 joerg self._dump('</td></tr><tr><td align="left">') 682 1.1 joerg self.visit_store(st) 683 1.1 joerg self._dump('</td></tr>') 684 1.1 joerg 685 1.1 joerg def visit_generic_map(self, m, prev_m=None): 686 1.1 joerg self._dump('<table border="0">') 687 1.1 joerg 688 1.1 joerg def dump_pair(m, k, is_added=None): 689 1.1 joerg self._dump('<tr><td>%s</td>' 690 1.1 joerg '<td align="left">%s</td>' 691 1.1 joerg '<td align="left">%s</td></tr>' 692 1.1 joerg % (self._diff_plus_minus(is_added), 693 1.1 joerg k, m.generic_map[k])) 694 1.1 joerg 695 1.1 joerg if prev_m is not None: 696 1.1 joerg removed, added = m.diff(prev_m) 697 1.1 joerg for k in removed: 698 1.1 joerg dump_pair(prev_m, k, False) 699 1.1 joerg for k in added: 700 1.1 joerg dump_pair(m, k, True) 701 1.1 joerg else: 702 1.1 joerg for k in m.generic_map: 703 1.1 joerg dump_pair(m, k, None) 704 1.1 joerg 705 1.1 joerg self._dump('</table>') 706 1.1 joerg 707 1.1 joerg def visit_generic_map_in_state(self, selector, title, s, prev_s=None): 708 1.1 joerg m = getattr(s, selector) 709 1.1 joerg prev_m = getattr(prev_s, selector) if prev_s is not None else None 710 1.1 joerg if m is None and prev_m is None: 711 1.1 joerg return 712 1.1 joerg 713 1.1 joerg self._dump('<hr />') 714 1.1 joerg self._dump('<tr><td align="left">' 715 1.1 joerg '<b>%s: </b>' % title) 716 1.1 joerg if m is None: 717 1.1 joerg self._dump('<i> Nothing!</i>') 718 1.1 joerg else: 719 1.1 joerg if prev_m is not None: 720 1.1 joerg if m.is_different(prev_m): 721 1.1 joerg self._dump('</td></tr><tr><td align="left">') 722 1.1 joerg self.visit_generic_map(m, prev_m) 723 1.1 joerg else: 724 1.1 joerg self._dump('<i> No changes!</i>') 725 1.1 joerg else: 726 1.1 joerg self._dump('</td></tr><tr><td align="left">') 727 1.1 joerg self.visit_generic_map(m) 728 1.1 joerg 729 1.1 joerg self._dump('</td></tr>') 730 1.1 joerg 731 1.1 joerg def visit_checker_messages(self, m, prev_m=None): 732 1.1 joerg self._dump('<table border="0">') 733 1.1 joerg 734 1.1 joerg def dump_line(l, is_added=None): 735 1.1 joerg self._dump('<tr><td>%s</td>' 736 1.1 joerg '<td align="left">%s</td></tr>' 737 1.1 joerg % (self._diff_plus_minus(is_added), l)) 738 1.1 joerg 739 1.1 joerg def dump_chk(chk, is_added=None): 740 1.1 joerg dump_line('<i>%s</i>:' % chk, is_added) 741 1.1 joerg 742 1.1 joerg if prev_m is not None: 743 1.1 joerg removed, added, updated = m.diff_messages(prev_m) 744 1.1 joerg for chk in removed: 745 1.1 joerg dump_chk(chk, False) 746 1.1 joerg for l in prev_m.items[chk].lines: 747 1.1 joerg dump_line(l, False) 748 1.1 joerg for chk in updated: 749 1.1 joerg dump_chk(chk) 750 1.1 joerg for l in m.items[chk].diff_lines(prev_m.items[chk]): 751 1.1 joerg dump_line(l[1:], l.startswith('+')) 752 1.1 joerg for chk in added: 753 1.1 joerg dump_chk(chk, True) 754 1.1 joerg for l in m.items[chk].lines: 755 1.1 joerg dump_line(l, True) 756 1.1 joerg else: 757 1.1 joerg for chk in m.items: 758 1.1 joerg dump_chk(chk) 759 1.1 joerg for l in m.items[chk].lines: 760 1.1 joerg dump_line(l) 761 1.1 joerg 762 1.1 joerg self._dump('</table>') 763 1.1 joerg 764 1.1 joerg def visit_checker_messages_in_state(self, s, prev_s=None): 765 1.1 joerg m = s.checker_messages 766 1.1 joerg prev_m = prev_s.checker_messages if prev_s is not None else None 767 1.1 joerg if m is None and prev_m is None: 768 1.1 joerg return 769 1.1 joerg 770 1.1 joerg self._dump('<hr />') 771 1.1 joerg self._dump('<tr><td align="left">' 772 1.1 joerg '<b>Checker State: </b>') 773 1.1 joerg if m is None: 774 1.1 joerg self._dump('<i> Nothing!</i>') 775 1.1 joerg else: 776 1.1 joerg if prev_m is not None: 777 1.1 joerg if m.is_different(prev_m): 778 1.1 joerg self._dump('</td></tr><tr><td align="left">') 779 1.1 joerg self.visit_checker_messages(m, prev_m) 780 1.1 joerg else: 781 1.1 joerg self._dump('<i> No changes!</i>') 782 1.1 joerg else: 783 1.1 joerg self._dump('</td></tr><tr><td align="left">') 784 1.1 joerg self.visit_checker_messages(m) 785 1.1 joerg 786 1.1 joerg self._dump('</td></tr>') 787 1.1 joerg 788 1.1 joerg def visit_state(self, s, prev_s): 789 1.1 joerg self.visit_store_in_state(s, prev_s) 790 1.1 joerg self.visit_environment_in_state('environment', 'Expressions', 791 1.1 joerg s, prev_s) 792 1.1 joerg self.visit_generic_map_in_state('constraints', 'Ranges', 793 1.1 joerg s, prev_s) 794 1.1 joerg self.visit_generic_map_in_state('dynamic_types', 'Dynamic Types', 795 1.1 joerg s, prev_s) 796 1.1 joerg self.visit_environment_in_state('constructing_objects', 797 1.1 joerg 'Objects Under Construction', 798 1.1 joerg s, prev_s) 799 1.1 joerg self.visit_checker_messages_in_state(s, prev_s) 800 1.1 joerg 801 1.1 joerg def visit_node(self, node): 802 1.1 joerg self._dump('%s [shape=record,' 803 1.1 joerg % (node.node_name())) 804 1.1 joerg if self._dark_mode: 805 1.1 joerg self._dump('color="white",fontcolor="gray80",') 806 1.1 joerg self._dump('label=<<table border="0">') 807 1.1 joerg 808 1.1 joerg self._dump('<tr><td bgcolor="%s"><b>State %s</b></td></tr>' 809 1.1 joerg % ("gray20" if self._dark_mode else "gray70", 810 1.1 joerg node.state.state_id 811 1.1 joerg if node.state is not None else 'Unspecified')) 812 1.1 joerg if not self._topo_mode: 813 1.1 joerg self._dump('<tr><td align="left" width="0">') 814 1.1 joerg if len(node.points) > 1: 815 1.1 joerg self._dump('<b>Program points:</b></td></tr>') 816 1.1 joerg else: 817 1.1 joerg self._dump('<b>Program point:</b></td></tr>') 818 1.1 joerg self._dump('<tr><td align="left" width="0">' 819 1.1 joerg '<table border="0" align="left" width="0">') 820 1.1 joerg for p in node.points: 821 1.1 joerg self.visit_program_point(p) 822 1.1 joerg self._dump('</table></td></tr>') 823 1.1 joerg 824 1.1 joerg if node.state is not None and not self._topo_mode: 825 1.1 joerg prev_s = None 826 1.1 joerg # Do diffs only when we have a unique predecessor. 827 1.1 joerg # Don't do diffs on the leaf nodes because they're 828 1.1 joerg # the important ones. 829 1.1 joerg if self._do_diffs and len(node.predecessors) == 1 \ 830 1.1 joerg and len(node.successors) > 0: 831 1.1 joerg prev_s = self._graph.nodes[node.predecessors[0]].state 832 1.1 joerg self.visit_state(node.state, prev_s) 833 1.1 joerg self._dump_raw('</table>>];\n') 834 1.1 joerg 835 1.1 joerg def visit_edge(self, pred, succ): 836 1.1 joerg self._dump_raw('%s -> %s%s;\n' % ( 837 1.1 joerg pred.node_name(), succ.node_name(), 838 1.1 joerg ' [color="white"]' if self._dark_mode else '' 839 1.1 joerg )) 840 1.1 joerg 841 1.1 joerg def visit_end_of_graph(self): 842 1.1 joerg self._dump_raw('}\n') 843 1.1 joerg 844 1.1 joerg if not self._dump_dot_only: 845 1.1 joerg import sys 846 1.1 joerg import tempfile 847 1.1 joerg 848 1.1 joerg def write_temp_file(suffix, data): 849 1.1 joerg fd, filename = tempfile.mkstemp(suffix=suffix) 850 1.1 joerg print('Writing "%s"...' % filename) 851 1.1 joerg with os.fdopen(fd, 'w') as fp: 852 1.1 joerg fp.write(data) 853 1.1 joerg print('Done! Please remember to remove the file.') 854 1.1 joerg return filename 855 1.1 joerg 856 1.1 joerg try: 857 1.1 joerg import graphviz 858 1.1 joerg except ImportError: 859 1.1 joerg # The fallback behavior if graphviz is not installed! 860 1.1 joerg print('Python graphviz not found. Please invoke') 861 1.1 joerg print(' $ pip install graphviz') 862 1.1 joerg print('in order to enable automatic conversion to HTML.') 863 1.1 joerg print() 864 1.1 joerg print('You may also convert DOT to SVG manually via') 865 1.1 joerg print(' $ dot -Tsvg input.dot -o output.svg') 866 1.1 joerg print() 867 1.1 joerg write_temp_file('.dot', self.output()) 868 1.1 joerg return 869 1.1 joerg 870 1.1 joerg svg = graphviz.pipe('dot', 'svg', self.output()) 871 1.1 joerg 872 1.1 joerg filename = write_temp_file( 873 1.1 joerg '.html', '<html><body bgcolor="%s">%s</body></html>' % ( 874 1.1 joerg '#1a1a1a' if self._dark_mode else 'white', svg)) 875 1.1 joerg if sys.platform == 'win32': 876 1.1 joerg os.startfile(filename) 877 1.1 joerg elif sys.platform == 'darwin': 878 1.1 joerg os.system('open "%s"' % filename) 879 1.1 joerg else: 880 1.1 joerg os.system('xdg-open "%s"' % filename) 881 1.1 joerg 882 1.1 joerg 883 1.1 joerg #===-----------------------------------------------------------------------===# 884 1.1 joerg # Explorers know how to traverse the ExplodedGraph in a certain order. 885 1.1 joerg # They would invoke a Visitor on every node or edge they encounter. 886 1.1 joerg #===-----------------------------------------------------------------------===# 887 1.1 joerg 888 1.1 joerg 889 1.1 joerg # BasicExplorer explores the whole graph in no particular order. 890 1.1.1.2 joerg class BasicExplorer: 891 1.1 joerg def explore(self, graph, visitor): 892 1.1 joerg visitor.visit_begin_graph(graph) 893 1.1 joerg for node in sorted(graph.nodes): 894 1.1 joerg logging.debug('Visiting ' + node) 895 1.1 joerg visitor.visit_node(graph.nodes[node]) 896 1.1 joerg for succ in sorted(graph.nodes[node].successors): 897 1.1 joerg logging.debug('Visiting edge: %s -> %s ' % (node, succ)) 898 1.1 joerg visitor.visit_edge(graph.nodes[node], graph.nodes[succ]) 899 1.1 joerg visitor.visit_end_of_graph() 900 1.1 joerg 901 1.1 joerg 902 1.1 joerg #===-----------------------------------------------------------------------===# 903 1.1 joerg # Trimmers cut out parts of the ExplodedGraph so that to focus on other parts. 904 1.1 joerg # Trimmers can be combined together by applying them sequentially. 905 1.1 joerg #===-----------------------------------------------------------------------===# 906 1.1 joerg 907 1.1 joerg 908 1.1 joerg # SinglePathTrimmer keeps only a single path - the leftmost path from the root. 909 1.1 joerg # Useful when the trimmed graph is still too large. 910 1.1.1.2 joerg class SinglePathTrimmer: 911 1.1 joerg def trim(self, graph): 912 1.1 joerg visited_nodes = set() 913 1.1 joerg node_id = graph.root_id 914 1.1 joerg while True: 915 1.1 joerg visited_nodes.add(node_id) 916 1.1 joerg node = graph.nodes[node_id] 917 1.1 joerg if len(node.successors) > 0: 918 1.1 joerg succ_id = node.successors[0] 919 1.1 joerg succ = graph.nodes[succ_id] 920 1.1 joerg node.successors = [succ_id] 921 1.1 joerg succ.predecessors = [node_id] 922 1.1 joerg if succ_id in visited_nodes: 923 1.1 joerg break 924 1.1 joerg node_id = succ_id 925 1.1 joerg else: 926 1.1 joerg break 927 1.1 joerg graph.nodes = {node_id: graph.nodes[node_id] 928 1.1 joerg for node_id in visited_nodes} 929 1.1 joerg 930 1.1 joerg 931 1.1 joerg # TargetedTrimmer keeps paths that lead to specific nodes and discards all 932 1.1 joerg # other paths. Useful when you cannot use -trim-egraph (e.g. when debugging 933 1.1 joerg # a crash). 934 1.1.1.2 joerg class TargetedTrimmer: 935 1.1 joerg def __init__(self, target_nodes): 936 1.1 joerg self._target_nodes = target_nodes 937 1.1 joerg 938 1.1 joerg @staticmethod 939 1.1 joerg def parse_target_node(node, graph): 940 1.1 joerg if node.startswith('0x'): 941 1.1 joerg ret = 'Node' + node 942 1.1 joerg assert ret in graph.nodes 943 1.1 joerg return ret 944 1.1 joerg else: 945 1.1 joerg for other_id in graph.nodes: 946 1.1 joerg other = graph.nodes[other_id] 947 1.1 joerg if other.node_id == int(node): 948 1.1 joerg return other_id 949 1.1 joerg 950 1.1 joerg @staticmethod 951 1.1 joerg def parse_target_nodes(target_nodes, graph): 952 1.1 joerg return [TargetedTrimmer.parse_target_node(node, graph) 953 1.1 joerg for node in target_nodes.split(',')] 954 1.1 joerg 955 1.1 joerg def trim(self, graph): 956 1.1 joerg queue = self._target_nodes 957 1.1 joerg visited_nodes = set() 958 1.1 joerg 959 1.1 joerg while len(queue) > 0: 960 1.1 joerg node_id = queue.pop() 961 1.1 joerg visited_nodes.add(node_id) 962 1.1 joerg node = graph.nodes[node_id] 963 1.1 joerg for pred_id in node.predecessors: 964 1.1 joerg if pred_id not in visited_nodes: 965 1.1 joerg queue.append(pred_id) 966 1.1 joerg graph.nodes = {node_id: graph.nodes[node_id] 967 1.1 joerg for node_id in visited_nodes} 968 1.1 joerg for node_id in graph.nodes: 969 1.1 joerg node = graph.nodes[node_id] 970 1.1 joerg node.successors = [succ_id for succ_id in node.successors 971 1.1 joerg if succ_id in visited_nodes] 972 1.1 joerg node.predecessors = [succ_id for succ_id in node.predecessors 973 1.1 joerg if succ_id in visited_nodes] 974 1.1 joerg 975 1.1 joerg 976 1.1 joerg #===-----------------------------------------------------------------------===# 977 1.1 joerg # The entry point to the script. 978 1.1 joerg #===-----------------------------------------------------------------------===# 979 1.1 joerg 980 1.1 joerg 981 1.1 joerg def main(): 982 1.1 joerg parser = argparse.ArgumentParser( 983 1.1 joerg description='Display and manipulate Exploded Graph dumps.') 984 1.1 joerg parser.add_argument('filename', type=str, 985 1.1 joerg help='the .dot file produced by the Static Analyzer') 986 1.1 joerg parser.add_argument('-v', '--verbose', action='store_const', 987 1.1 joerg dest='loglevel', const=logging.DEBUG, 988 1.1 joerg default=logging.WARNING, 989 1.1 joerg help='enable info prints') 990 1.1 joerg parser.add_argument('-d', '--diff', action='store_const', dest='diff', 991 1.1 joerg const=True, default=False, 992 1.1 joerg help='display differences between states') 993 1.1 joerg parser.add_argument('-t', '--topology', action='store_const', 994 1.1 joerg dest='topology', const=True, default=False, 995 1.1 joerg help='only display program points, omit states') 996 1.1 joerg parser.add_argument('-s', '--single-path', action='store_const', 997 1.1 joerg dest='single_path', const=True, default=False, 998 1.1 joerg help='only display the leftmost path in the graph ' 999 1.1 joerg '(useful for trimmed graphs that still ' 1000 1.1 joerg 'branch too much)') 1001 1.1 joerg parser.add_argument('--to', type=str, default=None, 1002 1.1 joerg help='only display execution paths from the root ' 1003 1.1 joerg 'to the given comma-separated list of nodes ' 1004 1.1 joerg 'identified by a pointer or a stable ID; ' 1005 1.1 joerg 'compatible with --single-path') 1006 1.1 joerg parser.add_argument('--dark', action='store_const', dest='dark', 1007 1.1 joerg const=True, default=False, 1008 1.1 joerg help='dark mode') 1009 1.1 joerg parser.add_argument('--gray', action='store_const', dest='gray', 1010 1.1 joerg const=True, default=False, 1011 1.1 joerg help='black-and-white mode') 1012 1.1 joerg parser.add_argument('--dump-dot-only', action='store_const', 1013 1.1 joerg dest='dump_dot_only', const=True, default=False, 1014 1.1 joerg help='instead of writing an HTML file and immediately ' 1015 1.1 joerg 'displaying it, dump the rewritten dot file ' 1016 1.1 joerg 'to stdout') 1017 1.1 joerg args = parser.parse_args() 1018 1.1 joerg logging.basicConfig(level=args.loglevel) 1019 1.1 joerg 1020 1.1 joerg graph = ExplodedGraph() 1021 1.1 joerg with open(args.filename) as fd: 1022 1.1 joerg for raw_line in fd: 1023 1.1 joerg raw_line = raw_line.strip() 1024 1.1 joerg graph.add_raw_line(raw_line) 1025 1.1 joerg 1026 1.1 joerg trimmers = [] 1027 1.1 joerg if args.to is not None: 1028 1.1 joerg trimmers.append(TargetedTrimmer( 1029 1.1 joerg TargetedTrimmer.parse_target_nodes(args.to, graph))) 1030 1.1 joerg if args.single_path: 1031 1.1 joerg trimmers.append(SinglePathTrimmer()) 1032 1.1 joerg 1033 1.1 joerg explorer = BasicExplorer() 1034 1.1 joerg 1035 1.1 joerg visitor = DotDumpVisitor(args.diff, args.dark, args.gray, args.topology, 1036 1.1 joerg args.dump_dot_only) 1037 1.1 joerg 1038 1.1 joerg for trimmer in trimmers: 1039 1.1 joerg trimmer.trim(graph) 1040 1.1 joerg 1041 1.1 joerg explorer.explore(graph, visitor) 1042 1.1 joerg 1043 1.1 joerg 1044 1.1 joerg if __name__ == '__main__': 1045 1.1 joerg main() 1046