LCOV - code coverage report
Current view: top level - gcc/diagnostics - html-sink.cc (source / functions) Coverage Total Hit
Test: gcc.info Lines: 79.3 % 827 656
Test Date: 2026-02-28 14:20:25 Functions: 74.3 % 70 52
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /* HTML output for diagnostics.
       2              :    Copyright (C) 2024-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 under
       8              : the terms of the GNU General Public License as published by the Free
       9              : Software Foundation; either version 3, or (at your option) any later
      10              : version.
      11              : 
      12              : GCC is distributed in the hope that it will be useful, but WITHOUT ANY
      13              : WARRANTY; without even the implied warranty of MERCHANTABILITY or
      14              : FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      15              : 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              : #include "config.h"
      22              : #define INCLUDE_MAP
      23              : #define INCLUDE_STRING
      24              : #define INCLUDE_VECTOR
      25              : #include "system.h"
      26              : #include "coretypes.h"
      27              : #include "diagnostics/metadata.h"
      28              : #include "diagnostics/sink.h"
      29              : #include "diagnostics/html-sink.h"
      30              : #include "diagnostics/text-sink.h"
      31              : #include "diagnostics/sarif-sink.h"
      32              : #include "diagnostics/output-file.h"
      33              : #include "diagnostics/buffering.h"
      34              : #include "diagnostics/paths.h"
      35              : #include "diagnostics/dumping.h"
      36              : #include "diagnostics/logging.h"
      37              : #include "diagnostics/client-data-hooks.h"
      38              : #include "selftest.h"
      39              : #include "diagnostics/selftest-context.h"
      40              : #include "pretty-print-format-impl.h"
      41              : #include "pretty-print-urlifier.h"
      42              : #include "diagnostics/changes.h"
      43              : #include "intl.h"
      44              : #include "xml.h"
      45              : #include "xml-printer.h"
      46              : #include "diagnostics/digraphs.h"
      47              : #include "diagnostics/state-graphs.h"
      48              : #include "graphviz.h"
      49              : #include "json.h"
      50              : #include "selftest-xml.h"
      51              : 
      52              : namespace diagnostics {
      53              : 
      54              : // struct html_generation_options
      55              : 
      56           62 : html_generation_options::html_generation_options ()
      57           62 : : m_css (true),
      58           62 :   m_javascript (true),
      59           62 :   m_show_state_diagrams (false),
      60           62 :   m_show_graph_sarif (false),
      61           62 :   m_show_graph_dot_src (false)
      62              : {
      63           62 : }
      64              : 
      65              : void
      66            0 : html_generation_options::dump (FILE *outfile, int indent) const
      67              : {
      68            0 :   DIAGNOSTICS_DUMPING_EMIT_BOOL_FIELD (m_css);
      69            0 :   DIAGNOSTICS_DUMPING_EMIT_BOOL_FIELD (m_javascript);
      70            0 :   DIAGNOSTICS_DUMPING_EMIT_BOOL_FIELD (m_show_state_diagrams);
      71            0 :   DIAGNOSTICS_DUMPING_EMIT_BOOL_FIELD (m_show_graph_sarif);
      72            0 :   DIAGNOSTICS_DUMPING_EMIT_BOOL_FIELD (m_show_graph_dot_src);
      73            0 : }
      74              : 
      75              : class html_builder;
      76              : 
      77              : /* Concrete buffering implementation subclass for HTML output.  */
      78              : 
      79              : class html_sink_buffer : public per_sink_buffer
      80              : {
      81              : public:
      82              :   friend class html_builder;
      83              :   friend class html_sink;
      84              : 
      85            0 :   html_sink_buffer (html_builder &builder)
      86            0 :   : m_builder (builder)
      87              :   {}
      88              : 
      89              :   void dump (FILE *out, int indent) const final override;
      90              :   bool empty_p () const final override;
      91              :   void move_to (per_sink_buffer &dest) final override;
      92              :   void clear () final override;
      93              :   void flush () final override;
      94              : 
      95              :   void add_result (std::unique_ptr<xml::element> result)
      96              :   {
      97              :     m_results.push_back (std::move (result));
      98              :   }
      99              : 
     100              : private:
     101              :   html_builder &m_builder;
     102              :   std::vector<std::unique_ptr<xml::element>> m_results;
     103              : };
     104              : 
     105              : /* A class for managing HTML output of diagnostics.
     106              : 
     107              :    Implemented:
     108              :    - message text
     109              : 
     110              :    Known limitations/missing functionality:
     111              :    - title for page
     112              :    - file/line/column
     113              :    - error vs warning
     114              :    - CWEs
     115              :    - rules
     116              :    - fix-it hints
     117              :    - paths
     118              : */
     119              : 
     120              : class html_builder
     121              : {
     122              : public:
     123              :   friend class html_sink_buffer;
     124              : 
     125              :   html_builder (context &dc,
     126              :                 pretty_printer &pp,
     127              :                 const line_maps *line_maps,
     128              :                 const html_generation_options &html_gen_opts);
     129              : 
     130              :   void dump (FILE *out, int indent) const;
     131              : 
     132              :   void
     133              :   set_main_input_filename (const char *name);
     134              : 
     135              :   void on_report_diagnostic (const diagnostic_info &diagnostic,
     136              :                              enum kind orig_diag_kind,
     137              :                              html_sink_buffer *buffer);
     138              :   void emit_diagram (const diagram &d);
     139              :   void emit_global_graph (const lazily_created<digraphs::digraph> &);
     140              :   void add_graph_for_logical_loc (const lazily_created<digraphs::digraph> &,
     141              :                                   logical_locations::key);
     142              : 
     143              :   void end_group ();
     144              : 
     145           22 :   std::unique_ptr<xml::element> take_current_diagnostic ()
     146              :   {
     147           22 :     return std::move (m_cur_diagnostic_element);
     148              :   }
     149              : 
     150              :   void flush_to_file (FILE *outf);
     151              : 
     152            4 :   const xml::document &get_document () const { return *m_document; }
     153              : 
     154           22 :   void set_printer (pretty_printer &pp)
     155              :   {
     156           22 :     m_printer = &pp;
     157              :   }
     158              : 
     159              :   std::unique_ptr<xml::element>
     160              :   make_element_for_metadata (const metadata &);
     161              : 
     162              :   std::unique_ptr<xml::element>
     163              :   make_element_for_patch (const diagnostic_info &diagnostic);
     164              : 
     165          119 :   void add_focus_id (std::string focus_id)
     166              :   {
     167          119 :     m_ui_focus_ids.append_string (focus_id.c_str ());
     168              :   }
     169              : 
     170              :   std::unique_ptr<xml::node>
     171              :   maybe_make_state_diagram (const paths::event &event);
     172              : 
     173              :   const logical_locations::manager *
     174          107 :   get_logical_loc_mgr () const
     175              :   {
     176          107 :     return m_context.get_logical_location_manager ();
     177              :   }
     178              : 
     179              : private:
     180              :   void
     181              :   add_stylesheet (std::string url);
     182              : 
     183              :   std::unique_ptr<xml::element>
     184              :   make_element_for_diagnostic (const diagnostic_info &diagnostic,
     185              :                                enum kind orig_diag_kind,
     186              :                                bool alert);
     187              : 
     188              :   std::unique_ptr<xml::element>
     189              :   make_metadata_element (label_text label,
     190              :                          label_text url);
     191              : 
     192              :   void
     193              :   add_at_nesting_level (size_t nesting_level,
     194              :                         std::unique_ptr<xml::element> child_diag_element);
     195              : 
     196              :   void
     197              :   push_nesting_level ();
     198              : 
     199              :   void
     200              :   pop_nesting_level ();
     201              : 
     202              :   void
     203              :   add_graph (const digraphs::digraph &dg,
     204              :              xml::element &parent_element);
     205              : 
     206              :   context &m_context;
     207              :   pretty_printer *m_printer;
     208              :   const line_maps *m_line_maps;
     209              :   html_generation_options m_html_gen_opts;
     210              : 
     211              :   std::unique_ptr<xml::document> m_document;
     212              :   xml::element *m_head_element;
     213              :   xml::element *m_title_element;
     214              :   xml::element *m_body_element;
     215              :   xml::element *m_diagnostics_element;
     216              :   std::unique_ptr<xml::element> m_cur_diagnostic_element;
     217              :   std::vector<xml::element *> m_cur_nesting_levels;
     218              :   int m_next_diag_id; // for handing out unique IDs
     219              :   json::array m_ui_focus_ids;
     220              :   logical_locations::key m_last_logical_location;
     221              :   location_t m_last_location;
     222              :   expanded_location m_last_expanded_location;
     223              :   std::map<logical_locations::key, xml::element *> m_per_logical_loc_graphs;
     224              : };
     225              : 
     226              : static std::unique_ptr<xml::element>
     227          129 : make_div (std::string class_)
     228              : {
     229          129 :   auto div = std::make_unique<xml::element> ("div", false);
     230          129 :   div->set_attr ("class", std::move (class_));
     231          129 :   return div;
     232              : }
     233              : 
     234              : static std::unique_ptr<xml::element>
     235           79 : make_span (std::string class_)
     236              : {
     237           79 :   auto span = std::make_unique<xml::element> ("span", true);
     238           79 :   span->set_attr ("class", std::move (class_));
     239           79 :   return span;
     240              : }
     241              : 
     242              : /* class html_sink_buffer : public per_sink_buffer.  */
     243              : 
     244              : void
     245            0 : html_sink_buffer::dump (FILE *out, int indent) const
     246              : {
     247            0 :   dumping::emit_heading (out, indent, "html_sink_buffer");
     248            0 :   int idx = 0;
     249            0 :   for (auto &result : m_results)
     250              :     {
     251            0 :       dumping::emit_indent (out, indent + 2);
     252            0 :       fprintf (out, "result[%i]:\n", idx);
     253            0 :       result->dump (out);
     254            0 :       fprintf (out, "\n");
     255            0 :       ++idx;
     256              :     }
     257            0 : }
     258              : 
     259              : bool
     260            0 : html_sink_buffer::empty_p () const
     261              : {
     262            0 :   return m_results.empty ();
     263              : }
     264              : 
     265              : void
     266            0 : html_sink_buffer::move_to (per_sink_buffer &base)
     267              : {
     268            0 :   html_sink_buffer &dest
     269              :     = static_cast<html_sink_buffer &> (base);
     270            0 :   for (auto &&result : m_results)
     271            0 :     dest.m_results.push_back (std::move (result));
     272            0 :   m_results.clear ();
     273            0 : }
     274              : 
     275              : void
     276            0 : html_sink_buffer::clear ()
     277              : {
     278            0 :   m_results.clear ();
     279            0 : }
     280              : 
     281              : void
     282            0 : html_sink_buffer::flush ()
     283              : {
     284            0 :   for (auto &&result : m_results)
     285            0 :     m_builder.m_diagnostics_element->add_child (std::move (result));
     286            0 :   m_results.clear ();
     287            0 : }
     288              : 
     289              : /* class html_builder.  */
     290              : 
     291              : /* Style information for writing out HTML paths.
     292              :    Colors taken from https://pf3.patternfly.org/v3/styles/color-palette/ */
     293              : 
     294              : static const char * const HTML_STYLE
     295              :   = ("  <style>\n"
     296              :      "    .linenum { color: white;\n"
     297              :      "               background-color: #0088ce;\n"
     298              :      "               white-space: pre;\n"
     299              :      "               border-right: 1px solid black; }\n"
     300              :      "    .ruler { color: red;\n"
     301              :      "              white-space: pre; }\n"
     302              :      "    .source { color: blue;\n"
     303              :      "              background-color: white;\n"
     304              :      "              white-space: pre; }\n"
     305              :      "    .annotation { color: green;\n"
     306              :      "                  background-color: white;\n"
     307              :      "                  white-space: pre; }\n"
     308              :      "    .linenum-gap { text-align: center;\n"
     309              :      "                   border-top: 1px solid black;\n"
     310              :      "                   border-right: 1px solid black;\n"
     311              :      "                   background-color: #ededed; }\n"
     312              :      "    .source-gap { border-bottom: 1px dashed black;\n"
     313              :      "                  border-top: 1px dashed black;\n"
     314              :      "                  background-color: #ededed; }\n"
     315              :      "    .no-locus-event { font-family: monospace;\n"
     316              :      "                      color: green;\n"
     317              :      "                      white-space: pre; }\n"
     318              :      "    .funcname { font-weight: bold; }\n"
     319              :      "    .events-hdr { color: white;\n"
     320              :      "                  background-color: #030303; }\n"
     321              :      "    .event-range {  border: 1px solid black;\n"
     322              :      "                    padding: 0px; }\n"
     323              :      "    .event-range-with-margin { border-spacing: 0; }\n"
     324              :      "    .locus { font-family: monospace;\n"
     325              :      "             border-spacing: 0px; }\n"
     326              :      "    .selected { color: white;\n"
     327              :      "                background-color: #0088ce; }\n"
     328              :      "    .stack-frame-with-margin { border-spacing: 0; }\n"
     329              :      "    .stack-frame {  padding: 5px;\n"
     330              :      "                    box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.5); }\n"
     331              :      "    .frame-funcname { text-align: right;\n"
     332              :      "                      font-style: italic; } \n"
     333              :      "    .highlight-a { color: #703fec;\n" // pf-purple-400
     334              :      "                   font-weight: bold; }\n"
     335              :      "    .highlight-b { color: #3f9c35;\n" // pf-green-400
     336              :      "                   font-weight: bold; }\n"
     337              :      "    .gcc-quoted-text { font-weight: bold;\n"
     338              :      "                       font-family: mono; }\n"
     339              :      "  </style>\n");
     340              : 
     341              : /* A little JavaScript for ease of navigation.
     342              :    Keys j/k move forward and backward cyclically through a list
     343              :    of focus ids (written out in another <script> tag as the HTML
     344              :    is flushed).  */
     345              : 
     346              : const char * const HTML_SCRIPT
     347              :   = ("  var current_focus_idx = 0;\n"
     348              :      "\n"
     349              :      "  function get_focus_span (focus_idx)\n"
     350              :      "  {\n"
     351              :      "      const element_id = focus_ids[focus_idx];\n"
     352              :      "      return document.getElementById(element_id);\n"
     353              :      "  }\n"
     354              :      "  function get_any_state_diagram (focus_idx)\n"
     355              :      "  {\n"
     356              :      "      const element_id = focus_ids[focus_idx];\n"
     357              :      "      return document.getElementById(element_id + \"-state-diagram\");\n"
     358              :      "  }\n"
     359              :      "  function unhighlight_current_focus_idx ()\n"
     360              :      "  {\n"
     361              :      "      get_focus_span (current_focus_idx).classList.remove ('selected');\n"
     362              :      "      state_diagram = get_any_state_diagram (current_focus_idx);\n"
     363              :      "      if (state_diagram) {\n"
     364              :      "          state_diagram.style.visibility = \"hidden\";\n"
     365              :      "      }\n"
     366              :      "  }\n"
     367              :      "  function highlight_current_focus_idx ()\n"
     368              :      "  {\n"
     369              :      "      const el = get_focus_span (current_focus_idx);\n"
     370              :      "      el.classList.add ('selected');\n"
     371              :      "      state_diagram = get_any_state_diagram (current_focus_idx);\n"
     372              :      "      if (state_diagram) {\n"
     373              :      "          state_diagram.style.visibility = \"visible\";\n"
     374              :      "      }\n"
     375              :      "      // Center the element on the screen\n"
     376              :      "      const top_y = el.getBoundingClientRect ().top + window.pageYOffset;\n"
     377              :      "      const middle = top_y - (window.innerHeight / 2);\n"
     378              :      "      window.scrollTo (0, middle);\n"
     379              :      "  }\n"
     380              :      "  function select_prev_focus_idx ()\n"
     381              :      "  {\n"
     382              :      "      unhighlight_current_focus_idx ();\n"
     383              :      "      if (current_focus_idx > 0)\n"
     384              :      "          current_focus_idx -= 1;\n"
     385              :      "      else\n"
     386              :      "          current_focus_idx = focus_ids.length - 1;\n"
     387              :      "      highlight_current_focus_idx ();\n"
     388              :      "  }\n"
     389              :      "  function select_next_focus_idx ()\n"
     390              :      "  {\n"
     391              :      "      unhighlight_current_focus_idx ();\n"
     392              :      "      if (current_focus_idx < focus_ids.length - 1)\n"
     393              :      "          current_focus_idx += 1;\n"
     394              :      "      else\n"
     395              :      "          current_focus_idx = 0;\n"
     396              :      "      highlight_current_focus_idx ();\n"
     397              :      "  }\n"
     398              :      "  document.addEventListener('keydown', function (ev) {\n"
     399              :      "      if (ev.key == 'j')\n"
     400              :      "          select_next_focus_idx ();\n"
     401              :      "      else if (ev.key == 'k')\n"
     402              :      "          select_prev_focus_idx ();\n"
     403              :      "  });\n"
     404              :      "  highlight_current_focus_idx ();\n");
     405              : 
     406           22 : struct html_doctypedecl : public xml::doctypedecl
     407              : {
     408           18 :   void write_as_xml (pretty_printer *pp,
     409              :                      int depth, bool indent) const final override
     410              :   {
     411           18 :     if (indent)
     412              :       {
     413           18 :         for (int i = 0; i < depth; ++i)
     414            0 :           pp_string (pp, "  ");
     415              :       }
     416           18 :     pp_string (pp, "<!DOCTYPE html\n"
     417              :                "     PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n"
     418              :                "     \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");
     419           18 :     if (indent)
     420           18 :       pp_newline (pp);
     421           18 :   }
     422              : };
     423              : 
     424              : /* html_builder's ctor.  */
     425              : 
     426           22 : html_builder::html_builder (context &dc,
     427              :                             pretty_printer &pp,
     428              :                             const line_maps *line_maps,
     429           22 :                             const html_generation_options &html_gen_opts)
     430           22 : : m_context (dc),
     431           22 :   m_printer (&pp),
     432           22 :   m_line_maps (line_maps),
     433           22 :   m_html_gen_opts (html_gen_opts),
     434           22 :   m_head_element (nullptr),
     435           22 :   m_title_element (nullptr),
     436           22 :   m_body_element (nullptr),
     437           22 :   m_diagnostics_element (nullptr),
     438           22 :   m_next_diag_id (0),
     439           22 :   m_last_location (UNKNOWN_LOCATION),
     440           22 :   m_last_expanded_location ({})
     441              : {
     442           22 :   gcc_assert (m_line_maps);
     443              : 
     444           22 :   m_document = std::make_unique<xml::document> ();
     445           22 :   m_document->m_doctypedecl = std::make_unique<html_doctypedecl> ();
     446           22 :   {
     447           22 :     auto html_element = std::make_unique<xml::element> ("html", false);
     448           22 :     html_element->set_attr ("xmlns",
     449           22 :                             "http://www.w3.org/1999/xhtml");
     450           22 :     xml::printer xp (*html_element.get ());
     451           22 :     m_document->add_child (std::move (html_element));
     452              : 
     453           22 :     {
     454           22 :       xml::auto_print_element head (xp, "head");
     455           22 :       m_head_element = xp.get_insertion_point ();
     456           22 :       {
     457           22 :         xml::auto_print_element title (xp, "title", true);
     458           22 :         m_title_element = xp.get_insertion_point ();
     459           22 :         m_title_element->add_text (" ");
     460           22 :       }
     461              : 
     462           22 :       if (m_html_gen_opts.m_css)
     463              :         {
     464           14 :           add_stylesheet ("https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/css/patternfly.min.css");
     465           14 :           add_stylesheet ("https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/css/patternfly-additions.min.css");
     466           14 :           xp.add_raw (HTML_STYLE);
     467              :         }
     468           22 :       if (m_html_gen_opts.m_javascript)
     469              :         {
     470            0 :           xp.push_tag ("script");
     471              :           /* Escaping rules are different for HTML <script> elements,
     472              :              so add the script "raw" for now.  */
     473            0 :           xp.add_raw (HTML_SCRIPT);
     474            0 :           xp.pop_tag ("script");
     475              :         }
     476           22 :     }
     477              : 
     478           22 :     {
     479           22 :       xml::auto_print_element body (xp, "body");
     480           22 :       m_body_element = xp.get_insertion_point ();
     481           22 :       {
     482           22 :         auto diagnostics_element = make_div ("gcc-diagnostic-list");
     483           22 :         m_diagnostics_element = diagnostics_element.get ();
     484           22 :         xp.append (std::move (diagnostics_element));
     485           22 :       }
     486           22 :     }
     487           22 :   }
     488           22 : }
     489              : 
     490              : void
     491            0 : html_builder::dump (FILE *out, int indent) const
     492              : {
     493            0 :   dumping::emit_heading (out, indent, "HTML generation options");
     494            0 :   m_html_gen_opts.dump (out, indent + 2);
     495            0 : }
     496              : 
     497              : void
     498           22 : html_builder::set_main_input_filename (const char *name)
     499              : {
     500           22 :   gcc_assert (m_title_element);
     501           22 :   if (name)
     502              :     {
     503           22 :       m_title_element->m_children.clear ();
     504           22 :       m_title_element->add_text (name);
     505              :     }
     506           22 : }
     507              : 
     508              : void
     509           28 : html_builder::add_stylesheet (std::string url)
     510              : {
     511           28 :   gcc_assert (m_head_element);
     512              : 
     513           28 :   xml::printer xp (*m_head_element);
     514           28 :   xp.push_tag ("link", false);
     515           28 :   xp.set_attr ("rel", "stylesheet");
     516           28 :   xp.set_attr ("type", "text/css");
     517           28 :   xp.set_attr ("href", std::move (url));
     518           28 : }
     519              : 
     520              : /* Implementation of "on_report_diagnostic" for HTML output.  */
     521              : 
     522              : void
     523          106 : html_builder::on_report_diagnostic (const diagnostic_info &diagnostic,
     524              :                                     enum kind orig_diag_kind,
     525              :                                     html_sink_buffer *buffer)
     526              : {
     527          106 :   if (diagnostic.m_kind == kind::ice || diagnostic.m_kind == kind::ice_nobt)
     528              :     {
     529              :       /* Print a header for the remaining output to stderr, and
     530              :          return, attempting to print the usual ICE messages to
     531              :          stderr.  Hopefully this will be helpful to the user in
     532              :          indicating what's gone wrong (also for DejaGnu, for pruning
     533              :          those messages).   */
     534            2 :       fnotice (stderr, "Internal compiler error:\n");
     535              :     }
     536              : 
     537          106 :   const int nesting_level = m_context.get_diagnostic_nesting_level ();
     538          106 :   bool alert = true;
     539          106 :   if (m_cur_diagnostic_element && nesting_level > 0)
     540              :     alert = false;
     541          106 :   if (!m_cur_diagnostic_element)
     542           77 :     m_last_logical_location = logical_locations::key ();
     543          106 :   auto diag_element
     544          106 :     = make_element_for_diagnostic (diagnostic, orig_diag_kind, alert);
     545          106 :   if (buffer)
     546              :     {
     547            0 :       gcc_assert (!m_cur_diagnostic_element);
     548            0 :       buffer->m_results.push_back (std::move (diag_element));
     549              :     }
     550              :   else
     551              :     {
     552          106 :       if (m_cur_diagnostic_element)
     553              :         {
     554              :           /* Nested diagnostic.  */
     555           29 :           gcc_assert (nesting_level >= 0);
     556           29 :           add_at_nesting_level (nesting_level, std::move (diag_element));
     557              :         }
     558              :       else
     559              :         /* Top-level diagnostic.  */
     560              :         {
     561           77 :           m_cur_diagnostic_element = std::move (diag_element);
     562           77 :           m_cur_nesting_levels.clear ();
     563              :         }
     564              :     }
     565          106 : }
     566              : 
     567              : // For ease of comparison with show-nesting-levels=yes
     568              : 
     569              : static void
     570           20 : add_nesting_level_attr (xml::element &element,
     571              :                         int nesting_level)
     572              : {
     573           20 :   element.set_attr ("nesting-level", std::to_string (nesting_level));
     574           20 : }
     575              : 
     576              : void
     577           29 : html_builder::
     578              : add_at_nesting_level (size_t nesting_level,
     579              :                       std::unique_ptr<xml::element> child_diag_element)
     580              : {
     581           29 :   gcc_assert (m_cur_diagnostic_element);
     582           35 :   while (nesting_level > m_cur_nesting_levels.size ())
     583            6 :     push_nesting_level ();
     584           31 :   while (nesting_level < m_cur_nesting_levels.size ())
     585            2 :     pop_nesting_level ();
     586              : 
     587           29 :   if (nesting_level > 0)
     588              :     {
     589           14 :       gcc_assert (!m_cur_nesting_levels.empty ());
     590           14 :       auto current_nesting_level = m_cur_nesting_levels.back ();
     591           14 :       xml::printer xp (*current_nesting_level);
     592           14 :       xp.push_tag ("li");
     593           14 :       add_nesting_level_attr (*xp.get_insertion_point (),
     594           14 :                               m_cur_nesting_levels.size ());
     595           14 :       xp.append (std::move (child_diag_element));
     596           14 :       xp.pop_tag ("li");
     597           14 :     }
     598              :   else
     599           15 :     m_cur_diagnostic_element->add_child (std::move (child_diag_element));
     600           29 : }
     601              : 
     602              : void
     603            6 : html_builder::push_nesting_level ()
     604              : {
     605            6 :   gcc_assert (m_cur_diagnostic_element);
     606            6 :   auto new_nesting_level = std::make_unique<xml::element> ("ul", false);
     607           12 :   add_nesting_level_attr (*new_nesting_level,
     608            6 :                           m_cur_nesting_levels.size () + 1);
     609            6 :   xml::element *current_nesting_level = nullptr;
     610            6 :   if (!m_cur_nesting_levels.empty ())
     611            3 :     current_nesting_level = m_cur_nesting_levels.back ();
     612            6 :   m_cur_nesting_levels.push_back (new_nesting_level.get ());
     613            6 :   if (current_nesting_level)
     614            3 :     current_nesting_level->add_child (std::move (new_nesting_level));
     615              :   else
     616            3 :     m_cur_diagnostic_element->add_child (std::move (new_nesting_level));
     617            6 : }
     618              : 
     619              : void
     620            2 : html_builder::pop_nesting_level ()
     621              : {
     622            2 :   gcc_assert (m_cur_diagnostic_element);
     623            2 :   m_cur_nesting_levels.pop_back ();
     624            2 : }
     625              : 
     626              : static void
     627            0 : print_pre_source (xml::printer &xp, const char *text)
     628              : {
     629            0 :   xp.push_tag_with_class ("pre", "source", true);
     630            0 :   xp.add_text (text);
     631            0 :   xp.pop_tag ("pre");
     632            0 : }
     633              : 
     634              : std::unique_ptr<xml::node>
     635           13 : html_builder::maybe_make_state_diagram (const paths::event &event)
     636              : {
     637           13 :   if (!m_html_gen_opts.m_show_state_diagrams)
     638           12 :     return nullptr;
     639              : 
     640            1 :   auto logical_loc_mgr = get_logical_loc_mgr ();
     641            1 :   if (!logical_loc_mgr)
     642            0 :     return nullptr;
     643              : 
     644              :   /* Get state graph; if we're going to print it later, also request
     645              :      the debug version.  */
     646            1 :   auto state_graph
     647              :     = event.maybe_make_diagnostic_state_graph
     648            1 :         (m_html_gen_opts.m_show_graph_sarif);
     649            1 :   if (!state_graph)
     650            0 :     return nullptr;
     651              : 
     652              :   // Convert it to .dot AST
     653            1 :   auto dot_graph = state_graphs::make_dot_graph (*state_graph,
     654            1 :                                                  *logical_loc_mgr);
     655            1 :   gcc_assert (dot_graph);
     656              : 
     657            1 :   auto wrapper = std::make_unique<xml::element> ("div", false);
     658            1 :   xml::printer xp (*wrapper);
     659              : 
     660            1 :   if (m_html_gen_opts.m_show_graph_sarif)
     661              :     {
     662              :       // For debugging, show the SARIF src inline:
     663            0 :       pretty_printer pp;
     664            0 :       state_graph->make_json_sarif_graph ()->print (&pp, true);
     665            0 :       print_pre_source (xp, pp_formatted_text (&pp));
     666            0 :     }
     667              : 
     668            1 :   if (m_html_gen_opts.m_show_graph_dot_src)
     669              :     {
     670              :       // For debugging, show the dot src inline:
     671            0 :       pretty_printer pp;
     672            0 :       dot::writer w (pp);
     673            0 :       dot_graph->print (w);
     674            0 :       print_pre_source (xp, pp_formatted_text (&pp));
     675            0 :     }
     676              : 
     677              :   // Turn the .dot into SVG and splice into place
     678            1 :   auto svg = dot::make_svg_from_graph (*dot_graph);
     679            1 :   if (svg)
     680            1 :     xp.append (std::move (svg));
     681              : 
     682            1 :   return wrapper;
     683            1 : }
     684              : 
     685              : /* Custom subclass of html_label_writer.
     686              :    Wrap labels within a <span> element, supplying them with event IDs.
     687              :    Add the IDs to the list of focus IDs.  */
     688              : 
     689            3 : class html_path_label_writer : public html_label_writer
     690              : {
     691              : public:
     692            3 :   html_path_label_writer (xml::printer &xp,
     693              :                           html_builder &builder,
     694              :                           const paths::path &path,
     695              :                           const std::string &event_id_prefix)
     696            3 :   : m_xp (xp),
     697            3 :     m_html_builder (builder),
     698            3 :     m_path (path),
     699            3 :     m_event_id_prefix (event_id_prefix),
     700            3 :     m_next_event_idx (0),
     701            3 :     m_curr_event_id ()
     702              :   {
     703              :   }
     704              : 
     705           13 :   void begin_label () final override
     706              :   {
     707           13 :     m_curr_event_id = m_next_event_idx++;
     708           13 :     m_xp.push_tag_with_class ("span", "event", true);
     709           13 :     m_xp.set_attr ("id", get_element_id ());
     710           13 :     m_html_builder.add_focus_id (get_element_id ());
     711           13 :   }
     712              : 
     713           13 :   void end_label () final override
     714              :   {
     715           13 :     const paths::event &event
     716           13 :       = m_path.get_event (m_curr_event_id.zero_based ());
     717           13 :     if (auto state_doc = m_html_builder.maybe_make_state_diagram (event))
     718              :     {
     719            1 :       m_xp.push_tag_with_class ("div", "state-diagram", false);
     720            2 :       m_xp.set_attr ("id", get_element_id () + "-state-diagram");
     721            1 :       m_xp.set_attr ("style",
     722            1 :                      ("position: absolute;"
     723              :                       " z-index: 1;"
     724              :                       " visibility: hidden;"));
     725            1 :       m_xp.append (std::move (state_doc));
     726            1 :       m_xp.pop_tag ("div");
     727           13 :     }
     728              : 
     729           13 :     m_xp.pop_tag ("span"); // from begin_label
     730           13 :   }
     731              : 
     732              : private:
     733              :   std::string
     734           27 :   get_element_id () const
     735              :   {
     736           27 :     gcc_assert (m_curr_event_id.known_p ());
     737           54 :     return (m_event_id_prefix
     738           27 :             + std::to_string (m_curr_event_id.zero_based ()));
     739              :   }
     740              : 
     741              :   xml::printer &m_xp;
     742              :   html_builder &m_html_builder;
     743              :   const paths::path &m_path;
     744              :   const std::string &m_event_id_prefix;
     745              :   int m_next_event_idx;
     746              :   paths::event_id_t m_curr_event_id;
     747              : };
     748              : 
     749              : /* See https://pf3.patternfly.org/v3/pattern-library/widgets/#alerts */
     750              : static const char *
     751           92 : get_pf_class_for_alert_div (enum kind diag_kind)
     752              : {
     753           92 :   switch (diag_kind)
     754              :     {
     755              :     case kind::debug:
     756              :     case kind::note:
     757              :       return "alert alert-info";
     758              : 
     759           65 :     case kind::anachronism:
     760           65 :     case kind::warning:
     761           65 :       return "alert alert-warning";
     762              : 
     763           11 :     case kind::error:
     764           11 :     case kind::sorry:
     765           11 :     case kind::ice:
     766           11 :     case kind::ice_nobt:
     767           11 :     case kind::fatal:
     768           11 :       return "alert alert-danger";
     769              : 
     770            0 :     default:
     771            0 :       gcc_unreachable ();
     772              :     }
     773              : }
     774              : 
     775              : static const char *
     776           92 : get_pf_class_for_alert_icon (enum kind diag_kind)
     777              : {
     778           92 :   switch (diag_kind)
     779              :     {
     780              :     case kind::debug:
     781              :     case kind::note:
     782              :       return "pficon pficon-info";
     783              : 
     784           65 :     case kind::anachronism:
     785           65 :     case kind::warning:
     786           65 :       return "pficon pficon-warning-triangle-o";
     787              : 
     788           11 :     case kind::error:
     789           11 :     case kind::sorry:
     790           11 :     case kind::ice:
     791           11 :     case kind::ice_nobt:
     792           11 :     case kind::fatal:
     793           11 :       return "pficon pficon-error-circle-o";
     794              : 
     795            0 :     default:
     796            0 :       gcc_unreachable ();
     797              :     }
     798              : }
     799              : 
     800              : static const char *
     801           70 : get_label_for_logical_location_kind (enum logical_locations::kind kind)
     802              : {
     803           70 :   switch (kind)
     804              :     {
     805            0 :     default:
     806            0 :       gcc_unreachable ();
     807              :     case logical_locations::kind::unknown:
     808              :       return nullptr;
     809              : 
     810              :     /* Kinds within executable code.  */
     811           70 :     case logical_locations::kind::function:
     812           70 :       return "Function";
     813            0 :     case logical_locations::kind::member:
     814            0 :       return "Member";
     815            0 :     case logical_locations::kind::module_:
     816            0 :       return "Module";
     817            0 :     case logical_locations::kind::namespace_:
     818            0 :       return "Namespace";
     819            0 :     case logical_locations::kind::type:
     820            0 :       return "Type";
     821            0 :     case logical_locations::kind::return_type:
     822            0 :       return "Return type";
     823            0 :     case logical_locations::kind::parameter:
     824            0 :       return "Parameter";
     825            0 :     case logical_locations::kind::variable:
     826            0 :       return "Variable";
     827              : 
     828              :     /* Kinds within XML or HTML documents.  */
     829            0 :     case logical_locations::kind::element:
     830            0 :       return "Element";
     831            0 :     case logical_locations::kind::attribute:
     832            0 :       return "Attribute";
     833            0 :     case logical_locations::kind::text:
     834            0 :       return "Text";
     835            0 :     case logical_locations::kind::comment:
     836            0 :       return "Comment";
     837            0 :     case logical_locations::kind::processing_instruction:
     838            0 :       return "Processing Instruction";
     839            0 :     case logical_locations::kind::dtd:
     840            0 :       return "DTD";
     841            0 :     case logical_locations::kind::declaration:
     842            0 :       return "Declaration";
     843              : 
     844              :   /* Kinds within JSON documents.  */
     845            0 :     case logical_locations::kind::object:
     846            0 :       return "Object";
     847            0 :     case logical_locations::kind::array:
     848            0 :       return "Array";
     849            0 :     case logical_locations::kind::property:
     850            0 :       return "Property";
     851            0 :     case logical_locations::kind::value:
     852            0 :       return "Value";
     853              :     }
     854              : }
     855              : 
     856              : static void
     857          334 : add_labelled_value (xml::printer &xp,
     858              :                     std::string id,
     859              :                     std::string label,
     860              :                     std::string value,
     861              :                     bool quote_value)
     862              : {
     863          334 :   xp.push_tag ("div", true);
     864          668 :   xp.set_attr ("id", id);
     865          334 :   xp.push_tag ("span");
     866          668 :   xp.add_text (label);
     867          334 :   xp.add_text (" ");
     868          334 :   xp.pop_tag ("span");
     869          334 :   xp.push_tag ("span");
     870          334 :   if (quote_value)
     871           70 :     xp.set_attr ("class", "gcc-quoted-text");
     872          334 :   xp.add_text (std::move (value));
     873          334 :   xp.pop_tag ("span");
     874          334 :   xp.pop_tag ("div");
     875          334 : }
     876              : 
     877          106 : class html_token_printer : public token_printer
     878              : {
     879              : public:
     880          118 :   html_token_printer (xml::element &parent_element)
     881              :     /* Ideally pp_token_lists that reach a token_printer should be
     882              :        "balanced", but for now they can have mismatching pp_tokens
     883              :        e.g. a begin_color without an end_color (PR other/120610).
     884              :        Give html_token_printer its own xml::printer as a firewall to
     885              :        limit the scope of the mismatches in the HTML.  */
     886          118 :     : m_xp (parent_element,
     887              :             /* Similarly we don't check that the popped tags match.  */
     888          118 :             false)
     889              :   {
     890              :   }
     891          118 :   void print_tokens (pretty_printer */*pp*/,
     892              :                      const pp_token_list &tokens) final override
     893              :   {
     894              :     /* Implement print_tokens by adding child elements to
     895              :        m_parent_element.  */
     896         1054 :     for (auto iter = tokens.m_first; iter; iter = iter->m_next)
     897          936 :       switch (iter->m_kind)
     898              :         {
     899            0 :         default:
     900            0 :           gcc_unreachable ();
     901              : 
     902          369 :         case pp_token::kind::text:
     903          369 :           {
     904          369 :             pp_token_text *sub = as_a <pp_token_text *> (iter);
     905              :             /* The value might be in the obstack, so we may need to
     906              :                copy it.  */
     907          369 :             m_xp.add_text (sub->m_value.get ());
     908              :             }
     909          369 :           break;
     910              : 
     911          130 :         case pp_token::kind::begin_color:
     912          130 :           {
     913          130 :             pp_token_begin_color *sub = as_a <pp_token_begin_color *> (iter);
     914          130 :             gcc_assert (sub->m_value.get ());
     915          130 :             m_xp.push_tag_with_class ("span", sub->m_value.get ());
     916              :           }
     917          130 :           break;
     918              : 
     919          130 :         case pp_token::kind::end_color:
     920          130 :           m_xp.pop_tag ("span");
     921          130 :           break;
     922              : 
     923          151 :         case pp_token::kind::begin_quote:
     924          151 :           {
     925          151 :             m_xp.add_text (open_quote);
     926          151 :             m_xp.push_tag_with_class ("span", "gcc-quoted-text");
     927              :           }
     928          151 :           break;
     929          151 :         case pp_token::kind::end_quote:
     930          151 :           {
     931          151 :             m_xp.pop_tag ("span");
     932          151 :             m_xp.add_text (close_quote);
     933              :           }
     934          151 :           break;
     935              : 
     936            0 :         case pp_token::kind::begin_url:
     937            0 :           {
     938            0 :             pp_token_begin_url *sub = as_a <pp_token_begin_url *> (iter);
     939            0 :             m_xp.push_tag ("a", true);
     940            0 :             m_xp.set_attr ("href", sub->m_value.get ());
     941              :           }
     942            0 :           break;
     943            0 :         case pp_token::kind::end_url:
     944            0 :           m_xp.pop_tag ("a");
     945            0 :           break;
     946              : 
     947            5 :         case pp_token::kind::event_id:
     948            5 :           {
     949            5 :             pp_token_event_id *sub = as_a <pp_token_event_id *> (iter);
     950            5 :             gcc_assert (sub->m_event_id.known_p ());
     951            5 :             m_xp.add_text ("(");
     952            5 :             m_xp.add_text (std::to_string (sub->m_event_id.one_based ()));
     953            5 :             m_xp.add_text (")");
     954              :           }
     955            5 :           break;
     956              :         }
     957          118 :   }
     958              : 
     959              : private:
     960              :   xml::printer m_xp;
     961              : };
     962              : 
     963              : /* Make a <div class="gcc-diagnostic"> for DIAGNOSTIC.
     964              : 
     965              :    If ALERT is true, make it be a PatternFly alert (see
     966              :    https://pf3.patternfly.org/v3/pattern-library/widgets/#alerts) and
     967              :    show severity text (e.g. "error: ").
     968              : 
     969              :    If ALERT is false, don't show the severity text and don't show
     970              :    the filename if it's the same as the previous diagnostic within the
     971              :    diagnostic group.  */
     972              : 
     973              : std::unique_ptr<xml::element>
     974          106 : html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic,
     975              :                                            enum kind orig_diag_kind,
     976              :                                            bool alert)
     977              : {
     978          106 :   const int diag_idx = m_next_diag_id++;
     979          106 :   std::string diag_id;
     980          106 :   {
     981          106 :     pretty_printer pp;
     982          106 :     pp_printf (&pp, "gcc-diag-%i", diag_idx);
     983          106 :     diag_id = pp_formatted_text (&pp);
     984          106 :   }
     985              : 
     986              :   // TODO: might be nice to emulate the text output format, but colorize it
     987              : 
     988              :   /* See https://pf3.patternfly.org/v3/pattern-library/widgets/#alerts
     989              :      which has this example:
     990              : <div class="alert alert-danger">
     991              :   <span class="pficon pficon-error-circle-o"></span>
     992              :   <strong>Hey there is a problem!</strong> Yeah this is really messed up and you should <a href="#" class="alert-link">know about it</a>.
     993              : </div>
     994              :   */
     995          106 :   auto diag_element = make_div ("gcc-diagnostic");
     996          212 :   diag_element->set_attr ("id", diag_id);
     997          106 :   if (alert)
     998          184 :     diag_element->set_attr ("class",
     999          184 :                             get_pf_class_for_alert_div (diagnostic.m_kind));
    1000              : 
    1001          106 :   xml::printer xp (*diag_element.get ());
    1002          106 :   const size_t depth_within_alert_div = 1;
    1003              : 
    1004          106 :   gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
    1005              : 
    1006          106 :   if (alert)
    1007              :     {
    1008          184 :       xp.push_tag_with_class ("span",
    1009           92 :                               get_pf_class_for_alert_icon (diagnostic.m_kind),
    1010              :                               true);
    1011           92 :       xp.add_text (" ");
    1012           92 :       xp.pop_tag ("span");
    1013              :     }
    1014              : 
    1015              :   // The rest goes in the <div>...
    1016          106 :   gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
    1017              : 
    1018          106 :   xp.push_tag_with_class ("div", "gcc-message", true);
    1019          106 :   std::string message_alert_id (diag_id + "-message");
    1020          212 :   xp.set_attr ("id", message_alert_id);
    1021          212 :   add_focus_id (message_alert_id);
    1022              : 
    1023          106 :   const size_t depth_within_message_div = depth_within_alert_div + 1;
    1024          106 :   gcc_assert (xp.get_num_open_tags () == depth_within_message_div);
    1025              : 
    1026              :   // Severity e.g. "warning: "
    1027          106 :   bool show_severity = true;
    1028          106 :   if (!alert)
    1029              :     show_severity = false;
    1030           92 :   if (show_severity)
    1031              :   {
    1032           92 :     xp.push_tag ("strong");
    1033           92 :     xp.add_text (_(get_text_for_kind (diagnostic.m_kind)));
    1034           92 :     xp.pop_tag ("strong");
    1035           92 :     xp.add_text (" ");
    1036              :   }
    1037              : 
    1038              :   // Add the message itself:
    1039          106 :   html_token_printer tok_printer (*xp.get_insertion_point ());
    1040          106 :   m_printer->set_token_printer (&tok_printer);
    1041          106 :   pp_output_formatted_text (m_printer, m_context.get_urlifier ());
    1042          106 :   m_printer->set_token_printer (nullptr);
    1043          106 :   pp_clear_output_area (m_printer);
    1044              : 
    1045              :   // Add any metadata as a suffix to the message
    1046          106 :   if (diagnostic.m_metadata)
    1047            4 :     if (auto e = make_element_for_metadata (*diagnostic.m_metadata))
    1048              :       {
    1049            2 :         xp.add_text (" ");
    1050            2 :         xp.append (std::move (e));
    1051            4 :       }
    1052              : 
    1053              :   // Add any option as a suffix to the message
    1054              : 
    1055          106 :   label_text option_text = label_text::take
    1056          106 :     (m_context.make_option_name (diagnostic.m_option_id,
    1057          106 :                                  orig_diag_kind, diagnostic.m_kind));
    1058          106 :   if (option_text.get ())
    1059              :     {
    1060           56 :       label_text option_url = label_text::take
    1061           56 :         (m_context.make_option_url (diagnostic.m_option_id));
    1062              : 
    1063           56 :       xp.add_text (" ");
    1064           56 :       auto option_span = make_span ("gcc-option");
    1065           56 :       option_span->add_text ("[");
    1066           56 :       {
    1067           56 :         if (option_url.get ())
    1068              :           {
    1069           56 :             auto anchor = std::make_unique<xml::element> ("a", true);
    1070           56 :             anchor->set_attr ("href", option_url.get ());
    1071           56 :             anchor->add_text (option_text.get ());
    1072           56 :             option_span->add_child (std::move (anchor));
    1073           56 :           }
    1074              :         else
    1075            0 :           option_span->add_text (option_text.get ());
    1076           56 :         option_span->add_text ("]");
    1077              :       }
    1078           56 :       xp.append (std::move (option_span));
    1079           56 :     }
    1080              : 
    1081          106 :   gcc_assert (xp.get_num_open_tags () == depth_within_message_div);
    1082              : 
    1083          106 :   xp.pop_tag ("div");
    1084              : 
    1085          106 :   gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
    1086              : 
    1087              :   /* Show any logical location.  */
    1088          106 :   if (auto logical_loc_mgr = get_logical_loc_mgr ())
    1089          102 :     if (auto client_data_hooks = m_context.get_client_data_hooks ())
    1090          102 :       if (auto logical_loc = client_data_hooks->get_current_logical_location ())
    1091           99 :         if (logical_loc != m_last_logical_location)
    1092              :           {
    1093           70 :             enum logical_locations::kind kind
    1094           70 :               = logical_loc_mgr->get_kind (logical_loc);;
    1095           70 :             if (const char *label = get_label_for_logical_location_kind (kind))
    1096              :               {
    1097           70 :                 label_text name_with_scope
    1098           70 :                   = logical_loc_mgr->get_name_with_scope (logical_loc);
    1099           70 :                 if (name_with_scope.get ())
    1100          140 :                   add_labelled_value (xp, "logical-location",
    1101          140 :                                       label, name_with_scope.get (), true);
    1102           70 :               }
    1103           70 :             m_last_logical_location = logical_loc;
    1104              :           }
    1105              : 
    1106              :   /* Show any physical location.  */
    1107          106 :   const expanded_location s
    1108          106 :     = diagnostic_expand_location (&diagnostic);
    1109          106 :   if (s != m_last_expanded_location
    1110          106 :       || alert)
    1111              :     {
    1112           92 :       if (s.file
    1113           88 :           && (s.file != m_last_expanded_location.file
    1114           75 :               || alert))
    1115           88 :         add_labelled_value (xp, "file", "File", s.file, false);
    1116           92 :       if (s.line)
    1117              :         {
    1118           88 :           add_labelled_value (xp, "line", "Line", std::to_string (s.line), false);
    1119           88 :           column_policy col_policy (m_context);
    1120           88 :           int converted_column = col_policy.converted_column (s);
    1121           88 :           if (converted_column >= 0)
    1122           88 :             add_labelled_value (xp, "column", "Column",
    1123          176 :                                 std::to_string (converted_column),
    1124              :                                 false);
    1125              :         }
    1126           92 :       if (s.file)
    1127           88 :         m_last_expanded_location = s;
    1128              :     }
    1129              : 
    1130              :   /* Source (and fix-it hints).  */
    1131          106 :   {
    1132              :     // TODO: m_context.m_last_location should be moved into the sink
    1133          106 :     location_t saved = m_context.m_last_location;
    1134          106 :     m_context.m_last_location = m_last_location;
    1135          106 :     m_context.maybe_show_locus_as_html
    1136          106 :       (*diagnostic.m_richloc,
    1137          106 :        m_context.get_source_printing_options (),
    1138          106 :        diagnostic.m_kind,
    1139              :        xp,
    1140              :        nullptr,
    1141              :        nullptr);
    1142          106 :     m_context.m_last_location = saved;
    1143          106 :     m_last_location = m_context.m_last_location;
    1144              :   }
    1145              : 
    1146          106 :   gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
    1147              : 
    1148              :   /* Execution path.  */
    1149          106 :   if (auto path = diagnostic.m_richloc->get_path ())
    1150              :     {
    1151            3 :       xp.push_tag ("div");
    1152            3 :       xp.set_attr ("id", "execution-path");
    1153              : 
    1154            3 :       xp.push_tag ("label", true);
    1155            3 :       const int num_events = path->num_events ();
    1156            3 :       pretty_printer pp;
    1157            3 :       pp_printf_n (&pp, num_events,
    1158              :                    "Execution path with %i event",
    1159              :                    "Execution path with %i events",
    1160              :                    num_events);
    1161            3 :       xp.add_text_from_pp (pp);
    1162            3 :       xp.pop_tag ("label");
    1163              : 
    1164            3 :       std::string event_id_prefix (diag_id + "-event-");
    1165            3 :       html_path_label_writer event_label_writer (xp, *this, *path,
    1166            3 :                                                  event_id_prefix);
    1167              : 
    1168            3 :       source_print_policy dspp (m_context);
    1169            3 :       print_path_as_html (xp, *path, m_context, &event_label_writer,
    1170              :                           dspp);
    1171              : 
    1172            3 :       xp.pop_tag ("div");
    1173            3 :     }
    1174              : 
    1175          106 :   gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
    1176              : 
    1177              :   // Try to display any per-diagnostic graphs
    1178          106 :   if (diagnostic.m_metadata)
    1179            4 :     if (auto ldg = diagnostic.m_metadata->get_lazy_digraphs ())
    1180              :       {
    1181            1 :         auto &digraphs = ldg->get_or_create ();
    1182            3 :         for (auto &dg : digraphs)
    1183            2 :           add_graph (*dg, *xp.get_insertion_point ());
    1184              :       }
    1185              : 
    1186          106 :   if (auto patch_element = make_element_for_patch (diagnostic))
    1187              :     {
    1188           34 :       xp.push_tag ("div");
    1189           34 :       xp.set_attr ("id", "suggested-fix");
    1190           34 :       xp.push_tag ("label", true);
    1191           34 :       xp.add_text ("Suggested fix");
    1192           34 :       xp.pop_tag ("label");
    1193           34 :       xp.append (std::move (patch_element));
    1194           34 :       xp.pop_tag ("div");
    1195          106 :     }
    1196              : 
    1197          106 :   return diag_element;
    1198          106 : }
    1199              : 
    1200              : std::unique_ptr<xml::element>
    1201          106 : html_builder::make_element_for_patch (const diagnostic_info &diagnostic)
    1202              : {
    1203          106 :   changes::change_set edit (m_context.get_file_cache ());
    1204          106 :   edit.add_fixits (diagnostic.m_richloc);
    1205          106 :   if (char *diff = edit.generate_diff (true))
    1206              :     {
    1207          106 :       if (strlen (diff) > 0)
    1208              :         {
    1209           34 :           auto element = std::make_unique<xml::element> ("pre", true);
    1210           34 :           element->set_attr ("class", "gcc-generated-patch");
    1211           34 :           element->add_text (diff);
    1212           34 :           free (diff);
    1213           34 :           return element;
    1214              :         }
    1215              :       else
    1216           72 :         free (diff);
    1217              :     }
    1218           72 :   return nullptr;
    1219          106 : }
    1220              : 
    1221              : std::unique_ptr<xml::element>
    1222           11 : html_builder::make_metadata_element (label_text label,
    1223              :                                      label_text url)
    1224              : {
    1225           11 :   auto item = make_span ("gcc-metadata-item");
    1226           11 :   xml::printer xp (*item.get ());
    1227           11 :   xp.add_text ("[");
    1228           11 :   {
    1229           11 :     if (url.get ())
    1230              :       {
    1231           11 :         xp.push_tag ("a", true);
    1232           11 :         xp.set_attr ("href", url.get ());
    1233              :       }
    1234           11 :     xp.add_text (label.get ());
    1235           11 :     if (url.get ())
    1236           11 :       xp.pop_tag ("a");
    1237              :   }
    1238           11 :   xp.add_text ("]");
    1239           11 :   return item;
    1240           11 : }
    1241              : 
    1242              : std::unique_ptr<xml::element>
    1243           12 : html_builder::make_element_for_metadata (const metadata &m)
    1244              : {
    1245           12 :   auto span_metadata = make_span ("gcc-metadata");
    1246              : 
    1247           12 :   int cwe = m.get_cwe ();
    1248           12 :   if (cwe)
    1249              :     {
    1250            6 :       pretty_printer pp;
    1251            6 :       pp_printf (&pp, "CWE-%i", cwe);
    1252            6 :       label_text label = label_text::take (xstrdup (pp_formatted_text (&pp)));
    1253            6 :       label_text url = label_text::take (get_cwe_url (cwe));
    1254            6 :       span_metadata->add_child
    1255           18 :         (make_metadata_element (std::move (label), std::move (url)));
    1256            6 :     }
    1257              : 
    1258           17 :   for (unsigned idx = 0; idx < m.get_num_rules (); ++idx)
    1259              :     {
    1260            5 :       auto &rule = m.get_rule (idx);
    1261            5 :       label_text label = label_text::take (rule.make_description ());
    1262            5 :       label_text url = label_text::take (rule.make_url ());
    1263            5 :       span_metadata->add_child
    1264           15 :         (make_metadata_element (std::move (label), std::move (url)));
    1265            5 :     }
    1266              : 
    1267           12 :   if (span_metadata->m_children.empty ())
    1268            2 :     return nullptr;
    1269              : 
    1270           10 :   return span_metadata;
    1271           12 : }
    1272              : 
    1273              : /* Implementation of diagnostics::context::m_diagrams.m_emission_cb
    1274              :    for HTML output.  */
    1275              : 
    1276              : void
    1277            0 : html_builder::emit_diagram (const diagram &)
    1278              : {
    1279              :   /* We must be within the emission of a top-level diagnostic.  */
    1280            0 :   gcc_assert (m_cur_diagnostic_element);
    1281              : 
    1282              :   // TODO: currently a no-op
    1283            0 : }
    1284              : 
    1285              : void
    1286           48 : html_builder::add_graph (const digraphs::digraph &dg,
    1287              :                          xml::element &parent_element)
    1288              : {
    1289           48 :   auto div = std::make_unique<xml::element> ("div", false);
    1290           48 :   div->set_attr ("class", "gcc-directed-graph");
    1291           48 :   xml::printer xp (*div);
    1292              : 
    1293           48 :   if (m_html_gen_opts.m_show_graph_sarif)
    1294              :     {
    1295              :       // For debugging, show the SARIF src inline:
    1296            0 :       pretty_printer pp;
    1297            0 :       dg.make_json_sarif_graph ()->print (&pp, true);
    1298            0 :       print_pre_source (xp, pp_formatted_text (&pp));
    1299            0 :     }
    1300              : 
    1301           48 :   if (auto dot_graph = dg.make_dot_graph ())
    1302              :     {
    1303           48 :       if (m_html_gen_opts.m_show_graph_dot_src)
    1304              :         {
    1305              :           // For debugging, show the dot src inline:
    1306            0 :           pretty_printer pp;
    1307            0 :           dot::writer w (pp);
    1308            0 :           dot_graph->print (w);
    1309            0 :           print_pre_source (xp, pp_formatted_text (&pp));
    1310            0 :         }
    1311              : 
    1312           48 :       if (auto svg_element = dot::make_svg_from_graph (*dot_graph))
    1313              :         {
    1314           48 :           if (const char *description = dg.get_description ())
    1315              :             {
    1316           48 :               xp.push_tag ("h2", true);
    1317           48 :               xp.add_text (description);
    1318           48 :               xp.pop_tag ("h2");
    1319              :             }
    1320           48 :           xp.append (std::move (svg_element));
    1321           48 :           parent_element.add_child (std::move (div));
    1322           48 :         }
    1323           48 :     }
    1324           48 : }
    1325              : 
    1326              : void
    1327            1 : html_builder::emit_global_graph (const lazily_created<digraphs::digraph> &ldg)
    1328              : {
    1329            1 :   auto &dg = ldg.get_or_create ();
    1330            1 :   gcc_assert (m_body_element);
    1331            1 :   add_graph (dg, *m_body_element);
    1332            1 : }
    1333              : 
    1334              : void
    1335           45 : html_builder::
    1336              : add_graph_for_logical_loc (const lazily_created<digraphs::digraph> &ldg,
    1337              :                            logical_locations::key logical_loc)
    1338              : {
    1339           45 :   gcc_assert (m_body_element);
    1340              : 
    1341           45 :   auto iter = m_per_logical_loc_graphs.find (logical_loc);
    1342           45 :   if (iter == m_per_logical_loc_graphs.end ())
    1343              :     {
    1344            1 :       auto logical_loc_element = make_div ("gcc-logical-location");
    1345            1 :       iter = m_per_logical_loc_graphs.insert ({logical_loc,
    1346            1 :           logical_loc_element.get ()}).first;
    1347            1 :       m_body_element->add_child (std::move (logical_loc_element));
    1348            1 :     }
    1349              : 
    1350           45 :   auto &dg = ldg.get_or_create ();
    1351           45 :   add_graph (dg, *iter->second);
    1352           45 : }
    1353              : 
    1354              : /* Implementation of "end_group_cb" for HTML output.  */
    1355              : 
    1356              : void
    1357           77 : html_builder::end_group ()
    1358              : {
    1359           77 :   if (m_cur_diagnostic_element)
    1360           77 :     m_diagnostics_element->add_child (std::move (m_cur_diagnostic_element));
    1361           77 : }
    1362              : 
    1363              : /* Create a top-level object, and add it to all the results
    1364              :    (and other entities) we've seen so far.
    1365              : 
    1366              :    Flush it all to OUTF.  */
    1367              : 
    1368              : void
    1369           14 : html_builder::flush_to_file (FILE *outf)
    1370              : {
    1371           14 :   DIAGNOSTICS_LOG_SCOPE_PRINTF0 (m_context.get_logger (),
    1372           14 :                                  "diagnostics::html_builder::flush_to_file");
    1373           14 :   if (m_html_gen_opts.m_javascript)
    1374              :     {
    1375            0 :       gcc_assert (m_head_element);
    1376            0 :       xml::printer xp (*m_head_element);
    1377              :       /* Add an initialization of the global js variable "focus_ids"
    1378              :          using the array of IDs we saved as we went.  */
    1379            0 :       xp.push_tag ("script");
    1380            0 :       pretty_printer pp;
    1381            0 :       pp_string (&pp, "focus_ids = ");
    1382            0 :       m_ui_focus_ids.print (&pp, true);
    1383            0 :       pp_string (&pp, ";\n");
    1384            0 :       xp.add_raw (pp_formatted_text (&pp));
    1385            0 :       xp.pop_tag ("script");
    1386            0 :     }
    1387           14 :   auto top = m_document.get ();
    1388           14 :   top->dump (outf);
    1389           14 :   fprintf (outf, "\n");
    1390           14 : }
    1391              : 
    1392              : class html_sink : public sink
    1393              : {
    1394              : public:
    1395           22 :   ~html_sink ()
    1396           22 :   {
    1397              :     /* Any diagnostics should have been handled by now.
    1398              :        If not, then something's gone wrong with diagnostic
    1399              :        groupings.  */
    1400           22 :     std::unique_ptr<xml::element> pending_diag
    1401           22 :       = m_builder.take_current_diagnostic ();
    1402           22 :     gcc_assert (!pending_diag);
    1403           22 :   }
    1404              : 
    1405            0 :   void dump (FILE *out, int indent) const override
    1406              :   {
    1407            0 :     sink::dump (out, indent);
    1408            0 :     dumping::emit_heading (out, indent, "html_builder");
    1409            0 :     m_builder.dump (out, indent + 2);
    1410            0 :   }
    1411              : 
    1412              :   void
    1413           22 :   set_main_input_filename (const char *name) final override
    1414              :   {
    1415           22 :     m_builder.set_main_input_filename (name);
    1416           14 :   }
    1417              : 
    1418              :   std::unique_ptr<per_sink_buffer>
    1419            0 :   make_per_sink_buffer () final override
    1420              :   {
    1421            0 :     return std::make_unique<html_sink_buffer> (m_builder);
    1422              :   }
    1423            0 :   void set_buffer (per_sink_buffer *base_buffer) final override
    1424              :   {
    1425            0 :     html_sink_buffer *buffer
    1426              :       = static_cast<html_sink_buffer *> (base_buffer);
    1427            0 :     m_buffer = buffer;
    1428            0 :   }
    1429              : 
    1430           77 :   void on_begin_group () final override
    1431              :   {
    1432              :     /* No-op,  */
    1433           77 :   }
    1434           77 :   void on_end_group () final override
    1435              :   {
    1436           77 :     m_builder.end_group ();
    1437           77 :   }
    1438              :   void
    1439          106 :   on_report_diagnostic (const diagnostic_info &diagnostic,
    1440              :                         enum kind orig_diag_kind) final override
    1441              :   {
    1442          106 :     DIAGNOSTICS_LOG_SCOPE_PRINTF0
    1443              :       (get_logger (),
    1444          106 :        "diagnostics::html_sink::on_report_diagnostic");
    1445          106 :     m_builder.on_report_diagnostic (diagnostic, orig_diag_kind, m_buffer);
    1446          106 :   }
    1447            0 :   void on_diagram (const diagram &d) final override
    1448              :   {
    1449            0 :     m_builder.emit_diagram (d);
    1450            0 :   }
    1451          103 :   void after_diagnostic (const diagnostic_info &) final override
    1452              :   {
    1453              :     /* No-op, but perhaps could show paths here.  */
    1454          103 :   }
    1455            0 :   bool follows_reference_printer_p () const final override
    1456              :   {
    1457            0 :     return false;
    1458              :   }
    1459           22 :   void update_printer () final override
    1460              :   {
    1461           22 :     m_printer = m_context.clone_printer ();
    1462              : 
    1463              :     /* Don't colorize the text.  */
    1464           22 :     pp_show_color (m_printer.get ()) = false;
    1465              : 
    1466              :     /* No textual URLs.  */
    1467           22 :     m_printer->set_url_format (URL_FORMAT_NONE);
    1468              : 
    1469              :     /* Update the builder to use the new printer.  */
    1470           22 :     m_builder.set_printer (*get_printer ());
    1471           22 :   }
    1472              : 
    1473              :   void
    1474            1 :   report_global_digraph (const lazily_created<digraphs::digraph> &ldg)
    1475              :     final override
    1476              :   {
    1477            1 :     m_builder.emit_global_graph (ldg);
    1478            1 :   }
    1479              : 
    1480              :   void
    1481           45 :   report_digraph_for_logical_location (const lazily_created<digraphs::digraph> &ldg,
    1482              :                                        logical_locations::key logical_loc) final override
    1483              :   {
    1484           45 :     m_builder.add_graph_for_logical_loc (ldg, logical_loc);
    1485           45 :   }
    1486              : 
    1487            4 :   const xml::document &get_document () const
    1488              :   {
    1489            4 :     return m_builder.get_document ();
    1490              :   }
    1491              : 
    1492            4 :   html_builder &get_builder () { return m_builder; }
    1493              : 
    1494              : protected:
    1495           22 :   html_sink (context &dc,
    1496              :              const line_maps *line_maps,
    1497              :              const html_generation_options &html_gen_opts)
    1498           22 :   : sink (dc),
    1499           22 :     m_builder (dc, *get_printer (), line_maps, html_gen_opts),
    1500           22 :     m_buffer (nullptr)
    1501           22 :   {}
    1502              : 
    1503              :   html_builder m_builder;
    1504              :   html_sink_buffer *m_buffer;
    1505              : };
    1506              : 
    1507              : class html_file_sink : public html_sink
    1508              : {
    1509              : public:
    1510           14 :   html_file_sink (context &dc,
    1511              :                   const line_maps *line_maps,
    1512              :                   const html_generation_options &html_gen_opts,
    1513              :                   output_file output_file_)
    1514           14 :   : html_sink (dc, line_maps, html_gen_opts),
    1515           14 :     m_output_file (std::move (output_file_))
    1516              :   {
    1517           14 :     gcc_assert (m_output_file.get_open_file ());
    1518           14 :     gcc_assert (m_output_file.get_filename ());
    1519           14 :   }
    1520           28 :   ~html_file_sink ()
    1521           14 :   {
    1522           14 :     m_builder.flush_to_file (m_output_file.get_open_file ());
    1523           28 :   }
    1524            0 :   void dump_kind (FILE *out) const override
    1525              :   {
    1526            0 :     fprintf (out, "html_file_sink: %s",
    1527              :              m_output_file.get_filename ());
    1528            0 :   }
    1529            7 :   bool machine_readable_stderr_p () const final override
    1530              :   {
    1531            7 :     return false;
    1532              :   }
    1533              : 
    1534              : private:
    1535              :   output_file m_output_file;
    1536              : };
    1537              : 
    1538              : /* Attempt to open BASE_FILE_NAME.html for writing.
    1539              :    Return a non-null output_file,
    1540              :    or return a null output_file and complain to DC
    1541              :    using LINE_MAPS.  */
    1542              : 
    1543              : output_file
    1544           14 : open_html_output_file (context &dc,
    1545              :                        line_maps *line_maps,
    1546              :                        const char *base_file_name)
    1547              : {
    1548           14 :   if (!base_file_name)
    1549              :     {
    1550            0 :       rich_location richloc (line_maps, UNKNOWN_LOCATION);
    1551            0 :       dc.emit_diagnostic_with_group
    1552            0 :         (kind::error, richloc, nullptr, 0,
    1553              :          "unable to determine filename for HTML output");
    1554            0 :       return output_file ();
    1555            0 :     }
    1556              : 
    1557           14 :   label_text filename = label_text::take (concat (base_file_name,
    1558              :                                                   ".html",
    1559           14 :                                                   nullptr));
    1560           14 :   FILE *outf = fopen (filename.get (), "w");
    1561           14 :   if (!outf)
    1562              :     {
    1563            0 :       rich_location richloc (line_maps, UNKNOWN_LOCATION);
    1564            0 :       dc.emit_diagnostic_with_group
    1565            0 :         (kind::error, richloc, nullptr, 0,
    1566              :          "unable to open %qs for HTML output: %m",
    1567              :          filename.get ());
    1568            0 :       return output_file ();
    1569            0 :     }
    1570           14 :   return output_file (outf, true, std::move (filename));
    1571           14 : }
    1572              : 
    1573              : std::unique_ptr<sink>
    1574           14 : make_html_sink (context &dc,
    1575              :                 const line_maps &line_maps,
    1576              :                 const html_generation_options &html_gen_opts,
    1577              :                 output_file output_file_)
    1578              : {
    1579           14 :   auto sink
    1580              :     = std::make_unique<html_file_sink> (dc,
    1581           28 :                                         &line_maps,
    1582              :                                         html_gen_opts,
    1583           14 :                                         std::move (output_file_));
    1584           14 :   sink->update_printer ();
    1585           14 :   return sink;
    1586           14 : }
    1587              : 
    1588              : #if CHECKING_P
    1589              : 
    1590              : namespace selftest {
    1591              : 
    1592              : /* Helper for writing tests of html_token_printer.
    1593              :    Printing to m_pp will appear as HTML within m_top_element, a <div>.  */
    1594              : 
    1595              : struct token_printer_test
    1596              : {
    1597           12 :   token_printer_test ()
    1598           12 :   : m_top_element ("div", true),
    1599           12 :     m_tok_printer (m_top_element)
    1600              :   {
    1601           12 :     m_pp.set_token_printer (&m_tok_printer);
    1602           12 :   }
    1603              : 
    1604              :   xml::element m_top_element;
    1605              :   html_token_printer m_tok_printer;
    1606              :   pretty_printer m_pp;
    1607              : };
    1608              : 
    1609              : static void
    1610            4 : test_token_printer ()
    1611              : {
    1612            4 :   {
    1613            4 :     token_printer_test t;
    1614            4 :     pp_printf (&t.m_pp, "hello world");
    1615            4 :     ASSERT_XML_PRINT_EQ
    1616              :       (t.m_top_element,
    1617              :        "<div>hello world</div>\n");
    1618            4 :   }
    1619              : 
    1620            4 :   {
    1621            4 :     token_printer_test t;
    1622            4 :     pp_printf (&t.m_pp, "%qs: %qs", "foo", "bar");
    1623            4 :     ASSERT_XML_PRINT_EQ
    1624              :       (t.m_top_element,
    1625              :        "<div>"
    1626              :        "`"
    1627              :        "<span class=\"gcc-quoted-text\">"
    1628              :        "foo"
    1629              :        "</span>"
    1630              :        "&apos;: `"
    1631              :        "<span class=\"gcc-quoted-text\">"
    1632              :        "bar"
    1633              :        "</span>"
    1634              :        "&apos;"
    1635              :        "</div>\n");
    1636            4 :   }
    1637              : 
    1638            4 :   {
    1639            4 :     token_printer_test t;
    1640            4 :     paths::event_id_t event_id (0);
    1641            4 :     pp_printf (&t.m_pp, "foo %@ bar", &event_id);
    1642            4 :     ASSERT_XML_PRINT_EQ
    1643              :       (t.m_top_element,
    1644              :        "<div>foo (1) bar</div>\n");
    1645            4 :   }
    1646            4 : }
    1647              : 
    1648              : /* A subclass of html_sink for writing selftests.
    1649              :    The XML output is cached internally, rather than written
    1650              :    out to a file.  */
    1651              : 
    1652           16 : class test_html_context : public test_context
    1653              : {
    1654              : public:
    1655            8 :   test_html_context ()
    1656            8 :   {
    1657            8 :     html_generation_options html_gen_opts;
    1658            8 :     html_gen_opts.m_css = false;
    1659            8 :     html_gen_opts.m_javascript = false;
    1660            8 :     auto sink = std::make_unique<html_buffered_sink> (*this,
    1661              :                                                       line_table,
    1662            8 :                                                       html_gen_opts);
    1663            8 :     sink->update_printer ();
    1664            8 :     sink->set_main_input_filename ("(main input filename)");
    1665            8 :     m_format = sink.get (); // borrowed
    1666              : 
    1667            8 :     set_sink (std::move (sink));
    1668            8 :   }
    1669              : 
    1670            4 :   const xml::document &get_document () const
    1671              :   {
    1672            4 :     return m_format->get_document ();
    1673              :   }
    1674              : 
    1675            4 :   html_builder &get_builder () const
    1676              :   {
    1677            4 :     return m_format->get_builder ();
    1678              :   }
    1679              : 
    1680              : private:
    1681              :   class html_buffered_sink : public html_sink
    1682              :   {
    1683              :   public:
    1684            8 :     html_buffered_sink (context &dc,
    1685              :                         const line_maps *line_maps,
    1686              :                         const html_generation_options &html_gen_opts)
    1687            8 :     : html_sink (dc, line_maps, html_gen_opts)
    1688              :     {
    1689              :     }
    1690            0 :     void dump_kind (FILE *out) const final override
    1691              :     {
    1692            0 :       fprintf (out, "html_buffered_sink");
    1693            0 :     }
    1694            0 :     bool machine_readable_stderr_p () const final override
    1695              :     {
    1696            0 :       return true;
    1697              :     }
    1698              :   };
    1699              : 
    1700              :   html_sink *m_format; // borrowed
    1701              : };
    1702              : 
    1703              : /* Test of reporting a diagnostic at UNKNOWN_LOCATION to a
    1704              :    diagnostics::context and examining the generated XML document.
    1705              :    Verify various basic properties. */
    1706              : 
    1707              : static void
    1708            4 : test_simple_log ()
    1709              : {
    1710            4 :   test_html_context dc;
    1711              : 
    1712            4 :   rich_location richloc (line_table, UNKNOWN_LOCATION);
    1713            4 :   dc.report (kind::error, richloc, nullptr, 0, "this is a test: %qs", "foo");
    1714              : 
    1715            4 :   const xml::document &doc  = dc.get_document ();
    1716              : 
    1717            4 :   ASSERT_XML_PRINT_EQ
    1718              :     (doc,
    1719              :      ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
    1720              :       "<!DOCTYPE html\n"
    1721              :       "     PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n"
    1722              :       "     \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
    1723              :       "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
    1724              :       "  <head>\n"
    1725              :       "    <title>(main input filename)</title>\n"
    1726              :       "  </head>\n"
    1727              :       "  <body>\n"
    1728              :       "    <div class=\"gcc-diagnostic-list\">\n"
    1729              :       "      <div class=\"alert alert-danger\" id=\"gcc-diag-0\">\n"
    1730              :       "        <span class=\"pficon pficon-error-circle-o\"> </span>\n"
    1731              :       "        <div class=\"gcc-message\" id=\"gcc-diag-0-message\"><strong>error: </strong> this is a test: `<span class=\"gcc-quoted-text\">foo</span>&apos;</div>\n"
    1732              :       "      </div>\n"
    1733              :       "    </div>\n"
    1734              :       "  </body>\n"
    1735              :       "</html>\n"));
    1736            4 : }
    1737              : 
    1738              : static void
    1739            4 : test_metadata ()
    1740              : {
    1741            4 :   test_html_context dc;
    1742            4 :   html_builder &b = dc.get_builder ();
    1743              : 
    1744            4 :   {
    1745            4 :     metadata m;
    1746            4 :     m.add_cwe (415);
    1747            4 :     auto element = b.make_element_for_metadata (m);
    1748            4 :     ASSERT_XML_PRINT_EQ
    1749              :       (*element,
    1750              :        "<span class=\"gcc-metadata\">"
    1751              :        "<span class=\"gcc-metadata-item\">"
    1752              :        "["
    1753              :        "<a href=\"https://cwe.mitre.org/data/definitions/415.html\">"
    1754              :        "CWE-415"
    1755              :        "</a>"
    1756              :        "]"
    1757              :        "</span>"
    1758              :        "</span>\n");
    1759            4 :   }
    1760              : 
    1761            4 :   {
    1762            4 :     metadata m;
    1763            4 :     metadata::precanned_rule rule ("MISC-42",
    1764            4 :                                    "http://example.com");
    1765            4 :     m.add_rule (rule);
    1766            4 :     auto element = b.make_element_for_metadata (m);
    1767            4 :     ASSERT_XML_PRINT_EQ
    1768              :       (*element,
    1769              :        "<span class=\"gcc-metadata\">"
    1770              :        "<span class=\"gcc-metadata-item\">"
    1771              :        "["
    1772              :        "<a href=\"http://example.com\">"
    1773              :        "MISC-42"
    1774              :        "</a>"
    1775              :        "]"
    1776              :        "</span>"
    1777              :        "</span>\n");
    1778            4 :   }
    1779            4 : }
    1780              : 
    1781              : /* Run all of the selftests within this file.  */
    1782              : 
    1783              : void
    1784            4 : html_sink_cc_tests ()
    1785              : {
    1786            4 :   ::selftest::auto_fix_quotes fix_quotes;
    1787            4 :   test_token_printer ();
    1788            4 :   test_simple_log ();
    1789            4 :   test_metadata ();
    1790            4 : }
    1791              : 
    1792              : } // namespace selftest
    1793              : 
    1794              : #endif /* CHECKING_P */
    1795              : 
    1796              : } // namespace diagnostics
        

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.