Home | History | Annotate | Line # | Download | only in analyzer
      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('&', '&amp;') \
    427      1.1  joerg              .replace('{', '\\{') \
    428      1.1  joerg              .replace('}', '\\}') \
    429      1.1  joerg              .replace('\\<', '&lt;') \
    430      1.1  joerg              .replace('\\>', '&gt;') \
    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