From cad3605b594598743de110bbe6afdf4e6130265b Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Wed, 19 Jun 2019 16:32:09 -0400
Subject: [PATCH 024/108] FIXME: combination of path fixes; adding metadata,
 and interprocedural path visualization

---
 gcc/common.opt                         |  27 +
 gcc/diagnostic-core.h                  |  13 +-
 gcc/diagnostic-format-json.cc          |  24 +
 gcc/diagnostic-path.h                  |  90 +++-
 gcc/diagnostic.c                       | 203 ++++++--
 gcc/diagnostic.h                       |  28 +
 gcc/opts.c                             |  12 +
 gcc/selftest-run-tests.c               |   1 +
 gcc/selftest.h                         |   1 +
 gcc/testsuite/gcc.dg/plugin/plugin.exp |   7 +-
 gcc/toplev.c                           |   6 +
 gcc/tree-diagnostic.c                  | 917 ++++++++++++++++++++++++++++++---
 12 files changed, 1179 insertions(+), 150 deletions(-)

diff --git a/gcc/common.opt b/gcc/common.opt
index 42fe7ec..3782c24 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1314,6 +1314,33 @@ fdiagnostics-show-option
 Common Var(flag_diagnostics_show_option) Init(1)
 Amend appropriate diagnostic messages with the command line option that controls them.
 
+fdiagnostics-show-metadata
+Common Var(flag_diagnostics_show_metadata) Init(1)
+FIXME; TODO: add to docs
+
+fdiagnostics-path-summary-format=
+Common Joined RejectNegative Var(flag_diagnostics_path_summary_format) Enum(diagnostic_path_summary_format) Init(DPSF_INLINE_EVENTS)
+FIXME; TODO: docs
+
+Enum
+Name(diagnostic_path_summary_format) Type(int)
+
+EnumValue
+Enum(diagnostic_path_summary_format) String(none) Value(DPSF_NONE)
+
+EnumValue
+Enum(diagnostic_path_summary_format) String(simple) Value(DPSF_SIMPLE)
+
+EnumValue
+Enum(diagnostic_path_summary_format) String(event-summaries) Value(DPSF_EVENT_SUMMARIES)
+
+EnumValue
+Enum(diagnostic_path_summary_format) String(inline-events) Value(DPSF_INLINE_EVENTS)
+
+fdiagnostics-show-path-depths
+Common Var(flag_diagnostics_show_path_depths) Init(0)
+Show stack depths of events in paths. (TODO: add to docs)
+
 fdiagnostics-minimum-margin-width=
 Common Joined UInteger Var(diagnostics_minimum_margin_width) Init(6)
 Set minimum width of left margin of source code when showing source
diff --git a/gcc/diagnostic-core.h b/gcc/diagnostic-core.h
index aeb285f..2e7f120 100644
--- a/gcc/diagnostic-core.h
+++ b/gcc/diagnostic-core.h
@@ -45,6 +45,9 @@ class auto_diagnostic_group
   ~auto_diagnostic_group ();
 };
 
+/* Forward decl.  */
+class diagnostic_metadata; /* See diagnostic-metadata.h.  */
+
 extern const char *progname;
 
 extern const char *trim_filename (const char *);
@@ -78,6 +81,9 @@ extern bool warning_at (location_t, int, const char *, ...)
     ATTRIBUTE_GCC_DIAG(3,4);
 extern bool warning_at (rich_location *, int, const char *, ...)
     ATTRIBUTE_GCC_DIAG(3,4);
+extern bool warning_at (rich_location *, const diagnostic_metadata &, int,
+			const char *, ...)
+    ATTRIBUTE_GCC_DIAG(4,5);
 extern void error (const char *, ...) ATTRIBUTE_GCC_DIAG(1,2);
 extern void error_n (location_t, unsigned HOST_WIDE_INT, const char *,
 		     const char *, ...)
@@ -109,9 +115,10 @@ extern bool emit_diagnostic (diagnostic_t, rich_location *, int,
 			     const char *, ...) ATTRIBUTE_GCC_DIAG(4,5);
 extern bool emit_diagnostic_valist (diagnostic_t, location_t, int, const char *,
 				    va_list *) ATTRIBUTE_GCC_DIAG (4,0);
-extern bool emit_diagnostic_valist (diagnostic_t, rich_location *, int,
-				    const char *, va_list *)
-  ATTRIBUTE_GCC_DIAG (4,0);
+extern bool emit_diagnostic_valist (diagnostic_t, rich_location *,
+				    const diagnostic_metadata *metadata,
+				    int, const char *, va_list *)
+  ATTRIBUTE_GCC_DIAG (5,0);
 extern bool seen_error (void);
 
 #ifdef BUFSIZ
diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc
index e42386f..a8d7ebf 100644
--- a/gcc/diagnostic-format-json.cc
+++ b/gcc/diagnostic-format-json.cc
@@ -23,6 +23,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "system.h"
 #include "coretypes.h"
 #include "diagnostic.h"
+#include "diagnostic-metadata.h"
 #include "json.h"
 
 /* The top-level JSON array of pending diagnostics.  */
@@ -102,6 +103,20 @@ json_from_fixit_hint (const fixit_hint *hint)
   return fixit_obj;
 }
 
+/* FIXME.  */
+
+static json::object *
+json_from_metadata (const diagnostic_metadata *metadata)
+{
+  json::object *metadata_obj = new json::object ();
+
+  // TODO:
+  if (metadata->get_cwe ())
+    metadata_obj->set ("cwe", new json::number (metadata->get_cwe ()));
+
+  return metadata_obj;
+}
+
 /* No-op implementation of "begin_diagnostic" for JSON output.  */
 
 static void
@@ -217,6 +232,12 @@ json_end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic,
       json::value *path_value = context->make_json_for_path (context, path);
       diag_obj->set ("path", path_value);
     }
+
+  if (diagnostic->metadata)
+    {
+      json::object *metadata_obj = json_from_metadata (diagnostic->metadata);
+      diag_obj->set ("metadata", metadata_obj);
+    }
 }
 
 /* No-op implementation of "begin_group_cb" for JSON output.  */
@@ -275,6 +296,9 @@ diagnostic_output_format_init (diagnostic_context *context,
 	context->final_cb =  json_final_cb;
 	context->print_path = NULL; /* handled in json_end_diagnostic.  */
 
+	/* The metadata is handled in JSON format, rather than as text.  */
+	context->show_metadata = false;
+
 	/* The option is handled in JSON format, rather than as text.  */
 	context->show_option_requested = false;
 
diff --git a/gcc/diagnostic-path.h b/gcc/diagnostic-path.h
index cde0ea5..b337feb 100644
--- a/gcc/diagnostic-path.h
+++ b/gcc/diagnostic-path.h
@@ -55,36 +55,86 @@ along with GCC; see the file COPYING3.  If not see
     function; more complicated examples (such as interprocedural paths)
     might be printed as multiple notes.  */
 
-struct diagnostic_path
+/* Abstract base classes, describing events within a path, and the paths
+   themselves.  */
+
+/* One event within a diagnostic_path.  */
+
+class diagnostic_event
 {
-  /* One event within the path.  */
-  struct event
-  {
-    event (location_t loc, tree fndecl, int depth, const char *desc);
-    ~event ();
+ public:
+  virtual ~diagnostic_event () {}
+
+  virtual location_t get_location () const = 0;
+
+  virtual tree get_fndecl () const = 0;
 
-    location_t m_loc;
-    /* TODO: generalize this to a "backtrace", so that consumers can
-       decipher interprocedural sequences of events:  */
-    tree m_fndecl;
+  /* Stack depth, so that consumers can visualizes the interprocedural
+     calls, returns, and frame nesting.  */
+  virtual int get_stack_depth () const = 0;
 
-    /* Stack depth, so that consumers can visualizes the interprocedural
-       calls, returns, and frame nesting.  */
-    int m_depth;
+  /* Get a localized (and possibly colorized) description of this event.  */
+  virtual label_text get_desc () const = 0;
+};
+
+/* Abstract base class for getting at a sequence of events.  */
 
-    char *m_desc; // has been i18n-ed and formatted
-  };
+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;
+};
 
-  diagnostic_path ();
-  diagnostic_path (const diagnostic_path &other);
-  diagnostic_path& operator= (const diagnostic_path &other);
+/* Concrete subclasses.  */
 
-  ~diagnostic_path ();
+/* 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 () const FINAL OVERRIDE
+  {
+    return label_text::borrow (m_desc);
+  }
+
+ private:
+  location_t m_loc;
+  tree m_fndecl;
+  int m_depth;
+  char *m_desc; // has been i18n-ed and formatted
+};
+
+/* A simple implementation of diagnostic_path, as a vector of
+   simple_diagnostic_event instances.  */
+
+class simple_diagnostic_path : public diagnostic_path
+{
+ public:
+  simple_diagnostic_path (pretty_printer *event_pp)
+  : m_event_pp (event_pp) {}
+
+  unsigned num_events () const FINAL OVERRIDE;
+  const diagnostic_event & get_event (int idx) const FINAL OVERRIDE;
 
   void add_event (location_t loc, tree fndecl, int depth, const char *fmt, ...)
     ATTRIBUTE_GCC_DIAG(5,6);
 
-  auto_vec<event *> m_events;
+ private:
+  auto_delete_vec<simple_diagnostic_event> m_events;
+
+  /* (for use by add_event).  */
+  pretty_printer *m_event_pp;
 };
 
 // FIXME:
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index 45743dc..8fc0efb 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -31,6 +31,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "backtrace.h"
 #include "diagnostic.h"
 #include "diagnostic-color.h"
+#include "diagnostic-metadata.h"
 #include "diagnostic-path.h"
 #include "edit-context.h"
 #include "selftest.h"
@@ -50,11 +51,13 @@ along with GCC; see the file COPYING3.  If not see
 #define permissive_error_option(DC) ((DC)->opt_permissive)
 
 /* Prototypes.  */
-static bool diagnostic_impl (rich_location *, int, const char *,
-			     va_list *, diagnostic_t) ATTRIBUTE_GCC_DIAG(3,0);
-static bool diagnostic_n_impl (rich_location *, int, unsigned HOST_WIDE_INT,
+static bool diagnostic_impl (rich_location *, const diagnostic_metadata *,
+			     int, const char *,
+			     va_list *, diagnostic_t) ATTRIBUTE_GCC_DIAG(4,0);
+static bool diagnostic_n_impl (rich_location *, const diagnostic_metadata *,
+			       int, unsigned HOST_WIDE_INT,
 			       const char *, const char *, va_list *,
-			       diagnostic_t) ATTRIBUTE_GCC_DIAG(5,0);
+			       diagnostic_t) ATTRIBUTE_GCC_DIAG(6,0);
 
 static void error_recursion (diagnostic_context *) ATTRIBUTE_NORETURN;
 static void real_abort (void) ATTRIBUTE_NORETURN;
@@ -175,6 +178,9 @@ diagnostic_initialize (diagnostic_context *context, int n_opts)
   diagnostic_set_caret_max_width (context, pp_line_cutoff (context->printer));
   for (i = 0; i < rich_location::STATICALLY_ALLOCATED_RANGES; i++)
     context->caret_chars[i] = '^';
+  context->show_metadata = false;
+  context->path_summary_format = DPSF_NONE;
+  context->show_path_depths = false;
   context->show_option_requested = false;
   context->abort_on_error = false;
   context->show_column = false;
@@ -291,6 +297,7 @@ diagnostic_set_info_translated (diagnostic_info *diagnostic, const char *msg,
   diagnostic->message.format_spec = msg;
   diagnostic->message.m_richloc = richloc;
   diagnostic->richloc = richloc;
+  diagnostic->metadata = NULL;
   diagnostic->kind = kind;
   diagnostic->option_index = 0;
 }
@@ -659,6 +666,22 @@ diagnostic_show_any_path (diagnostic_context *context,
     context->print_path (context, path);
 }
 
+/* FIXME.  */
+
+bool
+diagnostic_path::interprocedural_p () const
+{
+  const unsigned num = num_events ();
+  for (unsigned i = 0; i < num; i++)
+    {
+      if (get_event (i).get_fndecl () != get_event (0).get_fndecl ())
+	return true;
+      if (get_event (i).get_stack_depth () != get_event (0).get_stack_depth ())
+	return true;
+    }
+  return false;
+}
+
 void
 default_diagnostic_starter (diagnostic_context *context,
 			    diagnostic_info *diagnostic)
@@ -897,6 +920,43 @@ update_effective_level_from_pragmas (diagnostic_context *context,
   return diag_class;
 }
 
+/* FIXME.  */
+
+static char *
+get_cwe_url (int cwe)
+{
+  return xasprintf ("https://cwe.mitre.org/data/definitions/%i.html", cwe);
+}
+
+/* FIXME.  */
+
+static void
+print_any_metadata (diagnostic_context *context,
+		    const diagnostic_info *diagnostic)
+{
+  if (diagnostic->metadata == NULL)
+    return;
+
+  // FIXME:
+  int cwe = diagnostic->metadata->get_cwe ();
+  if (cwe)
+    {
+      pretty_printer *pp = context->printer;
+      char *saved_prefix = pp_take_prefix (context->printer);
+      pp_string (pp, " [");
+      pp_string (pp, colorize_start (pp_show_color (pp),
+				     diagnostic_kind_color[diagnostic->kind]));
+      char *cwe_url = get_cwe_url (cwe);
+      pp_begin_url (pp, cwe_url);
+      free (cwe_url);
+      pp_printf (pp, "CWE-%i", cwe);
+      pp_set_prefix (context->printer, saved_prefix);
+      pp_end_url (pp);
+      pp_string (pp, colorize_stop (pp_show_color (pp)));
+      pp_character (pp, ']');
+    }
+}
+
 /* Print any metadata about the option used to control DIAGNOSTIC to CONTEXT's
    printer, e.g. " [-Werror=uninitialized]".
    Subroutine of diagnostic_report_diagnostic.  */
@@ -1056,6 +1116,8 @@ diagnostic_report_diagnostic (diagnostic_context *context,
   pp_format (context->printer, &diagnostic->message);
   (*diagnostic_starter (context)) (context, diagnostic);
   pp_output_formatted_text (context->printer);
+  if (context->show_metadata)
+    print_any_metadata (context, diagnostic);
   if (context->show_option_requested)
     print_option_information (context, diagnostic, orig_diag_kind);
   (*diagnostic_finalizer (context)) (context, diagnostic, orig_diag_kind);
@@ -1182,8 +1244,8 @@ diagnostic_append_note (diagnostic_context *context,
    permerror, error, error_at, error_at, sorry, fatal_error, internal_error,
    and internal_error_no_backtrace, as documented and defined below.  */
 static bool
-diagnostic_impl (rich_location *richloc, int opt,
-		 const char *gmsgid,
+diagnostic_impl (rich_location *richloc, const diagnostic_metadata *metadata,
+		 int opt, const char *gmsgid,
 		 va_list *ap, diagnostic_t kind)
 {
   diagnostic_info diagnostic;
@@ -1199,13 +1261,15 @@ diagnostic_impl (rich_location *richloc, int opt,
       if (kind == DK_WARNING || kind == DK_PEDWARN)
 	diagnostic.option_index = opt;
     }
+  diagnostic.metadata = metadata;
   return diagnostic_report_diagnostic (global_dc, &diagnostic);
 }
 
 /* Implement inform_n, warning_n, and error_n, as documented and
    defined below.  */
 static bool
-diagnostic_n_impl (rich_location *richloc, int opt, unsigned HOST_WIDE_INT n,
+diagnostic_n_impl (rich_location *richloc, const diagnostic_metadata *metadata,
+		   int opt, unsigned HOST_WIDE_INT n,
 		   const char *singular_gmsgid,
 		   const char *plural_gmsgid,
 		   va_list *ap, diagnostic_t kind)
@@ -1225,6 +1289,7 @@ diagnostic_n_impl (rich_location *richloc, int opt, unsigned HOST_WIDE_INT n,
   diagnostic_set_info_translated (&diagnostic, text, ap, richloc, kind);
   if (kind == DK_WARNING)
     diagnostic.option_index = opt;
+  diagnostic.metadata = metadata;
   return diagnostic_report_diagnostic (global_dc, &diagnostic);
 }
 
@@ -1238,7 +1303,7 @@ emit_diagnostic (diagnostic_t kind, location_t location, int opt,
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, kind);
+  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, kind);
   va_end (ap);
   return ret;
 }
@@ -1252,7 +1317,7 @@ emit_diagnostic (diagnostic_t kind, rich_location *richloc, int opt,
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap, kind);
+  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap, kind);
   va_end (ap);
   return ret;
 }
@@ -1264,16 +1329,18 @@ emit_diagnostic_valist (diagnostic_t kind, location_t location, int opt,
 			const char *gmsgid, va_list *ap)
 {
   rich_location richloc (line_table, location);
-  return diagnostic_impl (&richloc, opt, gmsgid, ap, kind);
+  return diagnostic_impl (&richloc, NULL, opt, gmsgid, ap, kind);
 }
 
 /* Wrapper around diagnostic_impl taking a va_list parameter.  */
 
 bool
-emit_diagnostic_valist (diagnostic_t kind, rich_location *richloc, int opt,
+emit_diagnostic_valist (diagnostic_t kind, rich_location *richloc,
+			const diagnostic_metadata *metadata,
+			int opt,
 			const char *gmsgid, va_list *ap)
 {
-  return diagnostic_impl (richloc, opt, gmsgid, ap, kind);
+  return diagnostic_impl (richloc, metadata, opt, gmsgid, ap, kind);
 }
 
 /* An informative note at LOCATION.  Use this for additional details on an error
@@ -1285,7 +1352,7 @@ inform (location_t location, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_NOTE);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_NOTE);
   va_end (ap);
 }
 
@@ -1298,7 +1365,7 @@ inform (rich_location *richloc, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  diagnostic_impl (richloc, -1, gmsgid, &ap, DK_NOTE);
+  diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_NOTE);
   va_end (ap);
 }
 
@@ -1312,7 +1379,7 @@ inform_n (location_t location, unsigned HOST_WIDE_INT n,
   va_start (ap, plural_gmsgid);
   auto_diagnostic_group d;
   rich_location richloc (line_table, location);
-  diagnostic_n_impl (&richloc, -1, n, singular_gmsgid, plural_gmsgid,
+  diagnostic_n_impl (&richloc, NULL, -1, n, singular_gmsgid, plural_gmsgid,
 		     &ap, DK_NOTE);
   va_end (ap);
 }
@@ -1327,7 +1394,7 @@ warning (int opt, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, DK_WARNING);
+  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, DK_WARNING);
   va_end (ap);
   return ret;
 }
@@ -1343,7 +1410,7 @@ warning_at (location_t location, int opt, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, DK_WARNING);
+  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, DK_WARNING);
   va_end (ap);
   return ret;
 }
@@ -1358,7 +1425,25 @@ warning_at (rich_location *richloc, int opt, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap, DK_WARNING);
+  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap, DK_WARNING);
+  va_end (ap);
+  return ret;
+}
+
+/* FIXME.  */
+
+bool
+warning_at (rich_location *richloc, const diagnostic_metadata &metadata,
+	    int opt, const char *gmsgid, ...)
+{
+  gcc_assert (richloc);
+
+  auto_diagnostic_group d;
+  va_list ap;
+  va_start (ap, gmsgid);
+  bool ret
+    = diagnostic_impl (richloc, &metadata, opt, gmsgid, &ap,
+		       DK_WARNING);
   va_end (ap);
   return ret;
 }
@@ -1374,7 +1459,7 @@ warning_n (rich_location *richloc, int opt, unsigned HOST_WIDE_INT n,
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, plural_gmsgid);
-  bool ret = diagnostic_n_impl (richloc, opt, n,
+  bool ret = diagnostic_n_impl (richloc, NULL, opt, n,
 				singular_gmsgid, plural_gmsgid,
 				&ap, DK_WARNING);
   va_end (ap);
@@ -1393,7 +1478,7 @@ warning_n (location_t location, int opt, unsigned HOST_WIDE_INT n,
   va_list ap;
   va_start (ap, plural_gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_n_impl (&richloc, opt, n,
+  bool ret = diagnostic_n_impl (&richloc, NULL, opt, n,
 				singular_gmsgid, plural_gmsgid,
 				&ap, DK_WARNING);
   va_end (ap);
@@ -1420,7 +1505,7 @@ pedwarn (location_t location, int opt, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_impl (&richloc, opt, gmsgid, &ap, DK_PEDWARN);
+  bool ret = diagnostic_impl (&richloc, NULL, opt, gmsgid, &ap, DK_PEDWARN);
   va_end (ap);
   return ret;
 }
@@ -1435,7 +1520,7 @@ pedwarn (rich_location *richloc, int opt, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  bool ret = diagnostic_impl (richloc, opt, gmsgid, &ap, DK_PEDWARN);
+  bool ret = diagnostic_impl (richloc, NULL, opt, gmsgid, &ap, DK_PEDWARN);
   va_end (ap);
   return ret;
 }
@@ -1454,7 +1539,7 @@ permerror (location_t location, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, location);
-  bool ret = diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_PERMERROR);
+  bool ret = diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_PERMERROR);
   va_end (ap);
   return ret;
 }
@@ -1469,7 +1554,7 @@ permerror (rich_location *richloc, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  bool ret = diagnostic_impl (richloc, -1, gmsgid, &ap, DK_PERMERROR);
+  bool ret = diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_PERMERROR);
   va_end (ap);
   return ret;
 }
@@ -1483,7 +1568,7 @@ error (const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ERROR);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
   va_end (ap);
 }
 
@@ -1497,7 +1582,7 @@ error_n (location_t location, unsigned HOST_WIDE_INT n,
   va_list ap;
   va_start (ap, plural_gmsgid);
   rich_location richloc (line_table, location);
-  diagnostic_n_impl (&richloc, -1, n, singular_gmsgid, plural_gmsgid,
+  diagnostic_n_impl (&richloc, NULL, -1, n, singular_gmsgid, plural_gmsgid,
 		     &ap, DK_ERROR);
   va_end (ap);
 }
@@ -1510,7 +1595,7 @@ error_at (location_t loc, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, loc);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ERROR);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
   va_end (ap);
 }
 
@@ -1524,7 +1609,7 @@ error_at (rich_location *richloc, const char *gmsgid, ...)
   auto_diagnostic_group d;
   va_list ap;
   va_start (ap, gmsgid);
-  diagnostic_impl (richloc, -1, gmsgid, &ap, DK_ERROR);
+  diagnostic_impl (richloc, NULL, -1, gmsgid, &ap, DK_ERROR);
   va_end (ap);
 }
 
@@ -1538,7 +1623,7 @@ sorry (const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_SORRY);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_SORRY);
   va_end (ap);
 }
 
@@ -1550,7 +1635,7 @@ sorry_at (location_t loc, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, loc);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_SORRY);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_SORRY);
   va_end (ap);
 }
 
@@ -1572,7 +1657,7 @@ fatal_error (location_t loc, const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, loc);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_FATAL);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_FATAL);
   va_end (ap);
 
   gcc_unreachable ();
@@ -1589,7 +1674,7 @@ internal_error (const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ICE);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ICE);
   va_end (ap);
 
   gcc_unreachable ();
@@ -1605,7 +1690,7 @@ internal_error_no_backtrace (const char *gmsgid, ...)
   va_list ap;
   va_start (ap, gmsgid);
   rich_location richloc (line_table, input_location);
-  diagnostic_impl (&richloc, -1, gmsgid, &ap, DK_ICE_NOBT);
+  diagnostic_impl (&richloc, NULL, -1, gmsgid, &ap, DK_ICE_NOBT);
   va_end (ap);
 
   gcc_unreachable ();
@@ -1686,6 +1771,7 @@ auto_diagnostic_group::~auto_diagnostic_group ()
     }
 }
 
+#if 0
 /* struct diagnostic_path */
 
 diagnostic_path::diagnostic_path () : m_events ()
@@ -1723,19 +1809,33 @@ diagnostic_path::~diagnostic_path ()
     delete n;
 }
 
+#endif
+
+/* FIXME.  */
+
+unsigned
+simple_diagnostic_path::num_events () const
+{
+  return m_events.length ();
+}
+
+/* FIXME.  */
+
+const diagnostic_event &
+simple_diagnostic_path::get_event (int idx) const
+{
+  return *m_events[idx];
+}
+
 /* FIXME.  */
+// Use m_context's printer to format the events.
 
 void
-diagnostic_path::add_event (location_t loc, tree fndecl, int depth, const char *fmt, ...)
+simple_diagnostic_path::add_event (location_t loc, tree fndecl, int depth,
+				   const char *fmt, ...)
 {
-  /* FIXME: use global_dc's pp.  */
-#if 1
-  pretty_printer *pp = global_dc->printer;
+  pretty_printer *pp = m_event_pp;
   pp_clear_output_area (pp);
-#else
-  pretty_printer tmp_pp;
-  pretty_printer *pp = &tmp_pp;
-#endif
 
   text_info ti;
   rich_location rich_loc (line_table, UNKNOWN_LOCATION);
@@ -1750,34 +1850,29 @@ diagnostic_path::add_event (location_t loc, tree fndecl, int depth, const char *
   ti.x_data = NULL;
   ti.m_richloc = &rich_loc;
 
-#if 1
-#else
-  pp_show_color (pp) = pp_show_color (global_dc->printer);
-  pp_format_decoder (pp) = pp_format_decoder (global_dc->printer);
-#endif
   pp_format (pp, &ti);
   pp_output_formatted_text (pp);
 
   va_end (ap);
 
-  event *new_event = new event (loc, fndecl, depth, pp_formatted_text (pp));
+  simple_diagnostic_event *new_event
+    = new simple_diagnostic_event (loc, fndecl, depth, pp_formatted_text (pp));
   m_events.safe_push (new_event);
-#if 0  
-  fprintf (stderr, "DBG: added to path %p: %s\n", this, new_event->m_desc);
-#endif
 
   pp_clear_output_area (pp);
 }
 
-/* struct diagnostic_path::event.  */
+/* struct simple_diagnostic_event.  */
 
-diagnostic_path::event::event (location_t loc, tree fndecl, int depth,
-			       const char *desc)
+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))
 {
 }
 
-diagnostic_path::event::~event ()
+simple_diagnostic_event::~simple_diagnostic_event ()
 {
   free (m_desc);
 }
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index cb4a1cf..2951177 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -35,6 +35,22 @@ enum diagnostics_output_format
   DIAGNOSTICS_OUTPUT_FORMAT_JSON
 };
 
+/* FIXME.  */
+enum diagnostic_path_summary_format
+{
+  /* FIXME.  */
+  DPSF_NONE,
+
+  /* FIXME.  */
+  DPSF_SIMPLE,
+
+  /* FIXME.  */
+  DPSF_EVENT_SUMMARIES,
+
+  /* FIXME.  */
+  DPSF_INLINE_EVENTS
+};
+
 /* A diagnostic is described by the MESSAGE to send, the FILE and LINE of
    its context and its KIND (ice, error, warning, note, ...)  See complete
    list in diagnostic.def.  */
@@ -46,6 +62,9 @@ struct diagnostic_info
   /* The location at which the diagnostic is to be reported.  */
   rich_location *richloc;
 
+  /* FIXME.  */
+  const diagnostic_metadata *metadata;
+
   /* Auxiliary data for client.  */
   void *x_data;
   /* The kind of diagnostic it is about.  */
@@ -127,6 +146,15 @@ struct diagnostic_context
   /* Character used for caret diagnostics.  */
   char caret_chars[rich_location::STATICALLY_ALLOCATED_RANGES];
 
+  /* FIXME.  */
+  bool show_metadata;
+
+  /* FIXME.  */
+  enum diagnostic_path_summary_format path_summary_format;
+
+  /* FIXME.  */
+  bool show_path_depths;
+
   /* True if we should print the command line option which controls
      each diagnostic, if known.  */
   bool show_option_requested;
diff --git a/gcc/opts.c b/gcc/opts.c
index f0a96b9..cb962e4 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -2400,6 +2400,18 @@ common_handle_option (struct gcc_options *opts,
       dc->parseable_fixits_p = value;
       break;
 
+    case OPT_fdiagnostics_show_metadata:
+      dc->show_metadata = value;
+      break;
+
+    case OPT_fdiagnostics_path_summary_format_:
+      dc->path_summary_format = (enum diagnostic_path_summary_format)value;
+      break;
+
+    case OPT_fdiagnostics_show_path_depths:
+      dc->show_path_depths = value;
+      break;
+
     case OPT_fdiagnostics_show_option:
       dc->show_option_requested = value;
       break;
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index 184dfa6..3fb31ab 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -96,6 +96,7 @@ selftest::run_tests ()
   spellcheck_c_tests ();
   spellcheck_tree_c_tests ();
   tree_cfg_c_tests ();
+  tree_diagnostic_c_tests ();
   attribute_c_tests ();
 
   /* This one relies on most of the above.  */
diff --git a/gcc/selftest.h b/gcc/selftest.h
index 3e00e75..88dc951 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -246,6 +246,7 @@ extern void sreal_c_tests ();
 extern void store_merging_c_tests ();
 extern void tree_c_tests ();
 extern void tree_cfg_c_tests ();
+extern void tree_diagnostic_c_tests ();
 extern void typed_splay_tree_c_tests ();
 extern void unique_ptr_tests_cc_tests ();
 extern void vec_c_tests ();
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index 635a387..60065e6 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -59,7 +59,12 @@ set plugin_test_list [list \
     { diagnostic_plugin_test_paths.c \
 	  diagnostic-test-paths-1.c \
 	  diagnostic-test-paths-2.c \
-	  diagnostic-test-paths-3.c } \
+	  diagnostic-test-paths-3.c \
+	  diagnostic-path-summary-format-default.c \
+	  diagnostic-path-summary-format-none.c \
+	  diagnostic-path-summary-format-simple.c \
+	  diagnostic-path-summary-format-event-summaries.c \
+	  diagnostic-path-summary-format-inline-events.c } \
 ]
 
 foreach plugin_test $plugin_test_list {
diff --git a/gcc/toplev.c b/gcc/toplev.c
index 6b3bbdc..55d4bdb 100644
--- a/gcc/toplev.c
+++ b/gcc/toplev.c
@@ -1119,6 +1119,12 @@ general_init (const char *argv0, bool init_signals)
     = global_options_init.x_flag_diagnostics_show_labels;
   global_dc->show_line_numbers_p
     = global_options_init.x_flag_diagnostics_show_line_numbers;
+  global_dc->show_metadata
+    = global_options_init.x_flag_diagnostics_show_metadata;
+  global_dc->path_summary_format
+    = (enum diagnostic_path_summary_format)global_options_init.x_flag_diagnostics_path_summary_format;
+  global_dc->show_path_depths
+    = global_options_init.x_flag_diagnostics_show_path_depths;
   global_dc->show_option_requested
     = global_options_init.x_flag_diagnostics_show_option;
   global_dc->min_margin_width
diff --git a/gcc/tree-diagnostic.c b/gcc/tree-diagnostic.c
index 162b31f..91a3881 100644
--- a/gcc/tree-diagnostic.c
+++ b/gcc/tree-diagnostic.c
@@ -32,6 +32,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-path.h"
 #include "json.h"
 #include "gcc-rich-location.h"
+#include "diagnostic-color.h"
+#include "selftest.h"
 
 /* Prints out, if necessary, the name of the current function
    that caused an error.  Called from all error and warning functions.  */
@@ -322,10 +324,14 @@ class path_label : public range_label
   label_text get_text (unsigned range_idx) const FINAL OVERRIDE
   {
     unsigned event_idx = m_start_idx + range_idx;
-    const diagnostic_path::event *event = m_path->m_events[event_idx];
-
-    return label_text (xasprintf ("(%i) %s", event_idx + 1, event->m_desc),
-		       true);
+    const diagnostic_event &event = m_path->get_event (event_idx);
+
+    label_text event_text (event.get_desc ());
+    label_text result
+      = label_text::take (xasprintf ("(%i) %s", event_idx + 1,
+				     event_text.m_buffer));
+    event_text.maybe_free ();
+    return result;
   }
 
  private:
@@ -333,27 +339,35 @@ class path_label : public range_label
   unsigned m_start_idx;
 };
 
+static void
+print_event_range (pretty_printer *pp,
+		   const diagnostic_path *path,
+		   unsigned start_idx, unsigned end_idx,
+		   bool use_diagnostic);
+
+/* FIXME.  */
+
 static bool
-can_consolidate_events (diagnostic_path::event *e1,
-			diagnostic_path::event *e2)
+can_consolidate_events (const diagnostic_event &e1,
+			const diagnostic_event &e2)
 {
   // FIXME: might want to simply group them by function, and also
   // split whenever there's a backward jump?
-  if (e1->m_fndecl != e2->m_fndecl)
+  if (e1.get_fndecl () != e2.get_fndecl ())
     return false;
 
 #if 0
   // FIXME: do we need this?
-  if (e1->m_depth != e2->m_depth)
+  if (e1.m_depth != e2.m_depth)
     return false;
 #endif
 
   /* Neither can be macro-based.  */
   if (linemap_location_from_macro_expansion_p (line_table,
-					       e1->m_loc))
+					       e1.get_location ()))
     return false;
   if (linemap_location_from_macro_expansion_p (line_table,
-					       e2->m_loc))
+					       e2.get_location ()))
     return false;
 
   // TODO:
@@ -363,81 +377,439 @@ can_consolidate_events (diagnostic_path::event *e1,
   return true;
 }
 
+/* A class for grouing together the events in a diagnostic_path into
+   ranges of events, partitioned by stack frame (i.e. by fndecl and
+   stack depth).
+
+   For use by print_path_summary.  */
+
+struct interprocedural_path_summary
+{
+  /* A range of consecutive events within a diagnostic_path,
+     all with the same fndecl and stack_depth.  */
+  struct event_range
+  {
+    event_range (tree fndecl, int stack_depth, int start_idx, int end_idx)
+    : m_fndecl (fndecl), m_stack_depth (stack_depth),
+      m_start_idx (start_idx), m_end_idx (end_idx)
+    {}
+    tree m_fndecl;
+    int m_stack_depth;
+    int m_start_idx;
+    int m_end_idx;
+  };
+  interprocedural_path_summary (const diagnostic_path &path);
+
+  auto_vec <event_range> m_ranges;
+};
+
+/* interprocedural_path_summary's ctor.  */
+
+interprocedural_path_summary::
+interprocedural_path_summary (const diagnostic_path &path)
+{
+  const unsigned num_events = path.num_events ();
+
+  tree cur_fndecl = NULL_TREE;
+  int cur_stack_depth = 0;
+  unsigned cur_start_idx = 0;
+  bool in_range = false;
+  for (unsigned idx = 0; idx < num_events; idx++)
+    {
+      tree ev_fndecl = path.get_event (idx).get_fndecl ();
+      int ev_stack_depth = path.get_event (idx).get_stack_depth ();
+      if (ev_fndecl != cur_fndecl
+	  || ev_stack_depth != cur_stack_depth)
+	{
+	  if (in_range)
+	    m_ranges.safe_push (event_range (cur_fndecl, cur_stack_depth,
+					     cur_start_idx, idx - 1));
+	  in_range = true;
+	  cur_start_idx = idx;
+	}
+      cur_fndecl = ev_fndecl;
+      cur_stack_depth = ev_stack_depth;
+    }
+  if (in_range)
+    {
+      m_ranges.safe_push (event_range (cur_fndecl, cur_stack_depth,
+				       cur_start_idx, num_events - 1));
+    }
+}
+
 /* FIXME.  */
-// This needs to be in tree-diagnostic.c so we can hack up current_function_decl,
-// which is used by lhd_print_error_function etc.
 
-void
-tree_print_diagnostic_path (diagnostic_context *context,
-			    const diagnostic_path *path)
+static void
+write_indent (pretty_printer *pp, int spaces)
 {
+  for (int i = 0; i < spaces; i++)
+    pp_space (pp);
+}
 
-  /* TODO: options for different printing approaches.  */
-  if (context->show_caret && context->show_line_numbers_p)
+/* FIXME.  */
+/* We can't use "%qE" here since we can't guarantee the
+   capabilities of PP; -Wformat will complain.  */
+
+static void
+print_fndecl (pretty_printer *pp, tree fndecl, bool quoted)
+{
+  const char *n = DECL_NAME (fndecl)
+    ? identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2))
+    : _("<anonymous>");
+  if (quoted)
+    pp_printf (pp, "%qs", n);
+  else
+    pp_string (pp, n);
+}
+
+/* Print SUMMARY to PP, giving an overview of the interprocedural
+   calls and returns.
+
+   For example:
+
+   'foo' (events 1-2)
+     |
+     +--> 'bar' (events 3-4)
+            |
+            +--> 'baz' (events 5-6)
+                   |
+     <------------ +
+     |
+   'foo' (events 7-8)
+     |
+     +--> 'bar' (events 9-10)
+            |
+            +--> 'baz' (events 11-12).  */
+
+static void
+print_path_summary (pretty_printer *pp,
+		    const interprocedural_path_summary& summary,
+		    const diagnostic_path *path, bool show_depths,
+		    enum diagnostic_path_summary_format summary_format)
+{
+  auto_vec<int> name_lengths;
+
+  /* Populate name_lengths.  */
+  {
+    pretty_printer *tmp_pp = pp->clone ();
+    pp_show_color (tmp_pp) = 0;
+    int i;
+    interprocedural_path_summary::event_range *range;
+    FOR_EACH_VEC_ELT (summary.m_ranges, i, range)
+      {
+	print_fndecl (tmp_pp, range->m_fndecl, false);
+	/* We want the length of the quoted decl in characters, not in bytes.
+	   For now, we get the length of the unquoted decl (in bytes), hope
+	   it's ASCII, and add 2 for the quotes.  */
+	name_lengths.safe_push (strlen (pp_formatted_text (tmp_pp)) + 2);
+	pp_clear_output_area (tmp_pp);
+      }
+    delete tmp_pp;
+  }
+
+  /* FIXME: do we want this to have its own name?  */
+  const char *const line_color = "note";
+  const char *start_line_color
+    = colorize_start (pp_show_color (pp), line_color);
+  const char *end_line_color = colorize_stop (pp_show_color (pp));
+
+  /* Keep track of column numbers of existing '|' characters for
+     stack depths we've already printed.  */
+  const int EMPTY = -1;
+  const int DELETED = -2;
+  typedef int_hash <int, EMPTY, DELETED> vbar_hash;
+  hash_map <vbar_hash, int> vbar_column_for_depth;
+
+  /* Print the ranges.  */
+  const int base_indent = 2;
+  int cur_indent = base_indent;
+  unsigned i;
+  interprocedural_path_summary::event_range *range;
+  FOR_EACH_VEC_ELT (summary.m_ranges, i, range)
     {
-      /* Try consolidating events into rich locations.  */
-      unsigned start_idx = 0;
-      while (start_idx < path->m_events.length ())
+      write_indent (pp, cur_indent);
+      if (i > 0)
+	{
+	  const interprocedural_path_summary::event_range *prev_range
+	    = &summary.m_ranges[i - 1];
+	  if (range->m_stack_depth > prev_range->m_stack_depth)
+	    {
+	      /* Show pushed stack frame(s).  */
+	      const char *push_prefix = "+--> ";
+	      pp_string (pp, start_line_color);
+	      pp_string (pp, push_prefix);
+	      pp_string (pp, end_line_color);
+	      cur_indent += strlen (push_prefix);
+	    }
+	}
+      print_fndecl (pp, range->m_fndecl, true);
+      if (range->m_start_idx == range->m_end_idx)
+	pp_printf (pp, ": event %i",
+		   range->m_start_idx + 1);
+      else
+	pp_printf (pp, ": events %i-%i",
+		   range->m_start_idx + 1, range->m_end_idx + 1);
+      if (show_depths)
+	pp_printf (pp, " (depth %i)", range->m_stack_depth);
+      pp_newline (pp);
+
+      int midpoint = name_lengths[i] / 2;
+
+      switch (summary_format)
 	{
-	  path_label label (path, start_idx);
-	  diagnostic_path::event *event = path->m_events[start_idx];
-	  gcc_rich_location richloc (event->m_loc);
-	  unsigned j;
-	  for (j = start_idx + 1; j < path->m_events.length (); j++)
+	default:
+	case DPSF_NONE:
+	  gcc_unreachable ();
+
+	case DPSF_SIMPLE:
+	  break;
+	case DPSF_EVENT_SUMMARIES:
+	  {
+	    for (int event_idx = range->m_start_idx;
+		 event_idx <= range->m_end_idx; event_idx++)
+	      {
+		write_indent (pp, cur_indent + midpoint);
+		label_text event_desc = path->get_event (event_idx).get_desc ();
+		pp_string (pp, start_line_color);
+		pp_string (pp, "|");
+		pp_string (pp, end_line_color);
+		pp_printf (pp, " (%i): %s",
+			   event_idx + 1, event_desc.m_buffer);
+		event_desc.maybe_free ();
+		pp_newline (pp);
+	      }
+	  }
+	  break;
+	case DPSF_INLINE_EVENTS:
+	  {
+	    write_indent (pp, cur_indent + midpoint);
+	    pp_string (pp, start_line_color);
+	    pp_string (pp, "|");
+	    pp_string (pp, end_line_color);
+	    /* No newline here, as diagnostic_show_locus adds a leading
+	       newline.  */
+
+	    char *saved_prefix = pp_take_prefix (pp);
+	    char *prefix;
+	    {
+	      pretty_printer tmp_pp;
+	      write_indent (&tmp_pp, cur_indent + midpoint);
+	      pp_string (&tmp_pp, start_line_color);
+	      pp_string (&tmp_pp, "|");
+	      pp_string (&tmp_pp, end_line_color);
+	      prefix = xstrdup (pp_formatted_text (&tmp_pp));
+	    }
+	    pp_set_prefix (pp, prefix);
+	    pp_prefixing_rule (pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+	    print_event_range (pp, path,
+			       range->m_start_idx, range->m_end_idx,
+			       false);
+	    pp_set_prefix (pp, saved_prefix);
+
+	    write_indent (pp, cur_indent + midpoint);
+	    pp_string (pp, start_line_color);
+	    pp_string (pp, "|");
+	    pp_string (pp, end_line_color);
+	    pp_newline (pp);
+	  }
+	  break;
+	}
+
+      if (i < summary.m_ranges.length () - 1)
+	{
+	  const interprocedural_path_summary::event_range *next_range
+	    = &summary.m_ranges[i + 1];
+
+	  if (summary_format == DPSF_SIMPLE)
 	    {
-	      diagnostic_path::event *other = path->m_events[j];
-	      // TODO: label, and slacken the constraint
-	      if (can_consolidate_events (event, other))
-		if (richloc.add_location_if_nearby (other->m_loc, false,
-						    &label))
-		  continue;
-	      break;
+	      write_indent (pp, cur_indent + midpoint);
+	      pp_string (pp, start_line_color);
+	      pp_printf (pp, "|");
+	      pp_string (pp, end_line_color);
+	      pp_newline (pp);
 	    }
 
+	  if (range->m_stack_depth > next_range->m_stack_depth)
+	    {
+	      /* Show returning from stack frame(s), by printing
+		 something like:
+		 "                   |\n"
+		 "     <------------ +\n"
+		 "     |\n".  */
+
+	      int next_midpoint = name_lengths[i + 1] / 2;
+
+	      gcc_assert (vbar_column_for_depth.get
+			  (next_range->m_stack_depth));
+
+	      int vbar_for_next_frame
+		= *vbar_column_for_depth.get (next_range->m_stack_depth);
+
+	      int indent_for_next_frame = vbar_for_next_frame - next_midpoint;
+	      write_indent (pp, vbar_for_next_frame);
+	      pp_string (pp, start_line_color);
+	      pp_character (pp, '<');
+	      for (int i = indent_for_next_frame + next_midpoint;
+		   i < cur_indent + midpoint - 1; i++)
+		pp_character (pp, '-');
+	      pp_character (pp, '+');
+	      pp_string (pp, end_line_color);
+	      pp_newline (pp);
+	      cur_indent = indent_for_next_frame;
+
+	      write_indent (pp, vbar_for_next_frame);
+	      pp_string (pp, start_line_color);
+	      pp_printf (pp, "|");
+	      pp_string (pp, end_line_color);
+	      pp_newline (pp);
+	    }
+	  else if (range->m_stack_depth < next_range->m_stack_depth)
+	    {
+	      /* Prepare to show pushed stack frame.  */
+	      gcc_assert (range->m_stack_depth != EMPTY);
+	      gcc_assert (range->m_stack_depth != DELETED);
+	      vbar_column_for_depth.put (range->m_stack_depth,
+					 cur_indent + midpoint);
+	      cur_indent += midpoint;
+	    }
+
+	}
+    }
+  // TODO: maybe show state changes?
+  // TODO: maybe show all events
+  /* TODO: example of recursion in DejaGnu tests.  */
+}
+
+/* FIXME.
+   Print events, consolidating them into rich locations.  */
+
+static void
+print_event_range (pretty_printer *pp,
+		   const diagnostic_path *path,
+		   unsigned start_idx, unsigned end_idx,
+		   bool use_diagnostic)
+{
+  while (start_idx <= end_idx)
+    {
+      path_label label (path, start_idx);
+      const diagnostic_event &event = path->get_event (start_idx);
+      gcc_rich_location richloc (event.get_location (),
+				 use_diagnostic ? NULL : &label);
+      unsigned j;
+      for (j = start_idx + 1; j <= end_idx; j++)
+	{
+	  const diagnostic_event &other = path->get_event (j);
+	  // TODO: label, and slacken the constraint
+	  if (can_consolidate_events (event, other))
+	    if (richloc.add_location_if_nearby (other.get_location (),
+						false, &label))
+	      continue;
+	  break;
+	}
+
 #if 1
-	  /* Hack up state so that diagnostic_report_current_function will announce
-	     function name changes.  */
-	  tree saved_current_function_decl = current_function_decl;
-	  location_t saved_input_location = input_location;
-	  current_function_decl = event->m_fndecl;
-	  input_location = event->m_loc;
+      /* Hack up state so that diagnostic_report_current_function will announce
+	 function name changes.  */
+      tree saved_current_function_decl = current_function_decl;
+      location_t saved_input_location = input_location;
+      current_function_decl = event.get_fndecl ();
+      input_location = event.get_location ();
 #endif
 
 #if 0
-	  /* Alternatively, part of lhd_print_error_function.  */
-	  char *old_prefix = pp_take_prefix (context->printer);
-	  const char *file = LOCATION_FILE (event->m_loc);
-	  char *new_prefix = file ? file_name_as_prefix (context, file) : NULL;
-	  tree fndecl = event->m_fndecl;
-	  if (TREE_CODE (TREE_TYPE (fndecl)) == METHOD_TYPE)
-	    pp_printf
-	      (context->printer, _("In member function %qs"),
-	       identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2)));
-	  else
-	    pp_printf
-	      (context->printer, _("In function %qs"),
-	       identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2)));
-	  pp_newline_and_flush (context->printer);
-	  context->printer->prefix = old_prefix;
-	  free ((char*) new_prefix);
+      /* Alternatively, part of lhd_print_error_function.  */
+      char *old_prefix = pp_take_prefix (pp);
+      const char *file = LOCATION_FILE (event->m_loc);
+      char *new_prefix = file ? file_name_as_prefix (context, file) : NULL;
+      tree fndecl = event->m_fndecl;
+      if (TREE_CODE (TREE_TYPE (fndecl)) == METHOD_TYPE)
+	pp_printf
+	  (pp, _("In member function %qs"),
+	   identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2)));
+      else
+	pp_printf
+	  (pp, _("In function %qs"),
+	   identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2)));
+      pp_newline_and_flush (pp);
+      pp->prefix = old_prefix;
+      free ((char*) new_prefix);
 #endif
 
-	  inform (&richloc, "(%i) %s", start_idx + 1, event->m_desc);
-	  start_idx = j;
+      if (use_diagnostic)
+	{
+	  label_text event_text (event.get_desc ());
+	  inform (&richloc, "(%i) %s", start_idx + 1, event_text.m_buffer);
+	  event_text.maybe_free ();
+	}
+      else
+	{
+	  diagnostic_context dc;
+	  memset (&dc, 0, sizeof (dc));
+	  dc.printer = pp;
+	  dc.show_caret = true;
+	  dc.show_labels_p = global_dc->show_labels_p;
+	  dc.show_line_numbers_p = global_dc->show_line_numbers_p;
+	  dc.start_span = global_dc->start_span;
+	  dc.min_margin_width = global_dc->min_margin_width;
+	  dc.caret_chars[0] = '^';
+	  diagnostic_set_caret_max_width (&dc, pp_line_cutoff (pp));
+	  diagnostic_show_locus (&dc, &richloc, DK_NOTE); // FIXME
+	}
+
+      start_idx = j;
 
 #if 1
-	  current_function_decl = saved_current_function_decl;
-	  input_location = saved_input_location;
+      current_function_decl = saved_current_function_decl;
+      input_location = saved_input_location;
 #endif
-	}
+    }
+}
+
+/* FIXME.  */
+// This needs to be in tree-diagnostic.c so we can hack up current_function_decl,
+// which is used by lhd_print_error_function etc.
+
+void
+tree_print_diagnostic_path (diagnostic_context *context,
+			    const diagnostic_path *path)
+{
+  const unsigned num_events = path->num_events ();
+
+  /* Attempt to show intraprocedural summary.  */
+  if (path->interprocedural_p ()
+      && context->path_summary_format != DPSF_NONE)
+    {
+      inform (UNKNOWN_LOCATION, "summary of interprocedural path");
+
+      interprocedural_path_summary summary (*path);
+
+      char *saved_prefix = pp_take_prefix (context->printer);
+      pp_set_prefix (context->printer, NULL);
+      print_path_summary (context->printer,
+			  summary, path, context->show_path_depths,
+			  context->path_summary_format);
+      pp_flush (context->printer);
+      pp_set_prefix (context->printer, saved_prefix);
+    }
+
+  /* TODO: options for different printing approaches.  */
+  if (context->show_caret && context->show_line_numbers_p)
+    {
+      if (num_events > 0)
+	print_event_range (context->printer, path, 0, num_events - 1,
+			   true);
     }
   else
     {
       /* Simpler implementation: a note per event.  */
-      diagnostic_path::event *event;
-      int i;
-      FOR_EACH_VEC_ELT (path->m_events, i, event)
-	inform (event->m_loc, "(%i) %s", i + 1, event->m_desc);
+      for (unsigned i = 0; i < num_events; i++)
+	{
+	  const diagnostic_event &event = path->get_event (i);
+	  label_text event_text (event.get_desc ());
+	  inform (event.get_location (),
+		  "(%i) %s", i + 1, event_text.m_buffer);
+	  event_text.maybe_free ();
+	}
     }
 }
 
@@ -449,21 +821,24 @@ static json::value *
 tree_make_json_for_path (diagnostic_context *, const diagnostic_path *path)
 {
   json::array *path_array = new json::array ();
-  int i;
-  diagnostic_path::event *event;
-  FOR_EACH_VEC_ELT (path->m_events, i, event)
+  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->m_loc)
-	event_obj->set ("location", json_from_expanded_location (event->m_loc));
-      event_obj->set ("description", new json::string (event->m_desc));
-      if (event->m_fndecl)
+      if (event.get_location ())
+	event_obj->set ("location",
+			json_from_expanded_location (event.get_location ()));
+      label_text event_text (event.get_desc ());
+      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 (event->m_fndecl, 2));
+	    = 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->m_depth));
+      event_obj->set ("depth", new json::number (event.get_stack_depth ()));
       path_array->append (event_obj);
     }
   return path_array;
@@ -479,3 +854,401 @@ tree_diagnostics_defaults (diagnostic_context *context)
   context->print_path = tree_print_diagnostic_path;
   context->make_json_for_path = tree_make_json_for_path;
 }
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* FIXME.  */
+
+class test_diagnostic_path : public simple_diagnostic_path
+{
+ public:
+  test_diagnostic_path (pretty_printer *event_pp)
+  : simple_diagnostic_path (event_pp)
+  {
+  }
+
+  void add_entry (tree fndecl, int stack_depth)
+  {
+    add_event (UNKNOWN_LOCATION, fndecl, stack_depth,
+	       "entering %qE", fndecl);
+  }
+
+  void add_return (tree fndecl, int stack_depth)
+  {
+    add_event (UNKNOWN_LOCATION, fndecl, stack_depth,
+	       "returning to %qE", fndecl);
+  }
+
+  void add_call (tree caller, int caller_stack_depth, tree callee)
+  {
+    add_event (UNKNOWN_LOCATION, caller, caller_stack_depth,
+	       "calling %qE", callee);
+    add_entry (callee, caller_stack_depth + 1);
+  }
+};
+
+/* FIXME.  */
+
+static void
+test_empty_path (pretty_printer *event_pp)
+{
+  test_diagnostic_path path (event_pp);
+  ASSERT_FALSE (path.interprocedural_p ());
+
+  interprocedural_path_summary summary (path);
+  ASSERT_EQ (summary.m_ranges.length (), 0);
+
+  pretty_printer pp;
+  print_path_summary (&pp, summary, &path, true,
+		      DPSF_INLINE_EVENTS);
+  ASSERT_STREQ ("",
+		pp_formatted_text (&pp));
+}
+
+/* FIXME.  */
+
+static void
+test_intraprocedural_path (pretty_printer *event_pp)
+{
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_foo = build_fn_decl ("foo", fntype_void_void);
+
+  test_diagnostic_path path (event_pp);
+  path.add_event (UNKNOWN_LOCATION, fndecl_foo, 0, "first %qs", "free");
+  path.add_event (UNKNOWN_LOCATION, fndecl_foo, 0, "double %qs", "free");
+
+  ASSERT_FALSE (path.interprocedural_p ());
+
+  interprocedural_path_summary summary (path);
+  ASSERT_EQ (summary.m_ranges.length (), 1);
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_SIMPLE);
+    ASSERT_STREQ ("  `foo': events 1-2 (depth 0)\n",
+		  pp_formatted_text (&pp));
+  }
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_EVENT_SUMMARIES);
+    ASSERT_STREQ ("  `foo': events 1-2 (depth 0)\n"
+		  "    | (1): first `free'\n"
+		  "    | (2): double `free'\n",
+		  pp_formatted_text (&pp));
+  }
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_INLINE_EVENTS);
+#if 0
+    ASSERT_STREQ ("  `foo': events 1-2 (depth 0)\n"
+		  "    | (1): first `free'\n"
+		  "    | (2): double `free'\n",
+		  pp_formatted_text (&pp));
+#endif
+  }
+}
+
+/* FIXME.  */
+
+static void
+test_interprocedural_path_1 (pretty_printer *event_pp)
+{
+  /* Build fndecls.  The types aren't quite right, but that
+     doesn't matter for the purposes of this test.  */
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_test = build_fn_decl ("test", fntype_void_void);
+  tree fndecl_make_boxed_int
+    = build_fn_decl ("make_boxed_int", fntype_void_void);
+  tree fndecl_wrapped_malloc
+    = build_fn_decl ("wrapped_malloc", fntype_void_void);
+  tree fndecl_free_boxed_int
+    = build_fn_decl ("free_boxed_int", fntype_void_void);
+  tree fndecl_wrapped_free
+    = build_fn_decl ("wrapped_free", fntype_void_void);
+
+  test_diagnostic_path path (event_pp);
+  path.add_entry (fndecl_test, 0);
+  path.add_call (fndecl_test, 0, fndecl_make_boxed_int);
+  path.add_call (fndecl_make_boxed_int, 1, fndecl_wrapped_malloc);
+  path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_malloc, 2, "calling malloc");
+  path.add_return (fndecl_test, 0);
+  path.add_call (fndecl_test, 0, fndecl_free_boxed_int);
+  path.add_call (fndecl_free_boxed_int, 1, fndecl_wrapped_free);
+  path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_free, 2, "calling free");
+  path.add_return (fndecl_test, 0);
+  path.add_call (fndecl_test, 0, fndecl_free_boxed_int);
+  path.add_call (fndecl_free_boxed_int, 1, fndecl_wrapped_free);
+  path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_free, 2, "calling free");
+  ASSERT_EQ (path.num_events (), 18);
+
+  ASSERT_TRUE (path.interprocedural_p ());
+
+  interprocedural_path_summary summary (path);
+  ASSERT_EQ (summary.m_ranges.length (), 9);
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_SIMPLE);
+    ASSERT_STREQ
+      ("  `test': events 1-2 (depth 0)\n"
+       "     |\n"
+       "     +--> `make_boxed_int': events 3-4 (depth 1)\n"
+       "                  |\n"
+       "                  +--> `wrapped_malloc': events 5-6 (depth 2)\n"
+       "                               |\n"
+       "     <-------------------------+\n"
+       "     |\n"
+       "  `test': events 7-8 (depth 0)\n"
+       "     |\n"
+       "     +--> `free_boxed_int': events 9-10 (depth 1)\n"
+       "                  |\n"
+       "                  +--> `wrapped_free': events 11-12 (depth 2)\n"
+       "                              |\n"
+       "     <------------------------+\n"
+       "     |\n"
+       "  `test': events 13-14 (depth 0)\n"
+       "     |\n"
+       "     +--> `free_boxed_int': events 15-16 (depth 1)\n"
+       "                  |\n"
+       "                  +--> `wrapped_free': events 17-18 (depth 2)\n",
+       pp_formatted_text (&pp));
+  }
+
+  /* FIXME: this is a little too verbose.  Would be nice to show
+     just the most pertinent events (here 6, 12, and 18), given that
+     the interprocedural stuff is much clearer, given the picture.  */
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_EVENT_SUMMARIES);
+    ASSERT_STREQ
+      ("  `test': events 1-2 (depth 0)\n"
+       "     | (1): entering `test'\n"
+       "     | (2): calling `make_boxed_int'\n"
+       "     +--> `make_boxed_int': events 3-4 (depth 1)\n"
+       "                  | (3): entering `make_boxed_int'\n"
+       "                  | (4): calling `wrapped_malloc'\n"
+       "                  +--> `wrapped_malloc': events 5-6 (depth 2)\n"
+       "                               | (5): entering `wrapped_malloc'\n"
+       "                               | (6): calling malloc\n"
+       "     <-------------------------+\n"
+       "     |\n"
+       "  `test': events 7-8 (depth 0)\n"
+       "     | (7): returning to `test'\n"
+       "     | (8): calling `free_boxed_int'\n"
+       "     +--> `free_boxed_int': events 9-10 (depth 1)\n"
+       "                  | (9): entering `free_boxed_int'\n"
+       "                  | (10): calling `wrapped_free'\n"
+       "                  +--> `wrapped_free': events 11-12 (depth 2)\n"
+       "                              | (11): entering `wrapped_free'\n"
+       "                              | (12): calling free\n"
+       "     <------------------------+\n"
+       "     |\n"
+       "  `test': events 13-14 (depth 0)\n"
+       "     | (13): returning to `test'\n"
+       "     | (14): calling `free_boxed_int'\n"
+       "     +--> `free_boxed_int': events 15-16 (depth 1)\n"
+       "                  | (15): entering `free_boxed_int'\n"
+       "                  | (16): calling `wrapped_free'\n"
+       "                  +--> `wrapped_free': events 17-18 (depth 2)\n"
+       "                              | (17): entering `wrapped_free'\n"
+       "                              | (18): calling free\n",
+       pp_formatted_text (&pp));
+  }
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_INLINE_EVENTS);
+#if 0
+    ASSERT_STREQ
+      ("  `test': events 1-2 (depth 0)\n"
+       "     | (1): entering `test'\n"
+       "     | (2): calling `make_boxed_int'\n"
+       "     +--> `make_boxed_int': events 3-4 (depth 1)\n"
+       "                  | (3): entering `make_boxed_int'\n"
+       "                  | (4): calling `wrapped_malloc'\n"
+       "                  +--> `wrapped_malloc': events 5-6 (depth 2)\n"
+       "                               | (5): entering `wrapped_malloc'\n"
+       "                               | (6): calling malloc\n"
+       "     <-------------------------+\n"
+       "     |\n"
+       "  `test': events 7-8 (depth 0)\n"
+       "     | (7): returning to `test'\n"
+       "     | (8): calling `free_boxed_int'\n"
+       "     +--> `free_boxed_int': events 9-10 (depth 1)\n"
+       "                  | (9): entering `free_boxed_int'\n"
+       "                  | (10): calling `wrapped_free'\n"
+       "                  +--> `wrapped_free': events 11-12 (depth 2)\n"
+       "                              | (11): entering `wrapped_free'\n"
+       "                              | (12): calling free\n"
+       "     <------------------------+\n"
+       "     |\n"
+       "  `test': events 13-14 (depth 0)\n"
+       "     | (13): returning to `test'\n"
+       "     | (14): calling `free_boxed_int'\n"
+       "     +--> `free_boxed_int': events 15-16 (depth 1)\n"
+       "                  | (15): entering `free_boxed_int'\n"
+       "                  | (16): calling `wrapped_free'\n"
+       "                  +--> `wrapped_free': events 17-18 (depth 2)\n"
+       "                              | (17): entering `wrapped_free'\n"
+       "                              | (18): calling free\n",
+       pp_formatted_text (&pp));
+#endif
+  }
+}
+
+/* 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 ());
+
+  interprocedural_path_summary summary (path);
+  ASSERT_EQ (summary.m_ranges.length (), 5);
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_SIMPLE);
+    ASSERT_STREQ
+      ("  `foo': events 1-2 (depth 0)\n"
+       "    |\n"
+       "    +--> `bar': events 3-4 (depth 1)\n"
+       "           |\n"
+       "           +--> `baz': event 5 (depth 2)\n"
+       "                  |\n"
+       "           <------+\n"
+       "           |\n"
+       "         `bar': events 6-7 (depth 1)\n"
+       "           |\n"
+       "           +--> `baz': event 8 (depth 2)\n",
+       pp_formatted_text (&pp));
+  }
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_EVENT_SUMMARIES);
+    ASSERT_STREQ
+      ("  `foo': events 1-2 (depth 0)\n"
+       "    | (1): entering `foo'\n"
+       "    | (2): calling `bar'\n"
+       "    +--> `bar': events 3-4 (depth 1)\n"
+       "           | (3): entering `bar'\n"
+       "           | (4): calling `baz'\n"
+       "           +--> `baz': event 5 (depth 2)\n"
+       "                  | (5): entering `baz'\n"
+       "           <------+\n"
+       "           |\n"
+       "         `bar': events 6-7 (depth 1)\n"
+       "           | (6): returning to `bar'\n"
+       "           | (7): calling `baz'\n"
+       "           +--> `baz': event 8 (depth 2)\n"
+       "                  | (8): entering `baz'\n",
+       pp_formatted_text (&pp));
+  }
+}
+
+/* FIXME.  */
+static void
+test_recursion (pretty_printer *event_pp)
+{
+  tree fntype_void_void
+    = build_function_type_array (void_type_node, 0, NULL);
+  tree fndecl_factorial = build_fn_decl ("factorial", fntype_void_void);
+
+ test_diagnostic_path path (event_pp);
+  path.add_entry (fndecl_factorial, 0);
+  for (int depth = 0; depth < 3; depth++)
+    path.add_call (fndecl_factorial, depth, fndecl_factorial);
+  ASSERT_EQ (path.num_events (), 7);
+
+  ASSERT_TRUE (path.interprocedural_p ());
+
+  interprocedural_path_summary summary (path);
+  ASSERT_EQ (summary.m_ranges.length (), 4);
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_SIMPLE);
+    ASSERT_STREQ
+      ("  `factorial': events 1-2 (depth 0)\n"
+       "       |\n"
+       "       +--> `factorial': events 3-4 (depth 1)\n"
+       "                 |\n"
+       "                 +--> `factorial': events 5-6 (depth 2)\n"
+       "                           |\n"
+       "                           +--> `factorial': event 7 (depth 3)\n",
+       pp_formatted_text (&pp));
+  }
+
+  {
+    pretty_printer pp;
+    print_path_summary (&pp, summary, &path, true,
+			DPSF_EVENT_SUMMARIES);
+    ASSERT_STREQ
+      ("  `factorial': events 1-2 (depth 0)\n"
+       "       | (1): entering `factorial'\n"
+       "       | (2): calling `factorial'\n"
+       "       +--> `factorial': events 3-4 (depth 1)\n"
+       "                 | (3): entering `factorial'\n"
+       "                 | (4): calling `factorial'\n"
+       "                 +--> `factorial': events 5-6 (depth 2)\n"
+       "                           | (5): entering `factorial'\n"
+       "                           | (6): calling `factorial'\n"
+       "                           +--> `factorial': event 7 (depth 3)\n"
+       "                                     | (7): entering `factorial'\n",
+       pp_formatted_text (&pp));
+  }
+
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+tree_diagnostic_c_tests ()
+{
+  auto_fix_quotes fix_quotes;
+  pretty_printer *event_pp = global_dc->printer->clone ();
+  pp_show_color (event_pp) = 0;
+  test_empty_path (event_pp);
+  test_intraprocedural_path (event_pp);
+  test_interprocedural_path_1 (event_pp);
+  test_interprocedural_path_2 (event_pp);
+  test_recursion (event_pp);
+  delete event_pp;
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
-- 
1.8.5.3

