Add diagnostic paths
authorDavid Malcolm <dmalcolm@redhat.com>
Fri, 10 Jan 2020 21:22:12 +0000 (21:22 +0000)
committerDavid Malcolm <dmalcolm@gcc.gnu.org>
Fri, 10 Jan 2020 21:22:12 +0000 (21:22 +0000)
This patch adds support for associating a "diagnostic_path" with a
diagnostic: a sequence of events predicted by the compiler that leads to
the problem occurring, with their locations in the user's source,
text descriptions, and stack information (for handling interprocedural
paths).

For example, the following (hypothetical) error has a 3-event
intraprocedural 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 patch adds a new "%@" format code for printing event IDs, so that
in the above, the description of event (3) mentions event (1), showing
the user where the bogus NULL value comes from (the event IDs are
colorized to draw the user's attention to them).

There is a separation between data vs presentation: the above shows how
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.

Examples of how interprocedural paths are printed can be seen in the
test suite (which uses a plugin to exercise the code without relying
on specific warnings using this functionality).

Other output formats include
- JSON,
- printing each event as a separate "note", and
- to not emit paths.

gcc/ChangeLog:
* 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.

gcc/c-family/ChangeLog:
* 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.

gcc/testsuite/ChangeLog:
* 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.

libcpp/ChangeLog:
* 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.

From-SVN: r280142

42 files changed:
gcc/ChangeLog
gcc/Makefile.in
gcc/c-family/ChangeLog
gcc/c-family/c-format.c
gcc/c-family/c-format.h
gcc/common.opt
gcc/coretypes.h
gcc/diagnostic-color.c
gcc/diagnostic-event-id.h [new file with mode: 0644]
gcc/diagnostic-format-json.cc
gcc/diagnostic-path.h [new file with mode: 0644]
gcc/diagnostic-show-locus.c
gcc/diagnostic.c
gcc/diagnostic.def
gcc/diagnostic.h
gcc/doc/invoke.texi
gcc/gcc-rich-location.h
gcc/opts.c
gcc/pretty-print.c
gcc/selftest-run-tests.c
gcc/selftest.h
gcc/testsuite/ChangeLog
gcc/testsuite/gcc.dg/format/gcc_diag-10.c
gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-default.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-1.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-2.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-3.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-none.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-separate-events.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/plugin/plugin.exp
gcc/toplev.c
gcc/tree-diagnostic-path.cc [new file with mode: 0644]
gcc/tree-diagnostic.c
gcc/tree-diagnostic.h
libcpp/ChangeLog
libcpp/include/line-map.h
libcpp/line-map.c

index 6b24415a4d632bdbefb95ffa926f2950d1b0c60c..9e9f8221af4fd57cd65a6b1e8f722e7dc317292b 100644 (file)
@@ -1,3 +1,83 @@
+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
index 61b512c9b724f9447f29d0467895129338ff6ab7..864f8e3ef2c0b725631c4a0bf4aec1ecce00a1bb 100644 (file)
@@ -1521,6 +1521,7 @@ OBJS = \
        tree-data-ref.o \
        tree-dfa.o \
        tree-diagnostic.o \
+       tree-diagnostic-path.o \
        tree-dump.o \
        tree-eh.o \
        tree-emutls.o \
index 5448acf2f7b6d9af2c60f2cca5ded6b5c5573188..f21142aa19e7e3b012593ee1707d740236b10b53 100644 (file)
@@ -1,3 +1,10 @@
+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
index 96437906b50c6b7138b8e996c2806dadfe32145c..487edc7a5d772b68a32475fe22e6582808dd3466 100644 (file)
@@ -65,6 +65,7 @@ struct function_format_info
 
 /* 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;
@@ -752,6 +753,7 @@ static const format_char_info asm_fprintf_char_table[] =
   { "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 }, \
@@ -4988,6 +4990,11 @@ init_dynamic_diag_info (void)
       || 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)
index 5587b033536d3dc0994e99adfa6de16ded97eccc..ff8a9f988c379f60d04e94eca05cc600ba22e69a 100644 (file)
@@ -303,6 +303,7 @@ struct format_kind_info
 #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
index 9fc921109caa7744ad3ccde9d930cf53cfdf0811..e9b29fb4ee06028b5cccc0adb636821807a2981d 100644 (file)
@@ -1338,6 +1338,26 @@ fdiagnostics-show-cwe
 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.
index 4a8612541825eb15dedf00f8ce49e95fba57a8dd..d8fd50d79a44b8841c79bb703487e32aa00ad908 100644 (file)
@@ -153,6 +153,7 @@ struct cl_decoded_option;
 struct cl_option_handlers;
 struct diagnostic_context;
 class pretty_printer;
+class diagnostic_event_id_t;
 
 template<typename T> struct array_traits;
 
index 75fdc70dec63744bd4b2816b1b0c6a40028cee6e..d55479529211ce85c6e1ffdaf7206f2559223d23 100644 (file)
@@ -90,6 +90,7 @@ static struct color_cap color_dict[] =
   { "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 },
@@ -126,7 +127,7 @@ colorize_stop (bool show_color)
 
 /* 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'
diff --git a/gcc/diagnostic-event-id.h b/gcc/diagnostic-event-id.h
new file mode 100644 (file)
index 0000000..3c757fe
--- /dev/null
@@ -0,0 +1,61 @@
+/* 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 */
index b252a21a4d88113d3f57dd456794e8ab8a2429c3..7bda5c4ba8344360ff02f56874a5f45ff1e6a6a0 100644 (file)
@@ -42,7 +42,7 @@ static json::array *cur_children_array;
 
 /* Generate a JSON object for LOC.  */
 
-static json::object *
+json::value *
 json_from_expanded_location (location_t loc)
 {
   expanded_location exploc = expand_location (loc);
@@ -232,6 +232,13 @@ json_end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic,
       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.  */
@@ -288,6 +295,7 @@ diagnostic_output_format_init (diagnostic_context *context,
        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;
diff --git a/gcc/diagnostic-path.h b/gcc/diagnostic-path.h
new file mode 100644 (file)
index 0000000..d005da3
--- /dev/null
@@ -0,0 +1,149 @@
+/* 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 */
index 4ca8efe7847abe792a03a7fa3dd023965811863d..4618b4edb7d19846e2ce74ad07fc26212d66e761 100644 (file)
@@ -87,7 +87,17 @@ class colorizer
             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); }
@@ -385,6 +395,7 @@ class layout
   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;
@@ -958,6 +969,7 @@ layout::layout (diagnostic_context * context,
   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 ()),
@@ -1770,7 +1782,10 @@ layout::print_any_labels (linenum_type row)
              {
                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;
@@ -2506,7 +2521,9 @@ layout::print_line (linenum_type row)
    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
@@ -2515,10 +2532,11 @@ gcc_rich_location::add_location_if_nearby (location_t loc)
   location_range loc_range;
   loc_range.m_loc = loc;
   loc_range.m_range_display_kind = SHOW_RANGE_WITHOUT_CARET;
-  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;
 }
 
index 1ab420cbf8702cf2e4132b2b02c54e6d60bb2fb2..72afd7c6adf08fcce462e851daafce69b935f17f 100644 (file)
@@ -33,6 +33,7 @@ along with GCC; see the file COPYING3.  If not see
 #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"
@@ -187,6 +188,8 @@ diagnostic_initialize (diagnostic_context *context, int n_opts)
   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;
@@ -658,6 +661,38 @@ diagnostic_report_current_module (diagnostic_context *context, location_t where)
     }
 }
 
+/* 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)
@@ -1123,6 +1158,8 @@ diagnostic_report_diagnostic (diagnostic_context *context,
 
   context->lock--;
 
+  diagnostic_show_any_path (context, diagnostic);
+
   return true;
 }
 
@@ -1758,6 +1795,95 @@ auto_diagnostic_group::~auto_diagnostic_group ()
     }
 }
 
+/* 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 = &ap;
+  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.  */
index 3eb7b3e32dadb344ae800f95f019da680abe73c1..0a1c57307d0c5ee9b03773ce2e2835bfc2b94eed 100644 (file)
@@ -38,6 +38,11 @@ DEFINE_DIAGNOSTIC_KIND (DK_WARNING, "warning: ", "warning")
 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)
index a670e0ca743939cffff4baca4e0092ebde12c0e7..307dbcfb34a6f8257ce7d6b51903e75bff0bf131 100644 (file)
@@ -35,6 +35,23 @@ enum diagnostics_output_format
   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.  */
@@ -80,6 +97,7 @@ typedef void (*diagnostic_finalizer_fn) (diagnostic_context *,
                                         diagnostic_t);
 
 class edit_context;
+namespace json { class value; }
 
 /* This data structure bundles altogether any information relevant to
    the context of a diagnostic message.  */
@@ -134,6 +152,12 @@ struct diagnostic_context
      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;
@@ -208,6 +232,9 @@ struct diagnostic_context
      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;
 
@@ -351,6 +378,7 @@ extern void diagnostic_report_current_module (diagnostic_context *, location_t);
 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 *,
@@ -442,4 +470,6 @@ extern void diagnostic_output_format_init (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 */
index ba87fcce672539bd09a9eb93ccaa5649b7217421..f2c805c0a64d7f5c78863fb1bbfb029fc7618313 100644 (file)
@@ -281,6 +281,8 @@ Objective-C and Objective-C++ Dialects}.
 -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
@@ -3886,7 +3888,7 @@ for 88-color and 256-color modes background colors.
 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
@@ -3910,6 +3912,12 @@ SGR substring for warning: markers.
 @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.
@@ -4120,6 +4128,114 @@ Specifying the @option{-fno-elide-type} flag suppresses that behavior.
 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
@@ -4315,6 +4431,53 @@ to but not including @code{next} with @code{string}'s value.  Deletions
 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
index e7d74352249286c1f20c2513685e8f7b23d77d3a..4119476e68c87e8ce21d4b29171934972fcd65cf 100644 (file)
@@ -62,7 +62,9 @@ class gcc_rich_location : public rich_location
 
      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.
index d5efadb4334640cab854fa8483ef29316a5a5b42..fa4804c8d15edbde49df9ef19cb9be44b5cfe041 100644 (file)
@@ -2411,6 +2411,14 @@ common_handle_option (struct gcc_options *opts,
       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;
index ad8f3effc4d69654a58d138a6048170466f1a884..817c1059e08cbd3a13dfca129ea70163a4218db5 100644 (file)
@@ -24,6 +24,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "pretty-print.h"
 #include "diagnostic-color.h"
+#include "diagnostic-event-id.h"
 #include "selftest.h"
 
 #if HAVE_ICONV
@@ -1039,6 +1040,7 @@ pp_indent (pretty_printer *pp)
    %>: 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.
@@ -1428,6 +1430,21 @@ pp_format (pretty_printer *pp, text_info *text)
          }
          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;
@@ -2338,6 +2355,21 @@ test_pp_format ()
   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 }; 
index 241671d4bce0c03dadabaff66aad60549d03272c..f7f0bd34953566e189e9e345d904f918f9138705 100644 (file)
@@ -96,6 +96,7 @@ selftest::run_tests ()
   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.  */
index dfdb09dd894387cc75256e740f288da6a8176777..140784d6c14ed6d8f91aea87ed16b2a8c63dfd6c 100644 (file)
@@ -256,6 +256,7 @@ extern void sreal_c_tests ();
 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 ();
index 527d53bf0efea1ead117fc7731f8d558d4e83476..6576aee81c81a154053a5be57df6ac2b21a16b92 100644 (file)
        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
index ba2629b3ecbae24905ebb604030fc66bf2daacb3..a2f99feefc93ec8641490aa5b826d4285f0dcb37 100644 (file)
@@ -22,6 +22,9 @@ typedef struct gimple gimple;
 /* 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);
@@ -30,7 +33,7 @@ void tdiag (const char*, ...) FORMAT (tdiag);
 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 " } */
@@ -38,6 +41,7 @@ void test_diag (tree t, gimple *gc)
 
   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" } */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-default.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-default.c
new file mode 100644 (file)
index 0000000..5712dbd
--- /dev/null
@@ -0,0 +1,142 @@
+/* { 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);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-1.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-1.c
new file mode 100644 (file)
index 0000000..430d817
--- /dev/null
@@ -0,0 +1,142 @@
+/* { 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);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-2.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-2.c
new file mode 100644 (file)
index 0000000..c2bfabe
--- /dev/null
@@ -0,0 +1,154 @@
+/* { 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);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-3.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-inline-events-3.c
new file mode 100644 (file)
index 0000000..386cac9
--- /dev/null
@@ -0,0 +1,154 @@
+/* { 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);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-none.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-none.c
new file mode 100644 (file)
index 0000000..0a29f67
--- /dev/null
@@ -0,0 +1,43 @@
+/* { 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);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-separate-events.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-path-format-separate-events.c
new file mode 100644 (file)
index 0000000..dcb72c0
--- /dev/null
@@ -0,0 +1,44 @@
+/* { 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);
+}
+
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-1.c
new file mode 100644 (file)
index 0000000..7b11c90
--- /dev/null
@@ -0,0 +1,38 @@
+/* { 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 } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c
new file mode 100644 (file)
index 0000000..391aeb9
--- /dev/null
@@ -0,0 +1,56 @@
+/* { 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 "" } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-3.c
new file mode 100644 (file)
index 0000000..6971d7c
--- /dev/null
@@ -0,0 +1,38 @@
+/* { 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 ".*" } */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.c
new file mode 100644 (file)
index 0000000..847b6d4
--- /dev/null
@@ -0,0 +1,84 @@
+/* { 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 "" } */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
new file mode 100644 (file)
index 0000000..cf05ca3
--- /dev/null
@@ -0,0 +1,460 @@
+/* { 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;
+}
index 7a8dbd2adeab46052ffec0b3f560fa4fb348b5dc..c02b008271528262600bff74dbd08d3db74dfb08 100644 (file)
@@ -95,6 +95,17 @@ set plugin_test_list [list \
          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 \
index 0d70f6c07a0398c3a43e7df8a840aacab16d24d8..4c8be502c719b9242f757bfe740ff571dda84998 100644 (file)
@@ -1181,6 +1181,10 @@ general_init (const char *argv0, bool init_signals)
     = 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
diff --git a/gcc/tree-diagnostic-path.cc b/gcc/tree-diagnostic-path.cc
new file mode 100644 (file)
index 0000000..9e2ff10
--- /dev/null
@@ -0,0 +1,820 @@
+/* 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 */
index efd8a82fe418e6a71138bf0f6711ce814f1d3a08..8422714aecbc2de2dafad3f8776d4bfc8ccb26e3 100644 (file)
@@ -96,9 +96,8 @@ struct loc_map_pair
    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;
@@ -106,6 +105,8 @@ maybe_unwind_expanded_macro_loc (diagnostic_context *context,
   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;
@@ -142,7 +143,7 @@ maybe_unwind_expanded_macro_loc (diagnostic_context *context,
      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)
@@ -238,8 +239,7 @@ void
 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.  */
@@ -312,4 +312,6 @@ tree_diagnostics_defaults (diagnostic_context *context)
   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;
 }
index 3069c0f27be0c59154f5dfa590f77a9eadddc3b9..40dc9fa0e8319600feb0e7e833ecd688b5d28574 100644 (file)
@@ -57,4 +57,12 @@ void tree_diagnostics_defaults (diagnostic_context *context);
 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 */
index f8c539f7c5e13746c593841f2bb82bfff7f82fdd..fc22011c7d22b1a9a6dd61324c5fc3b848618f7c 100644 (file)
@@ -1,3 +1,11 @@
+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.
index 191bd429d13fe3276365f21dbdf42ed8ceb7252b..dbbc13762e39dbcc9206eda5fe9dc27f18cf7145 100644 (file)
@@ -1432,6 +1432,7 @@ semi_embedded_vec<T, NUM_EMBEDDED>::truncate (int len)
 }
 
 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
@@ -1727,6 +1728,10 @@ class rich_location
     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 ();
@@ -1751,6 +1756,8 @@ protected:
 
   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
index 2e12bf77b32e2e8c4f83b43c305e65f8f3bc80ea..8a390d0857be2b43ba94b527079b3394a0bdffe1 100644 (file)
@@ -2006,7 +2006,8 @@ rich_location::rich_location (line_maps *set, location_t loc,
   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);
 }