From c903032d99eb2f020f29cfb52241709ea4cbb917 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Thu, 3 Jan 2019 12:53:14 -0500
Subject: [PATCH 23/33] FIXME: diagnostic paths and diagnostic_metadata

FIXME: combination of path fixes; adding metadata, and interprocedural path visualization

FIXME: tree-diagnostic.c: path-handling fixup

Fixups to diagnostic paths

FIXME: add diagnostic-path-summary-format- plugin tests

FIXME: fixups to gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c

FIXME: fixups to diagnostic_metadata and paths

FIXME: simplify and consolidate diagnostic_path printing

FIXME: add stray diagnostic path plugin tests

FIXME: add missing diagnostic-metadata.h
---
 gcc/common.opt                                     |  24 +
 gcc/diagnostic-core.h                              |  10 +
 gcc/diagnostic-format-json.cc                      |  34 +-
 gcc/diagnostic-metadata.h                          |  44 ++
 gcc/diagnostic-path.h                              | 143 ++++
 gcc/diagnostic-show-locus.c                        |  19 +-
 gcc/diagnostic.c                                   | 287 ++++++-
 gcc/diagnostic.h                                   |  35 +
 gcc/gcc-rich-location.h                            |   4 +-
 gcc/opts.c                                         |  12 +
 gcc/selftest-run-tests.c                           |   1 +
 gcc/selftest.h                                     |   1 +
 .../gcc.dg/plugin/diagnostic-path-format-default.c | 140 ++++
 .../plugin/diagnostic-path-format-inline-events.c  | 140 ++++
 .../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   | 379 +++++++++
 gcc/testsuite/gcc.dg/plugin/plugin.exp             |   8 +
 gcc/toplev.c                                       |   6 +
 gcc/tree-diagnostic.c                              | 871 ++++++++++++++++++++-
 gcc/tree-diagnostic.h                              |  44 +-
 libcpp/include/line-map.h                          |   7 +
 libcpp/line-map.c                                  |   3 +-
 26 files changed, 2383 insertions(+), 50 deletions(-)
 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.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/common.opt b/gcc/common.opt
index 42fe7ec..3d90334 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1314,6 +1314,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)
+FIXME; TODO: add to docs
+
+fdiagnostics-path-format=
+Common Joined RejectNegative Var(flag_diagnostics_path_format) Enum(diagnostic_path_format) Init(DPF_INLINE_EVENTS)
+FIXME; TODO: docs
+
+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. (TODO: add to docs)
+
 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/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-format-json.cc b/gcc/diagnostic-format-json.cc
index da03b29..a8d7ebf 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"
 
 /* The top-level JSON array of pending diagnostics.  */
@@ -40,7 +41,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);
@@ -102,6 +103,20 @@ json_from_fixit_hint (const fixit_hint *hint)
   return fixit_obj;
 }
 
+/* FIXME.  */
+
+static json::object *
+json_from_metadata (const diagnostic_metadata *metadata)
+{
+  json::object *metadata_obj = new json::object ();
+
+  // TODO:
+  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
@@ -210,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.  */
@@ -266,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..2103971
--- /dev/null
+++ b/gcc/diagnostic-metadata.h
@@ -0,0 +1,44 @@
+/* Arbitrary key-value pairs 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
+
+/* FIXME.  */
+
+class diagnostic_metadata
+{
+ public:
+  diagnostic_metadata () : m_cwe (0) {}
+
+  void add_cwe (int cwe) { m_cwe = cwe; }
+  int get_cwe () const { return m_cwe; }
+
+  // TODO: how to store?
+ private:
+  int m_cwe;
+#if 0  
+  typedef hash_map <char *, char *,
+    simple_hashmap_traits<nofree_string_hash, char *> > map_t;
+  map_t m_map;
+#endif
+};
+
+#endif /* ! GCC_DIAGNOSTIC_METADATA_H */
diff --git a/gcc/diagnostic-path.h b/gcc/diagnostic-path.h
new file mode 100644
index 0000000..353437d
--- /dev/null
+++ b/gcc/diagnostic-path.h
@@ -0,0 +1,143 @@
+/* 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
+
+/* 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);
+           |     ^~~~~~~~~~~~~~~~~~~~~~~~~
+     test.c:25:10: note: (1) when 'PyList_New' fails, returning NULL
+        25 |   list = PyList_New(0);
+           |          ^~~~~~~~~~~~~
+        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
+    note, 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 notes.  */
+
+/* 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;
+};
+
+// FIXME:
+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 e2fd0f8..bd7f3af 100644
--- a/gcc/diagnostic-show-locus.c
+++ b/gcc/diagnostic-show-locus.c
@@ -1552,7 +1552,7 @@ 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);
 		pp_string (m_pp, label->m_text.m_buffer);
@@ -1561,7 +1561,7 @@ layout::print_any_labels (linenum_type row)
 	      }
 	    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, '|');
@@ -2243,7 +2243,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
@@ -2252,10 +2254,17 @@ 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;
+  
+#if 0
+  // FIXME: additional constraints (maybe a callback?)
+  if (LOCATION_LINE (loc) < LOCATION_LINE (get_loc ()))
+    return false;
+#endif
 
-  add_range (loc);
+  add_range (loc, SHOW_RANGE_WITHOUT_CARET, label);
   return true;
 }
 
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index e310437..3f8df0b 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -31,6 +31,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "backtrace.h"
 #include "diagnostic.h"
 #include "diagnostic-color.h"
+#include "diagnostic-metadata.h"
+#include "diagnostic-path.h"
 #include "edit-context.h"
 #include "selftest.h"
 #include "selftest-diagnostic.h"
@@ -49,11 +51,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;
@@ -174,6 +178,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;
@@ -290,6 +297,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;
 }
@@ -644,6 +652,36 @@ diagnostic_report_current_module (diagnostic_context *context, location_t where)
     }
 }
 
+/* FIXME.  */
+
+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);
+}
+
+/* FIXME.  */
+
+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)
@@ -882,6 +920,43 @@ update_effective_level_from_pragmas (diagnostic_context *context,
   return diag_class;
 }
 
+/* FIXME.  */
+
+static char *
+get_cwe_url (int cwe)
+{
+  return xasprintf ("https://cwe.mitre.org/data/definitions/%i.html", cwe);
+}
+
+/* FIXME.  */
+
+static void
+print_any_metadata (diagnostic_context *context,
+		    const diagnostic_info *diagnostic)
+{
+  if (diagnostic->metadata == NULL)
+    return;
+
+  // FIXME:
+  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.  */
@@ -1041,6 +1116,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);
@@ -1058,6 +1135,8 @@ diagnostic_report_diagnostic (diagnostic_context *context,
 
   context->lock--;
 
+  diagnostic_show_any_path (context, diagnostic);
+
   return true;
 }
 
@@ -1165,8 +1244,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;
@@ -1182,13 +1261,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)
@@ -1208,6 +1289,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);
 }
 
@@ -1221,7 +1303,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;
 }
@@ -1235,7 +1317,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;
 }
@@ -1247,7 +1329,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
@@ -1259,7 +1352,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);
 }
 
@@ -1272,7 +1365,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);
 }
 
@@ -1286,7 +1379,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);
 }
@@ -1301,7 +1394,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;
 }
@@ -1317,7 +1410,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;
 }
@@ -1332,7 +1425,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;
+}
+
+/* FIXME.  */
+
+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;
 }
@@ -1348,7 +1459,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);
@@ -1367,7 +1478,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);
@@ -1394,7 +1505,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;
 }
@@ -1409,7 +1520,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;
 }
@@ -1428,7 +1539,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;
 }
@@ -1443,7 +1554,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;
 }
@@ -1457,7 +1568,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);
 }
 
@@ -1471,7 +1582,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);
 }
@@ -1484,7 +1595,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);
 }
 
@@ -1498,7 +1609,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);
 }
 
@@ -1512,7 +1623,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);
 }
 
@@ -1524,7 +1635,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);
 }
 
@@ -1546,7 +1657,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 ();
@@ -1563,7 +1674,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 ();
@@ -1579,7 +1690,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 ();
@@ -1660,6 +1771,120 @@ auto_diagnostic_group::~auto_diagnostic_group ()
     }
 }
 
+#if 0
+/* struct diagnostic_path */
+
+diagnostic_path::diagnostic_path () : m_events ()
+{
+}
+
+/* FIXME: would it be better to use a ref-counted string to make this copying less expensive??? */
+
+diagnostic_path::diagnostic_path (const diagnostic_path &other)
+: m_events (other.m_events.length ())
+{
+  event *e;
+  int i;
+  FOR_EACH_VEC_ELT (other.m_events, i, e)
+    m_events.quick_push (new event (e->m_loc, e->m_fndecl, e->m_depth,
+				    e->m_desc));
+#if 0
+  fprintf (stderr, "DBG: diagnostic_path copy-ctor (length=%i)\n", m_events.length ());
+#endif
+}
+
+diagnostic_path&
+diagnostic_path::operator= (const diagnostic_path &other)
+{
+  gcc_unreachable (); // FIXME
+}
+
+/* diagnostic_path's dtor.  Delete the events.  */
+
+diagnostic_path::~diagnostic_path ()
+{
+  event *n;
+  int i;
+  FOR_EACH_VEC_ELT (m_events, i, n)
+    delete n;
+}
+
+#endif
+
+/* FIXME.  */
+
+unsigned
+simple_diagnostic_path::num_events () const
+{
+  return m_events.length ();
+}
+
+/* FIXME.  */
+
+const diagnostic_event &
+simple_diagnostic_path::get_event (int idx) const
+{
+  return *m_events[idx];
+}
+
+/* FIXME.  */
+// Use m_context's printer to format the events.
+
+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::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::~simple_diagnostic_event ()
+{
+  free (m_desc);
+}
+
+// FIXME:
+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.  */
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index da4b06d..3cfadbc 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,9 @@ struct diagnostic_info
   /* The location at which the diagnostic is to be reported.  */
   rich_location *richloc;
 
+  /* FIXME.  */
+  const diagnostic_metadata *metadata;
+
   /* Auxiliary data for client.  */
   void *x_data;
   /* The kind of diagnostic it is about.  */
@@ -76,6 +95,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 +146,15 @@ struct diagnostic_context
   /* Character used for caret diagnostics.  */
   char caret_chars[rich_location::STATICALLY_ALLOCATED_RANGES];
 
+  /* FIXME.  */
+  bool show_metadata;
+
+  /* FIXME.  */
+  enum diagnostic_path_format path_format;
+
+  /* FIXME.  */
+  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 +229,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;
 
@@ -340,6 +372,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 *,
@@ -431,4 +464,6 @@ extern void diagnostic_output_format_init (diagnostic_context *,
 /* Compute the number of digits in the decimal representation of an integer.  */
 extern int num_digits (int);
 
+extern json::value *json_from_expanded_location (location_t loc);
+
 #endif /* ! GCC_DIAGNOSTIC_H */
diff --git a/gcc/gcc-rich-location.h b/gcc/gcc-rich-location.h
index 8ca1ff5..b4d47d4 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/opts.c b/gcc/opts.c
index f0a96b9..7a3390f 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -2400,6 +2400,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/selftest-run-tests.c b/gcc/selftest-run-tests.c
index 184dfa6..3fb31ab 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -96,6 +96,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 3e00e75..88dc951 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -246,6 +246,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/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.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events.c
new file mode 100644
index 0000000..a457283
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events.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-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..8138c8c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
@@ -0,0 +1,379 @@
+/* { 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");
+      // TODO: show param within decl
+      //inform (
+    }
+}
+
+/* FIXME.  */
+
+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;
+};
+
+/* FIXME.  */
+
+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);
+  }
+
+#if 0
+  void add_return (tree fndecl, int stack_depth)
+  {
+    add_event (UNKNOWN_LOCATION, fndecl, stack_depth,
+	       "returning to %qE", fndecl);
+  }
+#endif
+  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);
+  }
+};
+
+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;
+
+  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 (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");
+	}
+
+      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");
+    }
+}
+
+
+/* FIXME.  */
+
+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..6adb761 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -94,6 +94,14 @@ 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.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 6b3bbdc..1ed7022 100644
--- a/gcc/toplev.c
+++ b/gcc/toplev.c
@@ -1119,6 +1119,12 @@ 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->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..c33617e 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,554 @@ 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 without colorization: we
+       shouldn't colorize within a range_label.  */
+    label_text event_text (event.get_desc (false));
+    gcc_assert (event_text.m_buffer);
+    label_text result
+      = label_text::take (xasprintf ("(%i) %s", event_idx + 1,
+				     event_text.m_buffer));
+    event_text.maybe_free ();
+    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,
+		   bool use_diagnostic);
+
+/* FIXME.  */
+
+static bool
+can_consolidate_events (const diagnostic_event &e1,
+			const diagnostic_event &e2)
+{
+  // FIXME: might want to simply group them by function, and also
+  // split whenever there's a backward jump?
+  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
+
+  /* Neither can be macro-based.  */
+  if (linemap_location_from_macro_expansion_p (line_table,
+					       e1.get_location ()))
+    return false;
+  if (linemap_location_from_macro_expansion_p (line_table,
+					       e2.get_location ()))
+    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_events_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_events_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,
+			     false);
+	  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;
+	    }
+
+	}
+    }
+  /* TODO: example of recursion in DejaGnu tests.  */
+}
+
+/* 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,
+		   bool use_diagnostic)
+{
+  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 (),
+				 use_diagnostic ? NULL : &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 1
+      /* Hack up state so that diagnostic_report_current_function will announce
+	 function name changes.  */
+      tree saved_current_function_decl = current_function_decl;
+      location_t saved_input_location = input_location;
+      current_function_decl = event.get_fndecl ();
+      input_location = event.get_location ();
+#endif
+
+#if 0
+      /* Alternatively, part of lhd_print_error_function.  */
+      char *old_prefix = pp_take_prefix (pp);
+      const char *file = LOCATION_FILE (event->m_loc);
+      char *new_prefix = file ? file_name_as_prefix (context, file) : NULL;
+      tree fndecl = event->m_fndecl;
+      if (TREE_CODE (TREE_TYPE (fndecl)) == METHOD_TYPE)
+	pp_printf
+	  (pp, _("In member function %qs"),
+	   identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2)));
+      else
+	pp_printf
+	  (pp, _("In function %qs"),
+	   identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2)));
+      pp_newline_and_flush (pp);
+      pp->prefix = old_prefix;
+      free ((char*) new_prefix);
+#endif
+
+      if (use_diagnostic)
+	{
+	  label_text event_text (event.get_desc (true));
+	  inform (&richloc, "(%i) %s", start_idx + 1, event_text.m_buffer);
+	  event_text.maybe_free ();
+	}
+      else
+	{
+	  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.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_NOTE); // FIXME
+	}
+
+      start_idx = j;
+
+#if 1
+      current_function_decl = saved_current_function_decl;
+      input_location = saved_input_location;
+#endif
+    }
+}
+
+/* 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 (true));
+	    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 +865,316 @@ 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 {
+
+/* FIXME.  */
+
+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);
+  }
+};
+
+/* FIXME.  */
+
+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));
+}
+
+/* FIXME.  */
+
+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, false);
+    ASSERT_STREQ ("  `foo': events 1-2 (depth 0)\n"
+		  "    |\n"
+		  "    |\n",
+		  pp_formatted_text (&pp));
+  }
+
+  {
+    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));
+  }
+}
+
+/* FIXME.  */
+
+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, false);
+    ASSERT_STREQ
+      ("  `test': events 1-2 (depth 0)\n"
+       "     |\n"
+       "     |\n"
+       "     +--> `make_boxed_int': events 3-4 (depth 1)\n"
+       "                  |\n"
+       "                  |\n"
+       "                  +--> `wrapped_malloc': events 5-6 (depth 2)\n"
+       "                               |\n"
+       "                               |\n"
+       "     <-------------------------+\n"
+       "     |\n"
+       "  `test': events 7-8 (depth 0)\n"
+       "     |\n"
+       "     |\n"
+       "     +--> `free_boxed_int': events 9-10 (depth 1)\n"
+       "                  |\n"
+       "                  |\n"
+       "                  +--> `wrapped_free': events 11-12 (depth 2)\n"
+       "                              |\n"
+       "                              |\n"
+       "     <------------------------+\n"
+       "     |\n"
+       "  `test': events 13-14 (depth 0)\n"
+       "     |\n"
+       "     |\n"
+       "     +--> `free_boxed_int': events 15-16 (depth 1)\n"
+       "                  |\n"
+       "                  |\n"
+       "                  +--> `wrapped_free': events 17-18 (depth 2)\n"
+       "                              |\n"
+       "                              |\n",
+       pp_formatted_text (&pp));
+  }
+
+  {
+    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));
+  }
+}
+
+/* FIXME.  */
+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/gcc/tree-diagnostic.h b/gcc/tree-diagnostic.h
index 3ce61c0..8e70acc 100644
--- a/gcc/tree-diagnostic.h
+++ b/gcc/tree-diagnostic.h
@@ -33,20 +33,48 @@ along with GCC; see the file COPYING3.  If not see
 #define diagnostic_last_function(DC)			\
   ((tree) diagnostic_context_auxiliary_data (DC))
 
+#define diagnostic_get_function(DI)		\
+  ( diagnostic_abstract_origin (DI)		\
+    ? diagnostic_abstract_origin (DI)		\
+    : current_function_decl)
+
 /* True if the last function in which a diagnostic was reported is
    different from the current one.  */
-#define diagnostic_last_function_changed(DC, DI)			\
-  (diagnostic_last_function (DC) != (diagnostic_abstract_origin (DI)	\
-				     ? diagnostic_abstract_origin (DI)	\
-				     : current_function_decl))
+#if 1
+#define diagnostic_last_function_changed(DC, DI)                       \
+  (diagnostic_last_function (DC) != (diagnostic_abstract_origin (DI)   \
+                                    ? diagnostic_abstract_origin (DI)  \
+                                    : current_function_decl))
+#else
+static inline bool
+diagnostic_last_function_changed (diagnostic_context *context,
+				  diagnostic_info *diagnostic)
+{
+  tree last_fn = diagnostic_last_function (context);
+  tree diag_fn = diagnostic_get_function (diagnostic);
+  return last_fn != diag_fn;
+}
+#endif
 
 /* Remember the current function as being the last one in which we report
    a diagnostic.  */
-#define diagnostic_set_last_function(DC, DI)		\
-  diagnostic_context_auxiliary_data (DC)		\
-    = (((DI) && diagnostic_abstract_origin (DI))	\
-       ? diagnostic_abstract_origin (DI)		\
+#if 1
+#define diagnostic_set_last_function(DC, DI)           \
+  diagnostic_context_auxiliary_data (DC)               \
+    = (((DI) && diagnostic_abstract_origin (DI))       \
+       ? diagnostic_abstract_origin (DI)               \
        : current_function_decl)
+#else
+static inline void
+diagnostic_set_last_function (diagnostic_context *context,
+			      diagnostic_info *diagnostic)
+{
+  diagnostic_context_auxiliary_data (context)
+    = (diagnostic
+       ? diagnostic_get_function (diagnostic)
+       : current_function_decl);
+}
+#endif
 
 void diagnostic_report_current_function (diagnostic_context *,
 					 diagnostic_info *);
diff --git a/libcpp/include/line-map.h b/libcpp/include/line-map.h
index 4c41e75..107fb46 100644
--- a/libcpp/include/line-map.h
+++ b/libcpp/include/line-map.h
@@ -1455,6 +1455,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
@@ -1750,6 +1751,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 ();
@@ -1774,6 +1779,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 ff679ed..eb953c3 100644
--- a/libcpp/line-map.c
+++ b/libcpp/line-map.c
@@ -1997,7 +1997,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

