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