[12/49] Add diagnostic paths
diff mbox series

Message ID 1573867416-55618-13-git-send-email-dmalcolm@redhat.com
State New
Headers show
Series
  • RFC: Add a static analysis framework to GCC
Related show

Commit Message

David Malcolm Nov. 16, 2019, 1:22 a.m. UTC
This patch adds support for associating a "diagnostic_path" with a
diagnostic: a sequence of events predicted by the compiler that leads to
the problem occurring, with their locations in the user's source,
text descriptions, and stack information (for handling interprocedural
paths).

For example, the following (hypothetical) error has a 3-event
intraprocedural path:

test.c: In function 'demo':
test.c:29:5: error: passing NULL as argument 1 to 'PyList_Append' which
  requires a non-NULL parameter
   29 |     PyList_Append(list, item);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~
  'demo': 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 from (1) as argument 1
     |

The patch adds a new "%@" format code for printing event IDs, so that
in the above, the description of event (3) mentions event (1), showing
the user where the bogus NULL value comes from (the event IDs are
colorized to draw the user's attention to them).

There is a separation between data vs presentation: the above shows how
the diagnostic-printing code has consolidated the path into a single run
of events, since all the events are near each other and within the same
function; more complicated examples (such as interprocedural paths)
might be printed as multiple runs of events.

Examples of how interprocedural paths are printed can be seen in the
test suite (which uses a plugin to exercise the code without relying
on specific warnings using this functionality).

Other output formats include
- JSON,
- printing each event as a separate "note", and
- to not emit paths.

(I have a separate script that can generate HTML from the JSON, but HTML
is not my speciality; help from a web front-end expert to make it look
good would be appreciated).

gcc/ChangeLog:
	* Makefile.in (OBJS): Add tree-diagnostic-path.o.
	* common.opt (fdiagnostics-path-format=): New option.
	(diagnostic_path_format): New enum.
	(fdiagnostics-show-path-depths): New option.
	* coretypes.h (diagnostic_event_id_t): New forward decl.
	* diagnostic-color.c (color_dict): Add "path".
	* diagnostic-event-id.h: New file.
	* diagnostic-format-json.cc (json_from_expanded_location): Make
	non-static.
	(json_end_diagnostic): Call context->make_json_for_path if it
	exists and the diagnostic has a path.
	(diagnostic_output_format_init): Clear context->print_path.
	* diagnostic-path.h: New file.
	* diagnostic-show-locus.c (colorizer::set_range): Special-case
	when printing a run of events in a diagnostic_path so that they
	all get the same color.
	(layout::m_diagnostic_path_p): New field.
	(layout::layout): Initialize it.
	(layout::print_any_labels): Don't colorize the label text for an
	event in a diagnostic_path.
	(gcc_rich_location::add_location_if_nearby): Add
	"restrict_to_current_line_spans" and "label" params.  Pass the
	former to layout.maybe_add_location_range; pass the latter
	when calling add_range.
	* diagnostic.c: Include "diagnostic-path.h".
	(diagnostic_initialize): Initialize context->path_format and
	context->show_path_depths.
	(diagnostic_show_any_path): New function.
	(diagnostic_path::interprocedural_p): New function.
	(diagnostic_report_diagnostic): Call diagnostic_show_any_path.
	(simple_diagnostic_path::num_events): New function.
	(simple_diagnostic_path::get_event): New function.
	(simple_diagnostic_path::add_event): New function.
	(simple_diagnostic_event::simple_diagnostic_event): New ctor.
	(simple_diagnostic_event::~simple_diagnostic_event): New dtor.
	(debug): New overload taking a diagnostic_path *.
	* diagnostic.def (DK_DIAGNOSTIC_PATH): New.
	* diagnostic.h (enum diagnostic_path_format): New enum.
	(json::value): New forward decl.
	(diagnostic_context::path_format): New field.
	(diagnostic_context::show_path_depths): New field.
	(diagnostic_context::print_path): New callback field.
	(diagnostic_context::make_json_for_path): New callback field.
	(diagnostic_show_any_path): New decl.
	(json_from_expanded_location): New decl.
	* doc/invoke.texi (-fdiagnostics-path-format=): New option.
	(-fdiagnostics-show-path-depths): New option.
	(-fdiagnostics-color): Add "path" to description of default
	GCC_COLORS; describe it.
	(-fdiagnostics-format=json): Document how diagnostic paths are
	represented in the JSON output format.
	* gcc-rich-location.h (gcc_rich_location::add_location_if_nearby):
	Add optional params "restrict_to_current_line_spans" and "label".
	* opts.c (common_handle_option): Handle
	OPT_fdiagnostics_path_format_ and
	OPT_fdiagnostics_show_path_depths.
	* pretty-print.c: Include "diagnostic-event-id.h".
	(pp_format): Implement "%@" format code for printing
	diagnostic_event_id_t *.
	(selftest::test_pp_format): Add tests for "%@".
	* selftest-run-tests.c (selftest::run_tests): Call
	selftest::tree_diagnostic_path_cc_tests.
	* selftest.h (selftest::tree_diagnostic_path_cc_tests): New decl.
	* toplev.c (general_init): Initialize global_dc->path_format and
	global_dc->show_path_depths.
	* tree-diagnostic-path.cc: New file.
	* tree-diagnostic.c (maybe_unwind_expanded_macro_loc): Make
	non-static.  Drop "diagnostic" param in favor of storing the
	original value of "where" and re-using it.
	(virt_loc_aware_diagnostic_finalizer): Update for dropped param of
	maybe_unwind_expanded_macro_loc.
	(tree_diagnostics_defaults): Initialize context->print_path and
	context->make_json_for_path.
	* tree-diagnostic.h (default_tree_diagnostic_path_printer): New
	decl.
	(default_tree_make_json_for_path): New decl.
	(maybe_unwind_expanded_macro_loc): New decl.

gcc/c-family/ChangeLog:
	* c-format.c (local_event_ptr_node): New.
	(PP_FORMAT_CHAR_TABLE): Add entry for "%@".
	(init_dynamic_diag_info): Initialize local_event_ptr_node.
	* c-format.h (T_EVENT_PTR): New define.

gcc/testsuite/ChangeLog:
	* gcc.dg/format/gcc_diag-10.c (diagnostic_event_id_t): New
	typedef.
	(test_diag): Add coverage of "%@".
	* gcc.dg/plugin/diagnostic-path-format-default.c: New test.
	* gcc.dg/plugin/diagnostic-path-format-inline-events-1.c: New test.
	* gcc.dg/plugin/diagnostic-path-format-inline-events-2.c: New test.
	* gcc.dg/plugin/diagnostic-path-format-inline-events-3.c: New test.
	* gcc.dg/plugin/diagnostic-path-format-none.c: New test.
	* gcc.dg/plugin/diagnostic-test-paths-1.c: New test.
	* gcc.dg/plugin/diagnostic-test-paths-2.c: New test.
	* gcc.dg/plugin/diagnostic-test-paths-3.c: New test.
	* gcc.dg/plugin/diagnostic_plugin_test_paths.c: New.
	* gcc.dg/plugin/plugin.exp: Add the new plugin and test cases.

libcpp/ChangeLog:
	* include/line-map.h (class diagnostic_path): New forward decl.
	(rich_location::get_path): New accessor.
	(rich_location::set_path): New function.
	(rich_location::m_path): New field.
	* line-map.c (rich_location::rich_location): Initialize m_path.
---
 gcc/Makefile.in                                    |   1 +
 gcc/c-family/c-format.c                            |   7 +
 gcc/c-family/c-format.h                            |   1 +
 gcc/common.opt                                     |  20 +
 gcc/coretypes.h                                    |   1 +
 gcc/diagnostic-color.c                             |   3 +-
 gcc/diagnostic-event-id.h                          |  61 ++
 gcc/diagnostic-format-json.cc                      |  10 +-
 gcc/diagnostic-path.h                              | 149 ++++
 gcc/diagnostic-show-locus.c                        |  28 +-
 gcc/diagnostic.c                                   | 126 ++++
 gcc/diagnostic.def                                 |   5 +
 gcc/diagnostic.h                                   |  30 +
 gcc/doc/invoke.texi                                | 165 ++++-
 gcc/gcc-rich-location.h                            |   4 +-
 gcc/opts.c                                         |   8 +
 gcc/pretty-print.c                                 |  32 +
 gcc/selftest-run-tests.c                           |   1 +
 gcc/selftest.h                                     |   1 +
 gcc/testsuite/gcc.dg/format/gcc_diag-10.c          |   6 +-
 .../gcc.dg/plugin/diagnostic-path-format-default.c | 142 ++++
 .../diagnostic-path-format-inline-events-1.c       | 142 ++++
 .../diagnostic-path-format-inline-events-2.c       | 154 ++++
 .../diagnostic-path-format-inline-events-3.c       | 153 ++++
 .../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        |  56 ++
 .../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             |  10 +
 gcc/toplev.c                                       |   4 +
 gcc/tree-diagnostic-path.cc                        | 809 +++++++++++++++++++++
 gcc/tree-diagnostic.c                              |  12 +-
 gcc/tree-diagnostic.h                              |   8 +
 libcpp/include/line-map.h                          |   7 +
 libcpp/line-map.c                                  |   3 +-
 37 files changed, 2685 insertions(+), 16 deletions(-)
 create mode 100644 gcc/diagnostic-event-id.h
 create mode 100644 gcc/diagnostic-path.h
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-default.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-1.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-2.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-3.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-none.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-separate-events.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
 create mode 100644 gcc/tree-diagnostic-path.cc

Comments

Jeff Law Dec. 7, 2019, 2:45 p.m. UTC | #1
On Fri, 2019-11-15 at 20:22 -0500, David Malcolm wrote:
> This patch adds support for associating a "diagnostic_path" with a
> diagnostic: a sequence of events predicted by the compiler that leads
> to
> the problem occurring, with their locations in the user's source,
> text descriptions, and stack information (for handling
> interprocedural
> paths).
> 
> For example, the following (hypothetical) error has a 3-event
> intraprocedural path:
> 
> test.c: In function 'demo':
> test.c:29:5: error: passing NULL as argument 1 to 'PyList_Append'
> which
>   requires a non-NULL parameter
>    29 |     PyList_Append(list, item);
>       |     ^~~~~~~~~~~~~~~~~~~~~~~~~
>   'demo': 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 from
> (1) as argument 1
>      |
> 
> The patch adds a new "%@" format code for printing event IDs, so that
> in the above, the description of event (3) mentions event (1),
> showing
> the user where the bogus NULL value comes from (the event IDs are
> colorized to draw the user's attention to them).
> 
> There is a separation between data vs presentation: the above shows
> how
> the diagnostic-printing code has consolidated the path into a single
> run
> of events, since all the events are near each other and within the
> same
> function; more complicated examples (such as interprocedural paths)
> might be printed as multiple runs of events.
> 
> Examples of how interprocedural paths are printed can be seen in the
> test suite (which uses a plugin to exercise the code without relying
> on specific warnings using this functionality).
> 
> Other output formats include
> - JSON,
> - printing each event as a separate "note", and
> - to not emit paths.
> 
> (I have a separate script that can generate HTML from the JSON, but
> HTML
> is not my speciality; help from a web front-end expert to make it
> look
> good would be appreciated).
> 
> gcc/ChangeLog:
> 	* Makefile.in (OBJS): Add tree-diagnostic-path.o.
> 	* common.opt (fdiagnostics-path-format=): New option.
> 	(diagnostic_path_format): New enum.
> 	(fdiagnostics-show-path-depths): New option.
> 	* coretypes.h (diagnostic_event_id_t): New forward decl.
> 	* diagnostic-color.c (color_dict): Add "path".
> 	* diagnostic-event-id.h: New file.
> 	* diagnostic-format-json.cc (json_from_expanded_location): Make
> 	non-static.
> 	(json_end_diagnostic): Call context->make_json_for_path if it
> 	exists and the diagnostic has a path.
> 	(diagnostic_output_format_init): Clear context->print_path.
> 	* diagnostic-path.h: New file.
> 	* diagnostic-show-locus.c (colorizer::set_range): Special-case
> 	when printing a run of events in a diagnostic_path so that they
> 	all get the same color.
> 	(layout::m_diagnostic_path_p): New field.
> 	(layout::layout): Initialize it.
> 	(layout::print_any_labels): Don't colorize the label text for
> an
> 	event in a diagnostic_path.
> 	(gcc_rich_location::add_location_if_nearby): Add
> 	"restrict_to_current_line_spans" and "label" params.  Pass the
> 	former to layout.maybe_add_location_range; pass the latter
> 	when calling add_range.
> 	* diagnostic.c: Include "diagnostic-path.h".
> 	(diagnostic_initialize): Initialize context->path_format and
> 	context->show_path_depths.
> 	(diagnostic_show_any_path): New function.
> 	(diagnostic_path::interprocedural_p): New function.
> 	(diagnostic_report_diagnostic): Call diagnostic_show_any_path.
> 	(simple_diagnostic_path::num_events): New function.
> 	(simple_diagnostic_path::get_event): New function.
> 	(simple_diagnostic_path::add_event): New function.
> 	(simple_diagnostic_event::simple_diagnostic_event): New ctor.
> 	(simple_diagnostic_event::~simple_diagnostic_event): New dtor.
> 	(debug): New overload taking a diagnostic_path *.
> 	* diagnostic.def (DK_DIAGNOSTIC_PATH): New.
> 	* diagnostic.h (enum diagnostic_path_format): New enum.
> 	(json::value): New forward decl.
> 	(diagnostic_context::path_format): New field.
> 	(diagnostic_context::show_path_depths): New field.
> 	(diagnostic_context::print_path): New callback field.
> 	(diagnostic_context::make_json_for_path): New callback field.
> 	(diagnostic_show_any_path): New decl.
> 	(json_from_expanded_location): New decl.
> 	* doc/invoke.texi (-fdiagnostics-path-format=): New option.
> 	(-fdiagnostics-show-path-depths): New option.
> 	(-fdiagnostics-color): Add "path" to description of default
> 	GCC_COLORS; describe it.
> 	(-fdiagnostics-format=json): Document how diagnostic paths are
> 	represented in the JSON output format.
> 	* gcc-rich-location.h
> (gcc_rich_location::add_location_if_nearby):
> 	Add optional params "restrict_to_current_line_spans" and
> "label".
> 	* opts.c (common_handle_option): Handle
> 	OPT_fdiagnostics_path_format_ and
> 	OPT_fdiagnostics_show_path_depths.
> 	* pretty-print.c: Include "diagnostic-event-id.h".
> 	(pp_format): Implement "%@" format code for printing
> 	diagnostic_event_id_t *.
> 	(selftest::test_pp_format): Add tests for "%@".
> 	* selftest-run-tests.c (selftest::run_tests): Call
> 	selftest::tree_diagnostic_path_cc_tests.
> 	* selftest.h (selftest::tree_diagnostic_path_cc_tests): New
> decl.
> 	* toplev.c (general_init): Initialize global_dc->path_format
> and
> 	global_dc->show_path_depths.
> 	* tree-diagnostic-path.cc: New file.
> 	* tree-diagnostic.c (maybe_unwind_expanded_macro_loc): Make
> 	non-static.  Drop "diagnostic" param in favor of storing the
> 	original value of "where" and re-using it.
> 	(virt_loc_aware_diagnostic_finalizer): Update for dropped param
> of
> 	maybe_unwind_expanded_macro_loc.
> 	(tree_diagnostics_defaults): Initialize context->print_path and
> 	context->make_json_for_path.
> 	* tree-diagnostic.h (default_tree_diagnostic_path_printer): New
> 	decl.
> 	(default_tree_make_json_for_path): New decl.
> 	(maybe_unwind_expanded_macro_loc): New decl.
> 
> gcc/c-family/ChangeLog:
> 	* c-format.c (local_event_ptr_node): New.
> 	(PP_FORMAT_CHAR_TABLE): Add entry for "%@".
> 	(init_dynamic_diag_info): Initialize local_event_ptr_node.
> 	* c-format.h (T_EVENT_PTR): New define.
> 
> gcc/testsuite/ChangeLog:
> 	* gcc.dg/format/gcc_diag-10.c (diagnostic_event_id_t): New
> 	typedef.
> 	(test_diag): Add coverage of "%@".
> 	* gcc.dg/plugin/diagnostic-path-format-default.c: New test.
> 	* gcc.dg/plugin/diagnostic-path-format-inline-events-1.c: New
> test.
> 	* gcc.dg/plugin/diagnostic-path-format-inline-events-2.c: New
> test.
> 	* gcc.dg/plugin/diagnostic-path-format-inline-events-3.c: New
> test.
> 	* gcc.dg/plugin/diagnostic-path-format-none.c: New test.
> 	* gcc.dg/plugin/diagnostic-test-paths-1.c: New test.
> 	* gcc.dg/plugin/diagnostic-test-paths-2.c: New test.
> 	* gcc.dg/plugin/diagnostic-test-paths-3.c: New test.
> 	* gcc.dg/plugin/diagnostic_plugin_test_paths.c: New.
> 	* gcc.dg/plugin/plugin.exp: Add the new plugin and test cases.
> 
> libcpp/ChangeLog:
> 	* include/line-map.h (class diagnostic_path): New forward decl.
> 	(rich_location::get_path): New accessor.
> 	(rich_location::set_path): New function.
> 	(rich_location::m_path): New field.
> 	* line-map.c (rich_location::rich_location): Initialize m_path.
Seems like we could make an argument to include this in an expanded
"diagnostic framework" maintainership which would allow self-approval.

jeff

Patch
diff mbox series

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 53d74e2..c87b00f 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1521,6 +1521,7 @@  OBJS = \
 	tree-data-ref.o \
 	tree-dfa.o \
 	tree-diagnostic.o \
+	tree-diagnostic-path.o \
 	tree-dump.o \
 	tree-eh.o \
 	tree-emutls.o \
diff --git a/gcc/c-family/c-format.c b/gcc/c-family/c-format.c
index 84475db..0e62d89 100644
--- a/gcc/c-family/c-format.c
+++ b/gcc/c-family/c-format.c
@@ -65,6 +65,7 @@  struct function_format_info
 
 /* Initialized in init_dynamic_diag_info.  */
 static GTY(()) tree local_tree_type_node;
+static GTY(()) tree local_event_ptr_node;
 static GTY(()) tree local_gimple_ptr_node;
 static GTY(()) tree local_cgraph_node_ptr_node;
 static GTY(()) tree locus;
@@ -752,6 +753,7 @@  static const format_char_info asm_fprintf_char_table[] =
   { "s",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "pq", "cR", NULL }, \
   { "p",   1, STD_C89, { T89_V,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q",  "c",  NULL }, \
   { "r",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "",    "//cR",   NULL }, \
+  { "@",   1, STD_C89, { T_EVENT_PTR,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "", "\"",   NULL }, \
   { "<",   0, STD_C89, NOARGUMENTS, "",      "<",   NULL }, \
   { ">",   0, STD_C89, NOARGUMENTS, "",      ">",   NULL }, \
   { "'" ,  0, STD_C89, NOARGUMENTS, "",      "",    NULL }, \
@@ -4986,6 +4988,11 @@  init_dynamic_diag_info (void)
       || local_cgraph_node_ptr_node == void_type_node)
     local_cgraph_node_ptr_node = get_pointer_to_named_type ("cgraph_node");
 
+  /* Similar to the above but for diagnostic_event_id_t*.  */
+  if (!local_event_ptr_node
+      || local_event_ptr_node == void_type_node)
+    local_event_ptr_node = get_pointer_to_named_type ("diagnostic_event_id_t");
+
   static tree hwi;
 
   if (!hwi)
diff --git a/gcc/c-family/c-format.h b/gcc/c-family/c-format.h
index 15a3153..91b1ee0 100644
--- a/gcc/c-family/c-format.h
+++ b/gcc/c-family/c-format.h
@@ -303,6 +303,7 @@  struct format_kind_info
 #define T_V	&void_type_node
 #define T89_G   { STD_C89, NULL, &local_gimple_ptr_node }
 #define T_CGRAPH_NODE   { STD_C89, NULL, &local_cgraph_node_ptr_node }
+#define T_EVENT_PTR    { STD_C89, NULL, &local_event_ptr_node }
 #define T89_T   { STD_C89, NULL, &local_tree_type_node }
 #define T89_V	{ STD_C89, NULL, T_V }
 #define T_W	&wchar_type_node
diff --git a/gcc/common.opt b/gcc/common.opt
index 228df32..adc9931 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1338,6 +1338,26 @@  fdiagnostics-show-metadata
 Common Var(flag_diagnostics_show_metadata) Init(1)
 Print additional metadata for diagnostic messages, where available.
 
+fdiagnostics-path-format=
+Common Joined RejectNegative Var(flag_diagnostics_path_format) Enum(diagnostic_path_format) Init(DPF_INLINE_EVENTS)
+Specify how to print any control-flow path associated with a diagnostic.
+
+Enum
+Name(diagnostic_path_format) Type(int)
+
+EnumValue
+Enum(diagnostic_path_format) String(none) Value(DPF_NONE)
+
+EnumValue
+Enum(diagnostic_path_format) String(separate-events) Value(DPF_SEPARATE_EVENTS)
+
+EnumValue
+Enum(diagnostic_path_format) String(inline-events) Value(DPF_INLINE_EVENTS)
+
+fdiagnostics-show-path-depths
+Common Var(flag_diagnostics_show_path_depths) Init(0)
+Show stack depths of events in paths.
+
 fdiagnostics-minimum-margin-width=
 Common Joined UInteger Var(diagnostics_minimum_margin_width) Init(6)
 Set minimum width of left margin of source code when showing source.
diff --git a/gcc/coretypes.h b/gcc/coretypes.h
index b683f12..1c03cc0 100644
--- a/gcc/coretypes.h
+++ b/gcc/coretypes.h
@@ -153,6 +153,7 @@  struct cl_decoded_option;
 struct cl_option_handlers;
 struct diagnostic_context;
 class pretty_printer;
+class diagnostic_event_id_t;
 
 template<typename T> struct array_traits;
 
diff --git a/gcc/diagnostic-color.c b/gcc/diagnostic-color.c
index abc919f..d32cff8 100644
--- a/gcc/diagnostic-color.c
+++ b/gcc/diagnostic-color.c
@@ -90,6 +90,7 @@  static struct color_cap color_dict[] =
   { "range2", SGR_SEQ (COLOR_FG_BLUE), 6, false },
   { "locus", SGR_SEQ (COLOR_BOLD), 5, false },
   { "quote", SGR_SEQ (COLOR_BOLD), 5, false },
+  { "path", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_CYAN), 4, false },
   { "fixit-insert", SGR_SEQ (COLOR_FG_GREEN), 12, false },
   { "fixit-delete", SGR_SEQ (COLOR_FG_RED), 12, false },
   { "diff-filename", SGR_SEQ (COLOR_BOLD), 13, false },
@@ -126,7 +127,7 @@  colorize_stop (bool show_color)
 
 /* Parse GCC_COLORS.  The default would look like:
    GCC_COLORS='error=01;31:warning=01;35:note=01;36:\
-   range1=32:range2=34:locus=01:quote=01:\
+   range1=32:range2=34:locus=01:quote=01:path=01;36:\
    fixit-insert=32:fixit-delete=31:'\
    diff-filename=01:diff-hunk=32:diff-delete=31:diff-insert=32:\
    type-diff=01;32'
diff --git a/gcc/diagnostic-event-id.h b/gcc/diagnostic-event-id.h
new file mode 100644
index 0000000..7f0f4e4
--- /dev/null
+++ b/gcc/diagnostic-event-id.h
@@ -0,0 +1,61 @@ 
+/* A class for referring to events within a diagnostic_path.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_DIAGNOSTIC_EVENT_ID_H
+#define GCC_DIAGNOSTIC_EVENT_ID_H
+
+/* A class for referring to events within a diagnostic_path.
+
+   They are stored as 0-based offsets into the events, but
+   printed (e.g. via %@) as 1-based numbers.
+
+   For example, a 3-event path has event offsets 0, 1, and 2,
+   which would be shown to the user as "(1)", "(2)" and "(3)".
+
+   This has its own header so that pretty-print.c can use this
+   to implement "%@" without bringing in all of diagnostic_path
+   (which e.g. refers to "tree").  */
+
+class diagnostic_event_id_t
+{
+ public:
+  diagnostic_event_id_t () : m_index (UNKNOWN_EVENT_IDX) {}
+  diagnostic_event_id_t (int zero_based_idx) : m_index (zero_based_idx) {}
+
+  bool known_p () const { return m_index != UNKNOWN_EVENT_IDX; }
+
+  int one_based () const
+  {
+    gcc_assert (known_p ());
+    return m_index + 1;
+  }
+
+ private:
+  static const int UNKNOWN_EVENT_IDX = -1;
+  int m_index; // zero-based
+};
+
+/* A pointer to a diagnostic_event_id_t, for use with the "%@" format
+   code, which will print a 1-based representation for it, with suitable
+   colorization, e.g. "(1)".
+   The %@ format code requires that known_p be true for the event ID. */
+typedef diagnostic_event_id_t *diagnostic_event_id_ptr;
+
+#endif /* ! GCC_DIAGNOSTIC_EVENT_ID_H */
diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc
index 200e844..c9beef9 100644
--- a/gcc/diagnostic-format-json.cc
+++ b/gcc/diagnostic-format-json.cc
@@ -42,7 +42,7 @@  static json::array *cur_children_array;
 
 /* Generate a JSON object for LOC.  */
 
-static json::object *
+json::value *
 json_from_expanded_location (location_t loc)
 {
   expanded_location exploc = expand_location (loc);
@@ -231,6 +231,13 @@  json_end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic,
       json::object *metadata_obj = json_from_metadata (diagnostic->metadata);
       diag_obj->set ("metadata", metadata_obj);
     }
+
+  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);
+    }
 }
 
 /* No-op implementation of "begin_group_cb" for JSON output.  */
@@ -287,6 +294,7 @@  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;
diff --git a/gcc/diagnostic-path.h b/gcc/diagnostic-path.h
new file mode 100644
index 0000000..de7d133
--- /dev/null
+++ b/gcc/diagnostic-path.h
@@ -0,0 +1,149 @@ 
+/* Paths through the code associated with a diagnostic.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_DIAGNOSTIC_PATH_H
+#define GCC_DIAGNOSTIC_PATH_H
+
+#include "diagnostic.h" /* for ATTRIBUTE_GCC_DIAG.  */
+#include "diagnostic-event-id.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 'demo':
+     test.c:29:5: error: passing NULL as argument 1 to 'PyList_Append' which
+       requires a non-NULL parameter
+        29 |     PyList_Append(list, item);
+           |     ^~~~~~~~~~~~~~~~~~~~~~~~~
+       'demo': 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 from (1) as argument 1
+          |
+
+    The diagnostic-printing code has consolidated the path into a single
+    run of events, since all the events are near each other and within the same
+    function; more complicated examples (such as interprocedural paths)
+    might be printed as multiple runs of events.  */
+
+/* Abstract base classes, describing events within a path, and the paths
+   themselves.  */
+
+/* One event within a diagnostic_path.  */
+
+class diagnostic_event
+{
+ public:
+  virtual ~diagnostic_event () {}
+
+  virtual location_t get_location () const = 0;
+
+  virtual tree get_fndecl () const = 0;
+
+  /* Stack depth, so that consumers can visualizes the interprocedural
+     calls, returns, and frame nesting.  */
+  virtual int get_stack_depth () const = 0;
+
+  /* Get a localized (and possibly colorized) description of this event.  */
+  virtual label_text get_desc (bool can_colorize) const = 0;
+};
+
+/* Abstract base class for getting at a sequence of events.  */
+
+class diagnostic_path
+{
+ public:
+  virtual ~diagnostic_path () {}
+  virtual unsigned num_events () const = 0;
+  virtual const diagnostic_event & get_event (int idx) const = 0;
+
+  bool interprocedural_p () const;
+};
+
+/* Concrete subclasses.  */
+
+/* A simple implementation of diagnostic_event.  */
+
+class simple_diagnostic_event : public diagnostic_event
+{
+ public:
+  simple_diagnostic_event (location_t loc, tree fndecl, int depth,
+			   const char *desc);
+  ~simple_diagnostic_event ();
+
+  location_t get_location () const FINAL OVERRIDE { return m_loc; }
+  tree get_fndecl () const FINAL OVERRIDE { return m_fndecl; }
+  int get_stack_depth () const FINAL OVERRIDE { return m_depth; }
+  label_text get_desc (bool) const FINAL OVERRIDE
+  {
+    return label_text::borrow (m_desc);
+  }
+
+ private:
+  location_t m_loc;
+  tree m_fndecl;
+  int m_depth;
+  char *m_desc; // has been i18n-ed and formatted
+};
+
+/* A simple implementation of diagnostic_path, as a vector of
+   simple_diagnostic_event instances.  */
+
+class simple_diagnostic_path : public diagnostic_path
+{
+ public:
+  simple_diagnostic_path (pretty_printer *event_pp)
+  : m_event_pp (event_pp) {}
+
+  unsigned num_events () const FINAL OVERRIDE;
+  const diagnostic_event & get_event (int idx) const FINAL OVERRIDE;
+
+  diagnostic_event_id_t add_event (location_t loc, tree fndecl, int depth,
+				   const char *fmt, ...)
+    ATTRIBUTE_GCC_DIAG(5,6);
+
+ private:
+  auto_delete_vec<simple_diagnostic_event> m_events;
+
+  /* (for use by add_event).  */
+  pretty_printer *m_event_pp;
+};
+
+extern void debug (diagnostic_path *path);
+
+#endif /* ! GCC_DIAGNOSTIC_PATH_H */
diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
index ec5fdc0..3cd385c 100644
--- a/gcc/diagnostic-show-locus.c
+++ b/gcc/diagnostic-show-locus.c
@@ -86,7 +86,17 @@  class colorizer
 	     diagnostic_t diagnostic_kind);
   ~colorizer ();
 
-  void set_range (int range_idx) { set_state (range_idx); }
+  void set_range (int range_idx)
+  {
+    /* Normally we emphasize the primary location, then alternate between
+       two colors for the secondary locations.
+       But if we're printing a run of events in a diagnostic path, that
+       makes no sense, so print all of them with the same colorization.  */
+    if (m_diagnostic_kind == DK_DIAGNOSTIC_PATH)
+      set_state (0);
+    else
+      set_state (range_idx);
+  }
   void set_normal_text () { set_state (STATE_NORMAL_TEXT); }
   void set_fixit_insert () { set_state (STATE_FIXIT_INSERT); }
   void set_fixit_delete () { set_state (STATE_FIXIT_DELETE); }
@@ -304,6 +314,7 @@  class layout
   bool m_show_labels_p;
   bool m_show_line_numbers_p;
   bool m_use_nn_for_line_numbers_p;
+  bool m_diagnostic_path_p;
   auto_vec <layout_range> m_layout_ranges;
   auto_vec <const fixit_hint *> m_fixit_hints;
   auto_vec <line_span> m_line_spans;
@@ -851,6 +862,7 @@  layout::layout (diagnostic_context * context,
   m_show_labels_p (context->show_labels_p),
   m_show_line_numbers_p (context->show_line_numbers_p),
   m_use_nn_for_line_numbers_p (context->use_nn_for_line_numbers_p),
+  m_diagnostic_path_p (diagnostic_kind == DK_DIAGNOSTIC_PATH),
   m_layout_ranges (richloc->get_num_locations ()),
   m_fixit_hints (richloc->get_num_fixit_hints ()),
   m_line_spans (1 + richloc->get_num_locations ()),
@@ -1571,7 +1583,10 @@  layout::print_any_labels (linenum_type row)
 	      {
 		gcc_assert (column <= label->m_column);
 		move_to_column (&column, label->m_column, true);
-		m_colorizer.set_range (label->m_state_idx);
+		/* Colorize the text, unless it's for events in a
+		   diagnostic_path.  */
+		if (!m_diagnostic_path_p)
+		  m_colorizer.set_range (label->m_state_idx);
 		pp_string (m_pp, label->m_text.m_buffer);
 		m_colorizer.set_normal_text ();
 		column += label->m_length;
@@ -2264,7 +2279,9 @@  layout::print_line (linenum_type row)
    Otherwise return false.  */
 
 bool
-gcc_rich_location::add_location_if_nearby (location_t loc)
+gcc_rich_location::add_location_if_nearby (location_t loc,
+					   bool restrict_to_current_line_spans,
+					   const range_label *label)
 {
   /* Use the layout location-handling logic to sanitize LOC,
      filtering it to the current line spans within a temporary
@@ -2273,10 +2290,11 @@  gcc_rich_location::add_location_if_nearby (location_t loc)
   location_range loc_range;
   loc_range.m_loc = loc;
   loc_range.m_range_display_kind = SHOW_RANGE_WITHOUT_CARET;
-  if (!layout.maybe_add_location_range (&loc_range, 0, true))
+  if (!layout.maybe_add_location_range (&loc_range, 0,
+					restrict_to_current_line_spans))
     return false;
 
-  add_range (loc);
+  add_range (loc, SHOW_RANGE_WITHOUT_CARET, label);
   return true;
 }
 
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index e8d0613..a645946 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -33,6 +33,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-color.h"
 #include "diagnostic-url.h"
 #include "diagnostic-metadata.h"
+#include "diagnostic-path.h"
 #include "edit-context.h"
 #include "selftest.h"
 #include "selftest-diagnostic.h"
@@ -187,6 +188,8 @@  diagnostic_initialize (diagnostic_context *context, int n_opts)
   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;
@@ -659,6 +662,38 @@  diagnostic_report_current_module (diagnostic_context *context, location_t where)
     }
 }
 
+/* If DIAGNOSTIC has a diagnostic_path and CONTEXT supports printing paths,
+   print the path.  */
+
+void
+diagnostic_show_any_path (diagnostic_context *context,
+			  diagnostic_info *diagnostic)
+{
+  const diagnostic_path *path = diagnostic->richloc->get_path ();
+  if (!path)
+    return;
+
+  if (context->print_path)
+    context->print_path (context, path);
+}
+
+/* Return true if the events in this path involve more than one
+   function, or false if it is purely intraprocedural.  */
+
+bool
+diagnostic_path::interprocedural_p () const
+{
+  const unsigned num = num_events ();
+  for (unsigned i = 0; i < num; i++)
+    {
+      if (get_event (i).get_fndecl () != get_event (0).get_fndecl ())
+	return true;
+      if (get_event (i).get_stack_depth () != get_event (0).get_stack_depth ())
+	return true;
+    }
+  return false;
+}
+
 void
 default_diagnostic_starter (diagnostic_context *context,
 			    diagnostic_info *diagnostic)
@@ -1124,6 +1159,8 @@  diagnostic_report_diagnostic (diagnostic_context *context,
 
   context->lock--;
 
+  diagnostic_show_any_path (context, diagnostic);
+
   return true;
 }
 
@@ -1765,6 +1802,95 @@  auto_diagnostic_group::~auto_diagnostic_group ()
     }
 }
 
+/* Implementation of diagnostic_path::num_events vfunc for
+   simple_diagnostic_path: simply get the number of events in the vec.  */
+
+unsigned
+simple_diagnostic_path::num_events () const
+{
+  return m_events.length ();
+}
+
+/* Implementation of diagnostic_path::get_event vfunc for
+   simple_diagnostic_path: simply return the event in the vec.  */
+
+const diagnostic_event &
+simple_diagnostic_path::get_event (int idx) const
+{
+  return *m_events[idx];
+}
+
+/* Add an event to this path at LOC within function FNDECL at
+   stack depth DEPTH.
+
+   Use m_context's printer to format FMT, as the text of the new
+   event.
+
+   Return the id of the new event.  */
+
+diagnostic_event_id_t
+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);
+
+  return diagnostic_event_id_t (m_events.length () - 1);
+}
+
+/* struct simple_diagnostic_event.  */
+
+/* simple_diagnostic_event's ctor.  */
+
+simple_diagnostic_event::simple_diagnostic_event (location_t loc,
+						  tree fndecl,
+						  int depth,
+						  const char *desc)
+: m_loc (loc), m_fndecl (fndecl), m_depth (depth), m_desc (xstrdup (desc))
+{
+}
+
+/* simple_diagnostic_event's dtor.  */
+
+simple_diagnostic_event::~simple_diagnostic_event ()
+{
+  free (m_desc);
+}
+
+/* Print PATH by emitting a dummy "note" associated with it.  */
+
+DEBUG_FUNCTION
+void debug (diagnostic_path *path)
+{
+  rich_location richloc (line_table, UNKNOWN_LOCATION);
+  richloc.set_path (path);
+  inform (&richloc, "debug path");
+}
+
 /* Really call the system 'abort'.  This has to go right at the end of
    this file, so that there are no functions after it that call abort
    and get the system abort instead of our macro.  */
diff --git a/gcc/diagnostic.def b/gcc/diagnostic.def
index f3c0b2c..b271ea4 100644
--- a/gcc/diagnostic.def
+++ b/gcc/diagnostic.def
@@ -38,6 +38,11 @@  DEFINE_DIAGNOSTIC_KIND (DK_WARNING, "warning: ", "warning")
 DEFINE_DIAGNOSTIC_KIND (DK_ANACHRONISM, "anachronism: ", "warning")
 DEFINE_DIAGNOSTIC_KIND (DK_NOTE, "note: ", "note")
 DEFINE_DIAGNOSTIC_KIND (DK_DEBUG, "debug: ", "note")
+
+/* For use when using the diagnostic_show_locus machinery to show
+   a range of events within a path.  */
+DEFINE_DIAGNOSTIC_KIND (DK_DIAGNOSTIC_PATH, "path: ", "path")
+
 /* These two would be re-classified as DK_WARNING or DK_ERROR, so the
 prefix does not matter.  */
 DEFINE_DIAGNOSTIC_KIND (DK_PEDWARN, "pedwarn: ", NULL)
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 0e4b6ad..fcf981e 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -35,6 +35,23 @@  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 printing such runs of events with multiple
+     calls to diagnostic_show_locus, showing the individual events in
+     each run via labels in the source.  */
+  DPF_INLINE_EVENTS
+};
+
 /* 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.  */
@@ -80,6 +97,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.  */
@@ -133,6 +151,12 @@  struct diagnostic_context
   /* True if we should print any metadata associated with diagnostics.  */
   bool show_metadata;
 
+  /* How should diagnostic_path objects be printed.  */
+  enum diagnostic_path_format path_format;
+
+  /* True if we should print stack depths when printing diagnostic paths.  */
+  bool show_path_depths;
+
   /* True if we should print the command line option which controls
      each diagnostic, if known.  */
   bool show_option_requested;
@@ -207,6 +231,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;
 
@@ -354,6 +381,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 *,
@@ -445,4 +473,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, bool use_nn_p = false);
 
+extern json::value *json_from_expanded_location (location_t loc);
+
 #endif /* ! GCC_DIAGNOSTIC_H */
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 209e2c6..08d9870 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -283,6 +283,8 @@  Objective-C and Objective-C++ Dialects}.
 -fdiagnostics-minimum-margin-width=@var{width} @gol
 -fdiagnostics-parseable-fixits  -fdiagnostics-generate-patch @gol
 -fdiagnostics-show-template-tree  -fno-elide-type @gol
+-fdiagnostics-path-format=@r{[}none@r{|}separate-events@r{|}inline-events@r{]} @gol
+-fdiagnostics-show-path-depths @gol
 -fno-show-column}
 
 @item Warning Options
@@ -3893,7 +3895,7 @@  for 88-color and 256-color modes background colors.
 The default @env{GCC_COLORS} is
 @smallexample
 error=01;31:warning=01;35:note=01;36:range1=32:range2=34:locus=01:\
-quote=01:fixit-insert=32:fixit-delete=31:\
+quote=01:path=01;36:fixit-insert=32:fixit-delete=31:\
 diff-filename=01:diff-hunk=32:diff-delete=31:diff-insert=32:\
 type-diff=01;32
 @end smallexample
@@ -3917,6 +3919,12 @@  SGR substring for warning: markers.
 @vindex note GCC_COLORS @r{capability}
 SGR substring for note: markers.
 
+@item path=
+@vindex path GCC_COLORS @r{capability}
+SGR substring for colorizing paths of control-flow events as printed
+via @option{-fdiagnostics-path-format=}, such as the identifiers of
+individual events and lines indicating interprocedural calls and returns.
+
 @item range1=
 @vindex range1 GCC_COLORS @r{capability}
 SGR substring for first additional range.
@@ -4131,6 +4139,114 @@  Specifying the @option{-fno-elide-type} flag suppresses that behavior.
 This flag also affects the output of the
 @option{-fdiagnostics-show-template-tree} flag.
 
+@item -fdiagnostics-path-format=@var{KIND}
+@opindex fdiagnostics-path-format
+Specify how to print paths of control-flow events for diagnostics that
+have such a path associated with them.
+
+@var{KIND} is @samp{none}, @samp{separate-events}, or @samp{inline-events},
+the default.
+
+@samp{none} means to not print diagnostic paths.
+
+@samp{separate-events} means to print a separate ``note'' diagnostic for
+each event within the diagnostic.  For example:
+
+@smallexample
+test.c:29:5: error: passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter
+test.c:25:10: note: (1) when 'PyList_New' fails, returning NULL
+test.c:27:3: note: (2) when 'i < count'
+test.c:29:5: note: (3) when calling 'PyList_Append', passing NULL from (1) as argument 1
+@end smallexample
+
+@samp{inline-events} means to print the events ``inline'' within the source
+code.  This view attempts to consolidate the events into runs of
+sufficiently-close events, printing them as labelled ranges within the source.
+
+For example, the same events as above might be printed as:
+
+@smallexample
+  'test': events 1-3
+    |
+    |   25 |   list = PyList_New(0);
+    |      |          ^~~~~~~~~~~~~
+    |      |          |
+    |      |          (1) when 'PyList_New' fails, returning NULL
+    |   26 |
+    |   27 |   for (i = 0; i < count; i++) @{
+    |      |   ~~~
+    |      |   |
+    |      |   (2) when 'i < count'
+    |   28 |     item = PyLong_FromLong(random());
+    |   29 |     PyList_Append(list, item);
+    |      |     ~~~~~~~~~~~~~~~~~~~~~~~~~
+    |      |     |
+    |      |     (3) when calling 'PyList_Append', passing NULL from (1) as argument 1
+    |
+@end smallexample
+
+Interprocedural control flow is shown by grouping the events by stack frame,
+and using indentation to show how stack frames are nested, pushed, and popped.
+
+For example:
+
+@smallexample
+  'test': events 1-2
+    |
+    |  133 | @{
+    |      | ^
+    |      | |
+    |      | (1) entering 'test'
+    |  134 |   boxed_int *obj = make_boxed_int (i);
+    |      |                    ~~~~~~~~~~~~~~~~~~
+    |      |                    |
+    |      |                    (2) calling 'make_boxed_int'
+    |
+    +--> 'make_boxed_int': events 3-4
+           |
+           |  120 | @{
+           |      | ^
+           |      | |
+           |      | (3) entering 'make_boxed_int'
+           |  121 |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+           |      |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+           |      |                                    |
+           |      |                                    (4) calling 'wrapped_malloc'
+           |
+           +--> 'wrapped_malloc': events 5-6
+                  |
+                  |    7 | @{
+                  |      | ^
+                  |      | |
+                  |      | (5) entering 'wrapped_malloc'
+                  |    8 |   return malloc (size);
+                  |      |          ~~~~~~~~~~~~~
+                  |      |          |
+                  |      |          (6) calling 'malloc'
+                  |
+    <-------------+
+    |
+ 'test': event 7
+    |
+    |  138 |   free_boxed_int (obj);
+    |      |   ^~~~~~~~~~~~~~~~~~~~
+    |      |   |
+    |      |   (7) calling 'free_boxed_int'
+    |
+(etc)
+@end smallexample
+
+@item -fdiagnostics-show-path-depths
+@opindex fdiagnostics-show-path-depths
+This option provides additional information when printing control-flow paths
+associated with a diagnostic.
+
+If this is option is provided then the stack depth will be printed for
+each run of events within @option{-fdiagnostics-path-format=separate-events}.
+
+This is intended for use by GCC developers and plugin developers when
+debugging diagnostics that report interprocedural control flow.
+
 @item -fno-show-column
 @opindex fno-show-column
 @opindex fshow-column
@@ -4326,6 +4442,53 @@  to but not including @code{next} with @code{string}'s value.  Deletions
 are expressed via an empty value for @code{string}, insertions by
 having @code{start} equal @code{next}.
 
+If the diagnostic has a path of control-flow events associated with it,
+it has a @code{path} array of objects representing the events.  Each
+event object has a @code{description} string, a @code{location} object,
+along with a @code{function} string and a @code{depth} number for
+representing interprocedural paths.  The @code{function} represents the
+current function at that event, and the @code{depth} represents the
+stack depth relative to some baseline: the higher, the more frames are
+within the stack.
+
+For example, the intraprocedural example shown for
+@option{-fdiagnostics-path-format=} might have this JSON for its path:
+
+@smallexample
+    "path": [
+        @{
+            "depth": 0,
+            "description": "when 'PyList_New' fails, returning NULL",
+            "function": "test",
+            "location": @{
+                "column": 10,
+                "file": "test.c",
+                "line": 25
+            @}
+        @},
+        @{
+            "depth": 0,
+            "description": "when 'i < count'",
+            "function": "test",
+            "location": @{
+                "column": 3,
+                "file": "test.c",
+                "line": 27
+            @}
+        @},
+        @{
+            "depth": 0,
+            "description": "when calling 'PyList_Append', passing NULL from (1) as argument 1",
+            "function": "test",
+            "location": @{
+                "column": 5,
+                "file": "test.c",
+                "line": 29
+            @}
+        @}
+    ]
+@end smallexample
+
 @end table
 
 @node Warning Options
diff --git a/gcc/gcc-rich-location.h b/gcc/gcc-rich-location.h
index 71f4f3d..4de697a 100644
--- a/gcc/gcc-rich-location.h
+++ b/gcc/gcc-rich-location.h
@@ -62,7 +62,9 @@  class gcc_rich_location : public rich_location
 
      Implemented in diagnostic-show-locus.c.  */
 
-  bool add_location_if_nearby (location_t loc);
+  bool add_location_if_nearby (location_t loc,
+			       bool restrict_to_current_line_spans = true,
+			       const range_label *label = NULL);
 
   /* Add a fix-it hint suggesting the insertion of CONTENT before
      INSERTION_POINT.
diff --git a/gcc/opts.c b/gcc/opts.c
index 0caaf04..b3436b2 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -2495,6 +2495,14 @@  common_handle_option (struct gcc_options *opts,
       dc->show_metadata = value;
       break;
 
+    case OPT_fdiagnostics_path_format_:
+      dc->path_format = (enum diagnostic_path_format)value;
+      break;
+
+    case OPT_fdiagnostics_show_path_depths:
+      dc->show_path_depths = value;
+      break;
+
     case OPT_fdiagnostics_show_option:
       dc->show_option_requested = value;
       break;
diff --git a/gcc/pretty-print.c b/gcc/pretty-print.c
index 89242ed..d5aa451 100644
--- a/gcc/pretty-print.c
+++ b/gcc/pretty-print.c
@@ -24,6 +24,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "pretty-print.h"
 #include "diagnostic-color.h"
+#include "diagnostic-event-id.h"
 #include "selftest.h"
 
 #if HAVE_ICONV
@@ -989,6 +990,7 @@  pp_indent (pretty_printer *pp)
    %>: closing quote.
    %': apostrophe (should only be used in untranslated messages;
        translations should use appropriate punctuation directly).
+   %@: diagnostic_event_id_ptr, for which event_id->known_p () must be true.
    %.*s: a substring the length of which is specified by an argument
 	 integer.
    %Ns: likewise, but length specified as constant in the format string.
@@ -1378,6 +1380,21 @@  pp_format (pretty_printer *pp, text_info *text)
 	  }
 	  break;
 
+	case '@':
+	  {
+	    /* diagnostic_event_id_t *.  */
+	    diagnostic_event_id_ptr event_id
+	      = va_arg (*text->args_ptr, diagnostic_event_id_ptr);
+	    gcc_assert (event_id->known_p ());
+
+	    pp_string (pp, colorize_start (pp_show_color (pp), "path"));
+	    pp_character (pp, '(');
+	    pp_decimal_int (pp, event_id->one_based ());
+	    pp_character (pp, ')');
+	    pp_string (pp, colorize_stop (pp_show_color (pp)));
+	  }
+	  break;
+
 	default:
 	  {
 	    bool ok;
@@ -2269,6 +2286,21 @@  test_pp_format ()
   assert_pp_format_colored (SELFTEST_LOCATION,
 			    "`\33[01m\33[Kfoo\33[m\33[K' 12345678", "%qs %x",
 			    "foo", 0x12345678);
+  /* Verify "%@".  */
+  {
+    diagnostic_event_id_t first (2);
+    diagnostic_event_id_t second (7);
+
+    ASSERT_PP_FORMAT_2 ("first `free' at (3); second `free' at (8)",
+			"first %<free%> at %@; second %<free%> at %@",
+			&first, &second);
+    assert_pp_format_colored
+      (SELFTEST_LOCATION,
+       "first `free' at (3);"
+       " second `free' at (8)",
+       "first %<free%> at %@; second %<free%> at %@",
+       &first, &second);
+  }
 
   /* Verify %Z.  */
   int v[] = { 1, 2, 3 }; 
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index 6ed7d82..92c8de7 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_path_cc_tests ();
   attribute_c_tests ();
 
   /* This one relies on most of the above.  */
diff --git a/gcc/selftest.h b/gcc/selftest.h
index 6f2c2af..165dfff 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -248,6 +248,7 @@  extern void sreal_c_tests ();
 extern void store_merging_c_tests ();
 extern void tree_c_tests ();
 extern void tree_cfg_c_tests ();
+extern void tree_diagnostic_path_cc_tests ();
 extern void typed_splay_tree_c_tests ();
 extern void unique_ptr_tests_cc_tests ();
 extern void vec_c_tests ();
diff --git a/gcc/testsuite/gcc.dg/format/gcc_diag-10.c b/gcc/testsuite/gcc.dg/format/gcc_diag-10.c
index ba2629b..a2f99fe 100644
--- a/gcc/testsuite/gcc.dg/format/gcc_diag-10.c
+++ b/gcc/testsuite/gcc.dg/format/gcc_diag-10.c
@@ -22,6 +22,9 @@  typedef struct gimple gimple;
 /* Likewise for gimple.  */
 typedef struct cgraph_node cgraph_node;
 
+/* Likewise for diagnostic_event_id_t.  */
+typedef struct diagnostic_event_id_t diagnostic_event_id_t;
+
 #define FORMAT(kind) __attribute__ ((format (__gcc_## kind ##__, 1, 2)))
 
 void diag (const char*, ...) FORMAT (diag);
@@ -30,7 +33,7 @@  void tdiag (const char*, ...) FORMAT (tdiag);
 void cxxdiag (const char*, ...) FORMAT (cxxdiag);
 void dump (const char*, ...) FORMAT (dump_printf);
 
-void test_diag (tree t, gimple *gc)
+void test_diag (tree t, gimple *gc, diagnostic_event_id_t *event_id_ptr)
 {
   diag ("%<");   /* { dg-warning "unterminated quoting directive" } */
   diag ("%>");   /* { dg-warning "unmatched quoting directive " } */
@@ -38,6 +41,7 @@  void test_diag (tree t, gimple *gc)
 
   diag ("%G", gc); /* { dg-warning "format" } */
   diag ("%K", t); /* { dg-warning "format" } */
+  diag ("%@", event_id_ptr);
 
   diag ("%R");       /* { dg-warning "unmatched color reset directive" } */
   diag ("%r", "");   /* { dg-warning "unterminated color directive" } */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-default.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-default.c
new file mode 100644
index 0000000..5712dbd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-default.c
@@ -0,0 +1,142 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-show-caret" } */
+
+#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 "" }
+   free (ptr);
+   ^~~~~~~~~~
+  'test': events 1-2
+    |
+    | {
+    | ^
+    | |
+    | (1) entering 'test'
+    |   boxed_int *obj = make_boxed_int (i);
+    |                    ~~~~~~~~~~~~~~~~~~
+    |                    |
+    |                    (2) calling 'make_boxed_int'
+    |
+    +--> 'make_boxed_int': events 3-4
+           |
+           | {
+           | ^
+           | |
+           | (3) entering 'make_boxed_int'
+           |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+           |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+           |                                    |
+           |                                    (4) calling 'wrapped_malloc'
+           |
+           +--> 'wrapped_malloc': events 5-6
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (5) entering 'wrapped_malloc'
+                  |   return malloc (size);
+                  |          ~~~~~~~~~~~~~
+                  |          |
+                  |          (6) calling 'malloc'
+                  |
+    <-------------+
+    |
+  'test': event 7
+    |
+    |   free_boxed_int (obj);
+    |   ^~~~~~~~~~~~~~~~~~~~
+    |   |
+    |   (7) calling 'free_boxed_int'
+    |
+    +--> 'free_boxed_int': events 8-9
+           |
+           | {
+           | ^
+           | |
+           | (8) entering 'free_boxed_int'
+           |   wrapped_free (bi);
+           |   ~~~~~~~~~~~~~~~~~
+           |   |
+           |   (9) calling 'wrapped_free'
+           |
+           +--> 'wrapped_free': events 10-11
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (10) entering 'wrapped_free'
+                  |   free (ptr);
+                  |   ~~~~~~~~~~
+                  |   |
+                  |   (11) calling 'free'
+                  |
+    <-------------+
+    |
+  'test': event 12
+    |
+    |   free_boxed_int (obj);
+    |   ^~~~~~~~~~~~~~~~~~~~
+    |   |
+    |   (12) calling 'free_boxed_int'
+    |
+    +--> 'free_boxed_int': events 13-14
+           |
+           | {
+           | ^
+           | |
+           | (13) entering 'free_boxed_int'
+           |   wrapped_free (bi);
+           |   ~~~~~~~~~~~~~~~~~
+           |   |
+           |   (14) calling 'wrapped_free'
+           |
+           +--> 'wrapped_free': events 15-16
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (15) entering 'wrapped_free'
+                  |   free (ptr);
+                  |   ~~~~~~~~~~
+                  |   |
+                  |   (16) calling 'free'
+                  |
+     { dg-end-multiline-output "" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-1.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-1.c
new file mode 100644
index 0000000..430d817
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-1.c
@@ -0,0 +1,142 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+#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 "" }
+   free (ptr);
+   ^~~~~~~~~~
+  'test': events 1-2
+    |
+    | {
+    | ^
+    | |
+    | (1) entering 'test'
+    |   boxed_int *obj = make_boxed_int (i);
+    |                    ~~~~~~~~~~~~~~~~~~
+    |                    |
+    |                    (2) calling 'make_boxed_int'
+    |
+    +--> 'make_boxed_int': events 3-4
+           |
+           | {
+           | ^
+           | |
+           | (3) entering 'make_boxed_int'
+           |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+           |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+           |                                    |
+           |                                    (4) calling 'wrapped_malloc'
+           |
+           +--> 'wrapped_malloc': events 5-6
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (5) entering 'wrapped_malloc'
+                  |   return malloc (size);
+                  |          ~~~~~~~~~~~~~
+                  |          |
+                  |          (6) calling 'malloc'
+                  |
+    <-------------+
+    |
+  'test': event 7
+    |
+    |   free_boxed_int (obj);
+    |   ^~~~~~~~~~~~~~~~~~~~
+    |   |
+    |   (7) calling 'free_boxed_int'
+    |
+    +--> 'free_boxed_int': events 8-9
+           |
+           | {
+           | ^
+           | |
+           | (8) entering 'free_boxed_int'
+           |   wrapped_free (bi);
+           |   ~~~~~~~~~~~~~~~~~
+           |   |
+           |   (9) calling 'wrapped_free'
+           |
+           +--> 'wrapped_free': events 10-11
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (10) entering 'wrapped_free'
+                  |   free (ptr);
+                  |   ~~~~~~~~~~
+                  |   |
+                  |   (11) calling 'free'
+                  |
+    <-------------+
+    |
+  'test': event 12
+    |
+    |   free_boxed_int (obj);
+    |   ^~~~~~~~~~~~~~~~~~~~
+    |   |
+    |   (12) calling 'free_boxed_int'
+    |
+    +--> 'free_boxed_int': events 13-14
+           |
+           | {
+           | ^
+           | |
+           | (13) entering 'free_boxed_int'
+           |   wrapped_free (bi);
+           |   ~~~~~~~~~~~~~~~~~
+           |   |
+           |   (14) calling 'wrapped_free'
+           |
+           +--> 'wrapped_free': events 15-16
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (15) entering 'wrapped_free'
+                  |   free (ptr);
+                  |   ~~~~~~~~~~
+                  |   |
+                  |   (16) calling 'free'
+                  |
+     { dg-end-multiline-output "" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-2.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-2.c
new file mode 100644
index 0000000..c2bfabe
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-2.c
@@ -0,0 +1,154 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+/* Verify that 'inline-events' copes gracefully with events with an
+   unknown location.  */
+
+#include <stdlib.h>
+
+extern void missing_location ();
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+  /* { dg-begin-multiline-output "" }
+   free (ptr);
+   ^~~~~~~~~~
+  '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
+    |
+    |cc1:
+    | (12): calling 'missing_location'
+    |
+  'test': event 13
+    |
+    |   free_boxed_int (obj);
+    |   ^~~~~~~~~~~~~~~~~~~~
+    |   |
+    |   (13) calling 'free_boxed_int'
+    |
+    +--> 'free_boxed_int': events 14-15
+           |
+           | {
+           | ^
+           | |
+           | (14) entering 'free_boxed_int'
+           |   wrapped_free (bi);
+           |   ~~~~~~~~~~~~~~~~~
+           |   |
+           |   (15) calling 'wrapped_free'
+           |
+           +--> 'wrapped_free': events 16-17
+                  |
+                  | {
+                  | ^
+                  | |
+                  | (16) entering 'wrapped_free'
+                  |   free (ptr);
+                  |   ~~~~~~~~~~
+                  |   |
+                  |   (17) calling 'free'
+                  |
+     { dg-end-multiline-output "" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+
+  free_boxed_int (obj);
+
+  missing_location ();
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-3.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-3.c
new file mode 100644
index 0000000..5e7b099
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-3.c
@@ -0,0 +1,153 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-line-numbers -fdiagnostics-nn-line-numbers -fdiagnostics-show-caret" } */
+
+/* Verify the interaction of inline-events with line numbers.  */
+
+#include <stdlib.h>
+
+extern void missing_location ();
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+  /* { dg-begin-multiline-output "" }
+   NN |   free (ptr);
+      |   ^~~~~~~~~~
+  'test': events 1-2
+    |
+    |   NN | {
+    |      | ^
+    |      | |
+    |      | (1) entering 'test'
+    |   NN |   boxed_int *obj = make_boxed_int (i);
+    |      |                    ~~~~~~~~~~~~~~~~~~
+    |      |                    |
+    |      |                    (2) calling 'make_boxed_int'
+    |
+    +--> 'make_boxed_int': events 3-4
+           |
+           |   NN | {
+           |      | ^
+           |      | |
+           |      | (3) entering 'make_boxed_int'
+           |   NN |   boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+           |      |                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+           |      |                                    |
+           |      |                                    (4) calling 'wrapped_malloc'
+           |
+           +--> 'wrapped_malloc': events 5-6
+                  |
+                  |   NN | {
+                  |      | ^
+                  |      | |
+                  |      | (5) entering 'wrapped_malloc'
+                  |   NN |   return malloc (size);
+                  |      |          ~~~~~~~~~~~~~
+                  |      |          |
+                  |      |          (6) calling 'malloc'
+                  |
+    <-------------+
+    |
+  'test': event 7
+    |
+    |   NN |   free_boxed_int (obj);
+    |      |   ^~~~~~~~~~~~~~~~~~~~
+    |      |   |
+    |      |   (7) calling 'free_boxed_int'
+    |
+    +--> 'free_boxed_int': events 8-9
+           |
+           |   NN | {
+           |      | ^
+           |      | |
+           |      | (8) entering 'free_boxed_int'
+           |   NN |   wrapped_free (bi);
+           |      |   ~~~~~~~~~~~~~~~~~
+           |      |   |
+           |      |   (9) calling 'wrapped_free'
+           |
+           +--> 'wrapped_free': events 10-11
+                  |
+                  |   NN | {
+                  |      | ^
+                  |      | |
+                  |      | (10) entering 'wrapped_free'
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (11) calling 'free'
+                  |
+    <-------------+
+    |
+  'test': event 12
+    |
+    |cc1:
+    | (12): calling 'missing_location'
+    |
+  'test': event 13
+    |
+    |   NN |   free_boxed_int (obj);
+    |      |   ^~~~~~~~~~~~~~~~~~~~
+    |      |   |
+    |      |   (13) calling 'free_boxed_int'
+    |
+    +--> 'free_boxed_int': events 14-15
+           |
+           |   NN | {
+           |      | ^
+           |      | |
+           |      | (14) entering 'free_boxed_int'
+           |   NN |   wrapped_free (bi);
+           |      |   ~~~~~~~~~~~~~~~~~
+           |      |   |
+           |      |   (15) calling 'wrapped_free'
+           |
+           +--> 'wrapped_free': events 16-17
+                  |
+                  |   NN | {
+                  |      | ^
+                  |      | |
+                  |      | (16) entering 'wrapped_free'
+                  |   NN |   free (ptr);
+                  |      |   ~~~~~~~~~~
+                  |      |   |
+                  |      |   (17) calling 'free'
+                  |
+     { dg-end-multiline-output "" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+
+  free_boxed_int (obj);
+
+  missing_location ();
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-none.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-none.c
new file mode 100644
index 0000000..0a29f67
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-none.c
@@ -0,0 +1,43 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=none" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{
+  boxed_int *obj = make_boxed_int (i);
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-separate-events.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-separate-events.c
new file mode 100644
index 0000000..dcb72c0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-separate-events.c
@@ -0,0 +1,44 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=separate-events" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+  return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+  free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+}
+
+typedef struct boxed_int
+{
+  int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+  boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+  result->i = i;
+  return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+  wrapped_free (bi);
+}
+
+void test (int i)
+{ /* { dg-message "\\(1\\) entering 'test'" } */
+  boxed_int *obj = make_boxed_int (i); /* { dg-message "\\(2\\) calling 'make_boxed_int'" } */
+  /* etc */
+
+  free_boxed_int (obj);
+
+  free_boxed_int (obj);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c
new file mode 100644
index 0000000..7b11c90
--- /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 from \\(1\\) 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..391aeb9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c
@@ -0,0 +1,56 @@ 
+/* { 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);
+      |     ^~~~~~~~~~~~~~~~~~~~~~~~~
+  '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 from (1) 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..8239815
--- /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);
+      diagnostic_event_id_t alloc_event_id
+	= 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 from %@ as argument %i",
+		      "PyList_Append", &alloc_event_id, 1);
+      richloc.set_path (&path);
+      error_at (&richloc,
+		"passing NULL as argument %i to %qs"
+		" which requires a non-NULL parameter",
+		1, "PyList_Append");
+    }
+}
+
+/* A (function, location_t) pair.  */
+
+struct event_location_t
+{
+  event_location_t ()
+  : m_fun (NULL), m_loc (UNKNOWN_LOCATION)
+  {}
+
+  event_location_t (function *fun, location_t loc)
+  : m_fun (fun), m_loc (loc)
+  {}
+
+  void set (const gimple *stmt, function *fun)
+  {
+    m_fun = fun;
+    m_loc = gimple_location (stmt);
+  }
+
+  function *m_fun;
+  location_t m_loc;
+};
+
+/* If FUN's name matches FUNCNAME, write the function and its start location
+   into *OUT_ENTRY.  */
+
+static void
+check_for_named_function (function *fun, const char *funcname,
+			  event_location_t *out_entry)
+{
+  gcc_assert (fun);
+  gcc_assert (funcname);
+
+  if (strcmp (IDENTIFIER_POINTER (DECL_NAME (fun->decl)), funcname))
+    return;
+
+  *out_entry = event_location_t (fun, fun->function_start_locus);
+}
+
+
+/* Example 2: an interprocedural path.  */
+
+class test_diagnostic_path : public simple_diagnostic_path
+{
+ public:
+  test_diagnostic_path (pretty_printer *event_pp)
+  : simple_diagnostic_path (event_pp)
+  {
+  }
+  void add_entry (event_location_t evloc, int stack_depth,
+		  const char *funcname)
+  {
+    gcc_assert (evloc.m_fun);
+    add_event (evloc.m_loc, evloc.m_fun->decl, stack_depth,
+	       "entering %qs", funcname);
+  }
+
+  void add_call (event_location_t call_evloc, int caller_stack_depth,
+		 event_location_t callee_entry_evloc, const char *callee)
+  {
+    gcc_assert (call_evloc.m_fun);
+    add_event (call_evloc.m_loc, call_evloc.m_fun->decl, caller_stack_depth,
+	       "calling %qs", callee);
+    add_entry (callee_entry_evloc, caller_stack_depth + 1, callee);
+  }
+
+  void add_leaf_call (event_location_t call_evloc, int caller_stack_depth,
+		      const char *callee)
+  {
+    gcc_assert (call_evloc.m_fun);
+    add_event (call_evloc.m_loc, call_evloc.m_fun->decl, caller_stack_depth,
+	       "calling %qs", callee);
+  }
+};
+
+static void
+example_2 ()
+{
+  gimple_stmt_iterator gsi;
+  basic_block bb;
+
+  event_location_t entry_to_wrapped_malloc;
+  event_location_t call_to_malloc;
+
+  event_location_t entry_to_wrapped_free;
+  event_location_t call_to_free;
+
+  event_location_t entry_to_make_boxed_int;
+  event_location_t call_to_wrapped_malloc;
+
+  event_location_t entry_to_free_boxed_int;
+  event_location_t call_to_wrapped_free;
+
+  event_location_t entry_to_test;
+  event_location_t call_to_make_boxed_int;
+  event_location_t call_to_free_boxed_int;
+
+  event_location_t call_to_missing_location;
+
+  cgraph_node *node;
+  FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+    {
+      function *fun = node->get_fun ();
+      FOR_EACH_BB_FN (bb, fun)
+	{
+	  check_for_named_function (fun, "wrapped_malloc",
+				    &entry_to_wrapped_malloc);
+	  check_for_named_function (fun, "wrapped_free",
+				    &entry_to_wrapped_free);
+	  check_for_named_function (fun, "make_boxed_int",
+				    &entry_to_make_boxed_int);
+	  check_for_named_function (fun, "free_boxed_int",
+				    &entry_to_free_boxed_int);
+	  check_for_named_function (fun, "test",
+				    &entry_to_test);
+
+	  for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+	    {
+	      gimple *stmt = gsi_stmt (gsi);
+	      if (gcall *call = check_for_named_call (stmt, "malloc", 1))
+		call_to_malloc.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "free", 1))
+		call_to_free.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "wrapped_malloc", 1))
+		call_to_wrapped_malloc.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "wrapped_free", 1))
+		call_to_wrapped_free.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "make_boxed_int", 1))
+		call_to_make_boxed_int.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "free_boxed_int", 1))
+		call_to_free_boxed_int.set (call, fun);
+	      if (gcall *call = check_for_named_call (stmt, "missing_location", 0))
+		{
+		  call_to_missing_location.set (call, fun);
+		  /* Simulate an event that's missing a useful location_t.  */
+		  call_to_missing_location.m_loc = UNKNOWN_LOCATION;
+		}
+	    }
+	}
+    }
+
+  if (call_to_malloc.m_fun)
+    {
+      auto_diagnostic_group d;
+
+      gcc_rich_location richloc (call_to_free.m_loc);
+      test_diagnostic_path path (global_dc->printer);
+      path.add_entry (entry_to_test, 0, "test");
+      path.add_call (call_to_make_boxed_int, 0,
+		     entry_to_make_boxed_int, "make_boxed_int");
+      path.add_call (call_to_wrapped_malloc, 1,
+		     entry_to_wrapped_malloc, "wrapped_malloc");
+      path.add_leaf_call (call_to_malloc, 2, "malloc");
+
+      for (int i = 0; i < 2; i++)
+	{
+	  path.add_call (call_to_free_boxed_int, 0,
+			 entry_to_free_boxed_int, "free_boxed_int");
+	  path.add_call (call_to_wrapped_free, 1,
+			 entry_to_wrapped_free, "wrapped_free");
+	  path.add_leaf_call (call_to_free, 2, "free");
+	  if (i == 0 && call_to_missing_location.m_fun)
+	    path.add_leaf_call (call_to_missing_location, 0, "missing_location");
+	}
+
+      richloc.set_path (&path);
+
+      diagnostic_metadata m;
+      m.add_cwe (415); /* CWE-415: Double Free.  */
+
+      warning_at (&richloc, m, 0,
+		  "double-free of %qs", "ptr");
+    }
+}
+
+unsigned int
+pass_test_show_path::execute (function *)
+{
+  example_1 ();
+  example_2 ();
+
+  return 0;
+}
+
+static opt_pass *
+make_pass_test_show_path (gcc::context *ctxt)
+{
+  return new pass_test_show_path (ctxt);
+}
+
+int
+plugin_init (struct plugin_name_args *plugin_info,
+	     struct plugin_gcc_version *version)
+{
+  struct register_pass_info pass_info;
+  const char *plugin_name = plugin_info->base_name;
+  int argc = plugin_info->argc;
+  struct plugin_argument *argv = plugin_info->argv;
+
+  if (!plugin_default_version_check (version, &gcc_version))
+    return 1;
+
+  pass_info.pass = make_pass_test_show_path (g);
+  pass_info.reference_pass_name = "whole-program";
+  pass_info.ref_pass_instance_number = 1;
+  pass_info.pos_op = PASS_POS_INSERT_BEFORE;
+  register_callback (plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL,
+		     &pass_info);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index 2f75463..4ae3048 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -94,6 +94,16 @@  set plugin_test_list [list \
 	  diagnostic-test-inlining-2.c \
 	  diagnostic-test-inlining-3.c \
 	  diagnostic-test-inlining-4.c } \
+    { diagnostic_plugin_test_paths.c \
+	  diagnostic-test-paths-1.c \
+	  diagnostic-test-paths-2.c \
+	  diagnostic-test-paths-3.c \
+	  diagnostic-path-format-default.c \
+	  diagnostic-path-format-none.c \
+	  diagnostic-path-format-separate-events.c \
+	  diagnostic-path-format-inline-events-1.c \
+	  diagnostic-path-format-inline-events-2.c \
+	  diagnostic-path-format-inline-events-3.c } \
     { location_overflow_plugin.c \
 	  location-overflow-test-1.c \
 	  location-overflow-test-2.c \
diff --git a/gcc/toplev.c b/gcc/toplev.c
index 7136164..d5dad4b 100644
--- a/gcc/toplev.c
+++ b/gcc/toplev.c
@@ -1104,6 +1104,10 @@  general_init (const char *argv0, bool init_signals)
     = global_options_init.x_flag_diagnostics_nn_line_numbers;
   global_dc->show_metadata
     = global_options_init.x_flag_diagnostics_show_metadata;
+  global_dc->path_format
+    = (enum diagnostic_path_format)global_options_init.x_flag_diagnostics_path_format;
+  global_dc->show_path_depths
+    = global_options_init.x_flag_diagnostics_show_path_depths;
   global_dc->show_option_requested
     = global_options_init.x_flag_diagnostics_show_option;
   global_dc->min_margin_width
diff --git a/gcc/tree-diagnostic-path.cc b/gcc/tree-diagnostic-path.cc
new file mode 100644
index 0000000..d044152
--- /dev/null
+++ b/gcc/tree-diagnostic-path.cc
@@ -0,0 +1,809 @@ 
+/* 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/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "diagnostic.h"
+#include "tree-pretty-print.h"
+#include "gimple-pretty-print.h"
+#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 "diagnostic-event-id.h"
+#include "selftest.h"
+#include "selftest-diagnostic.h"
+
+/* Anonymous namespace for path-printing code.  */
+
+namespace {
+
+/* Subclass of range_label for showing a consecutive run of events
+   within a diagnostic_path as labelled ranges within one
+   gcc_rich_location.  */
+
+class path_label : public range_label
+{
+ public:
+  path_label (const diagnostic_path *path, unsigned start_idx)
+  : m_path (path), m_start_idx (start_idx)
+  {}
+
+  label_text get_text (unsigned range_idx) const FINAL OVERRIDE
+  {
+    unsigned event_idx = m_start_idx + range_idx;
+    const diagnostic_event &event = m_path->get_event (event_idx);
+
+    /* Get the description of the event, perhaps with colorization:
+       normally, we don't colorize within a range_label, but this
+       is special-cased for diagnostic paths.  */
+    bool colorize = pp_show_color (global_dc->printer);
+    label_text event_text (event.get_desc (colorize));
+    gcc_assert (event_text.m_buffer);
+    pretty_printer pp;
+    pp_show_color (&pp) = pp_show_color (global_dc->printer);
+    diagnostic_event_id_t event_id (event_idx);
+    pp_printf (&pp, "%@ %s", &event_id, event_text.m_buffer);
+    event_text.maybe_free ();
+    label_text result = label_text::take (xstrdup (pp_formatted_text (&pp)));
+    return result;
+  }
+
+ private:
+  const diagnostic_path *m_path;
+  unsigned m_start_idx;
+};
+
+/* Return true if E1 and E2 can be consolidated into the same run of events
+   when printing a diagnostic_path.  */
+
+static bool
+can_consolidate_events (const diagnostic_event &e1,
+			const diagnostic_event &e2,
+			bool check_locations)
+{
+  if (e1.get_fndecl () != e2.get_fndecl ())
+    return false;
+
+  if (e1.get_stack_depth () != e2.get_stack_depth ())
+    return false;
+
+  if (check_locations)
+    {
+      location_t loc1 = e1.get_location ();
+      location_t loc2 = e2.get_location ();
+
+      if (loc1 < RESERVED_LOCATION_COUNT
+	  || loc2 < RESERVED_LOCATION_COUNT)
+	return false;
+
+      /* Neither can be macro-based.  */
+      if (linemap_location_from_macro_expansion_p (line_table, loc1))
+	return false;
+      if (linemap_location_from_macro_expansion_p (line_table, loc2))
+	return false;
+    }
+
+  /* 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).  */
+
+class path_summary
+{
+  /* A range of consecutive events within a diagnostic_path,
+     all with the same fndecl and stack_depth, and which are suitable
+     to print with a single call to diagnostic_show_locus.  */
+  struct event_range
+  {
+    event_range (const diagnostic_path *path, unsigned start_idx,
+		 const diagnostic_event &initial_event)
+    : m_path (path),
+      m_initial_event (initial_event),
+      m_fndecl (initial_event.get_fndecl ()),
+      m_stack_depth (initial_event.get_stack_depth ()),
+      m_start_idx (start_idx), m_end_idx (start_idx),
+      m_path_label (path, start_idx),
+      m_richloc (initial_event.get_location (), &m_path_label)
+    {}
+
+    bool maybe_add_event (const diagnostic_event &new_ev, unsigned idx,
+			  bool check_rich_locations)
+    {
+      if (!can_consolidate_events (m_initial_event, new_ev,
+				   check_rich_locations))
+	return false;
+      if (check_rich_locations)
+	if (!m_richloc.add_location_if_nearby (new_ev.get_location (),
+					       false, &m_path_label))
+	  return false;
+      m_end_idx = idx;
+      return true;
+    }
+
+    /* Print the events in this range to DC, typically as a single
+       call to the printer's diagnostic_show_locus.  */
+
+    void print (diagnostic_context *dc)
+    {
+      location_t initial_loc = m_initial_event.get_location ();
+
+      /* Emit a span indicating the filename (and line/column) if the
+	 line has changed relative to the last call to
+	 diagnostic_show_locus.  */
+      if (dc->show_caret)
+	{
+	  expanded_location exploc
+	    = linemap_client_expand_location_to_spelling_point
+	    (initial_loc, LOCATION_ASPECT_CARET);
+	  if (exploc.file != LOCATION_FILE (dc->last_location))
+	    dc->start_span (dc, exploc);
+	}
+
+      /* If we have an UNKNOWN_LOCATION (or BUILTINS_LOCATION) as the
+	 primary location for an event, diagnostic_show_locus won't print
+	 anything.
+
+	 In particular the label for the event won't get printed.
+	 Fail more gracefully in this case by showing the event
+	 index and text, at no particular location.  */
+      if (initial_loc <= BUILTINS_LOCATION)
+	{
+	  for (unsigned i = m_start_idx; i <= m_end_idx; i++)
+	    {
+	      const diagnostic_event &iter_event = m_path->get_event (i);
+	      diagnostic_event_id_t event_id (i);
+	      label_text event_text (iter_event.get_desc (true));
+	      pretty_printer *pp = dc->printer;
+	      pp_printf (pp, " %@: %s", &event_id, event_text.m_buffer);
+	      pp_newline (pp);
+	      event_text.maybe_free ();
+	    }
+	  return;
+	}
+
+      /* Call diagnostic_show_locus to show the events using labels.  */
+      diagnostic_show_locus (dc, &m_richloc, DK_DIAGNOSTIC_PATH);
+
+      /* If we have a macro expansion, show the expansion to the user.  */
+      if (linemap_location_from_macro_expansion_p (line_table, initial_loc))
+	{
+	  gcc_assert (m_start_idx == m_end_idx);
+	  maybe_unwind_expanded_macro_loc (dc, initial_loc);
+	}
+    }
+
+    const diagnostic_path *m_path;
+    const diagnostic_event &m_initial_event;
+    tree m_fndecl;
+    int m_stack_depth;
+    unsigned m_start_idx;
+    unsigned m_end_idx;
+    path_label m_path_label;
+    gcc_rich_location m_richloc;
+  };
+
+ public:
+  path_summary (const diagnostic_path &path, bool check_rich_locations);
+
+  void print (diagnostic_context *dc, bool show_depths) const;
+
+  unsigned get_num_ranges () const { return m_ranges.length (); }
+
+ private:
+  auto_delete_vec <event_range> m_ranges;
+};
+
+/* path_summary's ctor.  */
+
+path_summary::path_summary (const diagnostic_path &path,
+			    bool check_rich_locations)
+{
+  const unsigned num_events = path.num_events ();
+
+  event_range *cur_event_range = NULL;
+  for (unsigned idx = 0; idx < num_events; idx++)
+    {
+      const diagnostic_event &event = path.get_event (idx);
+      if (cur_event_range)
+	if (cur_event_range->maybe_add_event (event, idx, check_rich_locations))
+	  continue;
+
+      cur_event_range = new event_range (&path, idx, event);
+      m_ranges.safe_push (cur_event_range);
+    }
+}
+
+/* Write SPACES to PP.  */
+
+static void
+write_indent (pretty_printer *pp, int spaces)
+{
+  for (int i = 0; i < spaces; i++)
+    pp_space (pp);
+}
+
+/* Print FNDDECL to PP, quoting it if QUOTED is true.
+
+   We can't use "%qE" here since we can't guarantee the capabilities
+   of PP.  */
+
+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 this path_summary to DC, giving an overview of the interprocedural
+   calls and returns.
+
+   Print the event descriptions in a nested form, printing 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 |
+                   |    |
+
+   If SHOW_DEPTHS is true, append " (depth N)" to the header of each run
+   of events.
+
+   For events with UNKNOWN_LOCATION, print a summary of each the event.  */
+
+void
+path_summary::print (diagnostic_context *dc, bool show_depths) const
+{
+  pretty_printer *pp = dc->printer;
+
+  const int per_frame_indent = 2;
+
+  const char *const line_color = "path";
+  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;
+  event_range *range;
+  FOR_EACH_VEC_ELT (m_ranges, i, range)
+    {
+      write_indent (pp, cur_indent);
+      if (i > 0)
+	{
+	  const path_summary::event_range *prev_range
+	    = 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);
+
+      /* Print a run of events.  */
+      {
+	write_indent (pp, cur_indent + per_frame_indent);
+	pp_string (pp, start_line_color);
+	pp_string (pp, "|");
+	pp_string (pp, end_line_color);
+	pp_newline (pp);
+
+	char *saved_prefix = pp_take_prefix (pp);
+	char *prefix;
+	{
+	  pretty_printer tmp_pp;
+	  write_indent (&tmp_pp, cur_indent + per_frame_indent);
+	  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;
+	range->print (dc);
+	pp_set_prefix (pp, saved_prefix);
+
+	write_indent (pp, cur_indent + per_frame_indent);
+	pp_string (pp, start_line_color);
+	pp_string (pp, "|");
+	pp_string (pp, end_line_color);
+	pp_newline (pp);
+      }
+
+      if (i < m_ranges.length () - 1)
+	{
+	  const path_summary::event_range *next_range
+	    = 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".  */
+
+	      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 - per_frame_indent;
+	      write_indent (pp, vbar_for_next_frame);
+	      pp_string (pp, start_line_color);
+	      pp_character (pp, '<');
+	      for (int i = indent_for_next_frame + per_frame_indent;
+		   i < cur_indent + per_frame_indent - 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 + per_frame_indent);
+	      cur_indent += per_frame_indent;
+	    }
+
+	}
+    }
+}
+
+} /* end of anonymous namespace for path-printing code.  */
+
+/* Print PATH to CONTEXT, according to CONTEXT's path_format.  */
+
+void
+default_tree_diagnostic_path_printer (diagnostic_context *context,
+				      const diagnostic_path *path)
+{
+  gcc_assert (path);
+
+  const unsigned num_events = path->num_events ();
+
+  switch (context->path_format)
+    {
+    case DPF_NONE:
+      /* Do nothing.  */
+      return;
+
+    case DPF_SEPARATE_EVENTS:
+      {
+	/* A note per event.  */
+	for (unsigned i = 0; i < num_events; i++)
+	  {
+	    const diagnostic_event &event = path->get_event (i);
+	    label_text event_text (event.get_desc (false));
+	    gcc_assert (event_text.m_buffer);
+	    diagnostic_event_id_t event_id (i);
+	    inform (event.get_location (),
+		    "%@ %s", &event_id, event_text.m_buffer);
+	    event_text.maybe_free ();
+	  }
+      }
+      break;
+
+    case DPF_INLINE_EVENTS:
+      {
+	/* Consolidate related events.  */
+	path_summary summary (*path, true);
+	char *saved_prefix = pp_take_prefix (context->printer);
+	pp_set_prefix (context->printer, NULL);
+	summary.print (context, context->show_path_depths);
+	pp_flush (context->printer);
+	pp_set_prefix (context->printer, saved_prefix);
+      }
+    }
+}
+
+/* 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).  */
+
+json::value *
+default_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;
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* A subclass of simple_diagnostic_path that adds member functions
+   for adding test events.  */
+
+class test_diagnostic_path : public simple_diagnostic_path
+{
+ public:
+  test_diagnostic_path (pretty_printer *event_pp)
+  : simple_diagnostic_path (event_pp)
+  {
+  }
+
+  void add_entry (tree fndecl, int stack_depth)
+  {
+    add_event (UNKNOWN_LOCATION, fndecl, stack_depth,
+	       "entering %qE", fndecl);
+  }
+
+  void add_return (tree fndecl, int stack_depth)
+  {
+    add_event (UNKNOWN_LOCATION, fndecl, stack_depth,
+	       "returning to %qE", fndecl);
+  }
+
+  void add_call (tree caller, int caller_stack_depth, tree callee)
+  {
+    add_event (UNKNOWN_LOCATION, caller, caller_stack_depth,
+	       "calling %qE", callee);
+    add_entry (callee, caller_stack_depth + 1);
+  }
+};
+
+/* Verify that empty paths are handled gracefully.  */
+
+static void
+test_empty_path (pretty_printer *event_pp)
+{
+  test_diagnostic_path path (event_pp);
+  ASSERT_FALSE (path.interprocedural_p ());
+
+  path_summary summary (path, false);
+  ASSERT_EQ (summary.get_num_ranges (), 0);
+
+  test_diagnostic_context dc;
+  summary.print (&dc, true);
+  ASSERT_STREQ ("",
+		pp_formatted_text (dc.printer));
+}
+
+/* Verify that print_path_summary works on a purely intraprocedural path.  */
+
+static void
+test_intraprocedural_path (pretty_printer *event_pp)
+{
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_foo = build_fn_decl ("foo", fntype_void_void);
+
+  test_diagnostic_path path (event_pp);
+  path.add_event (UNKNOWN_LOCATION, fndecl_foo, 0, "first %qs", "free");
+  path.add_event (UNKNOWN_LOCATION, fndecl_foo, 0, "double %qs", "free");
+
+  ASSERT_FALSE (path.interprocedural_p ());
+
+  path_summary summary (path, false);
+  ASSERT_EQ (summary.get_num_ranges (), 1);
+
+  test_diagnostic_context dc;
+  summary.print (&dc, true);
+  ASSERT_STREQ ("  `foo': events 1-2 (depth 0)\n"
+		"    |\n"
+		"    | (1): first `free'\n"
+		"    | (2): double `free'\n"
+		"    |\n",
+		pp_formatted_text (dc.printer));
+}
+
+/* Verify that print_path_summary works on an interprocedural path.  */
+
+static void
+test_interprocedural_path_1 (pretty_printer *event_pp)
+{
+  /* Build fndecls.  The types aren't quite right, but that
+     doesn't matter for the purposes of this test.  */
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_test = build_fn_decl ("test", fntype_void_void);
+  tree fndecl_make_boxed_int
+    = build_fn_decl ("make_boxed_int", fntype_void_void);
+  tree fndecl_wrapped_malloc
+    = build_fn_decl ("wrapped_malloc", fntype_void_void);
+  tree fndecl_free_boxed_int
+    = build_fn_decl ("free_boxed_int", fntype_void_void);
+  tree fndecl_wrapped_free
+    = build_fn_decl ("wrapped_free", fntype_void_void);
+
+  test_diagnostic_path path (event_pp);
+  path.add_entry (fndecl_test, 0);
+  path.add_call (fndecl_test, 0, fndecl_make_boxed_int);
+  path.add_call (fndecl_make_boxed_int, 1, fndecl_wrapped_malloc);
+  path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_malloc, 2, "calling malloc");
+  path.add_return (fndecl_test, 0);
+  path.add_call (fndecl_test, 0, fndecl_free_boxed_int);
+  path.add_call (fndecl_free_boxed_int, 1, fndecl_wrapped_free);
+  path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_free, 2, "calling free");
+  path.add_return (fndecl_test, 0);
+  path.add_call (fndecl_test, 0, fndecl_free_boxed_int);
+  path.add_call (fndecl_free_boxed_int, 1, fndecl_wrapped_free);
+  path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_free, 2, "calling free");
+  ASSERT_EQ (path.num_events (), 18);
+
+  ASSERT_TRUE (path.interprocedural_p ());
+
+  path_summary summary (path, false);
+  ASSERT_EQ (summary.get_num_ranges (), 9);
+
+  test_diagnostic_context dc;
+  summary.print (&dc, true);
+  ASSERT_STREQ
+    ("  `test': events 1-2 (depth 0)\n"
+     "    |\n"
+     "    | (1): entering `test'\n"
+     "    | (2): calling `make_boxed_int'\n"
+     "    |\n"
+     "    +--> `make_boxed_int': events 3-4 (depth 1)\n"
+     "           |\n"
+     "           | (3): entering `make_boxed_int'\n"
+     "           | (4): calling `wrapped_malloc'\n"
+     "           |\n"
+     "           +--> `wrapped_malloc': events 5-6 (depth 2)\n"
+     "                  |\n"
+     "                  | (5): entering `wrapped_malloc'\n"
+     "                  | (6): calling malloc\n"
+     "                  |\n"
+     "    <-------------+\n"
+     "    |\n"
+     "  `test': events 7-8 (depth 0)\n"
+     "    |\n"
+     "    | (7): returning to `test'\n"
+     "    | (8): calling `free_boxed_int'\n"
+     "    |\n"
+     "    +--> `free_boxed_int': events 9-10 (depth 1)\n"
+     "           |\n"
+     "           | (9): entering `free_boxed_int'\n"
+     "           | (10): calling `wrapped_free'\n"
+     "           |\n"
+     "           +--> `wrapped_free': events 11-12 (depth 2)\n"
+     "                  |\n"
+     "                  | (11): entering `wrapped_free'\n"
+     "                  | (12): calling free\n"
+     "                  |\n"
+     "    <-------------+\n"
+     "    |\n"
+     "  `test': events 13-14 (depth 0)\n"
+     "    |\n"
+     "    | (13): returning to `test'\n"
+     "    | (14): calling `free_boxed_int'\n"
+     "    |\n"
+     "    +--> `free_boxed_int': events 15-16 (depth 1)\n"
+     "           |\n"
+     "           | (15): entering `free_boxed_int'\n"
+     "           | (16): calling `wrapped_free'\n"
+     "           |\n"
+     "           +--> `wrapped_free': events 17-18 (depth 2)\n"
+     "                  |\n"
+     "                  | (17): entering `wrapped_free'\n"
+     "                  | (18): calling free\n"
+     "                  |\n",
+     pp_formatted_text (dc.printer));
+}
+
+/* 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, false);
+  ASSERT_EQ (summary.get_num_ranges (), 5);
+
+  test_diagnostic_context dc;
+  summary.print (&dc, true);
+  ASSERT_STREQ
+    ("  `foo': events 1-2 (depth 0)\n"
+     "    |\n"
+     "    | (1): entering `foo'\n"
+     "    | (2): calling `bar'\n"
+     "    |\n"
+     "    +--> `bar': events 3-4 (depth 1)\n"
+     "           |\n"
+     "           | (3): entering `bar'\n"
+     "           | (4): calling `baz'\n"
+     "           |\n"
+     "           +--> `baz': event 5 (depth 2)\n"
+     "                  |\n"
+     "                  | (5): entering `baz'\n"
+     "                  |\n"
+     "           <------+\n"
+     "           |\n"
+     "         `bar': events 6-7 (depth 1)\n"
+     "           |\n"
+     "           | (6): returning to `bar'\n"
+     "           | (7): calling `baz'\n"
+     "           |\n"
+     "           +--> `baz': event 8 (depth 2)\n"
+     "                  |\n"
+     "                  | (8): entering `baz'\n"
+     "                  |\n",
+     pp_formatted_text (dc.printer));
+}
+
+/* Verify that print_path_summary is sane in the face of a recursive
+   diagnostic_path.  */
+
+static void
+test_recursion (pretty_printer *event_pp)
+{
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_factorial = build_fn_decl ("factorial", fntype_void_void);
+
+ test_diagnostic_path path (event_pp);
+  path.add_entry (fndecl_factorial, 0);
+  for (int depth = 0; depth < 3; depth++)
+    path.add_call (fndecl_factorial, depth, fndecl_factorial);
+  ASSERT_EQ (path.num_events (), 7);
+
+  ASSERT_TRUE (path.interprocedural_p ());
+
+  path_summary summary (path, false);
+  ASSERT_EQ (summary.get_num_ranges (), 4);
+
+  test_diagnostic_context dc;
+  summary.print (&dc, true);
+  ASSERT_STREQ
+    ("  `factorial': events 1-2 (depth 0)\n"
+     "    |\n"
+     "    | (1): entering `factorial'\n"
+     "    | (2): calling `factorial'\n"
+     "    |\n"
+     "    +--> `factorial': events 3-4 (depth 1)\n"
+     "           |\n"
+     "           | (3): entering `factorial'\n"
+     "           | (4): calling `factorial'\n"
+     "           |\n"
+     "           +--> `factorial': events 5-6 (depth 2)\n"
+     "                  |\n"
+     "                  | (5): entering `factorial'\n"
+     "                  | (6): calling `factorial'\n"
+     "                  |\n"
+     "                  +--> `factorial': event 7 (depth 3)\n"
+     "                         |\n"
+     "                         | (7): entering `factorial'\n"
+     "                         |\n",
+     pp_formatted_text (dc.printer));
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+tree_diagnostic_path_cc_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.c b/gcc/tree-diagnostic.c
index 7bd7430..d5f2f9e 100644
--- a/gcc/tree-diagnostic.c
+++ b/gcc/tree-diagnostic.c
@@ -96,9 +96,8 @@  struct loc_map_pair
    unwound macro expansion trace.  That's the part generated by this
    function.  */
 
-static void
+void
 maybe_unwind_expanded_macro_loc (diagnostic_context *context,
-                                 const diagnostic_info *diagnostic,
                                  location_t where)
 {
   const struct line_map *map;
@@ -106,6 +105,8 @@  maybe_unwind_expanded_macro_loc (diagnostic_context *context,
   unsigned ix;
   loc_map_pair loc, *iter;
 
+  const location_t original_loc = where;
+
   map = linemap_lookup (line_table, where);
   if (!linemap_macro_expansion_map_p (map))
     return;
@@ -142,7 +143,7 @@  maybe_unwind_expanded_macro_loc (diagnostic_context *context,
      first macro which expansion triggered this trace was expanded
      inside a system header.  */
   int saved_location_line =
-    expand_location_to_spelling_point (diagnostic_location (diagnostic)).line;
+    expand_location_to_spelling_point (original_loc).line;
 
   if (!LINEMAP_SYSP (ord_map))
     FOR_EACH_VEC_ELT (loc_vec, ix, iter)
@@ -238,8 +239,7 @@  void
 virt_loc_aware_diagnostic_finalizer (diagnostic_context *context,
 				     diagnostic_info *diagnostic)
 {
-  maybe_unwind_expanded_macro_loc (context, diagnostic,
-				   diagnostic_location (diagnostic));
+  maybe_unwind_expanded_macro_loc (context, diagnostic_location (diagnostic));
 }
 
 /* Default tree printer.   Handles declarations only.  */
@@ -312,4 +312,6 @@  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 = default_tree_diagnostic_path_printer;
+  context->make_json_for_path = default_tree_make_json_for_path;
 }
diff --git a/gcc/tree-diagnostic.h b/gcc/tree-diagnostic.h
index 3ce61c0..d97a973 100644
--- a/gcc/tree-diagnostic.h
+++ b/gcc/tree-diagnostic.h
@@ -57,4 +57,12 @@  void tree_diagnostics_defaults (diagnostic_context *context);
 bool default_tree_printer (pretty_printer *, text_info *, const char *,
 			   int, bool, bool, bool, bool *, const char **);
 
+extern void default_tree_diagnostic_path_printer (diagnostic_context *,
+						  const diagnostic_path *);
+extern json::value *default_tree_make_json_for_path (diagnostic_context *,
+						     const diagnostic_path *);
+
+extern void maybe_unwind_expanded_macro_loc (diagnostic_context *context,
+					     location_t where);
+
 #endif /* ! GCC_TREE_DIAGNOSTIC_H */
diff --git a/libcpp/include/line-map.h b/libcpp/include/line-map.h
index e4c7952..bd26fb7c 100644
--- a/libcpp/include/line-map.h
+++ b/libcpp/include/line-map.h
@@ -1459,6 +1459,7 @@  semi_embedded_vec<T, NUM_EMBEDDED>::truncate (int len)
 }
 
 class fixit_hint;
+class diagnostic_path;
 
 /* A "rich" source code location, for use when printing diagnostics.
    A rich_location has one or more carets&ranges, where the carets
@@ -1754,6 +1755,10 @@  class rich_location
     return !m_fixits_cannot_be_auto_applied;
   }
 
+  /* An optional path through the code.  */
+  const diagnostic_path *get_path () const { return m_path; }
+  void set_path (const diagnostic_path *path) { m_path = path; }
+
 private:
   bool reject_impossible_fixit (location_t where);
   void stop_supporting_fixits ();
@@ -1778,6 +1783,8 @@  protected:
 
   bool m_seen_impossible_fixit;
   bool m_fixits_cannot_be_auto_applied;
+
+  const diagnostic_path *m_path;
 };
 
 /* A struct for the result of range_label::get_text: a NUL-terminated buffer
diff --git a/libcpp/line-map.c b/libcpp/line-map.c
index feeb748..6f936e8 100644
--- a/libcpp/line-map.c
+++ b/libcpp/line-map.c
@@ -2006,7 +2006,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);
 }