From 770657d02c986c8724214dba51f21bb3c299ebae Mon Sep 17 00:00:00 2001 From: David Malcolm <dmalcolm@redhat.com> Date: Fri, 17 May 2024 14:51:47 -0400 Subject: [PATCH] diagnostics, analyzer: add CFG edge visualization to path-printing This patch adds some ability for links between labelled ranges when quoting the user's source code, and uses this to add links between events when printing diagnostic_paths, chopping them up further into event ranges that can be printed together. It adds links to the various "from..." - "...to" events in the analyzer. For example, previously we emitted this for c-c++-common/analyzer/infinite-loop-linked-list.c's while_loop_missing_next': infinite-loop-linked-list.c:30:10: warning: infinite loop [CWE-835] [-Wanalyzer-infinite-loop] 30 | while (n) | ^ 'while_loop_missing_next': events 1-5 30 | while (n) | ^ | | | (1) infinite loop here | (2) when 'n' is non-NULL: always following 'true' branch... | (5) ...to here 31 | { 32 | sum += n->val; | ~~~~~~~~~~~~~ | | | | | (3) ...to here | (4) looping back... whereas with the patch we now emit: infinite-loop-linked-list.c:30:10: warning: infinite loop [CWE-835] [-Wanalyzer-infinite-loop] 30 | while (n) | ^ 'while_loop_missing_next': events 1-3 30 | while (n) | ^ | | | (1) infinite loop here | (2) when 'n' is non-NULL: always following 'true' branch... ->-+ | | | | |+------------------------------------------------------------------------+ 31 || { 32 || sum += n->val; || ~~~~~~ || | |+------------->(3) ...to here 'while_loop_missing_next': event 4 32 | sum += n->val; | ~~~~^~~~~~~~~ | | | (4) looping back... ->-+ | | 'while_loop_missing_next': event 5 | | |+---------------------------------+ 30 || while (n) || ^ || | |+-------->(5) ...to here which I believe is easier to understand. The patch also implements the use of unicode characters and colorization for the lines (not shown in the above example). There is a new option -fno-diagnostics-show-event-links for getting back the old behavior (added to -fdiagnostics-plain-output). gcc/analyzer/ChangeLog: * checker-event.h (checker_event::connect_to_next_event_p): Implement new diagnostic_event::connect_to_next_event_p vfunc. (start_cfg_edge_event::connect_to_next_event_p): Likewise. (start_consolidated_cfg_edges_event::connect_to_next_event_p): Likewise. * infinite-loop.cc (class looping_back_event): New subclass. (infinite_loop_diagnostic::add_final_event): Use it. gcc/ChangeLog: * common.opt (fdiagnostics-show-event-links): New option. * diagnostic-label-effects.h: New file. * diagnostic-path.h (diagnostic_event::connect_to_next_event_p): New pure virtual function. (simple_diagnostic_event::connect_to_next_event_p): Implement it. (simple_diagnostic_event::connect_to_next_event): New. (simple_diagnostic_event::m_connected_to_next_event): New field. (simple_diagnostic_path::connect_to_next_event): New decl. * diagnostic-show-locus.cc: Include "text-art/theme.h" and "diagnostic-label-effects.h". (colorizer::set_cfg_edge): New. (layout::m_fallback_theme): New field. (layout::m_theme): New field. (layout::m_effect_info): New field. (layout::m_link_lhs_state): New enum and field. (layout::m_link_rhs_column): New field. (layout_range::has_in_edge): New. (layout_range::has_out_edge): New. (layout::layout): Add "effect_info" optional param. Initialize m_theme, m_link_lhs_state, and m_link_rhs_column. (layout::maybe_add_location_range): Remove stray "FIXME" from leading comment. (layout::print_source_line): Replace space after margin with a call to print_leftmost_column. (layout::print_leftmost_column): New. (layout::start_annotation_line): Make non-const. Gain responsibility for printing the leftmost column after the margin. (layout::print_annotation_line): Drop pp_space, as this is now added by start_annotation_line. (line_label::line_label): Add "has_in_edge" and "has_out_edge" params and initialize... (line_label::m_has_in_edge): New field. (line_label::m_has_out_edge): New field. (layout::print_any_labels): Pass edge information to line_label ctor. Keep track of in-edges and out-edges, adding visualizations of these links between labels. (layout::print_leading_fixits): Drop pp_character, as this is now added by start_annotation_line. (layout::print_trailing_fixits): Fix off-by-one errors in column calculation. (layout::move_to_column): Add comment about debugging. (layout::show_ruler): Make non-const. Drop pp_space calls, as this is now added by start_annotation_line. (layout::print_line): Call print_any_right_to_left_edge_lines. (layout::print_any_right_to_left_edge_lines): New. (layout::update_any_effects): New. (gcc_rich_location::add_location_if_nearby): Initialize loc_range.m_label. (diagnostic_context::maybe_show_locus): Add "effects" param and pass it to diagnostic_context::show_locus. (diagnostic_context::show_locus): Add "effects" param, passing it to layout's ctor. Call update_any_effects on the layout after printing the lines. (selftest::test_layout_x_offset_display_utf8): Update expected result for eliminated trailing newline. (selftest::test_layout_x_offset_display_utf8): Likewise. (selftest::test_layout_x_offset_display_tab): Likewise. * diagnostic.cc (diagnostic_context::initialize): Initialize m_source_printing.show_event_links_p. (simple_diagnostic_path::connect_to_next_event): New. (simple_diagnostic_event::simple_diagnostic_event): Initialize m_connected_to_next_event. * diagnostic.h (class diagnostic_source_effect_info): New forward decl. (diagnostic_source_printing_options::show_event_links_p): New field. (diagnostic_context::maybe_show_locus): Add optional "effect_info" param. (diagnostic_context::show_locus): Add "effect_info" param. (diagnostic_show_locus): Add optional "effect_info" param. * doc/invoke.texi: Add -fno-diagnostics-show-event-links. * lto-wrapper.cc (merge_and_complain): Add OPT_fdiagnostics_show_event_links to switch. (append_compiler_options): Likewise. (append_diag_options): Likewise. * opts-common.cc (decode_cmdline_options_to_array): Add "-fno-diagnostics-show-event-links" to -fdiagnostics-plain-output. * opts.cc (common_handle_option): Add case for OPT_fdiagnostics_show_event_links. * text-art/theme.cc (ascii_theme::get_cppchar): Handle cell_kind::CFG_*. (unicode_theme::get_cppchar): Likewise. * text-art/theme.h (theme::cell_kind): Add CFG_*. * toplev.cc (general_init): Initialize global_dc->m_source_printing.show_event_links_p. * tree-diagnostic-path.cc: Define INCLUDE_ALGORITHM, INCLUDE_MEMORY, and INCLUDE_STRING. Include "diagnostic-label-effects.h". (path_label::path_label): Initialize m_effects. (path_label::get_effects): New. (class path_label::path_label_effects): New. (path_label::m_effects): New field. (class per_thread_summary): Add "friend struct event_range;". (per_thread_summary::per_thread_summary): Initialize m_last_event. (per_thread_summary::m_last_event): New field. (struct event_range::per_source_line_info): New. (event_range::event_range): Make "t" non-const. Add "show_event_links" param and use it to initialize m_show_event_links. Add info for initial event. (event_range::get_per_source_line_info): New. (event_range::maybe_add_event): Verify compatibility of the new label and existing labels with respect to the link-printing code. Update per-source-line info when an event is added. (event_range::print): Add"effect_info" param and pass to diagnostic_show_locus. (event_range::m_per_thread_summary): Make non-const. (event_range::m_source_line_info_map): New field. (event_range::m_show_event_links): New field. (path_summary::path_summary): Add "show_event_links" optional param, passing it to event_range ctor calls. Update pts.m_last_event. (thread_event_printer::print_swimlane_for_event_range): Add "effect_info" param and pass it to range->print. (print_path_summary_as_text): Keep track of the column for any out-edges at the end of printing each event_range and use as the leading in-edge for the next event_range. (default_tree_diagnostic_path_printer): Pass in show_event_links_p to path_summary ctor. (selftest::path_events_have_column_data_p): New. (class selftest::control_flow_test): New. (selftest::test_control_flow_1): New. (selftest::test_control_flow_2): New. (selftest::test_control_flow_3): New. (selftest::assert_cfg_edge_path_streq): New. (ASSERT_CFG_EDGE_PATH_STREQ): New macro. (selftest::test_control_flow_4): New. (selftest::test_control_flow_5): New. (selftest::test_control_flow_6): New. (selftest::control_flow_tests): New. (selftest::tree_diagnostic_path_cc_tests): Disable colorization on global_dc's printer. Convert event_pp to a std::unique_ptr. Call control_flow_tests via for_each_line_table_case. (gen_command_line_string): Likewise. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/event-links-ascii.c: New test. * gcc.dg/analyzer/event-links-color.c: New test. * gcc.dg/analyzer/event-links-disabled.c: New test. * gcc.dg/analyzer/event-links-unicode.c: New test. libcpp/ChangeLog: * include/rich-location.h (class label_effects): New forward decl. (range_label::get_effects): New vfunc. Signed-off-by: David Malcolm <dmalcolm@redhat.com> --- 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 diff --git a/gcc/analyzer/checker-event.h b/gcc/analyzer/checker-event.h index d2fb87fb523f..7a4510ee81d0 100644 --- a/gcc/analyzer/checker-event.h +++ b/gcc/analyzer/checker-event.h @@ -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; diff --git a/gcc/analyzer/infinite-loop.cc b/gcc/analyzer/infinite-loop.cc index e277a8384a04..04346cdfdc3c 100644 --- a/gcc/analyzer/infinite-loop.cc +++ b/gcc/analyzer/infinite-loop.cc @@ -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, diff --git a/gcc/common.opt b/gcc/common.opt index 40d90817b868..2c078fdd1f86 100644 --- a/gcc/common.opt +++ b/gcc/common.opt @@ -1368,6 +1368,10 @@ fdiagnostics-show-caret Common Var(flag_diagnostics_show_caret) Init(1) Show the source line with a caret indicating the column. +fdiagnostics-show-event-links +Common Var(flag_diagnostics_show_event_links) Init(1) +Show lines linking related events in diagnostic paths. + fdiagnostics-show-labels Common Var(flag_diagnostics_show_labels) Init(1) Show labels annotating ranges of source code when showing source. diff --git a/gcc/diagnostic-label-effects.h b/gcc/diagnostic-label-effects.h new file mode 100644 index 000000000000..e640f3d1a1fe --- /dev/null +++ b/gcc/diagnostic-label-effects.h @@ -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 */ diff --git a/gcc/diagnostic-path.h b/gcc/diagnostic-path.h index 696991c6d736..982d68b872ea 100644 --- a/gcc/diagnostic-path.h +++ b/gcc/diagnostic-path.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; diff --git a/gcc/diagnostic-show-locus.cc b/gcc/diagnostic-show-locus.cc index f42006cfe2a1..007acc4e0147 100644 --- a/gcc/diagnostic-show-locus.cc +++ b/gcc/diagnostic-show-locus.cc @@ -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)); } diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc index 6ffd62361462..1f30d1d7cdac 100644 --- a/gcc/diagnostic.cc +++ b/gcc/diagnostic.cc @@ -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) { } diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h index 065ac784e258..7431f5a6e12c 100644 --- a/gcc/diagnostic.h +++ b/gcc/diagnostic.h @@ -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 diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index bcf518ac279a..b9408ecc9188 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -307,6 +307,7 @@ Objective-C and Objective-C++ Dialects}. -fdiagnostics-format=@r{[}text@r{|}sarif-stderr@r{|}sarif-file@r{|}json@r{|}json-stderr@r{|}json-file@r{]} -fno-diagnostics-json-formatting -fno-diagnostics-show-option -fno-diagnostics-show-caret +-fno-diagnostics-show-event-links -fno-diagnostics-show-labels -fno-diagnostics-show-line-numbers -fno-diagnostics-show-cwe -fno-diagnostics-show-rule @@ -5211,7 +5212,8 @@ options: -fdiagnostics-color=never -fdiagnostics-urls=never -fdiagnostics-path-format=separate-events --fdiagnostics-text-art-charset=none} +-fdiagnostics-text-art-charset=none +-fno-diagnostics-show-event-links} In the future, if GCC changes the default appearance of its diagnostics, the corresponding option to disable the new behavior will be added to this list. @@ -5446,6 +5448,31 @@ as the types of expressions: This option suppresses the printing of these labels (in the example above, the vertical bars and the ``char *'' and ``long int'' text). +@opindex fno-diagnostics-show-event-links +@opindex fdiagnostics-show-event-links +@item -fno-diagnostics-show-event-links +By default, when printing execution paths (via +@option{-fdiagnostics-path-format=inline-events}), GCC will print lines +connecting related events, such as the line connecting events 1 and 2 in: + +@smallexample + 3 | if (p) + | ^ + | | + | (1) following `false' branch (when `p' is NULL)... ->-+ + | | + | | + |+------------------------------------------------------------+ + 4 || return 0; + 5 || return *p; + || ~ + || | + |+-------->(2) ...to here + | (3) dereference of NULL `p' +@end smallexample + +This option suppresses the printing of such connector lines. + @opindex fno-diagnostics-show-cwe @opindex fdiagnostics-show-cwe @item -fno-diagnostics-show-cwe diff --git a/gcc/lto-wrapper.cc b/gcc/lto-wrapper.cc index cfded757f268..6dcf8b469a3c 100644 --- a/gcc/lto-wrapper.cc +++ b/gcc/lto-wrapper.cc @@ -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: diff --git a/gcc/opts-common.cc b/gcc/opts-common.cc index 2d1e86ff94fa..14084d08b05a 100644 --- a/gcc/opts-common.cc +++ b/gcc/opts-common.cc @@ -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; diff --git a/gcc/opts.cc b/gcc/opts.cc index 14d1767e48f5..f80d5d4ba8f9 100644 --- a/gcc/opts.cc +++ b/gcc/opts.cc @@ -2937,6 +2937,10 @@ common_handle_option (struct gcc_options *opts, dc->m_source_printing.enabled = value; break; + case OPT_fdiagnostics_show_event_links: + dc->m_source_printing.show_event_links_p = value; + break; + case OPT_fdiagnostics_show_labels: dc->m_source_printing.show_labels_p = value; break; @@ -3818,6 +3822,7 @@ gen_command_line_string (cl_decoded_option *options, case OPT_fdiagnostics_show_location_: case OPT_fdiagnostics_show_option: case OPT_fdiagnostics_show_caret: + case OPT_fdiagnostics_show_event_links: case OPT_fdiagnostics_show_labels: case OPT_fdiagnostics_show_line_numbers: case OPT_fdiagnostics_color_: diff --git a/gcc/testsuite/gcc.dg/analyzer/event-links-ascii.c b/gcc/testsuite/gcc.dg/analyzer/event-links-ascii.c new file mode 100644 index 000000000000..fd68944b339f --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/event-links-ascii.c @@ -0,0 +1,61 @@ +/* Verify that we print event links for the analyzer, using ASCII. + C only: we don't care about any C/C++ differences between source + locations here. */ + +/* { dg-additional-options "-fdiagnostics-path-format=inline-events" } */ +/* { dg-additional-options "-fdiagnostics-show-line-numbers" } */ +/* { dg-additional-options "-fdiagnostics-show-caret" } */ +/* { dg-additional-options "-fdiagnostics-show-event-links" } */ +/* { dg-enable-nn-line-numbers "" } */ + +void test (int flag_a, int val, void *p) +{ + if (flag_a) + __builtin_free (p); + switch (val) + { + default: + break; + case 41: + break; + case 42: + __builtin_free (p); /* { dg-warning "double-'free' of 'p'" } */ + break; + case 43: + break; + } +} + +/* { dg-begin-multiline-output "" } + NN | __builtin_free (p); + | ^~~~~~~~~~~~~~~~~~ + 'test': events 1-6 + NN | if (flag_a) + | ^ + | | + | (1) following 'true' branch (when 'flag_a != 0')... ->-+ + | | + | | + |+------------------------------------------------------------+ + NN || __builtin_free (p); + || ~~~~~~~~~~~~~~~~~~ + || | + |+--->(2) ...to here + | (3) first 'free' here + NN | switch (val) + | ~~~~~~ + | | + | (4) following 'case 42:' branch... ->-+ + | | +...... + | | + |+----------------------------------------+ + NN || case 42: + || ~~~~ + || | + |+--->(5) ...to here + NN | __builtin_free (p); + | ~~~~~~~~~~~~~~~~~~ + | | + | (6) second 'free' here; first 'free' was at (3) + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/event-links-color.c b/gcc/testsuite/gcc.dg/analyzer/event-links-color.c new file mode 100644 index 000000000000..3e5ef569fe55 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/event-links-color.c @@ -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 ".*" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/event-links-disabled.c b/gcc/testsuite/gcc.dg/analyzer/event-links-disabled.c new file mode 100644 index 000000000000..e9cbbfb7c783 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/event-links-disabled.c @@ -0,0 +1,55 @@ +/* Verify that -fno-diagnostics-show-event-links works. + C only: we don't care about any C/C++ differences between source + locations here. */ + +/* { dg-additional-options "-fdiagnostics-path-format=inline-events" } */ +/* { dg-additional-options "-fdiagnostics-show-line-numbers" } */ +/* { dg-additional-options "-fdiagnostics-show-caret" } */ +/* { dg-additional-options "-fno-diagnostics-show-event-links" } */ +/* { dg-enable-nn-line-numbers "" } */ + +void test (int flag_a, int val, void *p) +{ + if (flag_a) + __builtin_free (p); + switch (val) + { + default: + break; + case 41: + break; + case 42: + __builtin_free (p); /* { dg-warning "double-'free' of 'p'" } */ + break; + case 43: + break; + } +} + +/* { dg-begin-multiline-output "" } + NN | __builtin_free (p); + | ^~~~~~~~~~~~~~~~~~ + 'test': events 1-6 + NN | if (flag_a) + | ^ + | | + | (1) following 'true' branch (when 'flag_a != 0')... + NN | __builtin_free (p); + | ~~~~~~~~~~~~~~~~~~ + | | + | (2) ...to here + | (3) first 'free' here + NN | switch (val) + | ~~~~~~ + | | + | (4) following 'case 42:' branch... +...... + NN | case 42: + | ~~~~ + | | + | (5) ...to here + NN | __builtin_free (p); + | ~~~~~~~~~~~~~~~~~~ + | | + | (6) second 'free' here; first 'free' was at (3) + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/event-links-unicode.c b/gcc/testsuite/gcc.dg/analyzer/event-links-unicode.c new file mode 100644 index 000000000000..42f4b6f1ce0a --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/event-links-unicode.c @@ -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 "" } */ diff --git a/gcc/text-art/theme.cc b/gcc/text-art/theme.cc index cba4c585c469..1ee86c610289 100644 --- a/gcc/text-art/theme.cc +++ b/gcc/text-art/theme.cc @@ -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 */ } } diff --git a/gcc/text-art/theme.h b/gcc/text-art/theme.h index dd50f5a5e416..e41fcc872301 100644 --- a/gcc/text-art/theme.h +++ b/gcc/text-art/theme.h @@ -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; diff --git a/gcc/toplev.cc b/gcc/toplev.cc index bed1b0b780bd..f715f977b727 100644 --- a/gcc/toplev.cc +++ b/gcc/toplev.cc @@ -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 diff --git a/gcc/tree-diagnostic-path.cc b/gcc/tree-diagnostic-path.cc index 9ae5191774ec..743a8c2a1d29 100644 --- a/gcc/tree-diagnostic-path.cc +++ b/gcc/tree-diagnostic-path.cc @@ -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 diff --git a/libcpp/include/rich-location.h b/libcpp/include/rich-location.h index 94c6e4400532..a2ece8b033c0 100644 --- a/libcpp/include/rich-location.h +++ b/libcpp/include/rich-location.h @@ -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. -- GitLab