From e5693b8a981456ed77d5e539aae62327bdbaad3b Mon Sep 17 00:00:00 2001 From: Margus Veanes <veanes@users.noreply.github.com> Date: Sun, 9 Aug 2020 14:53:29 -0700 Subject: [PATCH] added support for saving state graph in dot format (#4621) * added support for saving state graph in dot format * moved write_dgml and write_dot under CASSERT * updated dgml and dot generation a bit so that a state that is both and alive state is detected as having green background but red border when the invariant is vioalted --- src/util/state_graph.cpp | 64 +++++++++++++++++++++++++++++++++++----- src/util/state_graph.h | 10 +++++-- 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/util/state_graph.cpp b/src/util/state_graph.cpp index 1a59dc4af..b5f621a00 100644 --- a/src/util/state_graph.cpp +++ b/src/util/state_graph.cpp @@ -263,8 +263,9 @@ void state_graph::add_state(state s) { if (m_seen.contains(s)) return; STRACE("state_graph", tout << "[state_graph] adding state " << s << ": ";); add_state_core(s); + CASSERT("state_graph", write_dgml()); + CASSERT("state_graph", write_dot()); CASSERT("state_graph", check_invariant()); - STRACE("state_graph", write_dgml();); STRACE("state_graph", tout << std::endl;); } void state_graph::mark_live(state s) { @@ -273,8 +274,9 @@ void state_graph::mark_live(state s) { SASSERT(m_state_ufind.is_root(s)); if (m_unexplored.contains(s)) mark_unknown_core(s); mark_live_recursive(s); + CASSERT("state_graph", write_dgml()); + CASSERT("state_graph", write_dot()); CASSERT("state_graph", check_invariant()); - STRACE("state_graph", write_dgml();); STRACE("state_graph", tout << std::endl;); } void state_graph::add_edge(state s1, state s2, bool maybecycle) { @@ -286,8 +288,9 @@ void state_graph::add_edge(state s1, state s2, bool maybecycle) { s2 = m_state_ufind.find(s2); add_edge_core(s1, s2, maybecycle); if (m_live.contains(s2)) mark_live(s1); + CASSERT("state_graph", write_dgml()); + CASSERT("state_graph", write_dot()); CASSERT("state_graph", check_invariant()); - STRACE("state_graph", write_dgml();); STRACE("state_graph", tout << std::endl;); } void state_graph::mark_done(state s) { @@ -298,8 +301,9 @@ void state_graph::mark_done(state s) { if (m_unexplored.contains(s)) mark_unknown_core(s); s = merge_all_cycles(s); mark_dead_recursive(s); // check if dead + CASSERT("state_graph", write_dgml()); + CASSERT("state_graph", write_dot()); CASSERT("state_graph", check_invariant()); - STRACE("state_graph", write_dgml();); STRACE("state_graph", tout << std::endl;); } @@ -413,10 +417,11 @@ std::ostream& state_graph::display(std::ostream& o) const { return o; } +#ifdef Z3DEBUG /* Output the whole state graph in dgml format into the file '.z3-state-graph.dgml' */ -void state_graph::write_dgml() { +bool state_graph::write_dgml() { std::ofstream dgml(".z3-state-graph.dgml"); dgml << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl << "<DirectedGraph xmlns=\"http://schemas.microsoft.com/vs/2009/dgml\" GraphDirection=\"TopToBottom\">" << std::endl @@ -432,10 +437,10 @@ void state_graph::write_dgml() { r = m_state_ufind.next(r); } while (r != s); dgml << "\" Category=\"State\">" << std::endl; - if (m_live.contains(s)) - dgml << "<Category Ref=\"Alive\"/>" << std::endl; if (m_dead.contains(s)) dgml << "<Category Ref=\"Dead\"/>" << std::endl; + if (m_live.contains(s)) + dgml << "<Category Ref=\"Alive\"/>" << std::endl; if (m_unexplored.contains(s)) dgml << "<Category Ref=\"Unexplored\"/>" << std::endl; dgml << "</Node>" << std::endl; @@ -469,6 +474,7 @@ void state_graph::write_dgml() { << "<Style TargetType=\"Node\" GroupLabel=\"Dead\" ValueLabel=\"True\">" << std::endl << "<Condition Expression=\"HasCategory('Dead')\"/>" << std::endl << "<Setter Property=\"Background\" Value=\"tomato\"/>" << std::endl + << "<Setter Property=\"Stroke\" Value=\"tomato\"/>" << std::endl << "</Style>" << std::endl << "<Style TargetType=\"Node\" GroupLabel=\"Unexplored\" ValueLabel=\"True\">" << std::endl << "<Condition Expression=\"HasCategory('Unexplored')\"/>" << std::endl @@ -491,4 +497,48 @@ void state_graph::write_dgml() { << "</Styles>" << std::endl << "</DirectedGraph>" << std::endl; dgml.close(); + return true; } + +/* + Output the whole state graph in dot format into the file '.z3-state-graph.dot' + */ +bool state_graph::write_dot() { + std::ofstream dot(".z3-state-graph.dot"); + dot << "digraph \"state_graph\" {" << std::endl + << "rankdir=TB" << std::endl + << "node [peripheries=1,style=filled,fillcolor=white,fontsize=24]" << std::endl; + for (auto s : m_seen) { + if (m_state_ufind.is_root(s)) { + dot << s << " [label=\""; + auto r = s; + dot << r; + do { + if (r != s) + dot << "," << r; + r = m_state_ufind.next(r); + } while (r != s); + dot << "\""; + if (m_unexplored.contains(s)) + dot << ",style=\"dashed,filled\""; + if (m_dead.contains(s)) + dot << ",color=tomato,fillcolor=tomato"; + if (m_live.contains(s)) + dot << ",fillcolor=green"; + dot << "]" << std::endl; + } + } + for (auto s : m_seen) { + if (m_state_ufind.is_root(s)) + for (auto t : m_targets[s]) { + dot << s << " -> " << t; + if (!m_sources_maybecycle[t].contains(s)) + dot << "[style=bold]"; + dot << std::endl; + } + } + dot << "}" << std::endl; + dot.close(); + return true; +} +#endif diff --git a/src/util/state_graph.h b/src/util/state_graph.h index 9e754048e..61e19946a 100644 --- a/src/util/state_graph.h +++ b/src/util/state_graph.h @@ -112,10 +112,16 @@ private: bool is_subset(state_set set1, state_set set2) const; bool is_disjoint(state_set set1, state_set set2) const; bool check_invariant() const; + /* + Output the whole state graph in dgml format into the file '.z3-state-graph.dgml' + */ + bool write_dgml(); + /* + Output the whole state graph in dot format into the file '.z3-state-graph.dot' + */ + bool write_dot(); #endif - void write_dgml(); - /* 'Core' functions that modify the plain graph, without updating SCCs or propagating live/dead state information.