@@ -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;
@@ -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,
@@ -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.
new file mode 100644
@@ -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 */
@@ -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;
@@ -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));
}
@@ -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)
{
}
@@ -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
@@ -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
@@ -5184,7 +5185,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.
@@ -5419,6 +5421,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
@@ -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:
@@ -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;
@@ -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_:
new file mode 100644
@@ -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 "" } */
new file mode 100644
@@ -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 | [01;35m[K__builtin_free (p)[m[K;
+ | [01;35m[K^~~~~~~~~~~~~~~~~~[m[K
+ '[01m[Ktest[m[K': events 1-6
+ NN | if [01;36m[K([m[Kflag_a)
+ | [01;36m[K^[m[K
+ | [01;36m[K|[m[K
+ | [01;36m[K(1)[m[K following '[01m[Ktrue[m[K' branch (when '[01m[Kflag_a != 0[m[K')...[01;36m[K ->-+[m[K
+ | [01;36m[K|[m[K
+ | [01;36m[K|[m[K
+ |[01;36m[K+[m[K[01;36m[K------------------------------------------------------------+[m[K
+ NN |[01;36m[K|[m[K [01;36m[K__builtin_free (p)[m[K;
+ |[01;36m[K|[m[K [01;36m[K~~~~~~~~~~~~~~~~~~[m[K
+ |[01;36m[K|[m[K [01;36m[K|[m[K
+ |[01;36m[K+[m[K[01;36m[K--->[m[K[01;36m[K(2)[m[K ...to here
+ | [01;36m[K(3)[m[K first '[01m[Kfree[m[K' here
+ NN | [01;36m[Kswitch[m[K (val)
+ | [01;36m[K~~~~~~[m[K
+ | [01;36m[K|[m[K
+ | [01;36m[K(4)[m[K following '[01m[Kcase 42:[m[K' branch...[01;36m[K ->-+[m[K
+ | [01;36m[K|[m[K
+......
+ | [01;36m[K|[m[K
+ |[01;36m[K+[m[K[01;36m[K----------------------------------------+[m[K
+ NN |[01;36m[K|[m[K [01;36m[Kcase[m[K 42:
+ |[01;36m[K|[m[K [01;36m[K~~~~[m[K
+ |[01;36m[K|[m[K [01;36m[K|[m[K
+ |[01;36m[K+[m[K[01;36m[K--->[m[K[01;36m[K(5)[m[K ...to here
+ NN | [01;36m[K__builtin_free (p)[m[K;
+ | [01;36m[K~~~~~~~~~~~~~~~~~~[m[K
+ | [01;36m[K|[m[K
+ | [01;36m[K(6)[m[K second '[01m[Kfree[m[K' here; first '[01m[Kfree[m[K' was at [01;36m[K(3)[m[K
+ { dg-end-multiline-output "" } */
+
+/* DejaGnu won't recognize the warning due to the colorization codes,
+ so skip it. */
+/* { dg-prune-output ".*" } */
new file mode 100644
@@ -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 "" } */
new file mode 100644
@@ -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 "" } */
@@ -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 */
}
}
@@ -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;
@@ -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
@@ -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
@@ -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.
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). Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu. Successful run of analyzer integration tests on x86_64-pc-linux-gnu. Pushed to trunk as r15-636-g770657d02c986c. 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> --- gcc/analyzer/checker-event.h | 3 + gcc/analyzer/infinite-loop.cc | 18 +- gcc/common.opt | 4 + gcc/diagnostic-label-effects.h | 58 + gcc/diagnostic-path.h | 16 + gcc/diagnostic-show-locus.cc | 387 +++++- gcc/diagnostic.cc | 12 + gcc/diagnostic.h | 17 +- gcc/doc/invoke.texi | 29 +- gcc/lto-wrapper.cc | 3 + gcc/opts-common.cc | 3 +- gcc/opts.cc | 5 + .../gcc.dg/analyzer/event-links-ascii.c | 61 + .../gcc.dg/analyzer/event-links-color.c | 66 + .../gcc.dg/analyzer/event-links-disabled.c | 55 + .../gcc.dg/analyzer/event-links-unicode.c | 62 + gcc/text-art/theme.cc | 30 + gcc/text-art/theme.h | 11 +- gcc/toplev.cc | 2 + gcc/tree-diagnostic-path.cc | 1178 ++++++++++++++++- libcpp/include/rich-location.h | 7 + 21 files changed, 1959 insertions(+), 68 deletions(-) create mode 100644 gcc/diagnostic-label-effects.h create mode 100644 gcc/testsuite/gcc.dg/analyzer/event-links-ascii.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/event-links-color.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/event-links-disabled.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/event-links-unicode.c