xref: /llvm-project/clang/utils/analyzer/exploded-graph-rewriter.py (revision 20cb4ec845dec70f304c054ba5b45c0a388112b8)
116236077SArtem Dergachev#!/usr/bin/env python
244fb55bfSArtem Dergachev#
344fb55bfSArtem Dergachev# ===- exploded-graph-rewriter.py - ExplodedGraph dump tool -----*- python -*--#
444fb55bfSArtem Dergachev#
544fb55bfSArtem Dergachev# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
644fb55bfSArtem Dergachev# See https://llvm.org/LICENSE.txt for license information.
744fb55bfSArtem Dergachev# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
844fb55bfSArtem Dergachev#
944fb55bfSArtem Dergachev# ===-----------------------------------------------------------------------===#
1044fb55bfSArtem Dergachev
1116236077SArtem Dergachev
1216236077SArtem Dergachevfrom __future__ import print_function
1316236077SArtem Dergachev
1416236077SArtem Dergachevimport argparse
1516236077SArtem Dergachevimport collections
16deb7accbSArtem Dergachevimport difflib
1716236077SArtem Dergachevimport json
1816236077SArtem Dergachevimport logging
19fc6059e8SArtem Dergachevimport os
2016236077SArtem Dergachevimport re
2116236077SArtem Dergachev
2216236077SArtem Dergachev
235fcf92e1SArtem Dergachev# ===-----------------------------------------------------------------------===#
245fcf92e1SArtem Dergachev# These data structures represent a deserialized ExplodedGraph.
255fcf92e1SArtem Dergachev# ===-----------------------------------------------------------------------===#
265fcf92e1SArtem Dergachev
275fcf92e1SArtem Dergachev
285740e77fSArtem Dergachev# A helper function for finding the difference between two dictionaries.
295740e77fSArtem Dergachevdef diff_dicts(curr, prev):
305740e77fSArtem Dergachev    removed = [k for k in prev if k not in curr or curr[k] != prev[k]]
315740e77fSArtem Dergachev    added = [k for k in curr if k not in prev or curr[k] != prev[k]]
325740e77fSArtem Dergachev    return (removed, added)
335740e77fSArtem Dergachev
345740e77fSArtem Dergachev
35beb85ad6SArtem Dergachev# Represents any program state trait that is a dictionary of key-value pairs.
36c98872e3SValeriy Savchenkoclass GenericMap:
3702f91ddfSArtem Dergachev    def __init__(self, items):
3802f91ddfSArtem Dergachev        self.generic_map = collections.OrderedDict(items)
39beb85ad6SArtem Dergachev
40beb85ad6SArtem Dergachev    def diff(self, prev):
41beb85ad6SArtem Dergachev        return diff_dicts(self.generic_map, prev.generic_map)
42beb85ad6SArtem Dergachev
43beb85ad6SArtem Dergachev    def is_different(self, prev):
44beb85ad6SArtem Dergachev        removed, added = self.diff(prev)
45beb85ad6SArtem Dergachev        return len(removed) != 0 or len(added) != 0
46beb85ad6SArtem Dergachev
47beb85ad6SArtem Dergachev
4816236077SArtem Dergachev# A deserialized source location.
49c98872e3SValeriy Savchenkoclass SourceLocation:
5016236077SArtem Dergachev    def __init__(self, json_loc):
51dd3c26a0STobias Hieta        logging.debug("json: %s" % json_loc)
52dd3c26a0STobias Hieta        self.line = json_loc["line"]
53dd3c26a0STobias Hieta        self.col = json_loc["column"]
54dd3c26a0STobias Hieta        self.filename = (
55dd3c26a0STobias Hieta            os.path.basename(json_loc["file"]) if "file" in json_loc else "(main file)"
56dd3c26a0STobias Hieta        )
57dd3c26a0STobias Hieta        self.spelling = (
58dd3c26a0STobias Hieta            SourceLocation(json_loc["spelling"]) if "spelling" in json_loc else None
59dd3c26a0STobias Hieta        )
60ed035ff8SArtem Dergachev
61ed035ff8SArtem Dergachev    def is_macro(self):
62ed035ff8SArtem Dergachev        return self.spelling is not None
6316236077SArtem Dergachev
6416236077SArtem Dergachev
6516236077SArtem Dergachev# A deserialized program point.
66c98872e3SValeriy Savchenkoclass ProgramPoint:
6716236077SArtem Dergachev    def __init__(self, json_pp):
68dd3c26a0STobias Hieta        self.kind = json_pp["kind"]
69dd3c26a0STobias Hieta        self.tag = json_pp["tag"]
70dd3c26a0STobias Hieta        self.node_id = json_pp["node_id"]
71dd3c26a0STobias Hieta        self.is_sink = bool(json_pp["is_sink"])
72dd3c26a0STobias Hieta        self.has_report = bool(json_pp["has_report"])
73dd3c26a0STobias Hieta        if self.kind == "Edge":
74dd3c26a0STobias Hieta            self.src_id = json_pp["src_id"]
75dd3c26a0STobias Hieta            self.dst_id = json_pp["dst_id"]
76dd3c26a0STobias Hieta        elif self.kind == "Statement":
77ed035ff8SArtem Dergachev            logging.debug(json_pp)
78dd3c26a0STobias Hieta            self.stmt_kind = json_pp["stmt_kind"]
79dd3c26a0STobias Hieta            self.cast_kind = json_pp["cast_kind"] if "cast_kind" in json_pp else None
80dd3c26a0STobias Hieta            self.stmt_point_kind = json_pp["stmt_point_kind"]
81dd3c26a0STobias Hieta            self.stmt_id = json_pp["stmt_id"]
82dd3c26a0STobias Hieta            self.pointer = json_pp["pointer"]
83dd3c26a0STobias Hieta            self.pretty = json_pp["pretty"]
84dd3c26a0STobias Hieta            self.loc = (
85dd3c26a0STobias Hieta                SourceLocation(json_pp["location"])
86dd3c26a0STobias Hieta                if json_pp["location"] is not None
87dd3c26a0STobias Hieta                else None
88dd3c26a0STobias Hieta            )
899cbf2dd6SBalazs Benics        elif self.kind == "CallEnter":
909cbf2dd6SBalazs Benics            self.callee_decl = json_pp.get("callee_decl", "None")
91dd3c26a0STobias Hieta        elif self.kind == "BlockEntrance":
92dd3c26a0STobias Hieta            self.block_id = json_pp["block_id"]
93*20cb4ec8SBalazs Benics        elif self.kind == "PostInitializer":
94*20cb4ec8SBalazs Benics            if "field_decl" in json_pp:
95*20cb4ec8SBalazs Benics                self.target = json_pp["field_decl"]
96*20cb4ec8SBalazs Benics            else:
97*20cb4ec8SBalazs Benics                self.target = json_pp["type"]
9816236077SArtem Dergachev
9916236077SArtem Dergachev
1005740e77fSArtem Dergachev# A single expression acting as a key in a deserialized Environment.
101c98872e3SValeriy Savchenkoclass EnvironmentBindingKey:
1025740e77fSArtem Dergachev    def __init__(self, json_ek):
1030a77d919SArtem Dergachev        # CXXCtorInitializer is not a Stmt!
104dd3c26a0STobias Hieta        self.stmt_id = (
105dd3c26a0STobias Hieta            json_ek["stmt_id"] if "stmt_id" in json_ek else json_ek["init_id"]
106dd3c26a0STobias Hieta        )
107dd3c26a0STobias Hieta        self.pretty = json_ek["pretty"]
108dd3c26a0STobias Hieta        self.kind = json_ek["kind"] if "kind" in json_ek else None
1095740e77fSArtem Dergachev
1105740e77fSArtem Dergachev    def _key(self):
1115740e77fSArtem Dergachev        return self.stmt_id
1125740e77fSArtem Dergachev
1135740e77fSArtem Dergachev    def __eq__(self, other):
1145740e77fSArtem Dergachev        return self._key() == other._key()
1155740e77fSArtem Dergachev
1165740e77fSArtem Dergachev    def __hash__(self):
1175740e77fSArtem Dergachev        return hash(self._key())
11816236077SArtem Dergachev
11916236077SArtem Dergachev
12016236077SArtem Dergachev# Deserialized description of a location context.
121c98872e3SValeriy Savchenkoclass LocationContext:
12216236077SArtem Dergachev    def __init__(self, json_frame):
123dd3c26a0STobias Hieta        self.lctx_id = json_frame["lctx_id"]
124dd3c26a0STobias Hieta        self.caption = json_frame["location_context"]
125dd3c26a0STobias Hieta        self.decl = json_frame["calling"]
126dd3c26a0STobias Hieta        self.loc = (
127dd3c26a0STobias Hieta            SourceLocation(json_frame["location"])
128dd3c26a0STobias Hieta            if json_frame["location"] is not None
129dd3c26a0STobias Hieta            else None
130dd3c26a0STobias Hieta        )
13116236077SArtem Dergachev
1325740e77fSArtem Dergachev    def _key(self):
1335740e77fSArtem Dergachev        return self.lctx_id
1345740e77fSArtem Dergachev
1355740e77fSArtem Dergachev    def __eq__(self, other):
1365740e77fSArtem Dergachev        return self._key() == other._key()
1375740e77fSArtem Dergachev
1385740e77fSArtem Dergachev    def __hash__(self):
1395740e77fSArtem Dergachev        return hash(self._key())
1405740e77fSArtem Dergachev
14116236077SArtem Dergachev
14216236077SArtem Dergachev# A group of deserialized Environment bindings that correspond to a specific
14316236077SArtem Dergachev# location context.
144c98872e3SValeriy Savchenkoclass EnvironmentFrame:
14516236077SArtem Dergachev    def __init__(self, json_frame):
14616236077SArtem Dergachev        self.location_context = LocationContext(json_frame)
1475740e77fSArtem Dergachev        self.bindings = collections.OrderedDict(
148dd3c26a0STobias Hieta            [(EnvironmentBindingKey(b), b["value"]) for b in json_frame["items"]]
149dd3c26a0STobias Hieta            if json_frame["items"] is not None
150dd3c26a0STobias Hieta            else []
151dd3c26a0STobias Hieta        )
1525740e77fSArtem Dergachev
1535740e77fSArtem Dergachev    def diff_bindings(self, prev):
1545740e77fSArtem Dergachev        return diff_dicts(self.bindings, prev.bindings)
1555740e77fSArtem Dergachev
1565740e77fSArtem Dergachev    def is_different(self, prev):
1575740e77fSArtem Dergachev        removed, added = self.diff_bindings(prev)
1585740e77fSArtem Dergachev        return len(removed) != 0 or len(added) != 0
15916236077SArtem Dergachev
16016236077SArtem Dergachev
1610a77d919SArtem Dergachev# A deserialized Environment. This class can also hold other entities that
162b032e3ffSisuckatcs# are similar to Environment, such as Objects Under Construction or
163b032e3ffSisuckatcs# Indices Of Elements Under Construction.
164c98872e3SValeriy Savchenkoclass GenericEnvironment:
16516236077SArtem Dergachev    def __init__(self, json_e):
1660a77d919SArtem Dergachev        self.frames = [EnvironmentFrame(f) for f in json_e]
16716236077SArtem Dergachev
1685740e77fSArtem Dergachev    def diff_frames(self, prev):
1695740e77fSArtem Dergachev        # TODO: It's difficult to display a good diff when frame numbers shift.
1705740e77fSArtem Dergachev        if len(self.frames) != len(prev.frames):
1715740e77fSArtem Dergachev            return None
17216236077SArtem Dergachev
1735740e77fSArtem Dergachev        updated = []
1745740e77fSArtem Dergachev        for i in range(len(self.frames)):
1755740e77fSArtem Dergachev            f = self.frames[i]
1765740e77fSArtem Dergachev            prev_f = prev.frames[i]
1775740e77fSArtem Dergachev            if f.location_context == prev_f.location_context:
1785740e77fSArtem Dergachev                if f.is_different(prev_f):
1795740e77fSArtem Dergachev                    updated.append(i)
1805740e77fSArtem Dergachev            else:
1815740e77fSArtem Dergachev                # We have the whole frame replaced with another frame.
1825740e77fSArtem Dergachev                # TODO: Produce a nice diff.
1835740e77fSArtem Dergachev                return None
1845740e77fSArtem Dergachev
1855740e77fSArtem Dergachev        # TODO: Add support for added/removed.
1865740e77fSArtem Dergachev        return updated
1875740e77fSArtem Dergachev
1885740e77fSArtem Dergachev    def is_different(self, prev):
1895740e77fSArtem Dergachev        updated = self.diff_frames(prev)
1905740e77fSArtem Dergachev        return updated is None or len(updated) > 0
1915740e77fSArtem Dergachev
1925740e77fSArtem Dergachev
1935740e77fSArtem Dergachev# A single binding key in a deserialized RegionStore cluster.
194c98872e3SValeriy Savchenkoclass StoreBindingKey:
1955740e77fSArtem Dergachev    def __init__(self, json_sk):
196dd3c26a0STobias Hieta        self.kind = json_sk["kind"]
197dd3c26a0STobias Hieta        self.offset = json_sk["offset"]
1985740e77fSArtem Dergachev
1995740e77fSArtem Dergachev    def _key(self):
2005740e77fSArtem Dergachev        return (self.kind, self.offset)
2015740e77fSArtem Dergachev
2025740e77fSArtem Dergachev    def __eq__(self, other):
2035740e77fSArtem Dergachev        return self._key() == other._key()
2045740e77fSArtem Dergachev
2055740e77fSArtem Dergachev    def __hash__(self):
2065740e77fSArtem Dergachev        return hash(self._key())
20716236077SArtem Dergachev
20816236077SArtem Dergachev
20916236077SArtem Dergachev# A single cluster of the deserialized RegionStore.
210c98872e3SValeriy Savchenkoclass StoreCluster:
21116236077SArtem Dergachev    def __init__(self, json_sc):
212dd3c26a0STobias Hieta        self.base_region = json_sc["cluster"]
2135740e77fSArtem Dergachev        self.bindings = collections.OrderedDict(
214dd3c26a0STobias Hieta            [(StoreBindingKey(b), b["value"]) for b in json_sc["items"]]
215dd3c26a0STobias Hieta        )
2165740e77fSArtem Dergachev
2175740e77fSArtem Dergachev    def diff_bindings(self, prev):
2185740e77fSArtem Dergachev        return diff_dicts(self.bindings, prev.bindings)
2195740e77fSArtem Dergachev
2205740e77fSArtem Dergachev    def is_different(self, prev):
2215740e77fSArtem Dergachev        removed, added = self.diff_bindings(prev)
2225740e77fSArtem Dergachev        return len(removed) != 0 or len(added) != 0
22316236077SArtem Dergachev
22416236077SArtem Dergachev
22516236077SArtem Dergachev# A deserialized RegionStore.
226c98872e3SValeriy Savchenkoclass Store:
22716236077SArtem Dergachev    def __init__(self, json_s):
228dd3c26a0STobias Hieta        self.ptr = json_s["pointer"]
2295740e77fSArtem Dergachev        self.clusters = collections.OrderedDict(
230dd3c26a0STobias Hieta            [(c["pointer"], StoreCluster(c)) for c in json_s["items"]]
231dd3c26a0STobias Hieta        )
2325740e77fSArtem Dergachev
2335740e77fSArtem Dergachev    def diff_clusters(self, prev):
2345740e77fSArtem Dergachev        removed = [k for k in prev.clusters if k not in self.clusters]
2355740e77fSArtem Dergachev        added = [k for k in self.clusters if k not in prev.clusters]
236dd3c26a0STobias Hieta        updated = [
237dd3c26a0STobias Hieta            k
238dd3c26a0STobias Hieta            for k in prev.clusters
239dd3c26a0STobias Hieta            if k in self.clusters and prev.clusters[k].is_different(self.clusters[k])
240dd3c26a0STobias Hieta        ]
2415740e77fSArtem Dergachev        return (removed, added, updated)
2425740e77fSArtem Dergachev
2435740e77fSArtem Dergachev    def is_different(self, prev):
2445740e77fSArtem Dergachev        removed, added, updated = self.diff_clusters(prev)
2455740e77fSArtem Dergachev        return len(removed) != 0 or len(added) != 0 or len(updated) != 0
24616236077SArtem Dergachev
24716236077SArtem Dergachev
248deb7accbSArtem Dergachev# Deserialized messages from a single checker in a single program state.
249deb7accbSArtem Dergachev# Basically a list of raw strings.
250c98872e3SValeriy Savchenkoclass CheckerLines:
251deb7accbSArtem Dergachev    def __init__(self, json_lines):
252deb7accbSArtem Dergachev        self.lines = json_lines
253deb7accbSArtem Dergachev
254deb7accbSArtem Dergachev    def diff_lines(self, prev):
255deb7accbSArtem Dergachev        lines = difflib.ndiff(prev.lines, self.lines)
256dd3c26a0STobias Hieta        return [l.strip() for l in lines if l.startswith("+") or l.startswith("-")]
257deb7accbSArtem Dergachev
258deb7accbSArtem Dergachev    def is_different(self, prev):
259deb7accbSArtem Dergachev        return len(self.diff_lines(prev)) > 0
260deb7accbSArtem Dergachev
261deb7accbSArtem Dergachev
262deb7accbSArtem Dergachev# Deserialized messages of all checkers, separated by checker.
263c98872e3SValeriy Savchenkoclass CheckerMessages:
264deb7accbSArtem Dergachev    def __init__(self, json_m):
265deb7accbSArtem Dergachev        self.items = collections.OrderedDict(
266dd3c26a0STobias Hieta            [(m["checker"], CheckerLines(m["messages"])) for m in json_m]
267dd3c26a0STobias Hieta        )
268deb7accbSArtem Dergachev
269deb7accbSArtem Dergachev    def diff_messages(self, prev):
270deb7accbSArtem Dergachev        removed = [k for k in prev.items if k not in self.items]
271deb7accbSArtem Dergachev        added = [k for k in self.items if k not in prev.items]
272dd3c26a0STobias Hieta        updated = [
273dd3c26a0STobias Hieta            k
274dd3c26a0STobias Hieta            for k in prev.items
275dd3c26a0STobias Hieta            if k in self.items and prev.items[k].is_different(self.items[k])
276dd3c26a0STobias Hieta        ]
277deb7accbSArtem Dergachev        return (removed, added, updated)
278deb7accbSArtem Dergachev
279deb7accbSArtem Dergachev    def is_different(self, prev):
280deb7accbSArtem Dergachev        removed, added, updated = self.diff_messages(prev)
281deb7accbSArtem Dergachev        return len(removed) != 0 or len(added) != 0 or len(updated) != 0
282deb7accbSArtem Dergachev
283deb7accbSArtem Dergachev
28416236077SArtem Dergachev# A deserialized program state.
285c98872e3SValeriy Savchenkoclass ProgramState:
28616236077SArtem Dergachev    def __init__(self, state_id, json_ps):
287dd3c26a0STobias Hieta        logging.debug("Adding ProgramState " + str(state_id))
28816236077SArtem Dergachev
289dd3c26a0STobias Hieta        store_key = "store"
290dd3c26a0STobias Hieta        env_key = "environment"
291dd3c26a0STobias Hieta        constraints_key = "constraints"
292dd3c26a0STobias Hieta        dyn_ty_key = "dynamic_types"
293dd3c26a0STobias Hieta        ctor_key = "constructing_objects"
294dd3c26a0STobias Hieta        ind_key = "index_of_element"
295dd3c26a0STobias Hieta        init_loop_key = "pending_init_loops"
296dd3c26a0STobias Hieta        dtor_key = "pending_destructors"
297dd3c26a0STobias Hieta        msg_key = "checker_messages"
298b5147937Sisuckatcs
299d93b810cSArtem Dergachev        if json_ps is None:
300d93b810cSArtem Dergachev            json_ps = {
301b5147937Sisuckatcs                store_key: None,
302b5147937Sisuckatcs                env_key: None,
303b5147937Sisuckatcs                constraints_key: None,
304b5147937Sisuckatcs                dyn_ty_key: None,
305b5147937Sisuckatcs                ctor_key: None,
306b5147937Sisuckatcs                ind_key: None,
307b5147937Sisuckatcs                init_loop_key: None,
308b5147937Sisuckatcs                dtor_key: None,
309dd3c26a0STobias Hieta                msg_key: None,
310d93b810cSArtem Dergachev            }
311d93b810cSArtem Dergachev
31216236077SArtem Dergachev        self.state_id = state_id
3130a77d919SArtem Dergachev
314dd3c26a0STobias Hieta        self.store = (
315dd3c26a0STobias Hieta            Store(json_ps[store_key]) if json_ps[store_key] is not None else None
316dd3c26a0STobias Hieta        )
3170a77d919SArtem Dergachev
318dd3c26a0STobias Hieta        self.environment = (
319dd3c26a0STobias Hieta            GenericEnvironment(json_ps[env_key]["items"])
320dd3c26a0STobias Hieta            if json_ps[env_key] is not None
321dd3c26a0STobias Hieta            else None
322dd3c26a0STobias Hieta        )
3230a77d919SArtem Dergachev
324dd3c26a0STobias Hieta        self.constraints = (
325dd3c26a0STobias Hieta            GenericMap([(c["symbol"], c["range"]) for c in json_ps[constraints_key]])
326dd3c26a0STobias Hieta            if json_ps[constraints_key] is not None
327dd3c26a0STobias Hieta            else None
328dd3c26a0STobias Hieta        )
3290a77d919SArtem Dergachev
330dd3c26a0STobias Hieta        self.dynamic_types = (
331dd3c26a0STobias Hieta            GenericMap(
332dd3c26a0STobias Hieta                [
333dd3c26a0STobias Hieta                    (
334dd3c26a0STobias Hieta                        t["region"],
335dd3c26a0STobias Hieta                        "%s%s"
336dd3c26a0STobias Hieta                        % (
337dd3c26a0STobias Hieta                            t["dyn_type"],
338dd3c26a0STobias Hieta                            " (or a sub-class)" if t["sub_classable"] else "",
339dd3c26a0STobias Hieta                        ),
340dd3c26a0STobias Hieta                    )
341dd3c26a0STobias Hieta                    for t in json_ps[dyn_ty_key]
342dd3c26a0STobias Hieta                ]
343dd3c26a0STobias Hieta            )
344dd3c26a0STobias Hieta            if json_ps[dyn_ty_key] is not None
345dd3c26a0STobias Hieta            else None
346dd3c26a0STobias Hieta        )
347b5147937Sisuckatcs
348dd3c26a0STobias Hieta        self.checker_messages = (
349dd3c26a0STobias Hieta            CheckerMessages(json_ps[msg_key]) if json_ps[msg_key] is not None else None
350dd3c26a0STobias Hieta        )
351b5147937Sisuckatcs
352b5147937Sisuckatcs        # State traits
353b5147937Sisuckatcs        #
354b5147937Sisuckatcs        # For traits we always check if a key exists because if a trait
355b5147937Sisuckatcs        # has no imformation, nothing will be printed in the .dot file
356b5147937Sisuckatcs        # we parse.
35702f91ddfSArtem Dergachev
358dd3c26a0STobias Hieta        self.constructing_objects = (
359dd3c26a0STobias Hieta            GenericEnvironment(json_ps[ctor_key])
360dd3c26a0STobias Hieta            if ctor_key in json_ps and json_ps[ctor_key] is not None
361dd3c26a0STobias Hieta            else None
362dd3c26a0STobias Hieta        )
3630a77d919SArtem Dergachev
364dd3c26a0STobias Hieta        self.index_of_element = (
365dd3c26a0STobias Hieta            GenericEnvironment(json_ps[ind_key])
366dd3c26a0STobias Hieta            if ind_key in json_ps and json_ps[ind_key] is not None
367dd3c26a0STobias Hieta            else None
368dd3c26a0STobias Hieta        )
369b032e3ffSisuckatcs
370dd3c26a0STobias Hieta        self.pending_init_loops = (
371dd3c26a0STobias Hieta            GenericEnvironment(json_ps[init_loop_key])
372dd3c26a0STobias Hieta            if init_loop_key in json_ps and json_ps[init_loop_key] is not None
373dd3c26a0STobias Hieta            else None
374dd3c26a0STobias Hieta        )
375b5147937Sisuckatcs
376dd3c26a0STobias Hieta        self.pending_destructors = (
377dd3c26a0STobias Hieta            GenericEnvironment(json_ps[dtor_key])
378dd3c26a0STobias Hieta            if dtor_key in json_ps and json_ps[dtor_key] is not None
379dd3c26a0STobias Hieta            else None
380dd3c26a0STobias Hieta        )
38116236077SArtem Dergachev
38216236077SArtem Dergachev
38316236077SArtem Dergachev# A deserialized exploded graph node. Has a default constructor because it
38416236077SArtem Dergachev# may be referenced as part of an edge before its contents are deserialized,
38516236077SArtem Dergachev# and in this moment we already need a room for predecessors and successors.
386c98872e3SValeriy Savchenkoclass ExplodedNode:
38716236077SArtem Dergachev    def __init__(self):
38816236077SArtem Dergachev        self.predecessors = []
38916236077SArtem Dergachev        self.successors = []
39016236077SArtem Dergachev
39116236077SArtem Dergachev    def construct(self, node_id, json_node):
392dd3c26a0STobias Hieta        logging.debug("Adding " + node_id)
39314e9eb3dSArtem Dergachev        self.ptr = node_id[4:]
394dd3c26a0STobias Hieta        self.points = [ProgramPoint(p) for p in json_node["program_points"]]
39514e9eb3dSArtem Dergachev        self.node_id = self.points[-1].node_id
396dd3c26a0STobias Hieta        self.state = ProgramState(
397dd3c26a0STobias Hieta            json_node["state_id"],
398dd3c26a0STobias Hieta            json_node["program_state"]
399dd3c26a0STobias Hieta            if json_node["program_state"] is not None
400dd3c26a0STobias Hieta            else None,
401dd3c26a0STobias Hieta        )
40216236077SArtem Dergachev
40316236077SArtem Dergachev        assert self.node_name() == node_id
40416236077SArtem Dergachev
40516236077SArtem Dergachev    def node_name(self):
406dd3c26a0STobias Hieta        return "Node" + self.ptr
40716236077SArtem Dergachev
40816236077SArtem Dergachev
40916236077SArtem Dergachev# A deserialized ExplodedGraph. Constructed by consuming a .dot file
41016236077SArtem Dergachev# line-by-line.
411c98872e3SValeriy Savchenkoclass ExplodedGraph:
41216236077SArtem Dergachev    # Parse .dot files with regular expressions.
41316236077SArtem Dergachev    node_re = re.compile(
414dd3c26a0STobias Hieta        '^(Node0x[0-9a-f]*) \\[shape=record,.*label="{(.*)\\\\l}"\\];$'
415dd3c26a0STobias Hieta    )
416dd3c26a0STobias Hieta    edge_re = re.compile("^(Node0x[0-9a-f]*) -> (Node0x[0-9a-f]*);$")
41716236077SArtem Dergachev
41816236077SArtem Dergachev    def __init__(self):
41916236077SArtem Dergachev        self.nodes = collections.defaultdict(ExplodedNode)
42016236077SArtem Dergachev        self.root_id = None
421dd3c26a0STobias Hieta        self.incomplete_line = ""
42216236077SArtem Dergachev
42316236077SArtem Dergachev    def add_raw_line(self, raw_line):
424dd3c26a0STobias Hieta        if raw_line.startswith("//"):
42516236077SArtem Dergachev            return
42616236077SArtem Dergachev
42716236077SArtem Dergachev        # Allow line breaks by waiting for ';'. This is not valid in
42816236077SArtem Dergachev        # a .dot file, but it is useful for writing tests.
429dd3c26a0STobias Hieta        if len(raw_line) > 0 and raw_line[-1] != ";":
43016236077SArtem Dergachev            self.incomplete_line += raw_line
43116236077SArtem Dergachev            return
43216236077SArtem Dergachev        raw_line = self.incomplete_line + raw_line
433dd3c26a0STobias Hieta        self.incomplete_line = ""
43416236077SArtem Dergachev
43516236077SArtem Dergachev        # Apply regexps one by one to see if it's a node or an edge
43616236077SArtem Dergachev        # and extract contents if necessary.
437dd3c26a0STobias Hieta        logging.debug("Line: " + raw_line)
43816236077SArtem Dergachev        result = self.edge_re.match(raw_line)
43916236077SArtem Dergachev        if result is not None:
440dd3c26a0STobias Hieta            logging.debug("Classified as edge line.")
44116236077SArtem Dergachev            pred = result.group(1)
44216236077SArtem Dergachev            succ = result.group(2)
44316236077SArtem Dergachev            self.nodes[pred].successors.append(succ)
44416236077SArtem Dergachev            self.nodes[succ].predecessors.append(pred)
44516236077SArtem Dergachev            return
44616236077SArtem Dergachev        result = self.node_re.match(raw_line)
44716236077SArtem Dergachev        if result is not None:
448dd3c26a0STobias Hieta            logging.debug("Classified as node line.")
44916236077SArtem Dergachev            node_id = result.group(1)
45016236077SArtem Dergachev            if len(self.nodes) == 0:
45116236077SArtem Dergachev                self.root_id = node_id
45216236077SArtem Dergachev            # Note: when writing tests you don't need to escape everything,
45316236077SArtem Dergachev            # even though in a valid dot file everything is escaped.
454dd3c26a0STobias Hieta            node_label = (
455dd3c26a0STobias Hieta                result.group(2)
456dd3c26a0STobias Hieta                .replace(" ", "")
457dd3c26a0STobias Hieta                .replace('\\"', '"')
458dd3c26a0STobias Hieta                .replace("\\{", "{")
459dd3c26a0STobias Hieta                .replace("\\}", "}")
460dd3c26a0STobias Hieta                .replace("\\\\", "\\")
461dd3c26a0STobias Hieta                .replace("\\|", "|")
462dd3c26a0STobias Hieta                .replace("\\<", "\\\\<")
463dd3c26a0STobias Hieta                .replace("\\>", "\\\\>")
464dd3c26a0STobias Hieta                .rstrip(",")
465dd3c26a0STobias Hieta            )
46601f9388dSDenys Petrov            # Handle `\l` separately because a string literal can be in code
46701f9388dSDenys Petrov            # like "string\\literal" with the `\l` inside.
46801f9388dSDenys Petrov            # Also on Windows macros __FILE__ produces specific delimiters `\`
46901f9388dSDenys Petrov            # and a directory or file may starts with the letter `l`.
47001f9388dSDenys Petrov            # Find all `\l` (like `,\l`, `}\l`, `[\l`) except `\\l`,
4715674a3c8SGabriel Ravier            # because the literal as a rule contains multiple `\` before `\l`.
472dd3c26a0STobias Hieta            node_label = re.sub(r"(?<!\\)\\l", "", node_label)
47316236077SArtem Dergachev            logging.debug(node_label)
47416236077SArtem Dergachev            json_node = json.loads(node_label)
47516236077SArtem Dergachev            self.nodes[node_id].construct(node_id, json_node)
47616236077SArtem Dergachev            return
477dd3c26a0STobias Hieta        logging.debug("Skipping.")
47816236077SArtem Dergachev
47916236077SArtem Dergachev
4805fcf92e1SArtem Dergachev# ===-----------------------------------------------------------------------===#
4815fcf92e1SArtem Dergachev# Visitors traverse a deserialized ExplodedGraph and do different things
4825fcf92e1SArtem Dergachev# with every node and edge.
4835fcf92e1SArtem Dergachev# ===-----------------------------------------------------------------------===#
4845fcf92e1SArtem Dergachev
4855fcf92e1SArtem Dergachev
48616236077SArtem Dergachev# A visitor that dumps the ExplodedGraph into a DOT file with fancy HTML-based
48716236077SArtem Dergachev# syntax highlighing.
488c98872e3SValeriy Savchenkoclass DotDumpVisitor:
4890c3e24f7SElla Ma    def __init__(
4900c3e24f7SElla Ma        self, do_diffs, dark_mode, gray_mode, topo_mode, dump_html_only, dump_dot_only
4910c3e24f7SElla Ma    ):
4920c3e24f7SElla Ma        assert not (dump_html_only and dump_dot_only), (
4930c3e24f7SElla Ma            "Option dump_html_only and dump_dot_only are conflict, "
4940c3e24f7SElla Ma            "they cannot be true at the same time."
4950c3e24f7SElla Ma        )
4960c3e24f7SElla Ma
4975740e77fSArtem Dergachev        self._do_diffs = do_diffs
498ad38e58eSArtem Dergachev        self._dark_mode = dark_mode
49978c0aefbSArtem Dergachev        self._gray_mode = gray_mode
500c6b5c5b9SArtem Dergachev        self._topo_mode = topo_mode
5010c3e24f7SElla Ma        self._dump_html_only = dump_html_only
502e9e36354SArtem Dergachev        self._dump_dot_only = dump_dot_only
503e9e36354SArtem Dergachev        self._output = []
50416236077SArtem Dergachev
505e9e36354SArtem Dergachev    def _dump_raw(self, s):
506e9e36354SArtem Dergachev        if self._dump_dot_only:
507dd3c26a0STobias Hieta            print(s, end="")
508e9e36354SArtem Dergachev        else:
509e9e36354SArtem Dergachev            self._output.append(s)
510e9e36354SArtem Dergachev
511e9e36354SArtem Dergachev    def output(self):
512e9e36354SArtem Dergachev        assert not self._dump_dot_only
513dd3c26a0STobias Hieta        return "".join(self._output)
51416236077SArtem Dergachev
51578c0aefbSArtem Dergachev    def _dump(self, s):
516dd3c26a0STobias Hieta        s = (
517dd3c26a0STobias Hieta            s.replace("&", "&amp;")
518dd3c26a0STobias Hieta            .replace("{", "\\{")
519dd3c26a0STobias Hieta            .replace("}", "\\}")
520dd3c26a0STobias Hieta            .replace("\\<", "&lt;")
521dd3c26a0STobias Hieta            .replace("\\>", "&gt;")
522dd3c26a0STobias Hieta            .replace("|", "\\|")
523dd3c26a0STobias Hieta        )
524dd3c26a0STobias Hieta        s = re.sub(r"(?<!\\)\\l", "<br />", s)
52578c0aefbSArtem Dergachev        if self._gray_mode:
526dd3c26a0STobias Hieta            s = re.sub(r'<font color="[a-z0-9]*">', "", s)
527dd3c26a0STobias Hieta            s = re.sub(r"</font>", "", s)
52878c0aefbSArtem Dergachev        self._dump_raw(s)
52916236077SArtem Dergachev
5305740e77fSArtem Dergachev    @staticmethod
5315740e77fSArtem Dergachev    def _diff_plus_minus(is_added):
5325740e77fSArtem Dergachev        if is_added is None:
533dd3c26a0STobias Hieta            return ""
5345740e77fSArtem Dergachev        if is_added:
5355740e77fSArtem Dergachev            return '<font color="forestgreen">+</font>'
5365740e77fSArtem Dergachev        return '<font color="red">-</font>'
5375740e77fSArtem Dergachev
53848a5c83aSArtem Dergachev    @staticmethod
53948a5c83aSArtem Dergachev    def _short_pretty(s):
54048a5c83aSArtem Dergachev        if s is None:
54148a5c83aSArtem Dergachev            return None
54248a5c83aSArtem Dergachev        if len(s) < 20:
54348a5c83aSArtem Dergachev            return s
544dd3c26a0STobias Hieta        left = s.find("{")
545dd3c26a0STobias Hieta        right = s.rfind("}")
54648a5c83aSArtem Dergachev        if left == -1 or right == -1 or left >= right:
54748a5c83aSArtem Dergachev            return s
548dd3c26a0STobias Hieta        candidate = s[0 : left + 1] + " ... " + s[right:]
54948a5c83aSArtem Dergachev        if len(candidate) >= len(s):
55048a5c83aSArtem Dergachev            return s
55148a5c83aSArtem Dergachev        return candidate
55248a5c83aSArtem Dergachev
553ed035ff8SArtem Dergachev    @staticmethod
554ed035ff8SArtem Dergachev    def _make_sloc(loc):
555ed035ff8SArtem Dergachev        if loc is None:
556dd3c26a0STobias Hieta            return "<i>Invalid Source Location</i>"
557ed035ff8SArtem Dergachev
558ed035ff8SArtem Dergachev        def make_plain_loc(loc):
559dd3c26a0STobias Hieta            return "%s:<b>%s</b>:<b>%s</b>" % (loc.filename, loc.line, loc.col)
560ed035ff8SArtem Dergachev
561ed035ff8SArtem Dergachev        if loc.is_macro():
562dd3c26a0STobias Hieta            return '%s <font color="royalblue1">' "(<i>spelling at </i> %s)</font>" % (
563dd3c26a0STobias Hieta                make_plain_loc(loc),
564dd3c26a0STobias Hieta                make_plain_loc(loc.spelling),
565dd3c26a0STobias Hieta            )
566ed035ff8SArtem Dergachev
567ed035ff8SArtem Dergachev        return make_plain_loc(loc)
568ed035ff8SArtem Dergachev
56916236077SArtem Dergachev    def visit_begin_graph(self, graph):
57016236077SArtem Dergachev        self._graph = graph
57116236077SArtem Dergachev        self._dump_raw('digraph "ExplodedGraph" {\n')
572ad38e58eSArtem Dergachev        if self._dark_mode:
573ad38e58eSArtem Dergachev            self._dump_raw('bgcolor="gray10";\n')
57416236077SArtem Dergachev        self._dump_raw('label="";\n')
57516236077SArtem Dergachev
57616236077SArtem Dergachev    def visit_program_point(self, p):
577dd3c26a0STobias Hieta        if p.kind in ["Edge", "BlockEntrance", "BlockExit"]:
578dd3c26a0STobias Hieta            color = "gold3"
579dd3c26a0STobias Hieta        elif p.kind in ["PreStmtPurgeDeadSymbols", "PostStmtPurgeDeadSymbols"]:
580dd3c26a0STobias Hieta            color = "red"
581dd3c26a0STobias Hieta        elif p.kind in ["CallEnter", "CallExitBegin", "CallExitEnd"]:
582dd3c26a0STobias Hieta            color = "dodgerblue" if self._dark_mode else "blue"
583dd3c26a0STobias Hieta        elif p.kind in ["Statement"]:
584dd3c26a0STobias Hieta            color = "cyan4"
58516236077SArtem Dergachev        else:
586dd3c26a0STobias Hieta            color = "forestgreen"
58716236077SArtem Dergachev
58814e9eb3dSArtem Dergachev        self._dump('<tr><td align="left">%s.</td>' % p.node_id)
58914e9eb3dSArtem Dergachev
590dd3c26a0STobias Hieta        if p.kind == "Statement":
5912ca53557SArtem Dergachev            # This avoids pretty-printing huge statements such as CompoundStmt.
5922ca53557SArtem Dergachev            # Such statements show up only at [Pre|Post]StmtPurgeDeadSymbols
593dd3c26a0STobias Hieta            skip_pretty = "PurgeDeadSymbols" in p.stmt_point_kind
594dd3c26a0STobias Hieta            stmt_color = "cyan3"
595dd3c26a0STobias Hieta            self._dump(
596dd3c26a0STobias Hieta                '<td align="left" width="0">%s:</td>'
59716236077SArtem Dergachev                '<td align="left" width="0"><font color="%s">'
598dd3c26a0STobias Hieta                "%s</font> </td>"
599ed035ff8SArtem Dergachev                '<td align="left"><i>S%s</i></td>'
6002ca53557SArtem Dergachev                '<td align="left"><font color="%s">%s</font></td>'
601ed035ff8SArtem Dergachev                '<td align="left">%s</td></tr>'
602dd3c26a0STobias Hieta                % (
603dd3c26a0STobias Hieta                    self._make_sloc(p.loc),
604dd3c26a0STobias Hieta                    color,
605dd3c26a0STobias Hieta                    "%s (%s)" % (p.stmt_kind, p.cast_kind)
606dd3c26a0STobias Hieta                    if p.cast_kind is not None
607dd3c26a0STobias Hieta                    else p.stmt_kind,
608dd3c26a0STobias Hieta                    p.stmt_id,
609dd3c26a0STobias Hieta                    stmt_color,
610dd3c26a0STobias Hieta                    p.stmt_point_kind,
611dd3c26a0STobias Hieta                    self._short_pretty(p.pretty) if not skip_pretty else "",
612dd3c26a0STobias Hieta                )
613dd3c26a0STobias Hieta            )
614dd3c26a0STobias Hieta        elif p.kind == "Edge":
615dd3c26a0STobias Hieta            self._dump(
616dd3c26a0STobias Hieta                '<td width="0"></td>'
61716236077SArtem Dergachev                '<td align="left" width="0">'
61816236077SArtem Dergachev                '<font color="%s">%s</font></td><td align="left">'
619dd3c26a0STobias Hieta                "[B%d] -\\> [B%d]</td></tr>" % (color, "BlockEdge", p.src_id, p.dst_id)
620dd3c26a0STobias Hieta            )
621dd3c26a0STobias Hieta        elif p.kind == "BlockEntrance":
622dd3c26a0STobias Hieta            self._dump(
623dd3c26a0STobias Hieta                '<td width="0"></td>'
6242ca53557SArtem Dergachev                '<td align="left" width="0">'
6252ca53557SArtem Dergachev                '<font color="%s">%s</font></td>'
626dd3c26a0STobias Hieta                '<td align="left">[B%d]</td></tr>' % (color, p.kind, p.block_id)
627dd3c26a0STobias Hieta            )
6289cbf2dd6SBalazs Benics        elif p.kind == "CallEnter":
6299cbf2dd6SBalazs Benics            self._dump(
6309cbf2dd6SBalazs Benics                '<td width="0"></td>'
6319cbf2dd6SBalazs Benics                '<td align="left" width="0">'
6329cbf2dd6SBalazs Benics                '<font color="%s">%s</font></td>'
6339cbf2dd6SBalazs Benics                '<td align="left">%s</td></tr>' % (color, p.kind, p.callee_decl)
6349cbf2dd6SBalazs Benics            )
635*20cb4ec8SBalazs Benics        elif p.kind == "PostInitializer":
636*20cb4ec8SBalazs Benics            self._dump(
637*20cb4ec8SBalazs Benics                '<td width="0"></td>'
638*20cb4ec8SBalazs Benics                '<td align="left" width="0">'
639*20cb4ec8SBalazs Benics                '<font color="%s">%s</font></td>'
640*20cb4ec8SBalazs Benics                '<td align="left">%s</td></tr>' % (color, p.kind, p.target)
641*20cb4ec8SBalazs Benics            )
64216236077SArtem Dergachev        else:
64316236077SArtem Dergachev            # TODO: Print more stuff for other kinds of points.
644dd3c26a0STobias Hieta            self._dump(
645dd3c26a0STobias Hieta                '<td width="0"></td>'
64616236077SArtem Dergachev                '<td align="left" width="0" colspan="2">'
647dd3c26a0STobias Hieta                '<font color="%s">%s</font></td></tr>' % (color, p.kind)
648dd3c26a0STobias Hieta            )
64916236077SArtem Dergachev
6505a72338bSArtem Dergachev        if p.tag is not None:
651dd3c26a0STobias Hieta            self._dump(
652dd3c26a0STobias Hieta                '<tr><td width="0"></td><td width="0"></td>'
6532ca53557SArtem Dergachev                '<td colspan="3" align="left">'
6545a72338bSArtem Dergachev                '<b>Tag: </b> <font color="crimson">'
655dd3c26a0STobias Hieta                "%s</font></td></tr>" % p.tag
656dd3c26a0STobias Hieta            )
6575a72338bSArtem Dergachev
65814e9eb3dSArtem Dergachev        if p.has_report:
659dd3c26a0STobias Hieta            self._dump(
660dd3c26a0STobias Hieta                '<tr><td width="0"></td><td width="0"></td>'
66114e9eb3dSArtem Dergachev                '<td colspan="3" align="left">'
66214e9eb3dSArtem Dergachev                '<font color="red"><b>Bug Report Attached'
663dd3c26a0STobias Hieta                "</b></font></td></tr>"
664dd3c26a0STobias Hieta            )
66514e9eb3dSArtem Dergachev        if p.is_sink:
666dd3c26a0STobias Hieta            self._dump(
667dd3c26a0STobias Hieta                '<tr><td width="0"></td><td width="0"></td>'
66814e9eb3dSArtem Dergachev                '<td colspan="3" align="left">'
66914e9eb3dSArtem Dergachev                '<font color="cornflowerblue"><b>Sink Node'
670dd3c26a0STobias Hieta                "</b></font></td></tr>"
671dd3c26a0STobias Hieta            )
67214e9eb3dSArtem Dergachev
6735740e77fSArtem Dergachev    def visit_environment(self, e, prev_e=None):
67416236077SArtem Dergachev        self._dump('<table border="0">')
67516236077SArtem Dergachev
6765740e77fSArtem Dergachev        def dump_location_context(lc, is_added=None):
677dd3c26a0STobias Hieta            self._dump(
678dd3c26a0STobias Hieta                "<tr><td>%s</td>"
6795740e77fSArtem Dergachev                '<td align="left"><b>%s</b></td>'
680628f36ffSArtem Dergachev                '<td align="left" colspan="2">'
681ad38e58eSArtem Dergachev                '<font color="gray60">%s </font>'
682dd3c26a0STobias Hieta                "%s</td></tr>"
683dd3c26a0STobias Hieta                % (
684dd3c26a0STobias Hieta                    self._diff_plus_minus(is_added),
685dd3c26a0STobias Hieta                    lc.caption,
686dd3c26a0STobias Hieta                    lc.decl,
687dd3c26a0STobias Hieta                    ("(%s)" % self._make_sloc(lc.loc)) if lc.loc is not None else "",
688dd3c26a0STobias Hieta                )
689dd3c26a0STobias Hieta            )
6905740e77fSArtem Dergachev
6915740e77fSArtem Dergachev        def dump_binding(f, b, is_added=None):
692dd3c26a0STobias Hieta            self._dump(
693dd3c26a0STobias Hieta                "<tr><td>%s</td>"
6945740e77fSArtem Dergachev                '<td align="left"><i>S%s</i></td>'
695dd3c26a0STobias Hieta                "%s"
69616236077SArtem Dergachev                '<td align="left">%s</td>'
69716236077SArtem Dergachev                '<td align="left">%s</td></tr>'
698dd3c26a0STobias Hieta                % (
699dd3c26a0STobias Hieta                    self._diff_plus_minus(is_added),
7000a77d919SArtem Dergachev                    b.stmt_id,
701ad38e58eSArtem Dergachev                    '<td align="left"><font color="%s"><i>'
702dd3c26a0STobias Hieta                    "%s</i></font></td>"
703dd3c26a0STobias Hieta                    % (
704dd3c26a0STobias Hieta                        "lavender" if self._dark_mode else "darkgreen",
705dd3c26a0STobias Hieta                        ("(%s)" % b.kind) if b.kind is not None else " ",
706ad38e58eSArtem Dergachev                    ),
707dd3c26a0STobias Hieta                    self._short_pretty(b.pretty),
708dd3c26a0STobias Hieta                    f.bindings[b],
709dd3c26a0STobias Hieta                )
710dd3c26a0STobias Hieta            )
7115740e77fSArtem Dergachev
7125740e77fSArtem Dergachev        frames_updated = e.diff_frames(prev_e) if prev_e is not None else None
7135740e77fSArtem Dergachev        if frames_updated:
7145740e77fSArtem Dergachev            for i in frames_updated:
7155740e77fSArtem Dergachev                f = e.frames[i]
7165740e77fSArtem Dergachev                prev_f = prev_e.frames[i]
7175740e77fSArtem Dergachev                dump_location_context(f.location_context)
7185740e77fSArtem Dergachev                bindings_removed, bindings_added = f.diff_bindings(prev_f)
7195740e77fSArtem Dergachev                for b in bindings_removed:
7205740e77fSArtem Dergachev                    dump_binding(prev_f, b, False)
7215740e77fSArtem Dergachev                for b in bindings_added:
7225740e77fSArtem Dergachev                    dump_binding(f, b, True)
7235740e77fSArtem Dergachev        else:
7245740e77fSArtem Dergachev            for f in e.frames:
7255740e77fSArtem Dergachev                dump_location_context(f.location_context)
7265740e77fSArtem Dergachev                for b in f.bindings:
7275740e77fSArtem Dergachev                    dump_binding(f, b)
72816236077SArtem Dergachev
729dd3c26a0STobias Hieta        self._dump("</table>")
73016236077SArtem Dergachev
7310a77d919SArtem Dergachev    def visit_environment_in_state(self, selector, title, s, prev_s=None):
7320a77d919SArtem Dergachev        e = getattr(s, selector)
7330a77d919SArtem Dergachev        prev_e = getattr(prev_s, selector) if prev_s is not None else None
7340a77d919SArtem Dergachev        if e is None and prev_e is None:
7350a77d919SArtem Dergachev            return
7360a77d919SArtem Dergachev
7370a77d919SArtem Dergachev        self._dump('<hr /><tr><td align="left"><b>%s: </b>' % title)
7380a77d919SArtem Dergachev        if e is None:
739dd3c26a0STobias Hieta            self._dump("<i> Nothing!</i>")
740b9c94f94SArtem Dergachev        else:
7410a77d919SArtem Dergachev            if prev_e is not None:
7420a77d919SArtem Dergachev                if e.is_different(prev_e):
743b9c94f94SArtem Dergachev                    self._dump('</td></tr><tr><td align="left">')
7440a77d919SArtem Dergachev                    self.visit_environment(e, prev_e)
745b9c94f94SArtem Dergachev                else:
746dd3c26a0STobias Hieta                    self._dump("<i> No changes!</i>")
747b9c94f94SArtem Dergachev            else:
748b9c94f94SArtem Dergachev                self._dump('</td></tr><tr><td align="left">')
7490a77d919SArtem Dergachev                self.visit_environment(e)
750b9c94f94SArtem Dergachev
751dd3c26a0STobias Hieta        self._dump("</td></tr>")
752b9c94f94SArtem Dergachev
7535740e77fSArtem Dergachev    def visit_store(self, s, prev_s=None):
75416236077SArtem Dergachev        self._dump('<table border="0">')
75516236077SArtem Dergachev
7565740e77fSArtem Dergachev        def dump_binding(s, c, b, is_added=None):
757dd3c26a0STobias Hieta            self._dump(
758dd3c26a0STobias Hieta                "<tr><td>%s</td>"
7595740e77fSArtem Dergachev                '<td align="left">%s</td>'
76016236077SArtem Dergachev                '<td align="left">%s</td>'
76116236077SArtem Dergachev                '<td align="left">%s</td>'
76216236077SArtem Dergachev                '<td align="left">%s</td></tr>'
763dd3c26a0STobias Hieta                % (
764dd3c26a0STobias Hieta                    self._diff_plus_minus(is_added),
765dd3c26a0STobias Hieta                    s.clusters[c].base_region,
766dd3c26a0STobias Hieta                    b.offset,
767dd3c26a0STobias Hieta                    "(<i>Default</i>)" if b.kind == "Default" else "",
768dd3c26a0STobias Hieta                    s.clusters[c].bindings[b],
769dd3c26a0STobias Hieta                )
770dd3c26a0STobias Hieta            )
7715740e77fSArtem Dergachev
7725740e77fSArtem Dergachev        if prev_s is not None:
773dd3c26a0STobias Hieta            clusters_removed, clusters_added, clusters_updated = s.diff_clusters(prev_s)
7745740e77fSArtem Dergachev            for c in clusters_removed:
7755740e77fSArtem Dergachev                for b in prev_s.clusters[c].bindings:
7765740e77fSArtem Dergachev                    dump_binding(prev_s, c, b, False)
7775740e77fSArtem Dergachev            for c in clusters_updated:
778dd3c26a0STobias Hieta                bindings_removed, bindings_added = s.clusters[c].diff_bindings(
779dd3c26a0STobias Hieta                    prev_s.clusters[c]
780dd3c26a0STobias Hieta                )
7815740e77fSArtem Dergachev                for b in bindings_removed:
7825740e77fSArtem Dergachev                    dump_binding(prev_s, c, b, False)
7835740e77fSArtem Dergachev                for b in bindings_added:
7845740e77fSArtem Dergachev                    dump_binding(s, c, b, True)
7855740e77fSArtem Dergachev            for c in clusters_added:
7865740e77fSArtem Dergachev                for b in s.clusters[c].bindings:
7875740e77fSArtem Dergachev                    dump_binding(s, c, b, True)
7885740e77fSArtem Dergachev        else:
7895740e77fSArtem Dergachev            for c in s.clusters:
7905740e77fSArtem Dergachev                for b in s.clusters[c].bindings:
7915740e77fSArtem Dergachev                    dump_binding(s, c, b)
79216236077SArtem Dergachev
793dd3c26a0STobias Hieta        self._dump("</table>")
79416236077SArtem Dergachev
795b9c94f94SArtem Dergachev    def visit_store_in_state(self, s, prev_s=None):
7960a77d919SArtem Dergachev        st = s.store
7970a77d919SArtem Dergachev        prev_st = prev_s.store if prev_s is not None else None
7980a77d919SArtem Dergachev        if st is None and prev_st is None:
7990a77d919SArtem Dergachev            return
8000a77d919SArtem Dergachev
80102f91ddfSArtem Dergachev        self._dump('<hr /><tr><td align="left"><b>Store: </b>')
8020a77d919SArtem Dergachev        if st is None:
803dd3c26a0STobias Hieta            self._dump("<i> Nothing!</i>")
80416236077SArtem Dergachev        else:
805daf41722SArtem Dergachev            if self._dark_mode:
806daf41722SArtem Dergachev                self._dump(' <font color="gray30">(%s)</font>' % st.ptr)
807daf41722SArtem Dergachev            else:
808daf41722SArtem Dergachev                self._dump(' <font color="gray">(%s)</font>' % st.ptr)
8090a77d919SArtem Dergachev            if prev_st is not None:
8100a77d919SArtem Dergachev                if s.store.is_different(prev_st):
8115740e77fSArtem Dergachev                    self._dump('</td></tr><tr><td align="left">')
8120a77d919SArtem Dergachev                    self.visit_store(st, prev_st)
8135740e77fSArtem Dergachev                else:
814dd3c26a0STobias Hieta                    self._dump("<i> No changes!</i>")
8155740e77fSArtem Dergachev            else:
8165740e77fSArtem Dergachev                self._dump('</td></tr><tr><td align="left">')
8170a77d919SArtem Dergachev                self.visit_store(st)
818dd3c26a0STobias Hieta        self._dump("</td></tr>")
819beb85ad6SArtem Dergachev
820beb85ad6SArtem Dergachev    def visit_generic_map(self, m, prev_m=None):
821beb85ad6SArtem Dergachev        self._dump('<table border="0">')
822beb85ad6SArtem Dergachev
823beb85ad6SArtem Dergachev        def dump_pair(m, k, is_added=None):
824dd3c26a0STobias Hieta            self._dump(
825dd3c26a0STobias Hieta                "<tr><td>%s</td>"
826beb85ad6SArtem Dergachev                '<td align="left">%s</td>'
827beb85ad6SArtem Dergachev                '<td align="left">%s</td></tr>'
828dd3c26a0STobias Hieta                % (self._diff_plus_minus(is_added), k, m.generic_map[k])
829dd3c26a0STobias Hieta            )
830beb85ad6SArtem Dergachev
831beb85ad6SArtem Dergachev        if prev_m is not None:
832beb85ad6SArtem Dergachev            removed, added = m.diff(prev_m)
833beb85ad6SArtem Dergachev            for k in removed:
834beb85ad6SArtem Dergachev                dump_pair(prev_m, k, False)
835beb85ad6SArtem Dergachev            for k in added:
836beb85ad6SArtem Dergachev                dump_pair(m, k, True)
837beb85ad6SArtem Dergachev        else:
838beb85ad6SArtem Dergachev            for k in m.generic_map:
839beb85ad6SArtem Dergachev                dump_pair(m, k, None)
840beb85ad6SArtem Dergachev
841dd3c26a0STobias Hieta        self._dump("</table>")
842beb85ad6SArtem Dergachev
84302f91ddfSArtem Dergachev    def visit_generic_map_in_state(self, selector, title, s, prev_s=None):
844beb85ad6SArtem Dergachev        m = getattr(s, selector)
84502f91ddfSArtem Dergachev        prev_m = getattr(prev_s, selector) if prev_s is not None else None
84602f91ddfSArtem Dergachev        if m is None and prev_m is None:
84702f91ddfSArtem Dergachev            return
84802f91ddfSArtem Dergachev
849dd3c26a0STobias Hieta        self._dump("<hr />")
850dd3c26a0STobias Hieta        self._dump('<tr><td align="left">' "<b>%s: </b>" % title)
851beb85ad6SArtem Dergachev        if m is None:
852dd3c26a0STobias Hieta            self._dump("<i> Nothing!</i>")
853beb85ad6SArtem Dergachev        else:
854beb85ad6SArtem Dergachev            if prev_m is not None:
855beb85ad6SArtem Dergachev                if m.is_different(prev_m):
856beb85ad6SArtem Dergachev                    self._dump('</td></tr><tr><td align="left">')
857beb85ad6SArtem Dergachev                    self.visit_generic_map(m, prev_m)
858beb85ad6SArtem Dergachev                else:
859dd3c26a0STobias Hieta                    self._dump("<i> No changes!</i>")
860deb7accbSArtem Dergachev            else:
861beb85ad6SArtem Dergachev                self._dump('</td></tr><tr><td align="left">')
862beb85ad6SArtem Dergachev                self.visit_generic_map(m)
863deb7accbSArtem Dergachev
864dd3c26a0STobias Hieta        self._dump("</td></tr>")
865deb7accbSArtem Dergachev
866deb7accbSArtem Dergachev    def visit_checker_messages(self, m, prev_m=None):
867deb7accbSArtem Dergachev        self._dump('<table border="0">')
868deb7accbSArtem Dergachev
869deb7accbSArtem Dergachev        def dump_line(l, is_added=None):
870dd3c26a0STobias Hieta            self._dump(
871dd3c26a0STobias Hieta                "<tr><td>%s</td>"
872dd3c26a0STobias Hieta                '<td align="left">%s</td></tr>' % (self._diff_plus_minus(is_added), l)
873dd3c26a0STobias Hieta            )
874deb7accbSArtem Dergachev
875deb7accbSArtem Dergachev        def dump_chk(chk, is_added=None):
876dd3c26a0STobias Hieta            dump_line("<i>%s</i>:" % chk, is_added)
877deb7accbSArtem Dergachev
878deb7accbSArtem Dergachev        if prev_m is not None:
879deb7accbSArtem Dergachev            removed, added, updated = m.diff_messages(prev_m)
880deb7accbSArtem Dergachev            for chk in removed:
881deb7accbSArtem Dergachev                dump_chk(chk, False)
882deb7accbSArtem Dergachev                for l in prev_m.items[chk].lines:
883deb7accbSArtem Dergachev                    dump_line(l, False)
884deb7accbSArtem Dergachev            for chk in updated:
885deb7accbSArtem Dergachev                dump_chk(chk)
886deb7accbSArtem Dergachev                for l in m.items[chk].diff_lines(prev_m.items[chk]):
887dd3c26a0STobias Hieta                    dump_line(l[1:], l.startswith("+"))
888deb7accbSArtem Dergachev            for chk in added:
889deb7accbSArtem Dergachev                dump_chk(chk, True)
890deb7accbSArtem Dergachev                for l in m.items[chk].lines:
891deb7accbSArtem Dergachev                    dump_line(l, True)
892deb7accbSArtem Dergachev        else:
893deb7accbSArtem Dergachev            for chk in m.items:
894deb7accbSArtem Dergachev                dump_chk(chk)
895deb7accbSArtem Dergachev                for l in m.items[chk].lines:
896deb7accbSArtem Dergachev                    dump_line(l)
897deb7accbSArtem Dergachev
898dd3c26a0STobias Hieta        self._dump("</table>")
899deb7accbSArtem Dergachev
900deb7accbSArtem Dergachev    def visit_checker_messages_in_state(self, s, prev_s=None):
901deb7accbSArtem Dergachev        m = s.checker_messages
902deb7accbSArtem Dergachev        prev_m = prev_s.checker_messages if prev_s is not None else None
903deb7accbSArtem Dergachev        if m is None and prev_m is None:
904deb7accbSArtem Dergachev            return
905deb7accbSArtem Dergachev
906dd3c26a0STobias Hieta        self._dump("<hr />")
907dd3c26a0STobias Hieta        self._dump('<tr><td align="left">' "<b>Checker State: </b>")
908deb7accbSArtem Dergachev        if m is None:
909dd3c26a0STobias Hieta            self._dump("<i> Nothing!</i>")
910deb7accbSArtem Dergachev        else:
911deb7accbSArtem Dergachev            if prev_m is not None:
912deb7accbSArtem Dergachev                if m.is_different(prev_m):
913deb7accbSArtem Dergachev                    self._dump('</td></tr><tr><td align="left">')
914deb7accbSArtem Dergachev                    self.visit_checker_messages(m, prev_m)
915deb7accbSArtem Dergachev                else:
916dd3c26a0STobias Hieta                    self._dump("<i> No changes!</i>")
917deb7accbSArtem Dergachev            else:
918deb7accbSArtem Dergachev                self._dump('</td></tr><tr><td align="left">')
919deb7accbSArtem Dergachev                self.visit_checker_messages(m)
920deb7accbSArtem Dergachev
921dd3c26a0STobias Hieta        self._dump("</td></tr>")
92216236077SArtem Dergachev
923b9c94f94SArtem Dergachev    def visit_state(self, s, prev_s):
924b9c94f94SArtem Dergachev        self.visit_store_in_state(s, prev_s)
925dd3c26a0STobias Hieta        self.visit_environment_in_state("environment", "Expressions", s, prev_s)
926dd3c26a0STobias Hieta        self.visit_generic_map_in_state("constraints", "Ranges", s, prev_s)
927dd3c26a0STobias Hieta        self.visit_generic_map_in_state("dynamic_types", "Dynamic Types", s, prev_s)
928dd3c26a0STobias Hieta        self.visit_environment_in_state(
929dd3c26a0STobias Hieta            "constructing_objects", "Objects Under Construction", s, prev_s
930dd3c26a0STobias Hieta        )
931dd3c26a0STobias Hieta        self.visit_environment_in_state(
932dd3c26a0STobias Hieta            "index_of_element", "Indices Of Elements Under Construction", s, prev_s
933dd3c26a0STobias Hieta        )
934dd3c26a0STobias Hieta        self.visit_environment_in_state(
935dd3c26a0STobias Hieta            "pending_init_loops", "Pending Array Init Loop Expressions", s, prev_s
936dd3c26a0STobias Hieta        )
937dd3c26a0STobias Hieta        self.visit_environment_in_state(
938dd3c26a0STobias Hieta            "pending_destructors", "Indices of Elements Under Destruction", s, prev_s
939dd3c26a0STobias Hieta        )
940deb7accbSArtem Dergachev        self.visit_checker_messages_in_state(s, prev_s)
94116236077SArtem Dergachev
94216236077SArtem Dergachev    def visit_node(self, node):
943dd3c26a0STobias Hieta        self._dump("%s [shape=record," % (node.node_name()))
944ad38e58eSArtem Dergachev        if self._dark_mode:
945ad38e58eSArtem Dergachev            self._dump('color="white",fontcolor="gray80",')
946ad38e58eSArtem Dergachev        self._dump('label=<<table border="0">')
94716236077SArtem Dergachev
948dd3c26a0STobias Hieta        self._dump(
949dd3c26a0STobias Hieta            '<tr><td bgcolor="%s"><b>State %s</b></td></tr>'
950dd3c26a0STobias Hieta            % (
951dd3c26a0STobias Hieta                "gray20" if self._dark_mode else "gray70",
952dd3c26a0STobias Hieta                node.state.state_id if node.state is not None else "Unspecified",
953dd3c26a0STobias Hieta            )
954dd3c26a0STobias Hieta        )
955c6b5c5b9SArtem Dergachev        if not self._topo_mode:
95616236077SArtem Dergachev            self._dump('<tr><td align="left" width="0">')
95716236077SArtem Dergachev            if len(node.points) > 1:
958dd3c26a0STobias Hieta                self._dump("<b>Program points:</b></td></tr>")
95916236077SArtem Dergachev            else:
960dd3c26a0STobias Hieta                self._dump("<b>Program point:</b></td></tr>")
961dd3c26a0STobias Hieta        self._dump(
962dd3c26a0STobias Hieta            '<tr><td align="left" width="0">'
963dd3c26a0STobias Hieta            '<table border="0" align="left" width="0">'
964dd3c26a0STobias Hieta        )
96516236077SArtem Dergachev        for p in node.points:
96616236077SArtem Dergachev            self.visit_program_point(p)
967dd3c26a0STobias Hieta        self._dump("</table></td></tr>")
96816236077SArtem Dergachev
969c6b5c5b9SArtem Dergachev        if node.state is not None and not self._topo_mode:
9705740e77fSArtem Dergachev            prev_s = None
9715740e77fSArtem Dergachev            # Do diffs only when we have a unique predecessor.
9725740e77fSArtem Dergachev            # Don't do diffs on the leaf nodes because they're
9735740e77fSArtem Dergachev            # the important ones.
974dd3c26a0STobias Hieta            if (
975dd3c26a0STobias Hieta                self._do_diffs
976dd3c26a0STobias Hieta                and len(node.predecessors) == 1
977dd3c26a0STobias Hieta                and len(node.successors) > 0
978dd3c26a0STobias Hieta            ):
9795740e77fSArtem Dergachev                prev_s = self._graph.nodes[node.predecessors[0]].state
9805740e77fSArtem Dergachev            self.visit_state(node.state, prev_s)
981dd3c26a0STobias Hieta        self._dump_raw("</table>>];\n")
98216236077SArtem Dergachev
98316236077SArtem Dergachev    def visit_edge(self, pred, succ):
984dd3c26a0STobias Hieta        self._dump_raw(
985dd3c26a0STobias Hieta            "%s -> %s%s;\n"
986dd3c26a0STobias Hieta            % (
987dd3c26a0STobias Hieta                pred.node_name(),
988dd3c26a0STobias Hieta                succ.node_name(),
989dd3c26a0STobias Hieta                ' [color="white"]' if self._dark_mode else "",
990dd3c26a0STobias Hieta            )
991dd3c26a0STobias Hieta        )
99216236077SArtem Dergachev
99316236077SArtem Dergachev    def visit_end_of_graph(self):
994dd3c26a0STobias Hieta        self._dump_raw("}\n")
99516236077SArtem Dergachev
996e9e36354SArtem Dergachev        if not self._dump_dot_only:
997e9e36354SArtem Dergachev            import sys
998e9e36354SArtem Dergachev            import tempfile
999e9e36354SArtem Dergachev
10005e876c54SBalazs Benics            def write_temp_file(suffix, prefix, data):
1001dd3c26a0STobias Hieta                fd, filename = tempfile.mkstemp(suffix, prefix, ".", True)
1002e9e36354SArtem Dergachev                print('Writing "%s"...' % filename)
1003dd3c26a0STobias Hieta                with os.fdopen(fd, "w") as fp:
1004e9e36354SArtem Dergachev                    fp.write(data)
1005dd3c26a0STobias Hieta                print("Done! Please remember to remove the file.")
1006e9e36354SArtem Dergachev                return filename
1007e9e36354SArtem Dergachev
1008e9e36354SArtem Dergachev            try:
1009e9e36354SArtem Dergachev                import graphviz
1010e9e36354SArtem Dergachev            except ImportError:
1011e9e36354SArtem Dergachev                # The fallback behavior if graphviz is not installed!
1012dd3c26a0STobias Hieta                print("Python graphviz not found. Please invoke")
1013dd3c26a0STobias Hieta                print("  $ pip install graphviz")
1014dd3c26a0STobias Hieta                print("in order to enable automatic conversion to HTML.")
1015e9e36354SArtem Dergachev                print()
1016dd3c26a0STobias Hieta                print("You may also convert DOT to SVG manually via")
1017dd3c26a0STobias Hieta                print("  $ dot -Tsvg input.dot -o output.svg")
1018e9e36354SArtem Dergachev                print()
1019dd3c26a0STobias Hieta                write_temp_file(".dot", "egraph-", self.output())
1020e9e36354SArtem Dergachev                return
1021e9e36354SArtem Dergachev
1022dd3c26a0STobias Hieta            svg = graphviz.pipe("dot", "svg", self.output().encode()).decode()
1023e9e36354SArtem Dergachev
1024e9e36354SArtem Dergachev            filename = write_temp_file(
1025dd3c26a0STobias Hieta                ".html",
1026dd3c26a0STobias Hieta                "egraph-",
1027dd3c26a0STobias Hieta                '<html><body bgcolor="%s">%s</body></html>'
1028dd3c26a0STobias Hieta                % ("#1a1a1a" if self._dark_mode else "white", svg),
1029dd3c26a0STobias Hieta            )
10300c3e24f7SElla Ma            if self._dump_html_only:
10310c3e24f7SElla Ma                return
1032dd3c26a0STobias Hieta            if sys.platform == "win32":
1033e9e36354SArtem Dergachev                os.startfile(filename)
1034dd3c26a0STobias Hieta            elif sys.platform == "darwin":
1035e9e36354SArtem Dergachev                os.system('open "%s"' % filename)
1036e9e36354SArtem Dergachev            else:
1037e9e36354SArtem Dergachev                os.system('xdg-open "%s"' % filename)
1038e9e36354SArtem Dergachev
103916236077SArtem Dergachev
10405fcf92e1SArtem Dergachev# ===-----------------------------------------------------------------------===#
10415fcf92e1SArtem Dergachev# Explorers know how to traverse the ExplodedGraph in a certain order.
10425fcf92e1SArtem Dergachev# They would invoke a Visitor on every node or edge they encounter.
10435fcf92e1SArtem Dergachev# ===-----------------------------------------------------------------------===#
10445fcf92e1SArtem Dergachev
10455fcf92e1SArtem Dergachev
104678566e45SArtem Dergachev# BasicExplorer explores the whole graph in no particular order.
1047c98872e3SValeriy Savchenkoclass BasicExplorer:
104816236077SArtem Dergachev    def explore(self, graph, visitor):
104916236077SArtem Dergachev        visitor.visit_begin_graph(graph)
105016236077SArtem Dergachev        for node in sorted(graph.nodes):
1051dd3c26a0STobias Hieta            logging.debug("Visiting " + node)
105216236077SArtem Dergachev            visitor.visit_node(graph.nodes[node])
105316236077SArtem Dergachev            for succ in sorted(graph.nodes[node].successors):
1054dd3c26a0STobias Hieta                logging.debug("Visiting edge: %s -> %s " % (node, succ))
105516236077SArtem Dergachev                visitor.visit_edge(graph.nodes[node], graph.nodes[succ])
105616236077SArtem Dergachev        visitor.visit_end_of_graph()
105716236077SArtem Dergachev
105816236077SArtem Dergachev
10590b26891fSArtem Dergachev# ===-----------------------------------------------------------------------===#
10600b26891fSArtem Dergachev# Trimmers cut out parts of the ExplodedGraph so that to focus on other parts.
10610b26891fSArtem Dergachev# Trimmers can be combined together by applying them sequentially.
10620b26891fSArtem Dergachev# ===-----------------------------------------------------------------------===#
10630b26891fSArtem Dergachev
10640b26891fSArtem Dergachev
10650b26891fSArtem Dergachev# SinglePathTrimmer keeps only a single path - the leftmost path from the root.
10660b26891fSArtem Dergachev# Useful when the trimmed graph is still too large.
1067c98872e3SValeriy Savchenkoclass SinglePathTrimmer:
10680b26891fSArtem Dergachev    def trim(self, graph):
10690b26891fSArtem Dergachev        visited_nodes = set()
107078566e45SArtem Dergachev        node_id = graph.root_id
107178566e45SArtem Dergachev        while True:
10720b26891fSArtem Dergachev            visited_nodes.add(node_id)
107378566e45SArtem Dergachev            node = graph.nodes[node_id]
10740b26891fSArtem Dergachev            if len(node.successors) > 0:
107578566e45SArtem Dergachev                succ_id = node.successors[0]
107678566e45SArtem Dergachev                succ = graph.nodes[succ_id]
10770b26891fSArtem Dergachev                node.successors = [succ_id]
10780b26891fSArtem Dergachev                succ.predecessors = [node_id]
10790b26891fSArtem Dergachev                if succ_id in visited_nodes:
108078566e45SArtem Dergachev                    break
108178566e45SArtem Dergachev                node_id = succ_id
10820b26891fSArtem Dergachev            else:
10830b26891fSArtem Dergachev                break
1084dd3c26a0STobias Hieta        graph.nodes = {node_id: graph.nodes[node_id] for node_id in visited_nodes}
108578566e45SArtem Dergachev
108678566e45SArtem Dergachev
10879289681eSArtem Dergachev# TargetedTrimmer keeps paths that lead to specific nodes and discards all
10889289681eSArtem Dergachev# other paths. Useful when you cannot use -trim-egraph (e.g. when debugging
10899289681eSArtem Dergachev# a crash).
1090c98872e3SValeriy Savchenkoclass TargetedTrimmer:
10919289681eSArtem Dergachev    def __init__(self, target_nodes):
10929289681eSArtem Dergachev        self._target_nodes = target_nodes
10939289681eSArtem Dergachev
10949289681eSArtem Dergachev    @staticmethod
10959289681eSArtem Dergachev    def parse_target_node(node, graph):
1096dd3c26a0STobias Hieta        if node.startswith("0x"):
1097dd3c26a0STobias Hieta            ret = "Node" + node
10989289681eSArtem Dergachev            assert ret in graph.nodes
10999289681eSArtem Dergachev            return ret
11009289681eSArtem Dergachev        else:
11019289681eSArtem Dergachev            for other_id in graph.nodes:
11029289681eSArtem Dergachev                other = graph.nodes[other_id]
11039289681eSArtem Dergachev                if other.node_id == int(node):
11049289681eSArtem Dergachev                    return other_id
11059289681eSArtem Dergachev
11069289681eSArtem Dergachev    @staticmethod
11079289681eSArtem Dergachev    def parse_target_nodes(target_nodes, graph):
1108dd3c26a0STobias Hieta        return [
1109dd3c26a0STobias Hieta            TargetedTrimmer.parse_target_node(node, graph)
1110dd3c26a0STobias Hieta            for node in target_nodes.split(",")
1111dd3c26a0STobias Hieta        ]
11129289681eSArtem Dergachev
11139289681eSArtem Dergachev    def trim(self, graph):
11149289681eSArtem Dergachev        queue = self._target_nodes
11159289681eSArtem Dergachev        visited_nodes = set()
11169289681eSArtem Dergachev
11179289681eSArtem Dergachev        while len(queue) > 0:
11189289681eSArtem Dergachev            node_id = queue.pop()
11199289681eSArtem Dergachev            visited_nodes.add(node_id)
11209289681eSArtem Dergachev            node = graph.nodes[node_id]
11219289681eSArtem Dergachev            for pred_id in node.predecessors:
11229289681eSArtem Dergachev                if pred_id not in visited_nodes:
11239289681eSArtem Dergachev                    queue.append(pred_id)
1124dd3c26a0STobias Hieta        graph.nodes = {node_id: graph.nodes[node_id] for node_id in visited_nodes}
11259289681eSArtem Dergachev        for node_id in graph.nodes:
11269289681eSArtem Dergachev            node = graph.nodes[node_id]
1127dd3c26a0STobias Hieta            node.successors = [
1128dd3c26a0STobias Hieta                succ_id for succ_id in node.successors if succ_id in visited_nodes
1129dd3c26a0STobias Hieta            ]
1130dd3c26a0STobias Hieta            node.predecessors = [
1131dd3c26a0STobias Hieta                succ_id for succ_id in node.predecessors if succ_id in visited_nodes
1132dd3c26a0STobias Hieta            ]
11339289681eSArtem Dergachev
11349289681eSArtem Dergachev
11355fcf92e1SArtem Dergachev# ===-----------------------------------------------------------------------===#
11365fcf92e1SArtem Dergachev# The entry point to the script.
11375fcf92e1SArtem Dergachev# ===-----------------------------------------------------------------------===#
11385fcf92e1SArtem Dergachev
11395fcf92e1SArtem Dergachev
114016236077SArtem Dergachevdef main():
1141e9e36354SArtem Dergachev    parser = argparse.ArgumentParser(
1142dd3c26a0STobias Hieta        description="Display and manipulate Exploded Graph dumps."
1143dd3c26a0STobias Hieta    )
1144dd3c26a0STobias Hieta    parser.add_argument(
1145dd3c26a0STobias Hieta        "filename", type=str, help="the .dot file produced by the Static Analyzer"
1146dd3c26a0STobias Hieta    )
1147dd3c26a0STobias Hieta    parser.add_argument(
1148dd3c26a0STobias Hieta        "-v",
1149dd3c26a0STobias Hieta        "--verbose",
1150dd3c26a0STobias Hieta        action="store_const",
1151dd3c26a0STobias Hieta        dest="loglevel",
1152dd3c26a0STobias Hieta        const=logging.DEBUG,
115316236077SArtem Dergachev        default=logging.WARNING,
1154dd3c26a0STobias Hieta        help="enable info prints",
1155dd3c26a0STobias Hieta    )
1156dd3c26a0STobias Hieta    parser.add_argument(
1157dd3c26a0STobias Hieta        "-d",
1158dd3c26a0STobias Hieta        "--diff",
1159dd3c26a0STobias Hieta        action="store_const",
1160dd3c26a0STobias Hieta        dest="diff",
1161dd3c26a0STobias Hieta        const=True,
1162dd3c26a0STobias Hieta        default=False,
1163dd3c26a0STobias Hieta        help="display differences between states",
1164dd3c26a0STobias Hieta    )
1165dd3c26a0STobias Hieta    parser.add_argument(
1166dd3c26a0STobias Hieta        "-t",
1167dd3c26a0STobias Hieta        "--topology",
1168dd3c26a0STobias Hieta        action="store_const",
1169dd3c26a0STobias Hieta        dest="topology",
1170dd3c26a0STobias Hieta        const=True,
1171dd3c26a0STobias Hieta        default=False,
1172dd3c26a0STobias Hieta        help="only display program points, omit states",
1173dd3c26a0STobias Hieta    )
1174dd3c26a0STobias Hieta    parser.add_argument(
1175dd3c26a0STobias Hieta        "-s",
1176dd3c26a0STobias Hieta        "--single-path",
1177dd3c26a0STobias Hieta        action="store_const",
1178dd3c26a0STobias Hieta        dest="single_path",
1179dd3c26a0STobias Hieta        const=True,
1180dd3c26a0STobias Hieta        default=False,
1181dd3c26a0STobias Hieta        help="only display the leftmost path in the graph "
1182dd3c26a0STobias Hieta        "(useful for trimmed graphs that still "
1183dd3c26a0STobias Hieta        "branch too much)",
1184dd3c26a0STobias Hieta    )
1185dd3c26a0STobias Hieta    parser.add_argument(
1186dd3c26a0STobias Hieta        "--to",
1187dd3c26a0STobias Hieta        type=str,
1188dd3c26a0STobias Hieta        default=None,
1189dd3c26a0STobias Hieta        help="only display execution paths from the root "
1190dd3c26a0STobias Hieta        "to the given comma-separated list of nodes "
1191dd3c26a0STobias Hieta        "identified by a pointer or a stable ID; "
1192dd3c26a0STobias Hieta        "compatible with --single-path",
1193dd3c26a0STobias Hieta    )
1194dd3c26a0STobias Hieta    parser.add_argument(
1195dd3c26a0STobias Hieta        "--dark",
1196dd3c26a0STobias Hieta        action="store_const",
1197dd3c26a0STobias Hieta        dest="dark",
1198dd3c26a0STobias Hieta        const=True,
1199dd3c26a0STobias Hieta        default=False,
1200dd3c26a0STobias Hieta        help="dark mode",
1201dd3c26a0STobias Hieta    )
1202dd3c26a0STobias Hieta    parser.add_argument(
1203dd3c26a0STobias Hieta        "--gray",
1204dd3c26a0STobias Hieta        action="store_const",
1205dd3c26a0STobias Hieta        dest="gray",
1206dd3c26a0STobias Hieta        const=True,
1207dd3c26a0STobias Hieta        default=False,
1208dd3c26a0STobias Hieta        help="black-and-white mode",
1209dd3c26a0STobias Hieta    )
12100c3e24f7SElla Ma    dump_conflict = parser.add_mutually_exclusive_group()
12110c3e24f7SElla Ma    dump_conflict.add_argument(
12120c3e24f7SElla Ma        "--dump-html-only",
12130c3e24f7SElla Ma        action="store_const",
12140c3e24f7SElla Ma        dest="dump_html_only",
12150c3e24f7SElla Ma        const=True,
12160c3e24f7SElla Ma        default=False,
12170c3e24f7SElla Ma        help="dump the rewritten egraph to a temporary HTML file, "
12180c3e24f7SElla Ma        "but do not open it immediately as by default",
12190c3e24f7SElla Ma    )
12200c3e24f7SElla Ma    dump_conflict.add_argument(
1221dd3c26a0STobias Hieta        "--dump-dot-only",
1222dd3c26a0STobias Hieta        action="store_const",
1223dd3c26a0STobias Hieta        dest="dump_dot_only",
1224dd3c26a0STobias Hieta        const=True,
1225dd3c26a0STobias Hieta        default=False,
1226dd3c26a0STobias Hieta        help="instead of writing an HTML file and immediately "
1227dd3c26a0STobias Hieta        "displaying it, dump the rewritten dot file "
1228dd3c26a0STobias Hieta        "to stdout",
1229dd3c26a0STobias Hieta    )
123016236077SArtem Dergachev    args = parser.parse_args()
123116236077SArtem Dergachev    logging.basicConfig(level=args.loglevel)
123216236077SArtem Dergachev
123316236077SArtem Dergachev    graph = ExplodedGraph()
123416236077SArtem Dergachev    with open(args.filename) as fd:
123516236077SArtem Dergachev        for raw_line in fd:
123616236077SArtem Dergachev            raw_line = raw_line.strip()
123716236077SArtem Dergachev            graph.add_raw_line(raw_line)
123816236077SArtem Dergachev
12390b26891fSArtem Dergachev    trimmers = []
12409289681eSArtem Dergachev    if args.to is not None:
1241dd3c26a0STobias Hieta        trimmers.append(
1242dd3c26a0STobias Hieta            TargetedTrimmer(TargetedTrimmer.parse_target_nodes(args.to, graph))
1243dd3c26a0STobias Hieta        )
12440b26891fSArtem Dergachev    if args.single_path:
12450b26891fSArtem Dergachev        trimmers.append(SinglePathTrimmer())
12460b26891fSArtem Dergachev
12470b26891fSArtem Dergachev    explorer = BasicExplorer()
12480b26891fSArtem Dergachev
1249dd3c26a0STobias Hieta    visitor = DotDumpVisitor(
12500c3e24f7SElla Ma        args.diff,
12510c3e24f7SElla Ma        args.dark,
12520c3e24f7SElla Ma        args.gray,
12530c3e24f7SElla Ma        args.topology,
12540c3e24f7SElla Ma        args.dump_html_only,
12550c3e24f7SElla Ma        args.dump_dot_only,
1256dd3c26a0STobias Hieta    )
125778566e45SArtem Dergachev
12580b26891fSArtem Dergachev    for trimmer in trimmers:
12590b26891fSArtem Dergachev        trimmer.trim(graph)
12600b26891fSArtem Dergachev
126116236077SArtem Dergachev    explorer.explore(graph, visitor)
126216236077SArtem Dergachev
126316236077SArtem Dergachev
1264dd3c26a0STobias Hietaif __name__ == "__main__":
126516236077SArtem Dergachev    main()
1266