diff options
Diffstat (limited to 'gcc/diagnostic-path-output.cc')
-rw-r--r-- | gcc/diagnostic-path-output.cc | 2713 |
1 files changed, 2713 insertions, 0 deletions
diff --git a/gcc/diagnostic-path-output.cc b/gcc/diagnostic-path-output.cc new file mode 100644 index 0000000..4c17865 --- /dev/null +++ b/gcc/diagnostic-path-output.cc @@ -0,0 +1,2713 @@ +/* Printing paths through the code associated with a diagnostic. + Copyright (C) 2019-2025 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalcolm@redhat.com> + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#define INCLUDE_ALGORITHM +#define INCLUDE_MAP +#define INCLUDE_STRING +#define INCLUDE_VECTOR +#include "system.h" +#include "coretypes.h" +#include "diagnostic.h" +#include "diagnostic-macro-unwinding.h" +#include "intl.h" +#include "diagnostic-path.h" +#include "gcc-rich-location.h" +#include "diagnostic-color.h" +#include "diagnostic-event-id.h" +#include "diagnostic-label-effects.h" +#include "pretty-print-markup.h" +#include "selftest.h" +#include "selftest-diagnostic.h" +#include "selftest-diagnostic-path.h" +#include "text-art/theme.h" +#include "diagnostic-format-text.h" +#include "diagnostic-format-html.h" +#include "xml.h" +#include "xml-printer.h" + +/* Disable warnings about missing quoting in GCC diagnostics for the print + calls below. */ +#if __GNUC__ >= 10 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wformat-diag" +#endif + +/* Anonymous namespace for path-printing code. */ + +namespace { + +/* A bundle of state for printing a path. */ + +class path_print_policy +{ +public: + path_print_policy (const diagnostic_text_output_format &text_output) + : m_source_policy (text_output.get_context ()) + { + } + + path_print_policy (const diagnostic_context &dc) + : m_source_policy (dc) + { + } + + text_art::theme * + get_diagram_theme () const + { + return m_source_policy.get_diagram_theme (); + } + + const diagnostic_source_print_policy & + get_source_policy () const { return m_source_policy; } + +private: + diagnostic_source_print_policy m_source_policy; +}; + +/* Subclass of range_label for showing a particular event + when showing a consecutive run of events within a diagnostic_path as + labelled ranges within one gcc_rich_location. */ + +class path_label : public range_label +{ + public: + path_label (const diagnostic_path &path, + const pretty_printer &ref_pp, + unsigned start_idx, + bool colorize, + bool allow_emojis) + : m_path (path), + m_ref_pp (ref_pp), + m_start_idx (start_idx), m_effects (*this), + m_colorize (colorize), m_allow_emojis (allow_emojis) + {} + + label_text get_text (unsigned range_idx) const final override + { + unsigned event_idx = m_start_idx + range_idx; + const diagnostic_event &event = m_path.get_event (event_idx); + + const diagnostic_event::meaning meaning (event.get_meaning ()); + + auto pp = m_ref_pp.clone (); + pp_show_color (pp.get ()) = m_colorize; + diagnostic_event_id_t event_id (event_idx); + + pp_printf (pp.get (), "%@", &event_id); + pp_space (pp.get ()); + + if (meaning.m_verb == diagnostic_event::VERB_danger + && m_allow_emojis) + { + pp_unicode_character (pp.get (), 0x26A0); /* U+26A0 WARNING SIGN. */ + /* Append U+FE0F VARIATION SELECTOR-16 to select the emoji + variation of the char. */ + pp_unicode_character (pp.get (), 0xFE0F); + /* U+26A0 WARNING SIGN has East_Asian_Width == Neutral, but in its + emoji variant is printed (by vte at least) with a 2nd half + overlapping the next char. Hence we add two spaces here: a space + to be covered by this overlap, plus another space of padding. */ + pp_string (pp.get (), " "); + } + + event.print_desc (*pp.get ()); + + label_text result + = label_text::take (xstrdup (pp_formatted_text (pp.get ()))); + return result; + } + + const label_effects *get_effects (unsigned /*range_idx*/) const + { + return &m_effects; + } + + private: + class path_label_effects : public label_effects + { + public: + path_label_effects (const path_label &path_label) + : m_path_label (path_label) + { + } + bool has_in_edge (unsigned range_idx) const final override + { + if (const diagnostic_event *prev_event + = m_path_label.get_prev_event (range_idx)) + return prev_event->connect_to_next_event_p (); + return false; + } + bool has_out_edge (unsigned range_idx) const final override + { + const diagnostic_event &event = m_path_label.get_event (range_idx); + return event.connect_to_next_event_p (); + } + + private: + const path_label &m_path_label; + }; + + const diagnostic_event &get_event (unsigned range_idx) const + { + unsigned event_idx = m_start_idx + range_idx; + return m_path.get_event (event_idx); + } + + const diagnostic_event *get_prev_event (unsigned range_idx) const + { + if (m_start_idx + range_idx == 0) + return nullptr; + unsigned event_idx = m_start_idx + range_idx - 1; + return &m_path.get_event (event_idx); + } + + const diagnostic_path &m_path; + const pretty_printer &m_ref_pp; + unsigned m_start_idx; + path_label_effects m_effects; + const bool m_colorize; + const bool m_allow_emojis; +}; + +/* Return true if E1 and E2 can be consolidated into the same run of events + when printing a diagnostic_path. */ + +static bool +can_consolidate_events (const diagnostic_path &path, + const diagnostic_event &e1, + unsigned ev1_idx, + const diagnostic_event &e2, + unsigned ev2_idx, + bool check_locations) +{ + if (e1.get_thread_id () != e2.get_thread_id ()) + return false; + + if (!path.same_function_p (ev1_idx, ev2_idx)) + return false; + + if (e1.get_stack_depth () != e2.get_stack_depth ()) + return false; + + if (check_locations) + { + location_t loc1 = e1.get_location (); + location_t loc2 = e2.get_location (); + + if (loc1 < RESERVED_LOCATION_COUNT + || loc2 < RESERVED_LOCATION_COUNT) + return false; + + /* Neither can be macro-based. */ + if (linemap_location_from_macro_expansion_p (line_table, loc1)) + return false; + if (linemap_location_from_macro_expansion_p (line_table, loc2)) + return false; + } + + /* Passed all the tests. */ + return true; +} + +struct event_range; +struct path_summary; +class thread_event_printer; + +/* A bundle of information about all of the events in a diagnostic_path + relating to a specific path, for use by path_summary. */ + +class per_thread_summary +{ +public: + per_thread_summary (const diagnostic_path &path, + const logical_location_manager &logical_loc_mgr, + label_text name, unsigned swimlane_idx) + : m_path (path), + m_logical_loc_mgr (logical_loc_mgr), + m_name (std::move (name)), + m_swimlane_idx (swimlane_idx), + m_last_event (nullptr), + m_min_depth (INT_MAX), + m_max_depth (INT_MIN) + {} + + void update_depth_limits (int stack_depth) + { + if (stack_depth < m_min_depth) + m_min_depth = stack_depth; + if (stack_depth > m_max_depth) + m_max_depth = stack_depth; + } + + const char *get_name () const { return m_name.get (); } + unsigned get_swimlane_index () const { return m_swimlane_idx; } + + bool interprocedural_p () const; + +private: + friend struct path_summary; + friend class thread_event_printer; + friend struct event_range; + + const diagnostic_path &m_path; + const logical_location_manager &m_logical_loc_mgr; + + const label_text m_name; + + /* The "swimlane index" is the order in which this per_thread_summary + was created, for use when printing the events. */ + const unsigned m_swimlane_idx; + + // The event ranges specific to this thread: + auto_vec<event_range *> m_event_ranges; + + const diagnostic_event *m_last_event; + + int m_min_depth; + int m_max_depth; +}; + +/* A stack frame for use in HTML output, holding child stack frames, + and event ranges. */ + +struct stack_frame +{ + stack_frame (std::unique_ptr<stack_frame> parent, + logical_location logical_loc, + int stack_depth) + : m_parent (std::move (parent)), + m_logical_loc (logical_loc), + m_stack_depth (stack_depth) + {} + + std::unique_ptr<stack_frame> m_parent; + logical_location m_logical_loc; + const int m_stack_depth; +}; + +/* Begin emitting content relating to a new stack frame within PARENT. + Allocated a new stack_frame and return it. */ + +static std::unique_ptr<stack_frame> +begin_html_stack_frame (xml::printer &xp, + std::unique_ptr<stack_frame> parent, + logical_location logical_loc, + int stack_depth, + const logical_location_manager *logical_loc_mgr) +{ + if (logical_loc) + { + gcc_assert (logical_loc_mgr); + xp.push_tag_with_class ("table", "stack-frame-with-margin", false); + xp.push_tag ("tr", false); + { + xp.push_tag_with_class ("td", "interprocmargin", false); + xp.set_attr ("style", "padding-left: 100px"); + xp.pop_tag (); + } + xp.push_tag_with_class ("td", "stack-frame", false); + label_text funcname + = logical_loc_mgr->get_name_for_path_output (logical_loc); + if (funcname.get ()) + { + xp.push_tag_with_class ("div", "frame-funcname", false); + xp.push_tag ("span", true); + xp.add_text (funcname.get ()); + xp.pop_tag (); // span + xp.pop_tag (); // div + } + } + return std::make_unique<stack_frame> (std::move (parent), + logical_loc, + stack_depth); +} + +/* Finish emitting content for FRAME and delete it. + Return parent. */ + +static std::unique_ptr<stack_frame> +end_html_stack_frame (xml::printer &xp, + std::unique_ptr<stack_frame> frame) +{ + auto parent = std::move (frame->m_parent); + if (frame->m_logical_loc) + { + xp.pop_tag (); // td + xp.pop_tag (); // tr + xp.pop_tag (); // table + } + return parent; +} + +/* Append an HTML <div> element to XP containing an SVG arrow representing + a change in stack depth from OLD_DEPTH to NEW_DEPTH. */ + +static void +emit_svg_arrow (xml::printer &xp, int old_depth, int new_depth) +{ + const int pixels_per_depth = 100; + const int min_depth = MIN (old_depth, new_depth); + const int base_x = 20; + const int excess = 30; + const int last_x + = base_x + (old_depth - min_depth) * pixels_per_depth; + const int this_x + = base_x + (new_depth - min_depth) * pixels_per_depth; + pretty_printer tmp_pp; + pretty_printer *pp = &tmp_pp; + pp_printf (pp, "<div class=\"%s\">\n", + old_depth < new_depth + ? "between-ranges-call" : "between-ranges-return"); + pp_printf (pp, " <svg height=\"30\" width=\"%i\">\n", + MAX (last_x, this_x) + excess); + pp_string + (pp, + " <defs>\n" + " <marker id=\"arrowhead\" markerWidth=\"10\" markerHeight=\"7\"\n" + " refX=\"0\" refY=\"3.5\" orient=\"auto\" stroke=\"#0088ce\" fill=\"#0088ce\">\n" + " <polygon points=\"0 0, 10 3.5, 0 7\"/>\n" + " </marker>\n" + " </defs>\n"); + pp_printf (pp, + " <polyline points=\"%i,0 %i,10 %i,10 %i,20\"\n", + last_x, last_x, this_x, this_x); + pp_string + (pp, + " style=\"fill:none;stroke: #0088ce\"\n" + " marker-end=\"url(#arrowhead)\"/>\n" + " </svg>\n" + "</div>\n\n"); + xp.add_raw (pp_formatted_text (pp)); +} + +/* A range of consecutive events within a diagnostic_path, all within the + same thread, and with the same fndecl and stack_depth, and which are suitable + to print with a single call to diagnostic_show_locus. */ +struct event_range +{ + /* A struct for tracking the mergability of labels on a particular + source line. In particular, track information about links between + labels to ensure that we only consolidate events involving links + that the source-printing code is able to handle (splitting them + otherwise). */ + struct per_source_line_info + { + void init (int line) + { + m_line = line; + m_has_in_edge = false; + m_has_out_edge = false; + m_min_label_source_column = INT_MAX; + m_max_label_source_column = INT_MIN; + } + + /* Return true if our source-printing/labelling/linking code can handle + the events already on this source line, *and* a new event at COLUMN. */ + bool + can_add_label_for_event_p (bool has_in_edge, + const diagnostic_event *prev_event, + bool has_out_edge, + int column) const + { + /* Any existing in-edge has to be the left-most label on its + source line. */ + if (m_has_in_edge && column < m_min_label_source_column) + return false; + /* Any existing out-edge has to be the right-most label on its + source line. */ + if (m_has_out_edge && column > m_max_label_source_column) + return false; + /* Can't have more than one in-edge. */ + if (m_has_in_edge && has_in_edge) + return false; + /* Can't have more than one out-edge. */ + if (m_has_out_edge && has_out_edge) + return false; + + if (has_in_edge) + { + /* Any new in-edge needs to be the left-most label on its + source line. */ + if (column > m_min_label_source_column) + return false; + + gcc_assert (prev_event); + const location_t prev_loc = prev_event->get_location (); + expanded_location prev_exploc + = linemap_client_expand_location_to_spelling_point + (line_table, prev_loc, LOCATION_ASPECT_CARET); + /* The destination in-edge's line number has to be <= the + source out-edge's line number (if any). */ + if (prev_exploc.line >= m_line) + return false; + } + + /* Any new out-edge needs to be the right-most label on its + source line. */ + if (has_out_edge) + if (column < m_max_label_source_column) + return false; + + /* All checks passed; we can add the new event at COLUMN. */ + return true; + } + + void + add_label_for_event (bool has_in_edge, bool has_out_edge, int column) + { + if (has_in_edge) + m_has_in_edge = true; + if (has_out_edge) + m_has_out_edge = true; + m_min_label_source_column = std::min (m_min_label_source_column, column); + m_max_label_source_column = std::max (m_max_label_source_column, column); + } + + int m_line; + bool m_has_in_edge; + bool m_has_out_edge; + int m_min_label_source_column; + int m_max_label_source_column; + }; + + event_range (const diagnostic_path &path, + const pretty_printer &ref_pp, + unsigned start_idx, + const diagnostic_event &initial_event, + per_thread_summary &t, + bool show_event_links, + bool colorize_labels, + bool allow_emojis) + : m_path (path), + m_initial_event (initial_event), + m_logical_loc (initial_event.get_logical_location ()), + m_stack_depth (initial_event.get_stack_depth ()), + m_start_idx (start_idx), m_end_idx (start_idx), + m_path_label (path, ref_pp, + start_idx, colorize_labels, allow_emojis), + m_richloc (initial_event.get_location (), &m_path_label, nullptr), + m_thread_id (initial_event.get_thread_id ()), + m_per_thread_summary (t), + m_show_event_links (show_event_links) + { + if (m_show_event_links) + { + expanded_location exploc + = linemap_client_expand_location_to_spelling_point + (line_table, initial_event.get_location (), LOCATION_ASPECT_CARET); + per_source_line_info &source_line_info + = get_per_source_line_info (exploc.line); + + const diagnostic_event *prev_thread_event = t.m_last_event; + const bool has_in_edge + = (prev_thread_event + ? prev_thread_event->connect_to_next_event_p () + : false); + const bool has_out_edge = initial_event.connect_to_next_event_p (); + + source_line_info.add_label_for_event + (has_in_edge, has_out_edge, exploc.column); + } + } + + per_source_line_info & + get_per_source_line_info (int source_line) + { + bool existed = false; + per_source_line_info &result + = m_source_line_info_map.get_or_insert (source_line, &existed); + if (!existed) + result.init (source_line); + return result; + } + + bool maybe_add_event (const path_print_policy &policy, + const diagnostic_event &new_ev, + unsigned new_ev_idx, + bool check_rich_locations) + { + if (!can_consolidate_events (m_path, + m_initial_event, m_start_idx, + new_ev, new_ev_idx, + check_rich_locations)) + return false; + + /* Verify compatibility of the new label and existing labels + with respect to the link-printing code. */ + expanded_location exploc + = linemap_client_expand_location_to_spelling_point + (line_table, new_ev.get_location (), LOCATION_ASPECT_CARET); + per_source_line_info &source_line_info + = get_per_source_line_info (exploc.line); + const diagnostic_event *prev_event = nullptr; + if (new_ev_idx > 0) + prev_event = &m_path.get_event (new_ev_idx - 1); + const bool has_in_edge = (prev_event + ? prev_event->connect_to_next_event_p () + : false); + const bool has_out_edge = new_ev.connect_to_next_event_p (); + if (m_show_event_links) + if (!source_line_info.can_add_label_for_event_p + (has_in_edge, prev_event, + has_out_edge, exploc.column)) + return false; + + /* Potentially verify that the locations are sufficiently close. */ + if (check_rich_locations) + if (!m_richloc.add_location_if_nearby (policy.get_source_policy (), + new_ev.get_location (), + false, &m_path_label)) + return false; + + m_end_idx = new_ev_idx; + m_per_thread_summary.m_last_event = &new_ev; + + if (m_show_event_links) + source_line_info.add_label_for_event + (has_in_edge, has_out_edge, exploc.column); + + return true; + } + + /* Print the events in this range to PP, typically as a single + call to diagnostic_show_locus. */ + + void print_as_text (pretty_printer &pp, + diagnostic_text_output_format &text_output, + diagnostic_source_effect_info *effect_info) + { + location_t initial_loc = m_initial_event.get_location (); + + diagnostic_context &dc = text_output.get_context (); + + /* Emit a span indicating the filename (and line/column) if the + line has changed relative to the last call to + diagnostic_show_locus. */ + if (dc.m_source_printing.enabled) + { + expanded_location exploc + = linemap_client_expand_location_to_spelling_point + (line_table, initial_loc, LOCATION_ASPECT_CARET); + if (exploc.file != LOCATION_FILE (dc.m_last_location)) + { + diagnostic_location_print_policy loc_policy (text_output); + loc_policy.print_text_span_start (dc, pp, exploc); + } + } + + /* If we have an UNKNOWN_LOCATION (or BUILTINS_LOCATION) as the + primary location for an event, diagnostic_show_locus won't print + anything. + + In particular the label for the event won't get printed. + Fail more gracefully in this case by showing the event + index and text, at no particular location. */ + if (get_pure_location (initial_loc) <= BUILTINS_LOCATION) + { + for (unsigned i = m_start_idx; i <= m_end_idx; i++) + { + const diagnostic_event &iter_event = m_path.get_event (i); + diagnostic_event_id_t event_id (i); + pp_printf (&pp, " %@: ", &event_id); + iter_event.print_desc (pp); + pp_newline (&pp); + } + return; + } + + /* Call diagnostic_show_locus to show the events using labels. */ + diagnostic_show_locus (&dc, text_output.get_source_printing_options (), + &m_richloc, DK_DIAGNOSTIC_PATH, &pp, + effect_info); + + /* If we have a macro expansion, show the expansion to the user. */ + if (linemap_location_from_macro_expansion_p (line_table, initial_loc)) + { + gcc_assert (m_start_idx == m_end_idx); + maybe_unwind_expanded_macro_loc (text_output, initial_loc); + } + } + + /* Print the events in this range to XP, typically as a single + call to diagnostic_show_locus_as_html. */ + + void print_as_html (xml::printer &xp, + diagnostic_context &dc, + diagnostic_source_effect_info *effect_info, + html_label_writer *event_label_writer) + { + location_t initial_loc = m_initial_event.get_location (); + + /* Emit a span indicating the filename (and line/column) if the + line has changed relative to the last call to + diagnostic_show_locus. */ + if (dc.m_source_printing.enabled) + { + expanded_location exploc + = linemap_client_expand_location_to_spelling_point + (line_table, initial_loc, LOCATION_ASPECT_CARET); + if (exploc.file != LOCATION_FILE (dc.m_last_location)) + { + diagnostic_location_print_policy loc_policy (dc); + loc_policy.print_html_span_start (dc, xp, exploc); + } + } + + /* If we have an UNKNOWN_LOCATION (or BUILTINS_LOCATION) as the + primary location for an event, diagnostic_show_locus_as_html won't print + anything. + + In particular the label for the event won't get printed. + Fail more gracefully in this case by showing the event + index and text, at no particular location. */ + if (get_pure_location (initial_loc) <= BUILTINS_LOCATION) + { + for (unsigned i = m_start_idx; i <= m_end_idx; i++) + { + const diagnostic_event &iter_event = m_path.get_event (i); + diagnostic_event_id_t event_id (i); + pretty_printer pp; + pp_printf (&pp, " %@: ", &event_id); + iter_event.print_desc (pp); + if (event_label_writer) + event_label_writer->begin_label (); + xp.add_text (pp_formatted_text (&pp)); + if (event_label_writer) + event_label_writer->end_label (); + } + return; + } + + /* Call diagnostic_show_locus_as_html to show the source, + showing events using labels. */ + diagnostic_show_locus_as_html (&dc, dc.m_source_printing, + &m_richloc, DK_DIAGNOSTIC_PATH, xp, + effect_info, event_label_writer); + + // TODO: show macro expansions + } + + const diagnostic_path &m_path; + const diagnostic_event &m_initial_event; + logical_location m_logical_loc; + int m_stack_depth; + unsigned m_start_idx; + unsigned m_end_idx; + path_label m_path_label; + gcc_rich_location m_richloc; + diagnostic_thread_id_t m_thread_id; + per_thread_summary &m_per_thread_summary; + hash_map<int_hash<int, -1, -2>, + per_source_line_info> m_source_line_info_map; + bool m_show_event_links; +}; + +/* A struct for grouping together the events in a diagnostic_path into + ranges of events, partitioned by thread and by stack frame (i.e. by fndecl + and stack depth). */ + +struct path_summary +{ + path_summary (const path_print_policy &policy, + const pretty_printer &ref_pp, + const diagnostic_path &path, + bool check_rich_locations, + bool colorize = false, + bool show_event_links = true); + + const logical_location_manager &get_logical_location_manager () const + { + return m_logical_loc_mgr; + } + unsigned get_num_ranges () const { return m_ranges.length (); } + bool multithreaded_p () const { return m_per_thread_summary.length () > 1; } + + const per_thread_summary &get_events_for_thread_id (diagnostic_thread_id_t tid) + { + per_thread_summary **slot = m_thread_id_to_events.get (tid); + gcc_assert (slot); + gcc_assert (*slot); + return **slot; + } + + const logical_location_manager &m_logical_loc_mgr; + auto_delete_vec <event_range> m_ranges; + auto_delete_vec <per_thread_summary> m_per_thread_summary; + hash_map<int_hash<diagnostic_thread_id_t, -1, -2>, + per_thread_summary *> m_thread_id_to_events; + +private: + per_thread_summary & + get_or_create_events_for_thread_id (const diagnostic_path &path, + diagnostic_thread_id_t tid) + { + if (per_thread_summary **slot = m_thread_id_to_events.get (tid)) + return **slot; + + const diagnostic_thread &thread = path.get_thread (tid); + per_thread_summary *pts + = new per_thread_summary (path, + m_logical_loc_mgr, + thread.get_name (false), + m_per_thread_summary.length ()); + m_thread_id_to_events.put (tid, pts); + m_per_thread_summary.safe_push (pts); + return *pts; + } +}; + +/* Return true iff there is more than one stack frame used by the events + of this thread. */ + +bool +per_thread_summary::interprocedural_p () const +{ + if (m_event_ranges.is_empty ()) + return false; + int first_stack_depth = m_event_ranges[0]->m_stack_depth; + for (auto range : m_event_ranges) + { + if (!m_path.same_function_p (m_event_ranges[0]->m_start_idx, + range->m_start_idx)) + return true; + if (range->m_stack_depth != first_stack_depth) + return true; + } + return false; +} + +/* path_summary's ctor. */ + +path_summary::path_summary (const path_print_policy &policy, + const pretty_printer &ref_pp, + const diagnostic_path &path, + bool check_rich_locations, + bool colorize, + bool show_event_links) +: m_logical_loc_mgr (path.get_logical_location_manager ()) +{ + const unsigned num_events = path.num_events (); + + event_range *cur_event_range = NULL; + for (unsigned idx = 0; idx < num_events; idx++) + { + const diagnostic_event &event = path.get_event (idx); + const diagnostic_thread_id_t thread_id = event.get_thread_id (); + per_thread_summary &pts + = get_or_create_events_for_thread_id (path, thread_id); + + pts.update_depth_limits (event.get_stack_depth ()); + + if (cur_event_range) + if (cur_event_range->maybe_add_event (policy, + event, + idx, check_rich_locations)) + continue; + + auto theme = policy.get_diagram_theme (); + const bool allow_emojis = theme ? theme->emojis_p () : false; + cur_event_range = new event_range (path, ref_pp, + idx, event, pts, + show_event_links, + colorize, + allow_emojis); + m_ranges.safe_push (cur_event_range); + pts.m_event_ranges.safe_push (cur_event_range); + pts.m_last_event = &event; + } +} + +/* Write SPACES to PP. */ + +static void +write_indent (pretty_printer *pp, int spaces) +{ + for (int i = 0; i < spaces; i++) + pp_space (pp); +} + +static const int base_indent = 2; +static const int per_frame_indent = 2; + +/* A bundle of state for printing event_range instances for a particular + thread. */ + +class thread_event_printer +{ +public: + thread_event_printer (const per_thread_summary &t, bool show_depths) + : m_per_thread_summary (t), + m_show_depths (show_depths), + m_cur_indent (base_indent), + m_vbar_column_for_depth (), + m_num_printed (0) + { + } + + /* Get the previous event_range within this thread, if any. */ + const event_range *get_any_prev_range () const + { + if (m_num_printed > 0) + return m_per_thread_summary.m_event_ranges[m_num_printed - 1]; + else + return nullptr; + } + + /* Get the next event_range within this thread, if any. */ + const event_range *get_any_next_range () const + { + if (m_num_printed < m_per_thread_summary.m_event_ranges.length () - 1) + return m_per_thread_summary.m_event_ranges[m_num_printed + 1]; + else + return nullptr; + } + + void + print_swimlane_for_event_range_as_text (diagnostic_text_output_format &text_output, + pretty_printer *pp, + const logical_location_manager &logical_loc_mgr, + event_range *range, + diagnostic_source_effect_info *effect_info) + { + gcc_assert (pp); + const char *const line_color = "path"; + const char *start_line_color + = colorize_start (pp_show_color (pp), line_color); + const char *end_line_color = colorize_stop (pp_show_color (pp)); + + text_art::ascii_theme fallback_theme; + text_art::theme *theme = text_output.get_diagram_theme (); + if (!theme) + theme = &fallback_theme; + + cppchar_t depth_marker_char = theme->get_cppchar + (text_art::theme::cell_kind::INTERPROCEDURAL_DEPTH_MARKER); + /* e.g. "|". */ + + const bool interprocedural_p = m_per_thread_summary.interprocedural_p (); + + write_indent (pp, m_cur_indent); + if (const event_range *prev_range = get_any_prev_range ()) + { + if (range->m_stack_depth > prev_range->m_stack_depth) + { + gcc_assert (interprocedural_p); + /* Show pushed stack frame(s). */ + cppchar_t left = theme->get_cppchar + (text_art::theme::cell_kind::INTERPROCEDURAL_PUSH_FRAME_LEFT); + cppchar_t middle = theme->get_cppchar + (text_art::theme::cell_kind::INTERPROCEDURAL_PUSH_FRAME_MIDDLE); + cppchar_t right = theme->get_cppchar + (text_art::theme::cell_kind::INTERPROCEDURAL_PUSH_FRAME_RIGHT); + /* e.g. "+--> ". */ + pp_string (pp, start_line_color); + pp_unicode_character (pp, left); + pp_unicode_character (pp, middle); + pp_unicode_character (pp, middle); + pp_unicode_character (pp, right); + pp_space (pp); + pp_string (pp, end_line_color); + m_cur_indent += 5; + } + } + if (range->m_logical_loc) + { + label_text name + (logical_loc_mgr.get_name_for_path_output (range->m_logical_loc)); + if (name.get ()) + pp_printf (pp, "%qs: ", name.get ()); + } + if (range->m_start_idx == range->m_end_idx) + pp_printf (pp, "event %i", + range->m_start_idx + 1); + else + pp_printf (pp, "events %i-%i", + range->m_start_idx + 1, range->m_end_idx + 1); + if (m_show_depths) + pp_printf (pp, " (depth %i)", range->m_stack_depth); + pp_newline (pp); + + /* Print a run of events. */ + if (interprocedural_p) + { + write_indent (pp, m_cur_indent + per_frame_indent); + pp_string (pp, start_line_color); + pp_unicode_character (pp, depth_marker_char); + pp_string (pp, end_line_color); + pp_newline (pp); + + char *saved_prefix = pp_take_prefix (pp); + char *prefix; + { + pretty_printer tmp_pp; + write_indent (&tmp_pp, m_cur_indent + per_frame_indent); + pp_string (&tmp_pp, start_line_color); + pp_unicode_character (&tmp_pp, depth_marker_char); + pp_string (&tmp_pp, end_line_color); + prefix = xstrdup (pp_formatted_text (&tmp_pp)); + } + pp_set_prefix (pp, prefix); + pp_prefixing_rule (pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; + range->print_as_text (*pp, text_output, effect_info); + pp_set_prefix (pp, saved_prefix); + + write_indent (pp, m_cur_indent + per_frame_indent); + pp_string (pp, start_line_color); + pp_unicode_character (pp, depth_marker_char); + pp_string (pp, end_line_color); + pp_newline (pp); + } + else + range->print_as_text (*pp, text_output, effect_info); + + if (const event_range *next_range = get_any_next_range ()) + { + if (range->m_stack_depth > next_range->m_stack_depth) + { + if (m_vbar_column_for_depth.get (next_range->m_stack_depth)) + { + /* Show returning from stack frame(s), by printing + something like: + " |\n" + " <-------------+\n" + " |\n". */ + gcc_assert (interprocedural_p); + cppchar_t left = theme->get_cppchar + (text_art::theme::cell_kind::INTERPROCEDURAL_POP_FRAMES_LEFT); + cppchar_t middle = theme->get_cppchar + (text_art::theme::cell_kind::INTERPROCEDURAL_POP_FRAMES_MIDDLE); + cppchar_t right = theme->get_cppchar + (text_art::theme::cell_kind::INTERPROCEDURAL_POP_FRAMES_RIGHT); + int vbar_for_next_frame + = *m_vbar_column_for_depth.get (next_range->m_stack_depth); + + int indent_for_next_frame + = vbar_for_next_frame - per_frame_indent; + write_indent (pp, vbar_for_next_frame); + pp_string (pp, start_line_color); + pp_unicode_character (pp, left); + for (int i = indent_for_next_frame + per_frame_indent; + i < m_cur_indent + per_frame_indent - 1; i++) + pp_unicode_character (pp, middle); + pp_unicode_character (pp, right); + pp_string (pp, end_line_color); + pp_newline (pp); + m_cur_indent = indent_for_next_frame; + + write_indent (pp, vbar_for_next_frame); + pp_string (pp, start_line_color); + pp_unicode_character (pp, depth_marker_char); + pp_string (pp, end_line_color); + pp_newline (pp); + } + else + { + /* Handle disjoint paths (e.g. a callback at some later + time). */ + m_cur_indent = base_indent; + } + } + else if (range->m_stack_depth < next_range->m_stack_depth) + { + /* Prepare to show pushed stack frame. */ + gcc_assert (interprocedural_p); + gcc_assert (range->m_stack_depth != EMPTY); + gcc_assert (range->m_stack_depth != DELETED); + m_vbar_column_for_depth.put (range->m_stack_depth, + m_cur_indent + per_frame_indent); + m_cur_indent += per_frame_indent; + } + } + + m_num_printed++; + } + + void + print_swimlane_for_event_range_as_html (diagnostic_context &dc, + xml::printer &xp, + html_label_writer *event_label_writer, + event_range *range, + diagnostic_source_effect_info *effect_info) + { + range->print_as_html (xp, dc, effect_info, event_label_writer); + m_num_printed++; + } + + int get_cur_indent () const { return m_cur_indent; } + +private: + const per_thread_summary &m_per_thread_summary; + bool m_show_depths; + + /* Print the ranges. */ + int m_cur_indent; + + /* Keep track of column numbers of existing '|' characters for + stack depths we've already printed. */ + static const int EMPTY = -1; + static const int DELETED = -2; + typedef int_hash <int, EMPTY, DELETED> vbar_hash; + hash_map <vbar_hash, int> m_vbar_column_for_depth; + + /* How many event ranges within this swimlane have we printed. + This is the index of the next event_range to print. */ + unsigned m_num_printed; +}; + +/* Print path_summary PS to TEXT_OUTPUT, giving an overview of the + interprocedural calls and returns. + + Print the event descriptions in a nested form, printing the event + descriptions within calls to diagnostic_show_locus, using labels to + show the events: + + 'foo' (events 1-2) + | NN | + | | + +--> 'bar' (events 3-4) + | NN | + | | + +--> 'baz' (events 5-6) + | NN | + | | + <------------ + + | + 'foo' (events 7-8) + | NN | + | | + +--> 'bar' (events 9-10) + | NN | + | | + +--> 'baz' (events 11-12) + | NN | + | | + + If SHOW_DEPTHS is true, append " (depth N)" to the header of each run + of events. + + For events with UNKNOWN_LOCATION, print a summary of each the event. */ + +static void +print_path_summary_as_text (const path_summary &ps, + diagnostic_text_output_format &text_output, + bool show_depths) +{ + pretty_printer *const pp = text_output.get_printer (); + + std::vector<thread_event_printer> thread_event_printers; + for (auto t : ps.m_per_thread_summary) + thread_event_printers.push_back (thread_event_printer (*t, show_depths)); + + unsigned i; + event_range *range; + int last_out_edge_column = -1; + FOR_EACH_VEC_ELT (ps.m_ranges, i, range) + { + const int swimlane_idx + = range->m_per_thread_summary.get_swimlane_index (); + if (ps.multithreaded_p ()) + if (i == 0 || ps.m_ranges[i - 1]->m_thread_id != range->m_thread_id) + { + if (i > 0) + pp_newline (pp); + pp_printf (pp, "Thread: %qs", + range->m_per_thread_summary.get_name ()); + pp_newline (pp); + } + thread_event_printer &tep = thread_event_printers[swimlane_idx]; + /* Wire up any trailing out-edge from previous range to leading in-edge + of this range. */ + diagnostic_source_effect_info effect_info; + effect_info.m_leading_in_edge_column = last_out_edge_column; + tep.print_swimlane_for_event_range_as_text + (text_output, pp, + ps.get_logical_location_manager (), + range, &effect_info); + last_out_edge_column = effect_info.m_trailing_out_edge_column; + } +} + +/* Print PS as HTML to XP, using DC and, if non-null EVENT_LABEL_WRITER. */ + +static void +print_path_summary_as_html (const path_summary &ps, + diagnostic_context &dc, + xml::printer &xp, + html_label_writer *event_label_writer, + bool show_depths) +{ + std::vector<thread_event_printer> thread_event_printers; + for (auto t : ps.m_per_thread_summary) + thread_event_printers.push_back (thread_event_printer (*t, show_depths)); + + const logical_location_manager *logical_loc_mgr + = dc.get_logical_location_manager (); + + xp.push_tag_with_class ("div", "event-ranges", false); + + /* Group the ranges into stack frames. */ + std::unique_ptr<stack_frame> curr_frame; + unsigned i; + event_range *range; + int last_out_edge_column = -1; + FOR_EACH_VEC_ELT (ps.m_ranges, i, range) + { + const int swimlane_idx + = range->m_per_thread_summary.get_swimlane_index (); + + const logical_location this_logical_loc = range->m_logical_loc; + const int this_depth = range->m_stack_depth; + if (curr_frame) + { + int old_stack_depth = curr_frame->m_stack_depth; + if (this_depth > curr_frame->m_stack_depth) + { + emit_svg_arrow (xp, old_stack_depth, this_depth); + curr_frame + = begin_html_stack_frame (xp, + std::move (curr_frame), + range->m_logical_loc, + range->m_stack_depth, + logical_loc_mgr); + } + else + { + while (this_depth < curr_frame->m_stack_depth + || this_logical_loc != curr_frame->m_logical_loc) + { + curr_frame = end_html_stack_frame (xp, std::move (curr_frame)); + if (curr_frame == NULL) + { + curr_frame + = begin_html_stack_frame (xp, + nullptr, + range->m_logical_loc, + range->m_stack_depth, + logical_loc_mgr); + break; + } + } + emit_svg_arrow (xp, old_stack_depth, this_depth); + } + } + else + { + curr_frame = begin_html_stack_frame (xp, + NULL, + range->m_logical_loc, + range->m_stack_depth, + logical_loc_mgr); + } + + xp.push_tag_with_class ("table", "event-range-with-margin", false); + xp.push_tag ("tr", false); + xp.push_tag_with_class ("td", "event-range", false); + xp.push_tag_with_class ("div", "events-hdr", true); + if (range->m_logical_loc) + { + gcc_assert (logical_loc_mgr); + label_text funcname + = logical_loc_mgr->get_name_for_path_output (range->m_logical_loc); + if (funcname.get ()) + { + xp.push_tag_with_class ("span", "funcname", true); + xp.add_text (funcname.get ()); + xp.pop_tag (); //span + xp.add_text (": "); + } + } + { + xp.push_tag_with_class ("span", "event-ids", true); + pretty_printer pp; + if (range->m_start_idx == range->m_end_idx) + pp_printf (&pp, "event %i", + range->m_start_idx + 1); + else + pp_printf (&pp, "events %i-%i", + range->m_start_idx + 1, range->m_end_idx + 1); + xp.add_text (pp_formatted_text (&pp)); + xp.pop_tag (); // span + } + if (show_depths) + { + xp.add_text (" "); + xp.push_tag_with_class ("span", "depth", true); + pretty_printer pp; + pp_printf (&pp, "(depth %i)", range->m_stack_depth); + xp.add_text (pp_formatted_text (&pp)); + xp.pop_tag (); //span + } + xp.pop_tag (); // div + + /* Print a run of events. */ + thread_event_printer &tep = thread_event_printers[swimlane_idx]; + /* Wire up any trailing out-edge from previous range to leading in-edge + of this range. */ + diagnostic_source_effect_info effect_info; + effect_info.m_leading_in_edge_column = last_out_edge_column; + tep.print_swimlane_for_event_range_as_html (dc, xp, event_label_writer, + range, &effect_info); + last_out_edge_column = effect_info.m_trailing_out_edge_column; + + xp.pop_tag (); // td + xp.pop_tag (); // tr + xp.pop_tag (); // table + } + + /* Close outstanding frames. */ + while (curr_frame) + curr_frame = end_html_stack_frame (xp, std::move (curr_frame)); + + xp.pop_tag (); // div +} + +} /* end of anonymous namespace for path-printing code. */ + +class element_event_desc : public pp_element +{ +public: + element_event_desc (const diagnostic_event &event) + : m_event (event) + { + } + + void add_to_phase_2 (pp_markup::context &ctxt) final override + { + auto pp = ctxt.m_pp.clone (); + m_event.print_desc (*pp.get ()); + pp_string (&ctxt.m_pp, pp_formatted_text (pp.get ())); + } + +private: + const diagnostic_event &m_event; +}; + +/* Print PATH according to the context's path_format. */ + +void +diagnostic_text_output_format::print_path (const diagnostic_path &path) +{ + const unsigned num_events = path.num_events (); + + switch (get_context ().get_path_format ()) + { + case DPF_NONE: + /* Do nothing. */ + return; + + case DPF_SEPARATE_EVENTS: + { + /* A note per event. */ + auto &logical_loc_mgr = path.get_logical_location_manager (); + for (unsigned i = 0; i < num_events; i++) + { + const diagnostic_event &event = path.get_event (i); + element_event_desc e_event_desc (event); + diagnostic_event_id_t event_id (i); + if (get_context ().show_path_depths_p ()) + { + int stack_depth = event.get_stack_depth (); + /* -fdiagnostics-path-format=separate-events doesn't print + fndecl information, so with -fdiagnostics-show-path-depths + print the fndecls too, if any. */ + if (logical_location logical_loc + = event.get_logical_location ()) + { + label_text name + (logical_loc_mgr.get_name_for_path_output (logical_loc)); + inform (event.get_location (), + "%@ %e (fndecl %qs, depth %i)", + &event_id, &e_event_desc, + name.get (), stack_depth); + } + else + inform (event.get_location (), + "%@ %e (depth %i)", + &event_id, &e_event_desc, + stack_depth); + } + else + inform (event.get_location (), + "%@ %e", &event_id, &e_event_desc); + } + } + break; + + case DPF_INLINE_EVENTS: + { + /* Consolidate related events. */ + path_print_policy policy (*this); + pretty_printer *const pp = get_printer (); + const bool check_rich_locations = true; + const bool colorize = pp_show_color (pp); + const bool show_event_links = m_source_printing.show_event_links_p; + path_summary summary (policy, + *pp, + path, + check_rich_locations, + colorize, + show_event_links); + char *saved_prefix = pp_take_prefix (pp); + pp_set_prefix (pp, NULL); + print_path_summary_as_text (summary, *this, + get_context ().show_path_depths_p ()); + pp_flush (pp); + pp_set_prefix (pp, saved_prefix); + } + break; + } +} + +/* Print PATH as HTML to XP, using DC and DSPP for settings. + If non-null, use EVENT_LABEL_WRITER when writing events. */ + +void +print_path_as_html (xml::printer &xp, + const diagnostic_path &path, + diagnostic_context &dc, + html_label_writer *event_label_writer, + const diagnostic_source_print_policy &dspp) +{ + path_print_policy policy (dc); + const bool check_rich_locations = true; + const bool colorize = false; + const diagnostic_source_printing_options &source_printing_opts + = dspp.get_options (); + const bool show_event_links = source_printing_opts.show_event_links_p; + path_summary summary (policy, + *dc.get_reference_printer (), + path, + check_rich_locations, + colorize, + show_event_links); + print_path_summary_as_html (summary, dc, xp, event_label_writer, + dc.show_path_depths_p ()); +} + +#if CHECKING_P + +namespace selftest { + +/* Return true iff all events in PATH have locations for which column data + is available, so that selftests that require precise string output can + bail out for awkward line_table cases. */ + +static bool +path_events_have_column_data_p (const diagnostic_path &path) +{ + for (unsigned idx = 0; idx < path.num_events (); idx++) + { + location_t event_loc = path.get_event (idx).get_location (); + if (line_table->get_pure_location (event_loc) + > LINE_MAP_MAX_LOCATION_WITH_COLS) + return false; + if (line_table->get_start (event_loc) > LINE_MAP_MAX_LOCATION_WITH_COLS) + return false; + if (line_table->get_finish (event_loc) > LINE_MAP_MAX_LOCATION_WITH_COLS) + return false; + } + return true; +} + +/* Verify that empty paths are handled gracefully. */ + +static void +test_empty_path (pretty_printer *event_pp) +{ + test_diagnostic_path path (event_pp); + ASSERT_FALSE (path.interprocedural_p ()); + + test_diagnostic_context dc; + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, false); + ASSERT_EQ (summary.get_num_ranges (), 0); + + print_path_summary_as_text (summary, text_output, true); + ASSERT_STREQ ("", + pp_formatted_text (text_output.get_printer ())); +} + +/* Verify that print_path_summary works on a purely intraprocedural path. */ + +static void +test_intraprocedural_path (pretty_printer *event_pp) +{ + test_diagnostic_path path (event_pp); + const char *const funcname = "foo"; + path.add_event (UNKNOWN_LOCATION, funcname, 0, "first %qs", "free"); + path.add_event (UNKNOWN_LOCATION, funcname, 0, "double %qs", "free"); + + ASSERT_FALSE (path.interprocedural_p ()); + + test_diagnostic_context dc; + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, false, false, false); + ASSERT_EQ (summary.get_num_ranges (), 1); + + print_path_summary_as_text (summary, text_output, true); + ASSERT_STREQ (" `foo': events 1-2 (depth 0)\n" + " (1): first `free'\n" + " (2): double `free'\n", + pp_formatted_text (text_output.get_printer ())); +} + +/* Verify that print_path_summary works on an interprocedural path. */ + +static void +test_interprocedural_path_1 (pretty_printer *event_pp) +{ + test_diagnostic_path path (event_pp); + path.add_entry ("test", 0); + path.add_call ("test", 0, "make_boxed_int"); + path.add_call ("make_boxed_int", 1, "wrapped_malloc"); + path.add_event (UNKNOWN_LOCATION, + "wrapped_malloc", 2, "calling malloc"); + path.add_return ("test", 0); + path.add_call ("test", 0, "free_boxed_int"); + path.add_call ("free_boxed_int", 1, "wrapped_free"); + path.add_event (UNKNOWN_LOCATION, "wrapped_free", 2, "calling free"); + path.add_return ("test", 0); + path.add_call ("test", 0, "free_boxed_int"); + path.add_call ("free_boxed_int", 1, "wrapped_free"); + path.add_event (UNKNOWN_LOCATION, "wrapped_free", 2, "calling free"); + ASSERT_EQ (path.num_events (), 18); + + ASSERT_TRUE (path.interprocedural_p ()); + + { + test_diagnostic_context dc; + diagnostic_text_output_format text_output (dc, nullptr, false); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, false); + ASSERT_EQ (summary.get_num_ranges (), 9); + + dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII); + print_path_summary_as_text (summary, text_output, true); + ASSERT_STREQ + (" `test': events 1-2 (depth 0)\n" + " |\n" + " | (1): entering `test'\n" + " | (2): calling `make_boxed_int'\n" + " |\n" + " +--> `make_boxed_int': events 3-4 (depth 1)\n" + " |\n" + " | (3): entering `make_boxed_int'\n" + " | (4): calling `wrapped_malloc'\n" + " |\n" + " +--> `wrapped_malloc': events 5-6 (depth 2)\n" + " |\n" + " | (5): entering `wrapped_malloc'\n" + " | (6): calling malloc\n" + " |\n" + " <-------------+\n" + " |\n" + " `test': events 7-8 (depth 0)\n" + " |\n" + " | (7): returning to `test'\n" + " | (8): calling `free_boxed_int'\n" + " |\n" + " +--> `free_boxed_int': events 9-10 (depth 1)\n" + " |\n" + " | (9): entering `free_boxed_int'\n" + " | (10): calling `wrapped_free'\n" + " |\n" + " +--> `wrapped_free': events 11-12 (depth 2)\n" + " |\n" + " | (11): entering `wrapped_free'\n" + " | (12): calling free\n" + " |\n" + " <-------------+\n" + " |\n" + " `test': events 13-14 (depth 0)\n" + " |\n" + " | (13): returning to `test'\n" + " | (14): calling `free_boxed_int'\n" + " |\n" + " +--> `free_boxed_int': events 15-16 (depth 1)\n" + " |\n" + " | (15): entering `free_boxed_int'\n" + " | (16): calling `wrapped_free'\n" + " |\n" + " +--> `wrapped_free': events 17-18 (depth 2)\n" + " |\n" + " | (17): entering `wrapped_free'\n" + " | (18): calling free\n" + " |\n", + pp_formatted_text (text_output.get_printer ())); + } + { + test_diagnostic_context dc; + dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE); + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, false); + print_path_summary_as_text (summary, text_output, true); + ASSERT_STREQ + (" `test': events 1-2 (depth 0)\n" + " │\n" + " │ (1): entering `test'\n" + " │ (2): calling `make_boxed_int'\n" + " │\n" + " └──> `make_boxed_int': events 3-4 (depth 1)\n" + " │\n" + " │ (3): entering `make_boxed_int'\n" + " │ (4): calling `wrapped_malloc'\n" + " │\n" + " └──> `wrapped_malloc': events 5-6 (depth 2)\n" + " │\n" + " │ (5): entering `wrapped_malloc'\n" + " │ (6): calling malloc\n" + " │\n" + " <─────────────┘\n" + " │\n" + " `test': events 7-8 (depth 0)\n" + " │\n" + " │ (7): returning to `test'\n" + " │ (8): calling `free_boxed_int'\n" + " │\n" + " └──> `free_boxed_int': events 9-10 (depth 1)\n" + " │\n" + " │ (9): entering `free_boxed_int'\n" + " │ (10): calling `wrapped_free'\n" + " │\n" + " └──> `wrapped_free': events 11-12 (depth 2)\n" + " │\n" + " │ (11): entering `wrapped_free'\n" + " │ (12): calling free\n" + " │\n" + " <─────────────┘\n" + " │\n" + " `test': events 13-14 (depth 0)\n" + " │\n" + " │ (13): returning to `test'\n" + " │ (14): calling `free_boxed_int'\n" + " │\n" + " └──> `free_boxed_int': events 15-16 (depth 1)\n" + " │\n" + " │ (15): entering `free_boxed_int'\n" + " │ (16): calling `wrapped_free'\n" + " │\n" + " └──> `wrapped_free': events 17-18 (depth 2)\n" + " │\n" + " │ (17): entering `wrapped_free'\n" + " │ (18): calling free\n" + " │\n", + pp_formatted_text (text_output.get_printer ())); + } + +} + +/* Example where we pop the stack to an intermediate frame, rather than the + initial one. */ + +static void +test_interprocedural_path_2 (pretty_printer *event_pp) +{ + test_diagnostic_path path (event_pp); + path.add_entry ("foo", 0); + path.add_call ("foo", 0, "bar"); + path.add_call ("bar", 1, "baz"); + path.add_return ("bar", 1); + path.add_call ("bar", 1, "baz"); + ASSERT_EQ (path.num_events (), 8); + + ASSERT_TRUE (path.interprocedural_p ()); + + { + test_diagnostic_context dc; + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, false); + ASSERT_EQ (summary.get_num_ranges (), 5); + dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII); + print_path_summary_as_text (summary, text_output, true); + ASSERT_STREQ + (" `foo': events 1-2 (depth 0)\n" + " |\n" + " | (1): entering `foo'\n" + " | (2): calling `bar'\n" + " |\n" + " +--> `bar': events 3-4 (depth 1)\n" + " |\n" + " | (3): entering `bar'\n" + " | (4): calling `baz'\n" + " |\n" + " +--> `baz': event 5 (depth 2)\n" + " |\n" + " | (5): entering `baz'\n" + " |\n" + " <------+\n" + " |\n" + " `bar': events 6-7 (depth 1)\n" + " |\n" + " | (6): returning to `bar'\n" + " | (7): calling `baz'\n" + " |\n" + " +--> `baz': event 8 (depth 2)\n" + " |\n" + " | (8): entering `baz'\n" + " |\n", + pp_formatted_text (text_output.get_printer ())); + } + { + test_diagnostic_context dc; + dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE); + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, false); + print_path_summary_as_text (summary, text_output, true); + ASSERT_STREQ + (" `foo': events 1-2 (depth 0)\n" + " │\n" + " │ (1): entering `foo'\n" + " │ (2): calling `bar'\n" + " │\n" + " └──> `bar': events 3-4 (depth 1)\n" + " │\n" + " │ (3): entering `bar'\n" + " │ (4): calling `baz'\n" + " │\n" + " └──> `baz': event 5 (depth 2)\n" + " │\n" + " │ (5): entering `baz'\n" + " │\n" + " <──────┘\n" + " │\n" + " `bar': events 6-7 (depth 1)\n" + " │\n" + " │ (6): returning to `bar'\n" + " │ (7): calling `baz'\n" + " │\n" + " └──> `baz': event 8 (depth 2)\n" + " │\n" + " │ (8): entering `baz'\n" + " │\n", + pp_formatted_text (text_output.get_printer ())); + } +} + +/* Verify that print_path_summary is sane in the face of a recursive + diagnostic_path. */ + +static void +test_recursion (pretty_printer *event_pp) +{ + test_diagnostic_path path (event_pp); + path.add_entry ("factorial", 0); + for (int depth = 0; depth < 3; depth++) + path.add_call ("factorial", depth, "factorial"); + ASSERT_EQ (path.num_events (), 7); + + ASSERT_TRUE (path.interprocedural_p ()); + + { + test_diagnostic_context dc; + dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII); + + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, false); + ASSERT_EQ (summary.get_num_ranges (), 4); + + print_path_summary_as_text (summary, text_output, true); + ASSERT_STREQ + (" `factorial': events 1-2 (depth 0)\n" + " |\n" + " | (1): entering `factorial'\n" + " | (2): calling `factorial'\n" + " |\n" + " +--> `factorial': events 3-4 (depth 1)\n" + " |\n" + " | (3): entering `factorial'\n" + " | (4): calling `factorial'\n" + " |\n" + " +--> `factorial': events 5-6 (depth 2)\n" + " |\n" + " | (5): entering `factorial'\n" + " | (6): calling `factorial'\n" + " |\n" + " +--> `factorial': event 7 (depth 3)\n" + " |\n" + " | (7): entering `factorial'\n" + " |\n", + pp_formatted_text (text_output.get_printer ())); + } + { + test_diagnostic_context dc; + dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE); + + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, false); + print_path_summary_as_text (summary, text_output, true); + ASSERT_STREQ + (" `factorial': events 1-2 (depth 0)\n" + " │\n" + " │ (1): entering `factorial'\n" + " │ (2): calling `factorial'\n" + " │\n" + " └──> `factorial': events 3-4 (depth 1)\n" + " │\n" + " │ (3): entering `factorial'\n" + " │ (4): calling `factorial'\n" + " │\n" + " └──> `factorial': events 5-6 (depth 2)\n" + " │\n" + " │ (5): entering `factorial'\n" + " │ (6): calling `factorial'\n" + " │\n" + " └──> `factorial': event 7 (depth 3)\n" + " │\n" + " │ (7): entering `factorial'\n" + " │\n", + pp_formatted_text (text_output.get_printer ())); + } +} + +/* Helper class for writing tests of control flow visualization. */ + +class control_flow_test +{ +public: + control_flow_test (const location &loc, + const line_table_case &case_, + const char *content) + : m_tmp_file (loc, ".c", content, + /* gcc_rich_location::add_location_if_nearby implicitly + uses global_dc's file_cache, so we need to evict + tmp when we're done. */ + &global_dc->get_file_cache ()), + m_ltt (case_) + { + m_ord_map + = linemap_check_ordinary (linemap_add (line_table, LC_ENTER, false, + m_tmp_file.get_filename (), 0)); + linemap_line_start (line_table, 1, 100); + } + + location_t get_line_and_column (int line, int column) + { + return linemap_position_for_line_and_column (line_table, m_ord_map, + line, column); + } + + location_t get_line_and_columns (int line, int first_column, int last_column) + { + return get_line_and_columns (line, + first_column, first_column, last_column); + } + + location_t get_line_and_columns (int line, + int first_column, + int caret_column, + int last_column) + { + return make_location (get_line_and_column (line, caret_column), + get_line_and_column (line, first_column), + get_line_and_column (line, last_column)); + } + +private: + temp_source_file m_tmp_file; + line_table_test m_ltt; + const line_map_ordinary *m_ord_map; +}; + +/* Example of event edges where all events can go in the same layout, + testing the 6 combinations of: + - ASCII vs Unicode vs event links off + - line numbering on and off. */ + +static void +test_control_flow_1 (const line_table_case &case_, + pretty_printer *event_pp) +{ + /* Create a tempfile and write some text to it. + ...000000000111111111122222222223333333333. + ...123456789012345678901234567890123456789. */ + const char *content + = ("int test (int *p)\n" /* line 1. */ + "{\n" /* line 2. */ + " if (p)\n" /* line 3. */ + " return 0;\n" /* line 4. */ + " return *p;\n" /* line 5. */ + "}\n"); /* line 6. */ + + control_flow_test t (SELFTEST_LOCATION, case_, content); + + const location_t conditional = t.get_line_and_column (3, 7); + const location_t cfg_dest = t.get_line_and_column (5, 10); + + test_diagnostic_path path (event_pp); + path.add_event (conditional, nullptr, 0, + "following %qs branch (when %qs is NULL)...", + "false", "p"); + path.connect_to_next_event (); + + path.add_event (cfg_dest, nullptr, 0, + "...to here"); + path.add_event (cfg_dest, nullptr, 0, + "dereference of NULL %qs", + "p"); + + if (!path_events_have_column_data_p (path)) + return; + + + { + test_diagnostic_context dc; + dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII); + dc.m_source_printing.show_event_links_p = true; + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, true); + print_path_summary_as_text (summary, text_output, false); + ASSERT_STREQ + (" events 1-3\n" + "FILENAME:3:7:\n" + " if (p)\n" + " ^\n" + " |\n" + " (1) following `false' branch (when `p' is NULL)... ->-+\n" + " |\n" + "FILENAME:5:10:\n" + " |\n" + "+------------------------------------------------------------+\n" + "| return *p;\n" + "| ~\n" + "| |\n" + "+-------->(2) ...to here\n" + " (3) dereference of NULL `p'\n", + pp_formatted_text (text_output.get_printer ())); + } + { + test_diagnostic_context dc; + dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII); + dc.m_source_printing.show_event_links_p = false; + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, true); + print_path_summary_as_text (summary, text_output, false); + ASSERT_STREQ + (" events 1-3\n" + "FILENAME:3:7:\n" + " if (p)\n" + " ^\n" + " |\n" + " (1) following `false' branch (when `p' is NULL)...\n" + "FILENAME:5:10:\n" + " return *p;\n" + " ~\n" + " |\n" + " (2) ...to here\n" + " (3) dereference of NULL `p'\n", + pp_formatted_text (text_output.get_printer ())); + } + { + test_diagnostic_context dc; + dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII); + dc.m_source_printing.show_line_numbers_p = true; + dc.m_source_printing.show_event_links_p = true; + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, true); + print_path_summary_as_text (summary, text_output, false); + ASSERT_STREQ + (" events 1-3\n" + "FILENAME:3:7:\n" + " 3 | if (p)\n" + " | ^\n" + " | |\n" + " | (1) following `false' branch (when `p' is NULL)... ->-+\n" + " | |\n" + " | |\n" + " |+------------------------------------------------------------+\n" + " 4 || return 0;\n" + " 5 || return *p;\n" + " || ~\n" + " || |\n" + " |+-------->(2) ...to here\n" + " | (3) dereference of NULL `p'\n", + pp_formatted_text (text_output.get_printer ())); + } + { + test_diagnostic_context dc; + dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII); + dc.m_source_printing.show_line_numbers_p = true; + dc.m_source_printing.show_event_links_p = false; + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, true); + print_path_summary_as_text (summary, text_output, false); + ASSERT_STREQ + (" events 1-3\n" + "FILENAME:3:7:\n" + " 3 | if (p)\n" + " | ^\n" + " | |\n" + " | (1) following `false' branch (when `p' is NULL)...\n" + " 4 | return 0;\n" + " 5 | return *p;\n" + " | ~\n" + " | |\n" + " | (2) ...to here\n" + " | (3) dereference of NULL `p'\n", + pp_formatted_text (text_output.get_printer ())); + } + { + test_diagnostic_context dc; + dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE); + dc.m_source_printing.show_event_links_p = true; + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, true); + print_path_summary_as_text (summary, text_output, false); + ASSERT_STREQ + (" events 1-3\n" + "FILENAME:3:7:\n" + " if (p)\n" + " ^\n" + " |\n" + " (1) following `false' branch (when `p' is NULL)... ─>─┐\n" + " │\n" + "FILENAME:5:10:\n" + " │\n" + "┌────────────────────────────────────────────────────────────┘\n" + "│ return *p;\n" + "│ ~\n" + "│ |\n" + "└────────>(2) ...to here\n" + " (3) dereference of NULL `p'\n", + pp_formatted_text (text_output.get_printer ())); + } + { + test_diagnostic_context dc; + dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE); + dc.m_source_printing.show_event_links_p = true; + dc.m_source_printing.show_line_numbers_p = true; + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, true); + print_path_summary_as_text (summary, text_output, false); + ASSERT_STREQ + (" events 1-3\n" + "FILENAME:3:7:\n" + " 3 | if (p)\n" + " | ^\n" + " | |\n" + " | (1) following `false' branch (when `p' is NULL)... ─>─┐\n" + " | │\n" + " | │\n" + " |┌────────────────────────────────────────────────────────────┘\n" + " 4 |│ return 0;\n" + " 5 |│ return *p;\n" + " |│ ~\n" + " |│ |\n" + " |└────────>(2) ...to here\n" + " | (3) dereference of NULL `p'\n", + pp_formatted_text (text_output.get_printer ())); + } +} + +/* Complex example involving a backedge. */ + +static void +test_control_flow_2 (const line_table_case &case_, + pretty_printer *event_pp) +{ + /* Create a tempfile and write some text to it. + ...000000000111111111122222222223333333333. + ...123456789012345678901234567890123456789. */ + const char *content + = ("int for_loop_noop_next (struct node *n)\n" /* <--------- line 1. */ + "{\n" /* <----------------------------------------------- line 2. */ + " int sum = 0;\n" /* <---------------------------------- line 3. */ + " for (struct node *iter = n; iter; iter->next)\n" /* <- line 4. */ + " sum += n->val;\n" /* <------------------------------ line 5. */ + " return sum;\n" /* <----------------------------------- line 6. */ + "}\n"); /* <-------------------------------------------- line 7. */ + /* Adapted from infinite-loop-linked-list.c where + "iter->next" should be "iter = iter->next". */ + + control_flow_test t (SELFTEST_LOCATION, case_, content); + + const location_t iter_test = t.get_line_and_columns (4, 31, 34); + const location_t loop_body_start = t.get_line_and_columns (5, 12, 17); + const location_t loop_body_end = t.get_line_and_columns (5, 5, 9, 17); + + test_diagnostic_path path (event_pp); + path.add_event (iter_test, nullptr, 0, "infinite loop here"); + + path.add_event (iter_test, nullptr, 0, "looping from here..."); + path.connect_to_next_event (); + + path.add_event (loop_body_start, nullptr, 0, "...to here"); + + path.add_event (loop_body_end, nullptr, 0, "looping back..."); + path.connect_to_next_event (); + + path.add_event (iter_test, nullptr, 0, "...to here"); + + if (!path_events_have_column_data_p (path)) + return; + + { + test_diagnostic_context dc; + dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII); + dc.m_source_printing.show_event_links_p = true; + dc.m_source_printing.show_line_numbers_p = true; + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, true); + print_path_summary_as_text (summary, text_output, false); + ASSERT_STREQ + (" events 1-3\n" + "FILENAME:4:31:\n" + " 4 | for (struct node *iter = n; iter; iter->next)\n" + " | ^~~~\n" + " | |\n" + " | (1) infinite loop here\n" + " | (2) looping from here... ->-+\n" + " | |\n" + " | |\n" + " |+----------------------------------------------------------+\n" + " 5 || sum += n->val;\n" + " || ~~~~~~ \n" + " || |\n" + " |+---------->(3) ...to here\n" + /* We need to start an new event_range here as event (4) is to the + left of event (3), and thus (4) would mess up the in-edge to (3). */ + " event 4\n" + " 5 | sum += n->val;\n" + " | ~~~~^~~~~~~~~\n" + " | |\n" + " | (4) looping back... ->-+\n" + " | |\n" + /* We need to start an new event_range here as event (4) with an + out-edge is on a later line (line 5) than its destination event (5), + on line 4. */ + " event 5\n" + " | |\n" + " |+-------------------------------+\n" + " 4 || for (struct node *iter = n; iter; iter->next)\n" + " || ^~~~\n" + " || |\n" + " |+----------------------------->(5) ...to here\n", + pp_formatted_text (text_output.get_printer ())); + } +} + +/* Complex example involving a backedge and both an in-edge and out-edge + on the same line. */ + +static void +test_control_flow_3 (const line_table_case &case_, + pretty_printer *event_pp) +{ + /* Create a tempfile and write some text to it. + ...000000000111111111122222222223333333333. + ...123456789012345678901234567890123456789. */ + const char *content + = ("void test_missing_comparison_in_for_condition_1 (int n)\n" + "{\n" /* <------------------------- line 2. */ + " for (int i = 0; n; i++)\n" /* <- line 3. */ + " {\n" /* <--------------------- line 4. */ + " }\n" /* <--------------------- line 5. */ + "}\n"); /* <----------------------- line 6. */ + /* Adapted from infinite-loop-1.c where the condition should have been + "i < n", rather than just "n". */ + + control_flow_test t (SELFTEST_LOCATION, case_, content); + + const location_t iter_test = t.get_line_and_column (3, 19); + const location_t iter_next = t.get_line_and_columns (3, 22, 24); + + test_diagnostic_path path (event_pp); + path.add_event (iter_test, nullptr, 0, "infinite loop here"); + + path.add_event (iter_test, nullptr, 0, "looping from here..."); + path.connect_to_next_event (); + + path.add_event (iter_next, nullptr, 0, "...to here"); + + path.add_event (iter_next, nullptr, 0, "looping back..."); + path.connect_to_next_event (); + + path.add_event (iter_test, nullptr, 0, "...to here"); + + if (!path_events_have_column_data_p (path)) + return; + + { + test_diagnostic_context dc; + dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII); + dc.m_source_printing.show_event_links_p = true; + dc.m_source_printing.show_line_numbers_p = true; + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, true); + print_path_summary_as_text (summary, text_output, false); + ASSERT_STREQ + (" events 1-2\n" + "FILENAME:3:19:\n" + " 3 | for (int i = 0; n; i++)\n" + " | ^\n" + " | |\n" + " | (1) infinite loop here\n" + " | (2) looping from here... ->-+\n" + " | |\n" + " events 3-4\n" + " | |\n" + " |+----------------------------------------------+\n" + " 3 || for (int i = 0; n; i++)\n" + " || ^~~\n" + " || |\n" + " |+-------------------->(3) ...to here\n" + " | (4) looping back... ->-+\n" + " | |\n" + /* We need to start an new event_range here as event (4) with an + out-edge is on the same line as its destination event (5), but + to the right, which we can't handle as a single event_range. */ + " event 5\n" + " | |\n" + " |+--------------------------------------------+\n" + " 3 || for (int i = 0; n; i++)\n" + " || ^\n" + " || |\n" + " |+----------------->(5) ...to here\n", + pp_formatted_text (text_output.get_printer ())); + } +} + +/* Implementation of ASSERT_CFG_EDGE_PATH_STREQ. */ + +static void +assert_cfg_edge_path_streq (const location &loc, + pretty_printer *event_pp, + const location_t src_loc, + const location_t dst_loc, + const char *expected_str) +{ + test_diagnostic_path path (event_pp); + path.add_event (src_loc, nullptr, 0, "from here..."); + path.connect_to_next_event (); + + path.add_event (dst_loc, nullptr, 0, "...to here"); + + if (!path_events_have_column_data_p (path)) + return; + + test_diagnostic_context dc; + dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII); + dc.m_source_printing.show_event_links_p = true; + dc.m_source_printing.show_line_numbers_p = true; + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, true); + print_path_summary_as_text (summary, text_output, false); + ASSERT_STREQ_AT (loc, expected_str, + pp_formatted_text (text_output.get_printer ())); +} + +/* Assert that if we make a path with an event with "from here..." at SRC_LOC + leading to an event "...to here" at DST_LOC that we print the path + as EXPECTED_STR. */ + +#define ASSERT_CFG_EDGE_PATH_STREQ(SRC_LOC, DST_LOC, EXPECTED_STR) \ + assert_cfg_edge_path_streq ((SELFTEST_LOCATION), (event_pp), \ + (SRC_LOC), (DST_LOC), (EXPECTED_STR)) + +/* Various examples of edge, trying to cover all combinations of: + - relative x positive of src label and dst label + - relative y position of labels: + - on same line + - on next line + - on line after next + - big gap, where src is before dst + - big gap, where src is after dst + and other awkward cases. */ + +static void +test_control_flow_4 (const line_table_case &case_, + pretty_printer *event_pp) +{ + std::string many_lines; + for (int i = 1; i <= 100; i++) + /* ............000000000111 + ............123456789012. */ + many_lines += "LHS RHS\n"; + control_flow_test t (SELFTEST_LOCATION, case_, many_lines.c_str ()); + + /* Same line. */ + { + /* LHS -> RHS. */ + ASSERT_CFG_EDGE_PATH_STREQ + (t.get_line_and_columns (3, 1, 3), + t.get_line_and_columns (3, 10, 12), + (" event 1\n" + "FILENAME:3:1:\n" + " 3 | LHS RHS\n" + " | ^~~\n" + " | |\n" + " | (1) from here... ->-+\n" + " | |\n" + " event 2\n" + " | |\n" + " |+--------------------+\n" + " 3 ||LHS RHS\n" + " || ^~~\n" + " || |\n" + " |+-------->(2) ...to here\n")); + + /* RHS -> LHS. */ + ASSERT_CFG_EDGE_PATH_STREQ + (t.get_line_and_columns (3, 10, 12), + t.get_line_and_columns (3, 1, 3), + (" event 1\n" + "FILENAME:3:10:\n" + " 3 | LHS RHS\n" + " | ^~~\n" + " | |\n" + " | (1) from here... ->-+\n" + " | |\n" + " event 2\n" + " | |\n" + " |+-----------------------------+\n" + " 3 ||LHS RHS\n" + " ||^~~\n" + " |||\n" + " |+(2) ...to here\n")); + } + + /* Next line. */ + { + /* LHS -> RHS. */ + ASSERT_CFG_EDGE_PATH_STREQ + (t.get_line_and_columns (3, 1, 3), + t.get_line_and_columns (4, 5, 7), + (" events 1-2\n" + "FILENAME:3:1:\n" + " 3 | LHS RHS\n" + " | ^~~\n" + " | |\n" + " | (1) from here... ->-+\n" + " | |\n" + " | |\n" + " |+--------------------+\n" + " 4 ||LHS RHS\n" + " || ~~~\n" + " || |\n" + " |+--->(2) ...to here\n")); + + /* RHS -> LHS. */ + ASSERT_CFG_EDGE_PATH_STREQ + (t.get_line_and_columns (3, 10, 12), + t.get_line_and_columns (4, 1, 3), + (" events 1-2\n" + "FILENAME:3:10:\n" + " 3 | LHS RHS\n" + " | ^~~\n" + " | |\n" + " | (1) from here... ->-+\n" + " | |\n" + " | |\n" + " |+-----------------------------+\n" + " 4 ||LHS RHS\n" + " ||~~~ \n" + " |||\n" + " |+(2) ...to here\n")); + } + + /* Line after next. */ + { + /* LHS -> RHS. */ + ASSERT_CFG_EDGE_PATH_STREQ + (t.get_line_and_columns (3, 1, 3), + t.get_line_and_columns (5, 10, 12), + (" events 1-2\n" + "FILENAME:3:1:\n" + " 3 | LHS RHS\n" + " | ^~~\n" + " | |\n" + " | (1) from here... ->-+\n" + " | |\n" + " | |\n" + " |+--------------------+\n" + " 4 ||LHS RHS\n" + " 5 ||LHS RHS\n" + " || ~~~\n" + " || |\n" + " |+-------->(2) ...to here\n")); + + /* RHS -> LHS. */ + ASSERT_CFG_EDGE_PATH_STREQ + (t.get_line_and_columns (3, 10, 12), + t.get_line_and_columns (5, 1, 3), + (" events 1-2\n" + "FILENAME:3:10:\n" + " 3 | LHS RHS\n" + " | ^~~\n" + " | |\n" + " | (1) from here... ->-+\n" + " | |\n" + " | |\n" + " |+-----------------------------+\n" + " 4 ||LHS RHS\n" + " 5 ||LHS RHS\n" + " ||~~~ \n" + " |||\n" + " |+(2) ...to here\n")); + } + + /* Big gap, increasing line number. */ + { + /* LHS -> RHS. */ + ASSERT_CFG_EDGE_PATH_STREQ + (t.get_line_and_columns (3, 1, 3), + t.get_line_and_columns (97, 10, 12), + (" events 1-2\n" + "FILENAME:3:1:\n" + " 3 | LHS RHS\n" + " | ^~~\n" + " | |\n" + " | (1) from here... ->-+\n" + " | |\n" + "......\n" + " | |\n" + " |+--------------------+\n" + " 97 ||LHS RHS\n" + " || ~~~\n" + " || |\n" + " |+-------->(2) ...to here\n")); + + /* RHS -> LHS. */ + ASSERT_CFG_EDGE_PATH_STREQ + (t.get_line_and_columns (3, 10, 12), + t.get_line_and_columns (97, 1, 3), + (" events 1-2\n" + "FILENAME:3:10:\n" + " 3 | LHS RHS\n" + " | ^~~\n" + " | |\n" + " | (1) from here... ->-+\n" + " | |\n" + "......\n" + " | |\n" + " |+-----------------------------+\n" + " 97 ||LHS RHS\n" + " ||~~~ \n" + " |||\n" + " |+(2) ...to here\n")); + } + + /* Big gap, decreasing line number. */ + { + /* LHS -> RHS. */ + ASSERT_CFG_EDGE_PATH_STREQ + (t.get_line_and_columns (97, 1, 3), + t.get_line_and_columns (3, 10, 12), + (" event 1\n" + "FILENAME:97:1:\n" + " 97 | LHS RHS\n" + " | ^~~\n" + " | |\n" + " | (1) from here... ->-+\n" + " | |\n" + " event 2\n" + " | |\n" + " |+--------------------+\n" + " 3 ||LHS RHS\n" + " || ^~~\n" + " || |\n" + " |+-------->(2) ...to here\n")); + + /* RHS -> LHS. */ + ASSERT_CFG_EDGE_PATH_STREQ + (t.get_line_and_columns (97, 10, 12), + t.get_line_and_columns (3, 1, 3), + (" event 1\n" + "FILENAME:97:10:\n" + " 97 | LHS RHS\n" + " | ^~~\n" + " | |\n" + " | (1) from here... ->-+\n" + " | |\n" + " event 2\n" + " | |\n" + " |+-----------------------------+\n" + " 3 ||LHS RHS\n" + " ||^~~\n" + " |||\n" + " |+(2) ...to here\n")); + } + + /* Unknown src. */ + { + ASSERT_CFG_EDGE_PATH_STREQ + (UNKNOWN_LOCATION, + t.get_line_and_columns (3, 10, 12), + (" event 1\n" + " (1): from here...\n" + " event 2\n" + "FILENAME:3:10:\n" + " 3 | LHS RHS\n" + " | ^~~\n" + " | |\n" + " |+-------->(2) ...to here\n")); + } + + /* Unknown dst. */ + { + ASSERT_CFG_EDGE_PATH_STREQ + (t.get_line_and_columns (3, 1, 3), + UNKNOWN_LOCATION, + (" event 1\n" + "FILENAME:3:1:\n" + " 3 | LHS RHS\n" + " | ^~~\n" + " | |\n" + " | (1) from here... ->-+\n" + " | |\n" + " event 2\n" + "FILENAME:\n" + " (2): ...to here\n")); + } +} + +/* Another complex example, adapted from data-model-20.c. */ + +static void +test_control_flow_5 (const line_table_case &case_, + pretty_printer *event_pp) +{ + /* Create a tempfile and write some text to it. + ...000000000111111111122222222223333333333444444444455555555556666666666. + ...123456789012345678901234567890123456789012345678901234567890123456789. */ + const char *content + = (" if ((arr = (struct foo **)malloc(n * sizeof(struct foo *))) == NULL)\n" + " return NULL;\n" /* <------------------------- line 2. */ + "\n" /* <----------------------------------------- line 3. */ + " for (i = 0; i < n; i++) {\n" /* <-------------- line 4. */ + " if ((arr[i] = (struct foo *)malloc(sizeof(struct foo))) == NULL) {\n"); + + control_flow_test t (SELFTEST_LOCATION, case_, content); + + test_diagnostic_path path (event_pp); + /* (1) */ + path.add_event (t.get_line_and_column (1, 6), nullptr, 0, + "following %qs branch (when %qs is non-NULL)...", + "false", "arr"); + path.connect_to_next_event (); + + /* (2) */ + path.add_event (t.get_line_and_columns (4, 8, 10, 12), nullptr, 0, + "...to here"); + + /* (3) */ + path.add_event (t.get_line_and_columns (4, 15, 17, 19), nullptr, 0, + "following %qs branch (when %qs)...", + "true", "i < n"); + path.connect_to_next_event (); + + /* (4) */ + path.add_event (t.get_line_and_column (5, 13), nullptr, 0, + "...to here"); + + /* (5) */ + path.add_event (t.get_line_and_columns (5, 33, 58), nullptr, 0, + "allocated here"); + + if (!path_events_have_column_data_p (path)) + return; + + { + test_diagnostic_context dc; + dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII); + dc.m_source_printing.show_event_links_p = true; + dc.m_source_printing.show_line_numbers_p = true; + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, true); + print_path_summary_as_text (summary, text_output, false); + ASSERT_STREQ + (" events 1-5\n" + "FILENAME:1:6:\n" + " 1 | if ((arr = (struct foo **)malloc(n * sizeof(struct foo *))) == NULL)\n" + " | ^\n" + " | |\n" + " | (1) following `false' branch (when `arr' is non-NULL)... ->-+\n" + " | |\n" + "......\n" + " | |\n" + " |+-----------------------------------------------------------------+\n" + " 4 || for (i = 0; i < n; i++) {\n" + " || ~~~~~ ~~~~~\n" + " || | |\n" + " || | (3) following `true' branch (when `i < n')... ->-+\n" + " |+-------->(2) ...to here |\n" + " | |\n" + " | |\n" + " |+-----------------------------------------------------------------+\n" + " 5 || if ((arr[i] = (struct foo *)malloc(sizeof(struct foo))) == NULL) {\n" + " || ~ ~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + " || | |\n" + " |+----------->(4) ...to here (5) allocated here\n", + pp_formatted_text (text_output.get_printer ())); + } +} + +/* Another complex example, adapted from loop-3.c. */ + +static void +test_control_flow_6 (const line_table_case &case_, + pretty_printer *event_pp) +{ + /* Create a tempfile and write some text to it. + ...000000000111111111122222222223333333. + ...123456789012345678901234567890123456. */ + const char *content + = ("#include <stdlib.h>\n" /* <------------------ line 1. */ + "\n" /* <------------------------------------- line 2. */ + "void test(int c)\n" /* <--------------------- line 3. */ + "{\n" /* <------------------------------------ line 4. */ + " int i;\n" /* <----------------------------- line 5. */ + " char *buffer = (char*)malloc(256);\n" /* <- line 6. */ + "\n" /* <------------------------------------- line 7. */ + " for (i=0; i<255; i++) {\n" /* <------------ line 8. */ + " buffer[i] = c;\n" /* <------------------- line 9. */ + "\n" /* <------------------------------------- line 10. */ + " free(buffer);\n" /* <-------------------- line 11. */ + " }\n"); /* <-------------------------------- line 12. */ + + control_flow_test t (SELFTEST_LOCATION, case_, content); + + test_diagnostic_path path (event_pp); + /* (1) */ + path.add_event (t.get_line_and_columns (6, 25, 35), nullptr, 0, + "allocated here"); + + /* (2) */ + path.add_event (t.get_line_and_columns (8, 13, 14, 17), nullptr, 0, + "following %qs branch (when %qs)...", + "true", "i <= 254"); + path.connect_to_next_event (); + + /* (3) */ + path.add_event (t.get_line_and_columns (9, 5, 15, 17), nullptr, 0, + "...to here"); + + /* (4) */ + path.add_event (t.get_line_and_columns (8, 13, 14, 17), nullptr, 0, + "following %qs branch (when %qs)...", + "true", "i <= 254"); + path.connect_to_next_event (); + + /* (5) */ + path.add_event (t.get_line_and_columns (9, 5, 15, 17), nullptr, 0, + "...to here"); + + if (!path_events_have_column_data_p (path)) + return; + + { + test_diagnostic_context dc; + dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII); + dc.m_source_printing.show_event_links_p = true; + dc.m_source_printing.show_line_numbers_p = true; + diagnostic_text_output_format text_output (dc); + path_print_policy policy (text_output); + path_summary summary (policy, *event_pp, path, true); + print_path_summary_as_text (summary, text_output, false); + ASSERT_STREQ + (" events 1-3\n" + "FILENAME:6:25:\n" + " 6 | char *buffer = (char*)malloc(256);\n" + " | ^~~~~~~~~~~\n" + " | |\n" + " | (1) allocated here\n" + " 7 | \n" + " 8 | for (i=0; i<255; i++) {\n" + " | ~~~~~ \n" + " | |\n" + " | (2) following `true' branch (when `i <= 254')... ->-+\n" + " | |\n" + " | |\n" + " |+-----------------------------------------------------------------+\n" + " 9 || buffer[i] = c;\n" + " || ~~~~~~~~~~~~~ \n" + " || |\n" + " |+------------->(3) ...to here\n" + " events 4-5\n" + " 8 | for (i=0; i<255; i++) {\n" + " | ~^~~~\n" + " | |\n" + " | (4) following `true' branch (when `i <= 254')... ->-+\n" + " | |\n" + " | |\n" + " |+-----------------------------------------------------------------+\n" + " 9 || buffer[i] = c;\n" + " || ~~~~~~~~~~~~~\n" + " || |\n" + " |+------------->(5) ...to here\n", + pp_formatted_text (text_output.get_printer ())); + } +} + +static void +control_flow_tests (const line_table_case &case_) +{ + pretty_printer pp; + pp_show_color (&pp) = false; + + test_control_flow_1 (case_, &pp); + test_control_flow_2 (case_, &pp); + test_control_flow_3 (case_, &pp); + test_control_flow_4 (case_, &pp); + test_control_flow_5 (case_, &pp); + test_control_flow_6 (case_, &pp); +} + +/* Run all of the selftests within this file. */ + +void +diagnostic_path_output_cc_tests () +{ + pretty_printer pp; + pp_show_color (&pp) = false; + + auto_fix_quotes fix_quotes; + test_empty_path (&pp); + test_intraprocedural_path (&pp); + test_interprocedural_path_1 (&pp); + test_interprocedural_path_2 (&pp); + test_recursion (&pp); + for_each_line_table_case (control_flow_tests); +} + +} // namespace selftest + +#if __GNUC__ >= 10 +# pragma GCC diagnostic pop +#endif + +#endif /* #if CHECKING_P */ |