/* Helper code for graphviz output. Copyright (C) 2019-2025 Free Software Foundation, Inc. Contributed by David Malcolm . 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 . */ #define INCLUDE_MAP #define INCLUDE_STRING #define INCLUDE_VECTOR #include "config.h" #include "system.h" #include "coretypes.h" #include "graphviz.h" #include "xml.h" #include "xml-printer.h" #include "pex.h" #include "selftest.h" dot::writer::writer (pretty_printer &pp) : m_pp (pp), m_indent (0) { } /* Print the current indent to the underlying pp. */ void dot::writer::write_indent () { for (int i = 0; i < m_indent * 4; ++i) pp_space (get_pp ()); } graphviz_out::graphviz_out (pretty_printer *pp) : writer (*pp) { gcc_assert (pp); } /* Formatted print of FMT. */ void graphviz_out::print (const char *fmt, ...) { va_list ap; va_start (ap, fmt); text_info text (fmt, &ap, errno); pp_format (get_pp (), &text); pp_output_formatted_text (get_pp ()); va_end (ap); } /* Formatted print of FMT. The text is indented by the current indent, and a newline is added. */ void graphviz_out::println (const char *fmt, ...) { va_list ap; write_indent (); va_start (ap, fmt); text_info text (fmt, &ap, errno); pp_format (get_pp (), &text); pp_output_formatted_text (get_pp ()); va_end (ap); pp_newline (get_pp ()); } /* Write the start of an HTML-like row via , writing to the stream so that followup text can be escaped. */ void graphviz_out::begin_tr () { pp_string (get_pp (), ""); pp_write_text_to_stream (get_pp ()); } /* Write the end of an HTML-like row via , writing to the stream so that followup text can be escaped. */ void graphviz_out::end_tr () { pp_string (get_pp (), ""); pp_write_text_to_stream (get_pp ()); } /* Write the start of an HTML-like , writing to the stream so that followup text can be escaped. */ void graphviz_out::begin_td () { pp_string (get_pp (), ""); pp_write_text_to_stream (get_pp ()); } /* Write the end of an HTML-like , writing to the stream so that followup text can be escaped. */ void graphviz_out::end_td () { pp_string (get_pp (), ""); pp_write_text_to_stream (get_pp ()); } /* Write the start of an HTML-like row via , writing to the stream so that followup text can be escaped. */ void graphviz_out::begin_trtd () { pp_string (get_pp (), ""); pp_write_text_to_stream (get_pp ()); } /* Write the end of an HTML-like row via , writing to the stream so that followup text can be escaped. */ void graphviz_out::end_tdtr () { pp_string (get_pp (), ""); pp_write_text_to_stream (get_pp ()); } namespace dot { // Definitions // struct ast_node void ast_node::dump () const { pretty_printer pp; pp.set_output_stream (stderr); writer w (pp); print (w); pp_newline (&pp); pp_flush (&pp); } // struct id id::id (std::string str) : m_str (std::move (str)), m_kind (is_identifier_p (m_str.c_str ()) ? kind::identifier : kind::quoted) { } id::id (const xml::node &n) : m_kind (kind::html) { pretty_printer pp; n.write_as_xml (&pp, 0, true); m_str = pp_formatted_text (&pp); } void id::print (writer &w) const { switch (m_kind) { default: gcc_unreachable (); case kind::identifier: w.write_string (m_str.c_str ()); break; case kind::quoted: w.write_character ('"'); for (auto ch : m_str) if (ch == '"') w.write_string ("\\\""); else w.write_character (ch); w.write_character ('"'); break; case kind::html: w.write_character ('<'); w.write_string (m_str.c_str ()); w.write_character ('>'); break; } } bool id::is_identifier_p (const char *str) { const char initial_ch = *str; if (initial_ch != '_' && !ISALPHA (initial_ch)) return false; for (const char *iter = str + 1; *iter; ++iter) { const char iter_ch = *iter; if (iter_ch != '_' && !ISALNUM (iter_ch)) return false; } return true; } // struct kv_pair void kv_pair::print (writer &w) const { m_key.print (w); w.write_character ('='); m_value.print (w); } // struct attr_list void attr_list::print (writer &w) const { if (m_kvs.empty ()) return; w.write_string (" ["); for (auto iter = m_kvs.begin (); iter != m_kvs.end (); ++iter) { if (iter != m_kvs.begin ()) w.write_string ("; "); iter->print (w); } w.write_string ("]"); } // struct stmt_list void stmt_list::print (writer &w) const { for (auto &stmt : m_stmts) { w.write_indent (); stmt->print (w); w.write_string (";"); w.write_newline (); } } void stmt_list::add_edge (node_id src_id, node_id dst_id) { m_stmts.push_back (std::make_unique (std::move (src_id), std::move (dst_id))); } void stmt_list::add_attr (id key, id value) { add_stmt (std::make_unique (kv_pair (std::move (key), std::move (value)))); } // struct graph void graph::print (writer &w) const { w.write_indent (); w.write_string ("digraph "); if (m_id) { m_id->print (w); w.write_character (' '); } w.write_string ("{"); w.write_newline (); w.indent (); m_stmt_list.print (w); w.outdent (); w.write_indent (); w.write_string ("}"); w.write_newline (); } // struct stmt_with_attr_list : public stmt void stmt_with_attr_list::set_label (dot::id value) { m_attrs.add (dot::id ("label"), std::move (value)); } // struct node_stmt : public stmt_with_attr_list void node_stmt::print (writer &w) const { m_id.print (w); m_attrs.print (w); } // struct attr_stmt : public stmt_with_attr_list void attr_stmt::print (writer &w) const { switch (m_kind) { default: gcc_unreachable (); case kind::graph: w.write_string ("graph"); break; case kind::node: w.write_string ("node"); break; case kind::edge: w.write_string ("edge"); break; } m_attrs.print (w); } // struct kv_stmt : public stmt void kv_stmt::print (writer &w) const { m_kv.print (w); } // struct node_id void node_id::print (writer &w) const { m_id.print (w); if (m_port) m_port->print (w); } // struct port void port::print (writer &w) const { if (m_id) { w.write_character (':'); m_id->print (w); } if (m_compass_pt) { w.write_character (':'); switch (*m_compass_pt) { default: gcc_unreachable (); case compass_pt::n: w.write_string ("n"); break; case compass_pt::ne: w.write_string ("ne"); break; case compass_pt::e: w.write_string ("e"); break; case compass_pt::se: w.write_string ("se"); break; case compass_pt::s: w.write_string ("s"); break; case compass_pt::sw: w.write_string ("sw"); break; case compass_pt::w: w.write_string ("w"); break; case compass_pt::nw: w.write_string ("nw"); break; case compass_pt::c: w.write_string ("c"); break; } } } // struct edge_stmt : public stmt_with_attr_list void edge_stmt::print (writer &w) const { for (auto iter = m_node_ids.begin (); iter != m_node_ids.end (); ++iter) { if (iter != m_node_ids.begin ()) w.write_string (" -> "); iter->print (w); } m_attrs.print (w); } // struct subgraph : public stmt void subgraph::print (writer &w) const { w.write_newline (); w.write_indent (); w.write_string ("subgraph "); m_id.print (w); w.write_string (" {"); w.write_newline (); w.indent (); m_stmt_list.print (w); w.outdent (); w.write_newline (); w.write_indent (); w.write_string ("}"); // newline and semicolon added by stmt_list } /* Convert G to graphviz source, attempt to invoke "dot -Tsvg" on it as a subprocess, and get the SVG source from stdout, or nullptr if there was a problem. */ static std::unique_ptr make_svg_document_buffer_from_graph (const graph &g) { /* Ideally there would be a way of doing this without invoking dot as a subprocess. */ std::vector args; args.push_back ("dot"); args.push_back ("-Tsvg"); pex p (0, "dot", nullptr); { auto pipe_stdin = p.input_file (true, nullptr); gcc_assert (pipe_stdin.m_file); pretty_printer pp; pp.set_output_stream (pipe_stdin.m_file); writer w (pp); g.print (w); pp_flush (&pp); } int err = 0; const char * errmsg = p.run (PEX_SEARCH, "dot", args, nullptr, nullptr, &err); auto pipe_stdout = p.read_output (); auto content = pipe_stdout.read_all (); if (errmsg) return nullptr; if (err) return nullptr; std::string result; result.reserve (content->size () + 1); for (auto &iter : *content) result.push_back (iter); return std::make_unique (std::move (result)); } /* Convert G to graphviz source, attempt to invoke "dot -Tsvg" on it as a subprocess, and get the SVG source from stdout, and extract the "svg" subtree as an xml::raw node. Note that this (a) invokes "dot" as a subprocess (b) assumes that we trust the output from "dot". Return nullptr if there was a problem. */ std::unique_ptr make_svg_from_graph (const graph &g) { auto svg_src = make_svg_document_buffer_from_graph (g); if (!svg_src) return nullptr; /* Skip past the XML header to the parts we care about. */ auto pos = svg_src->find ("