public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH/RFC] Add -fdiagnostics-path-format=html
@ 2020-10-22 17:58 David Malcolm
  2020-11-10 16:08 ` [PATCH/RFC v2] Add -fdiagnostics-path-format=html [v2] David Malcolm
  0 siblings, 1 reply; 3+ messages in thread
From: David Malcolm @ 2020-10-22 17:58 UTC (permalink / raw)
  To: gcc-patches

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


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH/RFC v2] Add -fdiagnostics-path-format=html [v2]
  2020-10-22 17:58 [PATCH/RFC] Add -fdiagnostics-path-format=html David Malcolm
@ 2020-11-10 16:08 ` David Malcolm
  2020-12-02  0:13   ` Jeff Law
  0 siblings, 1 reply; 3+ messages in thread
From: David Malcolm @ 2020-11-10 16:08 UTC (permalink / raw)
  To: gcc-patches

Here's an updated version of the HTML output idea from:
  https://gcc.gnu.org/pipermail/gcc-patches/2020-October/556848.html

I reworked the HTML path output to show stack frames as well as just
runs of events, using drop-shadows to give a 3D look.  The idea is to try
to highlight the stack of frames as if it were an actual stack of
overlapping cards.

Updated HTML for the example from the earlier patch can be seen here:
  https://dmalcolm.fedorapeople.org/gcc/2020-11-05/html-examples/test.c.path-1.html
As before, other examples can be seen in that directory, such as:
  Signal handler issue:
    https://dmalcolm.fedorapeople.org/gcc/2020-11-05/html-examples/signal-1.c.path-1.html
  Leak due to longjmp past a "free":
    https://dmalcolm.fedorapeople.org/gcc/2020-11-05/html-examples/setjmp-7.c.path-1.html

Other changes in v2:
* switched j and k in keyboard navigation so that j is "next event"
and k is "previous event"
* fixed event element IDs, fixing a bug where it assumed they were in
  ascending order
* moved HTML printing code out of path_summary and event_range
* more selftest coverage

As before, this approach merely emits the path information; it doesn't
capture the associated diagnostic.  I'm working on an alternate approach
for v3 of the patch that does that; doing that requires reworking
pretty_printer.

I'm not sure on exactly the correct approach here; for v3 I'm
experimenting with an "html" option for -fdiagnostics-format= which
when selected would supplement the existing text output, by additionally
writing out an HTML file for each diagnostic group, with path
information captured there.  Doing it per group means that the classic
"warning"/"note" pairs would be grouped together in that output.

I'm not sure what that ought to do with paths though; part of the point
of doing it is to have less verbose output on stderr whilst capturing
the pertinent information "on the side" in the HTML file.

Thoughts?

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu, FWIW.

gcc/ChangeLog:
	* common.opt (diagnostic_path_format): Add DPF_HTML.
	* diagnostic-format-json.cc: Include "selftest.h".
	* 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_label_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 "label_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.  Add
	"original_range_idx" param and use it to initialize...
	(line_label::m_original_range_idx): ...this new field.
	(layout::print_any_labels): Pass original index of range to
	line_label ctor.  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.
	(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_label_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.
	* selftest-diagnostic.c (selftest::html_printer::html_printer):
	New ctor.
	(selftest::html_printer::get_html_output): New.
	* selftest-diagnostic.h (class selftest::html_printer): New.
	* 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::get_filename): New.
	(path_summary::path_summary): Add "colorize" param and use it when
	creating event_ranges.
	(class html_path_label_writer): New.
	(struct stack_frame): New.
	(begin_html_stack_frame): New.
	(end_html_stack_frame): New.
	(emit_svg_arrow): New.
	(print_event_range_as_html): New.
	(print_path_summary_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.  Add test of
	HTML output.
	(selftest::test_recursion): Update for new param of path_summary
	ctor.
	* 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-format-json.cc                 |   1 +
 gcc/diagnostic-show-locus.c                   | 324 ++++++++++--
 gcc/diagnostic.c                              |   2 +-
 gcc/diagnostic.h                              |  23 +-
 gcc/doc/invoke.texi                           |  11 +-
 gcc/pretty-print.c                            |  43 ++
 gcc/pretty-print.h                            |   1 +
 gcc/selftest-diagnostic.c                     |  19 +
 gcc/selftest-diagnostic.h                     |  18 +
 .../plugin/diagnostic-path-format-html.c      |  45 ++
 gcc/testsuite/gcc.dg/plugin/plugin.exp        |   3 +-
 gcc/tree-diagnostic-path.cc                   | 476 +++++++++++++++++-
 gcc/tree-diagnostic.h                         |   3 +-
 14 files changed, 904 insertions(+), 68 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 d4cbb2f86a5..cb394b5c485 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1402,6 +1402,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-format-json.cc b/gcc/diagnostic-format-json.cc
index 465c42fdfde..b5f92f0f7e6 100644
--- a/gcc/diagnostic-format-json.cc
+++ b/gcc/diagnostic-format-json.cc
@@ -23,6 +23,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "system.h"
 #include "coretypes.h"
 #include "diagnostic.h"
+#include "selftest.h"
 #include "selftest-diagnostic.h"
 #include "diagnostic-metadata.h"
 #include "json.h"
diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
index da3c5b6a92d..a6a3a2a0b97 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_label_writer *label_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_label_writer *m_html_label_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_label_writer *label_writer)
 : m_context (context),
-  m_pp (context->printer),
+  m_pp (pp ? pp : context->printer),
+  m_is_html (is_html),
+  m_html_label_writer (label_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,9 +1703,10 @@ 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,
-	      label_text text)
-  : m_state_idx (state_idx), m_column (column),
+  line_label (const diagnostic_context *context, unsigned original_range_idx,
+	      int state_idx, int column, label_text text)
+  : m_original_range_idx (original_range_idx),
+    m_state_idx (state_idx), m_column (column),
     m_text (text), m_label_line (0), m_has_vbar (true)
   {
     const int bytes = strlen (text.m_buffer);
@@ -1679,6 +1728,7 @@ public:
     return -compare (ll1->m_state_idx, ll2->m_state_idx);
   }
 
+  int m_original_range_idx;
   int m_state_idx;
   int m_column;
   label_text m_text;
@@ -1722,7 +1772,8 @@ layout::print_any_labels (linenum_type row)
 	if (text.m_buffer == NULL)
 	  continue;
 
-	labels.safe_push (line_label (m_context, i, disp_col, text));
+	labels.safe_push (line_label (m_context, range->m_original_idx,
+				      i, disp_col, text));
       }
   }
 
@@ -1795,7 +1846,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 +1866,20 @@ 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_label_writer)
+		      m_html_label_writer->begin_label
+			(m_pp, label->m_original_range_idx);
+		  }
 		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_label_writer)
+		      m_html_label_writer->end_label (m_pp);
+		  }
 		m_colorizer.set_normal_text ();
 		column += label->m_display_width;
 	      }
@@ -1826,6 +1893,8 @@ layout::print_any_labels (linenum_type row)
 		column++;
 	      }
 	  }
+	if (m_is_html)
+	  pp_string (m_pp, "</td></tr>");
 	print_newline ();
       }
     }
@@ -2002,7 +2071,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 +2101,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 +2223,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 +2231,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 +2612,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 +2699,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.
+   LABEL_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_label_writer *label_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, label_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 +3038,31 @@ test_layout_x_offset_display_tab (const line_table_case &case_)
     }
 }
 
+/* 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 +3097,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 +4382,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 +5361,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 +5502,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..8bd0a98add6 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_label_writer
+{
+public:
+  virtual void begin_label (pretty_printer *pp, unsigned range_idx) = 0;
+  virtual void end_label (pretty_printer *pp) = 0;
+};
+extern bool diagnostic_show_locus_as_html (diagnostic_context *dc,
+					   pretty_printer *pp,
+					   html_label_writer *label_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 5320e6c1e1e..6ba1f3f252a 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -293,7 +293,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
@@ -4726,8 +4726,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.
 
@@ -4818,6 +4818,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/selftest-diagnostic.c b/gcc/selftest-diagnostic.c
index 82fddca89ab..2bb9238a195 100644
--- a/gcc/selftest-diagnostic.c
+++ b/gcc/selftest-diagnostic.c
@@ -59,6 +59,25 @@ test_diagnostic_context::start_span_cb (diagnostic_context *context,
   default_diagnostic_start_span_fn (context, exploc);
 }
 
+/* class html_printer.  */
+
+html_printer::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);
+}
+
+char *
+html_printer::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 ());
+}
+
 } // namespace selftest
 
 #endif /* #if CHECKING_P */
diff --git a/gcc/selftest-diagnostic.h b/gcc/selftest-diagnostic.h
index efce20ff417..ee939dff1fd 100644
--- a/gcc/selftest-diagnostic.h
+++ b/gcc/selftest-diagnostic.h
@@ -42,6 +42,24 @@ class test_diagnostic_context : public diagnostic_context
   start_span_cb (diagnostic_context *context, expanded_location exploc);
 };
 
+/* 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 ();
+
+  pretty_printer *get_pp () { return &m_pp; }
+
+  char *get_html_output ();
+
+ private:
+  pretty_printer m_pp;
+  named_temp_file m_outf_name;
+};
+
 } // namespace selftest
 
 #endif /* #if CHECKING_P */
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 164df86037e..02fe787bb29 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
@@ -116,13 +125,14 @@ can_consolidate_events (const diagnostic_event &e1,
 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)
   {}
 
@@ -192,6 +202,11 @@ struct event_range
       }
   }
 
+  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;
@@ -208,7 +223,8 @@ struct event_range
 
 struct path_summary
 {
-  path_summary (const diagnostic_path &path, bool check_rich_locations);
+  path_summary (const diagnostic_path &path, bool check_rich_locations,
+		bool colorize);
 
   unsigned get_num_ranges () const { return m_ranges.length (); }
 
@@ -218,7 +234,8 @@ struct 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 ();
 
@@ -230,7 +247,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);
     }
 }
@@ -436,13 +453,393 @@ print_path_summary_as_text (const path_summary *ps, diagnostic_context *dc,
     }
 }
 
+/* Custom subclass of html_label_writer.
+   Wrap labels within a <span> element, supplying them with event IDs.  */
+
+class html_path_label_writer : public html_label_writer
+{
+public:
+  html_path_label_writer (int start_idx) : m_start_idx (start_idx)
+  {
+  }
+
+  void begin_label (pretty_printer *pp,  unsigned range_idx) FINAL OVERRIDE
+  {
+    pp_printf (pp,
+	       "<span class=\"event\" id=\"event-%i\">",
+	       m_start_idx + range_idx + 1);
+    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_start_idx;
+};
+
+/* A stack frame for use in HTML output, holding child stack frames,
+   and event ranges. */
+
+struct stack_frame
+{
+  stack_frame (stack_frame *parent, tree fndecl, int stack_depth)
+  : m_parent (parent), m_fndecl (fndecl), m_stack_depth (stack_depth)
+  {}
+
+  stack_frame * const m_parent;
+  const tree m_fndecl;
+  const int m_stack_depth;
+};
+
+/* Begin emitting content relating to a new stack frame within PARENT.
+   Allocated a new stack_frame and return it.  */
+
+static stack_frame *
+begin_html_stack_frame (pretty_printer *pp,
+			stack_frame *parent,
+			tree fndecl,
+			int stack_depth)
+{
+  if (fndecl)
+    {
+      pp_string (pp,
+		 "<table class=\"stack-frame-with-margin\"><tr>\n"
+		 "  <td class=\"interprocmargin\" style=\"padding-left: 100px\"/>\n"
+		 "  <td class=\"stack-frame\">\n");
+      pp_string (pp, "<div class=\"frame-funcname\"><span>");
+      pp_write_text_to_stream (pp);
+      print_fndecl (pp, fndecl, false);
+      pp_write_text_as_html_to_stream (pp);
+      pp_string (pp, "</span></div>");
+    }
+  return new stack_frame (parent, fndecl, stack_depth);
+}
+
+/* Finish emitting content for FRAME and delete it.  */
+
+static void
+end_html_stack_frame (pretty_printer *pp,
+		      stack_frame *frame)
+{
+  if (frame->m_fndecl)
+    pp_string (pp, "</td></tr></table>\n");
+  delete frame;
+}
+
+/* Emit an HTML <div> element to PP containing an SVG arrow representing
+   a change in stack depth from OLD_DEPTH to NEW_DEPTH.  */
+
+static void
+emit_svg_arrow (pretty_printer *pp, int old_depth, int new_depth)
+{
+  const int pixels_per_depth = 100;
+  const int min_depth = MIN (old_depth, new_depth);
+  const int base_x = 20;
+  const int excess = 30;
+  const int last_x
+    = base_x + (old_depth - min_depth) * pixels_per_depth;
+  const int this_x
+    = base_x + (new_depth - min_depth) * pixels_per_depth;
+  pp_printf (pp, "<div class=\"%s\">\n",
+	     old_depth < new_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\" stroke=\"#0088ce\" fill=\"#0088ce\">\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: #0088ce\"\n"
+     "              marker-end=\"url(#arrowhead)\"/>\n"
+     "  </svg>\n"
+     "</div>\n\n");
+}
+
+/* Print RANGE in HTML form to PP.  */
+
+static void
+print_event_range_as_html (event_range *range,
+			   diagnostic_context *dc, pretty_printer *pp)
+{
+  html_path_label_writer writer (range->m_start_idx);
+
+  if (!diagnostic_show_locus_as_html (dc, pp, &writer, &range->m_richloc,
+				      DK_DIAGNOSTIC_PATH))
+    {
+      /* If nothing was printed, then print any events directly.  */
+      for (unsigned i = range->m_start_idx; i <= range->m_end_idx; i++)
+	{
+	  const diagnostic_event &iter_event = range->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);
+	  writer.begin_label (pp, i - range->m_start_idx);
+	  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);
+	  writer.end_label (pp);
+	  pp_string (pp, "</div>");
+	  pp_newline (pp);
+	  pp_write_text_to_stream (pp);
+	}
+    }
+}
+
+/* Print the path summary PS in HTML form to PP.  */
+
+static void
+print_path_summary_as_html (const path_summary *ps,
+			    diagnostic_context *dc,
+			    pretty_printer *pp,
+			    bool show_depths)
+{
+  pp_string (pp, "<div class=\"event-ranges\">\n\n");
+
+  /* Group the ranges into stack frames.  */
+  stack_frame *curr_frame = NULL;
+  auto_vec<stack_frame *> outermost_frames;
+  unsigned i;
+  event_range *range;
+  FOR_EACH_VEC_ELT (ps->m_ranges, i, range)
+    {
+      const tree this_fndecl = range->m_fndecl;
+      const int this_depth = range->m_stack_depth;
+      if (curr_frame)
+	{
+	  int old_stack_depth = curr_frame->m_stack_depth;
+	  if (this_depth > curr_frame->m_stack_depth)
+	    {
+	      emit_svg_arrow (pp, old_stack_depth, this_depth);
+	      stack_frame *to_push
+		= begin_html_stack_frame (pp,
+					  curr_frame,
+					  range->m_fndecl,
+					  range->m_stack_depth);
+	      curr_frame = to_push;
+	    }
+	  else
+	    {
+	      while (this_depth < curr_frame->m_stack_depth
+		     || this_fndecl != curr_frame->m_fndecl)
+		{
+		  stack_frame *parent = curr_frame->m_parent;
+		  end_html_stack_frame (pp, curr_frame);
+		  curr_frame = parent;
+		  if (curr_frame == NULL)
+		    {
+		      curr_frame
+			= begin_html_stack_frame (pp,
+						  NULL,
+						  range->m_fndecl,
+						  range->m_stack_depth);
+		      outermost_frames.safe_push (curr_frame);
+		      break;
+		    }
+		}
+	      emit_svg_arrow (pp, old_stack_depth, this_depth);
+	    }
+	}
+      else
+	{
+	  curr_frame = begin_html_stack_frame (pp,
+					       NULL,
+					       range->m_fndecl,
+					       range->m_stack_depth);
+	  outermost_frames.safe_push (curr_frame);
+	}
+
+      if (i > 0)
+	{
+	  event_range *last_range = ps->m_ranges[i - 1];
+	  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");
+      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.  */
+      print_event_range_as_html (range, dc, pp);
+
+      pp_string (pp, "</td></tr></table>\n");
+    }
+
+  /* Close outstanding frames.  */
+  while (curr_frame)
+    {
+      stack_frame *parent = curr_frame->m_parent;
+      end_html_stack_frame (pp, curr_frame);
+      curr_frame = parent;
+    }
+
+  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"
+     "    .stack-frame-with-margin { border-spacing: 0; }\n"
+     "    .stack-frame {  padding: 5px;\n"
+     "                    box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.5); }\n"
+     "    .frame-funcname { text-align: right;\n"
+     "                      font-style: italic; } \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_next_event ();\n"
+     "      else if (ev.key == 'k')\n"
+     "          select_prev_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);
+  print_path_summary_as_html (&summary, 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);
 
@@ -473,7 +870,7 @@ 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);
 	print_path_summary_as_text (&summary, context,
@@ -481,6 +878,41 @@ default_tree_diagnostic_path_printer (diagnostic_context *context,
 	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;
     }
 }
 
@@ -561,7 +993,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;
@@ -585,7 +1017,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;
@@ -634,7 +1066,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;
@@ -716,7 +1148,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;
@@ -748,6 +1180,14 @@ test_interprocedural_path_2 (pretty_printer *event_pp)
      "                  | (8): entering `baz'\n"
      "                  |\n",
      pp_formatted_text (dc.printer));
+
+  test_diagnostic_context dc2;
+  html_printer to_html;
+  print_path_summary_as_html (&summary, &dc2, to_html.get_pp (), true);
+  char *html = to_html.get_html_output ();
+  ASSERT_STR_CONTAINS (html, "id=\"event-1\"");
+  ASSERT_STR_CONTAINS (html, "id=\"event-8\"");
+  free (html);
 }
 
 /* Verify that print_path_summary is sane in the face of a recursive
@@ -768,7 +1208,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


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH/RFC v2] Add -fdiagnostics-path-format=html [v2]
  2020-11-10 16:08 ` [PATCH/RFC v2] Add -fdiagnostics-path-format=html [v2] David Malcolm
@ 2020-12-02  0:13   ` Jeff Law
  0 siblings, 0 replies; 3+ messages in thread
From: Jeff Law @ 2020-12-02  0:13 UTC (permalink / raw)
  To: David Malcolm, gcc-patches



On 11/10/20 9:08 AM, David Malcolm via Gcc-patches wrote:
> Here's an updated version of the HTML output idea from:
>   https://gcc.gnu.org/pipermail/gcc-patches/2020-October/556848.html
>
> I reworked the HTML path output to show stack frames as well as just
> runs of events, using drop-shadows to give a 3D look.  The idea is to try
> to highlight the stack of frames as if it were an actual stack of
> overlapping cards.
>
> Updated HTML for the example from the earlier patch can be seen here:
>   https://dmalcolm.fedorapeople.org/gcc/2020-11-05/html-examples/test.c.path-1.html
> As before, other examples can be seen in that directory, such as:
>   Signal handler issue:
>     https://dmalcolm.fedorapeople.org/gcc/2020-11-05/html-examples/signal-1.c.path-1.html
>   Leak due to longjmp past a "free":
>     https://dmalcolm.fedorapeople.org/gcc/2020-11-05/html-examples/setjmp-7.c.path-1.html
>
> Other changes in v2:
> * switched j and k in keyboard navigation so that j is "next event"
> and k is "previous event"
> * fixed event element IDs, fixing a bug where it assumed they were in
>   ascending order
> * moved HTML printing code out of path_summary and event_range
> * more selftest coverage
>
> As before, this approach merely emits the path information; it doesn't
> capture the associated diagnostic.  I'm working on an alternate approach
> for v3 of the patch that does that; doing that requires reworking
> pretty_printer.
>
> I'm not sure on exactly the correct approach here; for v3 I'm
> experimenting with an "html" option for -fdiagnostics-format= which
> when selected would supplement the existing text output, by additionally
> writing out an HTML file for each diagnostic group, with path
> information captured there.  Doing it per group means that the classic
> "warning"/"note" pairs would be grouped together in that output.
>
> I'm not sure what that ought to do with paths though; part of the point
> of doing it is to have less verbose output on stderr whilst capturing
> the pertinent information "on the side" in the HTML file.
>
> Thoughts?
>
> Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu, FWIW.
>
> gcc/ChangeLog:
> 	* common.opt (diagnostic_path_format): Add DPF_HTML.
> 	* diagnostic-format-json.cc: Include "selftest.h".
> 	* 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_label_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 "label_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.  Add
> 	"original_range_idx" param and use it to initialize...
> 	(line_label::m_original_range_idx): ...this new field.
> 	(layout::print_any_labels): Pass original index of range to
> 	line_label ctor.  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.
> 	(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_label_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.
> 	* selftest-diagnostic.c (selftest::html_printer::html_printer):
> 	New ctor.
> 	(selftest::html_printer::get_html_output): New.
> 	* selftest-diagnostic.h (class selftest::html_printer): New.
> 	* 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::get_filename): New.
> 	(path_summary::path_summary): Add "colorize" param and use it when
> 	creating event_ranges.
> 	(class html_path_label_writer): New.
> 	(struct stack_frame): New.
> 	(begin_html_stack_frame): New.
> 	(end_html_stack_frame): New.
> 	(emit_svg_arrow): New.
> 	(print_event_range_as_html): New.
> 	(print_path_summary_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.  Add test of
> 	HTML output.
> 	(selftest::test_recursion): Update for new param of path_summary
> 	ctor.
> 	* 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.
As I mentioned in our call today, I think this one should be your
choice.  I can certainly see the value in using html for paths instead
of ascii-art.   Though we probably need to make sure the final
diagnostic gets into the html file so someone looking at the HTML knows
what ultimately triggered the diagnostic.


This also starts us down the path of potentially having diagnostics as
artifacts that the CI or build systems could save/scan.  For various
reasons I think this would be helpful ;-)

jeff


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2020-12-02  0:13 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-10-22 17:58 [PATCH/RFC] Add -fdiagnostics-path-format=html David Malcolm
2020-11-10 16:08 ` [PATCH/RFC v2] Add -fdiagnostics-path-format=html [v2] David Malcolm
2020-12-02  0:13   ` Jeff Law

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).