The patch generalizes diagnostic-show-locus.c so that it can print
HTML, as well as the existing text output.
It uses this to implement a new -fdiagnostics-path-format=html
option, in which diagnostic paths are written out as HTML files
of the form "DUMP_BASE_NAME.path-[0-9]+.html"

For example:

LANG=C ./xgcc -B. -fanalyzer -c test.c -fdiagnostics-path-format=html
test.c: In function 'wrapped_free':
test.c:11:3: warning: double-'free' of 'ptr' [CWE-415] [-Wanalyzer-double-free]
   11 |   free (ptr);
      |   ^~~~~~~~~~
test.c:11:3: note: path with 23 events written to 'test.c.path-1.html'

which moves the verbose path information from stderr to the HTML file.

I've uploaded the generated HTML for that example to:
  
https://dmalcolm.fedorapeople.org/gcc/2020-10-22/html-examples/test.c.path-1.html
and other examples can be seen in that directory.

The interprocedural calls and returns are visualized in the generated
HTML by using embedded SVG elements to generate arrows.

There is a little JavaScript to allow for using the "j" and "k" keys
to go backwards and forwards through the events in the path.

Currently it merely emits the path information; it doesn't capture the
associated diagnostic; perhaps this should be a diagnostic format option
instead?  As is, the only HTML that's emitted are the paths for
diagnostics that have a path, so any result summarization script that
tries to scrape the build tree looking for HTML files is going to
miss diagnostics without a path.

Also, there are various places that could be improved.  For example,
LTO paths where the filename changes are currently printed rather
plainly:
  
https://dmalcolm.fedorapeople.org/gcc/2020-10-22/html-examples/a.wpa.path-1.html
Help with HTML, JS and CSS would be appreciated.

Tested lightly on Firefox, Chromium, and lynx.
Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.

Thoughts?

gcc/ChangeLog:
        * common.opt (diagnostic_path_format): Add DPF_HTML.
        * diagnostic-show-locus.c (colorizer::m_context): Replace with...
        (colorizer::m_pp): ...this new field.
        (layout::m_context): Make const.
        (layout::m_is_html): New field.
        (layout::m_html_writer): New field.
        (colorizer::colorizer): Update for use of pp rather than context.
        (colorizer::begin_state): Likewise.
        (colorizer::finish_state): Likewise.
        (colorizer::get_color_by_name): Likewise.
        (layout::layout): Make "context" param const.  Add "pp", "is_html"
        and "writer" params, with defaults.  Use them to initialize new
        fields.  Update for change to m_colorizer.
        (layout::print_source_line): Add HTML support.
        (layout::start_annotation_line): Likewise.
        (layout::print_annotation_line): Likewise.
        (line_label::line_label): Make context const.
        (layout::print_any_labels): Add HTML support.
        (get_affected_range): Make context const.
        (get_printed_columns): Likewise.
        (line_corrections::line_corrections): Likewise.
        (line_corrections::m_context): Likewise.
        (layout::print_line): Don't print fix-it hints for HTML support.
        (diagnostic_show_locus_as_html): New.
        (class selftest::html_printer): New.
        (selftest::assert_html_eq): New.
        (ASSERT_HTML_EQ): New.
        (selftest::test_one_liner_simple_caret): Verify the HTML output.
        (selftest::test_diagnostic_show_locus_fixit_lines): Likewise.
        (selftest::test_html): New.
        (selftest::diagnostic_show_locus_c_tests): Call it.
        * diagnostic.c (diagnostic_show_any_path): Pass the location to
        the print_path callback.
        * diagnostic.h (enum diagnostic_path_format): Add DPF_HTML.
        (diagnostic_context::num_html_paths): New field.
        (diagnostic_context::print_path): Add location_t param.
        (class html_writer): New.
        (diagnostic_show_locus_as_html): New decl.
        * doc/invoke.texi (Diagnostic Message Formatting Options): Add
        "html" to -fdiagnostics-path-format=.
        (-fdiagnostics-path-format=): Add html.
        * pretty-print.c (pp_write_text_as_html_to_stream): New.
        * pretty-print.h (pp_write_text_as_html_to_stream): New decl.
        * tree-diagnostic-path.cc: Define GCC_DIAG_STYLE where possible
        and necessary.  Include "options.h" for dump_base_name.
        (path_label::path_label): Add "colorize" param and use it to
        initialize m_colorize.
        (path_label::get_text): Use m_colorizer rather than accessing
        global_dc's printer.
        (path_label::m_colorize): New field.
        (event_range::event_range): Add "colorize" param and use it to
        initialize m_path_label.
        (event_range::print_as_html): New.
        (event_range::get_filename): New.
        (path_summary::path_summary): Add "colorize" param and use it when
        creating event_ranges.
        (class html_path_writer): New.
        (path_summary::print_as_html): New.
        (HTML_STYLE): New.
        (HTML_SCRIPT): New.
        (write_html_for_path): New.
        (default_tree_diagnostic_path_printer): Add "loc" param.
        Update DPF_INLINE_EVENTS for new "colorize" param of
        path_summary's ctor.  Add DPF_HTML.
        (selftest::test_empty_path): Update for new param of path_summary
        ctor.
        (selftest::test_intraprocedural_path): Likewise.
        (selftest::test_interprocedural_path_1): Likewise.
        (selftest::test_interprocedural_path_2): Likewise.
        (selftest::test_recursion): Likewise.
        * tree-diagnostic.h (default_tree_diagnostic_path_printer): Add
        location_t param.

gcc/testsuite/ChangeLog:
        * gcc.dg/plugin/diagnostic-path-format-html.c: New test.
        * gcc.dg/plugin/plugin.exp (plugin_test_list): Add it.
---
 gcc/common.opt                                |   3 +
 gcc/diagnostic-show-locus.c                   | 344 ++++++++++++++--
 gcc/diagnostic.c                              |   2 +-
 gcc/diagnostic.h                              |  23 +-
 gcc/doc/invoke.texi                           |  11 +-
 gcc/pretty-print.c                            |  43 ++
 gcc/pretty-print.h                            |   1 +
 .../plugin/diagnostic-path-format-html.c      |  45 +++
 gcc/testsuite/gcc.dg/plugin/plugin.exp        |   3 +-
 gcc/tree-diagnostic-path.cc                   | 368 +++++++++++++++++-
 gcc/tree-diagnostic.h                         |   3 +-
 11 files changed, 781 insertions(+), 65 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-html.c

diff --git a/gcc/common.opt b/gcc/common.opt
index 7e789d1c47f..c520d5d86e9 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1398,6 +1398,9 @@ Enum(diagnostic_path_format) String(separate-events) 
Value(DPF_SEPARATE_EVENTS)
 EnumValue
 Enum(diagnostic_path_format) String(inline-events) Value(DPF_INLINE_EVENTS)
 
+EnumValue
+Enum(diagnostic_path_format) String(html) Value(DPF_HTML)
+
 fdiagnostics-show-path-depths
 Common Var(flag_diagnostics_show_path_depths) Init(0)
 Show stack depths of events in paths.
diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
index da3c5b6a92d..a37d7ae3dc0 100644
--- a/gcc/diagnostic-show-locus.c
+++ b/gcc/diagnostic-show-locus.c
@@ -83,7 +83,7 @@ struct point_state
 class colorizer
 {
  public:
-  colorizer (diagnostic_context *context,
+  colorizer (pretty_printer *pp,
             diagnostic_t diagnostic_kind);
   ~colorizer ();
 
@@ -113,7 +113,7 @@ class colorizer
   static const int STATE_FIXIT_INSERT  = -2;
   static const int STATE_FIXIT_DELETE  = -3;
 
-  diagnostic_context *m_context;
+  pretty_printer *m_pp;
   diagnostic_t m_diagnostic_kind;
   int m_current_state;
   const char *m_range1;
@@ -324,9 +324,12 @@ test_line_span ()
 class layout
 {
  public:
-  layout (diagnostic_context *context,
+  layout (const diagnostic_context *context,
          rich_location *richloc,
-         diagnostic_t diagnostic_kind);
+         diagnostic_t diagnostic_kind,
+         pretty_printer *pp = NULL,
+         bool is_html = false,
+         html_writer *writer = NULL);
 
   bool maybe_add_location_range (const location_range *loc_range,
                                 unsigned original_idx,
@@ -384,8 +387,10 @@ class layout
   move_to_column (int *column, int dest_column, bool add_left_margin);
 
  private:
-  diagnostic_context *m_context;
+  const diagnostic_context *m_context;
   pretty_printer *m_pp;
+  bool m_is_html;
+  html_writer *m_html_writer;
   location_t m_primary_loc;
   exploc_with_display_col m_exploc;
   colorizer m_colorizer;
@@ -405,9 +410,9 @@ class layout
 /* The constructor for "colorizer".  Lookup and store color codes for the
    different kinds of things we might need to print.  */
 
-colorizer::colorizer (diagnostic_context *context,
-                     diagnostic_t diagnostic_kind) :
-  m_context (context),
+colorizer::colorizer (pretty_printer *pp,
+                     diagnostic_t diagnostic_kind)
+: m_pp (pp),
   m_diagnostic_kind (diagnostic_kind),
   m_current_state (STATE_NORMAL_TEXT)
 {
@@ -415,7 +420,7 @@ colorizer::colorizer (diagnostic_context *context,
   m_range2 = get_color_by_name ("range2");
   m_fixit_insert = get_color_by_name ("fixit-insert");
   m_fixit_delete = get_color_by_name ("fixit-delete");
-  m_stop_color = colorize_stop (pp_show_color (context->printer));
+  m_stop_color = colorize_stop (pp_show_color (m_pp));
 }
 
 /* The destructor for "colorize".  If colorization is on, print a code to
@@ -451,35 +456,35 @@ colorizer::begin_state (int state)
       break;
 
     case STATE_FIXIT_INSERT:
-      pp_string (m_context->printer, m_fixit_insert);
+      pp_string (m_pp, m_fixit_insert);
       break;
 
     case STATE_FIXIT_DELETE:
-      pp_string (m_context->printer, m_fixit_delete);
+      pp_string (m_pp, m_fixit_delete);
       break;
 
     case 0:
       /* Make range 0 be the same color as the "kind" text
         (error vs warning vs note).  */
       pp_string
-       (m_context->printer,
-        colorize_start (pp_show_color (m_context->printer),
+       (m_pp,
+        colorize_start (pp_show_color (m_pp),
                         diagnostic_get_color_for_kind (m_diagnostic_kind)));
       break;
 
     case 1:
-      pp_string (m_context->printer, m_range1);
+      pp_string (m_pp, m_range1);
       break;
 
     case 2:
-      pp_string (m_context->printer, m_range2);
+      pp_string (m_pp, m_range2);
       break;
 
     default:
       /* For ranges beyond 2, alternate between color 1 and color 2.  */
       {
        gcc_assert (state > 2);
-       pp_string (m_context->printer,
+       pp_string (m_pp,
                   state % 2 ? m_range1 : m_range2);
       }
       break;
@@ -492,7 +497,7 @@ void
 colorizer::finish_state (int state)
 {
   if (state != STATE_NORMAL_TEXT)
-    pp_string (m_context->printer, m_stop_color);
+    pp_string (m_pp, m_stop_color);
 }
 
 /* Get the color code for NAME (or the empty string if
@@ -501,7 +506,7 @@ colorizer::finish_state (int state)
 const char *
 colorizer::get_color_by_name (const char *name)
 {
-  return colorize_start (pp_show_color (m_context->printer), name);
+  return colorize_start (pp_show_color (m_pp), name);
 }
 
 /* Implementation of class layout_range.  */
@@ -961,14 +966,19 @@ fixit_cmp (const void *p_a, const void *p_b)
    Determine m_x_offset_display, to ensure that the primary caret
    will fit within the max_width provided by the diagnostic_context.  */
 
-layout::layout (diagnostic_context * context,
+layout::layout (const diagnostic_context *context,
                rich_location *richloc,
-               diagnostic_t diagnostic_kind)
+               diagnostic_t diagnostic_kind,
+               pretty_printer *pp,
+               bool is_html,
+               html_writer *writer)
 : m_context (context),
-  m_pp (context->printer),
+  m_pp (pp ? pp : context->printer),
+  m_is_html (is_html),
+  m_html_writer (writer),
   m_primary_loc (richloc->get_range (0)->m_loc),
   m_exploc (richloc->get_expanded_location (0), context->tabstop),
-  m_colorizer (context, diagnostic_kind),
+  m_colorizer (m_pp, diagnostic_kind),
   m_colorize_source_p (context->colorize_source_p),
   m_show_labels_p (context->show_labels_p),
   m_show_line_numbers_p (context->show_line_numbers_p),
@@ -1461,13 +1471,21 @@ layout::print_source_line (linenum_type row, const char 
*line, int line_bytes)
 {
   m_colorizer.set_normal_text ();
 
+  if (m_is_html)
+      pp_string (m_pp, "<tr>");
+
   pp_emit_prefix (m_pp);
   if (m_show_line_numbers_p)
     {
+      if (m_is_html)
+       pp_string (m_pp, "<td class=\"linenum\">");
       int width = num_digits (row);
       for (int i = 0; i < m_linenum_width - width; i++)
        pp_space (m_pp);
-      pp_printf (m_pp, "%i | ", row);
+      if (m_is_html)
+       pp_printf (m_pp, "%i</td>", row);
+      else
+       pp_printf (m_pp, "%i | ", row);
     }
   else
     pp_space (m_pp);
@@ -1481,6 +1499,12 @@ layout::print_source_line (linenum_type row, const char 
*line, int line_bytes)
      tab expansion, and for implementing m_x_offset_display.  */
   cpp_display_width_computation dw (line, line_bytes, m_context->tabstop);
 
+  if (m_is_html)
+    {
+      pp_string (m_pp, "<td class=\"source\">");
+      pp_write_text_to_stream (m_pp);
+    }
+
   /* Skip the first m_x_offset_display display columns.  In case the leading
      portion that will be skipped ends with a character with wcwidth > 1, then
      it is possible we skipped too much, so account for that by padding with
@@ -1558,6 +1582,12 @@ layout::print_source_line (linenum_type row, const char 
*line, int line_bytes)
       /* Output the character.  */
       while (c != dw.next_byte ()) pp_character (m_pp, *c++);
     }
+  if (m_is_html)
+    {
+      pp_write_text_as_html_to_stream (m_pp);
+      pp_string (m_pp, "</td>");
+      pp_string (m_pp, "</tr>");
+    }
   print_newline ();
   return lbounds;
 }
@@ -1587,16 +1617,23 @@ void
 layout::start_annotation_line (char margin_char) const
 {
   pp_emit_prefix (m_pp);
+  if (m_is_html)
+    pp_string (m_pp, "<tr>");
   if (m_show_line_numbers_p)
     {
-      /* Print the margin.  If MARGIN_CHAR != ' ', then print up to 3
-        of it, right-aligned, padded with spaces.  */
-      int i;
-      for (i = 0; i < m_linenum_width - 3; i++)
-       pp_space (m_pp);
-      for (; i < m_linenum_width; i++)
-       pp_character (m_pp, margin_char);
-      pp_string (m_pp, " |");
+      if (m_is_html)
+       pp_string (m_pp, "<td class=\"linenum\"/>");
+      else
+       {
+         /* Print the margin.  If MARGIN_CHAR != ' ', then print up to 3
+            of it, right-aligned, padded with spaces.  */
+         int i;
+         for (i = 0; i < m_linenum_width - 3; i++)
+           pp_space (m_pp);
+         for (; i < m_linenum_width; i++)
+           pp_character (m_pp, margin_char);
+         pp_string (m_pp, " |");
+       }
     }
 }
 
@@ -1610,7 +1647,13 @@ layout::print_annotation_line (linenum_type row, const 
line_bounds lbounds)
                                     lbounds.m_last_non_ws_disp_col);
 
   start_annotation_line ();
-  pp_space (m_pp);
+  if (m_is_html)
+    {
+      pp_string (m_pp, "<td class=\"annotation\">");
+      pp_write_text_to_stream (m_pp);
+    }
+  else
+    pp_space (m_pp);
 
   for (int column = 1 + m_x_offset_display; column < x_bound; column++)
     {
@@ -1645,6 +1688,11 @@ layout::print_annotation_line (linenum_type row, const 
line_bounds lbounds)
          pp_character (m_pp, ' ');
        }
     }
+  if (m_is_html)
+    {
+      pp_write_text_as_html_to_stream (m_pp);
+      pp_string (m_pp, "</td></tr>");
+    }
   print_newline ();
 }
 
@@ -1655,7 +1703,7 @@ layout::print_annotation_line (linenum_type row, const 
line_bounds lbounds)
 class line_label
 {
 public:
-  line_label (diagnostic_context *context, int state_idx, int column,
+  line_label (const diagnostic_context *context, int state_idx, int column,
              label_text text)
   : m_state_idx (state_idx), m_column (column),
     m_text (text), m_label_line (0), m_has_vbar (true)
@@ -1795,7 +1843,10 @@ layout::print_any_labels (linenum_type row)
     for (int label_line = 0; label_line <= max_label_line; label_line++)
       {
        start_annotation_line ();
-       pp_space (m_pp);
+       if (m_is_html)
+         pp_string (m_pp, "<td class=\"annotation\">");
+       else
+         pp_space (m_pp);
        int column = 1 + m_x_offset_display;
        line_label *label;
        FOR_EACH_VEC_ELT (labels, i, label)
@@ -1812,7 +1863,19 @@ layout::print_any_labels (linenum_type row)
                   diagnostic_path.  */
                if (!m_diagnostic_path_p)
                  m_colorizer.set_range (label->m_state_idx);
+               if (m_is_html)
+                 {
+                   pp_write_text_to_stream (m_pp);
+                   if (m_html_writer)
+                     m_html_writer->begin_label (m_pp);
+                 }
                pp_string (m_pp, label->m_text.m_buffer);
+               if (m_is_html)
+                 {
+                   pp_write_text_as_html_to_stream (m_pp);
+                   if (m_html_writer)
+                     m_html_writer->end_label (m_pp);
+                 }
                m_colorizer.set_normal_text ();
                column += label->m_display_width;
              }
@@ -1826,6 +1889,8 @@ layout::print_any_labels (linenum_type row)
                column++;
              }
          }
+       if (m_is_html)
+         pp_string (m_pp, "</td></tr>");
        print_newline ();
       }
     }
@@ -2002,7 +2067,7 @@ public:
 
 /* Get the range of bytes or display columns that HINT would affect.  */
 static column_range
-get_affected_range (diagnostic_context *context,
+get_affected_range (const diagnostic_context *context,
                    const fixit_hint *hint, enum column_unit col_unit)
 {
   expanded_location exploc_start = expand_location (hint->get_start_loc ());
@@ -2032,7 +2097,7 @@ get_affected_range (diagnostic_context *context,
 /* Get the range of display columns that would be printed for HINT.  */
 
 static column_range
-get_printed_columns (diagnostic_context *context, const fixit_hint *hint)
+get_printed_columns (const diagnostic_context *context, const fixit_hint *hint)
 {
   expanded_location exploc = expand_location (hint->get_start_loc ());
   int start_column = location_compute_display_column (exploc, 
context->tabstop);
@@ -2154,7 +2219,7 @@ correction::ensure_terminated ()
 class line_corrections
 {
 public:
-  line_corrections (diagnostic_context *context, const char *filename,
+  line_corrections (const diagnostic_context *context, const char *filename,
                    linenum_type row)
     : m_context (context), m_filename (filename), m_row (row)
   {}
@@ -2162,7 +2227,7 @@ public:
 
   void add_hint (const fixit_hint *hint);
 
-  diagnostic_context *m_context;
+  const diagnostic_context *m_context;
   const char *m_filename;
   linenum_type m_row;
   auto_vec <correction *> m_corrections;
@@ -2543,7 +2608,8 @@ layout::print_line (linenum_type row)
     print_annotation_line (row, lbounds);
   if (m_show_labels_p)
     print_any_labels (row);
-  print_trailing_fixits (row);
+  if (!m_is_html)
+    print_trailing_fixits (row);
 }
 
 } /* End of anonymous namespace.  */
@@ -2629,6 +2695,61 @@ diagnostic_show_locus (diagnostic_context * context,
     }
 }
 
+/* As diagnostic_show_locus, but print to PP in HTML form, writing to the
+   underlying stream as necessary whenever escaping the text.
+   PP may be a different printer to DC's printer.
+   WRITER can be NULL, or can be non-NULL to customize how the HTML
+   is printed.
+   Return true if anything was printed.  */
+
+bool
+diagnostic_show_locus_as_html (diagnostic_context *dc,
+                              pretty_printer *pp,
+                              html_writer *writer,
+                              rich_location *richloc,
+                              diagnostic_t diagnostic_kind)
+{
+  location_t loc = richloc->get_loc ();
+
+  /* Don't attempt to print source for UNKNOWN_LOCATION and for builtins.  */
+  if (loc <= BUILTINS_LOCATION)
+    return false;
+
+  pp_string (pp, "<table class=\"locus\">\n");
+
+  layout layout (dc, richloc, diagnostic_kind, pp, true, writer);
+  for (int line_span_idx = 0; line_span_idx < layout.get_num_line_spans ();
+       line_span_idx++)
+    {
+      const line_span *line_span = layout.get_line_span (line_span_idx);
+
+      if (line_span_idx > 0)
+       {
+         pp_string (pp,
+                    "<tbody class=\"line-span-jump\">\n"
+                    "<tr class=\"line-span-jump-row\">"
+                    "<td class=\"linenum-gap\">[...]</td>"
+                    "<td class=\"source-gap\"/></tr>\n"
+                    "</tbody>\n");
+       }
+
+      pp_string (pp, "<tbody class=\"line-span\">\n");
+
+      /* Iterate over the lines within this span (using linenum_arith_t to
+        avoid overflow with 0xffffffff causing an infinite loop).  */
+      linenum_arith_t last_line = line_span->get_last_line ();
+      for (linenum_arith_t row = line_span->get_first_line ();
+          row <= last_line; row++)
+       layout.print_line (row);
+
+      pp_string (pp, "</tbody>\n");
+    }
+
+  pp_string (pp, "</table>\n");
+
+  return true;
+}
+
 #if CHECKING_P
 
 namespace selftest {
@@ -2913,6 +3034,61 @@ test_layout_x_offset_display_tab (const line_table_case 
&case_)
     }
 }
 
+/* A test fixture for capturing escaped HTML output.
+   This has to be done from FILE * stream, rather than from a pretty_printer,
+   since escaping happens when writing from the latter to the former.  */
+
+class html_printer
+{
+ public:
+  html_printer ()
+    : m_pp (), m_outf_name (".html")
+  {
+    m_pp.buffer->stream = fopen (m_outf_name.get_filename (), "w");
+    ASSERT_NE (m_pp.buffer->stream, NULL);
+  }
+
+  pretty_printer *get_pp () { return &m_pp; }
+
+  char *get_html_output ()
+  {
+    gcc_assert (m_pp.buffer->stream);
+    pp_flush (&m_pp);
+    fclose (m_pp.buffer->stream);
+    m_pp.buffer->stream = NULL;
+    return read_file (SELFTEST_LOCATION, m_outf_name.get_filename ());
+  }
+
+ private:
+  pretty_printer m_pp;
+  named_temp_file m_outf_name;
+};
+
+/* Implementation of ASSERT_HTML_EQ.  */
+
+static void
+assert_html_eq (const location &loc,
+               diagnostic_context *dc,
+               rich_location *rich_loc,
+               diagnostic_t diagnostic_kind,
+               const char *expected_html)
+{
+
+  html_printer to_html;
+  diagnostic_show_locus_as_html (dc, to_html.get_pp (), NULL,
+                                rich_loc, diagnostic_kind);
+  char *actual_html = to_html.get_html_output ();
+  ASSERT_STREQ_AT (loc, actual_html, expected_html);
+  free (actual_html);
+}
+
+/* Assert that when printing RICHLOC as HTML to DC's printer that the
+   result written to the stream after escaping is EXPECTED_HTML.  */
+
+#define ASSERT_HTML_EQ(DC, RICHLOC, DK, EXPECTED_HTML) \
+  SELFTEST_BEGIN_STMT                                              \
+  assert_html_eq (SELFTEST_LOCATION, (DC), (RICHLOC), (DK), (EXPECTED_HTML)); \
+  SELFTEST_END_STMT
 
 /* Verify that diagnostic_show_locus works sanely on UNKNOWN_LOCATION.  */
 
@@ -2947,6 +3123,14 @@ test_one_liner_simple_caret ()
   ASSERT_STREQ (" foo = bar.field;\n"
                "          ^\n",
                pp_formatted_text (dc.printer));
+  ASSERT_HTML_EQ
+    (&dc, &richloc, DK_ERROR,
+     "<table class=\"locus\">\n"
+     "<tbody class=\"line-span\">\n"
+     "<tr> <td class=\"source\">foo = bar.field;</td></tr>\n"
+     "<tr><td class=\"annotation\">         ^</td></tr>\n"
+     "</tbody>\n"
+     "</table>\n");
 }
 
 /* Caret and range.  */
@@ -4224,6 +4408,22 @@ test_diagnostic_show_locus_fixit_lines (const 
line_table_case &case_)
                  "      |                         ^\n"
                  "      |                         =\n",
                  pp_formatted_text (dc.printer));
+    /* Verify that HTML output correctly emits a line-numbering gap.
+       Fix-it hints aren't yet emitted in HTML output.  */
+    ASSERT_HTML_EQ
+      (&dc, &richloc, DK_ERROR,
+       "<table class=\"locus\">\n"
+       "<tbody class=\"line-span\">\n"
+       "<tr><td class=\"linenum\">    3</td><td class=\"source\">              
         y</td></tr>\n"
+       "</tbody>\n"
+       "<tbody class=\"line-span-jump\">\n"
+       "<tr class=\"line-span-jump-row\"><td 
class=\"linenum-gap\">[...]</td><td class=\"source-gap\"/></tr>\n"
+       "</tbody>\n"
+       "<tbody class=\"line-span\">\n"
+       "<tr><td class=\"linenum\">    6</td><td class=\"source\">              
          : 0.0};</td></tr>\n"
+       "<tr><td class=\"linenum\"/><td class=\"annotation\">                   
     ^</td></tr>\n"
+       "</tbody>\n"
+       "</table>\n");
   }
 }
 
@@ -5187,6 +5387,71 @@ test_tab_expansion (const line_table_case &case_)
   }
 }
 
+/* Test of HTML output, with an example of labeling the ranges within
+   a rich_location.  */
+
+static void
+test_html (const line_table_case &case_)
+{
+  /* Create a tempfile and write some text to it.
+     ....................0000000001111111.
+     ....................1234567890123456.  */
+  const char *content = "foo = bar.field; /* < & > \".  */\n";
+  /* This is similar to the text in test_diagnostic_show_locus_one_liner,
+     but with characters needing HTML escaping added.  */
+  temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
+  line_table_test ltt (case_);
+
+  linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 1);
+
+  location_t last_col = linemap_position_for_column (line_table, 16);
+
+  /* Don't attempt to run the tests if column data might be unavailable.  */
+  if (last_col > LINE_MAP_MAX_LOCATION_WITH_COLS)
+    return;
+
+  location_t foo
+    = make_location (linemap_position_for_column (line_table, 1),
+                    linemap_position_for_column (line_table, 1),
+                    linemap_position_for_column (line_table, 3));
+  location_t bar
+    = make_location (linemap_position_for_column (line_table, 7),
+                    linemap_position_for_column (line_table, 7),
+                    linemap_position_for_column (line_table, 9));
+  location_t field
+    = make_location (linemap_position_for_column (line_table, 11),
+                    linemap_position_for_column (line_table, 11),
+                    linemap_position_for_column (line_table, 15));
+
+  if (field > LINE_MAP_MAX_LOCATION_WITH_COLS)
+      return;
+
+  /* Example where the labels need extra lines.  */
+  {
+    text_range_label label0 ("label 0");
+    text_range_label label1 ("label 1");
+    text_range_label label2 ("label 2");
+    gcc_rich_location richloc (foo, &label0);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+    richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+    test_diagnostic_context dc;
+    ASSERT_HTML_EQ
+      (&dc, &richloc, DK_ERROR,
+       "<table class=\"locus\">\n"
+       "<tbody class=\"line-span\">\n"
+       "<tr> <td class=\"source\">foo = bar.field;"
+       " /* &lt; &amp; &gt; &quot;.  */</td></tr>\n"
+       "<tr><td class=\"annotation\">^~~   ~~~ ~~~~~</td></tr>\n"
+       "<tr><td class=\"annotation\">|     |   |</td></tr>\n"
+       "<tr><td class=\"annotation\">|     |   label 2</td></tr>\n"
+       "<tr><td class=\"annotation\">|     label 1</td></tr>\n"
+       "<tr><td class=\"annotation\">label 0</td></tr>\n"
+       "</tbody>\n"
+       "</table>\n");
+  }
+}
+
 /* Verify that line numbers are correctly printed for the case of
    a multiline range in which the width of the line numbers changes
    (e.g. from "9" to "10").  */
@@ -5263,6 +5528,7 @@ diagnostic_show_locus_c_tests ()
   for_each_line_table_case (test_fixit_replace_containing_newline);
   for_each_line_table_case (test_fixit_deletion_affecting_newline);
   for_each_line_table_case (test_tab_expansion);
+  for_each_line_table_case (test_html);
 
   test_line_numbers_multiline_range ();
 }
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index 1b6c9845892..316b6c615b4 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -734,7 +734,7 @@ diagnostic_show_any_path (diagnostic_context *context,
     return;
 
   if (context->print_path)
-    context->print_path (context, path);
+    context->print_path (context, path, diagnostic->richloc->get_loc ());
 }
 
 /* Return true if the events in this path involve more than one
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 4051601abfd..c303a83ba22 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -63,7 +63,10 @@ enum diagnostic_path_format
      are close enough, and printing such runs of events with multiple
      calls to diagnostic_show_locus, showing the individual events in
      each run via labels in the source.  */
-  DPF_INLINE_EVENTS
+  DPF_INLINE_EVENTS,
+
+  /* Print diagnostic_paths by emitting an HTML file for each path.  */
+  DPF_HTML
 };
 
 /* A diagnostic is described by the MESSAGE to send, the FILE and LINE of
@@ -169,6 +172,9 @@ struct diagnostic_context
   /* How should diagnostic_path objects be printed.  */
   enum diagnostic_path_format path_format;
 
+  /* How many HTML paths have been emitted by DPF_HTML.  */
+  int num_html_paths;
+
   /* True if we should print stack depths when printing diagnostic paths.  */
   bool show_path_depths;
 
@@ -246,7 +252,9 @@ struct diagnostic_context
      particular option.  */
   char *(*get_option_url) (diagnostic_context *, int);
 
-  void (*print_path) (diagnostic_context *, const diagnostic_path *);
+  void (*print_path) (diagnostic_context *,
+                     const diagnostic_path *,
+                     location_t);
   json::value *(*make_json_for_path) (diagnostic_context *, const 
diagnostic_path *);
 
   /* Auxiliary data for client.  */
@@ -401,6 +409,17 @@ extern void diagnostic_report_current_module 
(diagnostic_context *, location_t);
 extern void diagnostic_show_locus (diagnostic_context *,
                                   rich_location *richloc,
                                   diagnostic_t diagnostic_kind);
+class html_writer
+{
+public:
+  virtual void begin_label (pretty_printer *pp) = 0;
+  virtual void end_label (pretty_printer *pp) = 0;
+};
+extern bool diagnostic_show_locus_as_html (diagnostic_context *dc,
+                                          pretty_printer *pp,
+                                          html_writer *writer,
+                                          rich_location *richloc,
+                                          diagnostic_t diagnostic_kind);
 extern void diagnostic_show_any_path (diagnostic_context *, diagnostic_info *);
 
 /* Force diagnostics controlled by OPTIDX to be kind KIND.  */
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 0b87822349f..eb8c323e50e 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -292,7 +292,7 @@ Objective-C and Objective-C++ Dialects}.
 -fdiagnostics-minimum-margin-width=@var{width} @gol
 -fdiagnostics-parseable-fixits  -fdiagnostics-generate-patch @gol
 -fdiagnostics-show-template-tree  -fno-elide-type @gol
--fdiagnostics-path-format=@r{[}none@r{|}separate-events@r{|}inline-events@r{]} 
@gol
+-fdiagnostics-path-format=@r{[}none@r{|}separate-events@r{|}inline-events@r{|}html@r{]}
 @gol
 -fdiagnostics-show-path-depths @gol
 -fno-show-column @gol
 -fdiagnostics-column-unit=@r{[}display@r{|}byte@r{]} @gol
@@ -4687,8 +4687,8 @@ This flag also affects the output of the
 Specify how to print paths of control-flow events for diagnostics that
 have such a path associated with them.
 
-@var{KIND} is @samp{none}, @samp{separate-events}, or @samp{inline-events},
-the default.
+@var{KIND} is @samp{none}, @samp{separate-events}, @samp{inline-events},
+or @samp{html}.  The default is @samp{inline-events}.
 
 @samp{none} means to not print diagnostic paths.
 
@@ -4779,6 +4779,11 @@ For example:
 (etc)
 @end smallexample
 
+@samp{html} means to write a separate HTML file for each diagnostic path,
+printing a ``note'' diagnostic giving the name of this file.  The generated
+HTML is intended for human consumption and the precise output format is
+liable to change.
+
 @item -fdiagnostics-show-path-depths
 @opindex fdiagnostics-show-path-depths
 This option provides additional information when printing control-flow paths
diff --git a/gcc/pretty-print.c b/gcc/pretty-print.c
index 407f7300dfb..335dae8bef0 100644
--- a/gcc/pretty-print.c
+++ b/gcc/pretty-print.c
@@ -956,6 +956,49 @@ pp_write_text_as_html_like_dot_to_stream (pretty_printer 
*pp)
   pp_clear_output_area (pp);
 }
 
+/* As pp_write_text_to_stream, but for HTML strings.
+
+   Flush the formatted text of pretty-printer PP onto the attached stream,
+   escaping these characters
+     ' " & < >
+   using named character references.  */
+
+void
+pp_write_text_as_html_to_stream (pretty_printer *pp)
+{
+  const char *text = pp_formatted_text (pp);
+  const char *p = text;
+  FILE *fp = pp_buffer (pp)->stream;
+
+  for (;*p; p++)
+    {
+      switch (*p)
+       {
+       case '\'':
+         fputs ("&apos;", fp);
+         break;
+       case '"':
+         fputs ("&quot;", fp);
+         break;
+       case '&':
+         fputs ("&amp;", fp);
+         break;
+       case '<':
+         fputs ("&lt;", fp);
+         break;
+       case '>':
+         fputs ("&gt;",fp);
+         break;
+
+       default:
+         fputc (*p, fp);
+         break;
+       }
+    }
+
+  pp_clear_output_area (pp);
+}
+
 /* Wrap a text delimited by START and END into PRETTY-PRINTER.  */
 static void
 pp_wrap_text (pretty_printer *pp, const char *start, const char *end)
diff --git a/gcc/pretty-print.h b/gcc/pretty-print.h
index 22892f12ab7..bf011f34c4b 100644
--- a/gcc/pretty-print.h
+++ b/gcc/pretty-print.h
@@ -398,6 +398,7 @@ extern void pp_string (pretty_printer *, const char *);
 extern void pp_write_text_to_stream (pretty_printer *);
 extern void pp_write_text_as_dot_label_to_stream (pretty_printer *, bool);
 extern void pp_write_text_as_html_like_dot_to_stream (pretty_printer *pp);
+extern void pp_write_text_as_html_to_stream (pretty_printer *pp);
 
 extern void pp_maybe_space (pretty_printer *);
 
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-html.c 
b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-html.c
new file mode 100644
index 00000000000..bdce3c0c843
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-html.c
@@ -0,0 +1,45 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=html" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+  /* etc */
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
+/* { dg-final { scan-file diagnostic-path-format-html.c.path-1.html "calling 
&apos;make_boxed_int&apos;" } } */
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp 
b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index 5dd102ae05c..cc7faf2cd0f 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -105,7 +105,8 @@ set plugin_test_list [list \
          diagnostic-path-format-separate-events.c \
          diagnostic-path-format-inline-events-1.c \
          diagnostic-path-format-inline-events-2.c \
-         diagnostic-path-format-inline-events-3.c } \
+         diagnostic-path-format-inline-events-3.c \
+         diagnostic-path-format-html.c } \
     { location_overflow_plugin.c \
          location-overflow-test-1.c \
          location-overflow-test-2.c \
diff --git a/gcc/tree-diagnostic-path.cc b/gcc/tree-diagnostic-path.cc
index 82b3c2d6b6a..151ffc5e7a9 100644
--- a/gcc/tree-diagnostic-path.cc
+++ b/gcc/tree-diagnostic-path.cc
@@ -18,10 +18,18 @@ You should have received a copy of the GNU General Public 
License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
+/* This source file uses pp_printf to build up HTML fragments, hence
+   use "_raw" to suppress -Wformat-diag checking (when building with a
+   sufficiently recent GCC version).  It also uses %qE, hence "tdiag".  */
+#if __GNUC__ >= 10
+#define GCC_DIAG_STYLE __gcc_tdiag_raw__
+#endif
+
 #include "config.h"
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
+#include "options.h"
 #include "diagnostic.h"
 #include "tree-pretty-print.h"
 #include "gimple-pretty-print.h"
@@ -47,8 +55,9 @@ namespace {
 class path_label : public range_label
 {
  public:
-  path_label (const diagnostic_path *path, unsigned start_idx)
-  : m_path (path), m_start_idx (start_idx)
+  path_label (const diagnostic_path *path, unsigned start_idx,
+             bool colorize)
+    : m_path (path), m_start_idx (start_idx), m_colorize (colorize)
   {}
 
   label_text get_text (unsigned range_idx) const FINAL OVERRIDE
@@ -59,11 +68,10 @@ class path_label : public range_label
     /* Get the description of the event, perhaps with colorization:
        normally, we don't colorize within a range_label, but this
        is special-cased for diagnostic paths.  */
-    bool colorize = pp_show_color (global_dc->printer);
-    label_text event_text (event.get_desc (colorize));
+    label_text event_text (event.get_desc (m_colorize));
     gcc_assert (event_text.m_buffer);
     pretty_printer pp;
-    pp_show_color (&pp) = pp_show_color (global_dc->printer);
+    pp_show_color (&pp) = m_colorize;
     diagnostic_event_id_t event_id (event_idx);
     pp_printf (&pp, "%@ %s", &event_id, event_text.m_buffer);
     event_text.maybe_free ();
@@ -74,6 +82,7 @@ class path_label : public range_label
  private:
   const diagnostic_path *m_path;
   unsigned m_start_idx;
+  bool m_colorize;
 };
 
 /* Return true if E1 and E2 can be consolidated into the same run of events
@@ -122,13 +131,14 @@ class path_summary
   struct event_range
   {
     event_range (const diagnostic_path *path, unsigned start_idx,
-                const diagnostic_event &initial_event)
+                const diagnostic_event &initial_event,
+                bool colorize)
     : m_path (path),
       m_initial_event (initial_event),
       m_fndecl (initial_event.get_fndecl ()),
       m_stack_depth (initial_event.get_stack_depth ()),
       m_start_idx (start_idx), m_end_idx (start_idx),
-      m_path_label (path, start_idx),
+      m_path_label (path, start_idx, colorize),
       m_richloc (initial_event.get_location (), &m_path_label)
     {}
 
@@ -198,6 +208,39 @@ class path_summary
        }
     }
 
+    void print_as_html (diagnostic_context *dc, pretty_printer *pp,
+                       html_writer *writer)
+    {
+      if (!diagnostic_show_locus_as_html (dc, pp, writer, &m_richloc,
+                                         DK_DIAGNOSTIC_PATH))
+       {
+         /* If nothing was printed, then print any events directly.  */
+         for (unsigned i = m_start_idx; i <= m_end_idx; i++)
+           {
+             const diagnostic_event &iter_event = m_path->get_event (i);
+             diagnostic_event_id_t event_id (i);
+             pp_printf (pp, "<div class=\"no-locus-event\">%@: ", &event_id);
+             pp_write_text_to_stream (pp);
+             if (writer)
+               writer->begin_label (pp);
+             label_text event_text (iter_event.get_desc (true));
+             pp_string (pp, event_text.m_buffer);
+             event_text.maybe_free ();
+             pp_write_text_as_html_to_stream (pp);
+             if (writer)
+               writer->end_label (pp);
+             pp_string (pp, "</div>");
+             pp_newline (pp);
+             pp_write_text_to_stream (pp);
+           }
+       }
+    }
+
+    const char *get_filename () const
+    {
+      return LOCATION_FILE (m_initial_event.get_location ());
+    }
+
     const diagnostic_path *m_path;
     const diagnostic_event &m_initial_event;
     tree m_fndecl;
@@ -209,9 +252,12 @@ class path_summary
   };
 
  public:
-  path_summary (const diagnostic_path &path, bool check_rich_locations);
+  path_summary (const diagnostic_path &path, bool check_rich_locations,
+               bool colorize);
 
   void print (diagnostic_context *dc, bool show_depths) const;
+  void print_as_html (diagnostic_context *dc, pretty_printer *pp,
+                     bool show_depths) const;
 
   unsigned get_num_ranges () const { return m_ranges.length (); }
 
@@ -222,7 +268,8 @@ class path_summary
 /* path_summary's ctor.  */
 
 path_summary::path_summary (const diagnostic_path &path,
-                           bool check_rich_locations)
+                           bool check_rich_locations,
+                           bool colorize)
 {
   const unsigned num_events = path.num_events ();
 
@@ -234,7 +281,7 @@ path_summary::path_summary (const diagnostic_path &path,
        if (cur_event_range->maybe_add_event (event, idx, check_rich_locations))
          continue;
 
-      cur_event_range = new event_range (&path, idx, event);
+      cur_event_range = new event_range (&path, idx, event, colorize);
       m_ranges.safe_push (cur_event_range);
     }
 }
@@ -441,13 +488,263 @@ path_summary::print (diagnostic_context *dc, bool 
show_depths) const
     }
 }
 
+/* Custom subclass of html_writer.
+   Wrap labels within a <span> element, supplying them with event IDs.  */
+
+class html_path_writer : public html_writer
+{
+public:
+  html_path_writer () : m_events_seen (0)
+  {
+  }
+
+  void begin_label (pretty_printer *pp) FINAL OVERRIDE
+  {
+    pp_printf (pp,
+              "<span class=\"event\" id=\"event-%i\">",
+              ++m_events_seen);
+    pp_write_text_to_stream (pp);
+  }
+
+  void end_label (pretty_printer *pp) FINAL OVERRIDE
+  {
+    pp_write_text_as_html_to_stream (pp);
+    pp_string (pp, "</span>");
+    pp_write_text_to_stream (pp);
+  }
+
+private:
+  int m_events_seen;
+};
+
+/* Print the path summary in HTML form to PP.  */
+
+void
+path_summary::print_as_html (diagnostic_context *dc,
+                            pretty_printer *pp,
+                            bool show_depths) const
+{
+  html_path_writer writer;
+
+  pp_string (pp, "<div class=\"event-ranges\">\n\n");
+
+  unsigned i;
+  event_range *range;
+  int min_depth = INT_MAX;
+  FOR_EACH_VEC_ELT (m_ranges, i, range)
+    if (range->m_stack_depth < min_depth)
+      min_depth = range->m_stack_depth;
+
+  /* Print the ranges.  */
+  FOR_EACH_VEC_ELT (m_ranges, i, range)
+    {
+      const int pixels_per_depth = 100;
+
+      if (i > 0)
+       {
+         event_range *last_range = m_ranges[i - 1];
+
+         const int this_depth = range->m_stack_depth;
+         const int last_depth = last_range->m_stack_depth;
+         if (this_depth != last_depth)
+           {
+             const int base_x = 20;
+             const int excess = 30;
+             const int last_x
+               = base_x + (last_depth - min_depth) * pixels_per_depth;
+             const int this_x
+               = base_x + (this_depth - min_depth) * pixels_per_depth;
+             pp_printf (pp, "<div class=\"%s\">\n",
+                        last_depth < this_depth
+                        ? "between-ranges-call" : "between-ranges-return");
+             pp_printf (pp, "  <svg height=\"30\" width=\"%i\">\n",
+                        MAX (last_x, this_x) + excess);
+             pp_string
+               (pp,
+                "    <defs>\n"
+                "      <marker id=\"arrowhead\" markerWidth=\"10\" 
markerHeight=\"7\"\n"
+                "              refX=\"0\" refY=\"3.5\" orient=\"auto\">\n"
+                "      <polygon points=\"0 0, 10 3.5, 0 7\"/>\n"
+                "      </marker>\n"
+                "    </defs>\n");
+             pp_printf (pp,
+                        "    <polyline points=\"%i,0 %i,10 %i,10 %i,20\"\n",
+                        last_x, last_x, this_x, this_x);
+             pp_string
+               (pp,
+                "              style=\"fill:none;stroke:black\"\n"
+                "              marker-end=\"url(#arrowhead)\"/>\n"
+                "  </svg>\n"
+                "</div>\n\n");
+           }
+
+         const char *last_file = last_range->get_filename ();
+         const char *this_file = range->get_filename ();
+         if (last_file && this_file && strcmp (last_file, this_file))
+           {
+             pp_printf (pp, "change of file to %qs\n", this_file);
+           }
+       }
+
+      pp_string (pp, "<table class=\"event-range-with-margin\"><tr>\n");
+
+      /* Indent to show stack depth.  */
+      pp_printf (pp,
+                ("  <td class=\"interprocmargin\""
+                 " style=\"padding-left: %ipx\"/>\n"),
+                (range->m_stack_depth - min_depth) * pixels_per_depth);
+      pp_string (pp, "  <td class=\"event-range\">\n");
+      pp_string (pp, "    <div class=\"events-hdr\">");
+      if (range->m_fndecl)
+       {
+         pp_string (pp, "<span class=\"funcname\">");
+         pp_write_text_to_stream (pp);
+         print_fndecl (pp, range->m_fndecl, true);
+         pp_write_text_as_html_to_stream (pp);
+         pp_string (pp, "</span>: ");
+       }
+      pp_string (pp, "<span class=\"event-ids\">");
+      if (range->m_start_idx == range->m_end_idx)
+       pp_printf (pp, "event %i",
+                  range->m_start_idx + 1);
+      else
+       pp_printf (pp, "events %i-%i",
+                  range->m_start_idx + 1, range->m_end_idx + 1);
+      pp_string (pp, "</span>");
+      if (show_depths)
+       pp_printf (pp, " <span class=\"depth\">(depth %i)</span>",
+                  range->m_stack_depth);
+      pp_string (pp, "</div>");
+      pp_newline (pp);
+
+      /* Print a run of events.  */
+      range->print_as_html (dc, pp, &writer);
+
+      pp_string (pp, "</td></tr></table>\n");
+    }
+
+  pp_string (pp, "\n</div>\n\n");
+}
+
+/* Style information for writing out HTML paths.
+   Colors taken from https://www.patternfly.org/v3/styles/color-palette/ */
+
+const char * const HTML_STYLE
+  = ("  <style>\n"
+     "    .linenum { color: white;\n"
+     "               background-color: #0088ce;\n"
+     "               white-space: pre;\n"
+     "               border-right: 1px solid black; }\n"
+     "    .source { color: blue;\n"
+     "              white-space: pre; }\n"
+     "    .annotation { color: green;\n"
+     "                  white-space: pre; }\n"
+     "    .linenum-gap { text-align: center;\n"
+     "                   border-top: 1px solid black;\n"
+     "                   border-right: 1px solid black;\n"
+     "                   background-color: #ededed; }\n"
+     "    .source-gap { border-bottom: 1px dashed black;\n"
+     "                  border-top: 1px dashed black;\n"
+     "                  background-color: #ededed; }\n"
+     "    .no-locus-event { font-family: monospace;\n"
+     "                      color: green;\n"
+     "                      white-space: pre; }\n"
+     "    .funcname { font-weight: bold; }\n"
+     "    .events-hdr { color: white;\n"
+     "                  background-color: #030303; }\n"
+     "    .event-range {  border: 1px solid black;\n"
+     "                    padding: 0px; }\n"
+     "    .event-range-with-margin { border-spacing: 0; }\n"
+     "    .locus { font-family: monospace;\n"
+     "             border-spacing: 0px; }\n"
+     "    .selected { color: white;\n"
+     "                background-color: #0088ce; }\n"
+     "  </style>\n");
+
+/* JavaScript for writing out HTML paths.  */
+
+const char * const HTML_SCRIPT
+  = ("<script>\n"
+     "  var current_event = 1;\n"
+     "\n"
+     "  function get_event_span (event_id)\n"
+     "  {\n"
+     "      const element_id = 'event-' + event_id;\n"
+     "      return document.getElementById(element_id);\n"
+     "  }\n"
+     "  function unhighlight_current_event ()\n"
+     "  {\n"
+     "      get_event_span (current_event).classList.remove ('selected');\n"
+     "  }\n"
+     "  function highlight_current_event ()\n"
+     "  {\n"
+     "      const el = get_event_span (current_event);\n"
+     "      el.classList.add ('selected');\n"
+     "      // Center the element on the screen\n"
+     "      const top_y = el.getBoundingClientRect ().top + 
window.pageYOffset;\n"
+     "      const middle = top_y - (window.innerHeight / 2);\n"
+     "      window.scrollTo (0, middle);\n"
+     "  }\n"
+     "  function select_prev_event ()\n"
+     "  {\n"
+     "      unhighlight_current_event ();\n"
+     "      if (current_event > 1)\n"
+     "          current_event -= 1;\n"
+     "      else\n"
+     "          current_event = max_event;\n"
+     "      highlight_current_event ();\n"
+     "  }\n"
+     "  function select_next_event ()\n"
+     "  {\n"
+     "      unhighlight_current_event ();\n"
+     "      if (current_event < max_event)\n"
+     "          current_event += 1;\n"
+     "      else\n"
+     "          current_event = 1;\n"
+     "      highlight_current_event ();\n"
+     "  }\n"
+     "  document.addEventListener('keydown', function (ev) {\n"
+     "      if (ev.key == 'j')\n"
+     "          select_prev_event ();\n"
+     "      else if (ev.key == 'k')\n"
+     "          select_next_event ();\n"
+     "  });\n"
+     "  highlight_current_event ();\n"
+     "</script>\n");
+
+/* Write PATH to PP in HTML form.  */
+
+static void
+write_html_for_path (diagnostic_context *context,
+                    const diagnostic_path *path,
+                    pretty_printer *pp)
+{
+  pp_string (pp, "<html>\n");
+  pp_string (pp, "<head>\n");
+  pp_string (pp, HTML_STYLE);
+  pp_string (pp, "</head>\n");
+  pp_string (pp, "<body>\n");
+  pp_string (pp, "<h1>Bug path</h1>\n\n");
+
+  /* Consolidate related events.  */
+  path_summary summary (*path, true, false);
+  summary.print_as_html (context, pp, context->show_path_depths);
+
+  pp_printf (pp, "<script>max_event=%i;</script>\n",
+            path->num_events ());
+  pp_string (pp, HTML_SCRIPT);
+  pp_string (pp, "</body>\n</html>\n");
+}
+
 } /* end of anonymous namespace for path-printing code.  */
 
-/* Print PATH to CONTEXT, according to CONTEXT's path_format.  */
+/* Print PATH to CONTEXT, according to CONTEXT's path_format,
+   for a diagnostic at LOC.  */
 
 void
 default_tree_diagnostic_path_printer (diagnostic_context *context,
-                                     const diagnostic_path *path)
+                                     const diagnostic_path *path,
+                                     location_t loc)
 {
   gcc_assert (path);
 
@@ -478,13 +775,48 @@ default_tree_diagnostic_path_printer (diagnostic_context 
*context,
     case DPF_INLINE_EVENTS:
       {
        /* Consolidate related events.  */
-       path_summary summary (*path, true);
+       path_summary summary (*path, true, pp_show_color (context->printer));
        char *saved_prefix = pp_take_prefix (context->printer);
        pp_set_prefix (context->printer, NULL);
        summary.print (context, context->show_path_depths);
        pp_flush (context->printer);
        pp_set_prefix (context->printer, saved_prefix);
       }
+      break;
+
+    case DPF_HTML:
+      {
+       const int path_id = ++context->num_html_paths;
+
+       /* Generate a filename of the form
+          "DUMP_BASE_NAME.path-[0-9]+.html".  */
+       char id_buf[100];
+       snprintf (id_buf, sizeof (id_buf), ".path-%d", path_id);
+       char *html_filename = concat (dump_base_name, id_buf, ".html", NULL);
+
+       FILE *html_outf = fopen (html_filename, "w");
+       if (!html_outf)
+         {
+           fnotice (stderr, "Could not open output file '%s'\n",
+                    html_filename);
+           free (html_filename);
+           return;
+         }
+
+       pretty_printer pp;
+       pp_buffer (&pp)->stream = html_outf;
+       write_html_for_path (context, path, &pp);
+       pp_flush (&pp);
+
+       fclose (html_outf);
+       unsigned num_events = path->num_events ();
+       inform_n (loc, num_events,
+                 "path with %i event written to %qs",
+                 "path with %i events written to %qs",
+                 num_events, html_filename);
+       free (html_filename);
+      }
+      break;
     }
 }
 
@@ -565,7 +897,7 @@ test_empty_path (pretty_printer *event_pp)
   test_diagnostic_path path (event_pp);
   ASSERT_FALSE (path.interprocedural_p ());
 
-  path_summary summary (path, false);
+  path_summary summary (path, false, false);
   ASSERT_EQ (summary.get_num_ranges (), 0);
 
   test_diagnostic_context dc;
@@ -589,7 +921,7 @@ test_intraprocedural_path (pretty_printer *event_pp)
 
   ASSERT_FALSE (path.interprocedural_p ());
 
-  path_summary summary (path, false);
+  path_summary summary (path, false, false);
   ASSERT_EQ (summary.get_num_ranges (), 1);
 
   test_diagnostic_context dc;
@@ -638,7 +970,7 @@ test_interprocedural_path_1 (pretty_printer *event_pp)
 
   ASSERT_TRUE (path.interprocedural_p ());
 
-  path_summary summary (path, false);
+  path_summary summary (path, false, false);
   ASSERT_EQ (summary.get_num_ranges (), 9);
 
   test_diagnostic_context dc;
@@ -720,7 +1052,7 @@ test_interprocedural_path_2 (pretty_printer *event_pp)
 
   ASSERT_TRUE (path.interprocedural_p ());
 
-  path_summary summary (path, false);
+  path_summary summary (path, false, false);
   ASSERT_EQ (summary.get_num_ranges (), 5);
 
   test_diagnostic_context dc;
@@ -772,7 +1104,7 @@ test_recursion (pretty_printer *event_pp)
 
   ASSERT_TRUE (path.interprocedural_p ());
 
-  path_summary summary (path, false);
+  path_summary summary (path, false, false);
   ASSERT_EQ (summary.get_num_ranges (), 4);
 
   test_diagnostic_context dc;
diff --git a/gcc/tree-diagnostic.h b/gcc/tree-diagnostic.h
index 40dc9fa0e83..e4207120b68 100644
--- a/gcc/tree-diagnostic.h
+++ b/gcc/tree-diagnostic.h
@@ -58,7 +58,8 @@ bool default_tree_printer (pretty_printer *, text_info *, 
const char *,
                           int, bool, bool, bool, bool *, const char **);
 
 extern void default_tree_diagnostic_path_printer (diagnostic_context *,
-                                                 const diagnostic_path *);
+                                                 const diagnostic_path *,
+                                                 location_t);
 extern json::value *default_tree_make_json_for_path (diagnostic_context *,
                                                     const diagnostic_path *);
 
-- 
2.26.2

Reply via email to