diagnostics: add support for nested diagnostics [PR116253]

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 <dmalcolm@redhat.com>
This commit is contained in:
David Malcolm 2024-11-18 14:20:17 -05:00
parent 6d90f5d0ae
commit a5af2ec16f
19 changed files with 560 additions and 22 deletions

View File

@ -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);

View File

@ -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. */

View File

@ -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);
}

View File

@ -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

View File

@ -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 */

View File

@ -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 ();
}

View File

@ -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

View File

@ -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;

View File

@ -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. */

View File

@ -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

View File

@ -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;
}

View File

@ -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" } } */

View File

@ -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

View File

@ -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 "" } */

View File

@ -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 "" } */

View File

@ -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 "" } */

View File

@ -0,0 +1,8 @@
/* { dg-do compile } */
extern void foo (void);
void test_nesting (void)
{
foo (); /* { dg-error "top-level error" } */
}

View File

@ -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;
}

View File

@ -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 \