+2020-01-10 David Malcolm <dmalcolm@redhat.com>
+
+ * Makefile.in (OBJS): Add tree-diagnostic-path.o.
+ * common.opt (fdiagnostics-path-format=): New option.
+ (diagnostic_path_format): New enum.
+ (fdiagnostics-show-path-depths): New option.
+ * coretypes.h (diagnostic_event_id_t): New forward decl.
+ * diagnostic-color.c (color_dict): Add "path".
+ * diagnostic-event-id.h: New file.
+ * diagnostic-format-json.cc (json_from_expanded_location): Make
+ non-static.
+ (json_end_diagnostic): Call context->make_json_for_path if it
+ exists and the diagnostic has a path.
+ (diagnostic_output_format_init): Clear context->print_path.
+ * diagnostic-path.h: New file.
+ * diagnostic-show-locus.c (colorizer::set_range): Special-case
+ when printing a run of events in a diagnostic_path so that they
+ all get the same color.
+ (layout::m_diagnostic_path_p): New field.
+ (layout::layout): Initialize it.
+ (layout::print_any_labels): Don't colorize the label text for an
+ event in a diagnostic_path.
+ (gcc_rich_location::add_location_if_nearby): Add
+ "restrict_to_current_line_spans" and "label" params. Pass the
+ former to layout.maybe_add_location_range; pass the latter
+ when calling add_range.
+ * diagnostic.c: Include "diagnostic-path.h".
+ (diagnostic_initialize): Initialize context->path_format and
+ context->show_path_depths.
+ (diagnostic_show_any_path): New function.
+ (diagnostic_path::interprocedural_p): New function.
+ (diagnostic_report_diagnostic): Call diagnostic_show_any_path.
+ (simple_diagnostic_path::num_events): New function.
+ (simple_diagnostic_path::get_event): New function.
+ (simple_diagnostic_path::add_event): New function.
+ (simple_diagnostic_event::simple_diagnostic_event): New ctor.
+ (simple_diagnostic_event::~simple_diagnostic_event): New dtor.
+ (debug): New overload taking a diagnostic_path *.
+ * diagnostic.def (DK_DIAGNOSTIC_PATH): New.
+ * diagnostic.h (enum diagnostic_path_format): New enum.
+ (json::value): New forward decl.
+ (diagnostic_context::path_format): New field.
+ (diagnostic_context::show_path_depths): New field.
+ (diagnostic_context::print_path): New callback field.
+ (diagnostic_context::make_json_for_path): New callback field.
+ (diagnostic_show_any_path): New decl.
+ (json_from_expanded_location): New decl.
+ * doc/invoke.texi (-fdiagnostics-path-format=): New option.
+ (-fdiagnostics-show-path-depths): New option.
+ (-fdiagnostics-color): Add "path" to description of default
+ GCC_COLORS; describe it.
+ (-fdiagnostics-format=json): Document how diagnostic paths are
+ represented in the JSON output format.
+ * gcc-rich-location.h (gcc_rich_location::add_location_if_nearby):
+ Add optional params "restrict_to_current_line_spans" and "label".
+ * opts.c (common_handle_option): Handle
+ OPT_fdiagnostics_path_format_ and
+ OPT_fdiagnostics_show_path_depths.
+ * pretty-print.c: Include "diagnostic-event-id.h".
+ (pp_format): Implement "%@" format code for printing
+ diagnostic_event_id_t *.
+ (selftest::test_pp_format): Add tests for "%@".
+ * selftest-run-tests.c (selftest::run_tests): Call
+ selftest::tree_diagnostic_path_cc_tests.
+ * selftest.h (selftest::tree_diagnostic_path_cc_tests): New decl.
+ * toplev.c (general_init): Initialize global_dc->path_format and
+ global_dc->show_path_depths.
+ * tree-diagnostic-path.cc: New file.
+ * tree-diagnostic.c (maybe_unwind_expanded_macro_loc): Make
+ non-static. Drop "diagnostic" param in favor of storing the
+ original value of "where" and re-using it.
+ (virt_loc_aware_diagnostic_finalizer): Update for dropped param of
+ maybe_unwind_expanded_macro_loc.
+ (tree_diagnostics_defaults): Initialize context->print_path and
+ context->make_json_for_path.
+ * tree-diagnostic.h (default_tree_diagnostic_path_printer): New
+ decl.
+ (default_tree_make_json_for_path): New decl.
+ (maybe_unwind_expanded_macro_loc): New decl.
+
2020-01-10 Jakub Jelinek <jakub@redhat.com>
PR tree-optimization/93210
tree-data-ref.o \
tree-dfa.o \
tree-diagnostic.o \
+ tree-diagnostic-path.o \
tree-dump.o \
tree-eh.o \
tree-emutls.o \
+2020-01-10 David Malcolm <dmalcolm@redhat.com>
+
+ * c-format.c (local_event_ptr_node): New.
+ (PP_FORMAT_CHAR_TABLE): Add entry for "%@".
+ (init_dynamic_diag_info): Initialize local_event_ptr_node.
+ * c-format.h (T_EVENT_PTR): New define.
+
2020-01-10 Martin Sebor <msebor@redhat.com>
PR c/93132
/* Initialized in init_dynamic_diag_info. */
static GTY(()) tree local_tree_type_node;
+static GTY(()) tree local_event_ptr_node;
static GTY(()) tree local_gimple_ptr_node;
static GTY(()) tree local_cgraph_node_ptr_node;
static GTY(()) tree locus;
{ "s", 1, STD_C89, { T89_C, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "pq", "cR", NULL }, \
{ "p", 1, STD_C89, { T89_V, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "q", "c", NULL }, \
{ "r", 1, STD_C89, { T89_C, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "//cR", NULL }, \
+ { "@", 1, STD_C89, { T_EVENT_PTR, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }, "", "\"", NULL }, \
{ "<", 0, STD_C89, NOARGUMENTS, "", "<", NULL }, \
{ ">", 0, STD_C89, NOARGUMENTS, "", ">", NULL }, \
{ "'" , 0, STD_C89, NOARGUMENTS, "", "", NULL }, \
|| local_cgraph_node_ptr_node == void_type_node)
local_cgraph_node_ptr_node = get_named_type ("cgraph_node");
+ /* Similar to the above but for diagnostic_event_id_t*. */
+ if (!local_event_ptr_node
+ || local_event_ptr_node == void_type_node)
+ local_event_ptr_node = get_named_type ("diagnostic_event_id_t");
+
static tree hwi;
if (!hwi)
#define T_V &void_type_node
#define T89_G { STD_C89, NULL, &local_gimple_ptr_node }
#define T_CGRAPH_NODE { STD_C89, NULL, &local_cgraph_node_ptr_node }
+#define T_EVENT_PTR { STD_C89, NULL, &local_event_ptr_node }
#define T89_T { STD_C89, NULL, &local_tree_type_node }
#define T89_V { STD_C89, NULL, T_V }
#define T_W &wchar_type_node
Common Var(flag_diagnostics_show_cwe) Init(1)
Print CWE identifiers for diagnostic messages, where available.
+fdiagnostics-path-format=
+Common Joined RejectNegative Var(flag_diagnostics_path_format) Enum(diagnostic_path_format) Init(DPF_INLINE_EVENTS)
+Specify how to print any control-flow path associated with a diagnostic.
+
+Enum
+Name(diagnostic_path_format) Type(int)
+
+EnumValue
+Enum(diagnostic_path_format) String(none) Value(DPF_NONE)
+
+EnumValue
+Enum(diagnostic_path_format) String(separate-events) Value(DPF_SEPARATE_EVENTS)
+
+EnumValue
+Enum(diagnostic_path_format) String(inline-events) Value(DPF_INLINE_EVENTS)
+
+fdiagnostics-show-path-depths
+Common Var(flag_diagnostics_show_path_depths) Init(0)
+Show stack depths of events in paths.
+
fdiagnostics-minimum-margin-width=
Common Joined UInteger Var(diagnostics_minimum_margin_width) Init(6)
Set minimum width of left margin of source code when showing source.
struct cl_option_handlers;
struct diagnostic_context;
class pretty_printer;
+class diagnostic_event_id_t;
template<typename T> struct array_traits;
{ "range2", SGR_SEQ (COLOR_FG_BLUE), 6, false },
{ "locus", SGR_SEQ (COLOR_BOLD), 5, false },
{ "quote", SGR_SEQ (COLOR_BOLD), 5, false },
+ { "path", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_CYAN), 4, false },
{ "fixit-insert", SGR_SEQ (COLOR_FG_GREEN), 12, false },
{ "fixit-delete", SGR_SEQ (COLOR_FG_RED), 12, false },
{ "diff-filename", SGR_SEQ (COLOR_BOLD), 13, false },
/* Parse GCC_COLORS. The default would look like:
GCC_COLORS='error=01;31:warning=01;35:note=01;36:\
- range1=32:range2=34:locus=01:quote=01:\
+ range1=32:range2=34:locus=01:quote=01:path=01;36:\
fixit-insert=32:fixit-delete=31:'\
diff-filename=01:diff-hunk=32:diff-delete=31:diff-insert=32:\
type-diff=01;32'
--- /dev/null
+/* A class for referring to events within a diagnostic_path.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_DIAGNOSTIC_EVENT_ID_H
+#define GCC_DIAGNOSTIC_EVENT_ID_H
+
+/* A class for referring to events within a diagnostic_path.
+
+ They are stored as 0-based offsets into the events, but
+ printed (e.g. via %@) as 1-based numbers.
+
+ For example, a 3-event path has event offsets 0, 1, and 2,
+ which would be shown to the user as "(1)", "(2)" and "(3)".
+
+ This has its own header so that pretty-print.c can use this
+ to implement "%@" without bringing in all of diagnostic_path
+ (which e.g. refers to "tree"). */
+
+class diagnostic_event_id_t
+{
+ public:
+ diagnostic_event_id_t () : m_index (UNKNOWN_EVENT_IDX) {}
+ diagnostic_event_id_t (int zero_based_idx) : m_index (zero_based_idx) {}
+
+ bool known_p () const { return m_index != UNKNOWN_EVENT_IDX; }
+
+ int one_based () const
+ {
+ gcc_assert (known_p ());
+ return m_index + 1;
+ }
+
+ private:
+ static const int UNKNOWN_EVENT_IDX = -1;
+ int m_index; // zero-based
+};
+
+/* A pointer to a diagnostic_event_id_t, for use with the "%@" format
+ code, which will print a 1-based representation for it, with suitable
+ colorization, e.g. "(1)".
+ The %@ format code requires that known_p be true for the event ID. */
+typedef diagnostic_event_id_t *diagnostic_event_id_ptr;
+
+#endif /* ! GCC_DIAGNOSTIC_EVENT_ID_H */
/* Generate a JSON object for LOC. */
-static json::object *
+json::value *
json_from_expanded_location (location_t loc)
{
expanded_location exploc = expand_location (loc);
json::object *metadata_obj = json_from_metadata (diagnostic->metadata);
diag_obj->set ("metadata", metadata_obj);
}
+
+ const diagnostic_path *path = richloc->get_path ();
+ if (path && context->make_json_for_path)
+ {
+ json::value *path_value = context->make_json_for_path (context, path);
+ diag_obj->set ("path", path_value);
+ }
}
/* No-op implementation of "begin_group_cb" for JSON output. */
context->begin_group_cb = json_begin_group;
context->end_group_cb = json_end_group;
context->final_cb = json_final_cb;
+ context->print_path = NULL; /* handled in json_end_diagnostic. */
/* The metadata is handled in JSON format, rather than as text. */
context->show_cwe = false;
--- /dev/null
+/* Paths through the code associated with a diagnostic.
+ Copyright (C) 2019-2020 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_DIAGNOSTIC_PATH_H
+#define GCC_DIAGNOSTIC_PATH_H
+
+#include "diagnostic.h" /* for ATTRIBUTE_GCC_DIAG. */
+#include "diagnostic-event-id.h"
+
+/* A diagnostic_path is an optional additional piece of metadata associated
+ with a diagnostic (via its rich_location).
+
+ It describes a sequence of events predicted by the compiler that
+ lead to the problem occurring, with their locations in the user's source,
+ and text descriptions.
+
+ For example, the following error has a 3-event path:
+
+ test.c: In function 'demo':
+ test.c:29:5: error: passing NULL as argument 1 to 'PyList_Append' which
+ requires a non-NULL parameter
+ 29 | PyList_Append(list, item);
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~
+ 'demo': events 1-3
+ |
+ | 25 | list = PyList_New(0);
+ | | ^~~~~~~~~~~~~
+ | | |
+ | | (1) when 'PyList_New' fails, returning NULL
+ | 26 |
+ | 27 | for (i = 0; i < count; i++) {
+ | | ~~~
+ | | |
+ | | (2) when 'i < count'
+ | 28 | item = PyLong_FromLong(random());
+ | 29 | PyList_Append(list, item);
+ | | ~~~~~~~~~~~~~~~~~~~~~~~~~
+ | | |
+ | | (3) when calling 'PyList_Append', passing NULL from (1) as argument 1
+ |
+
+ The diagnostic-printing code has consolidated the path into a single
+ run of events, since all the events are near each other and within the same
+ function; more complicated examples (such as interprocedural paths)
+ might be printed as multiple runs of events. */
+
+/* Abstract base classes, describing events within a path, and the paths
+ themselves. */
+
+/* One event within a diagnostic_path. */
+
+class diagnostic_event
+{
+ public:
+ virtual ~diagnostic_event () {}
+
+ virtual location_t get_location () const = 0;
+
+ virtual tree get_fndecl () const = 0;
+
+ /* Stack depth, so that consumers can visualizes the interprocedural
+ calls, returns, and frame nesting. */
+ virtual int get_stack_depth () const = 0;
+
+ /* Get a localized (and possibly colorized) description of this event. */
+ virtual label_text get_desc (bool can_colorize) const = 0;
+};
+
+/* Abstract base class for getting at a sequence of events. */
+
+class diagnostic_path
+{
+ public:
+ virtual ~diagnostic_path () {}
+ virtual unsigned num_events () const = 0;
+ virtual const diagnostic_event & get_event (int idx) const = 0;
+
+ bool interprocedural_p () const;
+};
+
+/* Concrete subclasses. */
+
+/* A simple implementation of diagnostic_event. */
+
+class simple_diagnostic_event : public diagnostic_event
+{
+ public:
+ simple_diagnostic_event (location_t loc, tree fndecl, int depth,
+ const char *desc);
+ ~simple_diagnostic_event ();
+
+ location_t get_location () const FINAL OVERRIDE { return m_loc; }
+ tree get_fndecl () const FINAL OVERRIDE { return m_fndecl; }
+ int get_stack_depth () const FINAL OVERRIDE { return m_depth; }
+ label_text get_desc (bool) const FINAL OVERRIDE
+ {
+ return label_text::borrow (m_desc);
+ }
+
+ private:
+ location_t m_loc;
+ tree m_fndecl;
+ int m_depth;
+ char *m_desc; // has been i18n-ed and formatted
+};
+
+/* A simple implementation of diagnostic_path, as a vector of
+ simple_diagnostic_event instances. */
+
+class simple_diagnostic_path : public diagnostic_path
+{
+ public:
+ simple_diagnostic_path (pretty_printer *event_pp)
+ : m_event_pp (event_pp) {}
+
+ unsigned num_events () const FINAL OVERRIDE;
+ const diagnostic_event & get_event (int idx) const FINAL OVERRIDE;
+
+ diagnostic_event_id_t add_event (location_t loc, tree fndecl, int depth,
+ const char *fmt, ...)
+ ATTRIBUTE_GCC_DIAG(5,6);
+
+ private:
+ auto_delete_vec<simple_diagnostic_event> m_events;
+
+ /* (for use by add_event). */
+ pretty_printer *m_event_pp;
+};
+
+extern void debug (diagnostic_path *path);
+
+#endif /* ! GCC_DIAGNOSTIC_PATH_H */
diagnostic_t diagnostic_kind);
~colorizer ();
- void set_range (int range_idx) { set_state (range_idx); }
+ void set_range (int range_idx)
+ {
+ /* Normally we emphasize the primary location, then alternate between
+ two colors for the secondary locations.
+ But if we're printing a run of events in a diagnostic path, that
+ makes no sense, so print all of them with the same colorization. */
+ if (m_diagnostic_kind == DK_DIAGNOSTIC_PATH)
+ set_state (0);
+ else
+ set_state (range_idx);
+ }
void set_normal_text () { set_state (STATE_NORMAL_TEXT); }
void set_fixit_insert () { set_state (STATE_FIXIT_INSERT); }
void set_fixit_delete () { set_state (STATE_FIXIT_DELETE); }
bool m_colorize_source_p;
bool m_show_labels_p;
bool m_show_line_numbers_p;
+ bool m_diagnostic_path_p;
auto_vec <layout_range> m_layout_ranges;
auto_vec <const fixit_hint *> m_fixit_hints;
auto_vec <line_span> m_line_spans;
m_colorize_source_p (context->colorize_source_p),
m_show_labels_p (context->show_labels_p),
m_show_line_numbers_p (context->show_line_numbers_p),
+ m_diagnostic_path_p (diagnostic_kind == DK_DIAGNOSTIC_PATH),
m_layout_ranges (richloc->get_num_locations ()),
m_fixit_hints (richloc->get_num_fixit_hints ()),
m_line_spans (1 + richloc->get_num_locations ()),
{
gcc_assert (column <= label->m_column);
move_to_column (&column, label->m_column, true);
- m_colorizer.set_range (label->m_state_idx);
+ /* Colorize the text, unless it's for events in a
+ diagnostic_path. */
+ if (!m_diagnostic_path_p)
+ m_colorizer.set_range (label->m_state_idx);
pp_string (m_pp, label->m_text.m_buffer);
m_colorizer.set_normal_text ();
column += label->m_display_width;
Otherwise return false. */
bool
-gcc_rich_location::add_location_if_nearby (location_t loc)
+gcc_rich_location::add_location_if_nearby (location_t loc,
+ bool restrict_to_current_line_spans,
+ const range_label *label)
{
/* Use the layout location-handling logic to sanitize LOC,
filtering it to the current line spans within a temporary
location_range loc_range;
loc_range.m_loc = loc;
loc_range.m_range_display_kind = SHOW_RANGE_WITHOUT_CARET;
- if (!layout.maybe_add_location_range (&loc_range, 0, true))
+ if (!layout.maybe_add_location_range (&loc_range, 0,
+ restrict_to_current_line_spans))
return false;
- add_range (loc);
+ add_range (loc, SHOW_RANGE_WITHOUT_CARET, label);
return true;
}
#include "diagnostic-color.h"
#include "diagnostic-url.h"
#include "diagnostic-metadata.h"
+#include "diagnostic-path.h"
#include "edit-context.h"
#include "selftest.h"
#include "selftest-diagnostic.h"
for (i = 0; i < rich_location::STATICALLY_ALLOCATED_RANGES; i++)
context->caret_chars[i] = '^';
context->show_cwe = false;
+ context->path_format = DPF_NONE;
+ context->show_path_depths = false;
context->show_option_requested = false;
context->abort_on_error = false;
context->show_column = false;
}
}
+/* If DIAGNOSTIC has a diagnostic_path and CONTEXT supports printing paths,
+ print the path. */
+
+void
+diagnostic_show_any_path (diagnostic_context *context,
+ diagnostic_info *diagnostic)
+{
+ const diagnostic_path *path = diagnostic->richloc->get_path ();
+ if (!path)
+ return;
+
+ if (context->print_path)
+ context->print_path (context, path);
+}
+
+/* Return true if the events in this path involve more than one
+ function, or false if it is purely intraprocedural. */
+
+bool
+diagnostic_path::interprocedural_p () const
+{
+ const unsigned num = num_events ();
+ for (unsigned i = 0; i < num; i++)
+ {
+ if (get_event (i).get_fndecl () != get_event (0).get_fndecl ())
+ return true;
+ if (get_event (i).get_stack_depth () != get_event (0).get_stack_depth ())
+ return true;
+ }
+ return false;
+}
+
void
default_diagnostic_starter (diagnostic_context *context,
diagnostic_info *diagnostic)
context->lock--;
+ diagnostic_show_any_path (context, diagnostic);
+
return true;
}
}
}
+/* Implementation of diagnostic_path::num_events vfunc for
+ simple_diagnostic_path: simply get the number of events in the vec. */
+
+unsigned
+simple_diagnostic_path::num_events () const
+{
+ return m_events.length ();
+}
+
+/* Implementation of diagnostic_path::get_event vfunc for
+ simple_diagnostic_path: simply return the event in the vec. */
+
+const diagnostic_event &
+simple_diagnostic_path::get_event (int idx) const
+{
+ return *m_events[idx];
+}
+
+/* Add an event to this path at LOC within function FNDECL at
+ stack depth DEPTH.
+
+ Use m_context's printer to format FMT, as the text of the new
+ event.
+
+ Return the id of the new event. */
+
+diagnostic_event_id_t
+simple_diagnostic_path::add_event (location_t loc, tree fndecl, int depth,
+ const char *fmt, ...)
+{
+ pretty_printer *pp = m_event_pp;
+ pp_clear_output_area (pp);
+
+ text_info ti;
+ rich_location rich_loc (line_table, UNKNOWN_LOCATION);
+
+ va_list ap;
+
+ va_start (ap, fmt);
+
+ ti.format_spec = _(fmt);
+ ti.args_ptr = ≈
+ ti.err_no = 0;
+ ti.x_data = NULL;
+ ti.m_richloc = &rich_loc;
+
+ pp_format (pp, &ti);
+ pp_output_formatted_text (pp);
+
+ va_end (ap);
+
+ simple_diagnostic_event *new_event
+ = new simple_diagnostic_event (loc, fndecl, depth, pp_formatted_text (pp));
+ m_events.safe_push (new_event);
+
+ pp_clear_output_area (pp);
+
+ return diagnostic_event_id_t (m_events.length () - 1);
+}
+
+/* struct simple_diagnostic_event. */
+
+/* simple_diagnostic_event's ctor. */
+
+simple_diagnostic_event::simple_diagnostic_event (location_t loc,
+ tree fndecl,
+ int depth,
+ const char *desc)
+: m_loc (loc), m_fndecl (fndecl), m_depth (depth), m_desc (xstrdup (desc))
+{
+}
+
+/* simple_diagnostic_event's dtor. */
+
+simple_diagnostic_event::~simple_diagnostic_event ()
+{
+ free (m_desc);
+}
+
+/* Print PATH by emitting a dummy "note" associated with it. */
+
+DEBUG_FUNCTION
+void debug (diagnostic_path *path)
+{
+ rich_location richloc (line_table, UNKNOWN_LOCATION);
+ richloc.set_path (path);
+ inform (&richloc, "debug path");
+}
+
/* Really call the system 'abort'. This has to go right at the end of
this file, so that there are no functions after it that call abort
and get the system abort instead of our macro. */
DEFINE_DIAGNOSTIC_KIND (DK_ANACHRONISM, "anachronism: ", "warning")
DEFINE_DIAGNOSTIC_KIND (DK_NOTE, "note: ", "note")
DEFINE_DIAGNOSTIC_KIND (DK_DEBUG, "debug: ", "note")
+
+/* For use when using the diagnostic_show_locus machinery to show
+ a range of events within a path. */
+DEFINE_DIAGNOSTIC_KIND (DK_DIAGNOSTIC_PATH, "path: ", "path")
+
/* These two would be re-classified as DK_WARNING or DK_ERROR, so the
prefix does not matter. */
DEFINE_DIAGNOSTIC_KIND (DK_PEDWARN, "pedwarn: ", NULL)
DIAGNOSTICS_OUTPUT_FORMAT_JSON
};
+/* An enum for controlling how diagnostic_paths should be printed. */
+enum diagnostic_path_format
+{
+ /* Don't print diagnostic_paths. */
+ DPF_NONE,
+
+ /* Print diagnostic_paths by emitting a separate "note" for every event
+ in the path. */
+ DPF_SEPARATE_EVENTS,
+
+ /* Print diagnostic_paths by consolidating events together where they
+ are close enough, and printing such runs of events with multiple
+ calls to diagnostic_show_locus, showing the individual events in
+ each run via labels in the source. */
+ DPF_INLINE_EVENTS
+};
+
/* A diagnostic is described by the MESSAGE to send, the FILE and LINE of
its context and its KIND (ice, error, warning, note, ...) See complete
list in diagnostic.def. */
diagnostic_t);
class edit_context;
+namespace json { class value; }
/* This data structure bundles altogether any information relevant to
the context of a diagnostic message. */
diagnostics. */
bool show_cwe;
+ /* How should diagnostic_path objects be printed. */
+ enum diagnostic_path_format path_format;
+
+ /* True if we should print stack depths when printing diagnostic paths. */
+ bool show_path_depths;
+
/* True if we should print the command line option which controls
each diagnostic, if known. */
bool show_option_requested;
particular option. */
char *(*get_option_url) (diagnostic_context *, int);
+ void (*print_path) (diagnostic_context *, const diagnostic_path *);
+ json::value *(*make_json_for_path) (diagnostic_context *, const diagnostic_path *);
+
/* Auxiliary data for client. */
void *x_data;
extern void diagnostic_show_locus (diagnostic_context *,
rich_location *richloc,
diagnostic_t diagnostic_kind);
+extern void diagnostic_show_any_path (diagnostic_context *, diagnostic_info *);
/* Force diagnostics controlled by OPTIDX to be kind KIND. */
extern diagnostic_t diagnostic_classify_diagnostic (diagnostic_context *,
/* Compute the number of digits in the decimal representation of an integer. */
extern int num_digits (int);
+extern json::value *json_from_expanded_location (location_t loc);
+
#endif /* ! GCC_DIAGNOSTIC_H */
-fdiagnostics-minimum-margin-width=@var{width} @gol
-fdiagnostics-parseable-fixits -fdiagnostics-generate-patch @gol
-fdiagnostics-show-template-tree -fno-elide-type @gol
+-fdiagnostics-path-format=@r{[}none@r{|}separate-events@r{|}inline-events@r{]} @gol
+-fdiagnostics-show-path-depths @gol
-fno-show-column}
@item Warning Options
The default @env{GCC_COLORS} is
@smallexample
error=01;31:warning=01;35:note=01;36:range1=32:range2=34:locus=01:\
-quote=01:fixit-insert=32:fixit-delete=31:\
+quote=01:path=01;36:fixit-insert=32:fixit-delete=31:\
diff-filename=01:diff-hunk=32:diff-delete=31:diff-insert=32:\
type-diff=01;32
@end smallexample
@vindex note GCC_COLORS @r{capability}
SGR substring for note: markers.
+@item path=
+@vindex path GCC_COLORS @r{capability}
+SGR substring for colorizing paths of control-flow events as printed
+via @option{-fdiagnostics-path-format=}, such as the identifiers of
+individual events and lines indicating interprocedural calls and returns.
+
@item range1=
@vindex range1 GCC_COLORS @r{capability}
SGR substring for first additional range.
This flag also affects the output of the
@option{-fdiagnostics-show-template-tree} flag.
+@item -fdiagnostics-path-format=@var{KIND}
+@opindex fdiagnostics-path-format
+Specify how to print paths of control-flow events for diagnostics that
+have such a path associated with them.
+
+@var{KIND} is @samp{none}, @samp{separate-events}, or @samp{inline-events},
+the default.
+
+@samp{none} means to not print diagnostic paths.
+
+@samp{separate-events} means to print a separate ``note'' diagnostic for
+each event within the diagnostic. For example:
+
+@smallexample
+test.c:29:5: error: passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter
+test.c:25:10: note: (1) when 'PyList_New' fails, returning NULL
+test.c:27:3: note: (2) when 'i < count'
+test.c:29:5: note: (3) when calling 'PyList_Append', passing NULL from (1) as argument 1
+@end smallexample
+
+@samp{inline-events} means to print the events ``inline'' within the source
+code. This view attempts to consolidate the events into runs of
+sufficiently-close events, printing them as labelled ranges within the source.
+
+For example, the same events as above might be printed as:
+
+@smallexample
+ 'test': events 1-3
+ |
+ | 25 | list = PyList_New(0);
+ | | ^~~~~~~~~~~~~
+ | | |
+ | | (1) when 'PyList_New' fails, returning NULL
+ | 26 |
+ | 27 | for (i = 0; i < count; i++) @{
+ | | ~~~
+ | | |
+ | | (2) when 'i < count'
+ | 28 | item = PyLong_FromLong(random());
+ | 29 | PyList_Append(list, item);
+ | | ~~~~~~~~~~~~~~~~~~~~~~~~~
+ | | |
+ | | (3) when calling 'PyList_Append', passing NULL from (1) as argument 1
+ |
+@end smallexample
+
+Interprocedural control flow is shown by grouping the events by stack frame,
+and using indentation to show how stack frames are nested, pushed, and popped.
+
+For example:
+
+@smallexample
+ 'test': events 1-2
+ |
+ | 133 | @{
+ | | ^
+ | | |
+ | | (1) entering 'test'
+ | 134 | boxed_int *obj = make_boxed_int (i);
+ | | ~~~~~~~~~~~~~~~~~~
+ | | |
+ | | (2) calling 'make_boxed_int'
+ |
+ +--> 'make_boxed_int': events 3-4
+ |
+ | 120 | @{
+ | | ^
+ | | |
+ | | (3) entering 'make_boxed_int'
+ | 121 | boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+ | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ | | |
+ | | (4) calling 'wrapped_malloc'
+ |
+ +--> 'wrapped_malloc': events 5-6
+ |
+ | 7 | @{
+ | | ^
+ | | |
+ | | (5) entering 'wrapped_malloc'
+ | 8 | return malloc (size);
+ | | ~~~~~~~~~~~~~
+ | | |
+ | | (6) calling 'malloc'
+ |
+ <-------------+
+ |
+ 'test': event 7
+ |
+ | 138 | free_boxed_int (obj);
+ | | ^~~~~~~~~~~~~~~~~~~~
+ | | |
+ | | (7) calling 'free_boxed_int'
+ |
+(etc)
+@end smallexample
+
+@item -fdiagnostics-show-path-depths
+@opindex fdiagnostics-show-path-depths
+This option provides additional information when printing control-flow paths
+associated with a diagnostic.
+
+If this is option is provided then the stack depth will be printed for
+each run of events within @option{-fdiagnostics-path-format=separate-events}.
+
+This is intended for use by GCC developers and plugin developers when
+debugging diagnostics that report interprocedural control flow.
+
@item -fno-show-column
@opindex fno-show-column
@opindex fshow-column
are expressed via an empty value for @code{string}, insertions by
having @code{start} equal @code{next}.
+If the diagnostic has a path of control-flow events associated with it,
+it has a @code{path} array of objects representing the events. Each
+event object has a @code{description} string, a @code{location} object,
+along with a @code{function} string and a @code{depth} number for
+representing interprocedural paths. The @code{function} represents the
+current function at that event, and the @code{depth} represents the
+stack depth relative to some baseline: the higher, the more frames are
+within the stack.
+
+For example, the intraprocedural example shown for
+@option{-fdiagnostics-path-format=} might have this JSON for its path:
+
+@smallexample
+ "path": [
+ @{
+ "depth": 0,
+ "description": "when 'PyList_New' fails, returning NULL",
+ "function": "test",
+ "location": @{
+ "column": 10,
+ "file": "test.c",
+ "line": 25
+ @}
+ @},
+ @{
+ "depth": 0,
+ "description": "when 'i < count'",
+ "function": "test",
+ "location": @{
+ "column": 3,
+ "file": "test.c",
+ "line": 27
+ @}
+ @},
+ @{
+ "depth": 0,
+ "description": "when calling 'PyList_Append', passing NULL from (1) as argument 1",
+ "function": "test",
+ "location": @{
+ "column": 5,
+ "file": "test.c",
+ "line": 29
+ @}
+ @}
+ ]
+@end smallexample
+
@end table
@node Warning Options
Implemented in diagnostic-show-locus.c. */
- bool add_location_if_nearby (location_t loc);
+ bool add_location_if_nearby (location_t loc,
+ bool restrict_to_current_line_spans = true,
+ const range_label *label = NULL);
/* Add a fix-it hint suggesting the insertion of CONTENT before
INSERTION_POINT.
dc->show_cwe = value;
break;
+ case OPT_fdiagnostics_path_format_:
+ dc->path_format = (enum diagnostic_path_format)value;
+ break;
+
+ case OPT_fdiagnostics_show_path_depths:
+ dc->show_path_depths = value;
+ break;
+
case OPT_fdiagnostics_show_option:
dc->show_option_requested = value;
break;
#include "intl.h"
#include "pretty-print.h"
#include "diagnostic-color.h"
+#include "diagnostic-event-id.h"
#include "selftest.h"
#if HAVE_ICONV
%>: closing quote.
%': apostrophe (should only be used in untranslated messages;
translations should use appropriate punctuation directly).
+ %@: diagnostic_event_id_ptr, for which event_id->known_p () must be true.
%.*s: a substring the length of which is specified by an argument
integer.
%Ns: likewise, but length specified as constant in the format string.
}
break;
+ case '@':
+ {
+ /* diagnostic_event_id_t *. */
+ diagnostic_event_id_ptr event_id
+ = va_arg (*text->args_ptr, diagnostic_event_id_ptr);
+ gcc_assert (event_id->known_p ());
+
+ pp_string (pp, colorize_start (pp_show_color (pp), "path"));
+ pp_character (pp, '(');
+ pp_decimal_int (pp, event_id->one_based ());
+ pp_character (pp, ')');
+ pp_string (pp, colorize_stop (pp_show_color (pp)));
+ }
+ break;
+
default:
{
bool ok;
assert_pp_format_colored (SELFTEST_LOCATION,
"`\33[01m\33[Kfoo\33[m\33[K' 12345678", "%qs %x",
"foo", 0x12345678);
+ /* Verify "%@". */
+ {
+ diagnostic_event_id_t first (2);
+ diagnostic_event_id_t second (7);
+
+ ASSERT_PP_FORMAT_2 ("first `free' at (3); second `free' at (8)",
+ "first %<free%> at %@; second %<free%> at %@",
+ &first, &second);
+ assert_pp_format_colored
+ (SELFTEST_LOCATION,
+ "first `\e[01m\e[Kfree\e[m\e[K' at \e[01;36m\e[K(3)\e[m\e[K;"
+ " second `\e[01m\e[Kfree\e[m\e[K' at \e[01;36m\e[K(8)\e[m\e[K",
+ "first %<free%> at %@; second %<free%> at %@",
+ &first, &second);
+ }
/* Verify %Z. */
int v[] = { 1, 2, 3 };
spellcheck_c_tests ();
spellcheck_tree_c_tests ();
tree_cfg_c_tests ();
+ tree_diagnostic_path_cc_tests ();
attribute_c_tests ();
/* This one relies on most of the above. */
extern void store_merging_c_tests ();
extern void tree_c_tests ();
extern void tree_cfg_c_tests ();
+extern void tree_diagnostic_path_cc_tests ();
extern void typed_splay_tree_c_tests ();
extern void unique_ptr_tests_cc_tests ();
extern void vec_c_tests ();
PR inline-asm/93027
* gcc.target/i386/pr93207.c: Run it only for x86-64.
+2020-01-10 David Malcolm <dmalcolm@redhat.com>
+
+ * gcc.dg/format/gcc_diag-10.c (diagnostic_event_id_t): New
+ typedef.
+ (test_diag): Add coverage of "%@".
+ * gcc.dg/plugin/diagnostic-path-format-default.c: New test.
+ * gcc.dg/plugin/diagnostic-path-format-inline-events-1.c: New test.
+ * gcc.dg/plugin/diagnostic-path-format-inline-events-2.c: New test.
+ * gcc.dg/plugin/diagnostic-path-format-inline-events-3.c: New test.
+ * gcc.dg/plugin/diagnostic-path-format-none.c: New test.
+ * gcc.dg/plugin/diagnostic-test-paths-1.c: New test.
+ * gcc.dg/plugin/diagnostic-test-paths-2.c: New test.
+ * gcc.dg/plugin/diagnostic-test-paths-3.c: New test.
+ * gcc.dg/plugin/diagnostic-test-paths-4.c: New test.
+ * gcc.dg/plugin/diagnostic_plugin_test_paths.c: New.
+ * gcc.dg/plugin/plugin.exp: Add the new plugin and test cases.
+
2020-01-10 David Malcolm <dmalcolm@redhat.com>
* lib/gcc-dg.exp (cleanup-after-saved-dg-test): Reset global
/* Likewise for gimple. */
typedef struct cgraph_node cgraph_node;
+/* Likewise for diagnostic_event_id_t. */
+typedef struct diagnostic_event_id_t diagnostic_event_id_t;
+
#define FORMAT(kind) __attribute__ ((format (__gcc_## kind ##__, 1, 2)))
void diag (const char*, ...) FORMAT (diag);
void cxxdiag (const char*, ...) FORMAT (cxxdiag);
void dump (const char*, ...) FORMAT (dump_printf);
-void test_diag (tree t, gimple *gc)
+void test_diag (tree t, gimple *gc, diagnostic_event_id_t *event_id_ptr)
{
diag ("%<"); /* { dg-warning "unterminated quoting directive" } */
diag ("%>"); /* { dg-warning "unmatched quoting directive " } */
diag ("%G", gc); /* { dg-warning "format" } */
diag ("%K", t); /* { dg-warning "format" } */
+ diag ("%@", event_id_ptr);
diag ("%R"); /* { dg-warning "unmatched color reset directive" } */
diag ("%r", ""); /* { dg-warning "unterminated color directive" } */
--- /dev/null
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-show-caret" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+ return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+ free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+ /* { dg-begin-multiline-output "" }
+ free (ptr);
+ ^~~~~~~~~~
+ 'test': events 1-2
+ |
+ | {
+ | ^
+ | |
+ | (1) entering 'test'
+ | boxed_int *obj = make_boxed_int (i);
+ | ~~~~~~~~~~~~~~~~~~
+ | |
+ | (2) calling 'make_boxed_int'
+ |
+ +--> 'make_boxed_int': events 3-4
+ |
+ | {
+ | ^
+ | |
+ | (3) entering 'make_boxed_int'
+ | boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ | |
+ | (4) calling 'wrapped_malloc'
+ |
+ +--> 'wrapped_malloc': events 5-6
+ |
+ | {
+ | ^
+ | |
+ | (5) entering 'wrapped_malloc'
+ | return malloc (size);
+ | ~~~~~~~~~~~~~
+ | |
+ | (6) calling 'malloc'
+ |
+ <-------------+
+ |
+ 'test': event 7
+ |
+ | free_boxed_int (obj);
+ | ^~~~~~~~~~~~~~~~~~~~
+ | |
+ | (7) calling 'free_boxed_int'
+ |
+ +--> 'free_boxed_int': events 8-9
+ |
+ | {
+ | ^
+ | |
+ | (8) entering 'free_boxed_int'
+ | wrapped_free (bi);
+ | ~~~~~~~~~~~~~~~~~
+ | |
+ | (9) calling 'wrapped_free'
+ |
+ +--> 'wrapped_free': events 10-11
+ |
+ | {
+ | ^
+ | |
+ | (10) entering 'wrapped_free'
+ | free (ptr);
+ | ~~~~~~~~~~
+ | |
+ | (11) calling 'free'
+ |
+ <-------------+
+ |
+ 'test': event 12
+ |
+ | free_boxed_int (obj);
+ | ^~~~~~~~~~~~~~~~~~~~
+ | |
+ | (12) calling 'free_boxed_int'
+ |
+ +--> 'free_boxed_int': events 13-14
+ |
+ | {
+ | ^
+ | |
+ | (13) entering 'free_boxed_int'
+ | wrapped_free (bi);
+ | ~~~~~~~~~~~~~~~~~
+ | |
+ | (14) calling 'wrapped_free'
+ |
+ +--> 'wrapped_free': events 15-16
+ |
+ | {
+ | ^
+ | |
+ | (15) entering 'wrapped_free'
+ | free (ptr);
+ | ~~~~~~~~~~
+ | |
+ | (16) calling 'free'
+ |
+ { dg-end-multiline-output "" } */
+}
+
+typedef struct boxed_int
+{
+ int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+ boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+ result->i = i;
+ return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+ wrapped_free (bi);
+}
+
+void test (int i)
+{
+ boxed_int *obj = make_boxed_int (i);
+
+ free_boxed_int (obj);
+
+ free_boxed_int (obj);
+}
+
--- /dev/null
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+ return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+ free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+ /* { dg-begin-multiline-output "" }
+ free (ptr);
+ ^~~~~~~~~~
+ 'test': events 1-2
+ |
+ | {
+ | ^
+ | |
+ | (1) entering 'test'
+ | boxed_int *obj = make_boxed_int (i);
+ | ~~~~~~~~~~~~~~~~~~
+ | |
+ | (2) calling 'make_boxed_int'
+ |
+ +--> 'make_boxed_int': events 3-4
+ |
+ | {
+ | ^
+ | |
+ | (3) entering 'make_boxed_int'
+ | boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ | |
+ | (4) calling 'wrapped_malloc'
+ |
+ +--> 'wrapped_malloc': events 5-6
+ |
+ | {
+ | ^
+ | |
+ | (5) entering 'wrapped_malloc'
+ | return malloc (size);
+ | ~~~~~~~~~~~~~
+ | |
+ | (6) calling 'malloc'
+ |
+ <-------------+
+ |
+ 'test': event 7
+ |
+ | free_boxed_int (obj);
+ | ^~~~~~~~~~~~~~~~~~~~
+ | |
+ | (7) calling 'free_boxed_int'
+ |
+ +--> 'free_boxed_int': events 8-9
+ |
+ | {
+ | ^
+ | |
+ | (8) entering 'free_boxed_int'
+ | wrapped_free (bi);
+ | ~~~~~~~~~~~~~~~~~
+ | |
+ | (9) calling 'wrapped_free'
+ |
+ +--> 'wrapped_free': events 10-11
+ |
+ | {
+ | ^
+ | |
+ | (10) entering 'wrapped_free'
+ | free (ptr);
+ | ~~~~~~~~~~
+ | |
+ | (11) calling 'free'
+ |
+ <-------------+
+ |
+ 'test': event 12
+ |
+ | free_boxed_int (obj);
+ | ^~~~~~~~~~~~~~~~~~~~
+ | |
+ | (12) calling 'free_boxed_int'
+ |
+ +--> 'free_boxed_int': events 13-14
+ |
+ | {
+ | ^
+ | |
+ | (13) entering 'free_boxed_int'
+ | wrapped_free (bi);
+ | ~~~~~~~~~~~~~~~~~
+ | |
+ | (14) calling 'wrapped_free'
+ |
+ +--> 'wrapped_free': events 15-16
+ |
+ | {
+ | ^
+ | |
+ | (15) entering 'wrapped_free'
+ | free (ptr);
+ | ~~~~~~~~~~
+ | |
+ | (16) calling 'free'
+ |
+ { dg-end-multiline-output "" } */
+}
+
+typedef struct boxed_int
+{
+ int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+ boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+ result->i = i;
+ return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+ wrapped_free (bi);
+}
+
+void test (int i)
+{
+ boxed_int *obj = make_boxed_int (i);
+
+ free_boxed_int (obj);
+
+ free_boxed_int (obj);
+}
+
--- /dev/null
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+/* Verify that 'inline-events' copes gracefully with events with an
+ unknown location. */
+
+#include <stdlib.h>
+
+extern void missing_location ();
+
+void *wrapped_malloc (size_t size)
+{
+ return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+ free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+ /* { dg-begin-multiline-output "" }
+ free (ptr);
+ ^~~~~~~~~~
+ 'test': events 1-2
+ |
+ | {
+ | ^
+ | |
+ | (1) entering 'test'
+ | boxed_int *obj = make_boxed_int (i);
+ | ~~~~~~~~~~~~~~~~~~
+ | |
+ | (2) calling 'make_boxed_int'
+ |
+ +--> 'make_boxed_int': events 3-4
+ |
+ | {
+ | ^
+ | |
+ | (3) entering 'make_boxed_int'
+ | boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ | |
+ | (4) calling 'wrapped_malloc'
+ |
+ +--> 'wrapped_malloc': events 5-6
+ |
+ | {
+ | ^
+ | |
+ | (5) entering 'wrapped_malloc'
+ | return malloc (size);
+ | ~~~~~~~~~~~~~
+ | |
+ | (6) calling 'malloc'
+ |
+ <-------------+
+ |
+ 'test': event 7
+ |
+ | free_boxed_int (obj);
+ | ^~~~~~~~~~~~~~~~~~~~
+ | |
+ | (7) calling 'free_boxed_int'
+ |
+ +--> 'free_boxed_int': events 8-9
+ |
+ | {
+ | ^
+ | |
+ | (8) entering 'free_boxed_int'
+ | wrapped_free (bi);
+ | ~~~~~~~~~~~~~~~~~
+ | |
+ | (9) calling 'wrapped_free'
+ |
+ +--> 'wrapped_free': events 10-11
+ |
+ | {
+ | ^
+ | |
+ | (10) entering 'wrapped_free'
+ | free (ptr);
+ | ~~~~~~~~~~
+ | |
+ | (11) calling 'free'
+ |
+ <-------------+
+ |
+ 'test': event 12
+ |
+ |cc1:
+ | (12): calling 'missing_location'
+ |
+ 'test': event 13
+ |
+ | free_boxed_int (obj);
+ | ^~~~~~~~~~~~~~~~~~~~
+ | |
+ | (13) calling 'free_boxed_int'
+ |
+ +--> 'free_boxed_int': events 14-15
+ |
+ | {
+ | ^
+ | |
+ | (14) entering 'free_boxed_int'
+ | wrapped_free (bi);
+ | ~~~~~~~~~~~~~~~~~
+ | |
+ | (15) calling 'wrapped_free'
+ |
+ +--> 'wrapped_free': events 16-17
+ |
+ | {
+ | ^
+ | |
+ | (16) entering 'wrapped_free'
+ | free (ptr);
+ | ~~~~~~~~~~
+ | |
+ | (17) calling 'free'
+ |
+ { dg-end-multiline-output "" } */
+}
+
+typedef struct boxed_int
+{
+ int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+ boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+ result->i = i;
+ return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+ wrapped_free (bi);
+}
+
+void test (int i)
+{
+ boxed_int *obj = make_boxed_int (i);
+
+ free_boxed_int (obj);
+
+ missing_location ();
+
+ free_boxed_int (obj);
+}
+
--- /dev/null
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-line-numbers -fdiagnostics-show-caret" } */
+/* { dg-enable-nn-line-numbers "" } */
+
+/* Verify the interaction of inline-events with line numbers. */
+
+#include <stdlib.h>
+
+extern void missing_location ();
+
+void *wrapped_malloc (size_t size)
+{
+ return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+ free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+ /* { dg-begin-multiline-output "" }
+ NN | free (ptr);
+ | ^~~~~~~~~~
+ 'test': events 1-2
+ |
+ | NN | {
+ | | ^
+ | | |
+ | | (1) entering 'test'
+ | NN | boxed_int *obj = make_boxed_int (i);
+ | | ~~~~~~~~~~~~~~~~~~
+ | | |
+ | | (2) calling 'make_boxed_int'
+ |
+ +--> 'make_boxed_int': events 3-4
+ |
+ | NN | {
+ | | ^
+ | | |
+ | | (3) entering 'make_boxed_int'
+ | NN | boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+ | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ | | |
+ | | (4) calling 'wrapped_malloc'
+ |
+ +--> 'wrapped_malloc': events 5-6
+ |
+ | NN | {
+ | | ^
+ | | |
+ | | (5) entering 'wrapped_malloc'
+ | NN | return malloc (size);
+ | | ~~~~~~~~~~~~~
+ | | |
+ | | (6) calling 'malloc'
+ |
+ <-------------+
+ |
+ 'test': event 7
+ |
+ | NN | free_boxed_int (obj);
+ | | ^~~~~~~~~~~~~~~~~~~~
+ | | |
+ | | (7) calling 'free_boxed_int'
+ |
+ +--> 'free_boxed_int': events 8-9
+ |
+ | NN | {
+ | | ^
+ | | |
+ | | (8) entering 'free_boxed_int'
+ | NN | wrapped_free (bi);
+ | | ~~~~~~~~~~~~~~~~~
+ | | |
+ | | (9) calling 'wrapped_free'
+ |
+ +--> 'wrapped_free': events 10-11
+ |
+ | NN | {
+ | | ^
+ | | |
+ | | (10) entering 'wrapped_free'
+ | NN | free (ptr);
+ | | ~~~~~~~~~~
+ | | |
+ | | (11) calling 'free'
+ |
+ <-------------+
+ |
+ 'test': event 12
+ |
+ |cc1:
+ | (12): calling 'missing_location'
+ |
+ 'test': event 13
+ |
+ | NN | free_boxed_int (obj);
+ | | ^~~~~~~~~~~~~~~~~~~~
+ | | |
+ | | (13) calling 'free_boxed_int'
+ |
+ +--> 'free_boxed_int': events 14-15
+ |
+ | NN | {
+ | | ^
+ | | |
+ | | (14) entering 'free_boxed_int'
+ | NN | wrapped_free (bi);
+ | | ~~~~~~~~~~~~~~~~~
+ | | |
+ | | (15) calling 'wrapped_free'
+ |
+ +--> 'wrapped_free': events 16-17
+ |
+ | NN | {
+ | | ^
+ | | |
+ | | (16) entering 'wrapped_free'
+ | NN | free (ptr);
+ | | ~~~~~~~~~~
+ | | |
+ | | (17) calling 'free'
+ |
+ { dg-end-multiline-output "" } */
+}
+
+typedef struct boxed_int
+{
+ int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+ boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+ result->i = i;
+ return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+ wrapped_free (bi);
+}
+
+void test (int i)
+{
+ boxed_int *obj = make_boxed_int (i);
+
+ free_boxed_int (obj);
+
+ missing_location ();
+
+ free_boxed_int (obj);
+}
+
--- /dev/null
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=none" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+ return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+ free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+}
+
+typedef struct boxed_int
+{
+ int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+ boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+ result->i = i;
+ return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+ wrapped_free (bi);
+}
+
+void test (int i)
+{
+ boxed_int *obj = make_boxed_int (i);
+
+ free_boxed_int (obj);
+
+ free_boxed_int (obj);
+}
+
--- /dev/null
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=separate-events" } */
+
+#include <stdlib.h>
+
+void *wrapped_malloc (size_t size)
+{
+ return malloc (size);
+}
+
+void wrapped_free (void *ptr)
+{
+ free (ptr); /* { dg-warning "double-free of 'ptr' \\\[CWE-415\\]" } */
+}
+
+typedef struct boxed_int
+{
+ int i;
+} boxed_int;
+
+boxed_int *
+make_boxed_int (int i)
+{
+ boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
+ result->i = i;
+ return result;
+}
+
+void
+free_boxed_int (boxed_int *bi)
+{
+ wrapped_free (bi);
+}
+
+void test (int i)
+{ /* { dg-message "\\(1\\) entering 'test'" } */
+ boxed_int *obj = make_boxed_int (i); /* { dg-message "\\(2\\) calling 'make_boxed_int'" } */
+ /* etc */
+
+ free_boxed_int (obj);
+
+ free_boxed_int (obj);
+}
+
--- /dev/null
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=separate-events" } */
+
+#include <stddef.h>
+#include <stdlib.h>
+
+/* Minimal reimplementation of cpython API. */
+typedef struct PyObject {} PyObject;
+extern int PyArg_ParseTuple (PyObject *args, const char *fmt, ...);
+extern PyObject *PyList_New (int);
+extern PyObject *PyLong_FromLong(long);
+extern void PyList_Append(PyObject *list, PyObject *item);
+
+PyObject *
+make_a_list_of_random_ints_badly(PyObject *self,
+ PyObject *args)
+{
+ PyObject *list, *item;
+ long count, i;
+
+ if (!PyArg_ParseTuple(args, "i", &count)) {
+ return NULL;
+ }
+
+ list = PyList_New(0); /* { dg-line PyList_New } */
+
+ for (i = 0; i < count; i++) { /* { dg-line for } */
+ item = PyLong_FromLong(random());
+ PyList_Append(list, item); /* { dg-line PyList_Append } */
+ }
+
+ return list;
+
+ /* { dg-error "passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter" "" { target *-*-* } PyList_Append } */
+ /* { dg-message "\\(1\\) when 'PyList_New' fails, returning NULL" "" { target *-*-* } PyList_New } */
+ /* { dg-message "\\(2\\) when 'i < count'" "" { target *-*-* } for } */
+ /* { dg-message "\\(3\\) when calling 'PyList_Append', passing NULL from \\(1\\) as argument 1" "" { target *-*-* } PyList_Append } */
+}
--- /dev/null
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-show-caret -fdiagnostics-show-line-numbers" } */
+
+#include <stddef.h>
+#include <stdlib.h>
+
+/* Minimal reimplementation of cpython API. */
+typedef struct PyObject {} PyObject;
+extern int PyArg_ParseTuple (PyObject *args, const char *fmt, ...);
+extern PyObject *PyList_New (int);
+extern PyObject *PyLong_FromLong(long);
+extern void PyList_Append(PyObject *list, PyObject *item);
+
+PyObject *
+make_a_list_of_random_ints_badly(PyObject *self,
+ PyObject *args)
+{
+ PyObject *list, *item;
+ long count, i;
+
+ if (!PyArg_ParseTuple(args, "i", &count)) {
+ return NULL;
+ }
+
+ list = PyList_New(0); /* { dg-line PyList_New } */
+
+ for (i = 0; i < count; i++) {
+ item = PyLong_FromLong(random());
+ PyList_Append(list, item); /* { dg-line PyList_Append } */
+ }
+
+ return list;
+
+ /* { dg-error "passing NULL as argument 1 to 'PyList_Append' which requires a non-NULL parameter" "" { target *-*-* } PyList_Append } */
+ /* { dg-begin-multiline-output "" }
+ 29 | PyList_Append(list, item);
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~
+ 'make_a_list_of_random_ints_badly': events 1-3
+ |
+ | 25 | list = PyList_New(0);
+ | | ^~~~~~~~~~~~~
+ | | |
+ | | (1) when 'PyList_New' fails, returning NULL
+ | 26 |
+ | 27 | for (i = 0; i < count; i++) {
+ | | ~~~
+ | | |
+ | | (2) when 'i < count'
+ | 28 | item = PyLong_FromLong(random());
+ | 29 | PyList_Append(list, item);
+ | | ~~~~~~~~~~~~~~~~~~~~~~~~~
+ | | |
+ | | (3) when calling 'PyList_Append', passing NULL from (1) as argument 1
+ |
+ { dg-end-multiline-output "" } */
+}
--- /dev/null
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-format=json" } */
+
+#include <stddef.h>
+#include <stdlib.h>
+
+/* Minimal reimplementation of cpython API. */
+typedef struct PyObject {} PyObject;
+extern int PyArg_ParseTuple (PyObject *args, const char *fmt, ...);
+extern PyObject *PyList_New (int);
+extern PyObject *PyLong_FromLong(long);
+extern void PyList_Append(PyObject *list, PyObject *item);
+
+PyObject *
+make_a_list_of_random_ints_badly(PyObject *self,
+ PyObject *args)
+{
+ PyObject *list, *item;
+ long count, i;
+
+ if (!PyArg_ParseTuple(args, "i", &count)) {
+ return NULL;
+ }
+
+ list = PyList_New(0);
+
+ for (i = 0; i < count; i++) {
+ item = PyLong_FromLong(random());
+ PyList_Append(list, item);
+ }
+
+ return list;
+}
+
+/* FIXME: test the events within a path. */
+/* { dg-regexp "\"kind\": \"error\"" } */
+/* { dg-regexp "\"path\": " } */
+/* { dg-regexp ".*" } */
--- /dev/null
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret -fdiagnostics-show-line-numbers" } */
+/* { dg-enable-nn-line-numbers "" } */
+
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+
+extern void body_of_program(void);
+
+void custom_logger(const char *msg)
+{
+ fprintf(stderr, "LOG: %s", msg); /* { dg-warning "call to 'fprintf' from within signal handler" } */
+}
+
+static void int_handler(int signum)
+{
+ custom_logger("got signal");
+}
+
+static void register_handler ()
+{
+ signal(SIGINT, int_handler);
+}
+
+void test (void)
+{
+ register_handler ();
+ body_of_program();
+}
+
+/* { dg-begin-multiline-output "" }
+ NN | fprintf(stderr, "LOG: %s", msg);
+ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 'test': events 1-2
+ |
+ | NN | {
+ | | ^
+ | | |
+ | | (1) entering 'test'
+ | NN | register_handler ();
+ | | ~~~~~~~~~~~~~~~~~~~
+ | | |
+ | | (2) calling 'register_handler'
+ |
+ +--> 'register_handler': events 3-4
+ |
+ | NN | {
+ | | ^
+ | | |
+ | | (3) entering 'register_handler'
+ | NN | signal(SIGINT, int_handler);
+ | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ | | |
+ | | (4) registering 'int_handler' as signal handler
+ |
+ event 5
+ |
+ |cc1:
+ | (5): later on, when the signal is delivered to the process
+ |
+ +--> 'int_handler': events 6-7
+ |
+ | NN | {
+ | | ^
+ | | |
+ | | (6) entering 'int_handler'
+ | NN | custom_logger("got signal");
+ | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ | | |
+ | | (7) calling 'custom_logger'
+ |
+ +--> 'custom_logger': events 8-9
+ |
+ | NN | {
+ | | ^
+ | | |
+ | | (8) entering 'custom_logger'
+ | NN | fprintf(stderr, "LOG: %s", msg);
+ | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ | | |
+ | | (9) calling 'fprintf'
+ |
+ { dg-end-multiline-output "" } */
--- /dev/null
+/* { dg-options "-O" } */
+
+/* This plugin exercises the path-printing code.
+
+ The goal is to unit-test the path-printing code without needing any
+ specific tests within the compiler's IR. We can't use any real
+ diagnostics for this, so we have to fake it, hence this plugin. */
+
+#include "gcc-plugin.h"
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "tree.h"
+#include "stringpool.h"
+#include "toplev.h"
+#include "basic-block.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "tree.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "plugin-version.h"
+#include "diagnostic.h"
+#include "diagnostic-path.h"
+#include "diagnostic-metadata.h"
+#include "context.h"
+#include "print-tree.h"
+#include "gcc-rich-location.h"
+#include "cgraph.h"
+
+int plugin_is_GPL_compatible;
+
+const pass_data pass_data_test_show_path =
+{
+ IPA_PASS, /* type */
+ "test_show_path", /* name */
+ OPTGROUP_NONE, /* optinfo_flags */
+ TV_NONE, /* tv_id */
+ PROP_ssa, /* properties_required */
+ 0, /* properties_provided */
+ 0, /* properties_destroyed */
+ 0, /* todo_flags_start */
+ 0, /* todo_flags_finish */
+};
+
+class pass_test_show_path : public ipa_opt_pass_d
+{
+public:
+ pass_test_show_path(gcc::context *ctxt)
+ : ipa_opt_pass_d (pass_data_test_show_path, ctxt,
+ NULL, /* generate_summary */
+ NULL, /* write_summary */
+ NULL, /* read_summary */
+ NULL, /* write_optimization_summary */
+ NULL, /* read_optimization_summary */
+ NULL, /* stmt_fixup */
+ 0, /* function_transform_todo_flags_start */
+ NULL, /* function_transform */
+ NULL) /* variable_transform */
+ {}
+
+ /* opt_pass methods: */
+ bool gate (function *) { return true; }
+ virtual unsigned int execute (function *);
+
+}; // class pass_test_show_path
+
+/* Determine if STMT is a call with NUM_ARGS arguments to a function
+ named FUNCNAME.
+ If so, return STMT as a gcall *. Otherwise return NULL. */
+
+static gcall *
+check_for_named_call (gimple *stmt,
+ const char *funcname, unsigned int num_args)
+{
+ gcc_assert (funcname);
+
+ gcall *call = dyn_cast <gcall *> (stmt);
+ if (!call)
+ return NULL;
+
+ tree fndecl = gimple_call_fndecl (call);
+ if (!fndecl)
+ return NULL;
+
+ if (strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), funcname))
+ return NULL;
+
+ if (gimple_call_num_args (call) != num_args)
+ {
+ error_at (stmt->location, "expected number of args: %i (got %i)",
+ num_args, gimple_call_num_args (call));
+ return NULL;
+ }
+
+ return call;
+}
+
+/* Example 1: a purely intraprocedural path. */
+
+static void
+example_1 ()
+{
+ gimple_stmt_iterator gsi;
+ basic_block bb;
+
+ gcall *call_to_PyList_Append = NULL;
+ gcall *call_to_PyList_New = NULL;
+ gcond *for_cond = NULL;
+ function *example_a_fun = NULL;
+
+ cgraph_node *node;
+ FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+ {
+ function *fun = node->get_fun ();
+ FOR_EACH_BB_FN (bb, fun)
+ {
+ for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+ if (gcall *call = check_for_named_call (stmt, "PyList_New", 1))
+ {
+ call_to_PyList_New = call;
+ example_a_fun = fun;
+ }
+ if (gcall *call = check_for_named_call (stmt, "PyList_Append", 2))
+ call_to_PyList_Append = call;
+ if (gcond *cond = dyn_cast <gcond *> (stmt))
+ for_cond = cond;
+ }
+ }
+ }
+
+ if (call_to_PyList_New && for_cond && call_to_PyList_Append)
+ {
+ auto_diagnostic_group d;
+ gcc_rich_location richloc (gimple_location (call_to_PyList_Append));
+ simple_diagnostic_path path (global_dc->printer);
+ diagnostic_event_id_t alloc_event_id
+ = path.add_event (gimple_location (call_to_PyList_New),
+ example_a_fun->decl, 0,
+ "when %qs fails, returning NULL",
+ "PyList_New");
+ path.add_event (gimple_location (for_cond),
+ example_a_fun->decl, 0,
+ "when %qs", "i < count");
+ path.add_event (gimple_location (call_to_PyList_Append),
+ example_a_fun->decl, 0,
+ "when calling %qs, passing NULL from %@ as argument %i",
+ "PyList_Append", &alloc_event_id, 1);
+ richloc.set_path (&path);
+ error_at (&richloc,
+ "passing NULL as argument %i to %qs"
+ " which requires a non-NULL parameter",
+ 1, "PyList_Append");
+ }
+}
+
+/* A (function, location_t) pair. */
+
+struct event_location_t
+{
+ event_location_t ()
+ : m_fun (NULL), m_loc (UNKNOWN_LOCATION)
+ {}
+
+ event_location_t (function *fun, location_t loc)
+ : m_fun (fun), m_loc (loc)
+ {}
+
+ void set (const gimple *stmt, function *fun)
+ {
+ m_fun = fun;
+ m_loc = gimple_location (stmt);
+ }
+
+ function *m_fun;
+ location_t m_loc;
+};
+
+/* If FUN's name matches FUNCNAME, write the function and its start location
+ into *OUT_ENTRY. */
+
+static void
+check_for_named_function (function *fun, const char *funcname,
+ event_location_t *out_entry)
+{
+ gcc_assert (fun);
+ gcc_assert (funcname);
+
+ if (strcmp (IDENTIFIER_POINTER (DECL_NAME (fun->decl)), funcname))
+ return;
+
+ *out_entry = event_location_t (fun, fun->function_start_locus);
+}
+
+
+/* Example 2: an interprocedural path. */
+
+class test_diagnostic_path : public simple_diagnostic_path
+{
+ public:
+ test_diagnostic_path (pretty_printer *event_pp)
+ : simple_diagnostic_path (event_pp)
+ {
+ }
+ void add_entry (event_location_t evloc, int stack_depth,
+ const char *funcname)
+ {
+ gcc_assert (evloc.m_fun);
+ add_event (evloc.m_loc, evloc.m_fun->decl, stack_depth,
+ "entering %qs", funcname);
+ }
+
+ void add_call (event_location_t call_evloc, int caller_stack_depth,
+ event_location_t callee_entry_evloc, const char *callee)
+ {
+ gcc_assert (call_evloc.m_fun);
+ add_event (call_evloc.m_loc, call_evloc.m_fun->decl, caller_stack_depth,
+ "calling %qs", callee);
+ add_entry (callee_entry_evloc, caller_stack_depth + 1, callee);
+ }
+
+ void add_leaf_call (event_location_t call_evloc, int caller_stack_depth,
+ const char *callee)
+ {
+ gcc_assert (call_evloc.m_fun);
+ add_event (call_evloc.m_loc, call_evloc.m_fun->decl, caller_stack_depth,
+ "calling %qs", callee);
+ }
+};
+
+static void
+example_2 ()
+{
+ gimple_stmt_iterator gsi;
+ basic_block bb;
+
+ event_location_t entry_to_wrapped_malloc;
+ event_location_t call_to_malloc;
+
+ event_location_t entry_to_wrapped_free;
+ event_location_t call_to_free;
+
+ event_location_t entry_to_make_boxed_int;
+ event_location_t call_to_wrapped_malloc;
+
+ event_location_t entry_to_free_boxed_int;
+ event_location_t call_to_wrapped_free;
+
+ event_location_t entry_to_test;
+ event_location_t call_to_make_boxed_int;
+ event_location_t call_to_free_boxed_int;
+
+ event_location_t call_to_missing_location;
+
+ cgraph_node *node;
+ FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+ {
+ function *fun = node->get_fun ();
+ FOR_EACH_BB_FN (bb, fun)
+ {
+ check_for_named_function (fun, "wrapped_malloc",
+ &entry_to_wrapped_malloc);
+ check_for_named_function (fun, "wrapped_free",
+ &entry_to_wrapped_free);
+ check_for_named_function (fun, "make_boxed_int",
+ &entry_to_make_boxed_int);
+ check_for_named_function (fun, "free_boxed_int",
+ &entry_to_free_boxed_int);
+ check_for_named_function (fun, "test",
+ &entry_to_test);
+
+ for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+ if (gcall *call = check_for_named_call (stmt, "malloc", 1))
+ call_to_malloc.set (call, fun);
+ if (gcall *call = check_for_named_call (stmt, "free", 1))
+ call_to_free.set (call, fun);
+ if (gcall *call = check_for_named_call (stmt, "wrapped_malloc", 1))
+ call_to_wrapped_malloc.set (call, fun);
+ if (gcall *call = check_for_named_call (stmt, "wrapped_free", 1))
+ call_to_wrapped_free.set (call, fun);
+ if (gcall *call = check_for_named_call (stmt, "make_boxed_int", 1))
+ call_to_make_boxed_int.set (call, fun);
+ if (gcall *call = check_for_named_call (stmt, "free_boxed_int", 1))
+ call_to_free_boxed_int.set (call, fun);
+ if (gcall *call = check_for_named_call (stmt, "missing_location", 0))
+ {
+ call_to_missing_location.set (call, fun);
+ /* Simulate an event that's missing a useful location_t. */
+ call_to_missing_location.m_loc = UNKNOWN_LOCATION;
+ }
+ }
+ }
+ }
+
+ if (call_to_malloc.m_fun)
+ {
+ auto_diagnostic_group d;
+
+ gcc_rich_location richloc (call_to_free.m_loc);
+ test_diagnostic_path path (global_dc->printer);
+ path.add_entry (entry_to_test, 0, "test");
+ path.add_call (call_to_make_boxed_int, 0,
+ entry_to_make_boxed_int, "make_boxed_int");
+ path.add_call (call_to_wrapped_malloc, 1,
+ entry_to_wrapped_malloc, "wrapped_malloc");
+ path.add_leaf_call (call_to_malloc, 2, "malloc");
+
+ for (int i = 0; i < 2; i++)
+ {
+ path.add_call (call_to_free_boxed_int, 0,
+ entry_to_free_boxed_int, "free_boxed_int");
+ path.add_call (call_to_wrapped_free, 1,
+ entry_to_wrapped_free, "wrapped_free");
+ path.add_leaf_call (call_to_free, 2, "free");
+ if (i == 0 && call_to_missing_location.m_fun)
+ path.add_leaf_call (call_to_missing_location, 0, "missing_location");
+ }
+
+ richloc.set_path (&path);
+
+ diagnostic_metadata m;
+ m.add_cwe (415); /* CWE-415: Double Free. */
+
+ warning_at (&richloc, m, 0,
+ "double-free of %qs", "ptr");
+ }
+}
+
+/* Example 3: an interprocedural path with a callback. */
+
+static void
+example_3 ()
+{
+ gimple_stmt_iterator gsi;
+ basic_block bb;
+
+ event_location_t entry_to_custom_logger;
+ event_location_t call_to_fprintf;
+
+ event_location_t entry_to_int_handler;
+ event_location_t call_to_custom_logger;
+
+ event_location_t entry_to_register_handler;
+ event_location_t call_to_signal;
+
+ event_location_t entry_to_test;
+ event_location_t call_to_register_handler;
+
+ cgraph_node *node;
+ FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node)
+ {
+ function *fun = node->get_fun ();
+ FOR_EACH_BB_FN (bb, fun)
+ {
+ check_for_named_function (fun, "custom_logger",
+ &entry_to_custom_logger);
+ check_for_named_function (fun, "int_handler",
+ &entry_to_int_handler);
+ check_for_named_function (fun, "register_handler",
+ &entry_to_register_handler);
+ check_for_named_function (fun, "test",
+ &entry_to_test);
+ for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+ if (gcall *call = check_for_named_call (stmt, "fprintf", 3))
+ call_to_fprintf.set (call, fun);
+ if (gcall *call = check_for_named_call (stmt, "custom_logger", 1))
+ call_to_custom_logger.set (call, fun);
+ if (gcall *call = check_for_named_call (stmt, "register_handler",
+ 0))
+ call_to_register_handler.set (call, fun);
+ if (gcall *call = check_for_named_call (stmt, "signal", 2))
+ call_to_signal.set (call, fun);
+ }
+ }
+ }
+
+ if (call_to_fprintf.m_fun)
+ {
+ auto_diagnostic_group d;
+
+ gcc_rich_location richloc (call_to_fprintf.m_loc);
+ test_diagnostic_path path (global_dc->printer);
+ path.add_entry (entry_to_test, 1, "test");
+ path.add_call (call_to_register_handler, 1,
+ entry_to_register_handler, "register_handler");
+ path.add_event (call_to_signal.m_loc, call_to_signal.m_fun->decl,
+ 2, "registering 'int_handler' as signal handler");
+ path.add_event (UNKNOWN_LOCATION, NULL_TREE, 0,
+ "later on, when the signal is delivered to the process");
+ path.add_entry (entry_to_int_handler, 1, "int_handler");
+ path.add_call (call_to_custom_logger, 1,
+ entry_to_custom_logger, "custom_logger");
+ path.add_leaf_call (call_to_fprintf, 2, "fprintf");
+
+ richloc.set_path (&path);
+
+ diagnostic_metadata m;
+ /* CWE-479: Signal Handler Use of a Non-reentrant Function. */
+ m.add_cwe (479);
+
+ warning_at (&richloc, m, 0,
+ "call to %qs from within signal handler",
+ "fprintf");
+ }
+}
+
+unsigned int
+pass_test_show_path::execute (function *)
+{
+ example_1 ();
+ example_2 ();
+ example_3 ();
+
+ return 0;
+}
+
+static opt_pass *
+make_pass_test_show_path (gcc::context *ctxt)
+{
+ return new pass_test_show_path (ctxt);
+}
+
+int
+plugin_init (struct plugin_name_args *plugin_info,
+ struct plugin_gcc_version *version)
+{
+ struct register_pass_info pass_info;
+ const char *plugin_name = plugin_info->base_name;
+ int argc = plugin_info->argc;
+ struct plugin_argument *argv = plugin_info->argv;
+
+ if (!plugin_default_version_check (version, &gcc_version))
+ return 1;
+
+ pass_info.pass = make_pass_test_show_path (g);
+ pass_info.reference_pass_name = "whole-program";
+ pass_info.ref_pass_instance_number = 1;
+ pass_info.pos_op = PASS_POS_INSERT_BEFORE;
+ register_callback (plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL,
+ &pass_info);
+
+ return 0;
+}
diagnostic-test-inlining-3.c \
diagnostic-test-inlining-4.c } \
{ diagnostic_plugin_test_metadata.c diagnostic-test-metadata.c } \
+ { diagnostic_plugin_test_paths.c \
+ diagnostic-test-paths-1.c \
+ diagnostic-test-paths-2.c \
+ diagnostic-test-paths-3.c \
+ diagnostic-test-paths-4.c \
+ diagnostic-path-format-default.c \
+ diagnostic-path-format-none.c \
+ diagnostic-path-format-separate-events.c \
+ diagnostic-path-format-inline-events-1.c \
+ diagnostic-path-format-inline-events-2.c \
+ diagnostic-path-format-inline-events-3.c } \
{ location_overflow_plugin.c \
location-overflow-test-1.c \
location-overflow-test-2.c \
= global_options_init.x_flag_diagnostics_show_line_numbers;
global_dc->show_cwe
= global_options_init.x_flag_diagnostics_show_cwe;
+ global_dc->path_format
+ = (enum diagnostic_path_format)global_options_init.x_flag_diagnostics_path_format;
+ global_dc->show_path_depths
+ = global_options_init.x_flag_diagnostics_show_path_depths;
global_dc->show_option_requested
= global_options_init.x_flag_diagnostics_show_option;
global_dc->min_margin_width
--- /dev/null
+/* Paths through the code associated with a diagnostic.
+ Copyright (C) 2019-2020 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"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "diagnostic.h"
+#include "tree-pretty-print.h"
+#include "gimple-pretty-print.h"
+#include "tree-diagnostic.h"
+#include "langhooks.h"
+#include "intl.h"
+#include "diagnostic-path.h"
+#include "json.h"
+#include "gcc-rich-location.h"
+#include "diagnostic-color.h"
+#include "diagnostic-event-id.h"
+#include "selftest.h"
+#include "selftest-diagnostic.h"
+
+/* Anonymous namespace for path-printing code. */
+
+namespace {
+
+/* 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, unsigned start_idx)
+ : m_path (path), m_start_idx (start_idx)
+ {}
+
+ 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);
+
+ /* Get the description of the event, perhaps with colorization:
+ normally, we don't colorize within a range_label, but this
+ is special-cased for diagnostic paths. */
+ bool colorize = pp_show_color (global_dc->printer);
+ label_text event_text (event.get_desc (colorize));
+ gcc_assert (event_text.m_buffer);
+ pretty_printer pp;
+ pp_show_color (&pp) = pp_show_color (global_dc->printer);
+ diagnostic_event_id_t event_id (event_idx);
+ pp_printf (&pp, "%@ %s", &event_id, event_text.m_buffer);
+ event_text.maybe_free ();
+ label_text result = label_text::take (xstrdup (pp_formatted_text (&pp)));
+ return result;
+ }
+
+ private:
+ const diagnostic_path *m_path;
+ unsigned m_start_idx;
+};
+
+/* 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_event &e1,
+ const diagnostic_event &e2,
+ bool check_locations)
+{
+ if (e1.get_fndecl () != e2.get_fndecl ())
+ 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;
+}
+
+/* A class for grouing together the events in a diagnostic_path into
+ ranges of events, partitioned by stack frame (i.e. by fndecl and
+ stack depth). */
+
+class path_summary
+{
+ /* A range of consecutive events within a diagnostic_path,
+ all with the same fndecl and stack_depth, and which are suitable
+ to print with a single call to diagnostic_show_locus. */
+ struct event_range
+ {
+ event_range (const diagnostic_path *path, unsigned start_idx,
+ const diagnostic_event &initial_event)
+ : m_path (path),
+ m_initial_event (initial_event),
+ m_fndecl (initial_event.get_fndecl ()),
+ m_stack_depth (initial_event.get_stack_depth ()),
+ m_start_idx (start_idx), m_end_idx (start_idx),
+ m_path_label (path, start_idx),
+ m_richloc (initial_event.get_location (), &m_path_label)
+ {}
+
+ bool maybe_add_event (const diagnostic_event &new_ev, unsigned idx,
+ bool check_rich_locations)
+ {
+ if (!can_consolidate_events (m_initial_event, new_ev,
+ check_rich_locations))
+ return false;
+ if (check_rich_locations)
+ if (!m_richloc.add_location_if_nearby (new_ev.get_location (),
+ false, &m_path_label))
+ return false;
+ m_end_idx = idx;
+ return true;
+ }
+
+ /* Print the events in this range to DC, typically as a single
+ call to the printer's diagnostic_show_locus. */
+
+ void print (diagnostic_context *dc)
+ {
+ 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->show_caret)
+ {
+ expanded_location exploc
+ = linemap_client_expand_location_to_spelling_point
+ (initial_loc, LOCATION_ASPECT_CARET);
+ if (exploc.file != LOCATION_FILE (dc->last_location))
+ dc->start_span (dc, 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 (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);
+ label_text event_text (iter_event.get_desc (true));
+ pretty_printer *pp = dc->printer;
+ pp_printf (pp, " %@: %s", &event_id, event_text.m_buffer);
+ pp_newline (pp);
+ event_text.maybe_free ();
+ }
+ return;
+ }
+
+ /* Call diagnostic_show_locus to show the events using labels. */
+ diagnostic_show_locus (dc, &m_richloc, DK_DIAGNOSTIC_PATH);
+
+ /* 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 (dc, initial_loc);
+ }
+ }
+
+ const diagnostic_path *m_path;
+ const diagnostic_event &m_initial_event;
+ tree m_fndecl;
+ int m_stack_depth;
+ unsigned m_start_idx;
+ unsigned m_end_idx;
+ path_label m_path_label;
+ gcc_rich_location m_richloc;
+ };
+
+ public:
+ path_summary (const diagnostic_path &path, bool check_rich_locations);
+
+ void print (diagnostic_context *dc, bool show_depths) const;
+
+ unsigned get_num_ranges () const { return m_ranges.length (); }
+
+ private:
+ auto_delete_vec <event_range> m_ranges;
+};
+
+/* path_summary's ctor. */
+
+path_summary::path_summary (const diagnostic_path &path,
+ bool check_rich_locations)
+{
+ 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);
+ if (cur_event_range)
+ if (cur_event_range->maybe_add_event (event, idx, check_rich_locations))
+ continue;
+
+ cur_event_range = new event_range (&path, idx, event);
+ m_ranges.safe_push (cur_event_range);
+ }
+}
+
+/* Write SPACES to PP. */
+
+static void
+write_indent (pretty_printer *pp, int spaces)
+{
+ for (int i = 0; i < spaces; i++)
+ pp_space (pp);
+}
+
+/* Print FNDDECL to PP, quoting it if QUOTED is true.
+
+ We can't use "%qE" here since we can't guarantee the capabilities
+ of PP. */
+
+static void
+print_fndecl (pretty_printer *pp, tree fndecl, bool quoted)
+{
+ const char *n = DECL_NAME (fndecl)
+ ? identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2))
+ : _("<anonymous>");
+ if (quoted)
+ pp_printf (pp, "%qs", n);
+ else
+ pp_string (pp, n);
+}
+
+/* Print this path_summary to DC, 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. */
+
+void
+path_summary::print (diagnostic_context *dc, bool show_depths) const
+{
+ pretty_printer *pp = dc->printer;
+
+ const int per_frame_indent = 2;
+
+ 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));
+
+ /* Keep track of column numbers of existing '|' characters for
+ stack depths we've already printed. */
+ const int EMPTY = -1;
+ const int DELETED = -2;
+ typedef int_hash <int, EMPTY, DELETED> vbar_hash;
+ hash_map <vbar_hash, int> vbar_column_for_depth;
+
+ /* Print the ranges. */
+ const int base_indent = 2;
+ int cur_indent = base_indent;
+ unsigned i;
+ event_range *range;
+ FOR_EACH_VEC_ELT (m_ranges, i, range)
+ {
+ write_indent (pp, cur_indent);
+ if (i > 0)
+ {
+ const path_summary::event_range *prev_range
+ = m_ranges[i - 1];
+ if (range->m_stack_depth > prev_range->m_stack_depth)
+ {
+ /* Show pushed stack frame(s). */
+ const char *push_prefix = "+--> ";
+ pp_string (pp, start_line_color);
+ pp_string (pp, push_prefix);
+ pp_string (pp, end_line_color);
+ cur_indent += strlen (push_prefix);
+ }
+ }
+ if (range->m_fndecl)
+ {
+ print_fndecl (pp, range->m_fndecl, true);
+ pp_string (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);
+ if (show_depths)
+ pp_printf (pp, " (depth %i)", range->m_stack_depth);
+ pp_newline (pp);
+
+ /* Print a run of events. */
+ {
+ write_indent (pp, cur_indent + per_frame_indent);
+ pp_string (pp, start_line_color);
+ pp_string (pp, "|");
+ 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, cur_indent + per_frame_indent);
+ pp_string (&tmp_pp, start_line_color);
+ pp_string (&tmp_pp, "|");
+ 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 (dc);
+ pp_set_prefix (pp, saved_prefix);
+
+ write_indent (pp, cur_indent + per_frame_indent);
+ pp_string (pp, start_line_color);
+ pp_string (pp, "|");
+ pp_string (pp, end_line_color);
+ pp_newline (pp);
+ }
+
+ if (i < m_ranges.length () - 1)
+ {
+ const path_summary::event_range *next_range
+ = m_ranges[i + 1];
+
+ if (range->m_stack_depth > next_range->m_stack_depth)
+ {
+ if (vbar_column_for_depth.get (next_range->m_stack_depth))
+ {
+ /* Show returning from stack frame(s), by printing
+ something like:
+ " |\n"
+ " <------------ +\n"
+ " |\n". */
+ int vbar_for_next_frame
+ = *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_character (pp, '<');
+ for (int i = indent_for_next_frame + per_frame_indent;
+ i < cur_indent + per_frame_indent - 1; i++)
+ pp_character (pp, '-');
+ pp_character (pp, '+');
+ pp_string (pp, end_line_color);
+ pp_newline (pp);
+ cur_indent = indent_for_next_frame;
+
+ write_indent (pp, vbar_for_next_frame);
+ pp_string (pp, start_line_color);
+ pp_printf (pp, "|");
+ pp_string (pp, end_line_color);
+ pp_newline (pp);
+ }
+ else
+ {
+ /* Handle disjoint paths (e.g. a callback at some later
+ time). */
+ cur_indent = base_indent;
+ }
+ }
+ else if (range->m_stack_depth < next_range->m_stack_depth)
+ {
+ /* Prepare to show pushed stack frame. */
+ gcc_assert (range->m_stack_depth != EMPTY);
+ gcc_assert (range->m_stack_depth != DELETED);
+ vbar_column_for_depth.put (range->m_stack_depth,
+ cur_indent + per_frame_indent);
+ cur_indent += per_frame_indent;
+ }
+
+ }
+ }
+}
+
+} /* end of anonymous namespace for path-printing code. */
+
+/* Print PATH to CONTEXT, according to CONTEXT's path_format. */
+
+void
+default_tree_diagnostic_path_printer (diagnostic_context *context,
+ const diagnostic_path *path)
+{
+ gcc_assert (path);
+
+ const unsigned num_events = path->num_events ();
+
+ switch (context->path_format)
+ {
+ case DPF_NONE:
+ /* Do nothing. */
+ return;
+
+ case DPF_SEPARATE_EVENTS:
+ {
+ /* A note per event. */
+ for (unsigned i = 0; i < num_events; i++)
+ {
+ const diagnostic_event &event = path->get_event (i);
+ label_text event_text (event.get_desc (false));
+ gcc_assert (event_text.m_buffer);
+ diagnostic_event_id_t event_id (i);
+ inform (event.get_location (),
+ "%@ %s", &event_id, event_text.m_buffer);
+ event_text.maybe_free ();
+ }
+ }
+ break;
+
+ case DPF_INLINE_EVENTS:
+ {
+ /* Consolidate related events. */
+ path_summary summary (*path, true);
+ char *saved_prefix = pp_take_prefix (context->printer);
+ pp_set_prefix (context->printer, NULL);
+ summary.print (context, context->show_path_depths);
+ pp_flush (context->printer);
+ pp_set_prefix (context->printer, saved_prefix);
+ }
+ }
+}
+
+/* This has to be here, rather than diagnostic-format-json.cc,
+ since diagnostic-format-json.o is within OBJS-libcommon and thus
+ doesn't have access to trees (for m_fndecl). */
+
+json::value *
+default_tree_make_json_for_path (diagnostic_context *,
+ const diagnostic_path *path)
+{
+ json::array *path_array = new json::array ();
+ for (unsigned i = 0; i < path->num_events (); i++)
+ {
+ const diagnostic_event &event = path->get_event (i);
+
+ json::object *event_obj = new json::object ();
+ if (event.get_location ())
+ event_obj->set ("location",
+ json_from_expanded_location (event.get_location ()));
+ label_text event_text (event.get_desc (false));
+ event_obj->set ("description", new json::string (event_text.m_buffer));
+ event_text.maybe_free ();
+ if (tree fndecl = event.get_fndecl ())
+ {
+ const char *function
+ = identifier_to_locale (lang_hooks.decl_printable_name (fndecl, 2));
+ event_obj->set ("function", new json::string (function));
+ }
+ event_obj->set ("depth",
+ new json::integer_number (event.get_stack_depth ()));
+ path_array->append (event_obj);
+ }
+ return path_array;
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* A subclass of simple_diagnostic_path that adds member functions
+ for adding test events. */
+
+class test_diagnostic_path : public simple_diagnostic_path
+{
+ public:
+ test_diagnostic_path (pretty_printer *event_pp)
+ : simple_diagnostic_path (event_pp)
+ {
+ }
+
+ void add_entry (tree fndecl, int stack_depth)
+ {
+ add_event (UNKNOWN_LOCATION, fndecl, stack_depth,
+ "entering %qE", fndecl);
+ }
+
+ void add_return (tree fndecl, int stack_depth)
+ {
+ add_event (UNKNOWN_LOCATION, fndecl, stack_depth,
+ "returning to %qE", fndecl);
+ }
+
+ void add_call (tree caller, int caller_stack_depth, tree callee)
+ {
+ add_event (UNKNOWN_LOCATION, caller, caller_stack_depth,
+ "calling %qE", callee);
+ add_entry (callee, caller_stack_depth + 1);
+ }
+};
+
+/* 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 ());
+
+ path_summary summary (path, false);
+ ASSERT_EQ (summary.get_num_ranges (), 0);
+
+ test_diagnostic_context dc;
+ summary.print (&dc, true);
+ ASSERT_STREQ ("",
+ pp_formatted_text (dc.printer));
+}
+
+/* Verify that print_path_summary works on a purely intraprocedural path. */
+
+static void
+test_intraprocedural_path (pretty_printer *event_pp)
+{
+ tree fntype_void_void
+ = build_function_type_array (void_type_node, 0, NULL);
+ tree fndecl_foo = build_fn_decl ("foo", fntype_void_void);
+
+ test_diagnostic_path path (event_pp);
+ path.add_event (UNKNOWN_LOCATION, fndecl_foo, 0, "first %qs", "free");
+ path.add_event (UNKNOWN_LOCATION, fndecl_foo, 0, "double %qs", "free");
+
+ ASSERT_FALSE (path.interprocedural_p ());
+
+ path_summary summary (path, false);
+ ASSERT_EQ (summary.get_num_ranges (), 1);
+
+ test_diagnostic_context dc;
+ summary.print (&dc, true);
+ ASSERT_STREQ (" `foo': events 1-2 (depth 0)\n"
+ " |\n"
+ " | (1): first `free'\n"
+ " | (2): double `free'\n"
+ " |\n",
+ pp_formatted_text (dc.printer));
+}
+
+/* Verify that print_path_summary works on an interprocedural path. */
+
+static void
+test_interprocedural_path_1 (pretty_printer *event_pp)
+{
+ /* Build fndecls. The types aren't quite right, but that
+ doesn't matter for the purposes of this test. */
+ tree fntype_void_void
+ = build_function_type_array (void_type_node, 0, NULL);
+ tree fndecl_test = build_fn_decl ("test", fntype_void_void);
+ tree fndecl_make_boxed_int
+ = build_fn_decl ("make_boxed_int", fntype_void_void);
+ tree fndecl_wrapped_malloc
+ = build_fn_decl ("wrapped_malloc", fntype_void_void);
+ tree fndecl_free_boxed_int
+ = build_fn_decl ("free_boxed_int", fntype_void_void);
+ tree fndecl_wrapped_free
+ = build_fn_decl ("wrapped_free", fntype_void_void);
+
+ test_diagnostic_path path (event_pp);
+ path.add_entry (fndecl_test, 0);
+ path.add_call (fndecl_test, 0, fndecl_make_boxed_int);
+ path.add_call (fndecl_make_boxed_int, 1, fndecl_wrapped_malloc);
+ path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_malloc, 2, "calling malloc");
+ path.add_return (fndecl_test, 0);
+ path.add_call (fndecl_test, 0, fndecl_free_boxed_int);
+ path.add_call (fndecl_free_boxed_int, 1, fndecl_wrapped_free);
+ path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_free, 2, "calling free");
+ path.add_return (fndecl_test, 0);
+ path.add_call (fndecl_test, 0, fndecl_free_boxed_int);
+ path.add_call (fndecl_free_boxed_int, 1, fndecl_wrapped_free);
+ path.add_event (UNKNOWN_LOCATION, fndecl_wrapped_free, 2, "calling free");
+ ASSERT_EQ (path.num_events (), 18);
+
+ ASSERT_TRUE (path.interprocedural_p ());
+
+ path_summary summary (path, false);
+ ASSERT_EQ (summary.get_num_ranges (), 9);
+
+ test_diagnostic_context dc;
+ summary.print (&dc, 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 (dc.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)
+{
+ /* Build fndecls. The types aren't quite right, but that
+ doesn't matter for the purposes of this test. */
+ tree fntype_void_void
+ = build_function_type_array (void_type_node, 0, NULL);
+ tree fndecl_foo = build_fn_decl ("foo", fntype_void_void);
+ tree fndecl_bar = build_fn_decl ("bar", fntype_void_void);
+ tree fndecl_baz = build_fn_decl ("baz", fntype_void_void);
+
+ test_diagnostic_path path (event_pp);
+ path.add_entry (fndecl_foo, 0);
+ path.add_call (fndecl_foo, 0, fndecl_bar);
+ path.add_call (fndecl_bar, 1, fndecl_baz);
+ path.add_return (fndecl_bar, 1);
+ path.add_call (fndecl_bar, 1, fndecl_baz);
+ ASSERT_EQ (path.num_events (), 8);
+
+ ASSERT_TRUE (path.interprocedural_p ());
+
+ path_summary summary (path, false);
+ ASSERT_EQ (summary.get_num_ranges (), 5);
+
+ test_diagnostic_context dc;
+ summary.print (&dc, 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 (dc.printer));
+}
+
+/* Verify that print_path_summary is sane in the face of a recursive
+ diagnostic_path. */
+
+static void
+test_recursion (pretty_printer *event_pp)
+{
+ tree fntype_void_void
+ = build_function_type_array (void_type_node, 0, NULL);
+ tree fndecl_factorial = build_fn_decl ("factorial", fntype_void_void);
+
+ test_diagnostic_path path (event_pp);
+ path.add_entry (fndecl_factorial, 0);
+ for (int depth = 0; depth < 3; depth++)
+ path.add_call (fndecl_factorial, depth, fndecl_factorial);
+ ASSERT_EQ (path.num_events (), 7);
+
+ ASSERT_TRUE (path.interprocedural_p ());
+
+ path_summary summary (path, false);
+ ASSERT_EQ (summary.get_num_ranges (), 4);
+
+ test_diagnostic_context dc;
+ summary.print (&dc, 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 (dc.printer));
+}
+
+/* Run all of the selftests within this file. */
+
+void
+tree_diagnostic_path_cc_tests ()
+{
+ auto_fix_quotes fix_quotes;
+ pretty_printer *event_pp = global_dc->printer->clone ();
+ pp_show_color (event_pp) = 0;
+ test_empty_path (event_pp);
+ test_intraprocedural_path (event_pp);
+ test_interprocedural_path_1 (event_pp);
+ test_interprocedural_path_2 (event_pp);
+ test_recursion (event_pp);
+ delete event_pp;
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
unwound macro expansion trace. That's the part generated by this
function. */
-static void
+void
maybe_unwind_expanded_macro_loc (diagnostic_context *context,
- const diagnostic_info *diagnostic,
location_t where)
{
const struct line_map *map;
unsigned ix;
loc_map_pair loc, *iter;
+ const location_t original_loc = where;
+
map = linemap_lookup (line_table, where);
if (!linemap_macro_expansion_map_p (map))
return;
first macro which expansion triggered this trace was expanded
inside a system header. */
int saved_location_line =
- expand_location_to_spelling_point (diagnostic_location (diagnostic)).line;
+ expand_location_to_spelling_point (original_loc).line;
if (!LINEMAP_SYSP (ord_map))
FOR_EACH_VEC_ELT (loc_vec, ix, iter)
virt_loc_aware_diagnostic_finalizer (diagnostic_context *context,
diagnostic_info *diagnostic)
{
- maybe_unwind_expanded_macro_loc (context, diagnostic,
- diagnostic_location (diagnostic));
+ maybe_unwind_expanded_macro_loc (context, diagnostic_location (diagnostic));
}
/* Default tree printer. Handles declarations only. */
diagnostic_starter (context) = default_tree_diagnostic_starter;
diagnostic_finalizer (context) = default_diagnostic_finalizer;
diagnostic_format_decoder (context) = default_tree_printer;
+ context->print_path = default_tree_diagnostic_path_printer;
+ context->make_json_for_path = default_tree_make_json_for_path;
}
bool default_tree_printer (pretty_printer *, text_info *, const char *,
int, bool, bool, bool, bool *, const char **);
+extern void default_tree_diagnostic_path_printer (diagnostic_context *,
+ const diagnostic_path *);
+extern json::value *default_tree_make_json_for_path (diagnostic_context *,
+ const diagnostic_path *);
+
+extern void maybe_unwind_expanded_macro_loc (diagnostic_context *context,
+ location_t where);
+
#endif /* ! GCC_TREE_DIAGNOSTIC_H */
+2020-01-10 David Malcolm <dmalcolm@redhat.com>
+
+ * include/line-map.h (class diagnostic_path): New forward decl.
+ (rich_location::get_path): New accessor.
+ (rich_location::set_path): New function.
+ (rich_location::m_path): New field.
+ * line-map.c (rich_location::rich_location): Initialize m_path.
+
2020-01-01 Jakub Jelinek <jakub@redhat.com>
Update copyright years.
}
class fixit_hint;
+class diagnostic_path;
/* A "rich" source code location, for use when printing diagnostics.
A rich_location has one or more carets&ranges, where the carets
return !m_fixits_cannot_be_auto_applied;
}
+ /* An optional path through the code. */
+ const diagnostic_path *get_path () const { return m_path; }
+ void set_path (const diagnostic_path *path) { m_path = path; }
+
private:
bool reject_impossible_fixit (location_t where);
void stop_supporting_fixits ();
bool m_seen_impossible_fixit;
bool m_fixits_cannot_be_auto_applied;
+
+ const diagnostic_path *m_path;
};
/* A struct for the result of range_label::get_text: a NUL-terminated buffer
m_have_expanded_location (false),
m_fixit_hints (),
m_seen_impossible_fixit (false),
- m_fixits_cannot_be_auto_applied (false)
+ m_fixits_cannot_be_auto_applied (false),
+ m_path (NULL)
{
add_range (loc, SHOW_RANGE_WITH_CARET, label);
}