Previously the diagnostic subsystem supported a one-deep hierarchy via auto_diagnostic_group, for associating notes with the warning/error they annotate; this only affects SARIF output, not text output.
This patch adds support to the diagnostics subsystem for capturing arbitrarily deep nesting structure within diagnostic messages. This patch: * adds the ability to express nesting internally when building diagnostics * captures the nesting in SARIF output in the form documented in SG15's P3358R0 ("SARIF for Structured Diagnostics") via a "nestingLevel" property * adds a new experimental mode to text output to see the hierarchy, via: -fdiagnostics-set-output=text:experimental-nesting=yes * adds test coverage via a plugin, which with the above option emits: • note: child 0 • note: grandchild 0 0 • note: grandchild 0 1 • note: grandchild 0 2 • note: child 1 • note: grandchild 1 0 • note: grandchild 1 1 • note: grandchild 1 2 • note: child 2 • note: grandchild 2 0 • note: grandchild 2 1 • note: grandchild 2 2 using '*' rather than '•' if the text_art::theme is ascii-only. My hope is to eventually: (a) use this to improve C++'s template diagnostics (b) remove the "experimental" caveat from the the text output mode but this patch doesn't touch the C++ frontend, leaving both of these to followup work. gcc/c-family/ChangeLog: PR other/116253 * c-opts.cc (c_diagnostic_text_finalizer): Use text_output.build_indent_prefix for prefix to diagnostic_show_locus. gcc/ChangeLog: PR other/116253 * diagnostic-core.h (class auto_diagnostic_nesting_level): New. * diagnostic-format-sarif.cc (class sarif_builder): Update leading comment re nesting of diagnostics. (sarif_result::on_nested_diagnostic): Add nestingLevel property. * diagnostic-format-text.cc (on_report_diagnostic): If we're showing nested diagnostics, then print changes of location on a new line, indented, and update m_last_location. (diagnostic_text_output_format::build_prefix): If m_show_nesting, then potentially add indentation and a bullet point. (get_bullet_point_unichar): New. (use_unicode_p): New. (diagnostic_text_output_format::build_indent_prefix): New. * diagnostic-format-text.h (diagnostic_text_output_format::diagnostic_text_output_format): Initialize m_show_nesting and m_show_nesting_levels. (diagnostic_text_output_format::build_indent_prefix): New decl. (diagnostic_text_output_format::show_nesting_p): New accessor (diagnostic_text_output_format::show_locations_in_nesting_p): Likewise. (diagnostic_text_output_format::set_show_nesting): New. (diagnostic_text_output_format::set_show_locations_in_nesting): New. (diagnostic_text_output_format::set_show_nesting_levels): New. (diagnostic_text_output_format::m_show_nesting): New field. (diagnostic_text_output_format::m_show_locations_in_nesting): New field. (diagnostic_text_output_format::m_show_nesting_levels): New field. * diagnostic-global-context.cc (auto_diagnostic_nesting_level::auto_diagnostic_nesting_level): New. (auto_diagnostic_nesting_level::~auto_diagnostic_nesting_level): New. * diagnostic-show-locus.cc (layout_printer::print): Temporarily set DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE. * diagnostic.cc (diagnostic_context::initialize): Update for renaming of m_nesting_depth to m_group_nesting_depth and initialize m_diagnostic_nesting_level. (diagnostic_context::finish): Update for renaming of m_nesting_depth to m_group_nesting_depth. (diagnostic_context::report_diagnostic): Likewise. (diagnostic_context::begin_group): Likewise. (diagnostic_context::end_group): Likewise. (diagnostic_context::push_nesting_level): New. (diagnostic_context::pop_nesting_level): New. (diagnostic_context::set_diagnostic_buffer): Update for renaming of m_nesting_depth to m_group_nesting_depth. Assert that we don't have nested diagnostics. * diagnostic.h (diagnostic_context::push_nesting_level): New decl. (diagnostic_context::pop_nesting_level): New decl. (diagnostic_context::get_diagnostic_nesting_level): New accessor. (diagnostic_context::build_indent_prefix): New decl. (diagnostic_context::m_diagnostic_groups): Rename m_nesting_depth to m_group_nesting_depth and add field m_diagnostic_nesting_level. * doc/invoke.texi (fdiagnostics-add-output): Add note about "experimental" schemes, keys, and values. Add keys "experimental-nesting", "experimental-nesting-show-locations", and "experimental-nesting-show-levels" to text scheme. * opts-diagnostic.cc (text_scheme_handler::make_sink): Add keys "experimental-nesting", "experimental-nesting-show-locations", and "experimental-nesting-show-levels". gcc/testsuite/ChangeLog: PR other/116253 * gcc.dg/plugin/diagnostic-test-nesting-sarif.c: New test. * gcc.dg/plugin/diagnostic-test-nesting-sarif.py: New test. * gcc.dg/plugin/diagnostic-test-nesting-text-indented-show-levels.c: New test. * gcc.dg/plugin/diagnostic-test-nesting-text-indented-unicode.c: New test. * gcc.dg/plugin/diagnostic-test-nesting-text-indented.c: New test. * gcc.dg/plugin/diagnostic-test-nesting-text-plain.c: New test. * gcc.dg/plugin/diagnostic_plugin_test_nesting.c: New test plugin. * gcc.dg/plugin/plugin.exp: Add the above. Signed-off-by: David Malcolm <dmalc...@redhat.com> --- gcc/c-family/c-opts.cc | 2 +- gcc/diagnostic-core.h | 12 ++ gcc/diagnostic-format-sarif.cc | 16 +- gcc/diagnostic-format-text.cc | 121 ++++++++++++++- gcc/diagnostic-format-text.h | 34 +++- gcc/diagnostic-global-context.cc | 12 ++ gcc/diagnostic-show-locus.cc | 5 + gcc/diagnostic.cc | 29 +++- gcc/diagnostic.h | 15 +- gcc/doc/invoke.texi | 18 +++ gcc/opts-diagnostic.cc | 32 +++- .../plugin/diagnostic-test-nesting-sarif.c | 16 ++ .../plugin/diagnostic-test-nesting-sarif.py | 39 +++++ ...c-test-nesting-text-indented-show-levels.c | 24 +++ ...ostic-test-nesting-text-indented-unicode.c | 24 +++ .../diagnostic-test-nesting-text-indented.c | 24 +++ .../diagnostic-test-nesting-text-plain.c | 8 + .../plugin/diagnostic_plugin_test_nesting.c | 145 ++++++++++++++++++ gcc/testsuite/gcc.dg/plugin/plugin.exp | 6 + 19 files changed, 560 insertions(+), 22 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-sarif.c create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-sarif.py create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-text-indented-show-levels.c create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-text-indented-unicode.c create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-text-indented.c create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-text-plain.c create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_nesting.c diff --git a/gcc/c-family/c-opts.cc b/gcc/c-family/c-opts.cc index b920b7d347d1..9a49874bb5e9 100644 --- a/gcc/c-family/c-opts.cc +++ b/gcc/c-family/c-opts.cc @@ -176,7 +176,7 @@ c_diagnostic_text_finalizer (diagnostic_text_output_format &text_output, { pretty_printer *const pp = text_output.get_printer (); char *saved_prefix = pp_take_prefix (pp); - pp_set_prefix (pp, NULL); + pp_set_prefix (pp, text_output.build_indent_prefix (false)); pp_newline (pp); diagnostic_show_locus (&text_output.get_context (), diagnostic->richloc, diagnostic->kind, pp); diff --git a/gcc/diagnostic-core.h b/gcc/diagnostic-core.h index 0fddf25403ce..46f33d6553e4 100644 --- a/gcc/diagnostic-core.h +++ b/gcc/diagnostic-core.h @@ -48,6 +48,18 @@ class auto_diagnostic_group ~auto_diagnostic_group (); }; +/* RAII-style class for nesting hierarchical diagnostics. + Any diagnostics emitted within the lifetime of this object + will be treated as one level of nesting deeper than diagnostics + emitted outside the lifetime of the object. */ + +class auto_diagnostic_nesting_level +{ + public: + auto_diagnostic_nesting_level (); + ~auto_diagnostic_nesting_level (); +}; + /* Forward decl. */ class diagnostic_metadata; /* See diagnostic-metadata.h. */ diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc index 5a3fd3f5dac8..0d56475dc6da 100644 --- a/gcc/diagnostic-format-sarif.cc +++ b/gcc/diagnostic-format-sarif.cc @@ -658,9 +658,13 @@ private: - secondary ranges without labels (as related locations) Known limitations: - - GCC supports one-deep nesting of diagnostics (via auto_diagnostic_group), - but we only capture location and message information from such nested - diagnostics (e.g. we ignore fix-it hints on them) + - GCC supports nesting of diagnostics (one-deep nesting via + auto_diagnostic_group, and arbitrary nesting via + auto_diagnostic_nesting_level). These are captured in the SARIF + as related locations, and so we only capture location and message + information from such nested diagnostics (e.g. we ignore fix-it + hints on them). Diagnostics within an auto_diagnostic_nesting_level + have their nesting level captured as a property. - although we capture command-line arguments (section 3.20.2), we don't yet capture response files. - doesn't capture "artifact.encoding" property @@ -1222,6 +1226,12 @@ sarif_result::on_nested_diagnostic (const diagnostic_info &diagnostic, pp_clear_output_area (builder.get_printer ()); location_obj->set<sarif_message> ("message", std::move (message_obj)); + /* Add nesting level, as per "P3358R0 SARIF for Structured Diagnostics" + https://wg21.link/P3358R0 */ + sarif_property_bag &bag = location_obj->get_or_create_properties (); + bag.set_integer ("nestingLevel", + builder.get_context ().get_diagnostic_nesting_level ()); + add_related_location (std::move (location_obj), builder); } diff --git a/gcc/diagnostic-format-text.cc b/gcc/diagnostic-format-text.cc index fd47ca4a15b2..217733784e8d 100644 --- a/gcc/diagnostic-format-text.cc +++ b/gcc/diagnostic-format-text.cc @@ -220,9 +220,35 @@ on_report_diagnostic (const diagnostic_info &diagnostic, if (m_context.m_show_option_requested) print_option_information (diagnostic, orig_diag_kind); + /* If we're showing nested diagnostics, then print the location + on a new line, indented. */ + if (m_show_nesting && m_show_locations_in_nesting) + { + const int nesting_level = get_context ().get_diagnostic_nesting_level (); + if (nesting_level > 0) + { + pp_set_prefix (pp, nullptr); + char *indent_prefix = build_indent_prefix (false); + /* Only print changes of location. */ + if (diagnostic_location (&diagnostic) + != get_context ().m_last_location) + { + const expanded_location s + = diagnostic_expand_location (&diagnostic); + label_text location_text = get_location_text (s); + pp_newline (pp); + pp_printf (pp, "%s%s", indent_prefix, location_text.get ()); + } + pp_set_prefix (pp, indent_prefix); + } + } + (*diagnostic_text_finalizer (&m_context)) (*this, &diagnostic, orig_diag_kind); + + if (m_show_nesting && m_show_locations_in_nesting) + get_context ().m_last_location = diagnostic_location (&diagnostic); } void @@ -257,8 +283,12 @@ after_diagnostic (const diagnostic_info &diagnostic) } /* Return a malloc'd string describing a location and the severity of the - diagnostic, e.g. "foo.c:42:10: error: ". The caller is responsible for - freeing the memory. */ + diagnostic, e.g. "foo.c:42:10: error: ". + + If m_show_nesting, then the above will be preceded by indentation to show + the level, and a bullet point. + + The caller is responsible for freeing the memory. */ char * diagnostic_text_output_format:: build_prefix (const diagnostic_info &diagnostic) const @@ -275,12 +305,22 @@ build_prefix (const diagnostic_info &diagnostic) const text_ce = colorize_stop (pp_show_color (pp)); } - const expanded_location s = diagnostic_expand_location (&diagnostic); - label_text location_text = get_location_text (s); - - char *result = build_message_string ("%s %s%s%s", location_text.get (), - text_cs, text, text_ce); - return result; + const int nesting_level = get_context ().get_diagnostic_nesting_level (); + if (m_show_nesting && nesting_level > 0) + { + char *indent_prefix = build_indent_prefix (true); + char *result = build_message_string ("%s%s%s%s", indent_prefix, + text_cs, text, text_ce); + free (indent_prefix); + return result; + } + else + { + const expanded_location s = diagnostic_expand_location (&diagnostic); + label_text location_text = get_location_text (s); + return build_message_string ("%s %s%s%s", location_text.get (), + text_cs, text, text_ce); + } } /* Same as build_prefix, but only the source FILE is given. */ @@ -294,6 +334,71 @@ diagnostic_text_output_format::file_name_as_prefix (const char *f) const return build_message_string ("%s%s:%s ", locus_cs, f, locus_ce); } +/* Get the unicode code point for bullet points when showing + nested diagnostics. */ + +static unsigned +get_bullet_point_unichar (bool unicode) +{ + if (unicode) + return 0x2022; /* U+2022: Bullet */ + else + return '*'; +} + +/* Return true if DC's theme supports unicode characters. */ + +static bool +use_unicode_p (const diagnostic_context &dc) +{ + if (text_art::theme *theme = dc.get_diagram_theme ()) + return theme->unicode_p (); + else + return false; +} + +/* Get the unicode code point for bullet points when showing + nested diagnostics. */ + +static unsigned +get_bullet_point_unichar (diagnostic_context &dc) +{ + return get_bullet_point_unichar (use_unicode_p (dc)); +} + +/* Return a malloc'd string for use as a prefix to show indentation. + If m_show_nesting is false, or we're at the top-level, then the + result will be the empty string. + + If m_show_nesting, then the result will contain indentation to show + the nesting level, then either a bullet point (if WITH_BULLET is true), + or a space. + + The caller is responsible for freeing the memory. */ + +char * +diagnostic_text_output_format::build_indent_prefix (bool with_bullet) const +{ + if (!m_show_nesting) + return xstrdup (""); + + const int nesting_level = get_context ().get_diagnostic_nesting_level (); + if (nesting_level == 0) + return xstrdup (""); + + pretty_printer pp; + for (int i = 0; i < nesting_level; i++) + pp_string (&pp, " "); + if (with_bullet) + pp_unicode_character (&pp, get_bullet_point_unichar (get_context ())); + else + pp_space (&pp); + pp_space (&pp); + if (m_show_nesting_levels) + pp_printf (&pp, "(level %i):", nesting_level); + return xstrdup (pp_formatted_text (&pp)); +} + /* Add a purely textual note with text GMSGID and with LOCATION. */ void diff --git a/gcc/diagnostic-format-text.h b/gcc/diagnostic-format-text.h index 78d1b2da0a0c..f01d8c118e62 100644 --- a/gcc/diagnostic-format-text.h +++ b/gcc/diagnostic-format-text.h @@ -39,7 +39,9 @@ public: m_column_policy (context), m_last_module (nullptr), m_includes_seen (nullptr), - m_follows_reference_printer (follows_reference_printer) + m_follows_reference_printer (follows_reference_printer), + m_show_nesting (false), + m_show_nesting_levels (false) {} ~diagnostic_text_output_format (); @@ -73,6 +75,8 @@ public: char *file_name_as_prefix (const char *) const; + char *build_indent_prefix (bool with_bullet) const; + void print_path (const diagnostic_path &path); bool show_column_p () const { return get_context ().m_show_column; } @@ -83,6 +87,22 @@ public: } diagnostic_location_print_policy get_location_print_policy () const; + bool show_nesting_p () const { return m_show_nesting; } + bool show_locations_in_nesting_p () const + { + return m_show_locations_in_nesting; + } + + void set_show_nesting (bool show_nesting) { m_show_nesting = show_nesting; } + void set_show_locations_in_nesting (bool val) + { + m_show_locations_in_nesting = val; + } + void set_show_nesting_levels (bool show_nesting_levels) + { + m_show_nesting_levels = show_nesting_levels; + } + protected: void print_any_cwe (const diagnostic_info &diagnostic); void print_any_rules (const diagnostic_info &diagnostic); @@ -112,6 +132,18 @@ protected: If false, this text output was created after the dc was created, and thus tracks its own values for color and m_url_format. */ bool m_follows_reference_printer; + + /* If true, then use indentation to show the nesting structure of + nested diagnostics, and print locations on separate lines after the + diagnostic message, rather than as a prefix to the message. */ + bool m_show_nesting; + + /* Set to false to suppress location-printing when showing nested + diagnostics, for use in DejaGnu tests. */ + bool m_show_locations_in_nesting; + + /* If true, then add "(level N):" when printing nested diagnostics. */ + bool m_show_nesting_levels; }; #endif /* ! GCC_DIAGNOSTIC_FORMAT_TEXT_H */ diff --git a/gcc/diagnostic-global-context.cc b/gcc/diagnostic-global-context.cc index bd299d08429b..f8b2f7fb52ce 100644 --- a/gcc/diagnostic-global-context.cc +++ b/gcc/diagnostic-global-context.cc @@ -576,3 +576,15 @@ auto_diagnostic_group::~auto_diagnostic_group () { global_dc->end_group (); } + +/* class auto_diagnostic_nesting_level. */ + +auto_diagnostic_nesting_level::auto_diagnostic_nesting_level () +{ + global_dc->push_nesting_level (); +} + +auto_diagnostic_nesting_level::~auto_diagnostic_nesting_level () +{ + global_dc->pop_nesting_level (); +} diff --git a/gcc/diagnostic-show-locus.cc b/gcc/diagnostic-show-locus.cc index a2a4b047ff9c..4e695c557a77 100644 --- a/gcc/diagnostic-show-locus.cc +++ b/gcc/diagnostic-show-locus.cc @@ -3323,6 +3323,9 @@ diagnostic_source_print_policy::print (pretty_printer &pp, void layout_printer::print (const diagnostic_source_print_policy &source_policy) { + diagnostic_prefixing_rule_t saved_rule = pp_prefixing_rule (&m_pp); + pp_prefixing_rule (&m_pp) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; + if (get_options ().show_ruler_p) show_ruler (m_layout.m_x_offset_display + get_options ().max_width); @@ -3359,6 +3362,8 @@ layout_printer::print (const diagnostic_source_print_policy &source_policy) if (auto effect_info = m_layout.m_effect_info) effect_info->m_trailing_out_edge_column = m_link_rhs_column; + + pp_prefixing_rule (&m_pp) = saved_rule; } #if CHECKING_P diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc index ecc4bd94eba1..cc642b017d22 100644 --- a/gcc/diagnostic.cc +++ b/gcc/diagnostic.cc @@ -281,7 +281,8 @@ diagnostic_context::initialize (int n_opts) m_tabstop = 8; m_escape_format = DIAGNOSTICS_ESCAPE_FORMAT_UNICODE; m_edit_context_ptr = nullptr; - m_diagnostic_groups.m_nesting_depth = 0; + m_diagnostic_groups.m_group_nesting_depth = 0; + m_diagnostic_groups.m_diagnostic_nesting_level = 0; m_diagnostic_groups.m_emission_count = 0; m_output_sinks.safe_push (new diagnostic_text_output_format (*this, true)); m_set_locations_cb = nullptr; @@ -384,7 +385,7 @@ diagnostic_context::finish () /* We might be handling a fatal error. Close any active diagnostic groups, which may trigger flushing sinks. */ - while (m_diagnostic_groups.m_nesting_depth > 0) + while (m_diagnostic_groups.m_group_nesting_depth > 0) end_group (); set_diagnostic_buffer (nullptr); @@ -1322,7 +1323,7 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic) /* Every call to report_diagnostic should be within a begin_group/end_group pair so that output formats can reliably flush diagnostics with on_end_group when the topmost group is ended. */ - gcc_assert (m_diagnostic_groups.m_nesting_depth > 0); + gcc_assert (m_diagnostic_groups.m_group_nesting_depth > 0); /* Give preference to being able to inhibit warnings, before they get reclassified to something else. */ @@ -1701,13 +1702,13 @@ fancy_abort (const char *file, int line, const char *function) void diagnostic_context::begin_group () { - m_diagnostic_groups.m_nesting_depth++; + m_diagnostic_groups.m_group_nesting_depth++; } void diagnostic_context::end_group () { - if (--m_diagnostic_groups.m_nesting_depth == 0) + if (--m_diagnostic_groups.m_group_nesting_depth == 0) { /* Handle the case where we've popped the final diagnostic group. If any diagnostics were emitted, give the context a chance @@ -1719,6 +1720,18 @@ diagnostic_context::end_group () } } +void +diagnostic_context::push_nesting_level () +{ + ++m_diagnostic_groups.m_diagnostic_nesting_level; +} + +void +diagnostic_context::pop_nesting_level () +{ + --m_diagnostic_groups.m_diagnostic_nesting_level; +} + void diagnostic_output_format::dump (FILE *out, int indent) const { @@ -1824,7 +1837,11 @@ diagnostic_context::set_diagnostic_buffer (diagnostic_buffer *buffer) /* We don't allow changing buffering within a diagnostic group (to simplify handling of buffered diagnostics within the diagnostic_format implementations). */ - gcc_assert (m_diagnostic_groups.m_nesting_depth == 0); + gcc_assert (m_diagnostic_groups.m_group_nesting_depth == 0); + + /* Likewise, for simplicity, we only allow changing buffers + at nesting level 0. */ + gcc_assert (m_diagnostic_groups.m_diagnostic_nesting_level == 0); m_diagnostic_buffer = buffer; diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h index 5b71523cf89b..f32d404c7a4d 100644 --- a/gcc/diagnostic.h +++ b/gcc/diagnostic.h @@ -557,6 +557,9 @@ public: void begin_group (); void end_group (); + void push_nesting_level (); + void pop_nesting_level (); + bool warning_enabled_at (location_t loc, diagnostic_option_id option_id); bool option_unspecified_p (diagnostic_option_id option_id) const @@ -723,6 +726,13 @@ public: const char *, const char *, va_list *, diagnostic_t) ATTRIBUTE_GCC_DIAG(7,0); + int get_diagnostic_nesting_level () const + { + return m_diagnostic_groups.m_diagnostic_nesting_level; + } + + char *build_indent_prefix () const; + int pch_save (FILE *f) { @@ -933,7 +943,10 @@ private: /* Fields relating to diagnostic groups. */ struct { /* How many diagnostic_group instances are currently alive. */ - int m_nesting_depth; + int m_group_nesting_depth; + + /* How many nesting levels have been pushed within this group. */ + int m_diagnostic_nesting_level; /* How many diagnostics have been emitted since the bottommost diagnostic_group was pushed. */ diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 93e9af3791c8..2f84bb9cf502 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -5937,6 +5937,9 @@ by @code{:} and one or more @var{KEY}=@var{VALUE} pairs, in this form: etc. +Schemes, keys, or values with a name prefixed ``experimental'' may change +or be removed without notice. + @var{SCHEME} can be @table @gcctabopt @@ -5952,6 +5955,21 @@ Supported keys are: Override colorization settings from @option{-fdiagnostics-color} for this text output. +@item experimental-nesting=@r{[}yes@r{|}no@r{]} +Enable an experimental mode that emphasizes hierarchical relationships +within diagnostics messages, displaying location information on separate +lines. + +@item experimental-nesting-show-locations=@r{[}yes@r{|}no@r{]} +If @code{experimental-nesting=yes}, then by default locations are +shown; set this key to @code{no} to disable printing such locations. +This exists for use by GCC developers, for writing DejaGnu test cases. + +@item experimental-nesting-show-levels=@r{[}yes@r{|}no@r{]} +This is a debugging option for use with @code{experimental-nesting=yes}. +Set this key to @code{yes} to print explicit nesting levels in the output. +This exists for use by GCC developers. + @end table @item sarif diff --git a/gcc/opts-diagnostic.cc b/gcc/opts-diagnostic.cc index 54040ba8d431..2c7e95260f70 100644 --- a/gcc/opts-diagnostic.cc +++ b/gcc/opts-diagnostic.cc @@ -361,6 +361,9 @@ text_scheme_handler::make_sink (const context &ctxt, const scheme_name_and_params &parsed_arg) const { bool show_color = pp_show_color (ctxt.m_dc.get_reference_printer ()); + bool show_nesting = false; + bool show_locations_in_nesting = true; + bool show_levels = false; for (auto& iter : parsed_arg.m_kvs) { const std::string &key = iter.first; @@ -371,17 +374,42 @@ text_scheme_handler::make_sink (const context &ctxt, return nullptr; continue; } + if (key == "experimental-nesting") + { + if (!parse_bool_value (ctxt, unparsed_arg, key, value, + show_nesting)) + return nullptr; + continue; + } + if (key == "experimental-nesting-show-locations") + { + if (!parse_bool_value (ctxt, unparsed_arg, key, value, + show_locations_in_nesting)) + return nullptr; + continue; + } + if (key == "experimental-nesting-show-levels") + { + if (!parse_bool_value (ctxt, unparsed_arg, key, value, show_levels)) + return nullptr; + continue; + } /* Key not found. */ auto_vec<const char *> known_keys; known_keys.safe_push ("color"); + known_keys.safe_push ("experimental-nesting"); + known_keys.safe_push ("experimental-nesting-show-locations"); + known_keys.safe_push ("experimental-nesting-show-levels"); ctxt.report_unknown_key (unparsed_arg, key, get_scheme_name (), known_keys); return nullptr; } - std::unique_ptr<diagnostic_output_format> sink; - sink = ::make_unique<diagnostic_text_output_format> (ctxt.m_dc); + auto sink = ::make_unique<diagnostic_text_output_format> (ctxt.m_dc); + sink->set_show_nesting (show_nesting); + sink->set_show_locations_in_nesting (show_locations_in_nesting); + sink->set_show_nesting_levels (show_levels); return sink; } diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-sarif.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-sarif.c new file mode 100644 index 000000000000..11d9933da248 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-sarif.c @@ -0,0 +1,16 @@ +/* { dg-do compile } */ +/* { dg-options "-fdiagnostics-add-output=sarif" } */ + +extern void foo (void); + +void test_nesting (void) +{ + foo (); /* { dg-error "top-level error" } */ +} + +/* Verify that some JSON was written to a file with the expected name. */ +/* { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest diagnostic-test-nesting-sarif.c "diagnostic-test-nesting-sarif.py" } } */ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-sarif.py b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-sarif.py new file mode 100644 index 000000000000..7791147de3d6 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-sarif.py @@ -0,0 +1,39 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_basics(sarif): + schema = sarif['$schema'] + assert schema == "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json" + + version = sarif['version'] + assert version == "2.1.0" + +def test_nested_result(sarif): + runs = sarif['runs'] + run = runs[0] + results = run['results'] + + assert len(results) == 1 + + result = results[0] + assert result['level'] == 'error' + assert result['message']['text'] == "top-level error" + + relatedLocations = result['relatedLocations'] + assert len(relatedLocations) == 12 + + for i in range(12): + note = relatedLocations[i] + text = note['message']['text'] + nestingLevel = note['properties']['nestingLevel'] + if i % 4 == 0: + assert text == 'child %i' % (i / 4) + assert nestingLevel == 1 + else: + assert text == 'grandchild %i %i' % ((i / 4), (i % 4) - 1) + assert nestingLevel == 2 diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-text-indented-show-levels.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-text-indented-show-levels.c new file mode 100644 index 000000000000..91b1056d9557 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-text-indented-show-levels.c @@ -0,0 +1,24 @@ +/* { dg-do compile } */ +/* { dg-options "-fdiagnostics-set-output=text:experimental-nesting=yes,experimental-nesting-show-levels=yes" } */ + +extern void foo (void); + +void test_nesting (void) +{ + foo (); /* { dg-error "top-level error" } */ +} + +/* { dg-begin-multiline-output "" } + * (level 1):note: child 0 + * (level 2):note: grandchild 0 0 + * (level 2):note: grandchild 0 1 + * (level 2):note: grandchild 0 2 + * (level 1):note: child 1 + * (level 2):note: grandchild 1 0 + * (level 2):note: grandchild 1 1 + * (level 2):note: grandchild 1 2 + * (level 1):note: child 2 + * (level 2):note: grandchild 2 0 + * (level 2):note: grandchild 2 1 + * (level 2):note: grandchild 2 2 + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-text-indented-unicode.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-text-indented-unicode.c new file mode 100644 index 000000000000..e843d196aaf1 --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-text-indented-unicode.c @@ -0,0 +1,24 @@ +/* { dg-do compile } */ +/* { dg-options "-fdiagnostics-set-output=text:experimental-nesting=yes -fdiagnostics-text-art-charset=unicode" } */ + +extern void foo (void); + +void test_nesting (void) +{ + foo (); /* { dg-error "top-level error" } */ +} + +/* { dg-begin-multiline-output "" } + • note: child 0 + • note: grandchild 0 0 + • note: grandchild 0 1 + • note: grandchild 0 2 + • note: child 1 + • note: grandchild 1 0 + • note: grandchild 1 1 + • note: grandchild 1 2 + • note: child 2 + • note: grandchild 2 0 + • note: grandchild 2 1 + • note: grandchild 2 2 + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-text-indented.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-text-indented.c new file mode 100644 index 000000000000..687acf98fece --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-text-indented.c @@ -0,0 +1,24 @@ +/* { dg-do compile } */ +/* { dg-options "-fdiagnostics-set-output=text:experimental-nesting=yes" } */ + +extern void foo (void); + +void test_nesting (void) +{ + foo (); /* { dg-error "top-level error" } */ +} + +/* { dg-begin-multiline-output "" } + * note: child 0 + * note: grandchild 0 0 + * note: grandchild 0 1 + * note: grandchild 0 2 + * note: child 1 + * note: grandchild 1 0 + * note: grandchild 1 1 + * note: grandchild 1 2 + * note: child 2 + * note: grandchild 2 0 + * note: grandchild 2 1 + * note: grandchild 2 2 + { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-text-plain.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-text-plain.c new file mode 100644 index 000000000000..042081d35c7b --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-text-plain.c @@ -0,0 +1,8 @@ +/* { dg-do compile } */ + +extern void foo (void); + +void test_nesting (void) +{ + foo (); /* { dg-error "top-level error" } */ +} diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_nesting.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_nesting.c new file mode 100644 index 000000000000..17c3574dbf7c --- /dev/null +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_nesting.c @@ -0,0 +1,145 @@ +/* This plugin exercises diagnostic nesting. */ + +#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.h" +#include "gimple-iterator.h" +#include "gimple-fold.h" +#include "tree-eh.h" +#include "gimple-expr.h" +#include "is-a.h" +#include "tree.h" +#include "tree-pass.h" +#include "intl.h" +#include "plugin-version.h" +#include "diagnostic.h" +#include "context.h" +#include "gcc-rich-location.h" + +int plugin_is_GPL_compatible; + +const pass_data pass_data_test_nesting = +{ + GIMPLE_PASS, /* type */ + "test_nesting", /* 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_nesting : public gimple_opt_pass +{ +public: + pass_test_nesting(gcc::context *ctxt) + : gimple_opt_pass(pass_data_test_nesting, ctxt) + {} + + /* opt_pass methods: */ + bool gate (function *) { return true; } + virtual unsigned int execute (function *); + +}; // class pass_test_nesting + +/* 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; +} + +/* Exercise diagnostic_nesting. */ + +unsigned int +pass_test_nesting::execute (function *fun) +{ + gimple_stmt_iterator gsi; + basic_block bb; + + 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, "foo", 0)) + { + location_t loc = gimple_location (call); + auto_diagnostic_group g; + error_at (loc, "top-level error"); + for (int i = 0; i < 3; i++) + { + auto_diagnostic_nesting_level sentinel; + inform (loc, "child %i", i); + for (int j = 0; j < 3; j++) + { + auto_diagnostic_nesting_level sentinel; + inform (loc, "grandchild %i %i", i, j); + } + } + } + } + + return 0; +} + +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 = new pass_test_nesting (g); + pass_info.reference_pass_name = "ssa"; + pass_info.ref_pass_instance_number = 1; + pass_info.pos_op = PASS_POS_INSERT_AFTER; + register_callback (plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, + &pass_info); + + return 0; +} diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp index 0b53e06d8352..dee8a2bfecb5 100644 --- a/gcc/testsuite/gcc.dg/plugin/plugin.exp +++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp @@ -108,6 +108,12 @@ set plugin_test_list [list \ { diagnostic_plugin_test_metadata.c \ diagnostic-test-metadata.c \ diagnostic-test-metadata-sarif.c } \ + { diagnostic_plugin_test_nesting.c \ + diagnostic-test-nesting-text-plain.c \ + diagnostic-test-nesting-text-indented.c \ + diagnostic-test-nesting-text-indented-show-levels.c \ + diagnostic-test-nesting-text-indented-unicode.c \ + diagnostic-test-nesting-sarif.c } \ { diagnostic_plugin_test_paths.c \ diagnostic-test-paths-1.c \ diagnostic-test-paths-2.c \ -- 2.26.3