mirror of git://gcc.gnu.org/git/gcc.git
565 lines
15 KiB
C++
565 lines
15 KiB
C++
/* Creating GraphViz .dot files from diagnostic state graphs.
|
|
Copyright (C) 2025 Free Software Foundation, Inc.
|
|
Contributed by David Malcolm <dmalcolm@redhat.com>.
|
|
|
|
This file is part of GCC.
|
|
|
|
GCC is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3, or (at your option)
|
|
any later version.
|
|
|
|
GCC is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GCC; see the file COPYING3. If not see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#define INCLUDE_ALGORITHM
|
|
#define INCLUDE_MAP
|
|
#define INCLUDE_SET
|
|
#define INCLUDE_STRING
|
|
#define INCLUDE_VECTOR
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include "coretypes.h"
|
|
|
|
#include "custom-sarif-properties/state-graphs.h"
|
|
#include "diagnostics/state-graphs.h"
|
|
#include "graphviz.h"
|
|
#include "xml.h"
|
|
#include "xml-printer.h"
|
|
#include "intl.h"
|
|
|
|
using namespace diagnostics;
|
|
using namespace diagnostics::state_graphs;
|
|
|
|
namespace state_node_properties = custom_sarif_properties::state_graphs::node;
|
|
|
|
static int
|
|
get_depth (const digraphs::node &n)
|
|
{
|
|
int deepest_child = 0;
|
|
for (size_t i = 0; i < n.get_num_children (); ++i)
|
|
deepest_child = std::max (deepest_child,
|
|
get_depth (n.get_child (i)));
|
|
return deepest_child + 1;
|
|
}
|
|
|
|
static const char *
|
|
get_color_for_dynalloc_state (enum state_node_properties::dynalloc_state_t dynalloc_st)
|
|
{
|
|
switch (dynalloc_st)
|
|
{
|
|
default:
|
|
gcc_unreachable ();
|
|
break;
|
|
case state_node_properties::dynalloc_state_t::unknown:
|
|
case state_node_properties::dynalloc_state_t::nonnull:
|
|
return nullptr;
|
|
|
|
case state_node_properties::dynalloc_state_t::unchecked:
|
|
return "#ec7a08"; // pf-orange-400
|
|
|
|
case state_node_properties::dynalloc_state_t::freed:
|
|
return "#cc0000"; // pf-red-100
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_color_for_dynalloc_state (dot::attr_list &attrs,
|
|
enum state_node_properties::dynalloc_state_t state)
|
|
{
|
|
if (const char *color = get_color_for_dynalloc_state (state))
|
|
attrs.add (dot::id ("color"), dot::id (color));
|
|
}
|
|
|
|
class state_diagram : public dot::graph
|
|
{
|
|
public:
|
|
state_diagram (const diagnostics::digraphs::digraph &input_state_graph,
|
|
const logical_locations::manager &logical_loc_mgr)
|
|
: m_logical_loc_mgr (logical_loc_mgr)
|
|
{
|
|
// "node [shape=plaintext]\n"
|
|
{
|
|
auto attr_stmt
|
|
= std::make_unique<dot::attr_stmt> (dot::attr_stmt::kind::node);
|
|
attr_stmt->m_attrs.add (dot::id ("shape"), dot::id ("plaintext"));
|
|
add_stmt (std::move (attr_stmt));
|
|
}
|
|
|
|
/* Determine which nodes are involved in edges. */
|
|
for (size_t i = 0; i < input_state_graph.get_num_edges (); ++i)
|
|
{
|
|
auto &edge = input_state_graph.get_edge (i);
|
|
m_src_nodes.insert (&edge.get_src_node ());
|
|
m_dst_nodes.insert (&edge.get_dst_node ());
|
|
}
|
|
|
|
/* Recurse down the nodes in the state graph, creating subgraphs
|
|
and then eventually creating nodes, and recursively
|
|
creating XML tables, and adding ports for the endpoints of edges
|
|
where needed. */
|
|
|
|
auto root_cluster
|
|
= std::make_unique<dot::subgraph> (dot::id ("cluster_memory_regions"));
|
|
for (size_t i = 0; i < input_state_graph.get_num_nodes (); ++i)
|
|
on_input_state_node (*root_cluster,
|
|
input_state_graph.get_node (i));
|
|
add_stmt (std::move (root_cluster));
|
|
|
|
/* Now create dot edges for edges in input_stage_graph. */
|
|
for (size_t i = 0; i < input_state_graph.get_num_edges (); ++i)
|
|
{
|
|
auto &edge = input_state_graph.get_edge (i);
|
|
auto &src_node = edge.get_src_node ();
|
|
auto &dst_node = edge.get_dst_node ();
|
|
|
|
auto src_port_id = m_src_node_to_port_id.find (&src_node);
|
|
if (src_port_id == m_src_node_to_port_id.end ())
|
|
continue;
|
|
auto dst_port_id = m_dst_node_to_port_id.find (&dst_node);
|
|
if (dst_port_id == m_dst_node_to_port_id.end ())
|
|
continue;
|
|
|
|
auto e = std::make_unique<dot::edge_stmt> (src_port_id->second,
|
|
dst_port_id->second);
|
|
set_color_for_dynalloc_state
|
|
(e->m_attrs,
|
|
dst_node.get_property (state_node_properties::dynalloc_state_prop));
|
|
|
|
add_stmt (std::move (e));
|
|
}
|
|
}
|
|
|
|
private:
|
|
struct pending_edge
|
|
{
|
|
dot::node_id m_src_node_id;
|
|
std::string m_dst_region_id;
|
|
};
|
|
|
|
dot::id
|
|
get_id_for_region (const char *region_id)
|
|
{
|
|
gcc_assert (region_id);
|
|
return std::string ("cluster_region_") + region_id;
|
|
}
|
|
|
|
dot::id
|
|
make_id (const diagnostics::digraphs::node &state_node, bool cluster)
|
|
{
|
|
std::string input_node_id = state_node.get_id ();
|
|
if (cluster)
|
|
return std::string ("cluster_") + input_node_id;
|
|
else
|
|
return input_node_id;
|
|
}
|
|
|
|
bool
|
|
starts_node_p (const diagnostics::digraphs::node &state_node)
|
|
{
|
|
switch (state_node.get_property (state_node_properties::kind_prop))
|
|
{
|
|
default:
|
|
return false;
|
|
|
|
case state_node_properties::kind_t::stack:
|
|
/* We want all frames in the stack in the same table,
|
|
so they are grouped. */
|
|
case state_node_properties::kind_t::dynalloc_buffer:
|
|
case state_node_properties::kind_t::variable:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const char *
|
|
get_label_for_node (const diagnostics::digraphs::node &state_node)
|
|
{
|
|
switch (state_node.get_property (state_node_properties::kind_prop))
|
|
{
|
|
default:
|
|
return nullptr;
|
|
|
|
case state_node_properties::kind_t::globals:
|
|
return _("Globals");
|
|
case state_node_properties::kind_t::code:
|
|
return _("Code");
|
|
case state_node_properties::kind_t::stack:
|
|
return _("Stack");
|
|
case state_node_properties::kind_t::heap_:
|
|
return _("Heap");
|
|
}
|
|
}
|
|
|
|
void
|
|
on_input_state_node (dot::subgraph &parent_subgraph,
|
|
const diagnostics::digraphs::node &state_node)
|
|
{
|
|
dot::id sg_id = make_id (state_node, true);
|
|
|
|
if (starts_node_p (state_node))
|
|
{
|
|
// Create node with table
|
|
xml::element table ("table", false);
|
|
xml::printer xp (table);
|
|
xp.set_attr ("border", "0");
|
|
xp.set_attr ("cellborder", "1");
|
|
xp.set_attr ("cellspacing", "0");
|
|
|
|
const int max_depth = get_depth (state_node);
|
|
const int num_columns = max_depth + 2;
|
|
|
|
dot::id id_of_dot_node = make_id (state_node, false);
|
|
on_node_in_table (id_of_dot_node, xp, state_node,
|
|
max_depth, 0, num_columns);
|
|
|
|
auto node = std::make_unique<dot::node_stmt> (std::move (id_of_dot_node));
|
|
node->m_attrs.add (dot::id ("shape"),
|
|
dot::id ("plaintext"));
|
|
|
|
// xml must be done by now
|
|
|
|
node->m_attrs.add (dot::id ("label"),
|
|
dot::id (table));
|
|
|
|
parent_subgraph.m_stmt_list.add_stmt (std::move (node));
|
|
}
|
|
else
|
|
{
|
|
auto child_subgraph = std::make_unique<dot::subgraph> (std::move (sg_id));
|
|
|
|
if (const char *label = get_label_for_node (state_node))
|
|
child_subgraph->add_attr (dot::id ("label"), dot::id (label));
|
|
|
|
// recurse:
|
|
for (size_t i = 0; i < state_node.get_num_children (); ++i)
|
|
on_input_state_node (*child_subgraph,
|
|
state_node.get_child (i));
|
|
parent_subgraph.m_stmt_list.add_stmt (std::move (child_subgraph));
|
|
}
|
|
}
|
|
|
|
enum class style { h1, h2 };
|
|
|
|
void
|
|
add_title_tr (const dot::id &id_of_dot_node,
|
|
xml::printer &xp,
|
|
int num_columns,
|
|
const diagnostics::digraphs::node &state_node,
|
|
std::string heading,
|
|
enum style styl,
|
|
enum state_node_properties::dynalloc_state_t dynalloc_state)
|
|
{
|
|
xp.push_tag ("tr", true);
|
|
xp.push_tag ("td", false);
|
|
xp.set_attr ("colspan", std::to_string (num_columns));
|
|
xp.set_attr ("cellpadding", "5");
|
|
|
|
const char *bgcolor;
|
|
const char *color;
|
|
if (const char *c = get_color_for_dynalloc_state (dynalloc_state))
|
|
{
|
|
bgcolor = c;
|
|
color = "white";
|
|
}
|
|
else
|
|
switch (styl)
|
|
{
|
|
default:
|
|
gcc_unreachable ();
|
|
case style::h1:
|
|
// from diagnostics/html-sink.cc: HTML_STYLE .linenum
|
|
bgcolor = "#0088ce";
|
|
color = "white";
|
|
break;
|
|
case style::h2:
|
|
// from diagnostics/html-sink.cc: HTML_STYLE .events-hdr
|
|
bgcolor = "#393f44"; // pf-black-800
|
|
color = "white";
|
|
break;
|
|
}
|
|
|
|
xp.set_attr ("bgcolor", bgcolor);
|
|
xp.push_tag ("font", false);
|
|
xp.set_attr ("color", color);
|
|
if (heading == "")
|
|
heading = " ";
|
|
xp.add_text (std::move (heading));
|
|
xp.pop_tag ("font");
|
|
|
|
maybe_add_dst_port (id_of_dot_node, xp, state_node);
|
|
|
|
xp.pop_tag ("td");
|
|
xp.pop_tag ("tr");
|
|
}
|
|
|
|
/* Recursively add <TR> to XP for STATE_NODE and its descendents. */
|
|
void
|
|
on_node_in_table (const dot::id &id_of_dot_node,
|
|
xml::printer &xp,
|
|
const diagnostics::digraphs::node &state_node,
|
|
int max_depth,
|
|
int depth,
|
|
int num_columns)
|
|
{
|
|
bool recurse = true;
|
|
auto input_node_kind
|
|
= state_node.get_property (state_node_properties::kind_prop);
|
|
|
|
switch (input_node_kind)
|
|
{
|
|
case state_node_properties::kind_t::padding:
|
|
case state_node_properties::kind_t::other:
|
|
return;
|
|
|
|
case state_node_properties::kind_t::stack:
|
|
add_title_tr (id_of_dot_node, xp, num_columns, state_node, "Stack",
|
|
style::h1,
|
|
state_node_properties::dynalloc_state_t::unknown);
|
|
break;
|
|
case state_node_properties::kind_t::stack_frame:
|
|
if (auto logical_loc = state_node.get_logical_loc ())
|
|
if (const char *function
|
|
= m_logical_loc_mgr.get_short_name (logical_loc))
|
|
add_title_tr (id_of_dot_node, xp, num_columns, state_node,
|
|
std::string ("Frame: ") + function,
|
|
style::h2,
|
|
state_node_properties::dynalloc_state_t::unknown);
|
|
break;
|
|
case state_node_properties::kind_t::dynalloc_buffer:
|
|
{
|
|
enum state_node_properties::dynalloc_state_t dynalloc_st
|
|
= state_node.get_property
|
|
(state_node_properties::dynalloc_state_prop);
|
|
const char *extents
|
|
= state_node.get_property (state_node_properties::dynamic_extents);
|
|
const char *type = state_node.get_property (state_node_properties::type);
|
|
pretty_printer pp;
|
|
switch (dynalloc_st)
|
|
{
|
|
default:
|
|
gcc_unreachable ();
|
|
|
|
case state_node_properties::dynalloc_state_t::unknown:
|
|
case state_node_properties::dynalloc_state_t::nonnull:
|
|
if (type)
|
|
{
|
|
if (extents)
|
|
pp_printf (&pp, "%s (%s byte allocation)",
|
|
type, extents);
|
|
else
|
|
pp_printf (&pp, "%s", type);
|
|
}
|
|
else
|
|
{
|
|
if (extents)
|
|
pp_printf (&pp, "%s byte allocation",
|
|
extents);
|
|
}
|
|
break;
|
|
|
|
case state_node_properties::dynalloc_state_t::unchecked:
|
|
if (type)
|
|
{
|
|
if (extents)
|
|
pp_printf (&pp, "%s (unchecked %s byte allocation)",
|
|
type, extents);
|
|
}
|
|
else
|
|
{
|
|
if (extents)
|
|
pp_printf (&pp, "Unchecked %s byte allocation",
|
|
extents);
|
|
}
|
|
break;
|
|
|
|
case state_node_properties::dynalloc_state_t::freed:
|
|
// TODO: show deallocator
|
|
// TODO: show deallocation event
|
|
pp_printf (&pp, "Freed buffer");
|
|
break;
|
|
}
|
|
maybe_add_dst_port (id_of_dot_node, xp, state_node);
|
|
add_title_tr (id_of_dot_node, xp, num_columns, state_node,
|
|
pp_formatted_text (&pp),
|
|
style::h2,
|
|
dynalloc_st);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
xp.push_tag ("tr", true);
|
|
|
|
maybe_add_dst_port (id_of_dot_node, xp, state_node);
|
|
|
|
if (depth > 0)
|
|
{
|
|
/* Indent, by create a <td> spanning "depth" columns. */
|
|
xp.push_tag ("td", false);
|
|
xp.set_attr ("colspan", std::to_string (depth));
|
|
xp.add_text (" "); // graphviz doesn't like <td/>
|
|
xp.pop_tag ("td");
|
|
}
|
|
|
|
switch (input_node_kind)
|
|
{
|
|
default:
|
|
break;
|
|
case state_node_properties::kind_t::variable:
|
|
{
|
|
const char *name
|
|
= state_node.get_property (state_node_properties::name);
|
|
gcc_assert (name);
|
|
xp.push_tag ("td", false);
|
|
maybe_add_dst_port (id_of_dot_node, xp, state_node);
|
|
push_src_text (xp);
|
|
xp.add_text (name);
|
|
pop_src_text (xp);
|
|
xp.pop_tag ("td");
|
|
}
|
|
break;
|
|
case state_node_properties::kind_t::element:
|
|
{
|
|
const char *index
|
|
= state_node.get_property (state_node_properties::index);
|
|
gcc_assert (index);
|
|
xp.push_tag ("td", false);
|
|
maybe_add_dst_port (id_of_dot_node, xp, state_node);
|
|
push_src_text (xp);
|
|
xp.add_text ("[");
|
|
xp.add_text (index);
|
|
xp.add_text ("]");
|
|
pop_src_text (xp);
|
|
xp.pop_tag ("td");
|
|
}
|
|
break;
|
|
case state_node_properties::kind_t::field:
|
|
{
|
|
const char *name
|
|
= state_node.get_property (state_node_properties::name);
|
|
gcc_assert (name);
|
|
xp.push_tag ("td", false);
|
|
maybe_add_dst_port (id_of_dot_node, xp, state_node);
|
|
push_src_text (xp);
|
|
xp.add_text (".");
|
|
xp.add_text (name);
|
|
pop_src_text (xp);
|
|
xp.pop_tag ("td");
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (const char *type
|
|
= state_node.get_property (state_node_properties::type))
|
|
{
|
|
xp.push_tag ("td", false);
|
|
xp.set_attr ("align", "right");
|
|
push_src_text (xp);
|
|
xp.add_text (type);
|
|
pop_src_text (xp);
|
|
xp.pop_tag ("td");
|
|
}
|
|
|
|
if (const char *value
|
|
= state_node.get_property (state_node_properties::value_str))
|
|
{
|
|
xp.push_tag ("td", false);
|
|
xp.set_attr ("align", "left");
|
|
maybe_add_src_port (id_of_dot_node, xp, state_node);
|
|
push_src_text (xp);
|
|
xp.add_text (value);
|
|
pop_src_text (xp);
|
|
xp.pop_tag ("td");
|
|
recurse = false;
|
|
}
|
|
|
|
xp.pop_tag ("tr");
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (recurse)
|
|
for (size_t i = 0; i < state_node.get_num_children (); ++i)
|
|
on_node_in_table (id_of_dot_node, xp,
|
|
state_node.get_child (i),
|
|
max_depth, depth + 1, num_columns);
|
|
}
|
|
|
|
void
|
|
push_src_text (xml::printer &xp)
|
|
{
|
|
xp.push_tag ("font");
|
|
xp.set_attr ("color", "blue");
|
|
}
|
|
|
|
void
|
|
pop_src_text (xml::printer &xp)
|
|
{
|
|
xp.pop_tag ("font");
|
|
}
|
|
|
|
/* If STATE_NODE is in m_src_nodes, add a port to XP for possible
|
|
incoming edges to use. */
|
|
|
|
void
|
|
maybe_add_src_port (const dot::id &id_of_dot_node,
|
|
xml::printer &xp,
|
|
const diagnostics::digraphs::node &state_node)
|
|
{
|
|
auto iter = m_src_nodes.find (&state_node);
|
|
if (iter == m_src_nodes.end ())
|
|
return;
|
|
|
|
dot::id src_id = make_id (state_node, false);
|
|
dot::node_id node_id (id_of_dot_node,
|
|
dot::port (src_id,
|
|
dot::compass_pt::e));
|
|
m_src_node_to_port_id.insert ({&state_node, node_id});
|
|
xp.set_attr ("port", src_id.m_str);
|
|
}
|
|
|
|
/* If STATE_NODE is in m_dst_nodes, add a port to XP for possible
|
|
incoming edges to use. */
|
|
|
|
void
|
|
maybe_add_dst_port (const dot::id &id_of_dot_node,
|
|
xml::printer &xp,
|
|
const diagnostics::digraphs::node &state_node)
|
|
{
|
|
auto iter = m_dst_nodes.find (&state_node);
|
|
if (iter == m_dst_nodes.end ())
|
|
return;
|
|
|
|
dot::id dst_id = make_id (state_node, false);
|
|
dot::node_id node_id (id_of_dot_node,
|
|
dot::port (dst_id/*,
|
|
dot::compass_pt::w*/));
|
|
m_dst_node_to_port_id.insert ({&state_node, node_id});
|
|
xp.set_attr ("port", dst_id.m_str);
|
|
}
|
|
|
|
private:
|
|
const logical_locations::manager &m_logical_loc_mgr;
|
|
|
|
/* All nodes involved in edges (and thus will need a port). */
|
|
std::set<const digraphs::node *> m_src_nodes;
|
|
std::set<const digraphs::node *> m_dst_nodes;
|
|
|
|
std::map<const digraphs::node *, dot::node_id> m_src_node_to_port_id;
|
|
std::map<const digraphs::node *, dot::node_id> m_dst_node_to_port_id;
|
|
};
|
|
|
|
std::unique_ptr<dot::graph>
|
|
state_graphs::
|
|
make_dot_graph (const digraphs::digraph &state_graph,
|
|
const logical_locations::manager &logical_loc_mgr)
|
|
{
|
|
return std::make_unique<state_diagram> (state_graph, logical_loc_mgr);
|
|
}
|