From b08c34f3bf6d17352abb6c20d0187dc11a89e745 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Tue, 19 Mar 2019 16:48:53 -0400
Subject: [PATCH 127/138] FIXME: WIP on interprocedural summary of paths

---
 gcc/analyzer/engine.cc                       |  12 -
 gcc/common.opt                               |   4 +
 gcc/diagnostic-path.h                        |   4 +-
 gcc/diagnostic.c                             |  33 +++
 gcc/diagnostic.h                             |   3 +
 gcc/opts.c                                   |   4 +
 gcc/selftest-run-tests.c                     |   1 +
 gcc/selftest.h                               |   1 +
 gcc/testsuite/gcc.dg/analyzer/analyzer.exp   |   2 +-
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c |   2 +-
 gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8.c |   2 +-
 gcc/toplev.c                                 |   2 +
 gcc/tree-diagnostic.c                        | 421 ++++++++++++++++++++++++++-
 13 files changed, 472 insertions(+), 19 deletions(-)

diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 26eb83c..89d747d 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -907,18 +907,6 @@ public:
 #endif
   }
 
-  bool interprocedural_p () const
-  {
-    checker_event *e;
-    int i;
-    FOR_EACH_VEC_ELT (m_events, i, e)
-      {
-	if (m_events[i]->m_fndecl != m_events[0]->m_fndecl)
-	  return true;
-      }
-    return false;
-  }
-
   auto_delete_vec<checker_event> m_events;
   int m_stack_depth;
 };
diff --git a/gcc/common.opt b/gcc/common.opt
index 67529af..8a8fc1f 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1325,6 +1325,10 @@ fdiagnostics-show-metadata
 Common Var(flag_diagnostics_show_metadata) Init(1)
 FIXME; TODO: add to docs
 
+fdiagnostics-show-path-summary
+Common Var(flag_diagnostics_show_path_summary) Init(1)
+Show summary of interprocedural 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-path.h b/gcc/diagnostic-path.h
index d0d4fce..a90c35b 100644
--- a/gcc/diagnostic-path.h
+++ b/gcc/diagnostic-path.h
@@ -84,6 +84,8 @@ class diagnostic_path
   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.  */
@@ -118,8 +120,6 @@ class simple_diagnostic_event : public diagnostic_event
 class simple_diagnostic_path : public diagnostic_path
 {
  public:
-  simple_diagnostic_path ();
-
   unsigned num_events () const FINAL OVERRIDE;
   const diagnostic_event & get_event (int idx) const FINAL OVERRIDE;
 
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index 9b49a4e..be790b0 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -179,6 +179,7 @@ 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->show_path_summary = false;
   context->show_option_requested = false;
   context->abort_on_error = false;
   context->show_column = false;
@@ -664,6 +665,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)
@@ -1793,6 +1810,22 @@ diagnostic_path::~diagnostic_path ()
 
 /* 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.  */
+
 void
 simple_diagnostic_path::add_event (location_t loc, tree fndecl, int depth, const char *fmt, ...)
 {
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index b1d3e11..0f8ddf7 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -133,6 +133,9 @@ struct diagnostic_context
   /* FIXME.  */
   bool show_metadata;
 
+  /* FIXME.  */
+  bool show_path_summary;
+
   /* 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 fcf2685..aff428a 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -2404,6 +2404,10 @@ common_handle_option (struct gcc_options *opts,
       dc->show_metadata = value;
       break;
 
+    case OPT_fdiagnostics_show_path_summary:
+      dc->show_path_summary = 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/analyzer/analyzer.exp b/gcc/testsuite/gcc.dg/analyzer/analyzer.exp
index a11251f..9679256 100644
--- a/gcc/testsuite/gcc.dg/analyzer/analyzer.exp
+++ b/gcc/testsuite/gcc.dg/analyzer/analyzer.exp
@@ -22,7 +22,7 @@ load_lib gcc-dg.exp
 # If a testcase doesn't have special options, use these.
 global DEFAULT_CFLAGS
 if ![info exists DEFAULT_CFLAGS] then {
-    set DEFAULT_CFLAGS " --analyzer"
+    set DEFAULT_CFLAGS " --analyzer -fno-diagnostics-show-path-summary"
 }
 
 # Initialize `dg'.
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c
index 1b9faa1..a167534 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-2.c
@@ -1,4 +1,4 @@
-/* { dg-options "--analyzer -fanalyzer-verbosity=1" } */
+/* { dg-options "--analyzer -fno-diagnostics-show-path-summary -fanalyzer-verbosity=1" } */
 
 #include <stdlib.h>
 
diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8.c b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8.c
index f633960..0d82941 100644
--- a/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8.c
+++ b/gcc/testsuite/gcc.dg/analyzer/malloc-ipa-8.c
@@ -1,6 +1,6 @@
 /* Example of a multilevel wrapper around malloc/free, with a double-free.  */
 
-/* { dg-options "--analyzer -fdump-analyzer-summaries -fanalyzer-checker=malloc" } */
+/* { dg-options "--analyzer -fno-diagnostics-show-path-summary -fdump-analyzer-summaries -fanalyzer-checker=malloc" } */
 
 #include <stdlib.h>
 
diff --git a/gcc/toplev.c b/gcc/toplev.c
index 2fdd2c8..5b50f15 100644
--- a/gcc/toplev.c
+++ b/gcc/toplev.c
@@ -1121,6 +1121,8 @@ general_init (const char *argv0, bool init_signals)
     = global_options_init.x_flag_diagnostics_show_line_numbers;
   global_dc->show_metadata
     = global_options_init.x_flag_diagnostics_show_metadata;
+  global_dc->show_path_summary
+    = global_options_init.x_flag_diagnostics_show_path_summary;
   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 36dedf5..d352ef8 100644
--- a/gcc/tree-diagnostic.c
+++ b/gcc/tree-diagnostic.c
@@ -32,6 +32,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-path.h"
 #include "json.h"
 #include "gcc-rich-location.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.  */
@@ -368,6 +369,235 @@ can_consolidate_events (const diagnostic_event &e1,
 }
 
 /* FIXME.  */
+
+struct interprocedural_path_summary
+{
+  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;
+};
+
+/* FIXME.  */
+
+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;
+	  // TODO: show the range of events
+	  // TODO: show ASCII art showing calls and returns
+	  // TODO: maybe show state changes?
+	}
+      cur_fndecl = ev_fndecl;
+      cur_stack_depth = ev_stack_depth;
+    }
+  if (in_range)
+    {
+      m_ranges.safe_push (event_range (cur_fndecl, cur_stack_depth,
+				       cur_start_idx, num_events - 1));
+    }
+}
+
+/* FIXME.  */
+
+static void
+write_indent (pretty_printer *pp, int spaces)
+{
+  for (int i = 0; i < spaces; i++)
+    pp_space (pp);
+}
+
+/* FIXME.  */
+/* We can't use "%qE" here since we can't guarantee the
+   capabilities of PP; -Wformat will complain.  */
+
+static void
+print_quoted_fndecl (pretty_printer *pp, tree fndecl)
+{
+  const char *n = DECL_NAME (fndecl)
+    ? identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2))
+    : _("<anonymous>");
+  pp_printf (pp, "%qs", 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)
+{
+  const bool show_events = (path != NULL);
+
+  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_quoted_fndecl (tmp_pp, range->m_fndecl);
+	name_lengths.safe_push (strlen (pp_formatted_text (tmp_pp)));
+	pp_clear_output_area (tmp_pp);
+      }
+    delete tmp_pp;
+  }
+
+#if 0
+  /* Determine indentation for each range.  */
+  auto_vec<int> per_range_vbar_column;
+  auto_vec<int> per_range_name_start_column;
+  // TODO: populate these, use them
+#endif
+
+  auto_vec<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)
+    {
+      write_indent (pp, cur_indent); // FIXME
+      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, push_prefix);
+	      cur_indent += strlen (push_prefix);
+	    }
+	}
+      print_quoted_fndecl (pp, range->m_fndecl);
+      if (range->m_start_idx == range->m_end_idx)
+	pp_printf (pp, ": event %i",
+		   range->m_start_idx + 1);
+      else
+	pp_printf (pp, ": events %i-%i",
+		   range->m_start_idx + 1, range->m_end_idx + 1);
+      if (show_depths)
+	pp_printf (pp, " (depth %i)", range->m_stack_depth);
+      pp_newline (pp);
+
+      int midpoint = name_lengths[i] / 2;
+
+      if (show_events)
+	{
+	  for (int event_idx = range->m_start_idx;
+	       event_idx <= range->m_end_idx; event_idx++)
+	    {
+	      write_indent (pp, cur_indent + midpoint); // FIXME
+	      label_text event_desc = path->get_event (event_idx).get_desc ();
+	      pp_printf (pp, "| (%i): %s",
+			 event_idx + 1, event_desc.m_buffer);
+	      event_desc.maybe_free ();
+	      pp_newline (pp);
+	    }
+	}
+
+      if (i < summary.m_ranges.length () - 1)
+	{
+	  const interprocedural_path_summary::event_range *next_range
+	    = &summary.m_ranges[i + 1];
+
+	  if (!show_events)
+	    {
+	      write_indent (pp, cur_indent + midpoint); // FIXME
+	      pp_printf (pp, "|");
+	      pp_newline (pp);
+	    }
+
+	  if (range->m_stack_depth > next_range->m_stack_depth)
+	    {
+	      // TODO: show returning from stack frame(s)
+	      int next_midpoint = name_lengths[i + 1] / 2;
+	      int indent_for_next_frame = base_indent; // FIXME
+	      // FIXME: ^^^ should use vbar_column_for_depth
+	      write_indent (pp, indent_for_next_frame + next_midpoint); // FIXME
+	      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_newline (pp);
+	      cur_indent = indent_for_next_frame;
+
+	      write_indent (pp, cur_indent + next_midpoint); // FIXME
+	      pp_printf (pp, "|");
+	      pp_newline (pp);
+	    }
+	  else if (range->m_stack_depth < next_range->m_stack_depth)
+	    {
+	      /* Prepare to show pushed stack frame.  */
+	      vbar_column_for_depth.safe_push (cur_indent + midpoint);
+	      cur_indent += midpoint;
+	    }
+
+	}
+    }
+  // TODO: maybe show state changes?
+  // TODO: maybe show all events
+  // TODO: color
+
+  // TODO: selftest for this!
+  // TODO: DejaGnu test for this
+  // TODO: option for controlling this
+  /* TODO: example of recursion.  */
+  /* TODO: example of recursion in DejaGnu tests.  */
+}
+
+/* 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.
 
@@ -375,13 +605,26 @@ 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->show_path_summary)
+    {
+      inform (UNKNOWN_LOCATION, "summary of interprocedural path");
+
+      interprocedural_path_summary summary (*path);
+      print_path_summary (context->printer,
+			  summary, path, true);
+      pp_flush (context->printer);
+    }
 
   /* TODO: options for different printing approaches.  */
   if (context->show_caret && context->show_line_numbers_p)
     {
       /* Try consolidating events into rich locations.  */
       unsigned start_idx = 0;
-      while (start_idx < path->num_events ())
+      while (start_idx < num_events)
 	{
 	  path_label label (path, start_idx);
 	  const diagnostic_event &event = path->get_event (start_idx);
@@ -440,7 +683,7 @@ tree_print_diagnostic_path (diagnostic_context *context,
   else
     {
       /* Simpler implementation: a note per event.  */
-      for (unsigned i = 0; i < path->num_events (); i++)
+      for (unsigned i = 0; i < num_events; i++)
 	{
 	  const diagnostic_event &event = path->get_event (i);
 	  label_text event_text (event.get_desc ());
@@ -492,3 +735,177 @@ 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 {
+
+class test_diagnostic_path : public simple_diagnostic_path
+{
+ public:
+  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 ()
+{
+  simple_diagnostic_path path;
+  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);
+  ASSERT_STREQ ("",
+		pp_formatted_text (&pp));
+}
+
+/* FIXME.  */
+
+static void
+test_interprocedural_path_1 ()
+{
+  /* 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;
+  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, NULL, true);
+    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);
+    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));
+  }
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+tree_diagnostic_c_tests ()
+{
+  auto_fix_quotes fix_quotes;
+  test_empty_path ();
+#if 0
+  test_intraprocedural_path ();
+#endif
+  test_interprocedural_path_1 ();
+#if 0
+  test_interprocedural_path_2 ();
+  test_recursion ();
+#endif
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
-- 
1.8.5.3

