From fae4f5e6298f52f808de3346ffc3bd54c008efef Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 3 Jan 2019 12:53:14 -0500
Subject: [PATCH 20/61] FIXME: diagnostic paths / diagnostic_metadata / NN for
 line numbers

combination of path fixes; adding metadata, and interprocedural path visualization
tree-diagnostic.c: path-handling fixup
Fixups to diagnostic paths
add diagnostic-path-summary-format- plugin tests
fixups to gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
fixups to diagnostic_metadata and paths
simplify and consolidate diagnostic_path printing
add stray diagnostic path plugin tests
add missing diagnostic-metadata.h
add -fdiagnostics-nn-line-numbers
diagnostic path fixups
tree-diagnostic.c: WIP on fixing events with UNKNOWN_LOCATION
WIP on fix for locations of returned states
revamp of colorization of diagnostic_paths (needs dg test coverage)
more on diagnostic paths
update comment in diagnostic-path.h
tree-diagnostic.c: WIP on better handling of events with UNKNOWN_LOCATION
fixup to diagnostic-path.h
fix colorization when printing separate events
---
 gcc/c-family/c-format.c                            |   7 +
 gcc/c-family/c-format.h                            |   1 +
 gcc/common.opt                                     |  28 +
 gcc/cp/error.c                                     |   1 +
 gcc/diagnostic-color.c                             |   1 +
 gcc/diagnostic-core.h                              |  10 +
 gcc/diagnostic-event-id.h                          |  61 ++
 gcc/diagnostic-format-json.cc                      |  33 +-
 gcc/diagnostic-metadata.h                          |  42 ++
 gcc/diagnostic-path.h                              | 147 ++++
 gcc/diagnostic-show-locus.c                        |  86 ++-
 gcc/diagnostic.c                                   | 272 ++++++-
 gcc/diagnostic.def                                 |   5 +
 gcc/diagnostic.h                                   |  42 +-
 gcc/doc/invoke.texi                                | 125 ++++
 gcc/dwarf2out.c                                    |   1 +
 gcc/gcc-rich-location.h                            |   4 +-
 gcc/input.c                                        |   4 +-
 gcc/lto-wrapper.c                                  |   3 +
 gcc/opts.c                                         |  16 +
 gcc/pretty-print.c                                 |  32 +
 gcc/selftest-run-tests.c                           |   1 +
 gcc/selftest.h                                     |   1 +
 gcc/testsuite/gcc.dg/format/gcc_diag-10.c          |   6 +-
 .../gcc.dg/plugin/diagnostic-path-format-default.c | 140 ++++
 .../diagnostic-path-format-inline-events-1.c       | 140 ++++
 .../diagnostic-path-format-inline-events-2.c       | 147 ++++
 .../diagnostic-path-format-inline-events-3.c       | 146 ++++
 .../gcc.dg/plugin/diagnostic-path-format-none.c    |  43 ++
 .../diagnostic-path-format-separate-events.c       |  44 ++
 .../gcc.dg/plugin/diagnostic-test-paths-1.c        |  38 +
 .../gcc.dg/plugin/diagnostic-test-paths-2.c        |  58 ++
 .../gcc.dg/plugin/diagnostic-test-paths-3.c        |  38 +
 .../gcc.dg/plugin/diagnostic_plugin_test_paths.c   | 380 ++++++++++
 gcc/testsuite/gcc.dg/plugin/plugin.exp             |  10 +
 gcc/toplev.c                                       |   8 +
 gcc/tree-diagnostic.c                              | 804 ++++++++++++++++++++-
 libcpp/include/line-map.h                          |   7 +
 libcpp/line-map.c                                  |   3 +-
 39 files changed, 2870 insertions(+), 65 deletions(-)
 create mode 100644 gcc/diagnostic-event-id.h
 create mode 100644 gcc/diagnostic-metadata.h
 create mode 100644 gcc/diagnostic-path.h
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-default.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-1.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-2.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-3.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-none.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-separate-events.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c

diff --git a/gcc/c-family/c-format.c b/gcc/c-family/c-format.c
index 56fd2d4..d8ce594 100644
--- a/gcc/c-family/c-format.c
+++ b/gcc/c-family/c-format.c
@@ -65,6 +65,7 @@ struct function_format_info
 
 /* Initialized in init_dynamic_diag_info.  */
 static GTY(()) tree local_tree_type_node;
+static GTY(()) tree local_event_ptr_node;
 static GTY(()) tree local_gimple_ptr_node;
 static GTY(()) tree local_cgraph_node_ptr_node;
 static GTY(()) tree locus;
@@ -747,6 +748,7 @@ static const format_char_info asm_fprintf_char_table[] =
   { "s",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "pq", "cR", NULL }, \
   { "p",   1, STD_C89, { T89_V,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q",  "c",  NULL }, \
   { "r",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "",    "//cR",   NULL }, \
+  { "@",   1, STD_C89, { T_EVENT_PTR,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "", "\"",   NULL }, \
   { "<",   0, STD_C89, NOARGUMENTS, "",      "<",   NULL }, \
   { ">",   0, STD_C89, NOARGUMENTS, "",      ">",   NULL }, \
   { "'" ,  0, STD_C89, NOARGUMENTS, "",      "",    NULL }, \
@@ -4980,6 +4982,11 @@ init_dynamic_diag_info (void)
       || local_cgraph_node_ptr_node == void_type_node)
     local_cgraph_node_ptr_node = get_pointer_to_named_type ("cgraph_node");
 
+  /* Similar to the above but for diagnostic_event_id_t*.  */
+  if (!local_event_ptr_node
+      || local_event_ptr_node == void_type_node)
+    local_event_ptr_node = get_pointer_to_named_type ("diagnostic_event_id_t");
+
   static tree hwi;
 
   if (!hwi)
diff --git a/gcc/c-family/c-format.h b/gcc/c-family/c-format.h
index 6aa68df..45599d1 100644
--- a/gcc/c-family/c-format.h
+++ b/gcc/c-family/c-format.h
@@ -301,6 +301,7 @@ struct format_kind_info
 #define T_V	&void_type_node
 #define T89_G   { STD_C89, NULL, &local_gimple_ptr_node }
 #define T_CGRAPH_NODE   { STD_C89, NULL, &local_cgraph_node_ptr_node }
+#define T_EVENT_PTR    { STD_C89, NULL, &local_event_ptr_node }
 #define T89_T   { STD_C89, NULL, &local_tree_type_node }
 #define T89_V	{ STD_C89, NULL, T_V }
 #define T_W	&wchar_type_node
diff --git a/gcc/common.opt b/gcc/common.opt
index cea5c0e2..ba8509f 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1257,6 +1257,10 @@ fdiagnostics-show-line-numbers
 Common Var(flag_diagnostics_show_line_numbers) Init(1)
 Show line numbers in the left margin when showing source.
 
+fdiagnostics-nn-line-numbers
+Common Var(flag_diagnostics_nn_line_numbers) Init(0)
+Replace line numbers with 'NN' when showing source.
+
 fdiagnostics-color
 Common Alias(fdiagnostics-color=,always,never)
 ;
@@ -1335,6 +1339,30 @@ fdiagnostics-show-option
 Common Var(flag_diagnostics_show_option) Init(1)
 Amend appropriate diagnostic messages with the command line option that controls them.
 
+fdiagnostics-show-metadata
+Common Var(flag_diagnostics_show_metadata) Init(1)
+Print additional metadata for diagnostic messages, where available.
+
+fdiagnostics-path-format=
+Common Joined RejectNegative Var(flag_diagnostics_path_format) Enum(diagnostic_path_format) Init(DPF_INLINE_EVENTS)
+Specify how to print any control-flow path associated with a diagnostic.
+
+Enum
+Name(diagnostic_path_format) Type(int)
+
+EnumValue
+Enum(diagnostic_path_format) String(none) Value(DPF_NONE)
+
+EnumValue
+Enum(diagnostic_path_format) String(separate-events) Value(DPF_SEPARATE_EVENTS)
+
+EnumValue
+Enum(diagnostic_path_format) String(inline-events) Value(DPF_INLINE_EVENTS)
+
+fdiagnostics-show-path-depths
+Common Var(flag_diagnostics_show_path_depths) Init(0)
+Show stack depths of events in paths.
+
 fdiagnostics-minimum-margin-width=
 Common Joined UInteger Var(diagnostics_minimum_margin_width) Init(6)
 Set minimum width of left margin of source code when showing source.
diff --git a/gcc/cp/error.c b/gcc/cp/error.c
index 9b19de9..8ee2cb0 100644
--- a/gcc/cp/error.c
+++ b/gcc/cp/error.c
@@ -4051,6 +4051,7 @@ defer_phase_2_of_type_diff (deferred_printed_type *deferred,
    %I   type difference (to).
    %K   tree
    %L	language as used in extern "lang".
+   %n	diagnostic_event_id_ptr
    %O	binary operator.
    %P   function parameter whose position is indicated by an integer.
    %Q	assignment operator.
diff --git a/gcc/diagnostic-color.c b/gcc/diagnostic-color.c
index abc919f..e5bae09 100644
--- a/gcc/diagnostic-color.c
+++ b/gcc/diagnostic-color.c
@@ -90,6 +90,7 @@ static struct color_cap color_dict[] =
   { "range2", SGR_SEQ (COLOR_FG_BLUE), 6, false },
   { "locus", SGR_SEQ (COLOR_BOLD), 5, false },
   { "quote", SGR_SEQ (COLOR_BOLD), 5, false },
+  { "path", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_CYAN), 4, false },
   { "fixit-insert", SGR_SEQ (COLOR_FG_GREEN), 12, false },
   { "fixit-delete", SGR_SEQ (COLOR_FG_RED), 12, false },
   { "diff-filename", SGR_SEQ (COLOR_BOLD), 13, false },
diff --git a/gcc/diagnostic-core.h b/gcc/diagnostic-core.h
index efafde4..2e7f120 100644
--- a/gcc/diagnostic-core.h
+++ b/gcc/diagnostic-core.h
@@ -45,6 +45,9 @@ class auto_diagnostic_group
   ~auto_diagnostic_group ();
 };
 
+/* Forward decl.  */
+class diagnostic_metadata; /* See diagnostic-metadata.h.  */
+
 extern const char *progname;
 
 extern const char *trim_filename (const char *);
@@ -78,6 +81,9 @@ extern bool warning_at (location_t, int, const char *, ...)
     ATTRIBUTE_GCC_DIAG(3,4);
 extern bool warning_at (rich_location *, int, const char *, ...)
     ATTRIBUTE_GCC_DIAG(3,4);
+extern bool warning_at (rich_location *, const diagnostic_metadata &, int,
+			const char *, ...)
+    ATTRIBUTE_GCC_DIAG(4,5);
 extern void error (const char *, ...) ATTRIBUTE_GCC_DIAG(1,2);
 extern void error_n (location_t, unsigned HOST_WIDE_INT, const char *,
 		     const char *, ...)
@@ -109,6 +115,10 @@ extern bool emit_diagnostic (diagnostic_t, rich_location *, int,
 			     const char *, ...) ATTRIBUTE_GCC_DIAG(4,5);
 extern bool emit_diagnostic_valist (diagnostic_t, location_t, int, const char *,
 				    va_list *) ATTRIBUTE_GCC_DIAG (4,0);
+extern bool emit_diagnostic_valist (diagnostic_t, rich_location *,
+				    const diagnostic_metadata *metadata,
+				    int, const char *, va_list *)
+  ATTRIBUTE_GCC_DIAG (5,0);
 extern bool seen_error (void);
 
 #ifdef BUFSIZ
diff --git a/gcc/diagnostic-event-id.h b/gcc/diagnostic-event-id.h
new file mode 100644
index 0000000..7f0f4e4
--- /dev/null
+++ b/gcc/diagnostic-event-id.h
@@ -0,0 +1,61 @@
+/* A class for referring to events within a diagnostic_path.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+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/>.  */
+
+#ifndef GCC_DIAGNOSTIC_EVENT_ID_H
+#define GCC_DIAGNOSTIC_EVENT_ID_H
+
+/* A class for referring to events within a diagnostic_path.
+
+   They are stored as 0-based offsets into the events, but
+   printed (e.g. via %@) as 1-based numbers.
+
+   For example, a 3-event path has event offsets 0, 1, and 2,
+   which would be shown to the user as "(1)", "(2)" and "(3)".
+
+   This has its own header so that pretty-print.c can use this
+   to implement "%@" without bringing in all of diagnostic_path
+   (which e.g. refers to "tree").  */
+
+class diagnostic_event_id_t
+{
+ public:
+  diagnostic_event_id_t () : m_index (UNKNOWN_EVENT_IDX) {}
+  diagnostic_event_id_t (int zero_based_idx) : m_index (zero_based_idx) {}
+
+  bool known_p () const { return m_index != UNKNOWN_EVENT_IDX; }
+
+  int one_based () const
+  {
+    gcc_assert (known_p ());
+    return m_index + 1;
+  }
+
+ private:
+  static const int UNKNOWN_EVENT_IDX = -1;
+  int m_index; // zero-based
+};
+
+/* A pointer to a diagnostic_event_id_t, for use with the "%@" format
+   code, which will print a 1-based representation for it, with suitable
+   colorization, e.g. "(1)".
+   The %@ format code requires that known_p be true for the event ID. */
+typedef diagnostic_event_id_t *diagnostic_event_id_ptr;
+
+#endif /* ! GCC_DIAGNOSTIC_EVENT_ID_H */
diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc
index eb99952..8d833a4 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 "diagnostic-metadata.h"
 #include "json.h"
 #include "selftest.h"
 
@@ -41,7 +42,7 @@ static json::array *cur_children_array;
 
 /* Generate a JSON object for LOC.  */
 
-static json::object *
+json::value *
 json_from_expanded_location (location_t loc)
 {
   expanded_location exploc = expand_location (loc);
@@ -103,6 +104,19 @@ json_from_fixit_hint (const fixit_hint *hint)
   return fixit_obj;
 }
 
+/* Generate a JSON object for METADATA.  */
+
+static json::object *
+json_from_metadata (const diagnostic_metadata *metadata)
+{
+  json::object *metadata_obj = new json::object ();
+
+  if (metadata->get_cwe ())
+    metadata_obj->set ("cwe", new json::number (metadata->get_cwe ()));
+
+  return metadata_obj;
+}
+
 /* No-op implementation of "begin_diagnostic" for JSON output.  */
 
 static void
@@ -211,6 +225,19 @@ json_end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic,
      TODO: functions
      TODO: inlining information
      TODO: macro expansion information.  */
+
+  const diagnostic_path *path = richloc->get_path ();
+  if (path && context->make_json_for_path)
+    {
+      json::value *path_value = context->make_json_for_path (context, path);
+      diag_obj->set ("path", path_value);
+    }
+
+  if (diagnostic->metadata)
+    {
+      json::object *metadata_obj = json_from_metadata (diagnostic->metadata);
+      diag_obj->set ("metadata", metadata_obj);
+    }
 }
 
 /* No-op implementation of "begin_group_cb" for JSON output.  */
@@ -267,6 +294,10 @@ diagnostic_output_format_init (diagnostic_context *context,
 	context->begin_group_cb = json_begin_group;
 	context->end_group_cb =  json_end_group;
 	context->final_cb =  json_final_cb;
+	context->print_path = NULL; /* handled in json_end_diagnostic.  */
+
+	/* The metadata is handled in JSON format, rather than as text.  */
+	context->show_metadata = false;
 
 	/* The option is handled in JSON format, rather than as text.  */
 	context->show_option_requested = false;
diff --git a/gcc/diagnostic-metadata.h b/gcc/diagnostic-metadata.h
new file mode 100644
index 0000000..a759d44
--- /dev/null
+++ b/gcc/diagnostic-metadata.h
@@ -0,0 +1,42 @@
+/* Additional metadata for a diagnostic.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+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/>.  */
+
+#ifndef GCC_DIAGNOSTIC_METADATA_H
+#define GCC_DIAGNOSTIC_METADATA_H
+
+/* A bundle of additional metadata that can be associated with a
+   diagnostic.
+
+   Currently this only supports associating a CWE identifier with a
+   diagnostic.  */
+
+class diagnostic_metadata
+{
+ public:
+  diagnostic_metadata () : m_cwe (0) {}
+
+  void add_cwe (int cwe) { m_cwe = cwe; }
+  int get_cwe () const { return m_cwe; }
+
+ private:
+  int m_cwe;
+};
+
+#endif /* ! GCC_DIAGNOSTIC_METADATA_H */
diff --git a/gcc/diagnostic-path.h b/gcc/diagnostic-path.h
new file mode 100644
index 0000000..fae8691
--- /dev/null
+++ b/gcc/diagnostic-path.h
@@ -0,0 +1,147 @@
+/* Paths through the code associated with a diagnostic.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+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/>.  */
+
+#ifndef GCC_DIAGNOSTIC_PATH_H
+#define GCC_DIAGNOSTIC_PATH_H
+
+#include "diagnostic.h" /* for ATTRIBUTE_GCC_DIAG.  */
+
+/* A diagnostic_path is an optional additional piece of metadata associated
+   with a diagnostic (via its rich_location).
+
+   It describes a sequence of events predicted by the compiler that
+   lead to the problem occurring, with their locations in the user's source,
+   and text descriptions.
+
+   For example, the following error has a 3-event path:
+
+     test.c: In function 'make_a_list_of_random_ints_badly':
+     test.c:29:5: error: passing NULL as argument 1 to 'PyList_Append' which
+       requires a non-NULL parameter
+        29 |     PyList_Append(list, item);
+           |     ^~~~~~~~~~~~~~~~~~~~~~~~~
+       'make_a_list_of_random_ints_badly': events 1-3
+                        |
+                        |   25 |   list = PyList_New(0);
+                        |      |          ^~~~~~~~~~~~~
+                        |      |          |
+                        |      |          (1) when 'PyList_New' fails, returning NULL
+                        |   26 |
+                        |   27 |   for (i = 0; i < count; i++) {
+                        |      |   ~~~
+                        |      |   |
+                        |      |   (2) when 'i < count'
+                        |   28 |     item = PyLong_FromLong(random());
+                        |   29 |     PyList_Append(list, item);
+                        |      |     ~~~~~~~~~~~~~~~~~~~~~~~~~
+                        |      |     |
+                        |      |     (3) when calling 'PyList_Append', passing NULL as argument 1
+                        |
+
+    The diagnostic-printing code has consolidated the path into a single
+    run of events, since all the events are near each other and within the same
+    function; more complicated examples (such as interprocedural paths)
+    might be printed as multiple runs of events.  */
+
+/* Abstract base classes, describing events within a path, and the paths
+   themselves.  */
+
+/* One event within a diagnostic_path.  */
+
+class diagnostic_event
+{
+ public:
+  virtual ~diagnostic_event () {}
+
+  virtual location_t get_location () const = 0;
+
+  virtual tree get_fndecl () const = 0;
+
+  /* Stack depth, so that consumers can visualizes the interprocedural
+     calls, returns, and frame nesting.  */
+  virtual int get_stack_depth () const = 0;
+
+  /* Get a localized (and possibly colorized) description of this event.  */
+  virtual label_text get_desc (bool can_colorize) const = 0;
+};
+
+/* Abstract base class for getting at a sequence of events.  */
+
+class diagnostic_path
+{
+ public:
+  virtual ~diagnostic_path () {}
+  virtual unsigned num_events () const = 0;
+  virtual const diagnostic_event & get_event (int idx) const = 0;
+
+  bool interprocedural_p () const;
+};
+
+/* Concrete subclasses.  */
+
+/* A simple implementation of diagnostic_event.  */
+
+class simple_diagnostic_event : public diagnostic_event
+{
+ public:
+  simple_diagnostic_event (location_t loc, tree fndecl, int depth,
+			   const char *desc);
+  ~simple_diagnostic_event ();
+
+  location_t get_location () const FINAL OVERRIDE { return m_loc; }
+  tree get_fndecl () const FINAL OVERRIDE { return m_fndecl; }
+  int get_stack_depth () const FINAL OVERRIDE { return m_depth; }
+  label_text get_desc (bool) const FINAL OVERRIDE
+  {
+    return label_text::borrow (m_desc);
+  }
+
+ private:
+  location_t m_loc;
+  tree m_fndecl;
+  int m_depth;
+  char *m_desc; // has been i18n-ed and formatted
+};
+
+/* A simple implementation of diagnostic_path, as a vector of
+   simple_diagnostic_event instances.  */
+
+class simple_diagnostic_path : public diagnostic_path
+{
+ public:
+  simple_diagnostic_path (pretty_printer *event_pp)
+  : m_event_pp (event_pp) {}
+
+  unsigned num_events () const FINAL OVERRIDE;
+  const diagnostic_event & get_event (int idx) const FINAL OVERRIDE;
+
+  void add_event (location_t loc, tree fndecl, int depth, const char *fmt, ...)
+    ATTRIBUTE_GCC_DIAG(5,6);
+
+ private:
+  auto_delete_vec<simple_diagnostic_event> m_events;
+
+  /* (for use by add_event).  */
+  pretty_printer *m_event_pp;
+};
+
+extern void debug (diagnostic_path *path);
+
+#endif /* ! GCC_DIAGNOSTIC_PATH_H */
diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
index 0cce82f..bc08847 100644
--- a/gcc/diagnostic-show-locus.c
+++ b/gcc/diagnostic-show-locus.c
@@ -86,7 +86,17 @@ class colorizer
 	     diagnostic_t diagnostic_kind);
   ~colorizer ();
 
-  void set_range (int range_idx) { set_state (range_idx); }
+  void set_range (int range_idx)
+  {
+    /* Normally we emphasize the primary location, then alternate between
+       two colors for the secondary locations.
+       But if we're printing a run of events in a diagnostic path, that
+       makes no sense, so print all of them with the same colorization.  */
+    if (m_diagnostic_kind == DK_DIAGNOSTIC_PATH)
+      set_state (0);
+    else
+      set_state (range_idx);
+  }
   void set_normal_text () { set_state (STATE_NORMAL_TEXT); }
   void set_fixit_insert () { set_state (STATE_FIXIT_INSERT); }
   void set_fixit_delete () { set_state (STATE_FIXIT_DELETE); }
@@ -303,6 +313,8 @@ class layout
   bool m_colorize_source_p;
   bool m_show_labels_p;
   bool m_show_line_numbers_p;
+  bool m_use_nn_for_line_numbers_p;
+  bool m_diagnostic_path_p;
   auto_vec <layout_range> m_layout_ranges;
   auto_vec <const fixit_hint *> m_fixit_hints;
   auto_vec <line_span> m_line_spans;
@@ -849,6 +861,8 @@ layout::layout (diagnostic_context * context,
   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),
+  m_use_nn_for_line_numbers_p (context->use_nn_for_line_numbers_p),
+  m_diagnostic_path_p (diagnostic_kind == DK_DIAGNOSTIC_PATH),
   m_layout_ranges (richloc->get_num_locations ()),
   m_fixit_hints (richloc->get_num_fixit_hints ()),
   m_line_spans (1 + richloc->get_num_locations ()),
@@ -884,7 +898,7 @@ layout::layout (diagnostic_context * context,
   int highest_line = last_span->m_last_line;
   if (highest_line < 0)
     highest_line = 0;
-  m_linenum_width = num_digits (highest_line);
+  m_linenum_width = num_digits (highest_line, m_use_nn_for_line_numbers_p);
   /* If we're showing jumps in the line-numbering, allow at least 3 chars.  */
   if (m_line_spans.length () > 1)
     m_linenum_width = MAX (m_linenum_width, 3);
@@ -1272,10 +1286,13 @@ layout::print_source_line (linenum_type row, const char *line, int line_width,
 
   if (m_show_line_numbers_p)
     {
-      int width = num_digits (row);
+      int width = num_digits (row, m_use_nn_for_line_numbers_p);
       for (int i = 0; i < m_linenum_width - width; i++)
 	pp_space (m_pp);
-      pp_printf (m_pp, "%i | ", row);
+      if (m_use_nn_for_line_numbers_p)
+	pp_printf (m_pp, "%s | ", "NN");
+      else
+	pp_printf (m_pp, "%i | ", row);
     }
   else
     pp_space (m_pp);
@@ -1564,16 +1581,19 @@ layout::print_any_labels (linenum_type row)
 
 	    if (label_line == label->m_label_line)
 	      {
-		gcc_assert (column <= label->m_column);
+		//gcc_assert (column <= label->m_column);
 		move_to_column (&column, label->m_column, true);
-		m_colorizer.set_range (label->m_state_idx);
+		/* Colorize the text, unless it's for events in a
+		   diagnostic_path.  */
+		if (!m_diagnostic_path_p)
+		  m_colorizer.set_range (label->m_state_idx);
 		pp_string (m_pp, label->m_text.m_buffer);
 		m_colorizer.set_normal_text ();
 		column += label->m_length;
 	      }
 	    else if (label->m_has_vbar)
 	      {
-		gcc_assert (column <= label->m_column);
+		//gcc_assert (column <= label->m_column);
 		move_to_column (&column, label->m_column, true);
 		m_colorizer.set_range (label->m_state_idx);
 		pp_character (m_pp, '|');
@@ -2259,7 +2279,9 @@ layout::print_line (linenum_type row)
    Otherwise return false.  */
 
 bool
-gcc_rich_location::add_location_if_nearby (location_t loc)
+gcc_rich_location::add_location_if_nearby (location_t loc,
+					   bool restrict_to_current_line_spans,
+					   const range_label *label)
 {
   /* Use the layout location-handling logic to sanitize LOC,
      filtering it to the current line spans within a temporary
@@ -2268,10 +2290,11 @@ gcc_rich_location::add_location_if_nearby (location_t loc)
   location_range loc_range;
   loc_range.m_loc = loc;
   loc_range.m_range_display_kind = SHOW_RANGE_WITHOUT_CARET;
-  if (!layout.maybe_add_location_range (&loc_range, 0, true))
+  if (!layout.maybe_add_location_range (&loc_range, 0,
+					restrict_to_current_line_spans))
     return false;
 
-  add_range (loc);
+  add_range (loc, SHOW_RANGE_WITHOUT_CARET, label);
   return true;
 }
 
@@ -3825,19 +3848,36 @@ test_line_numbers_multiline_range ()
     = linemap_position_for_line_and_column (line_table, ord_map, 11, 4);
   location_t loc = make_location (caret, start, finish);
 
-  test_diagnostic_context dc;
-  dc.show_line_numbers_p = true;
-  dc.min_margin_width = 0;
-  gcc_rich_location richloc (loc);
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-		" 9 | this is line 9\n"
-		"   |         ~~~~~~\n"
-		"10 | this is line 10\n"
-		"   | ~~~~~^~~~~~~~~~\n"
-		"11 | this is line 11\n"
-		"   | ~~~~  \n",
-		pp_formatted_text (dc.printer));
+  {
+    test_diagnostic_context dc;
+    dc.show_line_numbers_p = true;
+    dc.min_margin_width = 0;
+    gcc_rich_location richloc (loc);
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("\n"
+		  " 9 | this is line 9\n"
+		  "   |         ~~~~~~\n"
+		  "10 | this is line 10\n"
+		  "   | ~~~~~^~~~~~~~~~\n"
+		  "11 | this is line 11\n"
+		  "   | ~~~~  \n",
+		  pp_formatted_text (dc.printer));
+  }
+
+  /* Verify that obscuring line numbers via "NN" works (and always uses
+     at least two columns).  */
+  {
+    test_diagnostic_context dc;
+    dc.show_line_numbers_p = true;
+    dc.use_nn_for_line_numbers_p = true;
+    dc.min_margin_width = 0;
+    gcc_rich_location richloc (start);
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("\n"
+		  "NN | this is line 9\n"
+		  "   |         ^\n",
+		  pp_formatted_text (dc.printer));
+  }
 }
 
 /* Run all of the selftests within this file.  */
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index ca92268..25d699e 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -32,6 +32,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic.h"
 #include "diagnostic-color.h"
 #include "diagnostic-url.h"
+#include "diagnostic-metadata.h"
+#include "diagnostic-path.h"
 #include "edit-context.h"
 #include "selftest.h"
 #include "selftest-diagnostic.h"
@@ -58,11 +60,13 @@ along with GCC; see the file COPYING3.  If not see
 #define permissive_error_option(DC) ((DC)->opt_permissive)
 
 /* Prototypes.  */
-static bool diagnostic_impl (rich_location *, int, const char *,
-			     va_list *, diagnostic_t) ATTRIBUTE_GCC_DIAG(3,0);
-static bool diagnostic_n_impl (rich_location *, int, unsigned HOST_WIDE_INT,
+static bool diagnostic_impl (rich_location *, const diagnostic_metadata *,
+			     int, const char *,
+			     va_list *, diagnostic_t) ATTRIBUTE_GCC_DIAG(4,0);
+static bool diagnostic_n_impl (rich_location *, const diagnostic_metadata *,
+			       int, unsigned HOST_WIDE_INT,
 			       const char *, const char *, va_list *,
-			       diagnostic_t) ATTRIBUTE_GCC_DIAG(5,0);
+			       diagnostic_t) ATTRIBUTE_GCC_DIAG(6,0);
 
 static void error_recursion (diagnostic_context *) ATTRIBUTE_NORETURN;
 static void real_abort (void) ATTRIBUTE_NORETURN;
@@ -183,6 +187,9 @@ diagnostic_initialize (diagnostic_context *context, int n_opts)
   diagnostic_set_caret_max_width (context, pp_line_cutoff (context->printer));
   for (i = 0; i < rich_location::STATICALLY_ALLOCATED_RANGES; i++)
     context->caret_chars[i] = '^';
+  context->show_metadata = false;
+  context->path_format = DPF_NONE;
+  context->show_path_depths = false;
   context->show_option_requested = false;
   context->abort_on_error = false;
   context->show_column = false;
@@ -209,6 +216,7 @@ diagnostic_initialize (diagnostic_context *context, int n_opts)
   context->colorize_source_p = false;
   context->show_labels_p = false;
   context->show_line_numbers_p = false;
+  context->use_nn_for_line_numbers_p = false;
   context->min_margin_width = 0;
   context->show_ruler_p = false;
   context->parseable_fixits_p = false;
@@ -299,6 +307,7 @@ diagnostic_set_info_translated (diagnostic_info *diagnostic, const char *msg,
   diagnostic->message.format_spec = msg;
   diagnostic->message.m_richloc = richloc;
   diagnostic->richloc = richloc;
+  diagnostic->metadata = NULL;
   diagnostic->kind = kind;
   diagnostic->option_index = 0;
 }
@@ -653,6 +662,38 @@ diagnostic_report_current_module (diagnostic_context *context, location_t where)
     }
 }
 
+/* If DIAGNOSTIC has a diagnostic_path and CONTEXT supports printing paths,
+   print the path.  */
+
+void
+diagnostic_show_any_path (diagnostic_context *context,
+			  diagnostic_info *diagnostic)
+{
+  const diagnostic_path *path = diagnostic->richloc->get_path ();
+  if (!path)
+    return;
+
+  if (context->print_path)
+    context->print_path (context, path);
+}
+
+/* Return true if the events in this path involve more than one
+   function, or false if it is purely intraprocedural.  */
+
+bool
+diagnostic_path::interprocedural_p () const
+{
+  const unsigned num = num_events ();
+  for (unsigned i = 0; i < num; i++)
+    {
+      if (get_event (i).get_fndecl () != get_event (0).get_fndecl ())
+	return true;
+      if (get_event (i).get_stack_depth () != get_event (0).get_stack_depth ())
+	return true;
+    }
+  return false;
+}
+
 void
 default_diagnostic_starter (diagnostic_context *context,
 			    diagnostic_info *diagnostic)
@@ -897,6 +938,47 @@ update_effective_level_from_pragmas (diagnostic_context *context,
   return diag_class;
 }
 
+/* Generate a URL string describing CWE.  The caller is responsible for
+   freeing the string.  */
+
+static char *
+get_cwe_url (int cwe)
+{
+  return xasprintf ("https://cwe.mitre.org/data/definitions/%i.html", cwe);
+}
+
+/* If DIAGNOSTIC has metadata, print it.
+
+   For example, if the diagnostic metadata associates it with CWE-119,
+   " [CWE-119]" will be printed, suitably colorized, and with a URL of a
+   description of the security issue.  */
+
+static void
+print_any_metadata (diagnostic_context *context,
+		    const diagnostic_info *diagnostic)
+{
+  if (diagnostic->metadata == NULL)
+    return;
+
+  int cwe = diagnostic->metadata->get_cwe ();
+  if (cwe)
+    {
+      pretty_printer *pp = context->printer;
+      char *saved_prefix = pp_take_prefix (context->printer);
+      pp_string (pp, " [");
+      pp_string (pp, colorize_start (pp_show_color (pp),
+				     diagnostic_kind_color[diagnostic->kind]));
+      char *cwe_url = get_cwe_url (cwe);
+      pp_begin_url (pp, cwe_url);
+      free (cwe_url);
+      pp_printf (pp, "CWE-%i", cwe);
+      pp_set_prefix (context->printer, saved_prefix);
+      pp_end_url (pp);
+      pp_string (pp, colorize_stop (pp_show_color (pp)));
+      pp_character (pp, ']');
+    }
+}
+
 /* Print any metadata about the option used to control DIAGNOSTIC to CONTEXT's
    printer, e.g. " [-Werror=uninitialized]".
    Subroutine of diagnostic_report_diagnostic.  */
@@ -1057,6 +1139,8 @@ diagnostic_report_diagnostic (diagnostic_context *context,
   pp_format (context->printer, &diagnostic->message);
   (*diagnostic_starter (context)) (context, diagnostic);
   pp_output_formatted_text (context->printer);
+  if (context->show_metadata)
+    print_any_metadata (context, diagnostic);
   if (context->show_option_requested)
     print_option_information (context, diagnostic, orig_diag_kind);
   (*diagnostic_finalizer (context)) (context, diagnostic, orig_diag_kind);
@@ -1074,18 +1158,23 @@ diagnostic_report_diagnostic (diagnostic_context *context,
 
   context->lock--;
 
+  diagnostic_show_any_path (context, diagnostic);
+
   return true;
 }
 
 /* Get the number of digits in the decimal representation of VALUE.  */
 
 int
-num_digits (int value)
+num_digits (int value, bool use_nn_p)
 {
   /* Perhaps simpler to use log10 for this, but doing it this way avoids
      using floating point.  */
   gcc_assert (value >= 0);
 
+  if (use_nn_p)
+    return 2;
+
   if (value == 0)
     return 1;
 
@@ -1181,8 +1270,8 @@ diagnostic_append_note (diagnostic_context *context,
    permerror, error, error_at, error_at, sorry, fatal_error, internal_error,
    and internal_error_no_backtrace, as documented and defined below.  */
 static bool
-diagnostic_impl (rich_location *richloc, int opt,
-		 const char *gmsgid,
+diagnostic_impl (rich_location *richloc, const diagnostic_metadata *metadata,
+		 int opt, const char *gmsgid,
 		 va_list *ap, diagnostic_t kind)
 {
   diagnostic_info diagnostic;
@@ -1198,13 +1287,15 @@ diagnostic_impl (rich_location *richloc, int opt,
       if (kind == DK_WARNING || kind == DK_PEDWARN)
 	diagnostic.option_index = opt;
     }
+  diagnostic.metadata = metadata;
   return diagnostic_report_diagnostic (global_dc, &diagnostic);
 }
 
 /* Implement inform_n, warning_n, and error_n, as documented and
    defined below.  */
 static bool
-diagnostic_n_impl (rich_location *richloc, int opt, unsigned HOST_WIDE_INT n,
+diagnostic_n_impl (rich_location *richloc, const diagnostic_metadata *metadata,
+		   int opt, unsigned HOST_WIDE_INT n,
 		   const char *singular_gmsgid,
 		   const char *plural_gmsgid,
 		   va_list *ap, diagnostic_t kind)
@@ -1224,6 +1315,7 @@ diagnostic_n_impl (rich_location *richloc, int opt, unsigned HOST_WIDE_INT n,
   diagnostic_set_info_translated (&diagnostic, text, ap, richloc, kind);
   if (kind == DK_WARNING)
     diagnostic.option_index = opt;
+  diagnostic.metadata = metadata;
   return diagnostic_report_diagnostic (global_dc, &diagnostic);
 }
 
@@ -1237,7 +1329,7 @@ emit_diagnostic (diagnostic_t kind, location_t location, int opt,
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, kind);
+  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, kind);
   va_end (ap);
   return ret;
 }
@@ -1251,7 +1343,7 @@ emit_diagnostic (diagnostic_t kind, rich_location *richloc, int opt,
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap, kind);
+  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap, kind);
   va_end (ap);
   return ret;
 }
@@ -1263,7 +1355,18 @@ emit_diagnostic_valist (diagnostic_t kind, location_t location, int opt,
 			const char *gmsgid, va_list *ap)
 {
   rich_location richloc (line_table, location);
-  return diagnostic_impl (&richloc, opt, gmsgid, ap, kind);
+  return diagnostic_impl (&richloc, NULL, opt, gmsgid, ap, kind);
+}
+
+/* Wrapper around diagnostic_impl taking a va_list parameter.  */
+
+bool
+emit_diagnostic_valist (diagnostic_t kind, rich_location *richloc,
+			const diagnostic_metadata *metadata,
+			int opt,
+			const char *gmsgid, va_list *ap)
+{
+  return diagnostic_impl (richloc, metadata, opt, gmsgid, ap, kind);
 }
 
 /* An informative note at LOCATION.  Use this for additional details on an error
@@ -1275,7 +1378,7 @@ inform (location_t location, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_NOTE);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_NOTE);
   va_end (ap);
 }
 
@@ -1288,7 +1391,7 @@ inform (rich_location *richloc, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  diagnostic_impl (richloc, -1, gmsgid, &ap, DK_NOTE);
+  diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_NOTE);
   va_end (ap);
 }
 
@@ -1302,7 +1405,7 @@ inform_n (location_t location, unsigned HOST_WIDE_INT n,
   va_start (ap, plural_gmsgid);
   auto_diagnostic_group d;
   rich_location richloc (line_table, location);
-  diagnostic_n_impl (&richloc, -1, n, singular_gmsgid, plural_gmsgid,
+  diagnostic_n_impl (&richloc, NULL, -1, n, singular_gmsgid, plural_gmsgid,
 		     &ap, DK_NOTE);
   va_end (ap);
 }
@@ -1317,7 +1420,7 @@ warning (int opt, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, DK_WARNING);
+  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, DK_WARNING);
   va_end (ap);
   return ret;
 }
@@ -1333,7 +1436,7 @@ warning_at (location_t location, int opt, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, DK_WARNING);
+  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, DK_WARNING);
   va_end (ap);
   return ret;
 }
@@ -1348,7 +1451,25 @@ warning_at (rich_location *richloc, int opt, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap, DK_WARNING);
+  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap, DK_WARNING);
+  va_end (ap);
+  return ret;
+}
+
+/* Same as "warning at" above, but using METADATA.  */
+
+bool
+warning_at (rich_location *richloc, const diagnostic_metadata &metadata,
+	    int opt, const char *gmsgid, ...)
+{
+  gcc_assert (richloc);
+
+  auto_diagnostic_group d;
+  va_list ap;
+  va_start (ap, gmsgid);
+  bool ret
+    = diagnostic_impl (richloc, &metadata, opt, gmsgid, &ap,
+		       DK_WARNING);
   va_end (ap);
   return ret;
 }
@@ -1364,7 +1485,7 @@ warning_n (rich_location *richloc, int opt, unsigned HOST_WIDE_INT n,
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, plural_gmsgid);
-  bool ret = diagnostic_n_impl (richloc, opt, n,
+  bool ret = diagnostic_n_impl (richloc, NULL, opt, n,
 				singular_gmsgid, plural_gmsgid,
 				&ap, DK_WARNING);
   va_end (ap);
@@ -1383,7 +1504,7 @@ warning_n (location_t location, int opt, unsigned HOST_WIDE_INT n,
   va_list ap;
   va_start (ap, plural_gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_n_impl (&richloc, opt, n,
+  bool ret = diagnostic_n_impl (&richloc, NULL, opt, n,
 				singular_gmsgid, plural_gmsgid,
 				&ap, DK_WARNING);
   va_end (ap);
@@ -1410,7 +1531,7 @@ pedwarn (location_t location, int opt, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, DK_PEDWARN);
+  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, DK_PEDWARN);
   va_end (ap);
   return ret;
 }
@@ -1425,7 +1546,7 @@ pedwarn (rich_location *richloc, int opt, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap, DK_PEDWARN);
+  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap, DK_PEDWARN);
   va_end (ap);
   return ret;
 }
@@ -1444,7 +1565,7 @@ permerror (location_t location, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_PERMERROR);
+  bool ret = diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_PERMERROR);
   va_end (ap);
   return ret;
 }
@@ -1459,7 +1580,7 @@ permerror (rich_location *richloc, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  bool ret = diagnostic_impl (richloc, -1, gmsgid, &ap, DK_PERMERROR);
+  bool ret = diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_PERMERROR);
   va_end (ap);
   return ret;
 }
@@ -1473,7 +1594,7 @@ error (const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ERROR);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
   va_end (ap);
 }
 
@@ -1487,7 +1608,7 @@ error_n (location_t location, unsigned HOST_WIDE_INT n,
   va_list ap;
   va_start (ap, plural_gmsgid);
   rich_location richloc (line_table, location);
-  diagnostic_n_impl (&richloc, -1, n, singular_gmsgid, plural_gmsgid,
+  diagnostic_n_impl (&richloc, NULL, -1, n, singular_gmsgid, plural_gmsgid,
 		     &ap, DK_ERROR);
   va_end (ap);
 }
@@ -1500,7 +1621,7 @@ error_at (location_t loc, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, loc);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ERROR);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
   va_end (ap);
 }
 
@@ -1514,7 +1635,7 @@ error_at (rich_location *richloc, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  diagnostic_impl (richloc, -1, gmsgid, &ap, DK_ERROR);
+  diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
   va_end (ap);
 }
 
@@ -1528,7 +1649,7 @@ sorry (const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_SORRY);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_SORRY);
   va_end (ap);
 }
 
@@ -1540,7 +1661,7 @@ sorry_at (location_t loc, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, loc);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_SORRY);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_SORRY);
   va_end (ap);
 }
 
@@ -1562,7 +1683,7 @@ fatal_error (location_t loc, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, loc);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_FATAL);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_FATAL);
   va_end (ap);
 
   gcc_unreachable ();
@@ -1579,7 +1700,7 @@ internal_error (const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ICE);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ICE);
   va_end (ap);
 
   gcc_unreachable ();
@@ -1595,7 +1716,7 @@ internal_error_no_backtrace (const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ICE_NOBT);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ICE_NOBT);
   va_end (ap);
 
   gcc_unreachable ();
@@ -1676,6 +1797,91 @@ auto_diagnostic_group::~auto_diagnostic_group ()
     }
 }
 
+/* Implementation of diagnostic_path::num_events vfunc for
+   simple_diagnostic_path: simply get the number of events in the vec.  */
+
+unsigned
+simple_diagnostic_path::num_events () const
+{
+  return m_events.length ();
+}
+
+/* Implementation of diagnostic_path::get_event vfunc for
+   simple_diagnostic_path: simply return the event in the vec.  */
+
+const diagnostic_event &
+simple_diagnostic_path::get_event (int idx) const
+{
+  return *m_events[idx];
+}
+
+/* Add an event to this path at LOC within function FNDECL at
+   stack depth DEPTH.
+
+   Use m_context's printer to format FMT, as the text of the new
+   event.  */
+
+void
+simple_diagnostic_path::add_event (location_t loc, tree fndecl, int depth,
+				   const char *fmt, ...)
+{
+  pretty_printer *pp = m_event_pp;
+  pp_clear_output_area (pp);
+
+  text_info ti;
+  rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+
+  va_list ap;
+
+  va_start (ap, fmt);
+
+  ti.format_spec = _(fmt);
+  ti.args_ptr = &ap;
+  ti.err_no = 0;
+  ti.x_data = NULL;
+  ti.m_richloc = &rich_loc;
+
+  pp_format (pp, &ti);
+  pp_output_formatted_text (pp);
+
+  va_end (ap);
+
+  simple_diagnostic_event *new_event
+    = new simple_diagnostic_event (loc, fndecl, depth, pp_formatted_text (pp));
+  m_events.safe_push (new_event);
+
+  pp_clear_output_area (pp);
+}
+
+/* struct simple_diagnostic_event.  */
+
+/* simple_diagnostic_event's ctor.  */
+
+simple_diagnostic_event::simple_diagnostic_event (location_t loc,
+						  tree fndecl,
+						  int depth,
+						  const char *desc)
+: m_loc (loc), m_fndecl (fndecl), m_depth (depth), m_desc (xstrdup (desc))
+{
+}
+
+/* simple_diagnostic_event's dtor.  */
+
+simple_diagnostic_event::~simple_diagnostic_event ()
+{
+  free (m_desc);
+}
+
+/* Print PATH by emitting a dummy "note" associated with it.  */
+
+DEBUG_FUNCTION
+void debug (diagnostic_path *path)
+{
+  rich_location richloc (line_table, UNKNOWN_LOCATION);
+  richloc.set_path (path);
+  inform (&richloc, "debug path");
+}
+
 /* Really call the system 'abort'.  This has to go right at the end of
    this file, so that there are no functions after it that call abort
    and get the system abort instead of our macro.  */
@@ -1883,6 +2089,8 @@ test_num_digits ()
   ASSERT_EQ (7, num_digits (9999999));
   ASSERT_EQ (8, num_digits (10000000));
   ASSERT_EQ (8, num_digits (99999999));
+
+  ASSERT_EQ (2, num_digits (1000, true));
 }
 
 /* Run all of the selftests within this file.  */
diff --git a/gcc/diagnostic.def b/gcc/diagnostic.def
index f3c0b2c..b271ea4 100644
--- a/gcc/diagnostic.def
+++ b/gcc/diagnostic.def
@@ -38,6 +38,11 @@ DEFINE_DIAGNOSTIC_KIND (DK_WARNING, "warning: ", "warning")
 DEFINE_DIAGNOSTIC_KIND (DK_ANACHRONISM, "anachronism: ", "warning")
 DEFINE_DIAGNOSTIC_KIND (DK_NOTE, "note: ", "note")
 DEFINE_DIAGNOSTIC_KIND (DK_DEBUG, "debug: ", "note")
+
+/* For use when using the diagnostic_show_locus machinery to show
+   a range of events within a path.  */
+DEFINE_DIAGNOSTIC_KIND (DK_DIAGNOSTIC_PATH, "path: ", "path")
+
 /* These two would be re-classified as DK_WARNING or DK_ERROR, so the
 prefix does not matter.  */
 DEFINE_DIAGNOSTIC_KIND (DK_PEDWARN, "pedwarn: ", NULL)
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 0ae9383..97b47a1 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -35,6 +35,22 @@ enum diagnostics_output_format
   DIAGNOSTICS_OUTPUT_FORMAT_JSON
 };
 
+/* An enum for controlling how diagnostic_paths should be printed.  */
+enum diagnostic_path_format
+{
+  /* Don't print diagnostic_paths.  */
+  DPF_NONE,
+
+  /* Print diagnostic_paths by emitting a separate "note" for every event
+     in the path.  */
+  DPF_SEPARATE_EVENTS,
+
+  /* Print diagnostic_paths by consolidating events together where they
+     are close enough, and emitting a "note" for every such consolidated
+     group.  */
+  DPF_INLINE_EVENTS
+};
+
 /* A diagnostic is described by the MESSAGE to send, the FILE and LINE of
    its context and its KIND (ice, error, warning, note, ...)  See complete
    list in diagnostic.def.  */
@@ -46,6 +62,10 @@ struct diagnostic_info
   /* The location at which the diagnostic is to be reported.  */
   rich_location *richloc;
 
+  /* An optional bundle of metadata associated with the diagnostic
+     (or NULL).  */
+  const diagnostic_metadata *metadata;
+
   /* Auxiliary data for client.  */
   void *x_data;
   /* The kind of diagnostic it is about.  */
@@ -76,6 +96,7 @@ typedef void (*diagnostic_finalizer_fn) (diagnostic_context *,
 					 diagnostic_t);
 
 class edit_context;
+namespace json { class value; }
 
 /* This data structure bundles altogether any information relevant to
    the context of a diagnostic message.  */
@@ -126,6 +147,15 @@ struct diagnostic_context
   /* Character used for caret diagnostics.  */
   char caret_chars[rich_location::STATICALLY_ALLOCATED_RANGES];
 
+  /* True if we should print any metadata associated with diagnostics.  */
+  bool show_metadata;
+
+  /* How should diagnostic_path objects be printed.  */
+  enum diagnostic_path_format path_format;
+
+  /* True if we should print stack depths when printing diagnostic paths.  */
+  bool show_path_depths;
+
   /* True if we should print the command line option which controls
      each diagnostic, if known.  */
   bool show_option_requested;
@@ -200,6 +230,9 @@ struct diagnostic_context
      particular option.  */
   char *(*get_option_url) (diagnostic_context *, int);
 
+  void (*print_path) (diagnostic_context *, const diagnostic_path *);
+  json::value *(*make_json_for_path) (diagnostic_context *, const diagnostic_path *);
+
   /* Auxiliary data for client.  */
   void *x_data;
 
@@ -233,6 +266,10 @@ struct diagnostic_context
      showing line numbers?  */
   bool show_line_numbers_p;
 
+  /* When printing line numbers, should the actual numbers be replaced with
+     "NN"? (for ease of DejaGnu testing)  */
+  bool use_nn_for_line_numbers_p;
+
   /* If printing source code, what should the minimum width of the margin
      be?  Line numbers will be right-aligned, and padded to this width.  */
   int min_margin_width;
@@ -343,6 +380,7 @@ extern void diagnostic_report_current_module (diagnostic_context *, location_t);
 extern void diagnostic_show_locus (diagnostic_context *,
 				   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.  */
 extern diagnostic_t diagnostic_classify_diagnostic (diagnostic_context *,
@@ -432,6 +470,8 @@ extern void diagnostic_output_format_init (diagnostic_context *,
 					   enum diagnostics_output_format);
 
 /* Compute the number of digits in the decimal representation of an integer.  */
-extern int num_digits (int);
+extern int num_digits (int, bool use_nn_p = false);
+
+extern json::value *json_from_expanded_location (location_t loc);
 
 #endif /* ! GCC_DIAGNOSTIC_H */
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 34d4b54..7f9a027 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -277,9 +277,13 @@ Objective-C and Objective-C++ Dialects}.
 -fdiagnostics-format=@r{[}text@r{|}json@r{]}  @gol
 -fno-diagnostics-show-option  -fno-diagnostics-show-caret @gol
 -fno-diagnostics-show-labels  -fno-diagnostics-show-line-numbers @gol
+-fdiagnostics-nn-line-numbers @gol
+-fno-diagnostics-show-metadata  @gol
 -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-show-path-depths @gol
 -fno-show-column}
 
 @item Warning Options
@@ -3946,6 +3950,13 @@ as the types of expressions:
 This option suppresses the printing of these labels (in the example above,
 the vertical bars and the ``char *'' and ``long int'' text).
 
+@item -fno-diagnostics-show-metadata
+@opindex fno-diagnostics-show-metadata
+@opindex fdiagnostics-show-metadata
+Diagnostics can optionally contain @url{https://cwe.mitre.org/index.html, CWE}
+metadata.  By default, if this information is present, it will be printed with
+the diagnostic.  This option suppresses the printing of this metadata.
+
 @item -fno-diagnostics-show-line-numbers
 @opindex fno-diagnostics-show-line-numbers
 @opindex fdiagnostics-show-line-numbers
@@ -3953,6 +3964,12 @@ By default, when printing source code (via @option{-fdiagnostics-show-caret}),
 a left margin is printed, showing line numbers.  This option suppresses this
 left margin.
 
+@item -fdiagnostics-nn-line-numbers
+@opindex fdiagnostics-nn-line-numbers
+When printing source code in diagnostics, replace line numbers ``NN''.
+This option is intended for GCC developers, to make it easier to write
+certain kinds of automated test.
+
 @item -fdiagnostics-minimum-margin-width=@var{width}
 @opindex fdiagnostics-minimum-margin-width
 This option controls the minimum width of the left margin printed by
@@ -4051,6 +4068,114 @@ Specifying the @option{-fno-elide-type} flag suppresses that behavior.
 This flag also affects the output of the
 @option{-fdiagnostics-show-template-tree} flag.
 
+@item -fdiagnostics-path-format=@var{KIND}
+@opindex fdiagnostics-path-format
+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.
+
+@samp{none} means to not print diagnostic paths.
+
+@samp{separate-events} means to print a separate ``note'' diagnostic for
+each event within the diagnostic.  For example:
+
+@smallexample
+test.c:29:5: error: passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter
+test.c:25:10: note: (1) when 'PyList_New' fails, returning NULL
+test.c:27:3: note: (2) when 'i < count'
+test.c:29:5: note: (3) when calling 'PyList_Append', passing NULL as argument 1
+@end smallexample
+
+@samp{inline-events} means to print the events ``inline'' within the source
+code.  This view attempts to consolidate the events into runs of
+sufficiently-close events, printing them as labelled ranges within the source.
+
+For example, the same events as above might be printed as:
+
+@smallexample
+  'test': events 1-3
+     |
+     |   25 |   list = PyList_New(0);
+     |      |          ^~~~~~~~~~~~~
+     |      |          |
+     |      |          (1) when 'PyList_New' fails, returning NULL
+     |   26 |
+     |   27 |   for (i = 0; i < count; i++) @{
+     |      |   ~~~
+     |      |   |
+     |      |   (2) when 'i < count'
+     |   28 |     item = PyLong_FromLong(random());
+     |   29 |     PyList_Append(list, item);
+     |      |     ~~~~~~~~~~~~~~~~~~~~~~~~~
+     |      |     |
+     |      |     (3) when calling 'PyList_Append', passing NULL as argument 1
+     |
+@end smallexample
+
+Interprocedural control flow is shown by grouping the events by stack frame,
+and using indentation to show how stack frames are nested, pushed, and popped.
+
+For example:
+
+@smallexample
+  'test': events 1-2
+     |
+     |  133 | @{
+     |      | ^
+     |      | |
+     |      | (1) entering 'test'
+     |  134 |   boxed_int *obj = make_boxed_int (i);
+     |      |                    ~~~~~~~~~~~~~~~~~~
+     |      |                    |
+     |      |                    (2) calling 'make_boxed_int'
+     |
+     +--> 'make_boxed_int': events 3-4
+                  |
+                  |  120 | @{
+                  |      | ^
+                  |      | |
+                  |      | (3) entering 'make_boxed_int'
+                  |  121 |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+                  |      |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                  |      |                                    |
+                  |      |                                    (4) calling 'wrapped_malloc'
+                  |
+                  +--> 'wrapped_malloc': events 5-6
+                               |
+                               |    7 | @{
+                               |      | ^
+                               |      | |
+                               |      | (5) entering 'wrapped_malloc'
+                               |    8 |   return malloc (size);
+                               |      |          ~~~~~~~~~~~~~
+                               |      |          |
+                               |      |          (6) calling 'malloc'
+                               |
+     <-------------------------+
+     |
+  'test': event 7
+     |
+     |  138 |   free_boxed_int (obj);
+     |      |   ^~~~~~~~~~~~~~~~~~~~
+     |      |   |
+     |      |   (7) calling 'free_boxed_int'
+     |
+(etc)
+@end smallexample
+
+@item -fdiagnostics-show-path-depths
+@opindex fdiagnostics-show-path-depths
+This option provides additional information when printing control-flow paths
+associated with a diagnostic.
+
+If this is option is provided then the stack depth will be printed for
+each run of events within @option{-fdiagnostics-path-format=separate-events}.
+
+This is intended for use by GCC developers and plugin developers when
+debugging diagnostics that report interprocedural control flow.
+
 @item -fno-show-column
 @opindex fno-show-column
 @opindex fshow-column
diff --git a/gcc/dwarf2out.c b/gcc/dwarf2out.c
index bf69ce4..3207040 100644
--- a/gcc/dwarf2out.c
+++ b/gcc/dwarf2out.c
@@ -24490,6 +24490,7 @@ gen_producer_string (void)
       case OPT_fdiagnostics_show_caret:
       case OPT_fdiagnostics_show_labels:
       case OPT_fdiagnostics_show_line_numbers:
+      case OPT_fdiagnostics_nn_line_numbers:
       case OPT_fdiagnostics_color_:
       case OPT_fdiagnostics_format_:
       case OPT_fverbose_asm:
diff --git a/gcc/gcc-rich-location.h b/gcc/gcc-rich-location.h
index 71f4f3d..4de697a 100644
--- a/gcc/gcc-rich-location.h
+++ b/gcc/gcc-rich-location.h
@@ -62,7 +62,9 @@ class gcc_rich_location : public rich_location
 
      Implemented in diagnostic-show-locus.c.  */
 
-  bool add_location_if_nearby (location_t loc);
+  bool add_location_if_nearby (location_t loc,
+			       bool restrict_to_current_line_spans = true,
+			       const range_label *label = NULL);
 
   /* Add a fix-it hint suggesting the insertion of CONTENT before
      INSERTION_POINT.
diff --git a/gcc/input.c b/gcc/input.c
index 00301ef..ce9b535 100644
--- a/gcc/input.c
+++ b/gcc/input.c
@@ -1134,10 +1134,10 @@ dump_location_info (FILE *stream)
 	      if (max_col > line_text.length ())
 		max_col = line_text.length () + 1;
 
-	      int len_lnum = num_digits (exploc.line);
+	      int len_lnum = num_digits (exploc.line, false);
 	      if (len_lnum < 3)
 		len_lnum = 3;
-	      int len_loc = num_digits (loc);
+	      int len_loc = num_digits (loc, false);
 	      if (len_loc < 5)
 		len_loc = 5;
 
diff --git a/gcc/lto-wrapper.c b/gcc/lto-wrapper.c
index 9a7bbd0..aabfe6d 100644
--- a/gcc/lto-wrapper.c
+++ b/gcc/lto-wrapper.c
@@ -260,6 +260,7 @@ merge_and_complain (struct cl_decoded_option **decoded_options,
 	case OPT_fdiagnostics_show_caret:
 	case OPT_fdiagnostics_show_labels:
 	case OPT_fdiagnostics_show_line_numbers:
+	case OPT_fdiagnostics_nn_line_numbers:
 	case OPT_fdiagnostics_show_option:
 	case OPT_fdiagnostics_show_location_:
 	case OPT_fshow_column:
@@ -605,6 +606,7 @@ append_compiler_options (obstack *argv_obstack, struct cl_decoded_option *opts,
 	case OPT_fdiagnostics_show_caret:
 	case OPT_fdiagnostics_show_labels:
 	case OPT_fdiagnostics_show_line_numbers:
+	case OPT_fdiagnostics_nn_line_numbers:
 	case OPT_fdiagnostics_show_option:
 	case OPT_fdiagnostics_show_location_:
 	case OPT_fshow_column:
@@ -653,6 +655,7 @@ append_diag_options (obstack *argv_obstack, struct cl_decoded_option *opts,
 	case OPT_fdiagnostics_format_:
 	case OPT_fdiagnostics_show_caret:
 	case OPT_fdiagnostics_show_labels:
+	case OPT_fdiagnostics_nn_line_numbers:
 	case OPT_fdiagnostics_show_line_numbers:
 	case OPT_fdiagnostics_show_option:
 	case OPT_fdiagnostics_show_location_:
diff --git a/gcc/opts.c b/gcc/opts.c
index 62671f3..db69ebb 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -2488,6 +2488,10 @@ common_handle_option (struct gcc_options *opts,
       dc->show_line_numbers_p = value;
       break;
 
+    case OPT_fdiagnostics_nn_line_numbers:
+      dc->use_nn_for_line_numbers_p = value;
+      break;
+
     case OPT_fdiagnostics_color_:
       diagnostic_color_init (dc, value);
       break;
@@ -2505,6 +2509,18 @@ common_handle_option (struct gcc_options *opts,
       dc->parseable_fixits_p = value;
       break;
 
+    case OPT_fdiagnostics_show_metadata:
+      dc->show_metadata = value;
+      break;
+
+    case OPT_fdiagnostics_path_format_:
+      dc->path_format = (enum diagnostic_path_format)value;
+      break;
+
+    case OPT_fdiagnostics_show_path_depths:
+      dc->show_path_depths = value;
+      break;
+
     case OPT_fdiagnostics_show_option:
       dc->show_option_requested = value;
       break;
diff --git a/gcc/pretty-print.c b/gcc/pretty-print.c
index 89242ed..d5aa451 100644
--- a/gcc/pretty-print.c
+++ b/gcc/pretty-print.c
@@ -24,6 +24,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "pretty-print.h"
 #include "diagnostic-color.h"
+#include "diagnostic-event-id.h"
 #include "selftest.h"
 
 #if HAVE_ICONV
@@ -989,6 +990,7 @@ pp_indent (pretty_printer *pp)
    %>: closing quote.
    %': apostrophe (should only be used in untranslated messages;
        translations should use appropriate punctuation directly).
+   %@: diagnostic_event_id_ptr, for which event_id->known_p () must be true.
    %.*s: a substring the length of which is specified by an argument
 	 integer.
    %Ns: likewise, but length specified as constant in the format string.
@@ -1378,6 +1380,21 @@ pp_format (pretty_printer *pp, text_info *text)
 	  }
 	  break;
 
+	case '@':
+	  {
+	    /* diagnostic_event_id_t *.  */
+	    diagnostic_event_id_ptr event_id
+	      = va_arg (*text->args_ptr, diagnostic_event_id_ptr);
+	    gcc_assert (event_id->known_p ());
+
+	    pp_string (pp, colorize_start (pp_show_color (pp), "path"));
+	    pp_character (pp, '(');
+	    pp_decimal_int (pp, event_id->one_based ());
+	    pp_character (pp, ')');
+	    pp_string (pp, colorize_stop (pp_show_color (pp)));
+	  }
+	  break;
+
 	default:
 	  {
 	    bool ok;
@@ -2269,6 +2286,21 @@ test_pp_format ()
   assert_pp_format_colored (SELFTEST_LOCATION,
 			    "`\33[01m\33[Kfoo\33[m\33[K' 12345678", "%qs %x",
 			    "foo", 0x12345678);
+  /* Verify "%@".  */
+  {
+    diagnostic_event_id_t first (2);
+    diagnostic_event_id_t second (7);
+
+    ASSERT_PP_FORMAT_2 ("first `free' at (3); second `free' at (8)",
+			"first %<free%> at %@; second %<free%> at %@",
+			&first, &second);
+    assert_pp_format_colored
+      (SELFTEST_LOCATION,
+       "first `[01m[Kfree[m[K' at [01;36m[K(3)[m[K;"
+       " second `[01m[Kfree[m[K' at [01;36m[K(8)[m[K",
+       "first %<free%> at %@; second %<free%> at %@",
+       &first, &second);
+  }
 
   /* Verify %Z.  */
   int v[] = { 1, 2, 3 }; 
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index b9afb64..58184a1 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -97,6 +97,7 @@ selftest::run_tests ()
   spellcheck_c_tests ();
   spellcheck_tree_c_tests ();
   tree_cfg_c_tests ();
+  tree_diagnostic_c_tests ();
   attribute_c_tests ();
 
   /* This one relies on most of the above.  */
diff --git a/gcc/selftest.h b/gcc/selftest.h
index 6f2c2af..84c1594 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -248,6 +248,7 @@ extern void sreal_c_tests ();
 extern void store_merging_c_tests ();
 extern void tree_c_tests ();
 extern void tree_cfg_c_tests ();
+extern void tree_diagnostic_c_tests ();
 extern void typed_splay_tree_c_tests ();
 extern void unique_ptr_tests_cc_tests ();
 extern void vec_c_tests ();
diff --git a/gcc/testsuite/gcc.dg/format/gcc_diag-10.c b/gcc/testsuite/gcc.dg/format/gcc_diag-10.c
index ba2629b..a2f99fe 100644
--- a/gcc/testsuite/gcc.dg/format/gcc_diag-10.c
+++ b/gcc/testsuite/gcc.dg/format/gcc_diag-10.c
@@ -22,6 +22,9 @@ typedef struct gimple gimple;
 /* Likewise for gimple.  */
 typedef struct cgraph_node cgraph_node;
 
+/* Likewise for diagnostic_event_id_t.  */
+typedef struct diagnostic_event_id_t diagnostic_event_id_t;
+
 #define FORMAT(kind) __attribute__ ((format (__gcc_## kind ##__, 1, 2)))
 
 void diag (const char*, ...) FORMAT (diag);
@@ -30,7 +33,7 @@ void tdiag (const char*, ...) FORMAT (tdiag);
 void cxxdiag (const char*, ...) FORMAT (cxxdiag);
 void dump (const char*, ...) FORMAT (dump_printf);
 
-void test_diag (tree t, gimple *gc)
+void test_diag (tree t, gimple *gc, diagnostic_event_id_t *event_id_ptr)
 {
   diag ("%<");   /* { dg-warning "unterminated quoting directive" } */
   diag ("%>");   /* { dg-warning "unmatched quoting directive " } */
@@ -38,6 +41,7 @@ void test_diag (tree t, gimple *gc)
 
   diag ("%G", gc); /* { dg-warning "format" } */
   diag ("%K", t); /* { dg-warning "format" } */
+  diag ("%@", event_id_ptr);
 
   diag ("%R");       /* { dg-warning "unmatched color reset directive" } */
   diag ("%r", "");   /* { dg-warning "unterminated color directive" } */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-default.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-default.c
new file mode 100644
index 0000000..d68f856
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-default.c
@@ -0,0 +1,140 @@
+/* { dg-do compile } */
+/* { dg-options "" } */
+
+#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\\]" } */
+  /* { dg-begin-multiline-output "" }
+  'test': events 1-2
+     |
+     | {
+     | ^
+     | |
+     | (1) entering 'test'
+     |   boxed_int *obj = make_boxed_int (i);
+     |                    ~~~~~~~~~~~~~~~~~~
+     |                    |
+     |                    (2) calling 'make_boxed_int'
+     |
+     +--> 'make_boxed_int': events 3-4
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (3) entering 'make_boxed_int'
+                  |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+                  |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                  |                                    |
+                  |                                    (4) calling 'wrapped_malloc'
+                  |
+                  +--> 'wrapped_malloc': events 5-6
+                               |
+                               | {
+                               | ^
+                               | |
+                               | (5) entering 'wrapped_malloc'
+                               |   return malloc (size);
+                               |          ~~~~~~~~~~~~~
+                               |          |
+                               |          (6) calling 'malloc'
+                               |
+     <-------------------------+
+     |
+  'test': event 7
+     |
+     |   free_boxed_int (obj);
+     |   ^~~~~~~~~~~~~~~~~~~~
+     |   |
+     |   (7) calling 'free_boxed_int'
+     |
+     +--> 'free_boxed_int': events 8-9
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (8) entering 'free_boxed_int'
+                  |   wrapped_free (bi);
+                  |   ~~~~~~~~~~~~~~~~~
+                  |   |
+                  |   (9) calling 'wrapped_free'
+                  |
+                  +--> 'wrapped_free': events 10-11
+                              |
+                              | {
+                              | ^
+                              | |
+                              | (10) entering 'wrapped_free'
+                              |   free (ptr);
+                              |   ~~~~~~~~~~
+                              |   |
+                              |   (11) calling 'free'
+                              |
+     <------------------------+
+     |
+  'test': event 12
+     |
+     |   free_boxed_int (obj);
+     |   ^~~~~~~~~~~~~~~~~~~~
+     |   |
+     |   (12) calling 'free_boxed_int'
+     |
+     +--> 'free_boxed_int': events 13-14
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (13) entering 'free_boxed_int'
+                  |   wrapped_free (bi);
+                  |   ~~~~~~~~~~~~~~~~~
+                  |   |
+                  |   (14) calling 'wrapped_free'
+                  |
+                  +--> 'wrapped_free': events 15-16
+                              |
+                              | {
+                              | ^
+                              | |
+                              | (15) entering 'wrapped_free'
+                              |   free (ptr);
+                              |   ~~~~~~~~~~
+                              |   |
+                              |   (16) calling 'free'
+                              |
+     { dg-end-multiline-output "" } */
+}
+
+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);
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-1.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-1.c
new file mode 100644
index 0000000..a457283
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-1.c
@@ -0,0 +1,140 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=inline-events" } */
+
+#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\\]" } */
+  /* { dg-begin-multiline-output "" }
+  'test': events 1-2
+     |
+     | {
+     | ^
+     | |
+     | (1) entering 'test'
+     |   boxed_int *obj = make_boxed_int (i);
+     |                    ~~~~~~~~~~~~~~~~~~
+     |                    |
+     |                    (2) calling 'make_boxed_int'
+     |
+     +--> 'make_boxed_int': events 3-4
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (3) entering 'make_boxed_int'
+                  |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+                  |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                  |                                    |
+                  |                                    (4) calling 'wrapped_malloc'
+                  |
+                  +--> 'wrapped_malloc': events 5-6
+                               |
+                               | {
+                               | ^
+                               | |
+                               | (5) entering 'wrapped_malloc'
+                               |   return malloc (size);
+                               |          ~~~~~~~~~~~~~
+                               |          |
+                               |          (6) calling 'malloc'
+                               |
+     <-------------------------+
+     |
+  'test': event 7
+     |
+     |   free_boxed_int (obj);
+     |   ^~~~~~~~~~~~~~~~~~~~
+     |   |
+     |   (7) calling 'free_boxed_int'
+     |
+     +--> 'free_boxed_int': events 8-9
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (8) entering 'free_boxed_int'
+                  |   wrapped_free (bi);
+                  |   ~~~~~~~~~~~~~~~~~
+                  |   |
+                  |   (9) calling 'wrapped_free'
+                  |
+                  +--> 'wrapped_free': events 10-11
+                              |
+                              | {
+                              | ^
+                              | |
+                              | (10) entering 'wrapped_free'
+                              |   free (ptr);
+                              |   ~~~~~~~~~~
+                              |   |
+                              |   (11) calling 'free'
+                              |
+     <------------------------+
+     |
+  'test': event 12
+     |
+     |   free_boxed_int (obj);
+     |   ^~~~~~~~~~~~~~~~~~~~
+     |   |
+     |   (12) calling 'free_boxed_int'
+     |
+     +--> 'free_boxed_int': events 13-14
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (13) entering 'free_boxed_int'
+                  |   wrapped_free (bi);
+                  |   ~~~~~~~~~~~~~~~~~
+                  |   |
+                  |   (14) calling 'wrapped_free'
+                  |
+                  +--> 'wrapped_free': events 15-16
+                              |
+                              | {
+                              | ^
+                              | |
+                              | (15) entering 'wrapped_free'
+                              |   free (ptr);
+                              |   ~~~~~~~~~~
+                              |   |
+                              |   (16) calling 'free'
+                              |
+     { dg-end-multiline-output "" } */
+}
+
+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);
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-2.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-2.c
new file mode 100644
index 0000000..78dd895
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-2.c
@@ -0,0 +1,147 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=inline-events" } */
+
+/* Verify that 'inline-events' copes gracefully with events with an
+   unknown location.  */
+
+#include <stdlib.h>
+
+extern void missing_location ();
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+  /* { dg-begin-multiline-output "" }
+  'test': events 1-2
+     |
+     | {
+     | ^
+     | |
+     | (1) entering 'test'
+     |   boxed_int *obj = make_boxed_int (i);
+     |                    ~~~~~~~~~~~~~~~~~~
+     |                    |
+     |                    (2) calling 'make_boxed_int'
+     |
+     +--> 'make_boxed_int': events 3-4
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (3) entering 'make_boxed_int'
+                  |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+                  |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                  |                                    |
+                  |                                    (4) calling 'wrapped_malloc'
+                  |
+                  +--> 'wrapped_malloc': events 5-6
+                               |
+                               | {
+                               | ^
+                               | |
+                               | (5) entering 'wrapped_malloc'
+                               |   return malloc (size);
+                               |          ~~~~~~~~~~~~~
+                               |          |
+                               |          (6) calling 'malloc'
+                               |
+     <-------------------------+
+     |
+  'test': event 7
+     |
+     |   free_boxed_int (obj);
+     |   ^~~~~~~~~~~~~~~~~~~~
+     |   |
+     |   (7) calling 'free_boxed_int'
+     |
+     +--> 'free_boxed_int': events 8-9
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (8) entering 'free_boxed_int'
+                  |   wrapped_free (bi);
+                  |   ~~~~~~~~~~~~~~~~~
+                  |   |
+                  |   (9) calling 'wrapped_free'
+                  |
+                  +--> 'wrapped_free': events 10-11
+                              |
+                              | {
+                              | ^
+                              | |
+                              | (10) entering 'wrapped_free'
+                              |   free (ptr);
+                              |   ~~~~~~~~~~
+                              |   |
+                              |   (11) calling 'free'
+                              |
+     <------------------------+
+     |
+  'test': events 12-13
+     | (12): calling 'missing_location'
+     |   free_boxed_int (obj);
+     |   ^~~~~~~~~~~~~~~~~~~~
+     |   |
+     |   (13) calling 'free_boxed_int'
+     |
+     +--> 'free_boxed_int': events 14-15
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (14) entering 'free_boxed_int'
+                  |   wrapped_free (bi);
+                  |   ~~~~~~~~~~~~~~~~~
+                  |   |
+                  |   (15) calling 'wrapped_free'
+                  |
+                  +--> 'wrapped_free': events 16-17
+                              |
+                              | {
+                              | ^
+                              | |
+                              | (16) entering 'wrapped_free'
+                              |   free (ptr);
+                              |   ~~~~~~~~~~
+                              |   |
+                              |   (17) calling 'free'
+                              |
+     { dg-end-multiline-output "" } */
+}
+
+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);
+
+  free_boxed_int (obj);
+
+  missing_location ();
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-3.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-3.c
new file mode 100644
index 0000000..2c266be
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-3.c
@@ -0,0 +1,146 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers" } */
+
+/* Verify the interaction of inline-events with line numbers.  */
+
+#include <stdlib.h>
+
+extern void missing_location ();
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+  /* { dg-begin-multiline-output "" }
+  'test': events 1-2
+     |
+     |   NN | {
+     |      | ^
+     |      | |
+     |      | (1) entering 'test'
+     |   NN |   boxed_int *obj = make_boxed_int (i);
+     |      |                    ~~~~~~~~~~~~~~~~~~
+     |      |                    |
+     |      |                    (2) calling 'make_boxed_int'
+     |
+     +--> 'make_boxed_int': events 3-4
+                  |
+                  |   NN | {
+                  |      | ^
+                  |      | |
+                  |      | (3) entering 'make_boxed_int'
+                  |   NN |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+                  |      |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                  |      |                                    |
+                  |      |                                    (4) calling 'wrapped_malloc'
+                  |
+                  +--> 'wrapped_malloc': events 5-6
+                               |
+                               |   NN | {
+                               |      | ^
+                               |      | |
+                               |      | (5) entering 'wrapped_malloc'
+                               |   NN |   return malloc (size);
+                               |      |          ~~~~~~~~~~~~~
+                               |      |          |
+                               |      |          (6) calling 'malloc'
+                               |
+     <-------------------------+
+     |
+  'test': event 7
+     |
+     |   NN |   free_boxed_int (obj);
+     |      |   ^~~~~~~~~~~~~~~~~~~~
+     |      |   |
+     |      |   (7) calling 'free_boxed_int'
+     |
+     +--> 'free_boxed_int': events 8-9
+                  |
+                  |   NN | {
+                  |      | ^
+                  |      | |
+                  |      | (8) entering 'free_boxed_int'
+                  |   NN |   wrapped_free (bi);
+                  |      |   ~~~~~~~~~~~~~~~~~
+                  |      |   |
+                  |      |   (9) calling 'wrapped_free'
+                  |
+                  +--> 'wrapped_free': events 10-11
+                              |
+                              |   NN | {
+                              |      | ^
+                              |      | |
+                              |      | (10) entering 'wrapped_free'
+                              |   NN |   free (ptr);
+                              |      |   ~~~~~~~~~~
+                              |      |   |
+                              |      |   (11) calling 'free'
+                              |
+     <------------------------+
+     |
+  'test': events 12-13
+     | (12): calling 'missing_location'
+     |   NN |   free_boxed_int (obj);
+     |      |   ^~~~~~~~~~~~~~~~~~~~
+     |      |   |
+     |      |   (13) calling 'free_boxed_int'
+     |
+     +--> 'free_boxed_int': events 14-15
+                  |
+                  |   NN | {
+                  |      | ^
+                  |      | |
+                  |      | (14) entering 'free_boxed_int'
+                  |   NN |   wrapped_free (bi);
+                  |      |   ~~~~~~~~~~~~~~~~~
+                  |      |   |
+                  |      |   (15) calling 'wrapped_free'
+                  |
+                  +--> 'wrapped_free': events 16-17
+                              |
+                              |   NN | {
+                              |      | ^
+                              |      | |
+                              |      | (16) entering 'wrapped_free'
+                              |   NN |   free (ptr);
+                              |      |   ~~~~~~~~~~
+                              |      |   |
+                              |      |   (17) calling 'free'
+                              |
+     { dg-end-multiline-output "" } */
+}
+
+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);
+
+  free_boxed_int (obj);
+
+  missing_location ();
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-none.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-none.c
new file mode 100644
index 0000000..0a29f67
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-none.c
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=none" } */
+
+#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);
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-separate-events.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-separate-events.c
new file mode 100644
index 0000000..dcb72c0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-separate-events.c
@@ -0,0 +1,44 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=separate-events" } */
+
+#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)
+{ /* { dg-message "\\(1\\) entering 'test'" } */
+  boxed_int *obj = make_boxed_int (i); /* { dg-message "\\(2\\) calling 'make_boxed_int'" } */
+  /* etc */
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c
new file mode 100644
index 0000000..66c2c66c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c
@@ -0,0 +1,38 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=separate-events" } */
+
+#include <stddef.h>
+#include <stdlib.h>
+
+/* Minimal reimplementation of cpython API.  */
+typedef struct PyObject {} PyObject;
+extern int PyArg_ParseTuple (PyObject *args, const char *fmt, ...);
+extern PyObject *PyList_New (int);
+extern PyObject *PyLong_FromLong(long);
+extern void PyList_Append(PyObject *list, PyObject *item);
+
+PyObject *
+make_a_list_of_random_ints_badly(PyObject *self,
+				 PyObject *args)
+{
+  PyObject *list, *item;
+  long count, i;
+
+  if (!PyArg_ParseTuple(args, "i", &count)) {
+    return NULL;
+  }
+
+  list = PyList_New(0); /* { dg-line PyList_New } */
+	
+  for (i = 0; i < count; i++) { /* { dg-line for } */
+    item = PyLong_FromLong(random());
+    PyList_Append(list, item); /* { dg-line PyList_Append } */
+  }
+  
+  return list;
+
+  /* { dg-error "passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter" "" { target *-*-* } PyList_Append } */
+  /* { dg-message "\\(1\\) when 'PyList_New' fails, returning NULL" "" { target *-*-* } PyList_New } */
+  /* { dg-message "\\(2\\) when 'i < count'" "" { target *-*-* } for } */
+  /* { dg-message "\\(3\\) when calling 'PyList_Append', passing NULL as argument 1" "" { target *-*-* } PyList_Append } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c
new file mode 100644
index 0000000..124f5a4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c
@@ -0,0 +1,58 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-show-caret -fdiagnostics-show-line-numbers" } */
+
+#include <stddef.h>
+#include <stdlib.h>
+
+/* Minimal reimplementation of cpython API.  */
+typedef struct PyObject {} PyObject;
+extern int PyArg_ParseTuple (PyObject *args, const char *fmt, ...);
+extern PyObject *PyList_New (int);
+extern PyObject *PyLong_FromLong(long);
+extern void PyList_Append(PyObject *list, PyObject *item);
+
+PyObject *
+make_a_list_of_random_ints_badly(PyObject *self,
+				 PyObject *args)
+{
+  PyObject *list, *item;
+  long count, i;
+
+  if (!PyArg_ParseTuple(args, "i", &count)) {
+    return NULL;
+  }
+
+  list = PyList_New(0); /* { dg-line PyList_New } */
+	
+  for (i = 0; i < count; i++) {
+    item = PyLong_FromLong(random());
+    PyList_Append(list, item); /* { dg-line PyList_Append } */
+  }
+  
+  return list;
+
+  /* { dg-error "passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter" "" { target *-*-* } PyList_Append } */
+  /* { dg-begin-multiline-output "" }
+   29 |     PyList_Append(list, item);
+      |     ^~~~~~~~~~~~~~~~~~~~~~~~~
+     { dg-end-multiline-output "" } */
+  /* { dg-begin-multiline-output "" }
+  'make_a_list_of_random_ints_badly': events 1-3
+                   |
+                   |   25 |   list = PyList_New(0);
+                   |      |          ^~~~~~~~~~~~~
+                   |      |          |
+                   |      |          (1) when 'PyList_New' fails, returning NULL
+                   |   26 | 
+                   |   27 |   for (i = 0; i < count; i++) {
+                   |      |   ~~~     
+                   |      |   |
+                   |      |   (2) when 'i < count'
+                   |   28 |     item = PyLong_FromLong(random());
+                   |   29 |     PyList_Append(list, item);
+                   |      |     ~~~~~~~~~~~~~~~~~~~~~~~~~
+                   |      |     |
+                   |      |     (3) when calling 'PyList_Append', passing NULL as argument 1
+                   |
+     { dg-end-multiline-output "" } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c
new file mode 100644
index 0000000..6971d7c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c
@@ -0,0 +1,38 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-format=json" } */
+
+#include <stddef.h>
+#include <stdlib.h>
+
+/* Minimal reimplementation of cpython API.  */
+typedef struct PyObject {} PyObject;
+extern int PyArg_ParseTuple (PyObject *args, const char *fmt, ...);
+extern PyObject *PyList_New (int);
+extern PyObject *PyLong_FromLong(long);
+extern void PyList_Append(PyObject *list, PyObject *item);
+
+PyObject *
+make_a_list_of_random_ints_badly(PyObject *self,
+				 PyObject *args)
+{
+  PyObject *list, *item;
+  long count, i;
+
+  if (!PyArg_ParseTuple(args, "i", &count)) {
+    return NULL;
+  }
+
+  list = PyList_New(0);
+	
+  for (i = 0; i < count; i++) {
+    item = PyLong_FromLong(random());
+    PyList_Append(list, item);
+  }
+  
+  return list;
+}
+
+/* FIXME: test the events within a path.  */
+/* { dg-regexp "\"kind\": \"error\"" } */
+/* { dg-regexp "\"path\": " } */
+/* { dg-regexp ".*" } */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
new file mode 100644
index 0000000..05c1c4d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
@@ -0,0 +1,380 @@
+/* { dg-options "-O" } */
+
+/* This plugin exercises the path-printing code.
+
+   The goal is to unit-test the path-printing code without needing any
+   specific tests within the compiler's IR.  We can't use any real
+   diagnostics for this, so we have to fake it, hence this plugin.  */
+
+#include "gcc-plugin.h"
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "tree.h"
+#include "stringpool.h"
+#include "toplev.h"
+#include "basic-block.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "tree.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "plugin-version.h"
+#include "diagnostic.h"
+#include "diagnostic-path.h"
+#include "diagnostic-metadata.h"
+#include "context.h"
+#include "print-tree.h"
+#include "gcc-rich-location.h"
+#include "cgraph.h"
+
+int plugin_is_GPL_compatible;
+
+const pass_data pass_data_test_show_path =
+{
+  IPA_PASS, /* type */
+  "test_show_path", /* name */
+  OPTGROUP_NONE, /* optinfo_flags */
+  TV_NONE, /* tv_id */
+  PROP_ssa, /* properties_required */
+  0, /* properties_provided */
+  0, /* properties_destroyed */
+  0, /* todo_flags_start */
+  0, /* todo_flags_finish */
+};
+
+class pass_test_show_path : public ipa_opt_pass_d
+{
+public:
+  pass_test_show_path(gcc::context *ctxt)
+    : ipa_opt_pass_d (pass_data_test_show_path, ctxt,
+		      NULL, /* generate_summary */
+		      NULL, /* write_summary */
+		      NULL, /* read_summary */
+		      NULL, /* write_optimization_summary */
+		      NULL, /* read_optimization_summary */
+		      NULL, /* stmt_fixup */
+		      0, /* function_transform_todo_flags_start */
+		      NULL, /* function_transform */
+		      NULL) /* variable_transform */
+  {}
+
+  /* opt_pass methods: */
+  bool gate (function *) { return true; }
+  virtual unsigned int execute (function *);
+
+}; // class pass_test_show_path
+
+/* Determine if STMT is a call with NUM_ARGS arguments to a function
+   named FUNCNAME.
+   If so, return STMT as a gcall *.  Otherwise return NULL.  */
+
+static gcall *
+check_for_named_call (gimple *stmt,
+		      const char *funcname, unsigned int num_args)
+{
+  gcc_assert (funcname);
+
+  gcall *call = dyn_cast <gcall *> (stmt);
+  if (!call)
+    return NULL;
+
+  tree fndecl = gimple_call_fndecl (call);
+  if (!fndecl)
+    return NULL;
+
+  if (strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), funcname))
+    return NULL;
+
+  if (gimple_call_num_args (call) != num_args)
+    {
+      error_at (stmt->location, "expected number of args: %i (got %i)",
+		num_args, gimple_call_num_args (call));
+      return NULL;
+    }
+
+  return call;
+}
+
+/* Example 1: a purely intraprocedural path.  */
+
+static void
+example_1 ()
+{
+  gimple_stmt_iterator gsi;
+  basic_block bb;
+
+  gcall *call_to_PyList_Append = NULL;
+  gcall *call_to_PyList_New = NULL;
+  gcond *for_cond = NULL;
+  function *example_a_fun = NULL;
+
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    {
+      function *fun = node->get_fun ();
+      FOR_EACH_BB_FN (bb, fun)
+	{
+	  for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      if (gcall *call = check_for_named_call (stmt, "PyList_New", 1))
+		{
+		  call_to_PyList_New = call;
+		  example_a_fun = fun;
+		}
+	      if (gcall *call = check_for_named_call (stmt, "PyList_Append", 2))
+		call_to_PyList_Append = call;
+	      if (gcond *cond = dyn_cast <gcond *> (stmt))
+		for_cond = cond;
+	    }
+	}
+    }
+
+  if (call_to_PyList_New && for_cond && call_to_PyList_Append)
+    {
+      auto_diagnostic_group d;
+      gcc_rich_location richloc (gimple_location (call_to_PyList_Append));
+      simple_diagnostic_path path (global_dc->printer);
+      path.add_event (gimple_location (call_to_PyList_New),
+		      example_a_fun->decl, 0,
+		      "when %qs fails, returning NULL",
+		      "PyList_New");
+      path.add_event (gimple_location (for_cond),
+		      example_a_fun->decl, 0,
+		      "when %qs", "i < count");
+      path.add_event (gimple_location (call_to_PyList_Append),
+		      example_a_fun->decl, 0,
+		      "when calling %qs, passing NULL as argument %i",
+		      "PyList_Append", 1);
+      richloc.set_path (&path);
+      error_at (&richloc,
+		"passing NULL as argument %i to %qs"
+		" which requires a non-NULL parameter",
+		1, "PyList_Append");
+    }
+}
+
+/* A (function, location_t) pair.  */
+
+struct event_location_t
+{
+  event_location_t ()
+  : m_fun (NULL), m_loc (UNKNOWN_LOCATION)
+  {}
+
+  event_location_t (function *fun, location_t loc)
+  : m_fun (fun), m_loc (loc)
+  {}
+
+  void set (const gimple *stmt, function *fun)
+  {
+    m_fun = fun;
+    m_loc = gimple_location (stmt);
+  }
+
+  function *m_fun;
+  location_t m_loc;
+};
+
+/* If FUN's name matches FUNCNAME, write the function and its start location
+   into *OUT_ENTRY.  */
+
+static void
+check_for_named_function (function *fun, const char *funcname,
+			  event_location_t *out_entry)
+{
+  gcc_assert (fun);
+  gcc_assert (funcname);
+
+  if (strcmp (IDENTIFIER_POINTER (DECL_NAME (fun->decl)), funcname))
+    return;
+
+  *out_entry = event_location_t (fun, fun->function_start_locus);
+}
+
+
+/* Example 2: an interprocedural path.  */
+
+class test_diagnostic_path : public simple_diagnostic_path
+{
+ public:
+  test_diagnostic_path (pretty_printer *event_pp)
+  : simple_diagnostic_path (event_pp)
+  {
+  }
+  void add_entry (event_location_t evloc, int stack_depth,
+		  const char *funcname)
+  {
+    gcc_assert (evloc.m_fun);
+    add_event (evloc.m_loc, evloc.m_fun->decl, stack_depth,
+	       "entering %qs", funcname);
+  }
+
+  void add_call (event_location_t call_evloc, int caller_stack_depth,
+		 event_location_t callee_entry_evloc, const char *callee)
+  {
+    gcc_assert (call_evloc.m_fun);
+    add_event (call_evloc.m_loc, call_evloc.m_fun->decl, caller_stack_depth,
+	       "calling %qs", callee);
+    add_entry (callee_entry_evloc, caller_stack_depth + 1, callee);
+  }
+
+  void add_leaf_call (event_location_t call_evloc, int caller_stack_depth,
+		      const char *callee)
+  {
+    gcc_assert (call_evloc.m_fun);
+    add_event (call_evloc.m_loc, call_evloc.m_fun->decl, caller_stack_depth,
+	       "calling %qs", callee);
+  }
+};
+
+/* Example 1: an interprocedural path.  */
+
+static void
+example_2 ()
+{
+  gimple_stmt_iterator gsi;
+  basic_block bb;
+
+  event_location_t entry_to_wrapped_malloc;
+  event_location_t call_to_malloc;
+
+  event_location_t entry_to_wrapped_free;
+  event_location_t call_to_free;
+
+  event_location_t entry_to_make_boxed_int;
+  event_location_t call_to_wrapped_malloc;
+
+  event_location_t entry_to_free_boxed_int;
+  event_location_t call_to_wrapped_free;
+
+  event_location_t entry_to_test;
+  event_location_t call_to_make_boxed_int;
+  event_location_t call_to_free_boxed_int;
+
+  event_location_t call_to_missing_location;
+
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    {
+      function *fun = node->get_fun ();
+      FOR_EACH_BB_FN (bb, fun)
+	{
+	  check_for_named_function (fun, "wrapped_malloc",
+				    &entry_to_wrapped_malloc);
+	  check_for_named_function (fun, "wrapped_free",
+				    &entry_to_wrapped_free);
+	  check_for_named_function (fun, "make_boxed_int",
+				    &entry_to_make_boxed_int);
+	  check_for_named_function (fun, "free_boxed_int",
+				    &entry_to_free_boxed_int);
+	  check_for_named_function (fun, "test",
+				    &entry_to_test);
+
+	  for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      if (gcall *call = check_for_named_call (stmt, "malloc", 1))
+		call_to_malloc.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "free", 1))
+		call_to_free.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "wrapped_malloc", 1))
+		call_to_wrapped_malloc.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "wrapped_free", 1))
+		call_to_wrapped_free.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "make_boxed_int", 1))
+		call_to_make_boxed_int.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "free_boxed_int", 1))
+		call_to_free_boxed_int.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "missing_location", 0))
+		{
+		  call_to_missing_location.set (call, fun);
+		  /* Simulate an event that's missing a useful location_t.  */
+		  call_to_missing_location.m_loc = UNKNOWN_LOCATION;
+		}
+	    }
+	}
+    }
+
+  if (call_to_malloc.m_fun)
+    {
+      auto_diagnostic_group d;
+
+      gcc_rich_location richloc (call_to_free.m_loc);
+      test_diagnostic_path path (global_dc->printer);
+      path.add_entry (entry_to_test, 0, "test");
+      path.add_call (call_to_make_boxed_int, 0,
+		     entry_to_make_boxed_int, "make_boxed_int");
+      path.add_call (call_to_wrapped_malloc, 1,
+		     entry_to_wrapped_malloc, "wrapped_malloc");
+      path.add_leaf_call (call_to_malloc, 2, "malloc");
+
+      for (int i = 0; i < 2; i++)
+	{
+	  path.add_call (call_to_free_boxed_int, 0,
+			 entry_to_free_boxed_int, "free_boxed_int");
+	  path.add_call (call_to_wrapped_free, 1,
+			 entry_to_wrapped_free, "wrapped_free");
+	  path.add_leaf_call (call_to_free, 2, "free");
+	  if (i == 0 && call_to_missing_location.m_fun)
+	    path.add_leaf_call (call_to_missing_location, 0, "missing_location");
+	}
+
+      richloc.set_path (&path);
+
+      diagnostic_metadata m;
+      m.add_cwe (415); /* CWE-415: Double Free.  */
+
+      warning_at (&richloc, m, 0,
+		  "double-free of %qs", "ptr");
+    }
+}
+
+unsigned int
+pass_test_show_path::execute (function *)
+{
+  example_1 ();
+  example_2 ();
+
+  return 0;
+}
+
+static opt_pass *
+make_pass_test_show_path (gcc::context *ctxt)
+{
+  return new pass_test_show_path (ctxt);
+}
+
+int
+plugin_init (struct plugin_name_args *plugin_info,
+	     struct plugin_gcc_version *version)
+{
+  struct register_pass_info pass_info;
+  const char *plugin_name = plugin_info->base_name;
+  int argc = plugin_info->argc;
+  struct plugin_argument *argv = plugin_info->argv;
+
+  if (!plugin_default_version_check (version, &gcc_version))
+    return 1;
+
+  pass_info.pass = make_pass_test_show_path (g);
+  pass_info.reference_pass_name = "whole-program";
+  pass_info.ref_pass_instance_number = 1;
+  pass_info.pos_op = PASS_POS_INSERT_BEFORE;
+  register_callback (plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL,
+		     &pass_info);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index 2f75463..4ae3048 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -94,6 +94,16 @@ set plugin_test_list [list \
 	  diagnostic-test-inlining-2.c \
 	  diagnostic-test-inlining-3.c \
 	  diagnostic-test-inlining-4.c } \
+    { diagnostic_plugin_test_paths.c \
+	  diagnostic-test-paths-1.c \
+	  diagnostic-test-paths-2.c \
+	  diagnostic-test-paths-3.c \
+	  diagnostic-path-format-default.c \
+	  diagnostic-path-format-none.c \
+	  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 } \
     { location_overflow_plugin.c \
 	  location-overflow-test-1.c \
 	  location-overflow-test-2.c \
diff --git a/gcc/toplev.c b/gcc/toplev.c
index df4ca01..e07a8be 100644
--- a/gcc/toplev.c
+++ b/gcc/toplev.c
@@ -1100,6 +1100,14 @@ general_init (const char *argv0, bool init_signals)
     = global_options_init.x_flag_diagnostics_show_labels;
   global_dc->show_line_numbers_p
     = global_options_init.x_flag_diagnostics_show_line_numbers;
+  global_dc->use_nn_for_line_numbers_p
+    = global_options_init.x_flag_diagnostics_nn_line_numbers;
+  global_dc->show_metadata
+    = global_options_init.x_flag_diagnostics_show_metadata;
+  global_dc->path_format
+    = (enum diagnostic_path_format)global_options_init.x_flag_diagnostics_path_format;
+  global_dc->show_path_depths
+    = global_options_init.x_flag_diagnostics_show_path_depths;
   global_dc->show_option_requested
     = global_options_init.x_flag_diagnostics_show_option;
   global_dc->min_margin_width
diff --git a/gcc/tree-diagnostic.c b/gcc/tree-diagnostic.c
index 7bd7430..d8f8ba7 100644
--- a/gcc/tree-diagnostic.c
+++ b/gcc/tree-diagnostic.c
@@ -29,6 +29,11 @@ along with GCC; see the file COPYING3.  If not see
 #include "tree-diagnostic.h"
 #include "langhooks.h"
 #include "intl.h"
+#include "diagnostic-path.h"
+#include "json.h"
+#include "gcc-rich-location.h"
+#include "diagnostic-color.h"
+#include "selftest.h"
 
 /* Prints out, if necessary, the name of the current function
    that caused an error.  Called from all error and warning functions.  */
@@ -36,9 +41,9 @@ void
 diagnostic_report_current_function (diagnostic_context *context,
 				    diagnostic_info *diagnostic)
 {
-  diagnostic_report_current_module (context, diagnostic_location (diagnostic));
-  lang_hooks.print_error_function (context, LOCATION_FILE (input_location),
-				   diagnostic);
+  location_t loc = diagnostic_location (diagnostic);
+  diagnostic_report_current_module (context, loc);
+  lang_hooks.print_error_function (context, LOCATION_FILE (loc), diagnostic);
 }
 
 static void
@@ -305,6 +310,537 @@ default_tree_printer (pretty_printer *pp, text_info *text, const char *spec,
   return true;
 }
 
+/* Subclass of range_label for showing a consecutive run of events
+   within a diagnostic_path as labelled ranges within one
+   gcc_rich_location.  */
+
+class path_label : public range_label
+{
+ public:
+  path_label (const diagnostic_path *path, unsigned start_idx)
+  : m_path (path), m_start_idx (start_idx)
+  {}
+
+  label_text get_text (unsigned range_idx) const FINAL OVERRIDE
+  {
+    unsigned event_idx = m_start_idx + range_idx;
+    const diagnostic_event &event = m_path->get_event (event_idx);
+
+    /* 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));
+    gcc_assert (event_text.m_buffer);
+    pretty_printer pp;
+    pp_show_color (&pp) = pp_show_color (global_dc->printer);
+    pp_printf (&pp, "%r(%i)%R %s",
+	       "path", // FIXME: or maybe support emphasizing them
+	       event_idx + 1, event_text.m_buffer);
+    event_text.maybe_free ();
+    label_text result = label_text::take (xstrdup (pp_formatted_text (&pp)));
+    return result;
+  }
+
+ private:
+  const diagnostic_path *m_path;
+  unsigned m_start_idx;
+};
+
+static void
+print_event_range (pretty_printer *pp,
+		   const diagnostic_path *path,
+		   unsigned start_idx, unsigned end_idx);
+
+/* Return true if E1 and E2 can be consolidated into the same run of events
+   when printing a diagnostic_path.  */
+
+static bool
+can_consolidate_events (const diagnostic_event &e1,
+			const diagnostic_event &e2)
+{
+  if (e1.get_fndecl () != e2.get_fndecl ())
+    return false;
+
+#if 0
+  // FIXME: do we need this?
+  if (e1.m_depth != e2.m_depth)
+    return false;
+#endif
+
+  location_t loc1 = e1.get_location ();
+  location_t loc2 = e2.get_location ();
+
+  /* FIXME.  */
+  if (loc1 < RESERVED_LOCATION_COUNT
+      || loc2 < RESERVED_LOCATION_COUNT)
+    return false;
+
+  /* Neither can be macro-based.  */
+  if (linemap_location_from_macro_expansion_p (line_table, loc1))
+    return false;
+  if (linemap_location_from_macro_expansion_p (line_table, loc2))
+    return false;
+
+  // TODO:
+  /* && LOCATION_LINE (other->m_loc) >= LOCATION_LINE (event->m_loc) */
+
+  /* Passed all the tests.  */
+  return true;
+}
+
+/* A class for grouing together the events in a diagnostic_path into
+   ranges of events, partitioned by stack frame (i.e. by fndecl and
+   stack depth).
+
+   For use by print_path_summary.  */
+
+struct path_summary
+{
+  /* A range of consecutive events within a diagnostic_path,
+     all with the same fndecl and stack_depth.  */
+  struct event_range
+  {
+    event_range (tree fndecl, int stack_depth, int start_idx, int end_idx)
+    : m_fndecl (fndecl), m_stack_depth (stack_depth),
+      m_start_idx (start_idx), m_end_idx (end_idx)
+    {}
+    tree m_fndecl;
+    int m_stack_depth;
+    int m_start_idx;
+    int m_end_idx;
+  };
+  path_summary (const diagnostic_path &path);
+
+  auto_vec <event_range> m_ranges;
+};
+
+/* path_summary's ctor.  */
+
+path_summary::path_summary (const diagnostic_path &path)
+{
+  const unsigned num_events = path.num_events ();
+
+  tree cur_fndecl = NULL_TREE;
+  int cur_stack_depth = 0;
+  unsigned cur_start_idx = 0;
+  bool in_range = false;
+  for (unsigned idx = 0; idx < num_events; idx++)
+    {
+      tree ev_fndecl = path.get_event (idx).get_fndecl ();
+      int ev_stack_depth = path.get_event (idx).get_stack_depth ();
+      if (ev_fndecl != cur_fndecl
+	  || ev_stack_depth != cur_stack_depth)
+	{
+	  if (in_range)
+	    m_ranges.safe_push (event_range (cur_fndecl, cur_stack_depth,
+					     cur_start_idx, idx - 1));
+	  in_range = true;
+	  cur_start_idx = idx;
+	}
+      cur_fndecl = ev_fndecl;
+      cur_stack_depth = ev_stack_depth;
+    }
+  if (in_range)
+    {
+      m_ranges.safe_push (event_range (cur_fndecl, cur_stack_depth,
+				       cur_start_idx, num_events - 1));
+    }
+}
+
+/* FIXME.  */
+
+static void
+write_indent (pretty_printer *pp, int spaces)
+{
+  for (int i = 0; i < spaces; i++)
+    pp_space (pp);
+}
+
+/* FIXME.  */
+/* We can't use "%qE" here since we can't guarantee the
+   capabilities of PP; -Wformat will complain.  */
+
+static void
+print_fndecl (pretty_printer *pp, tree fndecl, bool quoted)
+{
+  const char *n = DECL_NAME (fndecl)
+    ? identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2))
+    : _("<anonymous>");
+  if (quoted)
+    pp_printf (pp, "%qs", n);
+  else
+    pp_string (pp, n);
+}
+
+/* Print SUMMARY to PP, giving an overview of the interprocedural
+   calls and returns.
+
+   If SHOW_EVENT_SUMMARIES is true, then print the event descriptions
+   in a nested form.
+
+   'foo' (events 1-2)
+     | (1) desc 1
+     | (2) desc 2
+     +--> 'bar' (events 3-4)
+            | (3) desc 3
+            | (4) desc 4
+            +--> 'baz' (events 5-6)
+                   | (5) desc 5
+                   | (6) desc 6
+     <------------ +
+     |
+   'foo' (events 7-8)
+     | (7) desc 7
+     | (8) desc 8
+     +--> 'bar' (events 9-10)
+            | (9) desc 9
+            | (10) desc 10
+            +--> 'baz' (events 11-12)
+                   | (11) desc 11
+                   | (12) desc 12
+
+   If SHOW_EVENT_SUMMARIES is false, then print the event descriptions
+   within calls to diagnostic_show_locus, using labels to show the
+   events.
+
+   'foo' (events 1-2)
+     | NN |
+     |    |
+     +--> 'bar' (events 3-4)
+            | NN |
+            |    |
+            +--> 'baz' (events 5-6)
+                   | NN |
+                   |    |
+     <------------ +
+     |
+   'foo' (events 7-8)
+     | NN |
+     |    |
+     +--> 'bar' (events 9-10)
+            | NN |
+            |    |
+            +--> 'baz' (events 11-12)
+                   | NN |
+                   |    |
+
+   SHOW_EVENT_SUMMARIES==true is currently only available via selftests.  */
+
+static void
+print_path_summary (pretty_printer *pp,
+		    const path_summary& summary,
+		    const diagnostic_path *path,
+		    bool show_depths,
+		    bool show_event_summaries)
+{
+  auto_vec<int> name_lengths;
+
+  /* Populate name_lengths.  */
+  {
+    pretty_printer *tmp_pp = pp->clone ();
+    pp_show_color (tmp_pp) = 0;
+    int i;
+    path_summary::event_range *range;
+    FOR_EACH_VEC_ELT (summary.m_ranges, i, range)
+      {
+	print_fndecl (tmp_pp, range->m_fndecl, false);
+	/* We want the length of the quoted decl in characters, not in bytes.
+	   For now, we get the length of the unquoted decl (in bytes), hope
+	   it's ASCII, and add 2 for the quotes.  */
+	name_lengths.safe_push (strlen (pp_formatted_text (tmp_pp)) + 2);
+	pp_clear_output_area (tmp_pp);
+      }
+    delete tmp_pp;
+  }
+
+  /* FIXME: do we want this to have its own name?  */
+  const char *const line_color = "note";
+  const char *start_line_color
+    = colorize_start (pp_show_color (pp), line_color);
+  const char *end_line_color = colorize_stop (pp_show_color (pp));
+
+  /* Keep track of column numbers of existing '|' characters for
+     stack depths we've already printed.  */
+  const int EMPTY = -1;
+  const int DELETED = -2;
+  typedef int_hash <int, EMPTY, DELETED> vbar_hash;
+  hash_map <vbar_hash, int> vbar_column_for_depth;
+
+  /* Print the ranges.  */
+  const int base_indent = 2;
+  int cur_indent = base_indent;
+  unsigned i;
+  path_summary::event_range *range;
+  FOR_EACH_VEC_ELT (summary.m_ranges, i, range)
+    {
+      write_indent (pp, cur_indent);
+      if (i > 0)
+	{
+	  const path_summary::event_range *prev_range
+	    = &summary.m_ranges[i - 1];
+	  if (range->m_stack_depth > prev_range->m_stack_depth)
+	    {
+	      /* Show pushed stack frame(s).  */
+	      const char *push_prefix = "+--> ";
+	      pp_string (pp, start_line_color);
+	      pp_string (pp, push_prefix);
+	      pp_string (pp, end_line_color);
+	      cur_indent += strlen (push_prefix);
+	    }
+	}
+      print_fndecl (pp, range->m_fndecl, true);
+      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);
+      if (show_depths)
+	pp_printf (pp, " (depth %i)", range->m_stack_depth);
+      pp_newline (pp);
+
+      int midpoint = name_lengths[i] / 2;
+
+      if (show_event_summaries)
+	{
+	  for (int event_idx = range->m_start_idx;
+	       event_idx <= range->m_end_idx; event_idx++)
+	    {
+	      write_indent (pp, cur_indent + midpoint);
+	      label_text event_desc
+		= path->get_event (event_idx).get_desc (true);
+	      pp_string (pp, start_line_color);
+	      pp_string (pp, "|");
+	      pp_string (pp, end_line_color);
+	      pp_printf (pp, " (%i): %s",
+			 event_idx + 1, event_desc.m_buffer);
+	      event_desc.maybe_free ();
+	      pp_newline (pp);
+	    }
+	}
+      else
+	{
+	  write_indent (pp, cur_indent + midpoint);
+	  pp_string (pp, start_line_color);
+	  pp_string (pp, "|");
+	  pp_string (pp, end_line_color);
+	  /* No newline here, as diagnostic_show_locus adds a leading
+	     newline.  */
+
+	  char *saved_prefix = pp_take_prefix (pp);
+	  char *prefix;
+	  {
+	    pretty_printer tmp_pp;
+	    write_indent (&tmp_pp, cur_indent + midpoint);
+	    pp_string (&tmp_pp, start_line_color);
+	    pp_string (&tmp_pp, "|");
+	    pp_string (&tmp_pp, end_line_color);
+	    prefix = xstrdup (pp_formatted_text (&tmp_pp));
+	  }
+	  pp_set_prefix (pp, prefix);
+	  pp_prefixing_rule (pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+	  print_event_range (pp, path,
+			     range->m_start_idx, range->m_end_idx);
+	  pp_set_prefix (pp, saved_prefix);
+
+	  write_indent (pp, cur_indent + midpoint);
+	  pp_string (pp, start_line_color);
+	  pp_string (pp, "|");
+	  pp_string (pp, end_line_color);
+	  pp_newline (pp);
+	}
+
+      if (i < summary.m_ranges.length () - 1)
+	{
+	  const path_summary::event_range *next_range
+	    = &summary.m_ranges[i + 1];
+
+	  if (range->m_stack_depth > next_range->m_stack_depth)
+	    {
+	      /* Show returning from stack frame(s), by printing
+		 something like:
+		 "                   |\n"
+		 "     <------------ +\n"
+		 "     |\n".  */
+
+	      int next_midpoint = name_lengths[i + 1] / 2;
+
+	      gcc_assert (vbar_column_for_depth.get
+			  (next_range->m_stack_depth));
+
+	      int vbar_for_next_frame
+		= *vbar_column_for_depth.get (next_range->m_stack_depth);
+
+	      int indent_for_next_frame = vbar_for_next_frame - next_midpoint;
+	      write_indent (pp, vbar_for_next_frame);
+	      pp_string (pp, start_line_color);
+	      pp_character (pp, '<');
+	      for (int i = indent_for_next_frame + next_midpoint;
+		   i < cur_indent + midpoint - 1; i++)
+		pp_character (pp, '-');
+	      pp_character (pp, '+');
+	      pp_string (pp, end_line_color);
+	      pp_newline (pp);
+	      cur_indent = indent_for_next_frame;
+
+	      write_indent (pp, vbar_for_next_frame);
+	      pp_string (pp, start_line_color);
+	      pp_printf (pp, "|");
+	      pp_string (pp, end_line_color);
+	      pp_newline (pp);
+	    }
+	  else if (range->m_stack_depth < next_range->m_stack_depth)
+	    {
+	      /* Prepare to show pushed stack frame.  */
+	      gcc_assert (range->m_stack_depth != EMPTY);
+	      gcc_assert (range->m_stack_depth != DELETED);
+	      vbar_column_for_depth.put (range->m_stack_depth,
+					 cur_indent + midpoint);
+	      cur_indent += midpoint;
+	    }
+
+	}
+    }
+}
+
+/* FIXME.
+   Print events, consolidating them into rich locations.  */
+
+static void
+print_event_range (pretty_printer *pp,
+		   const diagnostic_path *path,
+		   unsigned start_idx, unsigned end_idx)
+{
+  while (start_idx <= end_idx)
+    {
+      path_label label (path, start_idx);
+      const diagnostic_event &event = path->get_event (start_idx);
+      gcc_rich_location richloc (event.get_location (), &label);
+      unsigned j;
+      for (j = start_idx + 1; j <= end_idx; j++)
+	{
+	  const diagnostic_event &other = path->get_event (j);
+	  // TODO: label, and slacken the constraint
+	  if (can_consolidate_events (event, other))
+	    if (richloc.add_location_if_nearby (other.get_location (),
+						false, &label))
+	      continue;
+	  break;
+	}
+
+      if (event.get_location () > BUILTINS_LOCATION)
+	{
+	  diagnostic_context dc;
+	  memset (&dc, 0, sizeof (dc));
+	  dc.printer = pp;
+	  dc.show_caret = true;
+	  dc.show_labels_p = global_dc->show_labels_p;
+	  dc.show_line_numbers_p = global_dc->show_line_numbers_p;
+	  dc.use_nn_for_line_numbers_p = global_dc->use_nn_for_line_numbers_p;
+	  dc.start_span = global_dc->start_span;
+	  dc.min_margin_width = global_dc->min_margin_width;
+	  dc.caret_chars[0] = '^';
+	  diagnostic_set_caret_max_width (&dc, pp_line_cutoff (pp));
+	  diagnostic_show_locus (&dc, &richloc, DK_DIAGNOSTIC_PATH);
+	}
+      else
+	{
+	  /* If we have an UNKNOWN_LOCATION as the primary location
+	     for an event, diagnostic_show_locus won't print anything.
+	     In particular the label for the event won't get printed.
+	     Fail more gracefully in this case by showing the event
+	     index and text, at no particular location.  */
+	  for (unsigned k = start_idx; k < j; k++)
+	    {
+	      const diagnostic_event &iter_event = path->get_event (k);
+	      label_text event_text (iter_event.get_desc (true));
+	      pp_printf (pp, " (%i): %s",
+			 k + 1, event_text.m_buffer);
+	      event_text.maybe_free ();
+	    }
+	}
+
+      start_idx = j;
+    }
+}
+
+/* FIXME.  */
+// This needs to be in tree-diagnostic.c so we can hack up current_function_decl,
+// which is used by lhd_print_error_function etc.
+
+void
+tree_print_diagnostic_path (diagnostic_context *context,
+			    const diagnostic_path *path)
+{
+  const unsigned num_events = path->num_events ();
+
+  switch (context->path_format)
+    {
+    case DPF_NONE:
+      /* Do nothing.  */
+      return;
+
+    case DPF_SEPARATE_EVENTS:
+      {
+	/* A note per event.  */
+	for (unsigned i = 0; i < num_events; i++)
+	  {
+	    const diagnostic_event &event = path->get_event (i);
+	    label_text event_text (event.get_desc (false));
+	    gcc_assert (event_text.m_buffer);
+	    inform (event.get_location (),
+		    "(%i) %s", i + 1, event_text.m_buffer);
+	    event_text.maybe_free ();
+	  }
+      }
+      break;
+
+    case DPF_INLINE_EVENTS:
+      {
+	/* Consolidate related events.  */
+	path_summary summary (*path);
+	char *saved_prefix = pp_take_prefix (context->printer);
+	pp_set_prefix (context->printer, NULL);
+	print_path_summary (context->printer,
+			    summary, path, context->show_path_depths, false);
+	pp_flush (context->printer);
+	pp_set_prefix (context->printer, saved_prefix);
+      }
+    }
+}
+
+/* This has to be here, rather than diagnostic-format-json.cc,
+   since diagnostic-format-json.o is within OBJS-libcommon and thus
+   doesn't have access to trees (for m_fndecl).  */
+
+static json::value *
+tree_make_json_for_path (diagnostic_context *, const diagnostic_path *path)
+{
+  json::array *path_array = new json::array ();
+  for (unsigned i = 0; i < path->num_events (); i++)
+    {
+      const diagnostic_event &event = path->get_event (i);
+
+      json::object *event_obj = new json::object ();
+      if (event.get_location ())
+	event_obj->set ("location",
+			json_from_expanded_location (event.get_location ()));
+      label_text event_text (event.get_desc (false));
+      event_obj->set ("description", new json::string (event_text.m_buffer));
+      event_text.maybe_free ();
+      if (tree fndecl = event.get_fndecl ())
+	{
+	  const char *function
+	    = identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2));
+	  event_obj->set ("function", new json::string (function));
+	}
+      event_obj->set ("depth", new json::number (event.get_stack_depth ()));
+      path_array->append (event_obj);
+    }
+  return path_array;
+}
+
 /* Sets CONTEXT to use language independent diagnostics.  */
 void
 tree_diagnostics_defaults (diagnostic_context *context)
@@ -312,4 +848,266 @@ tree_diagnostics_defaults (diagnostic_context *context)
   diagnostic_starter (context) = default_tree_diagnostic_starter;
   diagnostic_finalizer (context) = default_diagnostic_finalizer;
   diagnostic_format_decoder (context) = default_tree_printer;
+  context->print_path = tree_print_diagnostic_path;
+  context->make_json_for_path = tree_make_json_for_path;
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* A subclass of simple_diagnostic_path that adds member functions
+   for adding test events.  */
+
+class test_diagnostic_path : public simple_diagnostic_path
+{
+ public:
+  test_diagnostic_path (pretty_printer *event_pp)
+  : simple_diagnostic_path (event_pp)
+  {
+  }
+
+  void add_entry (tree fndecl, int stack_depth)
+  {
+    add_event (UNKNOWN_LOCATION, fndecl, stack_depth,
+	       "entering %qE", fndecl);
+  }
+
+  void add_return (tree fndecl, int stack_depth)
+  {
+    add_event (UNKNOWN_LOCATION, fndecl, stack_depth,
+	       "returning to %qE", fndecl);
+  }
+
+  void add_call (tree caller, int caller_stack_depth, tree callee)
+  {
+    add_event (UNKNOWN_LOCATION, caller, caller_stack_depth,
+	       "calling %qE", callee);
+    add_entry (callee, caller_stack_depth + 1);
+  }
+};
+
+/* Verify that empty paths are handled gracefully.  */
+
+static void
+test_empty_path (pretty_printer *event_pp)
+{
+  test_diagnostic_path path (event_pp);
+  ASSERT_FALSE (path.interprocedural_p ());
+
+  path_summary summary (path);
+  ASSERT_EQ (summary.m_ranges.length (), 0);
+
+  pretty_printer pp;
+  print_path_summary (&pp, summary, &path, true, true);
+  ASSERT_STREQ ("",
+		pp_formatted_text (&pp));
+}
+
+/* Verify that print_path_summary works on a purely intraprocedural path.  */
+
+static void
+test_intraprocedural_path (pretty_printer *event_pp)
+{
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_foo = build_fn_decl ("foo", fntype_void_void);
+
+  test_diagnostic_path path (event_pp);
+  path.add_event (UNKNOWN_LOCATION, fndecl_foo, 0, "first %qs", "free");
+  path.add_event (UNKNOWN_LOCATION, fndecl_foo, 0, "double %qs", "free");
+
+  ASSERT_FALSE (path.interprocedural_p ());
+
+  path_summary summary (path);
+  ASSERT_EQ (summary.m_ranges.length (), 1);
+
+  pretty_printer pp;
+  print_path_summary (&pp, summary, &path, true, true);
+  ASSERT_STREQ ("  `foo': events 1-2 (depth 0)\n"
+		"    | (1): first `free'\n"
+		"    | (2): double `free'\n",
+		pp_formatted_text (&pp));
+}
+
+/* Verify that print_path_summary works on an interprocedural path.  */
+
+static void
+test_interprocedural_path_1 (pretty_printer *event_pp)
+{
+  /* Build fndecls.  The types aren't quite right, but that
+     doesn't matter for the purposes of this test.  */
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_test = build_fn_decl ("test", fntype_void_void);
+  tree fndecl_make_boxed_int
+    = build_fn_decl ("make_boxed_int", fntype_void_void);
+  tree fndecl_wrapped_malloc
+    = build_fn_decl ("wrapped_malloc", fntype_void_void);
+  tree fndecl_free_boxed_int
+    = build_fn_decl ("free_boxed_int", fntype_void_void);
+  tree fndecl_wrapped_free
+    = build_fn_decl ("wrapped_free", fntype_void_void);
+
+  test_diagnostic_path path (event_pp);
+  path.add_entry (fndecl_test, 0);
+  path.add_call (fndecl_test, 0, fndecl_make_boxed_int);
+  path.add_call (fndecl_make_boxed_int, 1, fndecl_wrapped_malloc);
+  path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_malloc, 2, "calling malloc");
+  path.add_return (fndecl_test, 0);
+  path.add_call (fndecl_test, 0, fndecl_free_boxed_int);
+  path.add_call (fndecl_free_boxed_int, 1, fndecl_wrapped_free);
+  path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_free, 2, "calling free");
+  path.add_return (fndecl_test, 0);
+  path.add_call (fndecl_test, 0, fndecl_free_boxed_int);
+  path.add_call (fndecl_free_boxed_int, 1, fndecl_wrapped_free);
+  path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_free, 2, "calling free");
+  ASSERT_EQ (path.num_events (), 18);
+
+  ASSERT_TRUE (path.interprocedural_p ());
+
+  path_summary summary (path);
+  ASSERT_EQ (summary.m_ranges.length (), 9);
+
+  pretty_printer pp;
+  print_path_summary (&pp, summary, &path, true, true);
+  ASSERT_STREQ
+    ("  `test': events 1-2 (depth 0)\n"
+     "     | (1): entering `test'\n"
+     "     | (2): calling `make_boxed_int'\n"
+     "     +--> `make_boxed_int': events 3-4 (depth 1)\n"
+     "                  | (3): entering `make_boxed_int'\n"
+     "                  | (4): calling `wrapped_malloc'\n"
+     "                  +--> `wrapped_malloc': events 5-6 (depth 2)\n"
+     "                               | (5): entering `wrapped_malloc'\n"
+     "                               | (6): calling malloc\n"
+     "     <-------------------------+\n"
+     "     |\n"
+     "  `test': events 7-8 (depth 0)\n"
+     "     | (7): returning to `test'\n"
+     "     | (8): calling `free_boxed_int'\n"
+     "     +--> `free_boxed_int': events 9-10 (depth 1)\n"
+     "                  | (9): entering `free_boxed_int'\n"
+     "                  | (10): calling `wrapped_free'\n"
+     "                  +--> `wrapped_free': events 11-12 (depth 2)\n"
+     "                              | (11): entering `wrapped_free'\n"
+     "                              | (12): calling free\n"
+     "     <------------------------+\n"
+     "     |\n"
+     "  `test': events 13-14 (depth 0)\n"
+     "     | (13): returning to `test'\n"
+     "     | (14): calling `free_boxed_int'\n"
+     "     +--> `free_boxed_int': events 15-16 (depth 1)\n"
+     "                  | (15): entering `free_boxed_int'\n"
+     "                  | (16): calling `wrapped_free'\n"
+     "                  +--> `wrapped_free': events 17-18 (depth 2)\n"
+     "                              | (17): entering `wrapped_free'\n"
+     "                              | (18): calling free\n",
+     pp_formatted_text (&pp));
 }
+
+/* Example where we pop the stack to an intermediate frame, rather than the
+   initial one.  */
+
+static void
+test_interprocedural_path_2 (pretty_printer *event_pp)
+{
+  /* Build fndecls.  The types aren't quite right, but that
+     doesn't matter for the purposes of this test.  */
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_foo = build_fn_decl ("foo", fntype_void_void);
+  tree fndecl_bar = build_fn_decl ("bar", fntype_void_void);
+  tree fndecl_baz = build_fn_decl ("baz", fntype_void_void);
+
+  test_diagnostic_path path (event_pp);
+  path.add_entry (fndecl_foo, 0);
+  path.add_call (fndecl_foo, 0, fndecl_bar);
+  path.add_call (fndecl_bar, 1, fndecl_baz);
+  path.add_return (fndecl_bar, 1);
+  path.add_call (fndecl_bar, 1, fndecl_baz);
+  ASSERT_EQ (path.num_events (), 8);
+
+  ASSERT_TRUE (path.interprocedural_p ());
+
+  path_summary summary (path);
+  ASSERT_EQ (summary.m_ranges.length (), 5);
+
+  pretty_printer pp;
+  print_path_summary (&pp, summary, &path, true, true);
+  ASSERT_STREQ
+    ("  `foo': events 1-2 (depth 0)\n"
+     "    | (1): entering `foo'\n"
+     "    | (2): calling `bar'\n"
+     "    +--> `bar': events 3-4 (depth 1)\n"
+     "           | (3): entering `bar'\n"
+     "           | (4): calling `baz'\n"
+     "           +--> `baz': event 5 (depth 2)\n"
+     "                  | (5): entering `baz'\n"
+     "           <------+\n"
+     "           |\n"
+     "         `bar': events 6-7 (depth 1)\n"
+     "           | (6): returning to `bar'\n"
+     "           | (7): calling `baz'\n"
+     "           +--> `baz': event 8 (depth 2)\n"
+     "                  | (8): entering `baz'\n",
+     pp_formatted_text (&pp));
+}
+
+/* Verify that print_path_summary is sane in the face of a recursive
+   diagnostic_path.  */
+
+static void
+test_recursion (pretty_printer *event_pp)
+{
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_factorial = build_fn_decl ("factorial", fntype_void_void);
+
+ test_diagnostic_path path (event_pp);
+  path.add_entry (fndecl_factorial, 0);
+  for (int depth = 0; depth < 3; depth++)
+    path.add_call (fndecl_factorial, depth, fndecl_factorial);
+  ASSERT_EQ (path.num_events (), 7);
+
+  ASSERT_TRUE (path.interprocedural_p ());
+
+  path_summary summary (path);
+  ASSERT_EQ (summary.m_ranges.length (), 4);
+
+  pretty_printer pp;
+  print_path_summary (&pp, summary, &path, true, true);
+  ASSERT_STREQ
+    ("  `factorial': events 1-2 (depth 0)\n"
+     "       | (1): entering `factorial'\n"
+     "       | (2): calling `factorial'\n"
+     "       +--> `factorial': events 3-4 (depth 1)\n"
+     "                 | (3): entering `factorial'\n"
+     "                 | (4): calling `factorial'\n"
+     "                 +--> `factorial': events 5-6 (depth 2)\n"
+     "                           | (5): entering `factorial'\n"
+     "                           | (6): calling `factorial'\n"
+     "                           +--> `factorial': event 7 (depth 3)\n"
+     "                                     | (7): entering `factorial'\n",
+     pp_formatted_text (&pp));
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+tree_diagnostic_c_tests ()
+{
+  auto_fix_quotes fix_quotes;
+  pretty_printer *event_pp = global_dc->printer->clone ();
+  pp_show_color (event_pp) = 0;
+  test_empty_path (event_pp);
+  test_intraprocedural_path (event_pp);
+  test_interprocedural_path_1 (event_pp);
+  test_interprocedural_path_2 (event_pp);
+  test_recursion (event_pp);
+  delete event_pp;
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
diff --git a/libcpp/include/line-map.h b/libcpp/include/line-map.h
index e4c7952..bd26fb7c 100644
--- a/libcpp/include/line-map.h
+++ b/libcpp/include/line-map.h
@@ -1459,6 +1459,7 @@ semi_embedded_vec<T, NUM_EMBEDDED>::truncate (int len)
 }
 
 class fixit_hint;
+class diagnostic_path;
 
 /* A "rich" source code location, for use when printing diagnostics.
    A rich_location has one or more carets&ranges, where the carets
@@ -1754,6 +1755,10 @@ class rich_location
     return !m_fixits_cannot_be_auto_applied;
   }
 
+  /* An optional path through the code.  */
+  const diagnostic_path *get_path () const { return m_path; }
+  void set_path (const diagnostic_path *path) { m_path = path; }
+
 private:
   bool reject_impossible_fixit (location_t where);
   void stop_supporting_fixits ();
@@ -1778,6 +1783,8 @@ protected:
 
   bool m_seen_impossible_fixit;
   bool m_fixits_cannot_be_auto_applied;
+
+  const diagnostic_path *m_path;
 };
 
 /* A struct for the result of range_label::get_text: a NUL-terminated buffer
diff --git a/libcpp/line-map.c b/libcpp/line-map.c
index d6924eb..aa81e19 100644
--- a/libcpp/line-map.c
+++ b/libcpp/line-map.c
@@ -2002,7 +2002,8 @@ rich_location::rich_location (line_maps *set, location_t loc,
   m_have_expanded_location (false),
   m_fixit_hints (),
   m_seen_impossible_fixit (false),
-  m_fixits_cannot_be_auto_applied (false)
+  m_fixits_cannot_be_auto_applied (false),
+  m_path (NULL)
 {
   add_range (loc, SHOW_RANGE_WITH_CARET, label);
 }
-- 
1.8.5.3

