LCOV - code coverage report
Current view: top level - gcc - graphviz.cc (source / functions) Coverage Total Hit
Test: gcc.info Lines: 83.8 % 445 373
Test Date: 2026-02-28 14:20:25 Functions: 91.9 % 37 34
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /* Helper code for graphviz output.
       2              :    Copyright (C) 2019-2026 Free Software Foundation, Inc.
       3              :    Contributed by David Malcolm <dmalcolm@redhat.com>.
       4              : 
       5              : This file is part of GCC.
       6              : 
       7              : GCC is free software; you can redistribute it and/or modify it
       8              : under the terms of the GNU General Public License as published by
       9              : the Free Software Foundation; either version 3, or (at your option)
      10              : any later version.
      11              : 
      12              : GCC is distributed in the hope that it will be useful, but
      13              : WITHOUT ANY WARRANTY; without even the implied warranty of
      14              : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      15              : General Public License for more details.
      16              : 
      17              : You should have received a copy of the GNU General Public License
      18              : along with GCC; see the file COPYING3.  If not see
      19              : <http://www.gnu.org/licenses/>.  */
      20              : 
      21              : #define INCLUDE_MAP
      22              : #define INCLUDE_STRING
      23              : #define INCLUDE_VECTOR
      24              : #include "config.h"
      25              : #include "system.h"
      26              : #include "coretypes.h"
      27              : #include "graphviz.h"
      28              : #include "xml.h"
      29              : #include "xml-printer.h"
      30              : #include "pex.h"
      31              : #include "selftest.h"
      32              : 
      33          550 : dot::writer::writer (pretty_printer &pp)
      34          550 : : m_pp (pp),
      35          550 :   m_indent (0)
      36              : {
      37          550 : }
      38              : 
      39              : /* Print the current indent to the underlying pp.  */
      40              : 
      41              : void
      42         8045 : dot::writer::write_indent ()
      43              : {
      44        65717 :   for (int i = 0; i < m_indent * 4; ++i)
      45        57672 :     pp_space (get_pp ());
      46         8045 : }
      47              : 
      48           40 : graphviz_out::graphviz_out (pretty_printer *pp)
      49           40 : : writer (*pp)
      50              : {
      51           40 :   gcc_assert (pp);
      52           40 : }
      53              : 
      54              : /* Formatted print of FMT.  */
      55              : 
      56              : void
      57         1287 : graphviz_out::print (const char *fmt, ...)
      58              : {
      59         1287 :   va_list ap;
      60              : 
      61         1287 :   va_start (ap, fmt);
      62         1287 :   text_info text (fmt, &ap, errno);
      63         1287 :   pp_format (get_pp (), &text);
      64         1287 :   pp_output_formatted_text (get_pp ());
      65         1287 :   va_end (ap);
      66         1287 : }
      67              : 
      68              : /* Formatted print of FMT.  The text is indented by the current
      69              :    indent, and a newline is added.  */
      70              : 
      71              : void
      72         3524 : graphviz_out::println (const char *fmt, ...)
      73              : {
      74         3524 :   va_list ap;
      75              : 
      76         3524 :   write_indent ();
      77              : 
      78         3524 :   va_start (ap, fmt);
      79         3524 :   text_info text (fmt, &ap, errno);
      80         3524 :   pp_format (get_pp (), &text);
      81         3524 :   pp_output_formatted_text (get_pp ());
      82         3524 :   va_end (ap);
      83              : 
      84         3524 :   pp_newline (get_pp ());
      85         3524 : }
      86              : 
      87              : /* Write the start of an HTML-like row via <TR>, writing to the stream
      88              :    so that followup text can be escaped.  */
      89              : 
      90              : void
      91          195 : graphviz_out::begin_tr ()
      92              : {
      93          195 :   pp_string (get_pp (), "<TR>");
      94          195 :   pp_write_text_to_stream (get_pp ());
      95          195 : }
      96              : 
      97              : /* Write the end of an HTML-like row via </TR>, writing to the stream
      98              :    so that followup text can be escaped.  */
      99              : 
     100              : void
     101          191 : graphviz_out::end_tr ()
     102              : {
     103          191 :   pp_string (get_pp (), "</TR>");
     104          191 :   pp_write_text_to_stream (get_pp ());
     105          191 : }
     106              : 
     107              : /* Write the start of an HTML-like <TD>, writing to the stream
     108              :    so that followup text can be escaped.  */
     109              : 
     110              : void
     111            0 : graphviz_out::begin_td ()
     112              : {
     113            0 :   pp_string (get_pp (), "<TD ALIGN=\"LEFT\">");
     114            0 :   pp_write_text_to_stream (get_pp ());
     115            0 : }
     116              : 
     117              : /* Write the end of an HTML-like </TD>, writing to the stream
     118              :    so that followup text can be escaped.  */
     119              : 
     120              : void
     121            0 : graphviz_out::end_td ()
     122              : {
     123            0 :   pp_string (get_pp (), "</TD>");
     124            0 :   pp_write_text_to_stream (get_pp ());
     125            0 : }
     126              : 
     127              : /* Write the start of an HTML-like row via <TR><TD>, writing to the stream
     128              :    so that followup text can be escaped.  */
     129              : 
     130              : void
     131          823 : graphviz_out::begin_trtd ()
     132              : {
     133          823 :   pp_string (get_pp (), "<TR><TD ALIGN=\"LEFT\">");
     134          823 :   pp_write_text_to_stream (get_pp ());
     135          823 : }
     136              : 
     137              : /* Write the end of an HTML-like row via </TD></TR>, writing to the stream
     138              :    so that followup text can be escaped.  */
     139              : 
     140              : void
     141          827 : graphviz_out::end_tdtr ()
     142              : {
     143          827 :   pp_string (get_pp (), "</TD></TR>");
     144          827 :   pp_write_text_to_stream (get_pp ());
     145          827 : }
     146              : 
     147              : namespace dot {
     148              : 
     149              : // Definitions
     150              : 
     151              : // struct ast_node
     152              : 
     153              : void
     154            0 : ast_node::dump () const
     155              : {
     156            0 :   pretty_printer pp;
     157            0 :   pp.set_output_stream (stderr);
     158            0 :   writer w (pp);
     159            0 :   print (w);
     160            0 :   pp_newline (&pp);
     161            0 :   pp_flush (&pp);
     162            0 : }
     163              : 
     164              : // struct id
     165              : 
     166         9149 : id::id (std::string str)
     167         9149 : : m_str (std::move (str)),
     168         9149 :   m_kind (is_identifier_p (m_str.c_str ())
     169         9149 :           ? kind::identifier
     170         9149 :           : kind::quoted)
     171              : {
     172         9149 : }
     173              : 
     174          193 : id::id (const xml::node &n)
     175          193 : : m_kind (kind::html)
     176              : {
     177          193 :   pretty_printer pp;
     178          193 :   n.write_as_xml (&pp, 0, true);
     179          193 :   m_str = pp_formatted_text (&pp);
     180          193 : }
     181              : 
     182              : void
     183         9341 : id::print (writer &w) const
     184              : {
     185         9341 :   switch (m_kind)
     186              :     {
     187            0 :     default:
     188            0 :       gcc_unreachable ();
     189              : 
     190         6718 :     case kind::identifier:
     191         6718 :       w.write_string (m_str.c_str ());
     192         6718 :       break;
     193              : 
     194         2430 :     case kind::quoted:
     195         2430 :       w.write_character ('"');
     196        28078 :       for (auto ch : m_str)
     197        25648 :         if (ch == '"')
     198            0 :           w.write_string ("\\\"");
     199              :         else
     200        25648 :           w.write_character (ch);
     201         2430 :       w.write_character ('"');
     202         2430 :       break;
     203              : 
     204          193 :     case kind::html:
     205          193 :       w.write_character ('<');
     206          193 :       w.write_string (m_str.c_str ());
     207          193 :       w.write_character ('>');
     208          193 :       break;
     209              : 
     210              :     }
     211         9341 : }
     212              : 
     213              : bool
     214         9169 : id::is_identifier_p (const char *str)
     215              : {
     216         9169 :   const char initial_ch = *str;
     217         9169 :   if (initial_ch != '_' && !ISALPHA (initial_ch))
     218              :     return false;
     219        51260 :   for (const char *iter = str + 1; *iter; ++iter)
     220              :     {
     221        44529 :       const char iter_ch = *iter;
     222        44529 :       if (iter_ch != '_' && !ISALNUM (iter_ch))
     223              :         return false;
     224              :     }
     225              :   return true;
     226              : }
     227              : 
     228              : // struct kv_pair
     229              : 
     230              : void
     231         3560 : kv_pair::print (writer &w) const
     232              : {
     233         3560 :   m_key.print (w);
     234         3560 :   w.write_character ('=');
     235         3560 :   m_value.print (w);
     236         3560 : }
     237              : 
     238              : // struct attr_list
     239              : 
     240              : void
     241         1871 : attr_list::print (writer &w) const
     242              : {
     243         1871 :   if (m_kvs.empty ())
     244              :     return;
     245         1853 :   w.write_string (" [");
     246         5356 :   for (auto iter = m_kvs.begin (); iter != m_kvs.end (); ++iter)
     247              :     {
     248         3503 :       if (iter != m_kvs.begin ())
     249         1650 :         w.write_string ("; ");
     250         3503 :       iter->print (w);
     251              :     }
     252         1853 :   w.write_string ("]");
     253              : }
     254              : 
     255              : // struct stmt_list
     256              : 
     257              : void
     258          122 : stmt_list::print (writer &w) const
     259              : {
     260         1662 :   for (auto &stmt : m_stmts)
     261              :     {
     262         1540 :       w.write_indent ();
     263         1540 :       stmt->print (w);
     264         1540 :       w.write_string (";");
     265         1540 :       w.write_newline ();
     266              :     }
     267          122 : }
     268              : 
     269              : void
     270            8 : stmt_list::add_edge (node_id src_id, node_id dst_id)
     271              : {
     272            8 :   m_stmts.push_back
     273           16 :     (std::make_unique<dot::edge_stmt>
     274            8 :      (std::move (src_id), std::move (dst_id)));
     275            8 : }
     276              : 
     277              : void
     278           52 : stmt_list::add_attr (id key, id value)
     279              : {
     280           52 :   add_stmt
     281           52 :     (std::make_unique <kv_stmt> (kv_pair (std::move (key),
     282          104 :                                           std::move (value))));
     283           52 : }
     284              : 
     285              : // struct graph
     286              : 
     287              : void
     288           65 : graph::print (writer &w) const
     289              : {
     290           65 :   w.write_indent ();
     291           65 :   w.write_string ("digraph ");
     292           65 :   if (m_id)
     293              :     {
     294            4 :       m_id->print (w);
     295            4 :       w.write_character (' ');
     296              :     }
     297           65 :   w.write_string ("{");
     298           65 :   w.write_newline ();
     299              : 
     300           65 :   w.indent ();
     301           65 :   m_stmt_list.print (w);
     302           65 :   w.outdent ();
     303              : 
     304           65 :   w.write_indent ();
     305           65 :   w.write_string ("}");
     306           65 :   w.write_newline ();
     307           65 : }
     308              : 
     309              : // struct stmt_with_attr_list : public stmt
     310              : 
     311              : void
     312          947 : stmt_with_attr_list::set_label (dot::id value)
     313              : {
     314          947 :   m_attrs.add (dot::id ("label"), std::move (value));
     315          947 : }
     316              : 
     317              : // struct node_stmt : public stmt_with_attr_list
     318              : 
     319              : void
     320          698 : node_stmt::print (writer &w) const
     321              : {
     322          698 :   m_id.print (w);
     323          698 :   m_attrs.print (w);
     324          698 : }
     325              : 
     326              : // struct attr_stmt : public stmt_with_attr_list
     327              : 
     328              : void
     329            5 : attr_stmt::print (writer &w) const
     330              : {
     331            5 :   switch (m_kind)
     332              :     {
     333            0 :     default:
     334            0 :       gcc_unreachable ();
     335            0 :     case kind::graph:
     336            0 :       w.write_string ("graph");
     337            0 :       break;
     338            5 :     case kind::node:
     339            5 :       w.write_string ("node");
     340            5 :       break;
     341            0 :     case kind::edge:
     342            0 :       w.write_string ("edge");
     343            0 :       break;
     344              :     }
     345            5 :   m_attrs.print (w);
     346            5 : }
     347              : 
     348              : // struct kv_stmt : public stmt
     349              : 
     350              : void
     351           57 : kv_stmt::print (writer &w) const
     352              : {
     353           57 :   m_kv.print (w);
     354           57 : }
     355              : 
     356              : bool
     357           90 : get_compass_pt_from_string (const char *str, enum compass_pt &out)
     358              : {
     359           90 :   if (strcmp (str, "n") == 0)
     360              :     {
     361           45 :       out = compass_pt::n;
     362           45 :       return true;
     363              :     }
     364           45 :   if (strcmp (str, "ne") == 0)
     365              :     {
     366            0 :       out = compass_pt::ne;
     367            0 :       return true;
     368              :     }
     369           45 :   if (strcmp (str, "e") == 0)
     370              :     {
     371            0 :       out = compass_pt::e;
     372            0 :       return true;
     373              :     }
     374           45 :   if (strcmp (str, "se") == 0)
     375              :     {
     376            0 :       out = compass_pt::se;
     377            0 :       return true;
     378              :     }
     379           45 :   if (strcmp (str, "s") == 0)
     380              :     {
     381           45 :       out = compass_pt::s;
     382           45 :       return true;
     383              :     }
     384            0 :   if (strcmp (str, "sw") == 0)
     385              :     {
     386            0 :       out = compass_pt::sw;
     387            0 :       return true;
     388              :     }
     389            0 :   if (strcmp (str, "w") == 0)
     390              :     {
     391            0 :       out = compass_pt::w;
     392            0 :       return true;
     393              :     }
     394            0 :   if (strcmp (str, "nw") == 0)
     395              :     {
     396            0 :       out = compass_pt::nw;
     397            0 :       return true;
     398              :     }
     399            0 :   if (strcmp (str, "c") == 0)
     400              :     {
     401            0 :       out = compass_pt::c;
     402            0 :       return true;
     403              :     }
     404              : 
     405              :   return false;
     406              : }
     407              : 
     408              : // struct node_id
     409              : 
     410              : void
     411         1446 : node_id::print (writer &w) const
     412              : {
     413         1446 :   m_id.print (w);
     414         1446 :   if (m_port)
     415          106 :     m_port->print (w);
     416         1446 : }
     417              : 
     418              : // struct port
     419              : 
     420              : void
     421          106 : port::print (writer &w) const
     422              : {
     423          106 :   if (m_id)
     424              :     {
     425           16 :       w.write_character (':');
     426           16 :       m_id->print (w);
     427              :     }
     428          106 :   if (m_compass_pt)
     429              :     {
     430           90 :       w.write_character (':');
     431           90 :       switch (*m_compass_pt)
     432              :         {
     433            0 :         default:
     434            0 :           gcc_unreachable ();
     435           45 :         case compass_pt::n:
     436           45 :           w.write_string ("n");
     437           45 :           break;
     438            0 :         case compass_pt::ne:
     439            0 :           w.write_string ("ne");
     440            0 :           break;
     441            0 :         case compass_pt::e:
     442            0 :           w.write_string ("e");
     443            0 :           break;
     444            0 :         case compass_pt::se:
     445            0 :           w.write_string ("se");
     446            0 :           break;
     447           45 :         case compass_pt::s:
     448           45 :           w.write_string ("s");
     449           45 :           break;
     450            0 :         case compass_pt::sw:
     451            0 :           w.write_string ("sw");
     452            0 :           break;
     453            0 :         case compass_pt::w:
     454            0 :           w.write_string ("w");
     455            0 :           break;
     456            0 :         case compass_pt::nw:
     457            0 :           w.write_string ("nw");
     458            0 :           break;
     459            0 :         case compass_pt::c:
     460            0 :           w.write_string ("c");
     461            0 :           break;
     462              :         }
     463              :     }
     464          106 : }
     465              : 
     466              : // struct edge_stmt : public stmt_with_attr_list
     467              : 
     468              : void
     469          723 : edge_stmt::print (writer &w) const
     470              : {
     471         2169 :   for (auto iter = m_node_ids.begin (); iter != m_node_ids.end (); ++iter)
     472              :     {
     473         1446 :       if (iter != m_node_ids.begin ())
     474          723 :         w.write_string (" -> ");
     475         1446 :       iter->print (w);
     476              :     }
     477          723 :   m_attrs.print (w);
     478          723 : }
     479              : 
     480              : // struct subgraph : public stmt
     481              : 
     482              : void
     483           57 : subgraph::print (writer &w) const
     484              : {
     485           57 :   w.write_newline ();
     486           57 :   w.write_indent ();
     487           57 :   w.write_string ("subgraph ");
     488           57 :   m_id.print (w);
     489           57 :   w.write_string (" {");
     490           57 :   w.write_newline ();
     491              : 
     492           57 :   w.indent ();
     493           57 :   m_stmt_list.print (w);
     494           57 :   w.outdent ();
     495           57 :   w.write_newline ();
     496              : 
     497           57 :   w.write_indent ();
     498           57 :   w.write_string ("}"); // newline and semicolon added by stmt_list
     499           57 : }
     500              : 
     501              : /* Convert G to graphviz source, attempt to invoke "dot -Tsvg" on it
     502              :    as a subprocess, and get the SVG source from stdout, or nullptr
     503              :    if there was a problem.  */
     504              : 
     505              : static std::unique_ptr<std::string>
     506           49 : make_svg_document_buffer_from_graph (const graph &g)
     507              : {
     508              :   /* Ideally there would be a way of doing this without
     509              :      invoking dot as a subprocess.  */
     510              : 
     511           49 :   std::vector<std::string> args;
     512           49 :   args.push_back ("dot");
     513           49 :   args.push_back ("-Tsvg");
     514              : 
     515           49 :   pex p (0, "dot", nullptr);
     516              : 
     517           49 :   {
     518           49 :     auto pipe_stdin = p.input_file (true, nullptr);
     519           49 :     gcc_assert (pipe_stdin.m_file);
     520           49 :     pretty_printer pp;
     521           49 :     pp.set_output_stream (pipe_stdin.m_file);
     522           49 :     writer w (pp);
     523           49 :     g.print (w);
     524           49 :     pp_flush (&pp);
     525           49 :   }
     526              : 
     527           49 :   int err = 0;
     528           49 :   const char * errmsg
     529           49 :     = p.run (PEX_SEARCH,
     530              :              "dot", args, nullptr, nullptr, &err);
     531           49 :   auto pipe_stdout = p.read_output ();
     532           49 :   auto content = pipe_stdout.read_all ();
     533              : 
     534           49 :   if (errmsg)
     535            0 :     return nullptr;
     536           49 :   if (err)
     537            0 :     return nullptr;
     538              : 
     539           49 :   std::string result;
     540           49 :   result.reserve (content->size () + 1);
     541       787880 :   for (auto &iter : *content)
     542       787831 :     result.push_back (iter);
     543           49 :   return std::make_unique<std::string> (std::move (result));
     544           49 : }
     545              : 
     546              : /* Convert G to graphviz source, attempt to invoke "dot -Tsvg" on it
     547              :    as a subprocess, and get the SVG source from stdout, and extract
     548              :    the "svg" subtree as an xml::raw node.
     549              : 
     550              :    Note that this
     551              :    (a) invokes "dot" as a subprocess
     552              :    (b) assumes that we trust the output from "dot".
     553              : 
     554              :    Return nullptr if there was a problem.  */
     555              : 
     556              : std::unique_ptr<xml::node>
     557           49 : make_svg_from_graph (const graph &g)
     558              : {
     559           49 :   auto svg_src = make_svg_document_buffer_from_graph (g);
     560           49 :   if (!svg_src)
     561            0 :     return nullptr;
     562              : 
     563              :   /* Skip past the XML header to the parts we care about.  */
     564           49 :   auto pos = svg_src->find ("<!-- Generated by graphviz");
     565           49 :   if (pos == svg_src->npos)
     566            0 :     return nullptr;
     567              : 
     568           49 :   auto substring = std::string (*svg_src, pos);
     569           49 :   return std::make_unique<xml::raw> (std::move (substring));
     570           49 : }
     571              : 
     572              : } // namespace dot
     573              : 
     574              : #if CHECKING_P
     575              : 
     576              : namespace selftest {
     577              : 
     578              : static void
     579            4 : test_ids ()
     580              : {
     581            4 :   ASSERT_TRUE (dot::id::is_identifier_p ("foo"));
     582            4 :   ASSERT_FALSE (dot::id::is_identifier_p ("hello world"));
     583            4 :   ASSERT_TRUE (dot::id::is_identifier_p ("foo42"));
     584            4 :   ASSERT_FALSE (dot::id::is_identifier_p ("42"));
     585            4 :   ASSERT_TRUE (dot::id::is_identifier_p ("_"));
     586            4 : }
     587              : 
     588              : static void
     589            4 : test_trivial_graph ()
     590              : {
     591            4 :   dot::graph g;
     592              :   // node "a"
     593            4 :   {
     594            4 :     g.add_stmt (std::make_unique<dot::node_stmt> (dot::id ("a")));
     595              :   }
     596              :   // node "b"
     597            4 :   {
     598            4 :     auto n = std::make_unique<dot::node_stmt> (dot::id ("b"));
     599            4 :     n->m_attrs.add (dot::id ("label"), dot::id ("This is node b"));
     600            4 :     n->m_attrs.add (dot::id ("color"), dot::id ("green"));
     601            4 :     g.add_stmt (std::move (n));
     602            4 :   }
     603              :   // an edge between them
     604            4 :   {
     605           12 :     auto e = std::make_unique<dot::edge_stmt> (dot::id ("a"),
     606           12 :                                                dot::id ("b"));
     607            4 :     e->m_attrs.add (dot::id ("label"), dot::id ("I'm an edge"));
     608            4 :     g.add_stmt (std::move (e));
     609            4 :   }
     610            4 :   pretty_printer pp;
     611            4 :   dot::writer w (pp);
     612            4 :   g.print (w);
     613            4 :   ASSERT_STREQ
     614              :     (pp_formatted_text (&pp),
     615              :      ("digraph {\n"
     616              :       "    a;\n"
     617              :       "    b [label=\"This is node b\"; color=green];\n"
     618              :       "    a -> b [label=\"I'm an edge\"];\n"
     619              :       "}\n"));
     620            4 : }
     621              : 
     622              : /* Recreating the HTML record example from
     623              :    https://graphviz.org/doc/info/shapes.html#html  */
     624              : 
     625              : static void
     626            4 : test_layout_example ()
     627              : {
     628            4 :   dot::graph g (dot::id ("structs"));
     629              : 
     630              :   // "node [shape=plaintext]\n"
     631            4 :   {
     632            4 :     auto attr_stmt
     633            4 :       = std::make_unique<dot::attr_stmt> (dot::attr_stmt::kind::node);
     634            4 :     attr_stmt->m_attrs.add (dot::id ("shape"), dot::id ("plaintext"));
     635            4 :     g.add_stmt (std::move (attr_stmt));
     636            4 :   }
     637              : 
     638              :   // struct1
     639            4 :   {
     640            4 :     auto n = std::make_unique<dot::node_stmt> (dot::id ("struct1"));
     641              : 
     642            4 :     xml::element table ("TABLE", false);
     643            4 :     xml::printer xp (table);
     644            4 :     xp.set_attr ("BORDER", "0");
     645            4 :     xp.set_attr ("CELLBORDER", "1");
     646            4 :     xp.set_attr ("CELLSPACING", "0");
     647              : 
     648            4 :     xp.push_tag ("TR", true);
     649              : 
     650            4 :     xp.push_tag ("TD", false);
     651            4 :     xp.add_text ("left");
     652            4 :     xp.pop_tag ("TD");
     653              : 
     654            4 :     xp.push_tag ("TD", false);
     655            4 :     xp.set_attr ("PORT", "f1");
     656            4 :     xp.add_text ("mid dle");
     657            4 :     xp.pop_tag ("TD");
     658              : 
     659            4 :     xp.push_tag ("TD", false);
     660            4 :     xp.set_attr ("PORT", "f2");
     661            4 :     xp.add_text ("right");
     662            4 :     xp.pop_tag ("TD");
     663              : 
     664            4 :     n->set_label (table);
     665            4 :     g.add_stmt (std::move (n));
     666            4 :   }
     667              : 
     668              :   // struct2
     669            4 :   {
     670            4 :     auto n = std::make_unique<dot::node_stmt> (dot::id ("struct2"));
     671            4 :     xml::element table ("TABLE", false);
     672            4 :     xml::printer xp (table);
     673            4 :     xp.set_attr ("BORDER", "0");
     674            4 :     xp.set_attr ("CELLBORDER", "1");
     675            4 :     xp.set_attr ("CELLSPACING", "0");
     676              : 
     677            4 :     xp.push_tag ("TR", true);
     678              : 
     679            4 :     xp.push_tag ("TD", false);
     680            4 :     xp.set_attr ("PORT", "f0");
     681            4 :     xp.add_text ("one");
     682            4 :     xp.pop_tag ("TD");
     683              : 
     684            4 :     xp.push_tag ("TD", false);
     685            4 :     xp.add_text ("two");
     686            4 :     xp.pop_tag ("TD");
     687              : 
     688            4 :     n->set_label (table);
     689            4 :     g.add_stmt (std::move (n));
     690            4 :   }
     691              : 
     692              :   // struct3
     693            4 :   {
     694            4 :     auto n = std::make_unique<dot::node_stmt> (dot::id ("struct3"));
     695            4 :     xml::element table ("TABLE", false);
     696            4 :     xml::printer xp (table);
     697            4 :     xp.set_attr ("BORDER", "0");
     698            4 :     xp.set_attr ("CELLBORDER", "1");
     699            4 :     xp.set_attr ("CELLSPACING", "0");
     700            4 :     xp.set_attr ("CELLPADDING", "4");
     701              : 
     702            4 :     xp.push_tag ("TR", false);
     703              : 
     704            4 :     xp.push_tag ("TD", true);
     705            4 :     xp.set_attr ("ROWSPAN", "3");
     706            4 :     xp.add_text ("hello");
     707            4 :     xp.append (std::make_unique<xml::element> ("BR", false));
     708            4 :     xp.add_text ("world");
     709            4 :     xp.pop_tag ("TD");
     710              : 
     711            4 :     xp.push_tag ("TD", true);
     712            4 :     xp.set_attr ("COLSPAN", "3");
     713            4 :     xp.add_text ("b");
     714            4 :     xp.pop_tag ("TD");
     715              : 
     716            4 :     xp.push_tag ("TD", true);
     717            4 :     xp.set_attr ("ROWSPAN", "3");
     718            4 :     xp.add_text ("g");
     719            4 :     xp.pop_tag ("TD");
     720              : 
     721            4 :     xp.push_tag ("TD", true);
     722            4 :     xp.set_attr ("ROWSPAN", "3");
     723            4 :     xp.add_text ("h");
     724            4 :     xp.pop_tag ("TD");
     725              : 
     726            4 :     xp.pop_tag ("TR");
     727              : 
     728            4 :     xp.push_tag ("TR", false);
     729              : 
     730            4 :     xp.push_tag ("TD", true);
     731            4 :     xp.add_text ("c");
     732            4 :     xp.pop_tag ("TD");
     733              : 
     734            4 :     xp.push_tag ("TD", true);
     735            4 :     xp.set_attr ("PORT", "here");
     736            4 :     xp.add_text ("d");
     737            4 :     xp.pop_tag ("TD");
     738              : 
     739            4 :     xp.push_tag ("TD", true);
     740            4 :     xp.add_text ("e");
     741            4 :     xp.pop_tag ("TD");
     742              : 
     743            4 :     xp.pop_tag ("TR");
     744              : 
     745            4 :     xp.push_tag ("TR", false);
     746              : 
     747            4 :     xp.push_tag ("TD", true);
     748            4 :     xp.set_attr ("COLSPAN", "3");
     749            4 :     xp.add_text ("f");
     750            4 :     xp.pop_tag ("TD");
     751              : 
     752            4 :     n->set_label (table);
     753            4 :     g.add_stmt (std::move (n));
     754            4 :   }
     755              : 
     756            4 :   g.m_stmt_list.add_edge
     757            8 :     (dot::node_id (dot::id ("struct1"),
     758           12 :                    dot::port (dot::id ("f1"))),
     759            8 :      dot::node_id (dot::id ("struct2"),
     760           12 :                    dot::port (dot::id ("f0"))));
     761            4 :   g.m_stmt_list.add_edge
     762            8 :     (dot::node_id (dot::id ("struct1"),
     763           12 :                    dot::port (dot::id ("f2"))),
     764            8 :      dot::node_id (dot::id ("struct3"),
     765           12 :                    dot::port (dot::id ("here"))));
     766              : 
     767            4 :   pretty_printer pp;
     768            4 :   dot::writer w (pp);
     769            4 :   g.print (w);
     770              : 
     771              :   /* There are some whitespace differences with the example in the
     772              :      GraphViz docs.  */
     773            4 :   ASSERT_STREQ
     774              :     (pp_formatted_text (&pp),
     775              :      ("digraph structs {\n"
     776              :       "    node [shape=plaintext];\n" // added semicolon
     777              :       "    struct1 [label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n"
     778              :       "  <TR><TD>left</TD><TD PORT=\"f1\">mid dle</TD><TD PORT=\"f2\">right</TD></TR>\n"
     779              :       "</TABLE>\n"
     780              :       ">];\n"
     781              :       "    struct2 [label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n"
     782              :       "  <TR><TD PORT=\"f0\">one</TD><TD>two</TD></TR>\n"
     783              :       "</TABLE>\n"
     784              :       ">];\n"
     785              :       "    struct3 [label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n"
     786              :       "  <TR>\n"
     787              :       "    <TD ROWSPAN=\"3\">hello<BR/>world</TD>\n"
     788              :       "    <TD COLSPAN=\"3\">b</TD>\n"
     789              :       "    <TD ROWSPAN=\"3\">g</TD>\n"
     790              :       "    <TD ROWSPAN=\"3\">h</TD>\n"
     791              :       "  </TR>\n"
     792              :       "  <TR>\n"
     793              :       "    <TD>c</TD>\n"
     794              :       "    <TD PORT=\"here\">d</TD>\n"
     795              :       "    <TD>e</TD>\n"
     796              :       "  </TR>\n"
     797              :       "  <TR>\n"
     798              :       "    <TD COLSPAN=\"3\">f</TD>\n"
     799              :       "  </TR>\n"
     800              :       "</TABLE>\n"
     801              :       ">];\n"
     802              :       "    struct1:f1 -> struct2:f0;\n"
     803              :       "    struct1:f2 -> struct3:here;\n"
     804              :       "}\n"));
     805            4 : }
     806              : 
     807              : /* Run all of the selftests within this file.  */
     808              : 
     809              : void
     810            4 : graphviz_cc_tests ()
     811              : {
     812            4 :   test_ids ();
     813            4 :   test_trivial_graph ();
     814            4 :   test_layout_example ();
     815            4 : }
     816              : 
     817              : } // namespace selftest
     818              : 
     819              : #endif /* CHECKING_P */
        

Generated by: LCOV version 2.4-beta

LCOV profile is generated on x86_64 machine using following configure options: configure --disable-bootstrap --enable-coverage=opt --enable-languages=c,c++,fortran,go,jit,lto,rust,m2 --enable-host-shared. GCC test suite is run with the built compiler.