]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
diagnostics, analyzer: add CFG edge visualization to path-printing
authorDavid Malcolm <dmalcolm@redhat.com>
Fri, 17 May 2024 18:51:47 +0000 (14:51 -0400)
committerDavid Malcolm <dmalcolm@redhat.com>
Fri, 17 May 2024 18:51:47 +0000 (14:51 -0400)
This patch adds some ability for links between labelled ranges when
quoting the user's source code, and uses this to add links between
events when printing diagnostic_paths, chopping them up further into
event ranges that can be printed together.
It adds links to the various "from..." - "...to" events in the
analyzer.

For example, previously we emitted this for
c-c++-common/analyzer/infinite-loop-linked-list.c's
while_loop_missing_next':

infinite-loop-linked-list.c:30:10: warning: infinite loop [CWE-835] [-Wanalyzer-infinite-loop]
   30 |   while (n)
      |          ^
  'while_loop_missing_next': events 1-5
   30 |   while (n)
      |          ^
      |          |
      |          (1) infinite loop here
      |          (2) when 'n' is non-NULL: always following 'true' branch...
      |          (5) ...to here
   31 |     {
   32 |       sum += n->val;
      |       ~~~~~~~~~~~~~
      |           |   |
      |           |   (3) ...to here
      |           (4) looping back...

whereas with the patch we now emit:

infinite-loop-linked-list.c:30:10: warning: infinite loop [CWE-835] [-Wanalyzer-infinite-loop]
   30 |   while (n)
      |          ^
  'while_loop_missing_next': events 1-3
   30 |   while (n)
      |          ^
      |          |
      |          (1) infinite loop here
      |          (2) when 'n' is non-NULL: always following 'true' branch... ->-+
      |                                                                         |
      |                                                                         |
      |+------------------------------------------------------------------------+
   31 ||    {
   32 ||      sum += n->val;
      ||             ~~~~~~
      ||              |
      |+------------->(3) ...to here
  'while_loop_missing_next': event 4
   32 |       sum += n->val;
      |       ~~~~^~~~~~~~~
      |           |
      |           (4) looping back... ->-+
      |                                  |
  'while_loop_missing_next': event 5
      |                                  |
      |+---------------------------------+
   30 ||  while (n)
      ||         ^
      ||         |
      |+-------->(5) ...to here

which I believe is easier to understand.

The patch also implements the use of unicode characters and colorization
for the lines (not shown in the above example).

There is a new option -fno-diagnostics-show-event-links for getting
back the old behavior (added to -fdiagnostics-plain-output).

gcc/analyzer/ChangeLog:
* checker-event.h (checker_event::connect_to_next_event_p):
Implement new diagnostic_event::connect_to_next_event_p vfunc.
(start_cfg_edge_event::connect_to_next_event_p): Likewise.
(start_consolidated_cfg_edges_event::connect_to_next_event_p):
Likewise.
* infinite-loop.cc (class looping_back_event): New subclass.
(infinite_loop_diagnostic::add_final_event): Use it.

gcc/ChangeLog:
* common.opt (fdiagnostics-show-event-links): New option.
* diagnostic-label-effects.h: New file.
* diagnostic-path.h (diagnostic_event::connect_to_next_event_p):
New pure virtual function.
(simple_diagnostic_event::connect_to_next_event_p): Implement it.
(simple_diagnostic_event::connect_to_next_event): New.
(simple_diagnostic_event::m_connected_to_next_event): New field.
(simple_diagnostic_path::connect_to_next_event): New decl.
* diagnostic-show-locus.cc: Include "text-art/theme.h" and
"diagnostic-label-effects.h".
(colorizer::set_cfg_edge): New.
(layout::m_fallback_theme): New field.
(layout::m_theme): New field.
(layout::m_effect_info): New field.
(layout::m_link_lhs_state): New enum and field.
(layout::m_link_rhs_column): New field.
(layout_range::has_in_edge): New.
(layout_range::has_out_edge): New.
(layout::layout): Add "effect_info" optional param.  Initialize
m_theme, m_link_lhs_state, and m_link_rhs_column.
(layout::maybe_add_location_range): Remove stray "FIXME" from
leading comment.
(layout::print_source_line): Replace space after margin with a
call to print_leftmost_column.
(layout::print_leftmost_column): New.
(layout::start_annotation_line): Make non-const.  Gain
responsibility for printing the leftmost column after the margin.
(layout::print_annotation_line): Drop pp_space, as this is now
added by start_annotation_line.
(line_label::line_label): Add "has_in_edge" and "has_out_edge"
params and initialize...
(line_label::m_has_in_edge): New field.
(line_label::m_has_out_edge): New field.
(layout::print_any_labels): Pass edge information to line_label
ctor.  Keep track of in-edges and out-edges, adding visualizations
of these links between labels.
(layout::print_leading_fixits):  Drop pp_character, as this is now
added by start_annotation_line.
(layout::print_trailing_fixits): Fix off-by-one errors in column
calculation.
(layout::move_to_column): Add comment about debugging.
(layout::show_ruler): Make non-const.  Drop pp_space calls, as
this is now added by start_annotation_line.
(layout::print_line): Call print_any_right_to_left_edge_lines.
(layout::print_any_right_to_left_edge_lines): New.
(layout::update_any_effects): New.
(gcc_rich_location::add_location_if_nearby): Initialize
loc_range.m_label.
(diagnostic_context::maybe_show_locus): Add "effects" param and
pass it to diagnostic_context::show_locus.
(diagnostic_context::show_locus): Add "effects" param, passing it
to layout's ctor.  Call update_any_effects on the layout after
printing the lines.
(selftest::test_layout_x_offset_display_utf8): Update expected
result for eliminated trailing newline.
(selftest::test_layout_x_offset_display_utf8): Likewise.
(selftest::test_layout_x_offset_display_tab): Likewise.
* diagnostic.cc (diagnostic_context::initialize): Initialize
m_source_printing.show_event_links_p.
(simple_diagnostic_path::connect_to_next_event): New.
(simple_diagnostic_event::simple_diagnostic_event): Initialize
m_connected_to_next_event.
* diagnostic.h (class diagnostic_source_effect_info): New forward
decl.
(diagnostic_source_printing_options::show_event_links_p): New
field.
(diagnostic_context::maybe_show_locus): Add optional "effect_info"
param.
(diagnostic_context::show_locus): Add "effect_info" param.
(diagnostic_show_locus): Add optional "effect_info" param.
* doc/invoke.texi: Add -fno-diagnostics-show-event-links.
* lto-wrapper.cc (merge_and_complain): Add
OPT_fdiagnostics_show_event_links to switch.
(append_compiler_options): Likewise.
(append_diag_options): Likewise.
* opts-common.cc (decode_cmdline_options_to_array): Add
"-fno-diagnostics-show-event-links" to -fdiagnostics-plain-output.
* opts.cc (common_handle_option): Add case for
OPT_fdiagnostics_show_event_links.
* text-art/theme.cc (ascii_theme::get_cppchar): Handle
cell_kind::CFG_*.
(unicode_theme::get_cppchar): Likewise.
* text-art/theme.h (theme::cell_kind): Add CFG_*.
* toplev.cc (general_init): Initialize
global_dc->m_source_printing.show_event_links_p.
* tree-diagnostic-path.cc: Define INCLUDE_ALGORITHM,
INCLUDE_MEMORY, and INCLUDE_STRING.  Include
"diagnostic-label-effects.h".
(path_label::path_label): Initialize m_effects.
(path_label::get_effects): New.
(class path_label::path_label_effects): New.
(path_label::m_effects): New field.
(class per_thread_summary): Add "friend struct event_range;".
(per_thread_summary::per_thread_summary): Initialize m_last_event.
(per_thread_summary::m_last_event): New field.
(struct event_range::per_source_line_info): New.
(event_range::event_range): Make "t" non-const.  Add
"show_event_links" param and use it to initialize
m_show_event_links.  Add info for initial event.
(event_range::get_per_source_line_info): New.
(event_range::maybe_add_event): Verify compatibility of the new
label and existing labels with respect to the link-printing code.
Update per-source-line info when an event is added.
(event_range::print): Add"effect_info" param and pass to
diagnostic_show_locus.
(event_range::m_per_thread_summary): Make non-const.
(event_range::m_source_line_info_map): New field.
(event_range::m_show_event_links): New field.
(path_summary::path_summary): Add "show_event_links" optional
param, passing it to event_range ctor calls. Update
pts.m_last_event.
(thread_event_printer::print_swimlane_for_event_range): Add
"effect_info" param and pass it to range->print.
(print_path_summary_as_text): Keep track of the column for any
out-edges at the end of printing each event_range and use as
the leading in-edge for the next event_range.
(default_tree_diagnostic_path_printer): Pass in show_event_links_p
to path_summary ctor.
(selftest::path_events_have_column_data_p): New.
(class selftest::control_flow_test): New.
(selftest::test_control_flow_1): New.
(selftest::test_control_flow_2): New.
(selftest::test_control_flow_3): New.
(selftest::assert_cfg_edge_path_streq): New.
(ASSERT_CFG_EDGE_PATH_STREQ): New macro.
(selftest::test_control_flow_4): New.
(selftest::test_control_flow_5): New.
(selftest::test_control_flow_6): New.
(selftest::control_flow_tests): New.
(selftest::tree_diagnostic_path_cc_tests): Disable colorization on
global_dc's printer.  Convert event_pp to a std::unique_ptr. Call
control_flow_tests via for_each_line_table_case.
(gen_command_line_string): Likewise.

gcc/testsuite/ChangeLog:
* gcc.dg/analyzer/event-links-ascii.c: New test.
* gcc.dg/analyzer/event-links-color.c: New test.
* gcc.dg/analyzer/event-links-disabled.c: New test.
* gcc.dg/analyzer/event-links-unicode.c: New test.

libcpp/ChangeLog:
* include/rich-location.h (class label_effects): New forward decl.
(range_label::get_effects): New vfunc.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
21 files changed:
gcc/analyzer/checker-event.h
gcc/analyzer/infinite-loop.cc
gcc/common.opt
gcc/diagnostic-label-effects.h [new file with mode: 0644]
gcc/diagnostic-path.h
gcc/diagnostic-show-locus.cc
gcc/diagnostic.cc
gcc/diagnostic.h
gcc/doc/invoke.texi
gcc/lto-wrapper.cc
gcc/opts-common.cc
gcc/opts.cc
gcc/testsuite/gcc.dg/analyzer/event-links-ascii.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/event-links-color.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/event-links-disabled.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/event-links-unicode.c [new file with mode: 0644]
gcc/text-art/theme.cc
gcc/text-art/theme.h
gcc/toplev.cc
gcc/tree-diagnostic-path.cc
libcpp/include/rich-location.h

index d2fb87fb523fa739bd966aaaf45c1c8ad3107e9f..7a4510ee81d0dfb1c1b0ba5a4747926b5561b8a0 100644 (file)
@@ -113,6 +113,7 @@ public:
       return NULL;
   }
   meaning get_meaning () const override;
+  bool connect_to_next_event_p () const override { return false; }
   diagnostic_thread_id_t get_thread_id () const final override
   {
     return 0;
@@ -451,6 +452,7 @@ public:
   }
 
   label_text get_desc (bool can_colorize) const override;
+  bool connect_to_next_event_p () const final override { return true; }
 
 protected:
   label_text maybe_describe_condition (bool can_colorize) const;
@@ -534,6 +536,7 @@ public:
 
   label_text get_desc (bool can_colorize) const final override;
   meaning get_meaning () const override;
+  bool connect_to_next_event_p () const final override { return true; }
 
  private:
   bool m_edge_sense;
index e277a8384a047fd53e6c610939083a5c65cfdc51..04346cdfdc3c4a4d3446f694cd036827da44a7ef 100644 (file)
@@ -162,6 +162,21 @@ public:
   }
 };
 
+class looping_back_event : public start_cfg_edge_event
+{
+public:
+  looping_back_event (const exploded_edge &eedge,
+                     const event_loc_info &loc_info)
+  : start_cfg_edge_event (eedge, loc_info)
+  {
+  }
+
+  label_text get_desc (bool can_colorize) const final override
+  {
+    return label_text::borrow ("looping back...");
+  }
+};
+
 /* A subclass of pending_diagnostic for complaining about suspected
    infinite loops.  */
 
@@ -300,8 +315,7 @@ public:
        else if (cfg_sedge->back_edge_p ())
          {
            emission_path->add_event
-             (make_unique<precanned_custom_event>
-              (loc_info_from, "looping back..."));
+             (make_unique<looping_back_event> (*eedge, loc_info_from));
            emission_path->add_event
              (make_unique<end_cfg_edge_event>
               (*eedge,
index 40d90817b8681790d54aac0a4c86cdf6a8f88b79..2c078fdd1f8637a182a964196d42c61b086df9dc 100644 (file)
@@ -1368,6 +1368,10 @@ fdiagnostics-show-caret
 Common Var(flag_diagnostics_show_caret) Init(1)
 Show the source line with a caret indicating the column.
 
+fdiagnostics-show-event-links
+Common Var(flag_diagnostics_show_event_links) Init(1)
+Show lines linking related events in diagnostic paths.
+
 fdiagnostics-show-labels
 Common Var(flag_diagnostics_show_labels) Init(1)
 Show labels annotating ranges of source code when showing source.
diff --git a/gcc/diagnostic-label-effects.h b/gcc/diagnostic-label-effects.h
new file mode 100644 (file)
index 0000000..e640f3d
--- /dev/null
@@ -0,0 +1,58 @@
+/* Classes for adding special effects when quoting source code.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_DIAGNOSTIC_LABEL_EFFECTS_H
+#define GCC_DIAGNOSTIC_LABEL_EFFECTS_H
+
+/* Abstract base class for describing special effects when printing
+   a label when quoting source code.  */
+
+class label_effects
+{
+public:
+  virtual ~label_effects () {}
+
+  /* Adding links between labels, e.g. for visualizing control flow
+     in execution paths.  */
+  virtual bool has_in_edge (unsigned range_idx) const = 0;
+  virtual bool has_out_edge (unsigned range_idx) const = 0;
+};
+
+/* A class to hold state when quoting a run of lines of source code.  */
+
+class diagnostic_source_effect_info
+{
+public:
+  diagnostic_source_effect_info ()
+  : m_leading_in_edge_column (-1),
+    m_trailing_out_edge_column (-1)
+  {
+  }
+
+  /* The column for an incoming link to the first label,
+     or -1 if no such link.  */
+  int m_leading_in_edge_column;
+
+  /* The column for an outgoing link from the final label,
+     or -1 if no such link.  */
+  int m_trailing_out_edge_column;
+};
+
+#endif /* GCC_DIAGNOSTIC_LABEL_EFFECTS_H */
index 696991c6d736683478d0db0fbcaea363bc742e1c..982d68b872eab5be8081e7794da2e6f565b37ced 100644 (file)
@@ -156,6 +156,10 @@ class diagnostic_event
 
   virtual meaning get_meaning () const = 0;
 
+  /* True iff we should draw a line connecting this event to the
+     next event (e.g. to highlight control flow).  */
+  virtual bool connect_to_next_event_p () const = 0;
+
   virtual diagnostic_thread_id_t get_thread_id () const = 0;
 
   /* Hook for SARIF output to allow for adding diagnostic-specific
@@ -224,16 +228,26 @@ class simple_diagnostic_event : public diagnostic_event
   {
     return meaning ();
   }
+  bool connect_to_next_event_p () const final override
+  {
+    return m_connected_to_next_event;
+  }
   diagnostic_thread_id_t get_thread_id () const final override
   {
     return m_thread_id;
   }
 
+  void connect_to_next_event ()
+  {
+    m_connected_to_next_event = true;
+  }
+
  private:
   location_t m_loc;
   tree m_fndecl;
   int m_depth;
   char *m_desc; // has been i18n-ed and formatted
+  bool m_connected_to_next_event;
   diagnostic_thread_id_t m_thread_id;
 };
 
@@ -277,6 +291,8 @@ class simple_diagnostic_path : public diagnostic_path
                    const char *fmt, ...)
     ATTRIBUTE_GCC_DIAG(6,7);
 
+  void connect_to_next_event ();
+
  private:
   auto_delete_vec<simple_diagnostic_thread> m_threads;
   auto_delete_vec<simple_diagnostic_event> m_events;
index f42006cfe2a15756054a4bc3bac8645590a51e31..007acc4e0147171af560be3ea367db7725dc5a56 100644 (file)
@@ -33,6 +33,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "selftest-diagnostic.h"
 #include "cpplib.h"
 #include "text-art/types.h"
+#include "text-art/theme.h"
+#include "diagnostic-label-effects.h"
 
 #ifdef HAVE_TERMIOS_H
 # include <termios.h>
@@ -100,6 +102,7 @@ class colorizer
     else
       set_state (range_idx);
   }
+  void set_cfg_edge () { set_state (0); }
   void set_normal_text () { set_state (STATE_NORMAL_TEXT); }
   void set_fixit_insert () { set_state (STATE_FIXIT_INSERT); }
   void set_fixit_delete () { set_state (STATE_FIXIT_DELETE); }
@@ -236,6 +239,9 @@ class layout_range
                       enum column_unit col_unit) const;
   bool intersects_line_p (linenum_type row) const;
 
+  bool has_in_edge () const;
+  bool has_out_edge () const;
+
   layout_point m_start;
   layout_point m_finish;
   enum range_display_kind m_range_display_kind;
@@ -371,7 +377,8 @@ class layout
   layout (const diagnostic_context &context,
          const rich_location &richloc,
          diagnostic_t diagnostic_kind,
-         pretty_printer *pp);
+         pretty_printer *pp,
+         diagnostic_source_effect_info *effect_info = nullptr);
 
   bool maybe_add_location_range (const location_range *loc_range,
                                 unsigned original_idx,
@@ -390,22 +397,27 @@ class layout
 
   void print_line (linenum_type row);
 
+  void print_any_right_to_left_edge_lines ();
+
   void on_bad_codepoint (const char *ptr, cppchar_t ch, size_t ch_sz);
 
+  void update_any_effects () const;
+
  private:
   bool will_show_line_p (linenum_type row) const;
   void print_leading_fixits (linenum_type row);
   line_bounds print_source_line (linenum_type row, const char *line,
                                 int line_bytes);
   bool should_print_annotation_line_p (linenum_type row) const;
-  void start_annotation_line (char margin_char = ' ') const;
+  void print_leftmost_column ();
+  void start_annotation_line (char margin_char = ' ');
   void print_annotation_line (linenum_type row, const line_bounds lbounds);
   void print_any_labels (linenum_type row);
   void print_trailing_fixits (linenum_type row);
 
   bool annotation_line_showed_range_p (linenum_type line, int start_column,
                                       int finish_column) const;
-  void show_ruler (int max_column) const;
+  void show_ruler (int max_column);
 
   bool validate_fixit_hint_p (const fixit_hint *hint);
 
@@ -437,6 +449,9 @@ class layout
   const line_maps *m_line_table;
   file_cache &m_file_cache;
   pretty_printer *m_pp;
+  const text_art::ascii_theme m_fallback_theme;
+  const text_art::theme &m_theme;
+  diagnostic_source_effect_info *m_effect_info;
   char_display_policy m_policy;
   location_t m_primary_loc;
   exploc_with_display_col m_exploc;
@@ -448,6 +463,55 @@ class layout
   int m_linenum_width;
   int m_x_offset_display;
   bool m_escape_on_output;
+
+  /* Fields for handling links between labels (e.g. for showing CFG edges
+     in execution paths).
+     Note that the logic for printing such links makes various simplifying
+     assumptions about the set of labels in the rich_location, and users
+     of this code will need to split up labels into separate rich_location
+     instances to respect these assumptions, or the output will look wrong.
+     See the diagnostic_path-printing code for more information.  */
+
+  /* An enum for describing the state of the leftmost column,
+     used for showing links between labels.
+     Consider e.g.
+     .x0000000001111111111222222222233333333334444444444.
+     .x1234567890123456789012345678901234567890123456789.
+     |      |                                    <- none
+     |      (9) following ‘false’ branch... ->-+ <- none
+     |                                         | <- none
+     |                                         | <- none
+     |+----------------------------------------+ <- rewinding to lhs
+     ||  result->i = i;                          <- at lhs
+     ||  ~~~~~~~~~~^~~                           <- at lhs
+     ||            |                             <- at lhs
+     |+----------->(10) ...to here               <- indenting to dest
+     ^^
+     ||
+     |leftmost column ("x" above).
+     "margin".  */
+  enum class link_lhs_state {
+    none,
+    rewinding_to_lhs,
+    at_lhs,
+    indenting_to_dest
+  } m_link_lhs_state;
+
+  /* The column of the current link on the RHS, if any, or
+     -1 if there is none.
+     Consider e.g.
+     .x0000000001111111111222222222233333333334444444444.
+     .x1234567890123456789012345678901234567890123456789.
+     |      |                                     <- -1
+     |      (10) following ‘false’ branch... ->-+ <- 42
+     |                                          | <- 42
+     |                                          | <- 42
+     |+-----------------------------------------+ <- 42
+     ||  result->i = i;                           <- -1
+     ||  ~~~~~~~~~~^~~                            <- -1
+     ||            |                              <- -1
+     |+----------->(11) ...to here                <- -1.  */
+  int m_link_rhs_column;
 };
 
 /* Implementation of "class colorizer".  */
@@ -691,6 +755,34 @@ layout_range::intersects_line_p (linenum_type row) const
   return true;
 }
 
+/* Return true if this layout_range should have an in-edge.  */
+
+bool
+layout_range::has_in_edge () const
+{
+  if (!m_label)
+    return false;
+  const label_effects *effects = m_label->get_effects (m_original_idx);
+  if (!effects)
+    return false;
+
+  return effects->has_in_edge (m_original_idx);
+}
+
+/* Return true if this layout_range should have an out-edge.  */
+
+bool
+layout_range::has_out_edge () const
+{
+  if (!m_label)
+    return false;
+  const label_effects *effects = m_label->get_effects (m_original_idx);
+  if (!effects)
+    return false;
+
+  return effects->has_out_edge (m_original_idx);
+}
+
 #if CHECKING_P
 
 /* Default for when we don't care what the tab expansion is set to.  */
@@ -1196,11 +1288,17 @@ make_policy (const diagnostic_context &dc,
 layout::layout (const diagnostic_context &context,
                const rich_location &richloc,
                diagnostic_t diagnostic_kind,
-               pretty_printer *pp)
+               pretty_printer *pp,
+               diagnostic_source_effect_info *effect_info)
 : m_options (context.m_source_printing),
   m_line_table (richloc.get_line_table ()),
   m_file_cache (context.get_file_cache ()),
   m_pp (pp ? pp : context.printer),
+  /* Ensure we have a non-null m_theme. */
+  m_theme (context.get_diagram_theme ()
+          ? *context.get_diagram_theme ()
+          : *static_cast <const text_art::theme *> (&m_fallback_theme)),
+  m_effect_info (effect_info),
   m_policy (make_policy (context, richloc)),
   m_primary_loc (richloc.get_range (0)->m_loc),
   m_exploc (m_file_cache,
@@ -1213,8 +1311,15 @@ layout::layout (const diagnostic_context &context,
   m_line_spans (1 + richloc.get_num_locations ()),
   m_linenum_width (0),
   m_x_offset_display (0),
-  m_escape_on_output (richloc.escape_on_output_p ())
+  m_escape_on_output (richloc.escape_on_output_p ()),
+  m_link_lhs_state (link_lhs_state::none),
+  m_link_rhs_column (-1)
 {
+  if (m_options.show_event_links_p)
+    if (effect_info)
+      if (effect_info->m_leading_in_edge_column)
+       m_link_rhs_column = effect_info->m_leading_in_edge_column;
+
   for (unsigned int idx = 0; idx < richloc.get_num_locations (); idx++)
     {
       /* This diagnostic printer can only cope with "sufficiently sane" ranges.
@@ -1249,7 +1354,7 @@ layout::layout (const diagnostic_context &context,
    those that we can sanely print.
 
    ORIGINAL_IDX is the index of LOC_RANGE within its rich_location,
-   (for use as extrinsic state by label ranges FIXME).
+   (for use as extrinsic state by label ranges).
 
    If RESTRICT_TO_CURRENT_LINE_SPANS is true, then LOC_RANGE is also
    filtered against this layout instance's current line spans: it
@@ -1718,10 +1823,10 @@ layout::print_source_line (linenum_type row, const char *line, int line_bytes)
       int width = num_digits (row);
       for (int i = 0; i < m_linenum_width - width; i++)
        pp_space (m_pp);
-      pp_printf (m_pp, "%i | ", row);
+      pp_printf (m_pp, "%i |", row);
     }
-  else
-    pp_space (m_pp);
+
+  print_leftmost_column ();
 
   /* We will stop printing the source line at any trailing whitespace.  */
   line_bytes = get_line_bytes_without_trailing_whitespace (line,
@@ -1824,11 +1929,59 @@ layout::should_print_annotation_line_p (linenum_type row) const
   return false;
 }
 
+/* Print the leftmost column after the margin, which is used for showing
+   links between labels (e.g. for CFG edges in execution paths).  */
+
+void
+layout::print_leftmost_column ()
+{
+  if (!m_options.show_event_links_p)
+    gcc_assert (m_link_lhs_state == link_lhs_state::none);
+
+  switch (m_link_lhs_state)
+    {
+    default:
+      gcc_unreachable ();
+    case link_lhs_state::none:
+      pp_space (m_pp);
+      break;
+    case link_lhs_state::rewinding_to_lhs:
+      {
+       m_colorizer.set_cfg_edge ();
+       const cppchar_t ch= m_theme.get_cppchar
+         (text_art::theme::cell_kind::CFG_FROM_LEFT_TO_DOWN);
+       pp_unicode_character (m_pp, ch);
+       m_colorizer.set_normal_text ();
+      }
+      break;
+    case link_lhs_state::at_lhs:
+      {
+       m_colorizer.set_cfg_edge ();
+       const cppchar_t ch= m_theme.get_cppchar
+         (text_art::theme::cell_kind::CFG_DOWN);
+       pp_unicode_character (m_pp, ch);
+       m_colorizer.set_normal_text ();
+      }
+      break;
+    case link_lhs_state::indenting_to_dest:
+      {
+       m_colorizer.set_cfg_edge ();
+       const cppchar_t ch= m_theme.get_cppchar
+         (text_art::theme::cell_kind::CFG_FROM_DOWN_TO_RIGHT);
+       pp_unicode_character (m_pp, ch);
+       m_colorizer.set_normal_text ();
+      }
+      break;
+    }
+}
+
 /* Begin an annotation line.  If m_show_line_numbers_p, print the left
-   margin, which is empty for annotation lines.  Otherwise, do nothing.  */
+   margin, which is empty for annotation lines.
+   After any left margin, print a leftmost column, which is used for
+   showing links between labels (e.g. for CFG edges in execution paths).  */
 
 void
-layout::start_annotation_line (char margin_char) const
+layout::start_annotation_line (char margin_char)
 {
   pp_emit_prefix (m_pp);
   if (m_options.show_line_numbers_p)
@@ -1842,6 +1995,10 @@ layout::start_annotation_line (char margin_char) const
        pp_character (m_pp, margin_char);
       pp_string (m_pp, " |");
     }
+  if (margin_char == ' ')
+    print_leftmost_column ();
+  else
+    pp_character (m_pp, margin_char);
 }
 
 /* Print a line consisting of the caret/underlines for the given
@@ -1854,7 +2011,6 @@ layout::print_annotation_line (linenum_type row, const line_bounds lbounds)
                                     lbounds.m_last_non_ws_disp_col);
 
   start_annotation_line ();
-  pp_space (m_pp);
 
   for (int column = 1 + m_x_offset_display; column < x_bound; column++)
     {
@@ -1926,9 +2082,13 @@ class line_label
 {
 public:
   line_label (int state_idx, int column,
-             label_text text)
+             label_text text,
+             bool has_in_edge,
+             bool has_out_edge)
   : m_state_idx (state_idx), m_column (column),
-    m_text (std::move (text)), m_label_line (0), m_has_vbar (true)
+    m_text (std::move (text)), m_label_line (0), m_has_vbar (true),
+    m_has_in_edge (has_in_edge),
+    m_has_out_edge (has_out_edge)
   {
     /* Using styled_string rather than cpp_display_width here
        lets us skip SGR formatting characters for color and URLs.
@@ -1959,6 +2119,8 @@ public:
   size_t m_display_width;
   int m_label_line;
   bool m_has_vbar;
+  bool m_has_in_edge;
+  bool m_has_out_edge;
 };
 
 /* Print any labels in this row.  */
@@ -1996,7 +2158,9 @@ layout::print_any_labels (linenum_type row)
        if (text.get () == NULL)
          continue;
 
-       labels.safe_push (line_label (i, disp_col, std::move (text)));
+       labels.safe_push (line_label (i, disp_col, std::move (text),
+                                     range->has_in_edge (),
+                                     range->has_out_edge ()));
       }
   }
 
@@ -2040,6 +2204,7 @@ layout::print_any_labels (linenum_type row)
            label 1         : label line 3.  */
 
   int max_label_line = 1;
+  int label_line_with_in_edge = -1;
   {
     int next_column = INT_MAX;
     line_label *label;
@@ -2058,18 +2223,28 @@ layout::print_any_labels (linenum_type row)
          }
 
        label->m_label_line = max_label_line;
+       if (m_options.show_event_links_p)
+         if (label->m_has_in_edge)
+           label_line_with_in_edge = max_label_line;
        next_column = label->m_column;
       }
   }
 
+  gcc_assert (labels.length () > 0);
+
   /* Print the "label lines".  For each label within the line, print
      either a vertical bar ('|') for the labels that are lower down, or the
      labels themselves once we've reached their line.  */
   {
     for (int label_line = 0; label_line <= max_label_line; label_line++)
       {
+       if (label_line == label_line_with_in_edge)
+         {
+           gcc_assert (m_options.show_event_links_p);
+           m_link_lhs_state = link_lhs_state::indenting_to_dest;
+         }
        start_annotation_line ();
-       pp_space (m_pp);
+
        int column = 1 + m_x_offset_display;
        line_label *label;
        FOR_EACH_VEC_ELT (labels, i, label)
@@ -2081,7 +2256,35 @@ layout::print_any_labels (linenum_type row)
            if (label_line == label->m_label_line)
              {
                gcc_assert (column <= label->m_column);
-               move_to_column (&column, label->m_column, true);
+
+               if (label_line == label_line_with_in_edge)
+                 {
+                   /* Print a prefix showing an incoming
+                      link from another label.
+                      .|+----------->(10) ...to here
+                      . ^~~~~~~~~~~~~
+                      . this text.  */
+                   gcc_assert (m_options.show_event_links_p);
+                   m_colorizer.set_cfg_edge ();
+                   const cppchar_t right= m_theme.get_cppchar
+                     (text_art::theme::cell_kind::CFG_RIGHT);
+                   while (column < label->m_column - 1)
+                     {
+                       pp_unicode_character (m_pp, right);
+                       column++;
+                     }
+                   if (column == label->m_column - 1)
+                     {
+                       pp_character (m_pp, '>');
+                       column++;
+                     }
+                   m_colorizer.set_normal_text ();
+                   m_link_lhs_state = link_lhs_state::none;
+                   label_line_with_in_edge = -1;
+                 }
+               else
+                 move_to_column (&column, label->m_column, true);
+               gcc_assert (column == label->m_column);
                /* Colorize the text, unless it's for events in a
                   diagnostic_path.  */
                if (!m_diagnostic_path_p)
@@ -2089,6 +2292,29 @@ layout::print_any_labels (linenum_type row)
                pp_string (m_pp, label->m_text.m_buffer);
                m_colorizer.set_normal_text ();
                column += label->m_display_width;
+               if (m_options.show_event_links_p && label->m_has_out_edge)
+                 {
+                   /* Print a suffix showing the start of a linkage
+                      to another label e.g. " ->-+" which will be the
+                      first part of e.g.
+                      .      (9) following ‘false’ branch... ->-+ <- HERE
+                      .                                         |
+                      .                                         |
+                      .  */
+                   const cppchar_t right= m_theme.get_cppchar
+                     (text_art::theme::cell_kind::CFG_RIGHT);
+                   const cppchar_t from_right_to_down= m_theme.get_cppchar
+                     (text_art::theme::cell_kind::CFG_FROM_RIGHT_TO_DOWN);
+                   m_colorizer.set_cfg_edge ();
+                   pp_space (m_pp);
+                   pp_unicode_character (m_pp, right);
+                   pp_unicode_character (m_pp, '>');
+                   pp_unicode_character (m_pp, right);
+                   pp_unicode_character (m_pp, from_right_to_down);
+                   m_colorizer.set_normal_text ();
+                   column += 5;
+                   m_link_rhs_column = column - 1;
+                 }
              }
            else if (label->m_has_vbar)
              {
@@ -2100,10 +2326,38 @@ layout::print_any_labels (linenum_type row)
                column++;
              }
          }
+
+       /* If we have a vertical link line on the RHS, print the
+          '|' on this annotation line after the labels.  */
+       if (m_link_rhs_column != -1 && column < m_link_rhs_column)
+         {
+           move_to_column (&column, m_link_rhs_column, true);
+           m_colorizer.set_cfg_edge ();
+           const cppchar_t down= m_theme.get_cppchar
+             (text_art::theme::cell_kind::CFG_DOWN);
+           pp_unicode_character (m_pp, down);
+           m_colorizer.set_normal_text ();
+         }
+
        print_newline ();
       }
     }
 
+  /* If we have a vertical link line on the RHS, print a trailing
+     annotation line showing the vertical line.  */
+  if (m_link_rhs_column != -1)
+    {
+      int column = 1 + m_x_offset_display;
+      start_annotation_line ();
+      move_to_column (&column, m_link_rhs_column, true);
+      m_colorizer.set_cfg_edge ();
+      const cppchar_t down= m_theme.get_cppchar
+       (text_art::theme::cell_kind::CFG_DOWN);
+      pp_unicode_character (m_pp, down);
+      m_colorizer.set_normal_text ();
+      print_newline ();
+    }
+
   /* Clean up.  */
   {
     line_label *label;
@@ -2139,7 +2393,6 @@ layout::print_leading_fixits (linenum_type row)
             the surrounding text.  */
          m_colorizer.set_normal_text ();
          start_annotation_line ('+');
-         pp_character (m_pp, '+');
          m_colorizer.set_fixit_insert ();
          /* Print all but the trailing newline of the fix-it hint.
             We have to print the newline separately to avoid
@@ -2598,7 +2851,7 @@ layout::print_trailing_fixits (linenum_type row)
   /* Now print the corrections.  */
   unsigned i;
   correction *c;
-  int column = m_x_offset_display;
+  int column = 1 + m_x_offset_display;
 
   if (!corrections.m_corrections.is_empty ())
     start_annotation_line ();
@@ -2649,7 +2902,7 @@ layout::print_trailing_fixits (linenum_type row)
     }
 
   /* Add a trailing newline, if necessary.  */
-  move_to_column (&column, 0, false);
+  move_to_column (&column, 1 + m_x_offset_display, false);
 }
 
 /* Disable any colorization and emit a newline.  */
@@ -2766,11 +3019,15 @@ layout::move_to_column (int *column, int dest_column, bool add_left_margin)
       print_newline ();
       if (add_left_margin)
        start_annotation_line ();
-      *column = m_x_offset_display;
+      *column = 1 + m_x_offset_display;
     }
 
   while (*column < dest_column)
     {
+      /* For debugging column issues, it can be helpful to replace this
+        pp_space call with
+          pp_character (m_pp, '0' + (*column % 10));
+        to visualize the changing value of "*column".  */
       pp_space (m_pp);
       (*column)++;
     }
@@ -2780,13 +3037,12 @@ layout::move_to_column (int *column, int dest_column, bool add_left_margin)
    (after the 1-column indent).  */
 
 void
-layout::show_ruler (int max_column) const
+layout::show_ruler (int max_column)
 {
   /* Hundreds.  */
   if (max_column > 99)
     {
       start_annotation_line ();
-      pp_space (m_pp);
       for (int column = 1 + m_x_offset_display; column <= max_column; column++)
        if (column % 10 == 0)
          pp_character (m_pp, '0' + (column / 100) % 10);
@@ -2797,7 +3053,6 @@ layout::show_ruler (int max_column) const
 
   /* Tens.  */
   start_annotation_line ();
-  pp_space (m_pp);
   for (int column = 1 + m_x_offset_display; column <= max_column; column++)
     if (column % 10 == 0)
       pp_character (m_pp, '0' + (column / 10) % 10);
@@ -2807,7 +3062,6 @@ layout::show_ruler (int max_column) const
 
   /* Units.  */
   start_annotation_line ();
-  pp_space (m_pp);
   for (int column = 1 + m_x_offset_display; column <= max_column; column++)
     pp_character (m_pp, '0' + (column % 10));
   pp_newline (m_pp);
@@ -2824,6 +3078,7 @@ layout::print_line (linenum_type row)
   if (!line)
     return;
 
+  print_any_right_to_left_edge_lines ();
   print_leading_fixits (row);
   const line_bounds lbounds
     = print_source_line (row, line.get_buffer (), line.length ());
@@ -2834,6 +3089,63 @@ layout::print_line (linenum_type row)
   print_trailing_fixits (row);
 }
 
+/* If there's a link column in the RHS, print something like this:
+   "                                           │\n"
+   "┌──────────────────────────────────────────┘\n"
+   showing the link entering at the top right and emerging
+   at the bottom left.  */
+
+void
+layout::print_any_right_to_left_edge_lines ()
+{
+  if (m_link_rhs_column == -1)
+    /* Can also happen if the out-edge had UNKNOWN_LOCATION.  */
+    return;
+
+  gcc_assert (m_options.show_event_links_p);
+
+  /* Print the line with "|".  */
+  start_annotation_line ();
+  int column = 1 + m_x_offset_display;
+  move_to_column (&column, m_link_rhs_column, true);
+  m_colorizer.set_cfg_edge ();
+  const cppchar_t down= m_theme.get_cppchar
+    (text_art::theme::cell_kind::CFG_DOWN);
+  pp_unicode_character (m_pp, down);
+  m_colorizer.set_normal_text ();
+  pp_newline (m_pp);
+
+  /* Print the line with "┌──────────────────────────────────────────┘".  */
+  m_link_lhs_state = link_lhs_state::rewinding_to_lhs;
+  start_annotation_line ();
+  m_colorizer.set_cfg_edge ();
+  const cppchar_t left= m_theme.get_cppchar
+    (text_art::theme::cell_kind::CFG_LEFT);
+  for (int column = 1 + m_x_offset_display; column < m_link_rhs_column;
+       column++)
+    pp_unicode_character (m_pp, left);
+  const cppchar_t from_down_to_left = m_theme.get_cppchar
+    (text_art::theme::cell_kind::CFG_FROM_DOWN_TO_LEFT);
+  pp_unicode_character (m_pp, from_down_to_left);
+  m_colorizer.set_normal_text ();
+  pp_newline (m_pp);
+
+  /* We now have a link line on the LHS,
+     and no longer have one on the RHS.  */
+  m_link_lhs_state = link_lhs_state::at_lhs;
+  m_link_rhs_column = -1;
+}
+
+/* Update this layout's m_effect_info (if any) after printing this
+   layout.  */
+
+void
+layout::update_any_effects () const
+{
+  if (m_effect_info)
+    m_effect_info->m_trailing_out_edge_column = m_link_rhs_column;
+}
+
 } /* End of anonymous namespace.  */
 
 /* If LOC is within the spans of lines that will already be printed for
@@ -2853,6 +3165,7 @@ gcc_rich_location::add_location_if_nearby (location_t loc,
   location_range loc_range;
   loc_range.m_loc = loc;
   loc_range.m_range_display_kind = SHOW_RANGE_WITHOUT_CARET;
+  loc_range.m_label = nullptr;
   if (!layout.maybe_add_location_range (&loc_range, 0,
                                        restrict_to_current_line_spans))
     return false;
@@ -2867,7 +3180,8 @@ gcc_rich_location::add_location_if_nearby (location_t loc,
 void
 diagnostic_context::maybe_show_locus (const rich_location &richloc,
                                      diagnostic_t diagnostic_kind,
-                                     pretty_printer *pp)
+                                     pretty_printer *pp,
+                                     diagnostic_source_effect_info *effects)
 {
   const location_t loc = richloc.get_loc ();
   /* Do nothing if source-printing has been disabled.  */
@@ -2888,19 +3202,22 @@ diagnostic_context::maybe_show_locus (const rich_location &richloc,
 
   m_last_location = loc;
 
-  show_locus (richloc, diagnostic_kind, pp);
+  show_locus (richloc, diagnostic_kind, pp, effects);
 }
 
 /* Print the physical source code corresponding to the location of
    this diagnostic, with additional annotations.
-   If PP is non-null, then use it rather than this context's printer.  */
+   If PP is non-null, then use it rather than this context's printer.
+   If EFFECTS is non-null, then use and update it.  */
 
 void
 diagnostic_context::show_locus (const rich_location &richloc,
                                diagnostic_t diagnostic_kind,
-                               pretty_printer *pp)
+                               pretty_printer *pp,
+                               diagnostic_source_effect_info *effects)
 {
-  layout layout (*this, richloc, diagnostic_kind, pp);
+  layout layout (*this, richloc, diagnostic_kind, pp, effects);
+
   for (int line_span_idx = 0; line_span_idx < layout.get_num_line_spans ();
        line_span_idx++)
     {
@@ -2929,6 +3246,8 @@ diagnostic_context::show_locus (const rich_location &richloc,
           row <= last_line; row++)
        layout.print_line (row);
     }
+
+  layout.update_any_effects ();
 }
 
 #if CHECKING_P
@@ -3137,7 +3456,7 @@ test_layout_x_offset_display_utf8 (const line_table_case &case_)
                  "   1 | \xf0\x9f\x98\x82\xf0\x9f\x98\x82 is a pair of emojis "
                  "that occupies 8 bytes and 4 display columns, starting at "
                  "column #102.\n"
-                 "     | ^\n\n",
+                 "     | ^\n",
                  pp_formatted_text (dc.printer));
   }
 
@@ -3162,7 +3481,7 @@ test_layout_x_offset_display_utf8 (const line_table_case &case_)
                  "   1 |  \xf0\x9f\x98\x82 is a pair of emojis "
                  "that occupies 8 bytes and 4 display columns, starting at "
                  "column #102.\n"
-                 "     |  ^\n\n",
+                 "     |  ^\n",
                  pp_formatted_text (dc.printer));
   }
 
@@ -3266,11 +3585,11 @@ test_layout_x_offset_display_tab (const line_table_case &case_)
       const char *output1
        = "   1 |   ' is a tab that occupies 1 byte and a variable number of "
          "display columns, starting at column #103.\n"
-         "     |   ^\n\n";
+         "     |   ^\n";
       const char *output2
        = "   1 | ` ' is a tab that occupies 1 byte and a variable number of "
          "display columns, starting at column #103.\n"
-         "     |   ^\n\n";
+         "     |   ^\n";
       const char *expected_output = (extra_width[tabstop] ? output1 : output2);
       ASSERT_STREQ (expected_output, pp_formatted_text (dc.printer));
     }
index 6ffd62361462e6a506c565c69145850c08efa6d5..1f30d1d7cdacb55e16106eaa54128e2e7125c461 100644 (file)
@@ -253,6 +253,7 @@ diagnostic_context::initialize (int n_opts)
   m_source_printing.show_line_numbers_p = false;
   m_source_printing.min_margin_width = 0;
   m_source_printing.show_ruler_p = false;
+  m_source_printing.show_event_links_p = false;
   m_report_bug = false;
   m_extra_output_kind = EXTRA_DIAGNOSTIC_OUTPUT_none;
   if (const char *var = getenv ("GCC_EXTRA_DIAGNOSTIC_OUTPUT"))
@@ -2627,6 +2628,16 @@ simple_diagnostic_path::add_thread_event (diagnostic_thread_id_t thread_id,
   return diagnostic_event_id_t (m_events.length () - 1);
 }
 
+/* Mark the most recent event on this path (which must exist) as being
+   connected to the next one to be added.  */
+
+void
+simple_diagnostic_path::connect_to_next_event ()
+{
+  gcc_assert (m_events.length () > 0);
+  m_events[m_events.length () - 1]->connect_to_next_event ();
+}
+
 /* struct simple_diagnostic_event.  */
 
 /* simple_diagnostic_event's ctor.  */
@@ -2638,6 +2649,7 @@ simple_diagnostic_event (location_t loc,
                         const char *desc,
                         diagnostic_thread_id_t thread_id)
 : m_loc (loc), m_fndecl (fndecl), m_depth (depth), m_desc (xstrdup (desc)),
+  m_connected_to_next_event (false),
   m_thread_id (thread_id)
 {
 }
index 065ac784e2587155840b26e2e786832f2c1ff1e2..7431f5a6e12cacc9cab19294c0f3df0f1bb3157c 100644 (file)
@@ -194,6 +194,7 @@ namespace json { class value; }
 class diagnostic_client_data_hooks;
 class logical_location;
 class diagnostic_diagram;
+class diagnostic_source_effect_info;
 
 /* Abstract base class for a particular output format for diagnostics;
    each value of -fdiagnostics-output-format= will have its own
@@ -360,6 +361,11 @@ struct diagnostic_source_printing_options
   /* Usable by plugins; if true, print a debugging ruler above the
      source output.  */
   bool show_ruler_p;
+
+  /* When printing events in an inline path, should we print lines
+     visualizing links between related events (e.g. for CFG paths)?
+     Corresponds to -fdiagnostics-show-event-links.  */
+  bool show_event_links_p;
 };
 
 /* This data structure bundles altogether any information relevant to
@@ -433,7 +439,8 @@ public:
 
   void maybe_show_locus (const rich_location &richloc,
                         diagnostic_t diagnostic_kind,
-                        pretty_printer *pp);
+                        pretty_printer *pp,
+                        diagnostic_source_effect_info *effect_info);
 
   void emit_diagram (const diagnostic_diagram &diagram);
 
@@ -573,7 +580,8 @@ private:
 
   void show_locus (const rich_location &richloc,
                   diagnostic_t diagnostic_kind,
-                  pretty_printer *pp);
+                  pretty_printer *pp,
+                  diagnostic_source_effect_info *effect_info);
 
   /* Data members.
      Ideally, all of these would be private and have "m_" prefixes.  */
@@ -920,10 +928,11 @@ inline void
 diagnostic_show_locus (diagnostic_context *context,
                       rich_location *richloc,
                       diagnostic_t diagnostic_kind,
-                      pretty_printer *pp = nullptr)
+                      pretty_printer *pp = nullptr,
+                      diagnostic_source_effect_info *effect_info = nullptr)
 {
   gcc_assert (richloc);
-  context->maybe_show_locus (*richloc, diagnostic_kind, pp);
+  context->maybe_show_locus (*richloc, diagnostic_kind, pp, effect_info);
 }
 
 /* Because we read source files a second time after the frontend did it the
index bcf518ac279a9439927b034723dd26dd6e7a4783..b9408ecc9188fd11c46565e7aa14208d0ee82f2b 100644 (file)
@@ -307,6 +307,7 @@ Objective-C and Objective-C++ Dialects}.
 -fdiagnostics-format=@r{[}text@r{|}sarif-stderr@r{|}sarif-file@r{|}json@r{|}json-stderr@r{|}json-file@r{]}
 -fno-diagnostics-json-formatting
 -fno-diagnostics-show-option  -fno-diagnostics-show-caret
+-fno-diagnostics-show-event-links
 -fno-diagnostics-show-labels  -fno-diagnostics-show-line-numbers
 -fno-diagnostics-show-cwe
 -fno-diagnostics-show-rule
@@ -5211,7 +5212,8 @@ options:
 -fdiagnostics-color=never
 -fdiagnostics-urls=never
 -fdiagnostics-path-format=separate-events
--fdiagnostics-text-art-charset=none}
+-fdiagnostics-text-art-charset=none
+-fno-diagnostics-show-event-links}
 In the future, if GCC changes the default appearance of its diagnostics, the
 corresponding option to disable the new behavior will be added to this list.
 
@@ -5446,6 +5448,31 @@ as the types of expressions:
 This option suppresses the printing of these labels (in the example above,
 the vertical bars and the ``char *'' and ``long int'' text).
 
+@opindex fno-diagnostics-show-event-links
+@opindex fdiagnostics-show-event-links
+@item -fno-diagnostics-show-event-links
+By default, when printing execution paths (via
+@option{-fdiagnostics-path-format=inline-events}), GCC will print lines
+connecting related events, such as the line connecting events 1 and 2 in:
+
+@smallexample
+    3 |   if (p)
+      |       ^
+      |       |
+      |       (1) following `false' branch (when `p' is NULL)... ->-+
+      |                                                             |
+      |                                                             |
+      |+------------------------------------------------------------+
+    4 ||    return 0;
+    5 ||  return *p;
+      ||         ~
+      ||         |
+      |+-------->(2) ...to here
+      |          (3) dereference of NULL `p'
+@end smallexample
+
+This option suppresses the printing of such connector lines.
+
 @opindex fno-diagnostics-show-cwe
 @opindex fdiagnostics-show-cwe
 @item -fno-diagnostics-show-cwe
index cfded757f2683f8464301bb2a4363209f87143ab..6dcf8b469a3cf6a4d012a3b14e8e830219e28195 100644 (file)
@@ -310,6 +310,7 @@ merge_and_complain (vec<cl_decoded_option> &decoded_options,
 
          /* Fallthru.  */
        case OPT_fdiagnostics_show_caret:
+       case OPT_fdiagnostics_show_event_links:
        case OPT_fdiagnostics_show_labels:
        case OPT_fdiagnostics_show_line_numbers:
        case OPT_fdiagnostics_show_option:
@@ -726,6 +727,7 @@ append_compiler_options (obstack *argv_obstack, vec<cl_decoded_option> opts)
       switch (option->opt_index)
        {
        case OPT_fdiagnostics_show_caret:
+       case OPT_fdiagnostics_show_event_links:
        case OPT_fdiagnostics_show_labels:
        case OPT_fdiagnostics_show_line_numbers:
        case OPT_fdiagnostics_show_option:
@@ -785,6 +787,7 @@ append_diag_options (obstack *argv_obstack, vec<cl_decoded_option> opts)
        case OPT_fdiagnostics_color_:
        case OPT_fdiagnostics_format_:
        case OPT_fdiagnostics_show_caret:
+       case OPT_fdiagnostics_show_event_links:
        case OPT_fdiagnostics_show_labels:
        case OPT_fdiagnostics_show_line_numbers:
        case OPT_fdiagnostics_show_option:
index 2d1e86ff94fa5a80e10846dc635e35f2c6a71bac..14084d08b05a0fbe671a5043065ceaa91882995a 100644 (file)
@@ -1090,7 +1090,8 @@ decode_cmdline_options_to_array (unsigned int argc, const char **argv,
            "-fdiagnostics-color=never",
            "-fdiagnostics-urls=never",
            "-fdiagnostics-path-format=separate-events",
-           "-fdiagnostics-text-art-charset=none"
+           "-fdiagnostics-text-art-charset=none",
+           "-fno-diagnostics-show-event-links"
          };
          const int num_expanded = ARRAY_SIZE (expanded_args);
          opt_array_len += num_expanded - 1;
index 14d1767e48f5c76a94ce5d179832f12559529845..f80d5d4ba8f99f758697b5c7a11fa89f258439f0 100644 (file)
@@ -2937,6 +2937,10 @@ common_handle_option (struct gcc_options *opts,
       dc->m_source_printing.enabled = value;
       break;
 
+    case OPT_fdiagnostics_show_event_links:
+      dc->m_source_printing.show_event_links_p = value;
+      break;
+
     case OPT_fdiagnostics_show_labels:
       dc->m_source_printing.show_labels_p = value;
       break;
@@ -3818,6 +3822,7 @@ gen_command_line_string (cl_decoded_option *options,
       case OPT_fdiagnostics_show_location_:
       case OPT_fdiagnostics_show_option:
       case OPT_fdiagnostics_show_caret:
+      case OPT_fdiagnostics_show_event_links:
       case OPT_fdiagnostics_show_labels:
       case OPT_fdiagnostics_show_line_numbers:
       case OPT_fdiagnostics_color_:
diff --git a/gcc/testsuite/gcc.dg/analyzer/event-links-ascii.c b/gcc/testsuite/gcc.dg/analyzer/event-links-ascii.c
new file mode 100644 (file)
index 0000000..fd68944
--- /dev/null
@@ -0,0 +1,61 @@
+/* Verify that we print event links for the analyzer, using ASCII.
+   C only: we don't care about any C/C++ differences between source
+   locations here.  */
+
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events" } */
+/* { dg-additional-options "-fdiagnostics-show-line-numbers" } */
+/* { dg-additional-options "-fdiagnostics-show-caret" } */
+/* { dg-additional-options "-fdiagnostics-show-event-links" } */
+/* { dg-enable-nn-line-numbers "" } */
+
+void test (int flag_a, int val, void *p)
+{
+  if (flag_a)
+    __builtin_free (p);
+  switch (val)
+    {
+    default:
+      break;
+    case 41:
+      break;
+    case 42:
+      __builtin_free (p); /* { dg-warning "double-'free' of 'p'" } */
+      break;
+    case 43:
+      break;
+    }
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |       __builtin_free (p);
+      |       ^~~~~~~~~~~~~~~~~~
+  'test': events 1-6
+   NN |   if (flag_a)
+      |      ^
+      |      |
+      |      (1) following 'true' branch (when 'flag_a != 0')... ->-+
+      |                                                             |
+      |                                                             |
+      |+------------------------------------------------------------+
+   NN ||    __builtin_free (p);
+      ||    ~~~~~~~~~~~~~~~~~~
+      ||    |
+      |+--->(2) ...to here
+      |     (3) first 'free' here
+   NN |   switch (val)
+      |   ~~~~~~
+      |   |
+      |   (4) following 'case 42:' branch... ->-+
+      |                                         |
+......
+      |                                         |
+      |+----------------------------------------+
+   NN ||    case 42:
+      ||    ~~~~
+      ||    |
+      |+--->(5) ...to here
+   NN |       __builtin_free (p);
+      |       ~~~~~~~~~~~~~~~~~~
+      |       |
+      |       (6) second 'free' here; first 'free' was at (3)
+    { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/event-links-color.c b/gcc/testsuite/gcc.dg/analyzer/event-links-color.c
new file mode 100644 (file)
index 0000000..3e5ef56
--- /dev/null
@@ -0,0 +1,66 @@
+/* Verify colorization of event links (using ASCII).
+   C only: we don't care about any C/C++ differences between source
+   locations here.  */
+
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events" } */
+/* { dg-additional-options "-fdiagnostics-show-line-numbers" } */
+/* { dg-additional-options "-fdiagnostics-show-caret" } */
+/* { dg-additional-options "-fdiagnostics-show-event-links" } */
+/* { dg-additional-options "-fdiagnostics-color=always" } */
+/* { dg-enable-nn-line-numbers "" } */
+
+void test (int flag_a, int val, void *p)
+{
+  if (flag_a)
+    __builtin_free (p);
+  switch (val)
+    {
+    default:
+      break;
+    case 41:
+      break;
+    case 42:
+      __builtin_free (p);
+      break;
+    case 43:
+      break;
+    }
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |       \e[01;35m\e[K__builtin_free (p)\e[m\e[K;
+      |       \e[01;35m\e[K^~~~~~~~~~~~~~~~~~\e[m\e[K
+  '\e[01m\e[Ktest\e[m\e[K': events 1-6
+   NN |   if \e[01;36m\e[K(\e[m\e[Kflag_a)
+      |      \e[01;36m\e[K^\e[m\e[K
+      |      \e[01;36m\e[K|\e[m\e[K
+      |      \e[01;36m\e[K(1)\e[m\e[K following '\e[01m\e[Ktrue\e[m\e[K' branch (when '\e[01m\e[Kflag_a != 0\e[m\e[K')...\e[01;36m\e[K ->-+\e[m\e[K
+      |                                                             \e[01;36m\e[K|\e[m\e[K
+      |                                                             \e[01;36m\e[K|\e[m\e[K
+      |\e[01;36m\e[K+\e[m\e[K\e[01;36m\e[K------------------------------------------------------------+\e[m\e[K
+   NN |\e[01;36m\e[K|\e[m\e[K    \e[01;36m\e[K__builtin_free (p)\e[m\e[K;
+      |\e[01;36m\e[K|\e[m\e[K    \e[01;36m\e[K~~~~~~~~~~~~~~~~~~\e[m\e[K
+      |\e[01;36m\e[K|\e[m\e[K    \e[01;36m\e[K|\e[m\e[K
+      |\e[01;36m\e[K+\e[m\e[K\e[01;36m\e[K--->\e[m\e[K\e[01;36m\e[K(2)\e[m\e[K ...to here
+      |     \e[01;36m\e[K(3)\e[m\e[K first '\e[01m\e[Kfree\e[m\e[K' here
+   NN |   \e[01;36m\e[Kswitch\e[m\e[K (val)
+      |   \e[01;36m\e[K~~~~~~\e[m\e[K
+      |   \e[01;36m\e[K|\e[m\e[K
+      |   \e[01;36m\e[K(4)\e[m\e[K following '\e[01m\e[Kcase 42:\e[m\e[K' branch...\e[01;36m\e[K ->-+\e[m\e[K
+      |                                         \e[01;36m\e[K|\e[m\e[K
+......
+      |                                         \e[01;36m\e[K|\e[m\e[K
+      |\e[01;36m\e[K+\e[m\e[K\e[01;36m\e[K----------------------------------------+\e[m\e[K
+   NN |\e[01;36m\e[K|\e[m\e[K    \e[01;36m\e[Kcase\e[m\e[K 42:
+      |\e[01;36m\e[K|\e[m\e[K    \e[01;36m\e[K~~~~\e[m\e[K
+      |\e[01;36m\e[K|\e[m\e[K    \e[01;36m\e[K|\e[m\e[K
+      |\e[01;36m\e[K+\e[m\e[K\e[01;36m\e[K--->\e[m\e[K\e[01;36m\e[K(5)\e[m\e[K ...to here
+   NN |       \e[01;36m\e[K__builtin_free (p)\e[m\e[K;
+      |       \e[01;36m\e[K~~~~~~~~~~~~~~~~~~\e[m\e[K
+      |       \e[01;36m\e[K|\e[m\e[K
+      |       \e[01;36m\e[K(6)\e[m\e[K second '\e[01m\e[Kfree\e[m\e[K' here; first '\e[01m\e[Kfree\e[m\e[K' was at \e[01;36m\e[K(3)\e[m\e[K
+    { dg-end-multiline-output "" } */
+
+/* DejaGnu won't recognize the warning due to the colorization codes, 
+   so skip it.  */
+/* { dg-prune-output ".*" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/event-links-disabled.c b/gcc/testsuite/gcc.dg/analyzer/event-links-disabled.c
new file mode 100644 (file)
index 0000000..e9cbbfb
--- /dev/null
@@ -0,0 +1,55 @@
+/* Verify that -fno-diagnostics-show-event-links works.
+   C only: we don't care about any C/C++ differences between source
+   locations here.  */
+
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events" } */
+/* { dg-additional-options "-fdiagnostics-show-line-numbers" } */
+/* { dg-additional-options "-fdiagnostics-show-caret" } */
+/* { dg-additional-options "-fno-diagnostics-show-event-links" } */
+/* { dg-enable-nn-line-numbers "" } */
+
+void test (int flag_a, int val, void *p)
+{
+  if (flag_a)
+    __builtin_free (p);
+  switch (val)
+    {
+    default:
+      break;
+    case 41:
+      break;
+    case 42:
+      __builtin_free (p); /* { dg-warning "double-'free' of 'p'" } */
+      break;
+    case 43:
+      break;
+    }
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |       __builtin_free (p);
+      |       ^~~~~~~~~~~~~~~~~~
+  'test': events 1-6
+   NN |   if (flag_a)
+      |      ^
+      |      |
+      |      (1) following 'true' branch (when 'flag_a != 0')...
+   NN |     __builtin_free (p);
+      |     ~~~~~~~~~~~~~~~~~~
+      |     |
+      |     (2) ...to here
+      |     (3) first 'free' here
+   NN |   switch (val)
+      |   ~~~~~~
+      |   |
+      |   (4) following 'case 42:' branch...
+......
+   NN |     case 42:
+      |     ~~~~
+      |     |
+      |     (5) ...to here
+   NN |       __builtin_free (p);
+      |       ~~~~~~~~~~~~~~~~~~
+      |       |
+      |       (6) second 'free' here; first 'free' was at (3)
+    { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/event-links-unicode.c b/gcc/testsuite/gcc.dg/analyzer/event-links-unicode.c
new file mode 100644 (file)
index 0000000..42f4b6f
--- /dev/null
@@ -0,0 +1,62 @@
+/* Verify that we print event links for the analyzer, using Unicode.
+   C only: we don't care about any C/C++ differences between source
+   locations here.  */
+
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events" } */
+/* { dg-additional-options "-fdiagnostics-show-line-numbers" } */
+/* { dg-additional-options "-fdiagnostics-show-caret" } */
+/* { dg-additional-options "-fdiagnostics-show-event-links" } */
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode" } */
+/* { dg-enable-nn-line-numbers "" } */
+
+void test (int flag_a, int val, void *p)
+{
+  if (flag_a)
+    __builtin_free (p);
+  switch (val)
+    {
+    default:
+      break;
+    case 41:
+      break;
+    case 42:
+      __builtin_free (p); /* { dg-warning "double-'free' of 'p'" } */
+      break;
+    case 43:
+      break;
+    }
+}
+
+/* { dg-begin-multiline-output "" }
+   NN |       __builtin_free (p);
+      |       ^~~~~~~~~~~~~~~~~~
+  'test': events 1-6
+   NN |   if (flag_a)
+      |      ^
+      |      |
+      |      (1) following 'true' branch (when 'flag_a != 0')... ─>─┐
+      |                                                             │
+      |                                                             │
+      |┌────────────────────────────────────────────────────────────┘
+   NN |│    __builtin_free (p);
+      |│    ~~~~~~~~~~~~~~~~~~
+      |│    |
+      |└───>(2) ...to here
+      |     (3) first 'free' here
+   NN |   switch (val)
+      |   ~~~~~~
+      |   |
+      |   (4) following 'case 42:' branch... ─>─┐
+      |                                         │
+......
+      |                                         │
+      |┌────────────────────────────────────────┘
+   NN |│    case 42:
+      |│    ~~~~
+      |│    |
+      |└───>(5) ...to here
+   NN |       __builtin_free (p);
+      |       ~~~~~~~~~~~~~~~~~~
+      |       |
+      |       (6) second 'free' here; first 'free' was at (3)
+    { dg-end-multiline-output "" } */
index cba4c585c469301c786de7c0ed3386fd810b5805..1ee86c61028935e9fa42f087f4d4a5d17171e2bd 100644 (file)
@@ -140,6 +140,21 @@ ascii_theme::get_cppchar (enum cell_kind kind) const
       return '-';
     case cell_kind::INTERPROCEDURAL_POP_FRAMES_RIGHT:
       return '+';
+
+    case cell_kind::CFG_RIGHT:
+      return '-';
+    case cell_kind::CFG_FROM_RIGHT_TO_DOWN:
+      return '+';
+    case cell_kind::CFG_DOWN:
+      return '|';
+    case cell_kind::CFG_FROM_DOWN_TO_LEFT:
+      return '+';
+    case cell_kind::CFG_LEFT:
+      return '-';
+    case cell_kind::CFG_FROM_LEFT_TO_DOWN:
+      return '+';
+    case cell_kind::CFG_FROM_DOWN_TO_RIGHT:
+      return '+';
     }
 }
 
@@ -210,5 +225,20 @@ unicode_theme::get_cppchar (enum cell_kind kind) const
       return 0x2500; /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */
     case cell_kind::INTERPROCEDURAL_POP_FRAMES_RIGHT:
       return 0x2518; /* "┘": U+2518: BOX DRAWINGS LIGHT UP AND LEFT.  */
+
+    case cell_kind::CFG_RIGHT:
+      return 0x2500; /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */
+    case cell_kind::CFG_FROM_RIGHT_TO_DOWN:
+      return 0x2510; /* "┐": U+2510: BOX DRAWINGS LIGHT DOWN AND LEFT */
+    case cell_kind::CFG_DOWN:
+      return 0x2502; /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */
+    case cell_kind::CFG_FROM_DOWN_TO_LEFT:
+      return 0x2518; /* "┘": U+2518: BOX DRAWINGS LIGHT UP AND LEFT.  */
+    case cell_kind::CFG_LEFT:
+      return 0x2500; /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */
+    case cell_kind::CFG_FROM_LEFT_TO_DOWN:
+      return 0x250c; /* "┌" U+250C: BOX DRAWINGS LIGHT DOWN AND RIGHT */
+    case cell_kind::CFG_FROM_DOWN_TO_RIGHT:
+      return 0x2514; /* "└": U+2514: BOX DRAWINGS LIGHT UP AND RIGHT  */
     }
 }
index dd50f5a5e416cba1a547a56dbdb4e3e6e97daca9..e41fcc8723010743a3515cd27fdf283dd4c576bf 100644 (file)
@@ -72,7 +72,16 @@ class theme
     INTERPROCEDURAL_DEPTH_MARKER,       /* e.g. "|".  */
     INTERPROCEDURAL_POP_FRAMES_LEFT,   /* e.g. "<".  */
     INTERPROCEDURAL_POP_FRAMES_MIDDLE, /* e.g. "-".  */
-    INTERPROCEDURAL_POP_FRAMES_RIGHT  /* e.g. "+".  */
+    INTERPROCEDURAL_POP_FRAMES_RIGHT,  /* e.g. "+".  */
+
+    /* CFG stuff.  */
+    CFG_RIGHT,              /* e.g. "-".  */
+    CFG_FROM_RIGHT_TO_DOWN, /* e.g. "+".  */
+    CFG_DOWN,               /* e.g. "|".  */
+    CFG_FROM_DOWN_TO_LEFT,  /* e.g. "+".  */
+    CFG_LEFT,               /* e.g. "-".  */
+    CFG_FROM_LEFT_TO_DOWN,  /* e.g. "+".  */
+    CFG_FROM_DOWN_TO_RIGHT  /* e.g. "+".  */
   };
 
   virtual ~theme () = default;
index bed1b0b780bd4cece0debd19e3be4f7729502cd9..f715f977b7277d5e9fd18d7192aa25d036f841e1 100644 (file)
@@ -1029,6 +1029,8 @@ general_init (const char *argv0, bool init_signals)
 
   global_dc->m_source_printing.enabled
     = global_options_init.x_flag_diagnostics_show_caret;
+  global_dc->m_source_printing.show_event_links_p
+    = global_options_init.x_flag_diagnostics_show_event_links;
   global_dc->m_source_printing.show_labels_p
     = global_options_init.x_flag_diagnostics_show_labels;
   global_dc->m_source_printing.show_line_numbers_p
index 9ae5191774ec57bfdd17eadb7a6a594cbee0fb52..743a8c2a1d29f778d3d2441a8349353c044df21f 100644 (file)
@@ -19,6 +19,9 @@ along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
 #include "config.h"
+#define INCLUDE_ALGORITHM
+#define INCLUDE_MEMORY
+#define INCLUDE_STRING
 #define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
@@ -34,6 +37,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "gcc-rich-location.h"
 #include "diagnostic-color.h"
 #include "diagnostic-event-id.h"
+#include "diagnostic-label-effects.h"
 #include "selftest.h"
 #include "selftest-diagnostic.h"
 #include "text-art/theme.h"
@@ -50,7 +54,7 @@ class path_label : public range_label
 {
  public:
   path_label (const diagnostic_path *path, unsigned start_idx)
-  : m_path (path), m_start_idx (start_idx)
+  : m_path (path), m_start_idx (start_idx), m_effects (*this)
   {}
 
   label_text get_text (unsigned range_idx) const final override
@@ -95,9 +99,53 @@ class path_label : public range_label
     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;
   unsigned m_start_idx;
+  path_label_effects m_effects;
 };
 
 /* Return true if E1 and E2 can be consolidated into the same run of events
@@ -150,6 +198,7 @@ public:
   per_thread_summary (label_text name, unsigned swimlane_idx)
   : m_name (std::move (name)),
     m_swimlane_idx (swimlane_idx),
+    m_last_event (nullptr),
     m_min_depth (INT_MAX),
     m_max_depth (INT_MIN)
   {}
@@ -170,6 +219,7 @@ public:
 private:
   friend struct path_summary;
   friend class thread_event_printer;
+  friend struct event_range;
 
   const label_text m_name;
 
@@ -179,6 +229,9 @@ private:
 
   // 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;
 };
@@ -188,9 +241,95 @@ private:
    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, unsigned start_idx,
               const diagnostic_event &initial_event,
-              const per_thread_summary &t)
+              per_thread_summary &t,
+              bool show_event_links)
   : m_path (path),
     m_initial_event (initial_event),
     m_fndecl (initial_event.get_fndecl ()),
@@ -199,8 +338,39 @@ struct event_range
     m_path_label (path, start_idx),
     m_richloc (initial_event.get_location (), &m_path_label),
     m_thread_id (initial_event.get_thread_id ()),
-    m_per_thread_summary (t)
-  {}
+    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 diagnostic_event &new_ev, unsigned idx,
                        bool check_rich_locations)
@@ -208,18 +378,48 @@ struct event_range
     if (!can_consolidate_events (m_initial_event, new_ev,
                                 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 (idx > 0)
+      prev_event = &m_path->get_event (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 (new_ev.get_location (),
                                             false, &m_path_label))
        return false;
+
     m_end_idx = 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 DC, typically as a single
      call to the printer's diagnostic_show_locus.  */
 
-  void print (diagnostic_context *dc, pretty_printer *pp)
+  void print (diagnostic_context *dc, pretty_printer *pp,
+             diagnostic_source_effect_info *effect_info)
   {
     location_t initial_loc = m_initial_event.get_location ();
 
@@ -256,7 +456,8 @@ struct event_range
       }
 
     /* Call diagnostic_show_locus to show the events using labels.  */
-    diagnostic_show_locus (dc, &m_richloc, DK_DIAGNOSTIC_PATH, pp);
+    diagnostic_show_locus (dc, &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))
@@ -275,7 +476,10 @@ struct event_range
   path_label m_path_label;
   gcc_rich_location m_richloc;
   diagnostic_thread_id_t m_thread_id;
-  const per_thread_summary &m_per_thread_summary;
+  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
@@ -284,7 +488,9 @@ struct event_range
 
 struct path_summary
 {
-  path_summary (const diagnostic_path &path, bool check_rich_locations);
+  path_summary (const diagnostic_path &path,
+               bool check_rich_locations,
+               bool show_event_links = true);
 
   unsigned get_num_ranges () const { return m_ranges.length (); }
   bool multithreaded_p () const { return m_per_thread_summary.length () > 1; }
@@ -342,7 +548,8 @@ per_thread_summary::interprocedural_p () const
 /* path_summary's ctor.  */
 
 path_summary::path_summary (const diagnostic_path &path,
-                           bool check_rich_locations)
+                           bool check_rich_locations,
+                           bool show_event_links)
 {
   const unsigned num_events = path.num_events ();
 
@@ -360,9 +567,11 @@ path_summary::path_summary (const diagnostic_path &path,
        if (cur_event_range->maybe_add_event (event, idx, check_rich_locations))
          continue;
 
-      cur_event_range = new event_range (&path, idx, event, pts);
+      cur_event_range = new event_range (&path, idx, event, pts,
+                                        show_event_links);
       m_ranges.safe_push (cur_event_range);
       pts.m_event_ranges.safe_push (cur_event_range);
+      pts.m_last_event = &event;
     }
 }
 
@@ -428,9 +637,11 @@ public:
       return nullptr;
   }
 
-  void print_swimlane_for_event_range (diagnostic_context *dc,
-                                      pretty_printer *pp,
-                                      event_range *range)
+  void
+  print_swimlane_for_event_range (diagnostic_context *dc,
+                                 pretty_printer *pp,
+                                 event_range *range,
+                                 diagnostic_source_effect_info *effect_info)
   {
     const char *const line_color = "path";
     const char *start_line_color
@@ -508,7 +719,7 @@ public:
        }
        pp_set_prefix (pp, prefix);
        pp_prefixing_rule (pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
-       range->print (dc, pp);
+       range->print (dc, pp, effect_info);
        pp_set_prefix (pp, saved_prefix);
 
        write_indent (pp, m_cur_indent + per_frame_indent);
@@ -518,7 +729,7 @@ public:
        pp_newline (pp);
       }
     else
-      range->print (dc, pp);
+      range->print (dc, pp, effect_info);
 
     if (const event_range *next_range = get_any_next_range ())
       {
@@ -648,6 +859,7 @@ print_path_summary_as_text (const path_summary *ps, diagnostic_context *dc,
 
   unsigned i;
   event_range *range;
+  int last_out_edge_column = -1;
   FOR_EACH_VEC_ELT (ps->m_ranges, i, range)
     {
       const int swimlane_idx
@@ -662,7 +874,12 @@ print_path_summary_as_text (const path_summary *ps, diagnostic_context *dc,
            pp_newline (pp);
          }
       thread_event_printer &tep = thread_event_printers[swimlane_idx];
-      tep.print_swimlane_for_event_range (dc, pp, range);
+      /* 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 (dc, pp, range, &effect_info);
+      last_out_edge_column = effect_info.m_trailing_out_edge_column;
     }
 }
 
@@ -721,7 +938,8 @@ default_tree_diagnostic_path_printer (diagnostic_context *context,
     case DPF_INLINE_EVENTS:
       {
        /* Consolidate related events.  */
-       path_summary summary (*path, true);
+       path_summary summary (*path, true,
+                             context->m_source_printing.show_event_links_p);
        char *saved_prefix = pp_take_prefix (context->printer);
        pp_set_prefix (context->printer, NULL);
        print_path_summary_as_text (&summary, context,
@@ -776,6 +994,27 @@ default_tree_make_json_for_path (diagnostic_context *context,
 
 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;
+}
+
 /* A subclass of simple_diagnostic_path that adds member functions
    for adding test events.  */
 
@@ -1172,20 +1411,909 @@ test_recursion (pretty_printer *event_pp)
   }
 }
 
+/* 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, NULL_TREE, 0,
+                 "following %qs branch (when %qs is NULL)...",
+                 "false", "p");
+  path.connect_to_next_event ();
+
+  path.add_event (cfg_dest, NULL_TREE, 0,
+                 "...to here");
+  path.add_event (cfg_dest, NULL_TREE, 0,
+                 "dereference of NULL %qs",
+                 "p");
+
+  if (!path_events_have_column_data_p (path))
+    return;
+
+  path_summary summary (path, true /*false*/);
+
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
+    dc.m_source_printing.show_event_links_p = true;
+    print_path_summary_as_text (&summary, &dc, 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 (dc.printer));
+  }
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
+    dc.m_source_printing.show_event_links_p = false;
+    print_path_summary_as_text (&summary, &dc, 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 (dc.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;
+    print_path_summary_as_text (&summary, &dc, 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 (dc.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;
+    print_path_summary_as_text (&summary, &dc, 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 (dc.printer));
+  }
+  {
+    test_diagnostic_context dc;
+    dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
+    dc.m_source_printing.show_event_links_p = true;
+    print_path_summary_as_text (&summary, &dc, 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 (dc.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;
+    print_path_summary_as_text (&summary, &dc, 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 (dc.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, NULL_TREE, 0, "infinite loop here");
+
+  path.add_event (iter_test, NULL_TREE, 0, "looping from here...");
+  path.connect_to_next_event ();
+
+  path.add_event (loop_body_start, NULL_TREE, 0, "...to here");
+
+  path.add_event (loop_body_end, NULL_TREE, 0, "looping back...");
+  path.connect_to_next_event ();
+
+  path.add_event (iter_test, NULL_TREE, 0, "...to here");
+
+  if (!path_events_have_column_data_p (path))
+    return;
+
+  path_summary summary (path, true);
+
+  {
+    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;
+    print_path_summary_as_text (&summary, &dc, 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 (dc.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, NULL_TREE, 0, "infinite loop here");
+
+  path.add_event (iter_test, NULL_TREE, 0, "looping from here...");
+  path.connect_to_next_event ();
+
+  path.add_event (iter_next, NULL_TREE, 0, "...to here");
+
+  path.add_event (iter_next, NULL_TREE, 0, "looping back...");
+  path.connect_to_next_event ();
+
+  path.add_event (iter_test, NULL_TREE, 0, "...to here");
+
+  if (!path_events_have_column_data_p (path))
+    return;
+
+  path_summary summary (path, true);
+
+  {
+    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;
+    print_path_summary_as_text (&summary, &dc, 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 (dc.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, NULL_TREE, 0, "from here...");
+  path.connect_to_next_event ();
+
+  path.add_event (dst_loc, NULL_TREE, 0, "...to here");
+
+  if (!path_events_have_column_data_p (path))
+    return;
+
+  path_summary summary (path, true);
+
+  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;
+  print_path_summary_as_text (&summary, &dc, false);
+  ASSERT_STREQ_AT (loc, expected_str,
+                  pp_formatted_text (dc.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), NULL_TREE, 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), NULL_TREE, 0,
+                 "...to here");
+
+  /* (3) */
+  path.add_event (t.get_line_and_columns (4, 15, 17, 19), NULL_TREE, 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), NULL_TREE, 0,
+                 "...to here");
+
+  /* (5) */
+  path.add_event (t.get_line_and_columns (5, 33, 58), NULL_TREE, 0,
+                 "allocated here");
+
+  if (!path_events_have_column_data_p (path))
+    return;
+
+  path_summary summary (path, true);
+
+  {
+    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;
+    print_path_summary_as_text (&summary, &dc, 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 (dc.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), NULL_TREE, 0,
+                 "allocated here");
+
+  /* (2) */
+  path.add_event (t.get_line_and_columns (8, 13, 14, 17), NULL_TREE, 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), NULL_TREE, 0,
+                 "...to here");
+
+  /* (4) */
+  path.add_event (t.get_line_and_columns (8, 13, 14, 17), NULL_TREE, 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), NULL_TREE, 0,
+                 "...to here");
+
+  if (!path_events_have_column_data_p (path))
+    return;
+
+  path_summary summary (path, true);
+
+  {
+    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;
+    print_path_summary_as_text (&summary, &dc, 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 (dc.printer));
+  }
+}
+
+static void
+control_flow_tests (const line_table_case &case_)
+{
+  std::unique_ptr<pretty_printer> event_pp
+    = std::unique_ptr<pretty_printer> (global_dc->printer->clone ());
+  pp_show_color (event_pp) = 0;
+
+  test_control_flow_1 (case_, event_pp.get ());
+  test_control_flow_2 (case_, event_pp.get ());
+  test_control_flow_3 (case_, event_pp.get ());
+  test_control_flow_4 (case_, event_pp.get ());
+  test_control_flow_5 (case_, event_pp.get ());
+  test_control_flow_6 (case_, event_pp.get ());
+}
+
 /* Run all of the selftests within this file.  */
 
 void
 tree_diagnostic_path_cc_tests ()
 {
+  /* In a few places we use the global dc's printer to determine
+     colorization so ensure this off during the tests.  */
+  bool saved_show_color = pp_show_color (global_dc->printer);
+  pp_show_color (global_dc->printer) = false;
+
   auto_fix_quotes fix_quotes;
-  pretty_printer *event_pp = global_dc->printer->clone ();
-  pp_show_color (event_pp) = 0;
-  test_empty_path (event_pp);
-  test_intraprocedural_path (event_pp);
-  test_interprocedural_path_1 (event_pp);
-  test_interprocedural_path_2 (event_pp);
-  test_recursion (event_pp);
-  delete event_pp;
+  std::unique_ptr<pretty_printer> event_pp
+    = std::unique_ptr<pretty_printer> (global_dc->printer->clone ());
+  test_empty_path (event_pp.get ());
+  test_intraprocedural_path (event_pp.get ());
+  test_interprocedural_path_1 (event_pp.get ());
+  test_interprocedural_path_2 (event_pp.get ());
+  test_recursion (event_pp.get ());
+  for_each_line_table_case (control_flow_tests);
+
+  pp_show_color (global_dc->printer) = saved_show_color;
 }
 
 } // namespace selftest
index 94c6e440053243f677464226fa5eb655b0bb8db1..a2ece8b033c0decb6a9e261db40f30b729ee84f5 100644 (file)
@@ -23,6 +23,7 @@ along with this program; see the file COPYING3.  If not see
 #define LIBCPP_RICH_LOCATION_H
 
 class range_label;
+class label_effects;
 
 /* A hint to diagnostic_show_locus on how to print a source range within a
    rich_location.
@@ -641,6 +642,12 @@ class range_label
      The RANGE_IDX is provided, allowing for range_label instances to be
      shared by multiple ranges if need be (the "flyweight" design pattern).  */
   virtual label_text get_text (unsigned range_idx) const = 0;
+
+  /* Get any special effects for the label (e.g. links to other labels).  */
+  virtual const label_effects *get_effects (unsigned /*range_idx*/) const
+  {
+    return nullptr;
+  }
 };
 
 /* A fix-it hint: a suggested insertion, replacement, or deletion of text.