]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
diagnostics: split path-printing into a new diagnostic-path-output.cc
authorDavid Malcolm <dmalcolm@redhat.com>
Tue, 27 May 2025 16:22:25 +0000 (12:22 -0400)
committerDavid Malcolm <dmalcolm@redhat.com>
Tue, 27 May 2025 16:22:25 +0000 (12:22 -0400)
No functional change intended.

gcc/ChangeLog:
* Makefile.in (OBJS-libcommon): Add diagnostic-path-output.o.
* diagnostic-path-output.cc: New file, taken from material in
diagnostic-path.cc.
* diagnostic-path.cc: Drop includes of
"diagnostic-macro-unwinding.h", "intl.h", "gcc-rich-location.h",
"diagnostic-color.h", "diagnostic-event-id.h",
"diagnostic-label-effects.h", "pretty-print-markup.h",
"selftest.h", "selftest-diagnostic.h",
"selftest-diagnostic-path.h", "text-art/theme.h", and
"diagnostic-format-text.h".
(class path_print_policy): Move to diagnostic-path-output.cc.
(class path_label): Likewise.
(can_consolidate_events): Likewise.
(class per_thread_summary): Likewise.
(struct event_range): Likewise.
(struct path_summary): Likewise.
(per_thread_summary::interprocedural_p): Likewise.
(path_summary::path_summary): Likewise.
(write_indent): Likewise.
(base_indent): Likewise.
(per_frame_indent): Likewise.
(class thread_event_printer): Likewise.
(print_path_summary_as_text): Likewise.
(class element_event_desc): Likewise.
(diagnostic_text_output_format::print_path): Likewise.
(selftest::path_events_have_column_data_p): Likewise.
(selftest::test_empty_path): Likewise.
(selftest::test_intraprocedural_path): Likewise.
(selftest::test_interprocedural_path_1): Likewise.
(selftest::test_interprocedural_path_2): Likewise.
(selftest::test_recursion): Likewise.
(class selftest::control_flow_test): Likewise.
(selftest::test_control_flow_1): Likewise.
(selftest::test_control_flow_2): Likewise.
(selftest::test_control_flow_3): Likewise.
(selftest::assert_cfg_edge_path_streq): Likewise.
(ASSERT_CFG_EDGE_PATH_STREQ): Likewise.
(selftest::test_control_flow_4): Likewise.
(selftest::test_control_flow_5): Likewise.
(selftest::test_control_flow_6): Likewise.
(selftest::control_flow_tests): Likewise.
(selftest::diagnostic_path_cc_tests): Likewise, renaming
accordingly.
* selftest-run-tests.cc (selftest::run_tests): Update for
move of path-printing selftests.
* selftest.h (selftest::diagnostic_path_cc_tests): Replace decl
with...
(selftest::diagnostic_path_output_cc_tests): ...this.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
gcc/Makefile.in
gcc/diagnostic-path-output.cc [new file with mode: 0644]
gcc/diagnostic-path.cc
gcc/selftest-run-tests.cc
gcc/selftest.h

index 366364a23deab251b1bdbb420f7d155c9d181a0f..a45238639f16ba208b15cc9856e470542543335f 100644 (file)
@@ -1856,6 +1856,7 @@ OBJS-libcommon = diagnostic-spec.o diagnostic.o diagnostic-color.o \
        diagnostic-global-context.o \
        diagnostic-macro-unwinding.o \
        diagnostic-path.o \
+       diagnostic-path-output.o \
        diagnostic-show-locus.o \
        edit-context.o \
        pretty-print.o intl.o \
diff --git a/gcc/diagnostic-path-output.cc b/gcc/diagnostic-path-output.cc
new file mode 100644 (file)
index 0000000..3fe6a0d
--- /dev/null
@@ -0,0 +1,2360 @@
+/* Printing paths through the code associated with a diagnostic.
+   Copyright (C) 2019-2025 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"
+#define INCLUDE_ALGORITHM
+#define INCLUDE_STRING
+#define INCLUDE_VECTOR
+#include "system.h"
+#include "coretypes.h"
+#include "diagnostic.h"
+#include "diagnostic-macro-unwinding.h"
+#include "intl.h"
+#include "diagnostic-path.h"
+#include "gcc-rich-location.h"
+#include "diagnostic-color.h"
+#include "diagnostic-event-id.h"
+#include "diagnostic-label-effects.h"
+#include "pretty-print-markup.h"
+#include "selftest.h"
+#include "selftest-diagnostic.h"
+#include "selftest-diagnostic-path.h"
+#include "text-art/theme.h"
+#include "diagnostic-format-text.h"
+
+/* Disable warnings about missing quoting in GCC diagnostics for the print
+   calls below.  */
+#if __GNUC__ >= 10
+#  pragma GCC diagnostic push
+#  pragma GCC diagnostic ignored "-Wformat-diag"
+#endif
+
+/* Anonymous namespace for path-printing code.  */
+
+namespace {
+
+/* A bundle of state for printing a path.  */
+
+class path_print_policy
+{
+public:
+  path_print_policy (const diagnostic_text_output_format &text_output)
+  : m_source_policy (text_output.get_context ())
+  {
+  }
+
+  text_art::theme *
+  get_diagram_theme () const
+  {
+    return  m_source_policy.get_diagram_theme ();
+  }
+
+  const diagnostic_source_print_policy &
+  get_source_policy () const { return m_source_policy; }
+
+private:
+  diagnostic_source_print_policy m_source_policy;
+};
+
+/* Subclass of range_label for showing a particular event
+   when 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,
+             const pretty_printer &ref_pp,
+             unsigned start_idx,
+             bool colorize,
+             bool allow_emojis)
+  : m_path (path),
+    m_ref_pp (ref_pp),
+    m_start_idx (start_idx), m_effects (*this),
+    m_colorize (colorize), m_allow_emojis (allow_emojis)
+  {}
+
+  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);
+
+    const diagnostic_event::meaning meaning (event.get_meaning ());
+
+    auto pp = m_ref_pp.clone ();
+    pp_show_color (pp.get ()) = m_colorize;
+    diagnostic_event_id_t event_id (event_idx);
+
+    pp_printf (pp.get (), "%@", &event_id);
+    pp_space (pp.get ());
+
+    if (meaning.m_verb == diagnostic_event::VERB_danger
+       && m_allow_emojis)
+      {
+       pp_unicode_character (pp.get (), 0x26A0); /* U+26A0 WARNING SIGN.  */
+       /* Append U+FE0F VARIATION SELECTOR-16 to select the emoji
+          variation of the char.  */
+       pp_unicode_character (pp.get (), 0xFE0F);
+       /* U+26A0 WARNING SIGN has East_Asian_Width == Neutral, but in its
+          emoji variant is printed (by vte at least) with a 2nd half
+          overlapping the next char.  Hence we add two spaces here: a space
+          to be covered by this overlap, plus another space of padding.  */
+       pp_string (pp.get (), "  ");
+      }
+
+    event.print_desc (*pp.get ());
+
+    label_text result
+      = label_text::take (xstrdup (pp_formatted_text (pp.get ())));
+    return result;
+  }
+
+  const label_effects *get_effects (unsigned /*range_idx*/) const
+  {
+    return &m_effects;
+  }
+
+ private:
+  class path_label_effects : public label_effects
+  {
+  public:
+    path_label_effects (const path_label &path_label)
+    : m_path_label (path_label)
+    {
+    }
+    bool has_in_edge (unsigned range_idx) const final override
+    {
+      if (const diagnostic_event *prev_event
+           = m_path_label.get_prev_event (range_idx))
+       return prev_event->connect_to_next_event_p ();
+      return false;
+    }
+    bool has_out_edge (unsigned range_idx) const final override
+    {
+      const diagnostic_event &event = m_path_label.get_event (range_idx);
+      return event.connect_to_next_event_p ();
+    }
+
+  private:
+    const path_label &m_path_label;
+  };
+
+  const diagnostic_event &get_event (unsigned range_idx) const
+  {
+    unsigned event_idx = m_start_idx + range_idx;
+    return m_path.get_event (event_idx);
+  }
+
+  const diagnostic_event *get_prev_event (unsigned range_idx) const
+  {
+    if (m_start_idx + range_idx == 0)
+      return nullptr;
+    unsigned event_idx = m_start_idx + range_idx - 1;
+    return &m_path.get_event (event_idx);
+  }
+
+  const diagnostic_path &m_path;
+  const pretty_printer &m_ref_pp;
+  unsigned m_start_idx;
+  path_label_effects m_effects;
+  const bool m_colorize;
+  const bool m_allow_emojis;
+};
+
+/* 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_path &path,
+                       const diagnostic_event &e1,
+                       unsigned ev1_idx,
+                       const diagnostic_event &e2,
+                       unsigned ev2_idx,
+                       bool check_locations)
+{
+  if (e1.get_thread_id () != e2.get_thread_id ())
+    return false;
+
+  if (!path.same_function_p (ev1_idx, ev2_idx))
+    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;
+}
+
+struct event_range;
+struct path_summary;
+class thread_event_printer;
+
+/* A bundle of information about all of the events in a diagnostic_path
+   relating to a specific path, for use by path_summary.  */
+
+class per_thread_summary
+{
+public:
+  per_thread_summary (const diagnostic_path &path,
+                     const logical_location_manager &logical_loc_mgr,
+                     label_text name, unsigned swimlane_idx)
+  : m_path (path),
+    m_logical_loc_mgr (logical_loc_mgr),
+    m_name (std::move (name)),
+    m_swimlane_idx (swimlane_idx),
+    m_last_event (nullptr),
+    m_min_depth (INT_MAX),
+    m_max_depth (INT_MIN)
+  {}
+
+  void update_depth_limits (int stack_depth)
+  {
+    if (stack_depth < m_min_depth)
+      m_min_depth = stack_depth;
+    if (stack_depth > m_max_depth)
+      m_max_depth = stack_depth;
+  }
+
+  const char *get_name () const { return m_name.get (); }
+  unsigned get_swimlane_index () const { return m_swimlane_idx; }
+
+  bool interprocedural_p () const;
+
+private:
+  friend struct path_summary;
+  friend class thread_event_printer;
+  friend struct event_range;
+
+  const diagnostic_path &m_path;
+  const logical_location_manager &m_logical_loc_mgr;
+
+  const label_text m_name;
+
+  /* The "swimlane index" is the order in which this per_thread_summary
+     was created, for use when printing the events.  */
+  const unsigned m_swimlane_idx;
+
+  // The event ranges specific to this thread:
+  auto_vec<event_range *> m_event_ranges;
+
+  const diagnostic_event *m_last_event;
+
+  int m_min_depth;
+  int m_max_depth;
+};
+
+/* A range of consecutive events within a diagnostic_path, all within the
+   same thread, and with the same fndecl and stack_depth, and which are suitable
+   to print with a single call to diagnostic_show_locus.  */
+struct event_range
+{
+  /* A struct for tracking the mergability of labels on a particular
+     source line.  In particular, track information about links between
+     labels to ensure that we only consolidate events involving links
+     that the source-printing code is able to handle (splitting them
+     otherwise).  */
+  struct per_source_line_info
+  {
+    void init (int line)
+    {
+      m_line = line;
+      m_has_in_edge = false;
+      m_has_out_edge = false;
+      m_min_label_source_column = INT_MAX;
+      m_max_label_source_column = INT_MIN;
+    }
+
+    /* Return true if our source-printing/labelling/linking code can handle
+       the events already on this source line, *and* a new event at COLUMN.  */
+    bool
+    can_add_label_for_event_p (bool has_in_edge,
+                              const diagnostic_event *prev_event,
+                              bool has_out_edge,
+                              int column) const
+    {
+      /* Any existing in-edge has to be the left-most label on its
+        source line.  */
+      if (m_has_in_edge && column < m_min_label_source_column)
+       return false;
+      /* Any existing out-edge has to be the right-most label on its
+        source line.  */
+      if (m_has_out_edge && column > m_max_label_source_column)
+       return false;
+      /* Can't have more than one in-edge.  */
+      if (m_has_in_edge && has_in_edge)
+       return false;
+      /* Can't have more than one out-edge.  */
+      if (m_has_out_edge && has_out_edge)
+       return false;
+
+      if (has_in_edge)
+       {
+         /* Any new in-edge needs to be the left-most label on its
+            source line.  */
+         if (column > m_min_label_source_column)
+           return false;
+
+         gcc_assert (prev_event);
+         const location_t prev_loc = prev_event->get_location ();
+         expanded_location prev_exploc
+           = linemap_client_expand_location_to_spelling_point
+               (line_table, prev_loc, LOCATION_ASPECT_CARET);
+         /* The destination in-edge's line number has to be <= the
+            source out-edge's line number (if any).  */
+         if (prev_exploc.line >= m_line)
+           return false;
+       }
+
+      /* Any new out-edge needs to be the right-most label on its
+        source line.  */
+      if (has_out_edge)
+       if (column < m_max_label_source_column)
+         return false;
+
+      /* All checks passed; we can add the new event at COLUMN.  */
+      return true;
+    }
+
+    void
+    add_label_for_event (bool has_in_edge, bool has_out_edge, int column)
+    {
+      if (has_in_edge)
+       m_has_in_edge = true;
+      if (has_out_edge)
+       m_has_out_edge = true;
+      m_min_label_source_column = std::min (m_min_label_source_column, column);
+      m_max_label_source_column = std::max (m_max_label_source_column, column);
+    }
+
+    int m_line;
+    bool m_has_in_edge;
+    bool m_has_out_edge;
+    int m_min_label_source_column;
+    int m_max_label_source_column;
+  };
+
+  event_range (const diagnostic_path &path,
+              const pretty_printer &ref_pp,
+              unsigned start_idx,
+              const diagnostic_event &initial_event,
+              per_thread_summary &t,
+              bool show_event_links,
+              bool colorize_labels,
+              bool allow_emojis)
+  : m_path (path),
+    m_initial_event (initial_event),
+    m_logical_loc (initial_event.get_logical_location ()),
+    m_stack_depth (initial_event.get_stack_depth ()),
+    m_start_idx (start_idx), m_end_idx (start_idx),
+    m_path_label (path, ref_pp,
+                 start_idx, colorize_labels, allow_emojis),
+    m_richloc (initial_event.get_location (), &m_path_label, nullptr),
+    m_thread_id (initial_event.get_thread_id ()),
+    m_per_thread_summary (t),
+    m_show_event_links (show_event_links)
+  {
+    if (m_show_event_links)
+      {
+       expanded_location exploc
+         = linemap_client_expand_location_to_spelling_point
+         (line_table, initial_event.get_location (), LOCATION_ASPECT_CARET);
+       per_source_line_info &source_line_info
+         = get_per_source_line_info (exploc.line);
+
+       const diagnostic_event *prev_thread_event = t.m_last_event;
+       const bool has_in_edge
+         = (prev_thread_event
+            ? prev_thread_event->connect_to_next_event_p ()
+            : false);
+       const bool has_out_edge = initial_event.connect_to_next_event_p ();
+
+       source_line_info.add_label_for_event
+         (has_in_edge, has_out_edge, exploc.column);
+      }
+  }
+
+  per_source_line_info &
+  get_per_source_line_info (int source_line)
+  {
+    bool existed = false;
+    per_source_line_info &result
+      = m_source_line_info_map.get_or_insert (source_line, &existed);
+    if (!existed)
+      result.init (source_line);
+    return result;
+  }
+
+  bool maybe_add_event (const path_print_policy &policy,
+                       const diagnostic_event &new_ev,
+                       unsigned new_ev_idx,
+                       bool check_rich_locations)
+  {
+    if (!can_consolidate_events (m_path,
+                                m_initial_event, m_start_idx,
+                                new_ev, new_ev_idx,
+                                check_rich_locations))
+      return false;
+
+    /* Verify compatibility of the new label and existing labels
+       with respect to the link-printing code.  */
+    expanded_location exploc
+      = linemap_client_expand_location_to_spelling_point
+      (line_table, new_ev.get_location (), LOCATION_ASPECT_CARET);
+    per_source_line_info &source_line_info
+      = get_per_source_line_info (exploc.line);
+    const diagnostic_event *prev_event = nullptr;
+    if (new_ev_idx > 0)
+      prev_event = &m_path.get_event (new_ev_idx - 1);
+    const bool has_in_edge = (prev_event
+                             ? prev_event->connect_to_next_event_p ()
+                             : false);
+    const bool has_out_edge = new_ev.connect_to_next_event_p ();
+    if (m_show_event_links)
+      if (!source_line_info.can_add_label_for_event_p
+         (has_in_edge, prev_event,
+          has_out_edge, exploc.column))
+       return false;
+
+    /* Potentially verify that the locations are sufficiently close.  */
+    if (check_rich_locations)
+      if (!m_richloc.add_location_if_nearby (policy.get_source_policy (),
+                                            new_ev.get_location (),
+                                            false, &m_path_label))
+       return false;
+
+    m_end_idx = new_ev_idx;
+    m_per_thread_summary.m_last_event = &new_ev;
+
+    if (m_show_event_links)
+      source_line_info.add_label_for_event
+       (has_in_edge, has_out_edge, exploc.column);
+
+    return true;
+  }
+
+  /* Print the events in this range to PP, typically as a single
+     call to diagnostic_show_locus.  */
+
+  void print (pretty_printer &pp,
+             diagnostic_text_output_format &text_output,
+             diagnostic_source_effect_info *effect_info)
+  {
+    location_t initial_loc = m_initial_event.get_location ();
+
+    diagnostic_context &dc = text_output.get_context ();
+
+    /* 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.m_source_printing.enabled)
+      {
+       expanded_location exploc
+         = linemap_client_expand_location_to_spelling_point
+         (line_table, initial_loc, LOCATION_ASPECT_CARET);
+       if (exploc.file != LOCATION_FILE (dc.m_last_location))
+         {
+           diagnostic_location_print_policy loc_policy (text_output);
+           diagnostic_start_span (&dc) (loc_policy, &pp, 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 (get_pure_location (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);
+           pp_printf (&pp, " %@: ", &event_id);
+           iter_event.print_desc (pp);
+           pp_newline (&pp);
+         }
+       return;
+      }
+
+    /* Call diagnostic_show_locus to show the events using labels.  */
+    diagnostic_show_locus (&dc, text_output.get_source_printing_options (),
+                          &m_richloc, DK_DIAGNOSTIC_PATH, &pp,
+                          effect_info);
+
+    /* 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 (text_output, initial_loc);
+      }
+  }
+
+  const diagnostic_path &m_path;
+  const diagnostic_event &m_initial_event;
+  logical_location m_logical_loc;
+  int m_stack_depth;
+  unsigned m_start_idx;
+  unsigned m_end_idx;
+  path_label m_path_label;
+  gcc_rich_location m_richloc;
+  diagnostic_thread_id_t m_thread_id;
+  per_thread_summary &m_per_thread_summary;
+  hash_map<int_hash<int, -1, -2>,
+          per_source_line_info> m_source_line_info_map;
+  bool m_show_event_links;
+};
+
+/* A struct for grouping together the events in a diagnostic_path into
+   ranges of events, partitioned by thread and by stack frame (i.e. by fndecl
+   and stack depth).  */
+
+struct path_summary
+{
+  path_summary (const path_print_policy &policy,
+               const pretty_printer &ref_pp,
+               const diagnostic_path &path,
+               bool check_rich_locations,
+               bool colorize = false,
+               bool show_event_links = true);
+
+  const logical_location_manager &get_logical_location_manager () const
+  {
+    return m_logical_loc_mgr;
+  }
+  unsigned get_num_ranges () const { return m_ranges.length (); }
+  bool multithreaded_p () const { return m_per_thread_summary.length () > 1; }
+
+  const per_thread_summary &get_events_for_thread_id (diagnostic_thread_id_t tid)
+  {
+    per_thread_summary **slot = m_thread_id_to_events.get (tid);
+    gcc_assert (slot);
+    gcc_assert (*slot);
+    return **slot;
+  }
+
+  const logical_location_manager &m_logical_loc_mgr;
+  auto_delete_vec <event_range> m_ranges;
+  auto_delete_vec <per_thread_summary> m_per_thread_summary;
+  hash_map<int_hash<diagnostic_thread_id_t, -1, -2>,
+          per_thread_summary *> m_thread_id_to_events;
+
+private:
+  per_thread_summary &
+  get_or_create_events_for_thread_id (const diagnostic_path &path,
+                                     diagnostic_thread_id_t tid)
+  {
+    if (per_thread_summary **slot = m_thread_id_to_events.get (tid))
+      return **slot;
+
+    const diagnostic_thread &thread = path.get_thread (tid);
+    per_thread_summary *pts
+      = new per_thread_summary (path,
+                               m_logical_loc_mgr,
+                               thread.get_name (false),
+                               m_per_thread_summary.length ());
+    m_thread_id_to_events.put (tid, pts);
+    m_per_thread_summary.safe_push (pts);
+    return *pts;
+  }
+};
+
+/* Return true iff there is more than one stack frame used by the events
+   of this thread.  */
+
+bool
+per_thread_summary::interprocedural_p () const
+{
+  if (m_event_ranges.is_empty ())
+    return false;
+  int first_stack_depth = m_event_ranges[0]->m_stack_depth;
+  for (auto range : m_event_ranges)
+    {
+      if (!m_path.same_function_p (m_event_ranges[0]->m_start_idx,
+                                  range->m_start_idx))
+         return true;
+      if (range->m_stack_depth != first_stack_depth)
+       return true;
+    }
+  return false;
+}
+
+/* path_summary's ctor.  */
+
+path_summary::path_summary (const path_print_policy &policy,
+                           const pretty_printer &ref_pp,
+                           const diagnostic_path &path,
+                           bool check_rich_locations,
+                           bool colorize,
+                           bool show_event_links)
+: m_logical_loc_mgr (path.get_logical_location_manager ())
+{
+  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);
+      const diagnostic_thread_id_t thread_id = event.get_thread_id ();
+      per_thread_summary &pts
+       = get_or_create_events_for_thread_id (path, thread_id);
+
+      pts.update_depth_limits (event.get_stack_depth ());
+
+      if (cur_event_range)
+       if (cur_event_range->maybe_add_event (policy,
+                                             event,
+                                             idx, check_rich_locations))
+         continue;
+
+      auto theme = policy.get_diagram_theme ();
+      const bool allow_emojis = theme ? theme->emojis_p () : false;
+      cur_event_range = new event_range (path, ref_pp,
+                                        idx, event, pts,
+                                        show_event_links,
+                                        colorize,
+                                        allow_emojis);
+      m_ranges.safe_push (cur_event_range);
+      pts.m_event_ranges.safe_push (cur_event_range);
+      pts.m_last_event = &event;
+    }
+}
+
+/* Write SPACES to PP.  */
+
+static void
+write_indent (pretty_printer *pp, int spaces)
+{
+  for (int i = 0; i < spaces; i++)
+    pp_space (pp);
+}
+
+static const int base_indent = 2;
+static const int per_frame_indent = 2;
+
+/* A bundle of state for printing event_range instances for a particular
+   thread.  */
+
+class thread_event_printer
+{
+public:
+  thread_event_printer (const per_thread_summary &t, bool show_depths)
+  : m_per_thread_summary (t),
+    m_show_depths (show_depths),
+    m_cur_indent (base_indent),
+    m_vbar_column_for_depth (),
+    m_num_printed (0)
+  {
+  }
+
+  /* Get the previous event_range within this thread, if any.  */
+  const event_range *get_any_prev_range () const
+  {
+    if (m_num_printed > 0)
+      return m_per_thread_summary.m_event_ranges[m_num_printed - 1];
+    else
+      return nullptr;
+  }
+
+  /* Get the next event_range within this thread, if any.  */
+  const event_range *get_any_next_range () const
+  {
+    if (m_num_printed < m_per_thread_summary.m_event_ranges.length () - 1)
+      return m_per_thread_summary.m_event_ranges[m_num_printed + 1];
+    else
+      return nullptr;
+  }
+
+  void
+  print_swimlane_for_event_range (diagnostic_text_output_format &text_output,
+                                 pretty_printer *pp,
+                                 const logical_location_manager &logical_loc_mgr,
+                                 event_range *range,
+                                 diagnostic_source_effect_info *effect_info)
+  {
+    gcc_assert (pp);
+    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));
+
+    text_art::ascii_theme fallback_theme;
+    text_art::theme *theme = text_output.get_diagram_theme ();
+    if (!theme)
+      theme = &fallback_theme;
+
+    cppchar_t depth_marker_char = theme->get_cppchar
+      (text_art::theme::cell_kind::INTERPROCEDURAL_DEPTH_MARKER);
+    /* e.g. "|".  */
+
+    const bool interprocedural_p = m_per_thread_summary.interprocedural_p ();
+
+    write_indent (pp, m_cur_indent);
+    if (const event_range *prev_range = get_any_prev_range ())
+      {
+       if (range->m_stack_depth > prev_range->m_stack_depth)
+         {
+           gcc_assert (interprocedural_p);
+           /* Show pushed stack frame(s).  */
+           cppchar_t left = theme->get_cppchar
+             (text_art::theme::cell_kind::INTERPROCEDURAL_PUSH_FRAME_LEFT);
+           cppchar_t middle = theme->get_cppchar
+             (text_art::theme::cell_kind::INTERPROCEDURAL_PUSH_FRAME_MIDDLE);
+           cppchar_t right = theme->get_cppchar
+             (text_art::theme::cell_kind::INTERPROCEDURAL_PUSH_FRAME_RIGHT);
+           /* e.g. "+--> ".  */
+           pp_string (pp, start_line_color);
+           pp_unicode_character (pp, left);
+           pp_unicode_character (pp, middle);
+           pp_unicode_character (pp, middle);
+           pp_unicode_character (pp, right);
+           pp_space (pp);
+           pp_string (pp, end_line_color);
+           m_cur_indent += 5;
+         }
+      }
+    if (range->m_logical_loc)
+      {
+       label_text name
+         (logical_loc_mgr.get_name_for_path_output (range->m_logical_loc));
+       if (name.get ())
+         pp_printf (pp, "%qs: ", name.get ());
+      }
+    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 (m_show_depths)
+      pp_printf (pp, " (depth %i)", range->m_stack_depth);
+    pp_newline (pp);
+
+    /* Print a run of events.  */
+    if (interprocedural_p)
+      {
+       write_indent (pp, m_cur_indent + per_frame_indent);
+       pp_string (pp, start_line_color);
+       pp_unicode_character (pp, depth_marker_char);
+       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, m_cur_indent + per_frame_indent);
+         pp_string (&tmp_pp, start_line_color);
+         pp_unicode_character (&tmp_pp, depth_marker_char);
+         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 (*pp, text_output, effect_info);
+       pp_set_prefix (pp, saved_prefix);
+
+       write_indent (pp, m_cur_indent + per_frame_indent);
+       pp_string (pp, start_line_color);
+       pp_unicode_character (pp, depth_marker_char);
+       pp_string (pp, end_line_color);
+       pp_newline (pp);
+      }
+    else
+      range->print (*pp, text_output, effect_info);
+
+    if (const event_range *next_range = get_any_next_range ())
+      {
+       if (range->m_stack_depth > next_range->m_stack_depth)
+         {
+           if (m_vbar_column_for_depth.get (next_range->m_stack_depth))
+             {
+               /* Show returning from stack frame(s), by printing
+                  something like:
+                  "                   |\n"
+                  "     <-------------+\n"
+                  "     |\n".  */
+               gcc_assert (interprocedural_p);
+               cppchar_t left = theme->get_cppchar
+                 (text_art::theme::cell_kind::INTERPROCEDURAL_POP_FRAMES_LEFT);
+               cppchar_t middle = theme->get_cppchar
+                 (text_art::theme::cell_kind::INTERPROCEDURAL_POP_FRAMES_MIDDLE);
+               cppchar_t right = theme->get_cppchar
+                 (text_art::theme::cell_kind::INTERPROCEDURAL_POP_FRAMES_RIGHT);
+               int vbar_for_next_frame
+                 = *m_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_unicode_character (pp, left);
+               for (int i = indent_for_next_frame + per_frame_indent;
+                    i < m_cur_indent + per_frame_indent - 1; i++)
+                 pp_unicode_character (pp, middle);
+               pp_unicode_character (pp, right);
+               pp_string (pp, end_line_color);
+               pp_newline (pp);
+               m_cur_indent = indent_for_next_frame;
+
+               write_indent (pp, vbar_for_next_frame);
+               pp_string (pp, start_line_color);
+               pp_unicode_character (pp, depth_marker_char);
+               pp_string (pp, end_line_color);
+               pp_newline (pp);
+             }
+           else
+             {
+               /* Handle disjoint paths (e.g. a callback at some later
+                  time).  */
+               m_cur_indent = base_indent;
+             }
+         }
+       else if (range->m_stack_depth < next_range->m_stack_depth)
+         {
+           /* Prepare to show pushed stack frame.  */
+           gcc_assert (interprocedural_p);
+           gcc_assert (range->m_stack_depth != EMPTY);
+           gcc_assert (range->m_stack_depth != DELETED);
+           m_vbar_column_for_depth.put (range->m_stack_depth,
+                                        m_cur_indent + per_frame_indent);
+           m_cur_indent += per_frame_indent;
+         }
+      }
+
+    m_num_printed++;
+  }
+
+  int get_cur_indent () const { return m_cur_indent; }
+
+private:
+  const per_thread_summary &m_per_thread_summary;
+  bool m_show_depths;
+
+  /* Print the ranges.  */
+  int m_cur_indent;
+
+  /* Keep track of column numbers of existing '|' characters for
+     stack depths we've already printed.  */
+  static const int EMPTY = -1;
+  static const int DELETED = -2;
+  typedef int_hash <int, EMPTY, DELETED> vbar_hash;
+  hash_map <vbar_hash, int> m_vbar_column_for_depth;
+
+  /* How many event ranges within this swimlane have we printed.
+     This is the index of the next event_range to print.  */
+  unsigned  m_num_printed;
+};
+
+/* Print path_summary PS to TEXT_OUTPUT, 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.  */
+
+static void
+print_path_summary_as_text (const path_summary &ps,
+                           diagnostic_text_output_format &text_output,
+                           bool show_depths)
+{
+  pretty_printer *const pp = text_output.get_printer ();
+
+  std::vector<thread_event_printer> thread_event_printers;
+  for (auto t : ps.m_per_thread_summary)
+    thread_event_printers.push_back (thread_event_printer (*t, show_depths));
+
+  unsigned i;
+  event_range *range;
+  int last_out_edge_column = -1;
+  FOR_EACH_VEC_ELT (ps.m_ranges, i, range)
+    {
+      const int swimlane_idx
+       = range->m_per_thread_summary.get_swimlane_index ();
+      if (ps.multithreaded_p ())
+       if (i == 0 || ps.m_ranges[i - 1]->m_thread_id != range->m_thread_id)
+         {
+           if (i > 0)
+             pp_newline (pp);
+           pp_printf (pp, "Thread: %qs",
+                      range->m_per_thread_summary.get_name ());
+           pp_newline (pp);
+         }
+      thread_event_printer &tep = thread_event_printers[swimlane_idx];
+      /* Wire up any trailing out-edge from previous range to leading in-edge
+        of this range.  */
+      diagnostic_source_effect_info effect_info;
+      effect_info.m_leading_in_edge_column = last_out_edge_column;
+      tep.print_swimlane_for_event_range (text_output, pp,
+                                         ps.get_logical_location_manager (),
+                                         range, &effect_info);
+      last_out_edge_column = effect_info.m_trailing_out_edge_column;
+    }
+}
+
+} /* end of anonymous namespace for path-printing code.  */
+
+class element_event_desc : public pp_element
+{
+public:
+  element_event_desc (const diagnostic_event &event)
+  : m_event (event)
+  {
+  }
+
+  void add_to_phase_2 (pp_markup::context &ctxt) final override
+  {
+    auto pp = ctxt.m_pp.clone ();
+    m_event.print_desc (*pp.get ());
+    pp_string (&ctxt.m_pp, pp_formatted_text (pp.get ()));
+  }
+
+private:
+  const diagnostic_event &m_event;
+};
+
+/* Print PATH according to the context's path_format.  */
+
+void
+diagnostic_text_output_format::print_path (const diagnostic_path &path)
+{
+  const unsigned num_events = path.num_events ();
+
+  switch (get_context ().get_path_format ())
+    {
+    case DPF_NONE:
+      /* Do nothing.  */
+      return;
+
+    case DPF_SEPARATE_EVENTS:
+      {
+       /* A note per event.  */
+       auto &logical_loc_mgr = path.get_logical_location_manager ();
+       for (unsigned i = 0; i < num_events; i++)
+         {
+           const diagnostic_event &event = path.get_event (i);
+           element_event_desc e_event_desc (event);
+           diagnostic_event_id_t event_id (i);
+           if (get_context ().show_path_depths_p ())
+             {
+               int stack_depth = event.get_stack_depth ();
+               /* -fdiagnostics-path-format=separate-events doesn't print
+                  fndecl information, so with -fdiagnostics-show-path-depths
+                  print the fndecls too, if any.  */
+               if (logical_location logical_loc
+                     = event.get_logical_location ())
+                 {
+                   label_text name
+                     (logical_loc_mgr.get_name_for_path_output (logical_loc));
+                   inform (event.get_location (),
+                           "%@ %e (fndecl %qs, depth %i)",
+                           &event_id, &e_event_desc,
+                           name.get (), stack_depth);
+                 }
+               else
+                 inform (event.get_location (),
+                         "%@ %e (depth %i)",
+                         &event_id, &e_event_desc,
+                         stack_depth);
+             }
+           else
+             inform (event.get_location (),
+                     "%@ %e", &event_id, &e_event_desc);
+         }
+      }
+      break;
+
+    case DPF_INLINE_EVENTS:
+      {
+       /* Consolidate related events.  */
+       path_print_policy policy (*this);
+       pretty_printer *const pp = get_printer ();
+       const bool check_rich_locations = true;
+       const bool colorize = pp_show_color (pp);
+       const bool show_event_links = m_source_printing.show_event_links_p;
+       path_summary summary (policy,
+                             *pp,
+                             path,
+                             check_rich_locations,
+                             colorize,
+                             show_event_links);
+       char *saved_prefix = pp_take_prefix (pp);
+       pp_set_prefix (pp, NULL);
+       print_path_summary_as_text (summary, *this,
+                                   get_context ().show_path_depths_p ());
+       pp_flush (pp);
+       pp_set_prefix (pp, saved_prefix);
+      }
+      break;
+    }
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Return true iff all events in PATH have locations for which column data
+   is available, so that selftests that require precise string output can
+   bail out for awkward line_table cases.  */
+
+static bool
+path_events_have_column_data_p (const diagnostic_path &path)
+{
+  for (unsigned idx = 0; idx < path.num_events (); idx++)
+    {
+      location_t event_loc = path.get_event (idx).get_location ();
+      if (line_table->get_pure_location (event_loc)
+         > LINE_MAP_MAX_LOCATION_WITH_COLS)
+       return false;
+      if (line_table->get_start (event_loc) > LINE_MAP_MAX_LOCATION_WITH_COLS)
+       return false;
+      if (line_table->get_finish (event_loc) > LINE_MAP_MAX_LOCATION_WITH_COLS)
+       return false;
+    }
+  return true;
+}
+
+/* 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 ());
+
+  test_diagnostic_context dc;
+  diagnostic_text_output_format text_output (dc);
+  path_print_policy policy (text_output);
+  path_summary summary (policy, *event_pp, path, false);
+  ASSERT_EQ (summary.get_num_ranges (), 0);
+
+  print_path_summary_as_text (summary, text_output, true);
+  ASSERT_STREQ ("",
+               pp_formatted_text (text_output.get_printer ()));
+}
+
+/* Verify that print_path_summary works on a purely intraprocedural path.  */
+
+static void
+test_intraprocedural_path (pretty_printer *event_pp)
+{
+  test_diagnostic_path path (event_pp);
+  const char *const funcname = "foo";
+  path.add_event (UNKNOWN_LOCATION, funcname, 0, "first %qs", "free");
+  path.add_event (UNKNOWN_LOCATION, funcname, 0, "double %qs", "free");
+
+  ASSERT_FALSE (path.interprocedural_p ());
+
+  test_diagnostic_context dc;
+  diagnostic_text_output_format text_output (dc);
+  path_print_policy policy (text_output);
+  path_summary summary (policy, *event_pp, path, false, false, false);
+  ASSERT_EQ (summary.get_num_ranges (), 1);
+
+  print_path_summary_as_text (summary, text_output, true);
+  ASSERT_STREQ ("  `foo': events 1-2 (depth 0)\n"
+               " (1): first `free'\n"
+               " (2): double `free'\n",
+               pp_formatted_text (text_output.get_printer ()));
+}
+
+/* Verify that print_path_summary works on an interprocedural path.  */
+
+static void
+test_interprocedural_path_1 (pretty_printer *event_pp)
+{
+  test_diagnostic_path path (event_pp);
+  path.add_entry ("test", 0);
+  path.add_call ("test", 0, "make_boxed_int");
+  path.add_call ("make_boxed_int", 1, "wrapped_malloc");
+  path.add_event (UNKNOWN_LOCATION,
+                 "wrapped_malloc", 2, "calling malloc");
+  path.add_return ("test", 0);
+  path.add_call ("test", 0, "free_boxed_int");
+  path.add_call ("free_boxed_int", 1, "wrapped_free");
+  path.add_event (UNKNOWN_LOCATION, "wrapped_free", 2, "calling free");
+  path.add_return ("test", 0);
+  path.add_call ("test", 0, "free_boxed_int");
+  path.add_call ("free_boxed_int", 1, "wrapped_free");
+  path.add_event (UNKNOWN_LOCATION, "wrapped_free", 2, "calling free");
+  ASSERT_EQ (path.num_events (), 18);
+
+  ASSERT_TRUE (path.interprocedural_p ());
+
+  {
+    test_diagnostic_context dc;
+    diagnostic_text_output_format text_output (dc, nullptr, false);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, *event_pp, path, false);
+    ASSERT_EQ (summary.get_num_ranges (), 9);
+
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
+    print_path_summary_as_text (summary, text_output, 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 (text_output.get_printer ()));
+  }
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
+    diagnostic_text_output_format text_output (dc);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, *event_pp, path, false);
+    print_path_summary_as_text (summary, text_output, 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 (text_output.get_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)
+{
+  test_diagnostic_path path (event_pp);
+  path.add_entry ("foo", 0);
+  path.add_call ("foo", 0, "bar");
+  path.add_call ("bar", 1, "baz");
+  path.add_return ("bar", 1);
+  path.add_call ("bar", 1, "baz");
+  ASSERT_EQ (path.num_events (), 8);
+
+  ASSERT_TRUE (path.interprocedural_p ());
+
+  {
+    test_diagnostic_context dc;
+    diagnostic_text_output_format text_output (dc);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, *event_pp, path, false);
+    ASSERT_EQ (summary.get_num_ranges (), 5);
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
+    print_path_summary_as_text (summary, text_output, 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 (text_output.get_printer ()));
+  }
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
+    diagnostic_text_output_format text_output (dc);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, *event_pp, path, false);
+    print_path_summary_as_text (summary, text_output, 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 (text_output.get_printer ()));
+  }
+}
+
+/* Verify that print_path_summary is sane in the face of a recursive
+   diagnostic_path.  */
+
+static void
+test_recursion (pretty_printer *event_pp)
+{
+ test_diagnostic_path path (event_pp);
+  path.add_entry ("factorial", 0);
+  for (int depth = 0; depth < 3; depth++)
+    path.add_call ("factorial", depth, "factorial");
+  ASSERT_EQ (path.num_events (), 7);
+
+  ASSERT_TRUE (path.interprocedural_p ());
+
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
+
+    diagnostic_text_output_format text_output (dc);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, *event_pp, path, false);
+    ASSERT_EQ (summary.get_num_ranges (), 4);
+
+    print_path_summary_as_text (summary, text_output, 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 (text_output.get_printer ()));
+  }
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
+
+    diagnostic_text_output_format text_output (dc);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, *event_pp, path, false);
+    print_path_summary_as_text (summary, text_output, 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 (text_output.get_printer ()));
+  }
+}
+
+/* Helper class for writing tests of control flow visualization.  */
+
+class control_flow_test
+{
+public:
+  control_flow_test (const location &loc,
+                    const line_table_case &case_,
+                    const char *content)
+  : m_tmp_file (loc, ".c", content,
+               /* gcc_rich_location::add_location_if_nearby implicitly
+                  uses global_dc's file_cache, so we need to evict
+                  tmp when we're done.  */
+               &global_dc->get_file_cache ()),
+    m_ltt (case_)
+  {
+    m_ord_map
+      = linemap_check_ordinary (linemap_add (line_table, LC_ENTER, false,
+                                            m_tmp_file.get_filename (), 0));
+    linemap_line_start (line_table, 1, 100);
+  }
+
+  location_t get_line_and_column (int line, int column)
+  {
+    return linemap_position_for_line_and_column (line_table, m_ord_map,
+                                                line, column);
+  }
+
+  location_t get_line_and_columns (int line, int first_column, int last_column)
+  {
+    return get_line_and_columns (line,
+                                first_column, first_column, last_column);
+  }
+
+  location_t get_line_and_columns (int line,
+                                  int first_column,
+                                  int caret_column,
+                                  int last_column)
+  {
+    return make_location (get_line_and_column (line, caret_column),
+                         get_line_and_column (line, first_column),
+                         get_line_and_column (line, last_column));
+  }
+
+private:
+  temp_source_file m_tmp_file;
+  line_table_test m_ltt;
+  const line_map_ordinary *m_ord_map;
+};
+
+/* Example of event edges where all events can go in the same layout,
+   testing the 6 combinations of:
+   - ASCII vs Unicode vs event links off
+   - line numbering on and off.  */
+
+static void
+test_control_flow_1 (const line_table_case &case_,
+                    pretty_printer *event_pp)
+{
+  /* Create a tempfile and write some text to it.
+     ...000000000111111111122222222223333333333.
+     ...123456789012345678901234567890123456789.  */
+  const char *content
+    = ("int test (int *p)\n" /* line 1.  */
+       "{\n"                 /* line 2.  */
+       "  if (p)\n"          /* line 3.  */
+       "    return 0;\n"     /* line 4.  */
+       "  return *p;\n"      /* line 5.  */
+       "}\n");               /* line 6.  */
+
+  control_flow_test t (SELFTEST_LOCATION, case_, content);
+
+  const location_t conditional = t.get_line_and_column (3, 7);
+  const location_t cfg_dest = t.get_line_and_column (5, 10);
+
+  test_diagnostic_path path (event_pp);
+  path.add_event (conditional, nullptr, 0,
+                 "following %qs branch (when %qs is NULL)...",
+                 "false", "p");
+  path.connect_to_next_event ();
+
+  path.add_event (cfg_dest, nullptr, 0,
+                 "...to here");
+  path.add_event (cfg_dest, nullptr, 0,
+                 "dereference of NULL %qs",
+                 "p");
+
+  if (!path_events_have_column_data_p (path))
+    return;
+
+
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
+    dc.m_source_printing.show_event_links_p = true;
+    diagnostic_text_output_format text_output (dc);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, *event_pp, path, true);
+    print_path_summary_as_text (summary, text_output, false);
+    ASSERT_STREQ
+      ("  events 1-3\n"
+       "FILENAME:3:7:\n"
+       "   if (p)\n"
+       "       ^\n"
+       "       |\n"
+       "       (1) following `false' branch (when `p' is NULL)... ->-+\n"
+       "                                                             |\n"
+       "FILENAME:5:10:\n"
+       "                                                             |\n"
+       "+------------------------------------------------------------+\n"
+       "|  return *p;\n"
+       "|         ~\n"
+       "|         |\n"
+       "+-------->(2) ...to here\n"
+       "          (3) dereference of NULL `p'\n",
+       pp_formatted_text (text_output.get_printer ()));
+  }
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
+    dc.m_source_printing.show_event_links_p = false;
+    diagnostic_text_output_format text_output (dc);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, *event_pp, path, true);
+    print_path_summary_as_text (summary, text_output, false);
+    ASSERT_STREQ
+      ("  events 1-3\n"
+       "FILENAME:3:7:\n"
+       "   if (p)\n"
+       "       ^\n"
+       "       |\n"
+       "       (1) following `false' branch (when `p' is NULL)...\n"
+       "FILENAME:5:10:\n"
+       "   return *p;\n"
+       "          ~\n"
+       "          |\n"
+       "          (2) ...to here\n"
+       "          (3) dereference of NULL `p'\n",
+       pp_formatted_text (text_output.get_printer ()));
+  }
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
+    dc.m_source_printing.show_line_numbers_p = true;
+    dc.m_source_printing.show_event_links_p = true;
+    diagnostic_text_output_format text_output (dc);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, *event_pp, path, true);
+    print_path_summary_as_text (summary, text_output, false);
+    ASSERT_STREQ
+      ("  events 1-3\n"
+       "FILENAME:3:7:\n"
+       "    3 |   if (p)\n"
+       "      |       ^\n"
+       "      |       |\n"
+       "      |       (1) following `false' branch (when `p' is NULL)... ->-+\n"
+       "      |                                                             |\n"
+       "      |                                                             |\n"
+       "      |+------------------------------------------------------------+\n"
+       "    4 ||    return 0;\n"
+       "    5 ||  return *p;\n"
+       "      ||         ~\n"
+       "      ||         |\n"
+       "      |+-------->(2) ...to here\n"
+       "      |          (3) dereference of NULL `p'\n",
+       pp_formatted_text (text_output.get_printer ()));
+  }
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
+    dc.m_source_printing.show_line_numbers_p = true;
+    dc.m_source_printing.show_event_links_p = false;
+    diagnostic_text_output_format text_output (dc);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, *event_pp, path, true);
+    print_path_summary_as_text (summary, text_output, false);
+    ASSERT_STREQ
+      ("  events 1-3\n"
+       "FILENAME:3:7:\n"
+       "    3 |   if (p)\n"
+       "      |       ^\n"
+       "      |       |\n"
+       "      |       (1) following `false' branch (when `p' is NULL)...\n"
+       "    4 |     return 0;\n"
+       "    5 |   return *p;\n"
+       "      |          ~\n"
+       "      |          |\n"
+       "      |          (2) ...to here\n"
+       "      |          (3) dereference of NULL `p'\n",
+       pp_formatted_text (text_output.get_printer ()));
+  }
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
+    dc.m_source_printing.show_event_links_p = true;
+    diagnostic_text_output_format text_output (dc);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, *event_pp, path, true);
+    print_path_summary_as_text (summary, text_output, false);
+    ASSERT_STREQ
+      ("  events 1-3\n"
+       "FILENAME:3:7:\n"
+       "   if (p)\n"
+       "       ^\n"
+       "       |\n"
+       "       (1) following `false' branch (when `p' is NULL)... ─>─┐\n"
+       "                                                             │\n"
+       "FILENAME:5:10:\n"
+       "                                                             │\n"
+       "┌────────────────────────────────────────────────────────────┘\n"
+       "│  return *p;\n"
+       "│         ~\n"
+       "│         |\n"
+       "└────────>(2) ...to here\n"
+       "          (3) dereference of NULL `p'\n",
+       pp_formatted_text (text_output.get_printer ()));
+  }
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
+    dc.m_source_printing.show_event_links_p = true;
+    dc.m_source_printing.show_line_numbers_p = true;
+    diagnostic_text_output_format text_output (dc);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, *event_pp, path, true);
+    print_path_summary_as_text (summary, text_output, false);
+    ASSERT_STREQ
+      ("  events 1-3\n"
+       "FILENAME:3:7:\n"
+       "    3 |   if (p)\n"
+       "      |       ^\n"
+       "      |       |\n"
+       "      |       (1) following `false' branch (when `p' is NULL)... ─>─┐\n"
+       "      |                                                             │\n"
+       "      |                                                             │\n"
+       "      |┌────────────────────────────────────────────────────────────┘\n"
+       "    4 |│    return 0;\n"
+       "    5 |│  return *p;\n"
+       "      |│         ~\n"
+       "      |│         |\n"
+       "      |└────────>(2) ...to here\n"
+       "      |          (3) dereference of NULL `p'\n",
+       pp_formatted_text (text_output.get_printer ()));
+  }
+}
+
+/* Complex example involving a backedge.  */
+
+static void
+test_control_flow_2 (const line_table_case &case_,
+                    pretty_printer *event_pp)
+{
+  /* Create a tempfile and write some text to it.
+     ...000000000111111111122222222223333333333.
+     ...123456789012345678901234567890123456789.  */
+  const char *content
+    = ("int for_loop_noop_next (struct node *n)\n" /* <--------- line 1.  */
+       "{\n" /* <----------------------------------------------- line 2.  */
+       "  int sum = 0;\n" /* <---------------------------------- line 3.  */
+       "  for (struct node *iter = n; iter; iter->next)\n" /* <- line 4.  */
+       "    sum += n->val;\n" /* <------------------------------ line 5.  */
+       "  return sum;\n" /* <----------------------------------- line 6.  */
+       "}\n");  /* <-------------------------------------------- line 7.  */
+  /* Adapted from infinite-loop-linked-list.c where
+     "iter->next" should be "iter = iter->next".  */
+
+  control_flow_test t (SELFTEST_LOCATION, case_, content);
+
+  const location_t iter_test = t.get_line_and_columns (4, 31, 34);
+  const location_t loop_body_start = t.get_line_and_columns (5, 12, 17);
+  const location_t loop_body_end = t.get_line_and_columns (5, 5, 9, 17);
+
+  test_diagnostic_path path (event_pp);
+  path.add_event (iter_test, nullptr, 0, "infinite loop here");
+
+  path.add_event (iter_test, nullptr, 0, "looping from here...");
+  path.connect_to_next_event ();
+
+  path.add_event (loop_body_start, nullptr, 0, "...to here");
+
+  path.add_event (loop_body_end, nullptr, 0, "looping back...");
+  path.connect_to_next_event ();
+
+  path.add_event (iter_test, nullptr, 0, "...to here");
+
+  if (!path_events_have_column_data_p (path))
+    return;
+
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
+    dc.m_source_printing.show_event_links_p = true;
+    dc.m_source_printing.show_line_numbers_p = true;
+    diagnostic_text_output_format text_output (dc);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, *event_pp, path, true);
+    print_path_summary_as_text (summary, text_output, false);
+    ASSERT_STREQ
+      ("  events 1-3\n"
+       "FILENAME:4:31:\n"
+       "    4 |   for (struct node *iter = n; iter; iter->next)\n"
+       "      |                               ^~~~\n"
+       "      |                               |\n"
+       "      |                               (1) infinite loop here\n"
+       "      |                               (2) looping from here... ->-+\n"
+       "      |                                                           |\n"
+       "      |                                                           |\n"
+       "      |+----------------------------------------------------------+\n"
+       "    5 ||    sum += n->val;\n"
+       "      ||           ~~~~~~              \n"
+       "      ||           |\n"
+       "      |+---------->(3) ...to here\n"
+       /* We need to start an new event_range here as event (4) is to the
+         left of event (3), and thus (4) would mess up the in-edge to (3).  */
+       "  event 4\n"
+       "    5 |     sum += n->val;\n"
+       "      |     ~~~~^~~~~~~~~\n"
+       "      |         |\n"
+       "      |         (4) looping back... ->-+\n"
+       "      |                                |\n"
+       /* We need to start an new event_range here as event (4) with an
+         out-edge is on a later line (line 5) than its destination event (5),
+         on line 4.  */
+       "  event 5\n"
+       "      |                                |\n"
+       "      |+-------------------------------+\n"
+       "    4 ||  for (struct node *iter = n; iter; iter->next)\n"
+       "      ||                              ^~~~\n"
+       "      ||                              |\n"
+       "      |+----------------------------->(5) ...to here\n",
+       pp_formatted_text (text_output.get_printer ()));
+  }
+}
+
+/* Complex example involving a backedge and both an in-edge and out-edge
+   on the same line.  */
+
+static void
+test_control_flow_3 (const line_table_case &case_,
+                    pretty_printer *event_pp)
+{
+  /* Create a tempfile and write some text to it.
+     ...000000000111111111122222222223333333333.
+     ...123456789012345678901234567890123456789.  */
+  const char *content
+    = ("void test_missing_comparison_in_for_condition_1 (int n)\n"
+       "{\n" /* <------------------------- line 2.  */
+       "  for (int i = 0; n; i++)\n" /* <- line 3.  */
+       "    {\n" /* <--------------------- line 4.  */
+       "    }\n" /* <--------------------- line 5.  */
+       "}\n"); /* <----------------------- line 6.  */
+  /* Adapted from infinite-loop-1.c where the condition should have been
+     "i < n", rather than just "n".  */
+
+  control_flow_test t (SELFTEST_LOCATION, case_, content);
+
+  const location_t iter_test = t.get_line_and_column (3, 19);
+  const location_t iter_next = t.get_line_and_columns (3, 22, 24);
+
+  test_diagnostic_path path (event_pp);
+  path.add_event (iter_test, nullptr, 0, "infinite loop here");
+
+  path.add_event (iter_test, nullptr, 0, "looping from here...");
+  path.connect_to_next_event ();
+
+  path.add_event (iter_next, nullptr, 0, "...to here");
+
+  path.add_event (iter_next, nullptr, 0, "looping back...");
+  path.connect_to_next_event ();
+
+  path.add_event (iter_test, nullptr, 0, "...to here");
+
+  if (!path_events_have_column_data_p (path))
+    return;
+
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
+    dc.m_source_printing.show_event_links_p = true;
+    dc.m_source_printing.show_line_numbers_p = true;
+    diagnostic_text_output_format text_output (dc);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, *event_pp, path, true);
+    print_path_summary_as_text (summary, text_output, false);
+    ASSERT_STREQ
+      ("  events 1-2\n"
+       "FILENAME:3:19:\n"
+       "    3 |   for (int i = 0; n; i++)\n"
+       "      |                   ^\n"
+       "      |                   |\n"
+       "      |                   (1) infinite loop here\n"
+       "      |                   (2) looping from here... ->-+\n"
+       "      |                                               |\n"
+       "  events 3-4\n"
+       "      |                                               |\n"
+       "      |+----------------------------------------------+\n"
+       "    3 ||  for (int i = 0; n; i++)\n"
+       "      ||                     ^~~\n"
+       "      ||                     |\n"
+       "      |+-------------------->(3) ...to here\n"
+       "      |                      (4) looping back... ->-+\n"
+       "      |                                             |\n"
+       /* We need to start an new event_range here as event (4) with an
+         out-edge is on the same line as its destination event (5), but
+         to the right, which we can't handle as a single event_range. */
+       "  event 5\n"
+       "      |                                             |\n"
+       "      |+--------------------------------------------+\n"
+       "    3 ||  for (int i = 0; n; i++)\n"
+       "      ||                  ^\n"
+       "      ||                  |\n"
+       "      |+----------------->(5) ...to here\n",
+       pp_formatted_text (text_output.get_printer ()));
+  }
+}
+
+/* Implementation of ASSERT_CFG_EDGE_PATH_STREQ.  */
+
+static void
+assert_cfg_edge_path_streq (const location &loc,
+                           pretty_printer *event_pp,
+                           const location_t src_loc,
+                           const location_t dst_loc,
+                           const char *expected_str)
+{
+  test_diagnostic_path path (event_pp);
+  path.add_event (src_loc, nullptr, 0, "from here...");
+  path.connect_to_next_event ();
+
+  path.add_event (dst_loc, nullptr, 0, "...to here");
+
+  if (!path_events_have_column_data_p (path))
+    return;
+
+  test_diagnostic_context dc;
+  dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
+  dc.m_source_printing.show_event_links_p = true;
+  dc.m_source_printing.show_line_numbers_p = true;
+  diagnostic_text_output_format text_output (dc);
+  path_print_policy policy (text_output);
+  path_summary summary (policy, *event_pp, path, true);
+  print_path_summary_as_text (summary, text_output, false);
+  ASSERT_STREQ_AT (loc, expected_str,
+                  pp_formatted_text (text_output.get_printer ()));
+}
+
+/* Assert that if we make a path with an event with "from here..." at SRC_LOC
+   leading to an event "...to here" at DST_LOC that we print the path
+   as EXPECTED_STR.  */
+
+#define ASSERT_CFG_EDGE_PATH_STREQ(SRC_LOC, DST_LOC, EXPECTED_STR) \
+  assert_cfg_edge_path_streq ((SELFTEST_LOCATION), (event_pp),    \
+                             (SRC_LOC), (DST_LOC), (EXPECTED_STR))
+
+/* Various examples of edge, trying to cover all combinations of:
+   - relative x positive of src label and dst label
+   - relative y position of labels:
+     - on same line
+     - on next line
+     - on line after next
+     - big gap, where src is before dst
+     - big gap, where src is after dst
+   and other awkward cases.  */
+
+static void
+test_control_flow_4 (const line_table_case &case_,
+                    pretty_printer *event_pp)
+{
+  std::string many_lines;
+  for (int i = 1; i <= 100; i++)
+    /* ............000000000111
+       ............123456789012.  */
+    many_lines += "LHS      RHS\n";
+  control_flow_test t (SELFTEST_LOCATION, case_, many_lines.c_str ());
+
+  /* Same line.  */
+  {
+    /* LHS -> RHS.  */
+    ASSERT_CFG_EDGE_PATH_STREQ
+      (t.get_line_and_columns (3, 1, 3),
+       t.get_line_and_columns (3, 10, 12),
+       ("  event 1\n"
+       "FILENAME:3:1:\n"
+       "    3 | LHS      RHS\n"
+       "      | ^~~\n"
+       "      | |\n"
+       "      | (1) from here... ->-+\n"
+       "      |                     |\n"
+       "  event 2\n"
+       "      |                     |\n"
+       "      |+--------------------+\n"
+       "    3 ||LHS      RHS\n"
+       "      ||         ^~~\n"
+       "      ||         |\n"
+       "      |+-------->(2) ...to here\n"));
+
+    /*  RHS -> LHS.  */
+    ASSERT_CFG_EDGE_PATH_STREQ
+      (t.get_line_and_columns (3, 10, 12),
+       t.get_line_and_columns (3, 1, 3),
+       ("  event 1\n"
+       "FILENAME:3:10:\n"
+       "    3 | LHS      RHS\n"
+       "      |          ^~~\n"
+       "      |          |\n"
+       "      |          (1) from here... ->-+\n"
+       "      |                              |\n"
+       "  event 2\n"
+       "      |                              |\n"
+       "      |+-----------------------------+\n"
+       "    3 ||LHS      RHS\n"
+       "      ||^~~\n"
+       "      |||\n"
+       "      |+(2) ...to here\n"));
+  }
+
+  /* Next line.  */
+  {
+    /* LHS -> RHS.  */
+    ASSERT_CFG_EDGE_PATH_STREQ
+      (t.get_line_and_columns (3, 1, 3),
+       t.get_line_and_columns (4, 5, 7),
+       ("  events 1-2\n"
+       "FILENAME:3:1:\n"
+       "    3 | LHS      RHS\n"
+       "      | ^~~\n"
+       "      | |\n"
+       "      | (1) from here... ->-+\n"
+       "      |                     |\n"
+       "      |                     |\n"
+       "      |+--------------------+\n"
+       "    4 ||LHS      RHS\n"
+       "      ||    ~~~\n"
+       "      ||    |\n"
+       "      |+--->(2) ...to here\n"));
+
+    /*  RHS -> LHS.  */
+    ASSERT_CFG_EDGE_PATH_STREQ
+      (t.get_line_and_columns (3, 10, 12),
+       t.get_line_and_columns (4, 1, 3),
+       ("  events 1-2\n"
+       "FILENAME:3:10:\n"
+       "    3 | LHS      RHS\n"
+       "      |          ^~~\n"
+       "      |          |\n"
+       "      |          (1) from here... ->-+\n"
+       "      |                              |\n"
+       "      |                              |\n"
+       "      |+-----------------------------+\n"
+       "    4 ||LHS      RHS\n"
+       "      ||~~~       \n"
+       "      |||\n"
+       "      |+(2) ...to here\n"));
+  }
+
+  /* Line after next.  */
+  {
+    /* LHS -> RHS.  */
+    ASSERT_CFG_EDGE_PATH_STREQ
+      (t.get_line_and_columns (3, 1, 3),
+       t.get_line_and_columns (5, 10, 12),
+       ("  events 1-2\n"
+       "FILENAME:3:1:\n"
+       "    3 | LHS      RHS\n"
+       "      | ^~~\n"
+       "      | |\n"
+       "      | (1) from here... ->-+\n"
+       "      |                     |\n"
+       "      |                     |\n"
+       "      |+--------------------+\n"
+       "    4 ||LHS      RHS\n"
+       "    5 ||LHS      RHS\n"
+       "      ||         ~~~\n"
+       "      ||         |\n"
+       "      |+-------->(2) ...to here\n"));
+
+    /*  RHS -> LHS.  */
+    ASSERT_CFG_EDGE_PATH_STREQ
+      (t.get_line_and_columns (3, 10, 12),
+       t.get_line_and_columns (5, 1, 3),
+       ("  events 1-2\n"
+       "FILENAME:3:10:\n"
+       "    3 | LHS      RHS\n"
+       "      |          ^~~\n"
+       "      |          |\n"
+       "      |          (1) from here... ->-+\n"
+       "      |                              |\n"
+       "      |                              |\n"
+       "      |+-----------------------------+\n"
+       "    4 ||LHS      RHS\n"
+       "    5 ||LHS      RHS\n"
+       "      ||~~~       \n"
+       "      |||\n"
+       "      |+(2) ...to here\n"));
+  }
+
+  /* Big gap, increasing line number.  */
+  {
+    /* LHS -> RHS.  */
+    ASSERT_CFG_EDGE_PATH_STREQ
+      (t.get_line_and_columns (3, 1, 3),
+       t.get_line_and_columns (97, 10, 12),
+       ("  events 1-2\n"
+       "FILENAME:3:1:\n"
+       "    3 | LHS      RHS\n"
+       "      | ^~~\n"
+       "      | |\n"
+       "      | (1) from here... ->-+\n"
+       "      |                     |\n"
+       "......\n"
+       "      |                     |\n"
+       "      |+--------------------+\n"
+       "   97 ||LHS      RHS\n"
+       "      ||         ~~~\n"
+       "      ||         |\n"
+       "      |+-------->(2) ...to here\n"));
+
+    /*  RHS -> LHS.  */
+    ASSERT_CFG_EDGE_PATH_STREQ
+      (t.get_line_and_columns (3, 10, 12),
+       t.get_line_and_columns (97, 1, 3),
+       ("  events 1-2\n"
+       "FILENAME:3:10:\n"
+       "    3 | LHS      RHS\n"
+       "      |          ^~~\n"
+       "      |          |\n"
+       "      |          (1) from here... ->-+\n"
+       "      |                              |\n"
+       "......\n"
+       "      |                              |\n"
+       "      |+-----------------------------+\n"
+       "   97 ||LHS      RHS\n"
+       "      ||~~~       \n"
+       "      |||\n"
+       "      |+(2) ...to here\n"));
+  }
+
+  /* Big gap, decreasing line number.  */
+  {
+    /* LHS -> RHS.  */
+    ASSERT_CFG_EDGE_PATH_STREQ
+      (t.get_line_and_columns (97, 1, 3),
+       t.get_line_and_columns (3, 10, 12),
+       ("  event 1\n"
+       "FILENAME:97:1:\n"
+       "   97 | LHS      RHS\n"
+       "      | ^~~\n"
+       "      | |\n"
+       "      | (1) from here... ->-+\n"
+       "      |                     |\n"
+       "  event 2\n"
+       "      |                     |\n"
+       "      |+--------------------+\n"
+       "    3 ||LHS      RHS\n"
+       "      ||         ^~~\n"
+       "      ||         |\n"
+       "      |+-------->(2) ...to here\n"));
+
+    /*  RHS -> LHS.  */
+    ASSERT_CFG_EDGE_PATH_STREQ
+      (t.get_line_and_columns (97, 10, 12),
+       t.get_line_and_columns (3, 1, 3),
+       ("  event 1\n"
+       "FILENAME:97:10:\n"
+       "   97 | LHS      RHS\n"
+       "      |          ^~~\n"
+       "      |          |\n"
+       "      |          (1) from here... ->-+\n"
+       "      |                              |\n"
+       "  event 2\n"
+       "      |                              |\n"
+       "      |+-----------------------------+\n"
+       "    3 ||LHS      RHS\n"
+       "      ||^~~\n"
+       "      |||\n"
+       "      |+(2) ...to here\n"));
+  }
+
+  /* Unknown src.  */
+  {
+    ASSERT_CFG_EDGE_PATH_STREQ
+      (UNKNOWN_LOCATION,
+       t.get_line_and_columns (3, 10, 12),
+       ("  event 1\n"
+       " (1): from here...\n"
+       "  event 2\n"
+       "FILENAME:3:10:\n"
+       "    3 | LHS      RHS\n"
+       "      |          ^~~\n"
+       "      |          |\n"
+       "      |+-------->(2) ...to here\n"));
+  }
+
+  /* Unknown dst.  */
+  {
+    ASSERT_CFG_EDGE_PATH_STREQ
+      (t.get_line_and_columns (3, 1, 3),
+       UNKNOWN_LOCATION,
+       ("  event 1\n"
+       "FILENAME:3:1:\n"
+       "    3 | LHS      RHS\n"
+       "      | ^~~\n"
+       "      | |\n"
+       "      | (1) from here... ->-+\n"
+       "      |                     |\n"
+       "  event 2\n"
+       "FILENAME:\n"
+       " (2): ...to here\n"));
+  }
+}
+
+/* Another complex example, adapted from data-model-20.c.  */
+
+static void
+test_control_flow_5 (const line_table_case &case_,
+                    pretty_printer *event_pp)
+{
+  /* Create a tempfile and write some text to it.
+     ...000000000111111111122222222223333333333444444444455555555556666666666.
+     ...123456789012345678901234567890123456789012345678901234567890123456789.  */
+  const char *content
+    = ("  if ((arr = (struct foo **)malloc(n * sizeof(struct foo *))) == NULL)\n"
+       "    return NULL;\n" /* <------------------------- line 2.  */
+       "\n" /* <----------------------------------------- line 3.  */
+       "  for (i = 0; i < n; i++) {\n" /* <-------------- line 4.  */
+       "    if ((arr[i] = (struct foo *)malloc(sizeof(struct foo))) == NULL) {\n");
+
+  control_flow_test t (SELFTEST_LOCATION, case_, content);
+
+  test_diagnostic_path path (event_pp);
+  /* (1) */
+  path.add_event (t.get_line_and_column (1, 6), nullptr, 0,
+                 "following %qs branch (when %qs is non-NULL)...",
+                 "false", "arr");
+  path.connect_to_next_event ();
+
+  /* (2) */
+  path.add_event (t.get_line_and_columns (4, 8, 10, 12), nullptr, 0,
+                 "...to here");
+
+  /* (3) */
+  path.add_event (t.get_line_and_columns (4, 15, 17, 19), nullptr, 0,
+                 "following %qs branch (when %qs)...",
+                 "true", "i < n");
+  path.connect_to_next_event ();
+
+  /* (4) */
+  path.add_event (t.get_line_and_column (5, 13), nullptr, 0,
+                 "...to here");
+
+  /* (5) */
+  path.add_event (t.get_line_and_columns (5, 33, 58), nullptr, 0,
+                 "allocated here");
+
+  if (!path_events_have_column_data_p (path))
+    return;
+
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
+    dc.m_source_printing.show_event_links_p = true;
+    dc.m_source_printing.show_line_numbers_p = true;
+    diagnostic_text_output_format text_output (dc);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, *event_pp, path, true);
+    print_path_summary_as_text (summary, text_output, false);
+    ASSERT_STREQ
+      ("  events 1-5\n"
+       "FILENAME:1:6:\n"
+       "    1 |   if ((arr = (struct foo **)malloc(n * sizeof(struct foo *))) == NULL)\n"
+       "      |      ^\n"
+       "      |      |\n"
+       "      |      (1) following `false' branch (when `arr' is non-NULL)... ->-+\n"
+       "      |                                                                  |\n"
+       "......\n"
+       "      |                                                                  |\n"
+       "      |+-----------------------------------------------------------------+\n"
+       "    4 ||  for (i = 0; i < n; i++) {\n"
+       "      ||       ~~~~~  ~~~~~\n"
+       "      ||         |      |\n"
+       "      ||         |      (3) following `true' branch (when `i < n')... ->-+\n"
+       "      |+-------->(2) ...to here                                          |\n"
+       "      |                                                                  |\n"
+       "      |                                                                  |\n"
+       "      |+-----------------------------------------------------------------+\n"
+       "    5 ||    if ((arr[i] = (struct foo *)malloc(sizeof(struct foo))) == NULL) {\n"
+       "      ||            ~                   ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+       "      ||            |                   |\n"
+       "      |+----------->(4) ...to here      (5) allocated here\n",
+       pp_formatted_text (text_output.get_printer ()));
+  }
+}
+
+/* Another complex example, adapted from loop-3.c.  */
+
+static void
+test_control_flow_6 (const line_table_case &case_,
+                    pretty_printer *event_pp)
+{
+  /* Create a tempfile and write some text to it.
+     ...000000000111111111122222222223333333.
+     ...123456789012345678901234567890123456.  */
+  const char *content
+    = ("#include <stdlib.h>\n" /* <------------------ line 1.  */
+       "\n" /* <------------------------------------- line 2.  */
+       "void test(int c)\n" /* <--------------------- line 3.  */
+       "{\n" /* <------------------------------------ line 4.  */
+       "  int i;\n" /* <----------------------------- line 5.  */
+       "  char *buffer = (char*)malloc(256);\n" /* <- line 6.  */
+       "\n" /* <------------------------------------- line 7.  */
+       "  for (i=0; i<255; i++) {\n" /* <------------ line 8.  */
+       "    buffer[i] = c;\n" /* <------------------- line 9.  */
+       "\n" /* <------------------------------------- line 10.  */
+       "    free(buffer);\n" /* <-------------------- line 11.  */
+       "  }\n"); /* <-------------------------------- line 12.  */
+
+  control_flow_test t (SELFTEST_LOCATION, case_, content);
+
+  test_diagnostic_path path (event_pp);
+  /* (1) */
+  path.add_event (t.get_line_and_columns (6, 25, 35), nullptr, 0,
+                 "allocated here");
+
+  /* (2) */
+  path.add_event (t.get_line_and_columns (8, 13, 14, 17), nullptr, 0,
+                 "following %qs branch (when %qs)...",
+                 "true", "i <= 254");
+  path.connect_to_next_event ();
+
+  /* (3) */
+  path.add_event (t.get_line_and_columns (9, 5, 15, 17), nullptr, 0,
+                 "...to here");
+
+  /* (4) */
+  path.add_event (t.get_line_and_columns (8, 13, 14, 17), nullptr, 0,
+                 "following %qs branch (when %qs)...",
+                 "true", "i <= 254");
+  path.connect_to_next_event ();
+
+  /* (5) */
+  path.add_event (t.get_line_and_columns (9, 5, 15, 17), nullptr, 0,
+                 "...to here");
+
+  if (!path_events_have_column_data_p (path))
+    return;
+
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
+    dc.m_source_printing.show_event_links_p = true;
+    dc.m_source_printing.show_line_numbers_p = true;
+    diagnostic_text_output_format text_output (dc);
+    path_print_policy policy (text_output);
+    path_summary summary (policy, *event_pp, path, true);
+    print_path_summary_as_text (summary, text_output, false);
+    ASSERT_STREQ
+      ("  events 1-3\n"
+       "FILENAME:6:25:\n"
+       "    6 |   char *buffer = (char*)malloc(256);\n"
+       "      |                         ^~~~~~~~~~~\n"
+       "      |                         |\n"
+       "      |                         (1) allocated here\n"
+       "    7 | \n"
+       "    8 |   for (i=0; i<255; i++) {\n"
+       "      |             ~~~~~        \n"
+       "      |              |\n"
+       "      |              (2) following `true' branch (when `i <= 254')... ->-+\n"
+       "      |                                                                  |\n"
+       "      |                                                                  |\n"
+       "      |+-----------------------------------------------------------------+\n"
+       "    9 ||    buffer[i] = c;\n"
+       "      ||    ~~~~~~~~~~~~~        \n"
+       "      ||              |\n"
+       "      |+------------->(3) ...to here\n"
+       "  events 4-5\n"
+       "    8 |   for (i=0; i<255; i++) {\n"
+       "      |             ~^~~~\n"
+       "      |              |\n"
+       "      |              (4) following `true' branch (when `i <= 254')... ->-+\n"
+       "      |                                                                  |\n"
+       "      |                                                                  |\n"
+       "      |+-----------------------------------------------------------------+\n"
+       "    9 ||    buffer[i] = c;\n"
+       "      ||    ~~~~~~~~~~~~~\n"
+       "      ||              |\n"
+       "      |+------------->(5) ...to here\n",
+       pp_formatted_text (text_output.get_printer ()));
+  }
+}
+
+static void
+control_flow_tests (const line_table_case &case_)
+{
+  pretty_printer pp;
+  pp_show_color (&pp) = false;
+
+  test_control_flow_1 (case_, &pp);
+  test_control_flow_2 (case_, &pp);
+  test_control_flow_3 (case_, &pp);
+  test_control_flow_4 (case_, &pp);
+  test_control_flow_5 (case_, &pp);
+  test_control_flow_6 (case_, &pp);
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+diagnostic_path_output_cc_tests ()
+{
+  pretty_printer pp;
+  pp_show_color (&pp) = false;
+
+  auto_fix_quotes fix_quotes;
+  test_empty_path (&pp);
+  test_intraprocedural_path (&pp);
+  test_interprocedural_path_1 (&pp);
+  test_interprocedural_path_2 (&pp);
+  test_recursion (&pp);
+  for_each_line_table_case (control_flow_tests);
+}
+
+} // namespace selftest
+
+#if __GNUC__ >= 10
+#  pragma GCC diagnostic pop
+#endif
+
+#endif /* #if CHECKING_P */
index 9340e4e3a297065ee2e2f34bf4b28d1a6ccfd4ba..7a9c051c534cc62ca89201c08e40b06afcc0ddea 100644 (file)
@@ -25,19 +25,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "system.h"
 #include "coretypes.h"
 #include "diagnostic.h"
-#include "diagnostic-macro-unwinding.h"
-#include "intl.h"
 #include "diagnostic-path.h"
-#include "gcc-rich-location.h"
-#include "diagnostic-color.h"
-#include "diagnostic-event-id.h"
-#include "diagnostic-label-effects.h"
-#include "pretty-print-markup.h"
-#include "selftest.h"
-#include "selftest-diagnostic.h"
-#include "selftest-diagnostic-path.h"
-#include "text-art/theme.h"
-#include "diagnostic-format-text.h"
 
 /* Disable warnings about missing quoting in GCC diagnostics for the print
    calls below.  */
@@ -226,2315 +214,6 @@ void debug (diagnostic_path *path)
   inform (&richloc, "debug path");
 }
 
-/* Anonymous namespace for path-printing code.  */
-
-namespace {
-
-/* A bundle of state for printing a path.  */
-
-class path_print_policy
-{
-public:
-  path_print_policy (const diagnostic_text_output_format &text_output)
-  : m_source_policy (text_output.get_context ())
-  {
-  }
-
-  text_art::theme *
-  get_diagram_theme () const
-  {
-    return  m_source_policy.get_diagram_theme ();
-  }
-
-  const diagnostic_source_print_policy &
-  get_source_policy () const { return m_source_policy; }
-
-private:
-  diagnostic_source_print_policy m_source_policy;
-};
-
-/* Subclass of range_label for showing a particular event
-   when 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,
-             const pretty_printer &ref_pp,
-             unsigned start_idx,
-             bool colorize,
-             bool allow_emojis)
-  : m_path (path),
-    m_ref_pp (ref_pp),
-    m_start_idx (start_idx), m_effects (*this),
-    m_colorize (colorize), m_allow_emojis (allow_emojis)
-  {}
-
-  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);
-
-    const diagnostic_event::meaning meaning (event.get_meaning ());
-
-    auto pp = m_ref_pp.clone ();
-    pp_show_color (pp.get ()) = m_colorize;
-    diagnostic_event_id_t event_id (event_idx);
-
-    pp_printf (pp.get (), "%@", &event_id);
-    pp_space (pp.get ());
-
-    if (meaning.m_verb == diagnostic_event::VERB_danger
-       && m_allow_emojis)
-      {
-       pp_unicode_character (pp.get (), 0x26A0); /* U+26A0 WARNING SIGN.  */
-       /* Append U+FE0F VARIATION SELECTOR-16 to select the emoji
-          variation of the char.  */
-       pp_unicode_character (pp.get (), 0xFE0F);
-       /* U+26A0 WARNING SIGN has East_Asian_Width == Neutral, but in its
-          emoji variant is printed (by vte at least) with a 2nd half
-          overlapping the next char.  Hence we add two spaces here: a space
-          to be covered by this overlap, plus another space of padding.  */
-       pp_string (pp.get (), "  ");
-      }
-
-    event.print_desc (*pp.get ());
-
-    label_text result
-      = label_text::take (xstrdup (pp_formatted_text (pp.get ())));
-    return result;
-  }
-
-  const label_effects *get_effects (unsigned /*range_idx*/) const
-  {
-    return &m_effects;
-  }
-
- private:
-  class path_label_effects : public label_effects
-  {
-  public:
-    path_label_effects (const path_label &path_label)
-    : m_path_label (path_label)
-    {
-    }
-    bool has_in_edge (unsigned range_idx) const final override
-    {
-      if (const diagnostic_event *prev_event
-           = m_path_label.get_prev_event (range_idx))
-       return prev_event->connect_to_next_event_p ();
-      return false;
-    }
-    bool has_out_edge (unsigned range_idx) const final override
-    {
-      const diagnostic_event &event = m_path_label.get_event (range_idx);
-      return event.connect_to_next_event_p ();
-    }
-
-  private:
-    const path_label &m_path_label;
-  };
-
-  const diagnostic_event &get_event (unsigned range_idx) const
-  {
-    unsigned event_idx = m_start_idx + range_idx;
-    return m_path.get_event (event_idx);
-  }
-
-  const diagnostic_event *get_prev_event (unsigned range_idx) const
-  {
-    if (m_start_idx + range_idx == 0)
-      return nullptr;
-    unsigned event_idx = m_start_idx + range_idx - 1;
-    return &m_path.get_event (event_idx);
-  }
-
-  const diagnostic_path &m_path;
-  const pretty_printer &m_ref_pp;
-  unsigned m_start_idx;
-  path_label_effects m_effects;
-  const bool m_colorize;
-  const bool m_allow_emojis;
-};
-
-/* 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_path &path,
-                       const diagnostic_event &e1,
-                       unsigned ev1_idx,
-                       const diagnostic_event &e2,
-                       unsigned ev2_idx,
-                       bool check_locations)
-{
-  if (e1.get_thread_id () != e2.get_thread_id ())
-    return false;
-
-  if (!path.same_function_p (ev1_idx, ev2_idx))
-    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;
-}
-
-struct event_range;
-struct path_summary;
-class thread_event_printer;
-
-/* A bundle of information about all of the events in a diagnostic_path
-   relating to a specific path, for use by path_summary.  */
-
-class per_thread_summary
-{
-public:
-  per_thread_summary (const diagnostic_path &path,
-                     const logical_location_manager &logical_loc_mgr,
-                     label_text name, unsigned swimlane_idx)
-  : m_path (path),
-    m_logical_loc_mgr (logical_loc_mgr),
-    m_name (std::move (name)),
-    m_swimlane_idx (swimlane_idx),
-    m_last_event (nullptr),
-    m_min_depth (INT_MAX),
-    m_max_depth (INT_MIN)
-  {}
-
-  void update_depth_limits (int stack_depth)
-  {
-    if (stack_depth < m_min_depth)
-      m_min_depth = stack_depth;
-    if (stack_depth > m_max_depth)
-      m_max_depth = stack_depth;
-  }
-
-  const char *get_name () const { return m_name.get (); }
-  unsigned get_swimlane_index () const { return m_swimlane_idx; }
-
-  bool interprocedural_p () const;
-
-private:
-  friend struct path_summary;
-  friend class thread_event_printer;
-  friend struct event_range;
-
-  const diagnostic_path &m_path;
-  const logical_location_manager &m_logical_loc_mgr;
-
-  const label_text m_name;
-
-  /* The "swimlane index" is the order in which this per_thread_summary
-     was created, for use when printing the events.  */
-  const unsigned m_swimlane_idx;
-
-  // The event ranges specific to this thread:
-  auto_vec<event_range *> m_event_ranges;
-
-  const diagnostic_event *m_last_event;
-
-  int m_min_depth;
-  int m_max_depth;
-};
-
-/* A range of consecutive events within a diagnostic_path, all within the
-   same thread, and with the same fndecl and stack_depth, and which are suitable
-   to print with a single call to diagnostic_show_locus.  */
-struct event_range
-{
-  /* A struct for tracking the mergability of labels on a particular
-     source line.  In particular, track information about links between
-     labels to ensure that we only consolidate events involving links
-     that the source-printing code is able to handle (splitting them
-     otherwise).  */
-  struct per_source_line_info
-  {
-    void init (int line)
-    {
-      m_line = line;
-      m_has_in_edge = false;
-      m_has_out_edge = false;
-      m_min_label_source_column = INT_MAX;
-      m_max_label_source_column = INT_MIN;
-    }
-
-    /* Return true if our source-printing/labelling/linking code can handle
-       the events already on this source line, *and* a new event at COLUMN.  */
-    bool
-    can_add_label_for_event_p (bool has_in_edge,
-                              const diagnostic_event *prev_event,
-                              bool has_out_edge,
-                              int column) const
-    {
-      /* Any existing in-edge has to be the left-most label on its
-        source line.  */
-      if (m_has_in_edge && column < m_min_label_source_column)
-       return false;
-      /* Any existing out-edge has to be the right-most label on its
-        source line.  */
-      if (m_has_out_edge && column > m_max_label_source_column)
-       return false;
-      /* Can't have more than one in-edge.  */
-      if (m_has_in_edge && has_in_edge)
-       return false;
-      /* Can't have more than one out-edge.  */
-      if (m_has_out_edge && has_out_edge)
-       return false;
-
-      if (has_in_edge)
-       {
-         /* Any new in-edge needs to be the left-most label on its
-            source line.  */
-         if (column > m_min_label_source_column)
-           return false;
-
-         gcc_assert (prev_event);
-         const location_t prev_loc = prev_event->get_location ();
-         expanded_location prev_exploc
-           = linemap_client_expand_location_to_spelling_point
-               (line_table, prev_loc, LOCATION_ASPECT_CARET);
-         /* The destination in-edge's line number has to be <= the
-            source out-edge's line number (if any).  */
-         if (prev_exploc.line >= m_line)
-           return false;
-       }
-
-      /* Any new out-edge needs to be the right-most label on its
-        source line.  */
-      if (has_out_edge)
-       if (column < m_max_label_source_column)
-         return false;
-
-      /* All checks passed; we can add the new event at COLUMN.  */
-      return true;
-    }
-
-    void
-    add_label_for_event (bool has_in_edge, bool has_out_edge, int column)
-    {
-      if (has_in_edge)
-       m_has_in_edge = true;
-      if (has_out_edge)
-       m_has_out_edge = true;
-      m_min_label_source_column = std::min (m_min_label_source_column, column);
-      m_max_label_source_column = std::max (m_max_label_source_column, column);
-    }
-
-    int m_line;
-    bool m_has_in_edge;
-    bool m_has_out_edge;
-    int m_min_label_source_column;
-    int m_max_label_source_column;
-  };
-
-  event_range (const diagnostic_path &path,
-              const pretty_printer &ref_pp,
-              unsigned start_idx,
-              const diagnostic_event &initial_event,
-              per_thread_summary &t,
-              bool show_event_links,
-              bool colorize_labels,
-              bool allow_emojis)
-  : m_path (path),
-    m_initial_event (initial_event),
-    m_logical_loc (initial_event.get_logical_location ()),
-    m_stack_depth (initial_event.get_stack_depth ()),
-    m_start_idx (start_idx), m_end_idx (start_idx),
-    m_path_label (path, ref_pp,
-                 start_idx, colorize_labels, allow_emojis),
-    m_richloc (initial_event.get_location (), &m_path_label, nullptr),
-    m_thread_id (initial_event.get_thread_id ()),
-    m_per_thread_summary (t),
-    m_show_event_links (show_event_links)
-  {
-    if (m_show_event_links)
-      {
-       expanded_location exploc
-         = linemap_client_expand_location_to_spelling_point
-         (line_table, initial_event.get_location (), LOCATION_ASPECT_CARET);
-       per_source_line_info &source_line_info
-         = get_per_source_line_info (exploc.line);
-
-       const diagnostic_event *prev_thread_event = t.m_last_event;
-       const bool has_in_edge
-         = (prev_thread_event
-            ? prev_thread_event->connect_to_next_event_p ()
-            : false);
-       const bool has_out_edge = initial_event.connect_to_next_event_p ();
-
-       source_line_info.add_label_for_event
-         (has_in_edge, has_out_edge, exploc.column);
-      }
-  }
-
-  per_source_line_info &
-  get_per_source_line_info (int source_line)
-  {
-    bool existed = false;
-    per_source_line_info &result
-      = m_source_line_info_map.get_or_insert (source_line, &existed);
-    if (!existed)
-      result.init (source_line);
-    return result;
-  }
-
-  bool maybe_add_event (const path_print_policy &policy,
-                       const diagnostic_event &new_ev,
-                       unsigned new_ev_idx,
-                       bool check_rich_locations)
-  {
-    if (!can_consolidate_events (m_path,
-                                m_initial_event, m_start_idx,
-                                new_ev, new_ev_idx,
-                                check_rich_locations))
-      return false;
-
-    /* Verify compatibility of the new label and existing labels
-       with respect to the link-printing code.  */
-    expanded_location exploc
-      = linemap_client_expand_location_to_spelling_point
-      (line_table, new_ev.get_location (), LOCATION_ASPECT_CARET);
-    per_source_line_info &source_line_info
-      = get_per_source_line_info (exploc.line);
-    const diagnostic_event *prev_event = nullptr;
-    if (new_ev_idx > 0)
-      prev_event = &m_path.get_event (new_ev_idx - 1);
-    const bool has_in_edge = (prev_event
-                             ? prev_event->connect_to_next_event_p ()
-                             : false);
-    const bool has_out_edge = new_ev.connect_to_next_event_p ();
-    if (m_show_event_links)
-      if (!source_line_info.can_add_label_for_event_p
-         (has_in_edge, prev_event,
-          has_out_edge, exploc.column))
-       return false;
-
-    /* Potentially verify that the locations are sufficiently close.  */
-    if (check_rich_locations)
-      if (!m_richloc.add_location_if_nearby (policy.get_source_policy (),
-                                            new_ev.get_location (),
-                                            false, &m_path_label))
-       return false;
-
-    m_end_idx = new_ev_idx;
-    m_per_thread_summary.m_last_event = &new_ev;
-
-    if (m_show_event_links)
-      source_line_info.add_label_for_event
-       (has_in_edge, has_out_edge, exploc.column);
-
-    return true;
-  }
-
-  /* Print the events in this range to PP, typically as a single
-     call to diagnostic_show_locus.  */
-
-  void print (pretty_printer &pp,
-             diagnostic_text_output_format &text_output,
-             diagnostic_source_effect_info *effect_info)
-  {
-    location_t initial_loc = m_initial_event.get_location ();
-
-    diagnostic_context &dc = text_output.get_context ();
-
-    /* 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.m_source_printing.enabled)
-      {
-       expanded_location exploc
-         = linemap_client_expand_location_to_spelling_point
-         (line_table, initial_loc, LOCATION_ASPECT_CARET);
-       if (exploc.file != LOCATION_FILE (dc.m_last_location))
-         {
-           diagnostic_location_print_policy loc_policy (text_output);
-           diagnostic_start_span (&dc) (loc_policy, &pp, 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 (get_pure_location (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);
-           pp_printf (&pp, " %@: ", &event_id);
-           iter_event.print_desc (pp);
-           pp_newline (&pp);
-         }
-       return;
-      }
-
-    /* Call diagnostic_show_locus to show the events using labels.  */
-    diagnostic_show_locus (&dc, text_output.get_source_printing_options (),
-                          &m_richloc, DK_DIAGNOSTIC_PATH, &pp,
-                          effect_info);
-
-    /* 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 (text_output, initial_loc);
-      }
-  }
-
-  const diagnostic_path &m_path;
-  const diagnostic_event &m_initial_event;
-  logical_location m_logical_loc;
-  int m_stack_depth;
-  unsigned m_start_idx;
-  unsigned m_end_idx;
-  path_label m_path_label;
-  gcc_rich_location m_richloc;
-  diagnostic_thread_id_t m_thread_id;
-  per_thread_summary &m_per_thread_summary;
-  hash_map<int_hash<int, -1, -2>,
-          per_source_line_info> m_source_line_info_map;
-  bool m_show_event_links;
-};
-
-/* A struct for grouping together the events in a diagnostic_path into
-   ranges of events, partitioned by thread and by stack frame (i.e. by fndecl
-   and stack depth).  */
-
-struct path_summary
-{
-  path_summary (const path_print_policy &policy,
-               const pretty_printer &ref_pp,
-               const diagnostic_path &path,
-               bool check_rich_locations,
-               bool colorize = false,
-               bool show_event_links = true);
-
-  const logical_location_manager &get_logical_location_manager () const
-  {
-    return m_logical_loc_mgr;
-  }
-  unsigned get_num_ranges () const { return m_ranges.length (); }
-  bool multithreaded_p () const { return m_per_thread_summary.length () > 1; }
-
-  const per_thread_summary &get_events_for_thread_id (diagnostic_thread_id_t tid)
-  {
-    per_thread_summary **slot = m_thread_id_to_events.get (tid);
-    gcc_assert (slot);
-    gcc_assert (*slot);
-    return **slot;
-  }
-
-  const logical_location_manager &m_logical_loc_mgr;
-  auto_delete_vec <event_range> m_ranges;
-  auto_delete_vec <per_thread_summary> m_per_thread_summary;
-  hash_map<int_hash<diagnostic_thread_id_t, -1, -2>,
-          per_thread_summary *> m_thread_id_to_events;
-
-private:
-  per_thread_summary &
-  get_or_create_events_for_thread_id (const diagnostic_path &path,
-                                     diagnostic_thread_id_t tid)
-  {
-    if (per_thread_summary **slot = m_thread_id_to_events.get (tid))
-      return **slot;
-
-    const diagnostic_thread &thread = path.get_thread (tid);
-    per_thread_summary *pts
-      = new per_thread_summary (path,
-                               m_logical_loc_mgr,
-                               thread.get_name (false),
-                               m_per_thread_summary.length ());
-    m_thread_id_to_events.put (tid, pts);
-    m_per_thread_summary.safe_push (pts);
-    return *pts;
-  }
-};
-
-/* Return true iff there is more than one stack frame used by the events
-   of this thread.  */
-
-bool
-per_thread_summary::interprocedural_p () const
-{
-  if (m_event_ranges.is_empty ())
-    return false;
-  int first_stack_depth = m_event_ranges[0]->m_stack_depth;
-  for (auto range : m_event_ranges)
-    {
-      if (!m_path.same_function_p (m_event_ranges[0]->m_start_idx,
-                                  range->m_start_idx))
-         return true;
-      if (range->m_stack_depth != first_stack_depth)
-       return true;
-    }
-  return false;
-}
-
-/* path_summary's ctor.  */
-
-path_summary::path_summary (const path_print_policy &policy,
-                           const pretty_printer &ref_pp,
-                           const diagnostic_path &path,
-                           bool check_rich_locations,
-                           bool colorize,
-                           bool show_event_links)
-: m_logical_loc_mgr (path.get_logical_location_manager ())
-{
-  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);
-      const diagnostic_thread_id_t thread_id = event.get_thread_id ();
-      per_thread_summary &pts
-       = get_or_create_events_for_thread_id (path, thread_id);
-
-      pts.update_depth_limits (event.get_stack_depth ());
-
-      if (cur_event_range)
-       if (cur_event_range->maybe_add_event (policy,
-                                             event,
-                                             idx, check_rich_locations))
-         continue;
-
-      auto theme = policy.get_diagram_theme ();
-      const bool allow_emojis = theme ? theme->emojis_p () : false;
-      cur_event_range = new event_range (path, ref_pp,
-                                        idx, event, pts,
-                                        show_event_links,
-                                        colorize,
-                                        allow_emojis);
-      m_ranges.safe_push (cur_event_range);
-      pts.m_event_ranges.safe_push (cur_event_range);
-      pts.m_last_event = &event;
-    }
-}
-
-/* Write SPACES to PP.  */
-
-static void
-write_indent (pretty_printer *pp, int spaces)
-{
-  for (int i = 0; i < spaces; i++)
-    pp_space (pp);
-}
-
-static const int base_indent = 2;
-static const int per_frame_indent = 2;
-
-/* A bundle of state for printing event_range instances for a particular
-   thread.  */
-
-class thread_event_printer
-{
-public:
-  thread_event_printer (const per_thread_summary &t, bool show_depths)
-  : m_per_thread_summary (t),
-    m_show_depths (show_depths),
-    m_cur_indent (base_indent),
-    m_vbar_column_for_depth (),
-    m_num_printed (0)
-  {
-  }
-
-  /* Get the previous event_range within this thread, if any.  */
-  const event_range *get_any_prev_range () const
-  {
-    if (m_num_printed > 0)
-      return m_per_thread_summary.m_event_ranges[m_num_printed - 1];
-    else
-      return nullptr;
-  }
-
-  /* Get the next event_range within this thread, if any.  */
-  const event_range *get_any_next_range () const
-  {
-    if (m_num_printed < m_per_thread_summary.m_event_ranges.length () - 1)
-      return m_per_thread_summary.m_event_ranges[m_num_printed + 1];
-    else
-      return nullptr;
-  }
-
-  void
-  print_swimlane_for_event_range (diagnostic_text_output_format &text_output,
-                                 pretty_printer *pp,
-                                 const logical_location_manager &logical_loc_mgr,
-                                 event_range *range,
-                                 diagnostic_source_effect_info *effect_info)
-  {
-    gcc_assert (pp);
-    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));
-
-    text_art::ascii_theme fallback_theme;
-    text_art::theme *theme = text_output.get_diagram_theme ();
-    if (!theme)
-      theme = &fallback_theme;
-
-    cppchar_t depth_marker_char = theme->get_cppchar
-      (text_art::theme::cell_kind::INTERPROCEDURAL_DEPTH_MARKER);
-    /* e.g. "|".  */
-
-    const bool interprocedural_p = m_per_thread_summary.interprocedural_p ();
-
-    write_indent (pp, m_cur_indent);
-    if (const event_range *prev_range = get_any_prev_range ())
-      {
-       if (range->m_stack_depth > prev_range->m_stack_depth)
-         {
-           gcc_assert (interprocedural_p);
-           /* Show pushed stack frame(s).  */
-           cppchar_t left = theme->get_cppchar
-             (text_art::theme::cell_kind::INTERPROCEDURAL_PUSH_FRAME_LEFT);
-           cppchar_t middle = theme->get_cppchar
-             (text_art::theme::cell_kind::INTERPROCEDURAL_PUSH_FRAME_MIDDLE);
-           cppchar_t right = theme->get_cppchar
-             (text_art::theme::cell_kind::INTERPROCEDURAL_PUSH_FRAME_RIGHT);
-           /* e.g. "+--> ".  */
-           pp_string (pp, start_line_color);
-           pp_unicode_character (pp, left);
-           pp_unicode_character (pp, middle);
-           pp_unicode_character (pp, middle);
-           pp_unicode_character (pp, right);
-           pp_space (pp);
-           pp_string (pp, end_line_color);
-           m_cur_indent += 5;
-         }
-      }
-    if (range->m_logical_loc)
-      {
-       label_text name
-         (logical_loc_mgr.get_name_for_path_output (range->m_logical_loc));
-       if (name.get ())
-         pp_printf (pp, "%qs: ", name.get ());
-      }
-    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 (m_show_depths)
-      pp_printf (pp, " (depth %i)", range->m_stack_depth);
-    pp_newline (pp);
-
-    /* Print a run of events.  */
-    if (interprocedural_p)
-      {
-       write_indent (pp, m_cur_indent + per_frame_indent);
-       pp_string (pp, start_line_color);
-       pp_unicode_character (pp, depth_marker_char);
-       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, m_cur_indent + per_frame_indent);
-         pp_string (&tmp_pp, start_line_color);
-         pp_unicode_character (&tmp_pp, depth_marker_char);
-         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 (*pp, text_output, effect_info);
-       pp_set_prefix (pp, saved_prefix);
-
-       write_indent (pp, m_cur_indent + per_frame_indent);
-       pp_string (pp, start_line_color);
-       pp_unicode_character (pp, depth_marker_char);
-       pp_string (pp, end_line_color);
-       pp_newline (pp);
-      }
-    else
-      range->print (*pp, text_output, effect_info);
-
-    if (const event_range *next_range = get_any_next_range ())
-      {
-       if (range->m_stack_depth > next_range->m_stack_depth)
-         {
-           if (m_vbar_column_for_depth.get (next_range->m_stack_depth))
-             {
-               /* Show returning from stack frame(s), by printing
-                  something like:
-                  "                   |\n"
-                  "     <-------------+\n"
-                  "     |\n".  */
-               gcc_assert (interprocedural_p);
-               cppchar_t left = theme->get_cppchar
-                 (text_art::theme::cell_kind::INTERPROCEDURAL_POP_FRAMES_LEFT);
-               cppchar_t middle = theme->get_cppchar
-                 (text_art::theme::cell_kind::INTERPROCEDURAL_POP_FRAMES_MIDDLE);
-               cppchar_t right = theme->get_cppchar
-                 (text_art::theme::cell_kind::INTERPROCEDURAL_POP_FRAMES_RIGHT);
-               int vbar_for_next_frame
-                 = *m_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_unicode_character (pp, left);
-               for (int i = indent_for_next_frame + per_frame_indent;
-                    i < m_cur_indent + per_frame_indent - 1; i++)
-                 pp_unicode_character (pp, middle);
-               pp_unicode_character (pp, right);
-               pp_string (pp, end_line_color);
-               pp_newline (pp);
-               m_cur_indent = indent_for_next_frame;
-
-               write_indent (pp, vbar_for_next_frame);
-               pp_string (pp, start_line_color);
-               pp_unicode_character (pp, depth_marker_char);
-               pp_string (pp, end_line_color);
-               pp_newline (pp);
-             }
-           else
-             {
-               /* Handle disjoint paths (e.g. a callback at some later
-                  time).  */
-               m_cur_indent = base_indent;
-             }
-         }
-       else if (range->m_stack_depth < next_range->m_stack_depth)
-         {
-           /* Prepare to show pushed stack frame.  */
-           gcc_assert (interprocedural_p);
-           gcc_assert (range->m_stack_depth != EMPTY);
-           gcc_assert (range->m_stack_depth != DELETED);
-           m_vbar_column_for_depth.put (range->m_stack_depth,
-                                        m_cur_indent + per_frame_indent);
-           m_cur_indent += per_frame_indent;
-         }
-      }
-
-    m_num_printed++;
-  }
-
-  int get_cur_indent () const { return m_cur_indent; }
-
-private:
-  const per_thread_summary &m_per_thread_summary;
-  bool m_show_depths;
-
-  /* Print the ranges.  */
-  int m_cur_indent;
-
-  /* Keep track of column numbers of existing '|' characters for
-     stack depths we've already printed.  */
-  static const int EMPTY = -1;
-  static const int DELETED = -2;
-  typedef int_hash <int, EMPTY, DELETED> vbar_hash;
-  hash_map <vbar_hash, int> m_vbar_column_for_depth;
-
-  /* How many event ranges within this swimlane have we printed.
-     This is the index of the next event_range to print.  */
-  unsigned  m_num_printed;
-};
-
-/* Print path_summary PS to TEXT_OUTPUT, 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.  */
-
-static void
-print_path_summary_as_text (const path_summary &ps,
-                           diagnostic_text_output_format &text_output,
-                           bool show_depths)
-{
-  pretty_printer *const pp = text_output.get_printer ();
-
-  std::vector<thread_event_printer> thread_event_printers;
-  for (auto t : ps.m_per_thread_summary)
-    thread_event_printers.push_back (thread_event_printer (*t, show_depths));
-
-  unsigned i;
-  event_range *range;
-  int last_out_edge_column = -1;
-  FOR_EACH_VEC_ELT (ps.m_ranges, i, range)
-    {
-      const int swimlane_idx
-       = range->m_per_thread_summary.get_swimlane_index ();
-      if (ps.multithreaded_p ())
-       if (i == 0 || ps.m_ranges[i - 1]->m_thread_id != range->m_thread_id)
-         {
-           if (i > 0)
-             pp_newline (pp);
-           pp_printf (pp, "Thread: %qs",
-                      range->m_per_thread_summary.get_name ());
-           pp_newline (pp);
-         }
-      thread_event_printer &tep = thread_event_printers[swimlane_idx];
-      /* Wire up any trailing out-edge from previous range to leading in-edge
-        of this range.  */
-      diagnostic_source_effect_info effect_info;
-      effect_info.m_leading_in_edge_column = last_out_edge_column;
-      tep.print_swimlane_for_event_range (text_output, pp,
-                                         ps.get_logical_location_manager (),
-                                         range, &effect_info);
-      last_out_edge_column = effect_info.m_trailing_out_edge_column;
-    }
-}
-
-} /* end of anonymous namespace for path-printing code.  */
-
-class element_event_desc : public pp_element
-{
-public:
-  element_event_desc (const diagnostic_event &event)
-  : m_event (event)
-  {
-  }
-
-  void add_to_phase_2 (pp_markup::context &ctxt) final override
-  {
-    auto pp = ctxt.m_pp.clone ();
-    m_event.print_desc (*pp.get ());
-    pp_string (&ctxt.m_pp, pp_formatted_text (pp.get ()));
-  }
-
-private:
-  const diagnostic_event &m_event;
-};
-
-/* Print PATH according to the context's path_format.  */
-
-void
-diagnostic_text_output_format::print_path (const diagnostic_path &path)
-{
-  const unsigned num_events = path.num_events ();
-
-  switch (get_context ().get_path_format ())
-    {
-    case DPF_NONE:
-      /* Do nothing.  */
-      return;
-
-    case DPF_SEPARATE_EVENTS:
-      {
-       /* A note per event.  */
-       auto &logical_loc_mgr = path.get_logical_location_manager ();
-       for (unsigned i = 0; i < num_events; i++)
-         {
-           const diagnostic_event &event = path.get_event (i);
-           element_event_desc e_event_desc (event);
-           diagnostic_event_id_t event_id (i);
-           if (get_context ().show_path_depths_p ())
-             {
-               int stack_depth = event.get_stack_depth ();
-               /* -fdiagnostics-path-format=separate-events doesn't print
-                  fndecl information, so with -fdiagnostics-show-path-depths
-                  print the fndecls too, if any.  */
-               if (logical_location logical_loc
-                     = event.get_logical_location ())
-                 {
-                   label_text name
-                     (logical_loc_mgr.get_name_for_path_output (logical_loc));
-                   inform (event.get_location (),
-                           "%@ %e (fndecl %qs, depth %i)",
-                           &event_id, &e_event_desc,
-                           name.get (), stack_depth);
-                 }
-               else
-                 inform (event.get_location (),
-                         "%@ %e (depth %i)",
-                         &event_id, &e_event_desc,
-                         stack_depth);
-             }
-           else
-             inform (event.get_location (),
-                     "%@ %e", &event_id, &e_event_desc);
-         }
-      }
-      break;
-
-    case DPF_INLINE_EVENTS:
-      {
-       /* Consolidate related events.  */
-       path_print_policy policy (*this);
-       pretty_printer *const pp = get_printer ();
-       const bool check_rich_locations = true;
-       const bool colorize = pp_show_color (pp);
-       const bool show_event_links = m_source_printing.show_event_links_p;
-       path_summary summary (policy,
-                             *pp,
-                             path,
-                             check_rich_locations,
-                             colorize,
-                             show_event_links);
-       char *saved_prefix = pp_take_prefix (pp);
-       pp_set_prefix (pp, NULL);
-       print_path_summary_as_text (summary, *this,
-                                   get_context ().show_path_depths_p ());
-       pp_flush (pp);
-       pp_set_prefix (pp, saved_prefix);
-      }
-      break;
-    }
-}
-
-#if CHECKING_P
-
-namespace selftest {
-
-/* Return true iff all events in PATH have locations for which column data
-   is available, so that selftests that require precise string output can
-   bail out for awkward line_table cases.  */
-
-static bool
-path_events_have_column_data_p (const diagnostic_path &path)
-{
-  for (unsigned idx = 0; idx < path.num_events (); idx++)
-    {
-      location_t event_loc = path.get_event (idx).get_location ();
-      if (line_table->get_pure_location (event_loc)
-         > LINE_MAP_MAX_LOCATION_WITH_COLS)
-       return false;
-      if (line_table->get_start (event_loc) > LINE_MAP_MAX_LOCATION_WITH_COLS)
-       return false;
-      if (line_table->get_finish (event_loc) > LINE_MAP_MAX_LOCATION_WITH_COLS)
-       return false;
-    }
-  return true;
-}
-
-/* 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 ());
-
-  test_diagnostic_context dc;
-  diagnostic_text_output_format text_output (dc);
-  path_print_policy policy (text_output);
-  path_summary summary (policy, *event_pp, path, false);
-  ASSERT_EQ (summary.get_num_ranges (), 0);
-
-  print_path_summary_as_text (summary, text_output, true);
-  ASSERT_STREQ ("",
-               pp_formatted_text (text_output.get_printer ()));
-}
-
-/* Verify that print_path_summary works on a purely intraprocedural path.  */
-
-static void
-test_intraprocedural_path (pretty_printer *event_pp)
-{
-  test_diagnostic_path path (event_pp);
-  const char *const funcname = "foo";
-  path.add_event (UNKNOWN_LOCATION, funcname, 0, "first %qs", "free");
-  path.add_event (UNKNOWN_LOCATION, funcname, 0, "double %qs", "free");
-
-  ASSERT_FALSE (path.interprocedural_p ());
-
-  test_diagnostic_context dc;
-  diagnostic_text_output_format text_output (dc);
-  path_print_policy policy (text_output);
-  path_summary summary (policy, *event_pp, path, false, false, false);
-  ASSERT_EQ (summary.get_num_ranges (), 1);
-
-  print_path_summary_as_text (summary, text_output, true);
-  ASSERT_STREQ ("  `foo': events 1-2 (depth 0)\n"
-               " (1): first `free'\n"
-               " (2): double `free'\n",
-               pp_formatted_text (text_output.get_printer ()));
-}
-
-/* Verify that print_path_summary works on an interprocedural path.  */
-
-static void
-test_interprocedural_path_1 (pretty_printer *event_pp)
-{
-  test_diagnostic_path path (event_pp);
-  path.add_entry ("test", 0);
-  path.add_call ("test", 0, "make_boxed_int");
-  path.add_call ("make_boxed_int", 1, "wrapped_malloc");
-  path.add_event (UNKNOWN_LOCATION,
-                 "wrapped_malloc", 2, "calling malloc");
-  path.add_return ("test", 0);
-  path.add_call ("test", 0, "free_boxed_int");
-  path.add_call ("free_boxed_int", 1, "wrapped_free");
-  path.add_event (UNKNOWN_LOCATION, "wrapped_free", 2, "calling free");
-  path.add_return ("test", 0);
-  path.add_call ("test", 0, "free_boxed_int");
-  path.add_call ("free_boxed_int", 1, "wrapped_free");
-  path.add_event (UNKNOWN_LOCATION, "wrapped_free", 2, "calling free");
-  ASSERT_EQ (path.num_events (), 18);
-
-  ASSERT_TRUE (path.interprocedural_p ());
-
-  {
-    test_diagnostic_context dc;
-    diagnostic_text_output_format text_output (dc, nullptr, false);
-    path_print_policy policy (text_output);
-    path_summary summary (policy, *event_pp, path, false);
-    ASSERT_EQ (summary.get_num_ranges (), 9);
-
-    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
-    print_path_summary_as_text (summary, text_output, 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 (text_output.get_printer ()));
-  }
-  {
-    test_diagnostic_context dc;
-    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
-    diagnostic_text_output_format text_output (dc);
-    path_print_policy policy (text_output);
-    path_summary summary (policy, *event_pp, path, false);
-    print_path_summary_as_text (summary, text_output, 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 (text_output.get_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)
-{
-  test_diagnostic_path path (event_pp);
-  path.add_entry ("foo", 0);
-  path.add_call ("foo", 0, "bar");
-  path.add_call ("bar", 1, "baz");
-  path.add_return ("bar", 1);
-  path.add_call ("bar", 1, "baz");
-  ASSERT_EQ (path.num_events (), 8);
-
-  ASSERT_TRUE (path.interprocedural_p ());
-
-  {
-    test_diagnostic_context dc;
-    diagnostic_text_output_format text_output (dc);
-    path_print_policy policy (text_output);
-    path_summary summary (policy, *event_pp, path, false);
-    ASSERT_EQ (summary.get_num_ranges (), 5);
-    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
-    print_path_summary_as_text (summary, text_output, 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 (text_output.get_printer ()));
-  }
-  {
-    test_diagnostic_context dc;
-    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
-    diagnostic_text_output_format text_output (dc);
-    path_print_policy policy (text_output);
-    path_summary summary (policy, *event_pp, path, false);
-    print_path_summary_as_text (summary, text_output, 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 (text_output.get_printer ()));
-  }
-}
-
-/* Verify that print_path_summary is sane in the face of a recursive
-   diagnostic_path.  */
-
-static void
-test_recursion (pretty_printer *event_pp)
-{
- test_diagnostic_path path (event_pp);
-  path.add_entry ("factorial", 0);
-  for (int depth = 0; depth < 3; depth++)
-    path.add_call ("factorial", depth, "factorial");
-  ASSERT_EQ (path.num_events (), 7);
-
-  ASSERT_TRUE (path.interprocedural_p ());
-
-  {
-    test_diagnostic_context dc;
-    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
-
-    diagnostic_text_output_format text_output (dc);
-    path_print_policy policy (text_output);
-    path_summary summary (policy, *event_pp, path, false);
-    ASSERT_EQ (summary.get_num_ranges (), 4);
-
-    print_path_summary_as_text (summary, text_output, 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 (text_output.get_printer ()));
-  }
-  {
-    test_diagnostic_context dc;
-    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
-
-    diagnostic_text_output_format text_output (dc);
-    path_print_policy policy (text_output);
-    path_summary summary (policy, *event_pp, path, false);
-    print_path_summary_as_text (summary, text_output, 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 (text_output.get_printer ()));
-  }
-}
-
-/* Helper class for writing tests of control flow visualization.  */
-
-class control_flow_test
-{
-public:
-  control_flow_test (const location &loc,
-                    const line_table_case &case_,
-                    const char *content)
-  : m_tmp_file (loc, ".c", content,
-               /* gcc_rich_location::add_location_if_nearby implicitly
-                  uses global_dc's file_cache, so we need to evict
-                  tmp when we're done.  */
-               &global_dc->get_file_cache ()),
-    m_ltt (case_)
-  {
-    m_ord_map
-      = linemap_check_ordinary (linemap_add (line_table, LC_ENTER, false,
-                                            m_tmp_file.get_filename (), 0));
-    linemap_line_start (line_table, 1, 100);
-  }
-
-  location_t get_line_and_column (int line, int column)
-  {
-    return linemap_position_for_line_and_column (line_table, m_ord_map,
-                                                line, column);
-  }
-
-  location_t get_line_and_columns (int line, int first_column, int last_column)
-  {
-    return get_line_and_columns (line,
-                                first_column, first_column, last_column);
-  }
-
-  location_t get_line_and_columns (int line,
-                                  int first_column,
-                                  int caret_column,
-                                  int last_column)
-  {
-    return make_location (get_line_and_column (line, caret_column),
-                         get_line_and_column (line, first_column),
-                         get_line_and_column (line, last_column));
-  }
-
-private:
-  temp_source_file m_tmp_file;
-  line_table_test m_ltt;
-  const line_map_ordinary *m_ord_map;
-};
-
-/* Example of event edges where all events can go in the same layout,
-   testing the 6 combinations of:
-   - ASCII vs Unicode vs event links off
-   - line numbering on and off.  */
-
-static void
-test_control_flow_1 (const line_table_case &case_,
-                    pretty_printer *event_pp)
-{
-  /* Create a tempfile and write some text to it.
-     ...000000000111111111122222222223333333333.
-     ...123456789012345678901234567890123456789.  */
-  const char *content
-    = ("int test (int *p)\n" /* line 1.  */
-       "{\n"                 /* line 2.  */
-       "  if (p)\n"          /* line 3.  */
-       "    return 0;\n"     /* line 4.  */
-       "  return *p;\n"      /* line 5.  */
-       "}\n");               /* line 6.  */
-
-  control_flow_test t (SELFTEST_LOCATION, case_, content);
-
-  const location_t conditional = t.get_line_and_column (3, 7);
-  const location_t cfg_dest = t.get_line_and_column (5, 10);
-
-  test_diagnostic_path path (event_pp);
-  path.add_event (conditional, nullptr, 0,
-                 "following %qs branch (when %qs is NULL)...",
-                 "false", "p");
-  path.connect_to_next_event ();
-
-  path.add_event (cfg_dest, nullptr, 0,
-                 "...to here");
-  path.add_event (cfg_dest, nullptr, 0,
-                 "dereference of NULL %qs",
-                 "p");
-
-  if (!path_events_have_column_data_p (path))
-    return;
-
-
-  {
-    test_diagnostic_context dc;
-    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
-    dc.m_source_printing.show_event_links_p = true;
-    diagnostic_text_output_format text_output (dc);
-    path_print_policy policy (text_output);
-    path_summary summary (policy, *event_pp, path, true);
-    print_path_summary_as_text (summary, text_output, false);
-    ASSERT_STREQ
-      ("  events 1-3\n"
-       "FILENAME:3:7:\n"
-       "   if (p)\n"
-       "       ^\n"
-       "       |\n"
-       "       (1) following `false' branch (when `p' is NULL)... ->-+\n"
-       "                                                             |\n"
-       "FILENAME:5:10:\n"
-       "                                                             |\n"
-       "+------------------------------------------------------------+\n"
-       "|  return *p;\n"
-       "|         ~\n"
-       "|         |\n"
-       "+-------->(2) ...to here\n"
-       "          (3) dereference of NULL `p'\n",
-       pp_formatted_text (text_output.get_printer ()));
-  }
-  {
-    test_diagnostic_context dc;
-    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
-    dc.m_source_printing.show_event_links_p = false;
-    diagnostic_text_output_format text_output (dc);
-    path_print_policy policy (text_output);
-    path_summary summary (policy, *event_pp, path, true);
-    print_path_summary_as_text (summary, text_output, false);
-    ASSERT_STREQ
-      ("  events 1-3\n"
-       "FILENAME:3:7:\n"
-       "   if (p)\n"
-       "       ^\n"
-       "       |\n"
-       "       (1) following `false' branch (when `p' is NULL)...\n"
-       "FILENAME:5:10:\n"
-       "   return *p;\n"
-       "          ~\n"
-       "          |\n"
-       "          (2) ...to here\n"
-       "          (3) dereference of NULL `p'\n",
-       pp_formatted_text (text_output.get_printer ()));
-  }
-  {
-    test_diagnostic_context dc;
-    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
-    dc.m_source_printing.show_line_numbers_p = true;
-    dc.m_source_printing.show_event_links_p = true;
-    diagnostic_text_output_format text_output (dc);
-    path_print_policy policy (text_output);
-    path_summary summary (policy, *event_pp, path, true);
-    print_path_summary_as_text (summary, text_output, false);
-    ASSERT_STREQ
-      ("  events 1-3\n"
-       "FILENAME:3:7:\n"
-       "    3 |   if (p)\n"
-       "      |       ^\n"
-       "      |       |\n"
-       "      |       (1) following `false' branch (when `p' is NULL)... ->-+\n"
-       "      |                                                             |\n"
-       "      |                                                             |\n"
-       "      |+------------------------------------------------------------+\n"
-       "    4 ||    return 0;\n"
-       "    5 ||  return *p;\n"
-       "      ||         ~\n"
-       "      ||         |\n"
-       "      |+-------->(2) ...to here\n"
-       "      |          (3) dereference of NULL `p'\n",
-       pp_formatted_text (text_output.get_printer ()));
-  }
-  {
-    test_diagnostic_context dc;
-    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
-    dc.m_source_printing.show_line_numbers_p = true;
-    dc.m_source_printing.show_event_links_p = false;
-    diagnostic_text_output_format text_output (dc);
-    path_print_policy policy (text_output);
-    path_summary summary (policy, *event_pp, path, true);
-    print_path_summary_as_text (summary, text_output, false);
-    ASSERT_STREQ
-      ("  events 1-3\n"
-       "FILENAME:3:7:\n"
-       "    3 |   if (p)\n"
-       "      |       ^\n"
-       "      |       |\n"
-       "      |       (1) following `false' branch (when `p' is NULL)...\n"
-       "    4 |     return 0;\n"
-       "    5 |   return *p;\n"
-       "      |          ~\n"
-       "      |          |\n"
-       "      |          (2) ...to here\n"
-       "      |          (3) dereference of NULL `p'\n",
-       pp_formatted_text (text_output.get_printer ()));
-  }
-  {
-    test_diagnostic_context dc;
-    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
-    dc.m_source_printing.show_event_links_p = true;
-    diagnostic_text_output_format text_output (dc);
-    path_print_policy policy (text_output);
-    path_summary summary (policy, *event_pp, path, true);
-    print_path_summary_as_text (summary, text_output, false);
-    ASSERT_STREQ
-      ("  events 1-3\n"
-       "FILENAME:3:7:\n"
-       "   if (p)\n"
-       "       ^\n"
-       "       |\n"
-       "       (1) following `false' branch (when `p' is NULL)... ─>─┐\n"
-       "                                                             │\n"
-       "FILENAME:5:10:\n"
-       "                                                             │\n"
-       "┌────────────────────────────────────────────────────────────┘\n"
-       "│  return *p;\n"
-       "│         ~\n"
-       "│         |\n"
-       "└────────>(2) ...to here\n"
-       "          (3) dereference of NULL `p'\n",
-       pp_formatted_text (text_output.get_printer ()));
-  }
-  {
-    test_diagnostic_context dc;
-    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
-    dc.m_source_printing.show_event_links_p = true;
-    dc.m_source_printing.show_line_numbers_p = true;
-    diagnostic_text_output_format text_output (dc);
-    path_print_policy policy (text_output);
-    path_summary summary (policy, *event_pp, path, true);
-    print_path_summary_as_text (summary, text_output, false);
-    ASSERT_STREQ
-      ("  events 1-3\n"
-       "FILENAME:3:7:\n"
-       "    3 |   if (p)\n"
-       "      |       ^\n"
-       "      |       |\n"
-       "      |       (1) following `false' branch (when `p' is NULL)... ─>─┐\n"
-       "      |                                                             │\n"
-       "      |                                                             │\n"
-       "      |┌────────────────────────────────────────────────────────────┘\n"
-       "    4 |│    return 0;\n"
-       "    5 |│  return *p;\n"
-       "      |│         ~\n"
-       "      |│         |\n"
-       "      |└────────>(2) ...to here\n"
-       "      |          (3) dereference of NULL `p'\n",
-       pp_formatted_text (text_output.get_printer ()));
-  }
-}
-
-/* Complex example involving a backedge.  */
-
-static void
-test_control_flow_2 (const line_table_case &case_,
-                    pretty_printer *event_pp)
-{
-  /* Create a tempfile and write some text to it.
-     ...000000000111111111122222222223333333333.
-     ...123456789012345678901234567890123456789.  */
-  const char *content
-    = ("int for_loop_noop_next (struct node *n)\n" /* <--------- line 1.  */
-       "{\n" /* <----------------------------------------------- line 2.  */
-       "  int sum = 0;\n" /* <---------------------------------- line 3.  */
-       "  for (struct node *iter = n; iter; iter->next)\n" /* <- line 4.  */
-       "    sum += n->val;\n" /* <------------------------------ line 5.  */
-       "  return sum;\n" /* <----------------------------------- line 6.  */
-       "}\n");  /* <-------------------------------------------- line 7.  */
-  /* Adapted from infinite-loop-linked-list.c where
-     "iter->next" should be "iter = iter->next".  */
-
-  control_flow_test t (SELFTEST_LOCATION, case_, content);
-
-  const location_t iter_test = t.get_line_and_columns (4, 31, 34);
-  const location_t loop_body_start = t.get_line_and_columns (5, 12, 17);
-  const location_t loop_body_end = t.get_line_and_columns (5, 5, 9, 17);
-
-  test_diagnostic_path path (event_pp);
-  path.add_event (iter_test, nullptr, 0, "infinite loop here");
-
-  path.add_event (iter_test, nullptr, 0, "looping from here...");
-  path.connect_to_next_event ();
-
-  path.add_event (loop_body_start, nullptr, 0, "...to here");
-
-  path.add_event (loop_body_end, nullptr, 0, "looping back...");
-  path.connect_to_next_event ();
-
-  path.add_event (iter_test, nullptr, 0, "...to here");
-
-  if (!path_events_have_column_data_p (path))
-    return;
-
-  {
-    test_diagnostic_context dc;
-    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
-    dc.m_source_printing.show_event_links_p = true;
-    dc.m_source_printing.show_line_numbers_p = true;
-    diagnostic_text_output_format text_output (dc);
-    path_print_policy policy (text_output);
-    path_summary summary (policy, *event_pp, path, true);
-    print_path_summary_as_text (summary, text_output, false);
-    ASSERT_STREQ
-      ("  events 1-3\n"
-       "FILENAME:4:31:\n"
-       "    4 |   for (struct node *iter = n; iter; iter->next)\n"
-       "      |                               ^~~~\n"
-       "      |                               |\n"
-       "      |                               (1) infinite loop here\n"
-       "      |                               (2) looping from here... ->-+\n"
-       "      |                                                           |\n"
-       "      |                                                           |\n"
-       "      |+----------------------------------------------------------+\n"
-       "    5 ||    sum += n->val;\n"
-       "      ||           ~~~~~~              \n"
-       "      ||           |\n"
-       "      |+---------->(3) ...to here\n"
-       /* We need to start an new event_range here as event (4) is to the
-         left of event (3), and thus (4) would mess up the in-edge to (3).  */
-       "  event 4\n"
-       "    5 |     sum += n->val;\n"
-       "      |     ~~~~^~~~~~~~~\n"
-       "      |         |\n"
-       "      |         (4) looping back... ->-+\n"
-       "      |                                |\n"
-       /* We need to start an new event_range here as event (4) with an
-         out-edge is on a later line (line 5) than its destination event (5),
-         on line 4.  */
-       "  event 5\n"
-       "      |                                |\n"
-       "      |+-------------------------------+\n"
-       "    4 ||  for (struct node *iter = n; iter; iter->next)\n"
-       "      ||                              ^~~~\n"
-       "      ||                              |\n"
-       "      |+----------------------------->(5) ...to here\n",
-       pp_formatted_text (text_output.get_printer ()));
-  }
-}
-
-/* Complex example involving a backedge and both an in-edge and out-edge
-   on the same line.  */
-
-static void
-test_control_flow_3 (const line_table_case &case_,
-                    pretty_printer *event_pp)
-{
-  /* Create a tempfile and write some text to it.
-     ...000000000111111111122222222223333333333.
-     ...123456789012345678901234567890123456789.  */
-  const char *content
-    = ("void test_missing_comparison_in_for_condition_1 (int n)\n"
-       "{\n" /* <------------------------- line 2.  */
-       "  for (int i = 0; n; i++)\n" /* <- line 3.  */
-       "    {\n" /* <--------------------- line 4.  */
-       "    }\n" /* <--------------------- line 5.  */
-       "}\n"); /* <----------------------- line 6.  */
-  /* Adapted from infinite-loop-1.c where the condition should have been
-     "i < n", rather than just "n".  */
-
-  control_flow_test t (SELFTEST_LOCATION, case_, content);
-
-  const location_t iter_test = t.get_line_and_column (3, 19);
-  const location_t iter_next = t.get_line_and_columns (3, 22, 24);
-
-  test_diagnostic_path path (event_pp);
-  path.add_event (iter_test, nullptr, 0, "infinite loop here");
-
-  path.add_event (iter_test, nullptr, 0, "looping from here...");
-  path.connect_to_next_event ();
-
-  path.add_event (iter_next, nullptr, 0, "...to here");
-
-  path.add_event (iter_next, nullptr, 0, "looping back...");
-  path.connect_to_next_event ();
-
-  path.add_event (iter_test, nullptr, 0, "...to here");
-
-  if (!path_events_have_column_data_p (path))
-    return;
-
-  {
-    test_diagnostic_context dc;
-    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
-    dc.m_source_printing.show_event_links_p = true;
-    dc.m_source_printing.show_line_numbers_p = true;
-    diagnostic_text_output_format text_output (dc);
-    path_print_policy policy (text_output);
-    path_summary summary (policy, *event_pp, path, true);
-    print_path_summary_as_text (summary, text_output, false);
-    ASSERT_STREQ
-      ("  events 1-2\n"
-       "FILENAME:3:19:\n"
-       "    3 |   for (int i = 0; n; i++)\n"
-       "      |                   ^\n"
-       "      |                   |\n"
-       "      |                   (1) infinite loop here\n"
-       "      |                   (2) looping from here... ->-+\n"
-       "      |                                               |\n"
-       "  events 3-4\n"
-       "      |                                               |\n"
-       "      |+----------------------------------------------+\n"
-       "    3 ||  for (int i = 0; n; i++)\n"
-       "      ||                     ^~~\n"
-       "      ||                     |\n"
-       "      |+-------------------->(3) ...to here\n"
-       "      |                      (4) looping back... ->-+\n"
-       "      |                                             |\n"
-       /* We need to start an new event_range here as event (4) with an
-         out-edge is on the same line as its destination event (5), but
-         to the right, which we can't handle as a single event_range. */
-       "  event 5\n"
-       "      |                                             |\n"
-       "      |+--------------------------------------------+\n"
-       "    3 ||  for (int i = 0; n; i++)\n"
-       "      ||                  ^\n"
-       "      ||                  |\n"
-       "      |+----------------->(5) ...to here\n",
-       pp_formatted_text (text_output.get_printer ()));
-  }
-}
-
-/* Implementation of ASSERT_CFG_EDGE_PATH_STREQ.  */
-
-static void
-assert_cfg_edge_path_streq (const location &loc,
-                           pretty_printer *event_pp,
-                           const location_t src_loc,
-                           const location_t dst_loc,
-                           const char *expected_str)
-{
-  test_diagnostic_path path (event_pp);
-  path.add_event (src_loc, nullptr, 0, "from here...");
-  path.connect_to_next_event ();
-
-  path.add_event (dst_loc, nullptr, 0, "...to here");
-
-  if (!path_events_have_column_data_p (path))
-    return;
-
-  test_diagnostic_context dc;
-  dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
-  dc.m_source_printing.show_event_links_p = true;
-  dc.m_source_printing.show_line_numbers_p = true;
-  diagnostic_text_output_format text_output (dc);
-  path_print_policy policy (text_output);
-  path_summary summary (policy, *event_pp, path, true);
-  print_path_summary_as_text (summary, text_output, false);
-  ASSERT_STREQ_AT (loc, expected_str,
-                  pp_formatted_text (text_output.get_printer ()));
-}
-
-/* Assert that if we make a path with an event with "from here..." at SRC_LOC
-   leading to an event "...to here" at DST_LOC that we print the path
-   as EXPECTED_STR.  */
-
-#define ASSERT_CFG_EDGE_PATH_STREQ(SRC_LOC, DST_LOC, EXPECTED_STR) \
-  assert_cfg_edge_path_streq ((SELFTEST_LOCATION), (event_pp),    \
-                             (SRC_LOC), (DST_LOC), (EXPECTED_STR))
-
-/* Various examples of edge, trying to cover all combinations of:
-   - relative x positive of src label and dst label
-   - relative y position of labels:
-     - on same line
-     - on next line
-     - on line after next
-     - big gap, where src is before dst
-     - big gap, where src is after dst
-   and other awkward cases.  */
-
-static void
-test_control_flow_4 (const line_table_case &case_,
-                    pretty_printer *event_pp)
-{
-  std::string many_lines;
-  for (int i = 1; i <= 100; i++)
-    /* ............000000000111
-       ............123456789012.  */
-    many_lines += "LHS      RHS\n";
-  control_flow_test t (SELFTEST_LOCATION, case_, many_lines.c_str ());
-
-  /* Same line.  */
-  {
-    /* LHS -> RHS.  */
-    ASSERT_CFG_EDGE_PATH_STREQ
-      (t.get_line_and_columns (3, 1, 3),
-       t.get_line_and_columns (3, 10, 12),
-       ("  event 1\n"
-       "FILENAME:3:1:\n"
-       "    3 | LHS      RHS\n"
-       "      | ^~~\n"
-       "      | |\n"
-       "      | (1) from here... ->-+\n"
-       "      |                     |\n"
-       "  event 2\n"
-       "      |                     |\n"
-       "      |+--------------------+\n"
-       "    3 ||LHS      RHS\n"
-       "      ||         ^~~\n"
-       "      ||         |\n"
-       "      |+-------->(2) ...to here\n"));
-
-    /*  RHS -> LHS.  */
-    ASSERT_CFG_EDGE_PATH_STREQ
-      (t.get_line_and_columns (3, 10, 12),
-       t.get_line_and_columns (3, 1, 3),
-       ("  event 1\n"
-       "FILENAME:3:10:\n"
-       "    3 | LHS      RHS\n"
-       "      |          ^~~\n"
-       "      |          |\n"
-       "      |          (1) from here... ->-+\n"
-       "      |                              |\n"
-       "  event 2\n"
-       "      |                              |\n"
-       "      |+-----------------------------+\n"
-       "    3 ||LHS      RHS\n"
-       "      ||^~~\n"
-       "      |||\n"
-       "      |+(2) ...to here\n"));
-  }
-
-  /* Next line.  */
-  {
-    /* LHS -> RHS.  */
-    ASSERT_CFG_EDGE_PATH_STREQ
-      (t.get_line_and_columns (3, 1, 3),
-       t.get_line_and_columns (4, 5, 7),
-       ("  events 1-2\n"
-       "FILENAME:3:1:\n"
-       "    3 | LHS      RHS\n"
-       "      | ^~~\n"
-       "      | |\n"
-       "      | (1) from here... ->-+\n"
-       "      |                     |\n"
-       "      |                     |\n"
-       "      |+--------------------+\n"
-       "    4 ||LHS      RHS\n"
-       "      ||    ~~~\n"
-       "      ||    |\n"
-       "      |+--->(2) ...to here\n"));
-
-    /*  RHS -> LHS.  */
-    ASSERT_CFG_EDGE_PATH_STREQ
-      (t.get_line_and_columns (3, 10, 12),
-       t.get_line_and_columns (4, 1, 3),
-       ("  events 1-2\n"
-       "FILENAME:3:10:\n"
-       "    3 | LHS      RHS\n"
-       "      |          ^~~\n"
-       "      |          |\n"
-       "      |          (1) from here... ->-+\n"
-       "      |                              |\n"
-       "      |                              |\n"
-       "      |+-----------------------------+\n"
-       "    4 ||LHS      RHS\n"
-       "      ||~~~       \n"
-       "      |||\n"
-       "      |+(2) ...to here\n"));
-  }
-
-  /* Line after next.  */
-  {
-    /* LHS -> RHS.  */
-    ASSERT_CFG_EDGE_PATH_STREQ
-      (t.get_line_and_columns (3, 1, 3),
-       t.get_line_and_columns (5, 10, 12),
-       ("  events 1-2\n"
-       "FILENAME:3:1:\n"
-       "    3 | LHS      RHS\n"
-       "      | ^~~\n"
-       "      | |\n"
-       "      | (1) from here... ->-+\n"
-       "      |                     |\n"
-       "      |                     |\n"
-       "      |+--------------------+\n"
-       "    4 ||LHS      RHS\n"
-       "    5 ||LHS      RHS\n"
-       "      ||         ~~~\n"
-       "      ||         |\n"
-       "      |+-------->(2) ...to here\n"));
-
-    /*  RHS -> LHS.  */
-    ASSERT_CFG_EDGE_PATH_STREQ
-      (t.get_line_and_columns (3, 10, 12),
-       t.get_line_and_columns (5, 1, 3),
-       ("  events 1-2\n"
-       "FILENAME:3:10:\n"
-       "    3 | LHS      RHS\n"
-       "      |          ^~~\n"
-       "      |          |\n"
-       "      |          (1) from here... ->-+\n"
-       "      |                              |\n"
-       "      |                              |\n"
-       "      |+-----------------------------+\n"
-       "    4 ||LHS      RHS\n"
-       "    5 ||LHS      RHS\n"
-       "      ||~~~       \n"
-       "      |||\n"
-       "      |+(2) ...to here\n"));
-  }
-
-  /* Big gap, increasing line number.  */
-  {
-    /* LHS -> RHS.  */
-    ASSERT_CFG_EDGE_PATH_STREQ
-      (t.get_line_and_columns (3, 1, 3),
-       t.get_line_and_columns (97, 10, 12),
-       ("  events 1-2\n"
-       "FILENAME:3:1:\n"
-       "    3 | LHS      RHS\n"
-       "      | ^~~\n"
-       "      | |\n"
-       "      | (1) from here... ->-+\n"
-       "      |                     |\n"
-       "......\n"
-       "      |                     |\n"
-       "      |+--------------------+\n"
-       "   97 ||LHS      RHS\n"
-       "      ||         ~~~\n"
-       "      ||         |\n"
-       "      |+-------->(2) ...to here\n"));
-
-    /*  RHS -> LHS.  */
-    ASSERT_CFG_EDGE_PATH_STREQ
-      (t.get_line_and_columns (3, 10, 12),
-       t.get_line_and_columns (97, 1, 3),
-       ("  events 1-2\n"
-       "FILENAME:3:10:\n"
-       "    3 | LHS      RHS\n"
-       "      |          ^~~\n"
-       "      |          |\n"
-       "      |          (1) from here... ->-+\n"
-       "      |                              |\n"
-       "......\n"
-       "      |                              |\n"
-       "      |+-----------------------------+\n"
-       "   97 ||LHS      RHS\n"
-       "      ||~~~       \n"
-       "      |||\n"
-       "      |+(2) ...to here\n"));
-  }
-
-  /* Big gap, decreasing line number.  */
-  {
-    /* LHS -> RHS.  */
-    ASSERT_CFG_EDGE_PATH_STREQ
-      (t.get_line_and_columns (97, 1, 3),
-       t.get_line_and_columns (3, 10, 12),
-       ("  event 1\n"
-       "FILENAME:97:1:\n"
-       "   97 | LHS      RHS\n"
-       "      | ^~~\n"
-       "      | |\n"
-       "      | (1) from here... ->-+\n"
-       "      |                     |\n"
-       "  event 2\n"
-       "      |                     |\n"
-       "      |+--------------------+\n"
-       "    3 ||LHS      RHS\n"
-       "      ||         ^~~\n"
-       "      ||         |\n"
-       "      |+-------->(2) ...to here\n"));
-
-    /*  RHS -> LHS.  */
-    ASSERT_CFG_EDGE_PATH_STREQ
-      (t.get_line_and_columns (97, 10, 12),
-       t.get_line_and_columns (3, 1, 3),
-       ("  event 1\n"
-       "FILENAME:97:10:\n"
-       "   97 | LHS      RHS\n"
-       "      |          ^~~\n"
-       "      |          |\n"
-       "      |          (1) from here... ->-+\n"
-       "      |                              |\n"
-       "  event 2\n"
-       "      |                              |\n"
-       "      |+-----------------------------+\n"
-       "    3 ||LHS      RHS\n"
-       "      ||^~~\n"
-       "      |||\n"
-       "      |+(2) ...to here\n"));
-  }
-
-  /* Unknown src.  */
-  {
-    ASSERT_CFG_EDGE_PATH_STREQ
-      (UNKNOWN_LOCATION,
-       t.get_line_and_columns (3, 10, 12),
-       ("  event 1\n"
-       " (1): from here...\n"
-       "  event 2\n"
-       "FILENAME:3:10:\n"
-       "    3 | LHS      RHS\n"
-       "      |          ^~~\n"
-       "      |          |\n"
-       "      |+-------->(2) ...to here\n"));
-  }
-
-  /* Unknown dst.  */
-  {
-    ASSERT_CFG_EDGE_PATH_STREQ
-      (t.get_line_and_columns (3, 1, 3),
-       UNKNOWN_LOCATION,
-       ("  event 1\n"
-       "FILENAME:3:1:\n"
-       "    3 | LHS      RHS\n"
-       "      | ^~~\n"
-       "      | |\n"
-       "      | (1) from here... ->-+\n"
-       "      |                     |\n"
-       "  event 2\n"
-       "FILENAME:\n"
-       " (2): ...to here\n"));
-  }
-}
-
-/* Another complex example, adapted from data-model-20.c.  */
-
-static void
-test_control_flow_5 (const line_table_case &case_,
-                    pretty_printer *event_pp)
-{
-  /* Create a tempfile and write some text to it.
-     ...000000000111111111122222222223333333333444444444455555555556666666666.
-     ...123456789012345678901234567890123456789012345678901234567890123456789.  */
-  const char *content
-    = ("  if ((arr = (struct foo **)malloc(n * sizeof(struct foo *))) == NULL)\n"
-       "    return NULL;\n" /* <------------------------- line 2.  */
-       "\n" /* <----------------------------------------- line 3.  */
-       "  for (i = 0; i < n; i++) {\n" /* <-------------- line 4.  */
-       "    if ((arr[i] = (struct foo *)malloc(sizeof(struct foo))) == NULL) {\n");
-
-  control_flow_test t (SELFTEST_LOCATION, case_, content);
-
-  test_diagnostic_path path (event_pp);
-  /* (1) */
-  path.add_event (t.get_line_and_column (1, 6), nullptr, 0,
-                 "following %qs branch (when %qs is non-NULL)...",
-                 "false", "arr");
-  path.connect_to_next_event ();
-
-  /* (2) */
-  path.add_event (t.get_line_and_columns (4, 8, 10, 12), nullptr, 0,
-                 "...to here");
-
-  /* (3) */
-  path.add_event (t.get_line_and_columns (4, 15, 17, 19), nullptr, 0,
-                 "following %qs branch (when %qs)...",
-                 "true", "i < n");
-  path.connect_to_next_event ();
-
-  /* (4) */
-  path.add_event (t.get_line_and_column (5, 13), nullptr, 0,
-                 "...to here");
-
-  /* (5) */
-  path.add_event (t.get_line_and_columns (5, 33, 58), nullptr, 0,
-                 "allocated here");
-
-  if (!path_events_have_column_data_p (path))
-    return;
-
-  {
-    test_diagnostic_context dc;
-    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
-    dc.m_source_printing.show_event_links_p = true;
-    dc.m_source_printing.show_line_numbers_p = true;
-    diagnostic_text_output_format text_output (dc);
-    path_print_policy policy (text_output);
-    path_summary summary (policy, *event_pp, path, true);
-    print_path_summary_as_text (summary, text_output, false);
-    ASSERT_STREQ
-      ("  events 1-5\n"
-       "FILENAME:1:6:\n"
-       "    1 |   if ((arr = (struct foo **)malloc(n * sizeof(struct foo *))) == NULL)\n"
-       "      |      ^\n"
-       "      |      |\n"
-       "      |      (1) following `false' branch (when `arr' is non-NULL)... ->-+\n"
-       "      |                                                                  |\n"
-       "......\n"
-       "      |                                                                  |\n"
-       "      |+-----------------------------------------------------------------+\n"
-       "    4 ||  for (i = 0; i < n; i++) {\n"
-       "      ||       ~~~~~  ~~~~~\n"
-       "      ||         |      |\n"
-       "      ||         |      (3) following `true' branch (when `i < n')... ->-+\n"
-       "      |+-------->(2) ...to here                                          |\n"
-       "      |                                                                  |\n"
-       "      |                                                                  |\n"
-       "      |+-----------------------------------------------------------------+\n"
-       "    5 ||    if ((arr[i] = (struct foo *)malloc(sizeof(struct foo))) == NULL) {\n"
-       "      ||            ~                   ~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
-       "      ||            |                   |\n"
-       "      |+----------->(4) ...to here      (5) allocated here\n",
-       pp_formatted_text (text_output.get_printer ()));
-  }
-}
-
-/* Another complex example, adapted from loop-3.c.  */
-
-static void
-test_control_flow_6 (const line_table_case &case_,
-                    pretty_printer *event_pp)
-{
-  /* Create a tempfile and write some text to it.
-     ...000000000111111111122222222223333333.
-     ...123456789012345678901234567890123456.  */
-  const char *content
-    = ("#include <stdlib.h>\n" /* <------------------ line 1.  */
-       "\n" /* <------------------------------------- line 2.  */
-       "void test(int c)\n" /* <--------------------- line 3.  */
-       "{\n" /* <------------------------------------ line 4.  */
-       "  int i;\n" /* <----------------------------- line 5.  */
-       "  char *buffer = (char*)malloc(256);\n" /* <- line 6.  */
-       "\n" /* <------------------------------------- line 7.  */
-       "  for (i=0; i<255; i++) {\n" /* <------------ line 8.  */
-       "    buffer[i] = c;\n" /* <------------------- line 9.  */
-       "\n" /* <------------------------------------- line 10.  */
-       "    free(buffer);\n" /* <-------------------- line 11.  */
-       "  }\n"); /* <-------------------------------- line 12.  */
-
-  control_flow_test t (SELFTEST_LOCATION, case_, content);
-
-  test_diagnostic_path path (event_pp);
-  /* (1) */
-  path.add_event (t.get_line_and_columns (6, 25, 35), nullptr, 0,
-                 "allocated here");
-
-  /* (2) */
-  path.add_event (t.get_line_and_columns (8, 13, 14, 17), nullptr, 0,
-                 "following %qs branch (when %qs)...",
-                 "true", "i <= 254");
-  path.connect_to_next_event ();
-
-  /* (3) */
-  path.add_event (t.get_line_and_columns (9, 5, 15, 17), nullptr, 0,
-                 "...to here");
-
-  /* (4) */
-  path.add_event (t.get_line_and_columns (8, 13, 14, 17), nullptr, 0,
-                 "following %qs branch (when %qs)...",
-                 "true", "i <= 254");
-  path.connect_to_next_event ();
-
-  /* (5) */
-  path.add_event (t.get_line_and_columns (9, 5, 15, 17), nullptr, 0,
-                 "...to here");
-
-  if (!path_events_have_column_data_p (path))
-    return;
-
-  {
-    test_diagnostic_context dc;
-    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
-    dc.m_source_printing.show_event_links_p = true;
-    dc.m_source_printing.show_line_numbers_p = true;
-    diagnostic_text_output_format text_output (dc);
-    path_print_policy policy (text_output);
-    path_summary summary (policy, *event_pp, path, true);
-    print_path_summary_as_text (summary, text_output, false);
-    ASSERT_STREQ
-      ("  events 1-3\n"
-       "FILENAME:6:25:\n"
-       "    6 |   char *buffer = (char*)malloc(256);\n"
-       "      |                         ^~~~~~~~~~~\n"
-       "      |                         |\n"
-       "      |                         (1) allocated here\n"
-       "    7 | \n"
-       "    8 |   for (i=0; i<255; i++) {\n"
-       "      |             ~~~~~        \n"
-       "      |              |\n"
-       "      |              (2) following `true' branch (when `i <= 254')... ->-+\n"
-       "      |                                                                  |\n"
-       "      |                                                                  |\n"
-       "      |+-----------------------------------------------------------------+\n"
-       "    9 ||    buffer[i] = c;\n"
-       "      ||    ~~~~~~~~~~~~~        \n"
-       "      ||              |\n"
-       "      |+------------->(3) ...to here\n"
-       "  events 4-5\n"
-       "    8 |   for (i=0; i<255; i++) {\n"
-       "      |             ~^~~~\n"
-       "      |              |\n"
-       "      |              (4) following `true' branch (when `i <= 254')... ->-+\n"
-       "      |                                                                  |\n"
-       "      |                                                                  |\n"
-       "      |+-----------------------------------------------------------------+\n"
-       "    9 ||    buffer[i] = c;\n"
-       "      ||    ~~~~~~~~~~~~~\n"
-       "      ||              |\n"
-       "      |+------------->(5) ...to here\n",
-       pp_formatted_text (text_output.get_printer ()));
-  }
-}
-
-static void
-control_flow_tests (const line_table_case &case_)
-{
-  pretty_printer pp;
-  pp_show_color (&pp) = false;
-
-  test_control_flow_1 (case_, &pp);
-  test_control_flow_2 (case_, &pp);
-  test_control_flow_3 (case_, &pp);
-  test_control_flow_4 (case_, &pp);
-  test_control_flow_5 (case_, &pp);
-  test_control_flow_6 (case_, &pp);
-}
-
-/* Run all of the selftests within this file.  */
-
-void
-diagnostic_path_cc_tests ()
-{
-  pretty_printer pp;
-  pp_show_color (&pp) = false;
-
-  auto_fix_quotes fix_quotes;
-  test_empty_path (&pp);
-  test_intraprocedural_path (&pp);
-  test_interprocedural_path_1 (&pp);
-  test_interprocedural_path_2 (&pp);
-  test_recursion (&pp);
-  for_each_line_table_case (control_flow_tests);
-}
-
-} // namespace selftest
-
 #if __GNUC__ >= 10
 #  pragma GCC diagnostic pop
 #endif
-
-#endif /* #if CHECKING_P */
index 959e5d0a0f8dd46eb23376394d79e8ee9b36b3bb..0090e56bab6ab9ed651f261ecc6eaaa1f80b7c52 100644 (file)
@@ -106,7 +106,7 @@ selftest::run_tests ()
   spellcheck_cc_tests ();
   spellcheck_tree_cc_tests ();
   tree_cfg_cc_tests ();
-  diagnostic_path_cc_tests ();
+  diagnostic_path_output_cc_tests ();
   simple_diagnostic_path_cc_tests ();
   lazy_diagnostic_path_cc_tests ();
   attribs_cc_tests ();
index 7e1d94cd39ef341b6b474e9b61b8e49347028ca1..cd85840b83e0f3baf1fdff9f9786e47390067985 100644 (file)
@@ -224,7 +224,7 @@ extern void diagnostic_color_cc_tests ();
 extern void diagnostic_format_html_cc_tests ();
 extern void diagnostic_format_json_cc_tests ();
 extern void diagnostic_format_sarif_cc_tests ();
-extern void diagnostic_path_cc_tests ();
+extern void diagnostic_path_output_cc_tests ();
 extern void diagnostic_show_locus_cc_tests ();
 extern void digraph_cc_tests ();
 extern void dumpfile_cc_tests ();