blob: a3ac9a0519ac9eaed8c0fd1c61fcd70bec237885 [file] [log] [blame]
/* 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 "diagnostics/macro-unwinding.h"
#include "intl.h"
#include "diagnostics/paths.h"
#include "gcc-rich-location.h"
#include "diagnostics/color.h"
#include "diagnostics/event-id.h"
#include "diagnostics/source-printing-effects.h"
#include "pretty-print-markup.h"
#include "selftest.h"
#include "diagnostics/selftest-context.h"
#include "diagnostics/selftest-paths.h"
#include "text-art/theme.h"
#include "diagnostics/text-sink.h"
#include "diagnostics/html-sink.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 {
using namespace diagnostics;
using namespace diagnostics::paths;
/* A bundle of state for printing a path. */
class path_print_policy
{
public:
path_print_policy (const diagnostics::text_sink &text_output)
: m_source_policy (text_output.get_context ())
{
}
path_print_policy (const diagnostics::context &dc)
: m_source_policy (dc)
{
}
text_art::theme *
get_diagram_theme () const
{
return m_source_policy.get_diagram_theme ();
}
const diagnostics::source_print_policy &
get_source_policy () const { return m_source_policy; }
private:
diagnostics::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 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 event &ev = m_path.get_event (event_idx);
const event::meaning meaning (ev.get_meaning ());
auto pp = m_ref_pp.clone ();
pp_show_color (pp.get ()) = m_colorize;
event_id_t event_id (event_idx);
pp_printf (pp.get (), "%@", &event_id);
pp_space (pp.get ());
if (meaning.m_verb == 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 (), " ");
}
ev.print_desc (*pp.get ());
label_text result
= label_text::take (xstrdup (pp_formatted_text (pp.get ())));
return result;
}
const diagnostics::label_effects *
get_effects (unsigned /*range_idx*/) const final override
{
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 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 event &ev = m_path_label.get_event (range_idx);
return ev.connect_to_next_event_p ();
}
private:
const path_label &m_path_label;
};
const event &get_event (unsigned range_idx) const
{
unsigned event_idx = m_start_idx + range_idx;
return m_path.get_event (event_idx);
}
const 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 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 path &p,
const event &e1,
unsigned ev1_idx,
const event &e2,
unsigned ev2_idx,
bool check_locations)
{
if (e1.get_thread_id () != e2.get_thread_id ())
return false;
if (!p.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 path &path_,
const logical_locations::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 path &m_path;
const logical_locations::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 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_locations::key 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_locations::key 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_locations::key logical_loc,
int stack_depth,
const logical_locations::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 ("td");
}
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 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 path &path_,
const pretty_printer &ref_pp,
unsigned start_idx,
const 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 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 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 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,
diagnostics::text_sink &text_output,
diagnostics::source_effect_info *effect_info)
{
location_t initial_loc = m_initial_event.get_location ();
diagnostics::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.get_source_printing_options ().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))
{
diagnostics::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 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, diagnostics::kind::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,
diagnostics::context &dc,
diagnostics::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.get_source_printing_options ().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))
{
diagnostics::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 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_from_pp (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.get_source_printing_options (),
&m_richloc, diagnostics::kind::path, xp,
effect_info, event_label_writer);
// TODO: show macro expansions
}
const path &m_path;
const event &m_initial_event;
logical_locations::key 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;
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 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 path &path_,
bool check_rich_locations,
bool colorize = false,
bool show_event_links = true);
const logical_locations::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 (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_locations::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<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 path &path_,
thread_id_t tid)
{
if (per_thread_summary **slot = m_thread_id_to_events.get (tid))
return **slot;
const 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 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 = nullptr;
for (unsigned idx = 0; idx < num_events; idx++)
{
const event &ev = path_.get_event (idx);
const thread_id_t thread_id = ev.get_thread_id ();
per_thread_summary &pts
= get_or_create_events_for_thread_id (path_, thread_id);
pts.update_depth_limits (ev.get_stack_depth ());
if (cur_event_range)
if (cur_event_range->maybe_add_event (policy,
ev,
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, ev, 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 = &ev;
}
}
/* 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 (diagnostics::text_sink &text_output,
pretty_printer *pp,
const logical_locations::manager &logical_loc_mgr,
event_range *range,
diagnostics::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 (diagnostics::context &dc,
xml::printer &xp,
html_label_writer *event_label_writer,
event_range *range,
diagnostics::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,
diagnostics::text_sink &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. */
diagnostics::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,
diagnostics::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_locations::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_locations::key 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 == nullptr)
{
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,
nullptr,
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_from_pp (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_from_pp (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. */
diagnostics::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 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 event &m_event;
};
/* Print PATH according to the context's path_format. */
void
diagnostics::text_sink::print_path (const 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 event &ev = path_.get_event (i);
element_event_desc e_event_desc (ev);
diagnostic_event_id_t event_id (i);
if (get_context ().show_path_depths_p ())
{
int stack_depth = ev.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_locations::key logical_loc
= ev.get_logical_location ())
{
label_text name
(logical_loc_mgr.get_name_for_path_output (logical_loc));
inform (ev.get_location (),
"%@ %e (fndecl %qs, depth %i)",
&event_id, &e_event_desc,
name.get (), stack_depth);
}
else
inform (ev.get_location (),
"%@ %e (depth %i)",
&event_id, &e_event_desc,
stack_depth);
}
else
inform (ev.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, nullptr);
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
diagnostics::print_path_as_html (xml::printer &xp,
const path &path_,
context &dc,
html_label_writer *event_label_writer,
const source_print_policy &dspp)
{
path_print_policy policy (dc);
const bool check_rich_locations = true;
const bool colorize = false;
const 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 diagnostics {
namespace paths {
namespace selftest {
using location = ::selftest::location;
using line_table_case = ::selftest::line_table_case;
using line_table_test = ::selftest::line_table_test;
using temp_source_file = ::selftest::temp_source_file;
using test_context = diagnostics::selftest::test_context;
/* 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 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)
{
logical_locations::selftest::test_manager logical_loc_mgr;
test_path path (logical_loc_mgr, event_pp);
ASSERT_FALSE (path.interprocedural_p ());
test_context dc;
text_sink 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)
{
logical_locations::selftest::test_manager logical_loc_mgr;
test_path path (logical_loc_mgr, 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 ());
selftest::test_context dc;
text_sink 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)
{
logical_locations::selftest::test_manager logical_loc_mgr;
test_path path (logical_loc_mgr, 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 ());
{
selftest::test_context dc;
text_sink 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 ()));
}
{
selftest::test_context dc;
dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
text_sink 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)
{
logical_locations::selftest::test_manager logical_loc_mgr;
test_path path (logical_loc_mgr, 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 ());
{
selftest::test_context dc;
text_sink 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 ()));
}
{
selftest::test_context dc;
dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
text_sink 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)
{
logical_locations::selftest::test_manager logical_loc_mgr;
test_path path (logical_loc_mgr, 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 ());
{
selftest::test_context dc;
dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
text_sink 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 ()));
}
{
selftest::test_context dc;
dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
text_sink 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 selftest::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);
logical_locations::selftest::test_manager logical_loc_mgr;
test_path path (logical_loc_mgr, 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;
{
selftest::test_context dc;
dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
dc.show_event_links (true);
text_sink 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 ()));
}
{
selftest::test_context dc;
dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
dc.show_event_links (false);
text_sink 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 ()));
}
{
selftest::test_context dc;
dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
dc.show_line_numbers (true);
dc.show_event_links (true);
text_sink 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 ()));
}
{
selftest::test_context dc;
dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
dc.show_line_numbers (true);
dc.show_event_links (false);
text_sink 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 ()));
}
{
selftest::test_context dc;
dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
dc.show_event_links (true);
text_sink 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 ()));
}
{
selftest::test_context dc;
dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE);
dc.show_event_links (true);
dc.show_line_numbers (true);
text_sink 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);
logical_locations::selftest::test_manager logical_loc_mgr;
test_path path (logical_loc_mgr, 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;
{
selftest::test_context dc;
dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
dc.show_event_links (true);
dc.show_line_numbers (true);
text_sink 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);
logical_locations::selftest::test_manager logical_loc_mgr;
test_path path (logical_loc_mgr, 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;
{
selftest::test_context dc;
dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
dc.show_event_links (true);
dc.show_line_numbers (true);
text_sink 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)
{
logical_locations::selftest::test_manager logical_loc_mgr;
test_path path (logical_loc_mgr, 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;
selftest::test_context dc;
dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
dc.show_event_links (true);
dc.show_line_numbers (true);
text_sink 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);
logical_locations::selftest::test_manager logical_loc_mgr;
test_path path (logical_loc_mgr, 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;
{
selftest::test_context dc;
dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
dc.show_event_links (true);
dc.show_line_numbers (true);
text_sink 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);
logical_locations::selftest::test_manager logical_loc_mgr;
test_path path (logical_loc_mgr, 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;
{
selftest::test_context dc;
dc.set_text_art_charset (DIAGNOSTICS_TEXT_ART_CHARSET_ASCII);
dc.show_event_links (true);
dc.show_line_numbers (true);
text_sink 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);
}
} // namespace diagnostics::paths::selftest
} // namespace diagnostics::paths
namespace selftest { // diagnostics::selftest
/* Run all of the selftests within this file. */
void
paths_output_cc_tests ()
{
pretty_printer pp;
pp_show_color (&pp) = false;
::selftest::auto_fix_quotes fix_quotes;
diagnostics::paths::selftest::test_empty_path (&pp);
diagnostics::paths::selftest::test_intraprocedural_path (&pp);
diagnostics::paths::selftest::test_interprocedural_path_1 (&pp);
diagnostics::paths::selftest::test_interprocedural_path_2 (&pp);
diagnostics::paths::selftest::test_recursion (&pp);
for_each_line_table_case (diagnostics::paths::selftest::control_flow_tests);
}
} // namespace diagnostics::selftest
} // namespace diagnostics
#if __GNUC__ >= 10
# pragma GCC diagnostic pop
#endif
#endif /* #if CHECKING_P */