This patch adds support to sarif-replay for "nestingLevel"
from "P3358R0 SARIF for Structured Diagnostics"
https://wg21.link/P3358R0
Doing so revealed a bug where libgdiagnostics was always
creating new location_t values (and thus also
diagnostic_physical_location instances), rather than reusing
existing location_t values, leading to excess source printing.
The patch also fixes this bug, adding a new flag to libgdiagnostics
for debugging physical locations, and exposing this in sarif-replay
via a new "-fdebug-physical-locations" maintainer option.
Finally, the patch adds test coverage for the HTML sink's output
of nested diagnostics (both from a GCC plugin, and from sarif-replay).
Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r16-2766-g7969e4859ed007.
gcc/ChangeLog:
PR diagnostics/116253
* diagnostics/context.cc (context::set_nesting_level): New.
* diagnostics/context.h (context::set_nesting_level): New decl.
* doc/libgdiagnostics/topics/compatibility.rst
(LIBGDIAGNOSTICS_ABI_5): New.
* doc/libgdiagnostics/topics/physical-locations.rst
(diagnostic_manager_set_debug_physical_locations): New.
* libgdiagnostics++.h (manager::set_debug_physical_locations):
New.
* libgdiagnostics-private.h
(private_diagnostic_set_nesting_level): New decl.
* libgdiagnostics.cc (diagnostic_manager::diagnostic_manager):
Initialize m_debug_physical_locations.
(diagnostic_manager::new_location_from_file_and_line): Add debug
printing.
(diagnostic_manager::new_location_from_file_line_column):
Likewise.
(diagnostic_manager::new_location_from_range): Likewise.
(diagnostic_manager::set_debug_physical_locations): New.
(diagnostic_manager::ensure_linemap_for_file_and_line): Avoid
redundant calls to linemap_add.
(diagnostic_manager::new_location): Add debug printing.
(diagnostic_manager::m_debug_physical_locations): New field.
(diagnostic::diagnostic): Initialize m_nesting_level.
(diagnostic::get_nesting_level): New accessor.
(diagnostic::set_nesting_level): New.
(diagnostic::m_nesting_level): New field.
(diagnostic_manager::emit_va): Set and reset the nesting level
of the context from that of the diagnostic.
(diagnostic_manager_set_debug_physical_locations): New.
(private_diagnostic_set_nesting_level): New.
* libgdiagnostics.h
(diagnostic_manager_set_debug_physical_locations): New decl.
* libgdiagnostics.map (LIBGDIAGNOSTICS_ABI_5): New.
* libsarifreplay.cc (sarif_replayer::handle_result_obj): Support
the "nestingLevel" property.
* libsarifreplay.h (replay_options::m_debug_physical_locations):
New field.
* sarif-replay.cc: Add -fdebug-physical-locations.
gcc/testsuite/ChangeLog:
PR diagnostics/116253
* gcc.dg/plugin/diagnostic-test-nesting-html.c: New test.
* gcc.dg/plugin/diagnostic-test-nesting-html.py: New test script.
* gcc.dg/plugin/plugin.exp: Add it.
* libgdiagnostics.dg/test-multiple-lines.c: Update expected output
to show fix-it hint.
* sarif-replay.dg/2.1.0-valid/nested-diagnostics-1.sarif: New test.
---
gcc/diagnostics/context.cc | 6 +
gcc/diagnostics/context.h | 1 +
.../libgdiagnostics/topics/compatibility.rst | 9 +
.../topics/physical-locations.rst | 16 ++
gcc/libgdiagnostics++.h | 10 ++
gcc/libgdiagnostics-private.h | 7 +
gcc/libgdiagnostics.cc | 77 +++++++-
gcc/libgdiagnostics.h | 10 ++
gcc/libgdiagnostics.map | 8 +
gcc/libsarifreplay.cc | 12 ++
gcc/libsarifreplay.h | 1 +
gcc/sarif-replay.cc | 13 +-
.../plugin/diagnostic-test-nesting-html.c | 13 ++
.../plugin/diagnostic-test-nesting-html.py | 69 ++++++++
gcc/testsuite/gcc.dg/plugin/plugin.exp | 1 +
.../libgdiagnostics.dg/test-multiple-lines.c | 1 +
.../2.1.0-valid/nested-diagnostics-1.sarif | 164 ++++++++++++++++++
17 files changed, 408 insertions(+), 10 deletions(-)
create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-html.c
create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-html.py
create mode 100644
gcc/testsuite/sarif-replay.dg/2.1.0-valid/nested-diagnostics-1.sarif
diff --git a/gcc/diagnostics/context.cc b/gcc/diagnostics/context.cc
index 4d33f97017f7..a1441ca5e73a 100644
--- a/gcc/diagnostics/context.cc
+++ b/gcc/diagnostics/context.cc
@@ -1681,6 +1681,12 @@ context::pop_nesting_level ()
inhibit_notes_in_group (/*inhibit=*/false);
}
+void
+context::set_nesting_level (int new_level)
+{
+ m_diagnostic_groups.m_diagnostic_nesting_level = new_level;
+}
+
void
sink::dump (FILE *out, int indent) const
{
diff --git a/gcc/diagnostics/context.h b/gcc/diagnostics/context.h
index a5f1a41967b4..b6ec85cc26ee 100644
--- a/gcc/diagnostics/context.h
+++ b/gcc/diagnostics/context.h
@@ -301,6 +301,7 @@ public:
void push_nesting_level ();
void pop_nesting_level ();
+ void set_nesting_level (int new_level);
bool warning_enabled_at (location_t loc, option_id opt_id);
diff --git a/gcc/doc/libgdiagnostics/topics/compatibility.rst
b/gcc/doc/libgdiagnostics/topics/compatibility.rst
index 0ca41a330095..ccf123650f98 100644
--- a/gcc/doc/libgdiagnostics/topics/compatibility.rst
+++ b/gcc/doc/libgdiagnostics/topics/compatibility.rst
@@ -262,3 +262,12 @@ working with :type:`diagnostic_message_buffer`.
* :func:`diagnostic_add_location_with_label_via_msg_buf`
* :func:`diagnostic_execution_path_add_event_via_msg_buf`
+
+.. _LIBGDIAGNOSTICS_ABI_5:
+
+``LIBGDIAGNOSTICS_ABI_5``
+-------------------------
+
+``LIBGDIAGNOSTICS_ABI_5`` covers the addition of this function:
+
+ * :func:`diagnostic_manager_set_debug_physical_locations`
diff --git a/gcc/doc/libgdiagnostics/topics/physical-locations.rst
b/gcc/doc/libgdiagnostics/topics/physical-locations.rst
index be8e7ebc5d1f..fcd81a02741c 100644
--- a/gcc/doc/libgdiagnostics/topics/physical-locations.rst
+++ b/gcc/doc/libgdiagnostics/topics/physical-locations.rst
@@ -304,3 +304,19 @@ This diagnostic has three locations
.. code-block:: c
#ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer
+
+.. function:: void diagnostic_manager_set_debug_physical_locations
(diagnostic_manager *mgr, \
+ int value)
+
+ Calling ``diagnostic_manager_set_debug_physical_locations (mgr, 1);``
+ will lead to debugging information being printed to ``stderr`` when
+ creating :type:`diagnostic_physical_location` instances.
+
+ The precise format of these messages is subject to change.
+
+ This function was added in :ref:`LIBGDIAGNOSTICS_ABI_5`; you can
+ test for its presence using
+
+ .. code-block:: c
+
+ #ifdef
LIBDIAGNOSTICS_HAVE_diagnostic_manager_set_debug_physical_locations
diff --git a/gcc/libgdiagnostics++.h b/gcc/libgdiagnostics++.h
index c955d56db5b3..fc20398f90c2 100644
--- a/gcc/libgdiagnostics++.h
+++ b/gcc/libgdiagnostics++.h
@@ -477,6 +477,9 @@ public:
void
take_global_graph (graph g);
+ void
+ set_debug_physical_locations (bool value);
+
diagnostic_manager *m_inner;
bool m_owned;
};
@@ -926,6 +929,13 @@ manager::take_global_graph (graph g)
g.m_owned = false;
}
+inline void
+manager::set_debug_physical_locations (bool value)
+{
+ diagnostic_manager_set_debug_physical_locations (m_inner,
+ value ? 1 : 0);
+}
+
// class graph
inline void
diff --git a/gcc/libgdiagnostics-private.h b/gcc/libgdiagnostics-private.h
index 0e90f8720079..4186c67e67e3 100644
--- a/gcc/libgdiagnostics-private.h
+++ b/gcc/libgdiagnostics-private.h
@@ -58,6 +58,13 @@ private_diagnostic_execution_path_add_event_3
(diagnostic_execution_path *path,
LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (5)
LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (6);
+/* Entrypoint added in LIBGDIAGNOSTICS_ABI_5. */
+
+extern void
+private_diagnostic_set_nesting_level (diagnostic *diag,
+ int nesting_level)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
} // extern "C"
#endif /* LIBGDIAGNOSTICS_PRIVATE_H */
diff --git a/gcc/libgdiagnostics.cc b/gcc/libgdiagnostics.cc
index 7351d33641bc..784e281a4902 100644
--- a/gcc/libgdiagnostics.cc
+++ b/gcc/libgdiagnostics.cc
@@ -639,7 +639,8 @@ struct diagnostic_manager
public:
diagnostic_manager ()
: m_current_diag (nullptr),
- m_prev_diag_logical_loc (nullptr)
+ m_prev_diag_logical_loc (nullptr),
+ m_debug_physical_locations (false)
{
linemap_init (&m_line_table, BUILTINS_LOCATION);
m_line_table.m_reallocator = xrealloc;
@@ -747,6 +748,9 @@ public:
new_location_from_file_and_line (const diagnostic_file *file,
diagnostic_line_num_t line_num)
{
+ if (m_debug_physical_locations)
+ fprintf (stderr, "new_location_from_file_and_line (%s, %i)",
+ file->get_name (), line_num);
ensure_linemap_for_file_and_line (file, line_num);
location_t loc = linemap_position_for_column (&m_line_table, 0);
return new_location (loc);
@@ -757,6 +761,9 @@ public:
diagnostic_line_num_t line_num,
diagnostic_column_num_t column_num)
{
+ if (m_debug_physical_locations)
+ fprintf (stderr, "new_location_from_file_line_column (%s, %i, %i)",
+ file->get_name (), line_num, column_num);
ensure_linemap_for_file_and_line (file, line_num);
location_t loc = linemap_position_for_column (&m_line_table, column_num);
return new_location (loc);
@@ -767,12 +774,23 @@ public:
const diagnostic_physical_location *loc_start,
const diagnostic_physical_location *loc_end)
{
+ if (m_debug_physical_locations)
+ fprintf (stderr, "new_location_from_range (%p, %p, %p)",
+ (const void *)loc_caret,
+ (const void *)loc_start,
+ (const void *)loc_end);
return new_location
(m_line_table.make_location (as_location_t (loc_caret),
as_location_t (loc_start),
as_location_t (loc_end)));
}
+ void
+ set_debug_physical_locations (bool value)
+ {
+ m_debug_physical_locations = value;
+ }
+
const diagnostic_logical_location *
new_logical_location (enum diagnostic_logical_location_kind_t kind,
const diagnostic_logical_location *parent,
@@ -874,11 +892,17 @@ private:
linemap_add (&m_line_table, LC_ENTER, false, file->get_name (), 0);
else
{
- line_map *map
- = const_cast<line_map *>
- (linemap_add (&m_line_table, LC_RENAME_VERBATIM, false,
- file->get_name (), 0));
- ((line_map_ordinary *)map)->included_from = UNKNOWN_LOCATION;
+ line_map_ordinary *last_map
+ = LINEMAPS_LAST_ORDINARY_MAP (&m_line_table);
+ if (last_map->to_file != file->get_name ()
+ || linenum < last_map->to_line)
+ {
+ line_map *map
+ = const_cast<line_map *>
+ (linemap_add (&m_line_table, LC_RENAME_VERBATIM, false,
+ file->get_name (), 0));
+ ((line_map_ordinary *)map)->included_from = UNKNOWN_LOCATION;
+ }
}
linemap_line_start (&m_line_table, linenum, 100);
}
@@ -888,11 +912,19 @@ private:
{
if (loc == UNKNOWN_LOCATION)
return nullptr;
+ if (m_debug_physical_locations)
+ fprintf (stderr, ": new_location (%lx)", loc);
if (diagnostic_physical_location **slot = m_location_t_map.get (loc))
- return *slot;
+ {
+ if (m_debug_physical_locations)
+ fprintf (stderr, ": cache hit: %p\n", (const void *)*slot);
+ return *slot;
+ }
diagnostic_physical_location *phys_loc
= new diagnostic_physical_location (this, loc);
m_location_t_map.put (loc, phys_loc);
+ if (m_debug_physical_locations)
+ fprintf (stderr, ": cache miss: %p\n", (const void *)phys_loc);
return phys_loc;
}
@@ -909,6 +941,7 @@ private:
const diagnostic *m_current_diag;
const diagnostic_logical_location *m_prev_diag_logical_loc;
std::unique_ptr<diagnostics::changes::change_set> m_change_set;
+ bool m_debug_physical_locations;
};
class impl_rich_location : public rich_location
@@ -1221,7 +1254,8 @@ public:
m_level (level),
m_rich_loc (diag_mgr.get_line_table ()),
m_logical_loc (nullptr),
- m_path (nullptr)
+ m_path (nullptr),
+ m_nesting_level (0)
{
m_metadata.set_lazy_digraphs (&m_graphs);
}
@@ -1325,6 +1359,9 @@ public:
return m_graphs;
}
+ int get_nesting_level () const { return m_nesting_level; }
+ void set_nesting_level (int value) { m_nesting_level = value; }
+
private:
diagnostic_manager &m_diag_mgr;
enum diagnostic_level m_level;
@@ -1335,6 +1372,7 @@ private:
std::vector<std::unique_ptr<range_label>> m_labels;
std::vector<std::unique_ptr<impl_rule>> m_rules;
std::unique_ptr<diagnostic_execution_path> m_path;
+ int m_nesting_level;
};
static enum diagnostics::kind
@@ -1624,8 +1662,9 @@ GCC_DIAGNOSTIC_PUSH_IGNORED(-Wsuggest-attribute=format)
GCC_DIAGNOSTIC_POP
info.m_metadata = diag.get_metadata ();
info.m_x_data = &diag;
+ m_dc.set_nesting_level (diag.get_nesting_level ());
diagnostic_report_diagnostic (&m_dc, &info);
-
+ m_dc.set_nesting_level (0);
m_dc.end_group ();
}
@@ -2972,3 +3011,23 @@ private_diagnostic_execution_path_add_event_3
(diagnostic_execution_path *path,
return as_diagnostic_event_id (result);
}
+
+/* Public entrypoint. */
+
+void
+diagnostic_manager_set_debug_physical_locations (diagnostic_manager *mgr,
+ int value)
+{
+ FAIL_IF_NULL (mgr);
+ mgr->set_debug_physical_locations (value);
+}
+
+/* Private entrypoint. */
+
+void
+private_diagnostic_set_nesting_level (diagnostic *diag,
+ int nesting_level)
+{
+ FAIL_IF_NULL (diag);
+ diag->set_nesting_level (nesting_level);
+}
diff --git a/gcc/libgdiagnostics.h b/gcc/libgdiagnostics.h
index c202feb1cae3..0ae56f05ea6d 100644
--- a/gcc/libgdiagnostics.h
+++ b/gcc/libgdiagnostics.h
@@ -1115,6 +1115,16 @@ diagnostic_node_set_label_via_msg_buf (diagnostic_node
*node,
LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (2);
+/* If non-zero, print debugging information to stderr when
+ creating diagnostic_physical_location instances.
+
+ Added in LIBGDIAGNOSTICS_ABI_5. */
+#define LIBDIAGNOSTICS_HAVE_diagnostic_manager_set_debug_physical_locations
+
+extern void
+diagnostic_manager_set_debug_physical_locations (diagnostic_manager *mgr,
+ int value);
+
/* DEFERRED:
- thread-safety
- plural forms
diff --git a/gcc/libgdiagnostics.map b/gcc/libgdiagnostics.map
index 91f3951a35b4..0400ca7268a7 100644
--- a/gcc/libgdiagnostics.map
+++ b/gcc/libgdiagnostics.map
@@ -141,3 +141,11 @@ LIBGDIAGNOSTICS_ABI_4 {
# Private hook used by sarif-replay
private_diagnostic_execution_path_add_event_3;
} LIBGDIAGNOSTICS_ABI_3;
+
+LIBGDIAGNOSTICS_ABI_5 {
+ global:
+ diagnostic_manager_set_debug_physical_locations;
+
+ # Private hook used by sarif-replay
+ private_diagnostic_set_nesting_level;
+} LIBGDIAGNOSTICS_ABI_4;
diff --git a/gcc/libsarifreplay.cc b/gcc/libsarifreplay.cc
index 1e4a74f71b55..e8fc6d0d170c 100644
--- a/gcc/libsarifreplay.cc
+++ b/gcc/libsarifreplay.cc
@@ -1408,10 +1408,22 @@ sarif_replayer::handle_result_obj (const json::object
&result_obj,
rule_obj));
if (!msg_buf.m_inner)
return status::err_invalid_sarif;
+
auto note (m_output_mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_NOTE));
note.set_location (physical_loc);
note.set_logical_location (logical_loc);
add_any_annotations (note, annotations);
+
+ /* Look for "nestingLevel" property, as per
+ "P3358R0 SARIF for Structured Diagnostics"
+ https://wg21.link/P3358R0 */
+ if (auto nesting_level
+ = maybe_get_property_bag_value<json::integer_number>
+ (*location_obj,
+ "nestingLevel"))
+ private_diagnostic_set_nesting_level (note.m_inner,
+ nesting_level->get ());
+
notes.push_back ({std::move (note), std::move (msg_buf)});
}
else
diff --git a/gcc/libsarifreplay.h b/gcc/libsarifreplay.h
index fb66014086a0..313a905754e0 100644
--- a/gcc/libsarifreplay.h
+++ b/gcc/libsarifreplay.h
@@ -31,6 +31,7 @@ struct replay_options
bool m_echo_file;
bool m_json_comments;
bool m_verbose;
+ bool m_debug_physical_locations;
enum diagnostic_colorize m_diagnostics_colorize;
};
diff --git a/gcc/sarif-replay.cc b/gcc/sarif-replay.cc
index a96c97bd92ba..c740c29b2a32 100644
--- a/gcc/sarif-replay.cc
+++ b/gcc/sarif-replay.cc
@@ -93,6 +93,11 @@ static const char *const usage_msg = (
"\n"
" --usage\n"
" Print this message and exit.\n"
+"\n"
+"Options for maintainers:\n"
+"\n"
+" -fdebug-physical-locations\n"
+" Dump debugging information about physical locations to stderr.\n"
"\n");
static void
@@ -182,7 +187,11 @@ parse_options (int argc, char **argv,
print_version ();
exit (0);
}
-
+ else if (strcmp (option, "-fdebug-physical-locations") == 0)
+ {
+ opts.m_replay_opts.m_debug_physical_locations = true;
+ handled = true;
+ }
if (!handled)
{
if (option[0] == '-')
@@ -245,6 +254,8 @@ main (int argc, char **argv)
note.finish ("about to replay %qs...", filename);
}
libgdiagnostics::manager playback_mgr;
+ playback_mgr.set_debug_physical_locations
+ (opts.m_replay_opts.m_debug_physical_locations);
playback_mgr.add_text_sink (stderr,
opts.m_replay_opts.m_diagnostics_colorize);
for (auto spec : opts.m_extra_output_specs)
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-html.c
b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-html.c
new file mode 100644
index 000000000000..8ff7b355bff7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-html.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-add-output=experimental-html:javascript=no" } */
+
+extern void foo (void);
+
+void test_nesting (void)
+{
+ foo (); /* { dg-error "top-level error" } */
+}
+
+/* Use a Python script to verify various properties about the generated
+ .html file:
+ { dg-final { run-html-pytest diagnostic-test-nesting-html.c
"diagnostic-test-nesting-html.py" } } */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-html.py
b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-html.py
new file mode 100644
index 000000000000..3899ba530d98
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-nesting-html.py
@@ -0,0 +1,69 @@
+# Verify that nesting works in HTML output.
+
+from htmltest import *
+
+import pytest
+
[email protected](scope='function', autouse=True)
+def html_tree():
+ return html_tree_from_env()
+
+def test_nesting(html_tree):
+ root = html_tree.getroot ()
+ assert root.tag == make_tag('html')
+
+ body = root.find('xhtml:body', ns)
+ assert body is not None
+
+ diag_list = body.find('xhtml:div', ns)
+ assert diag_list is not None
+ assert diag_list.attrib['class'] == 'gcc-diagnostic-list'
+
+ diag = diag_list.find('xhtml:div', ns)
+ assert diag is not None
+
+ message = diag.find("./xhtml:div[@class='gcc-message']", ns)
+ assert message.attrib['id'] == 'gcc-diag-0-message'
+
+ assert message[0].tag == make_tag('strong')
+ assert message[0].tail == ' top-level error'
+
+ # We expect 12 messages, with the given IDs and text:
+ for i in range(12):
+ child = diag.find(".//xhtml:div[@id='gcc-diag-%i']" % (i + 1),
+ ns)
+ assert child is not None
+
+ message = child.find("./xhtml:div[@class='gcc-message']", ns)
+ assert message.attrib['id'] == 'gcc-diag-%i-message' % (i + 1)
+
+ if i % 4 == 0:
+ assert message.text == 'child %i' % (i / 4)
+ else:
+ assert message.text == 'grandchild %i %i' % ((i / 4), (i % 4) - 1)
+
+ # We expect the messages to be organized into nested <ul> with
+ # "nesting-level" set, all below a <ul>
+ child_ul = diag.find("./xhtml:ul[@nesting-level='1']", ns)
+ assert child_ul is not None
+ msg_id = 1
+ for i in range(3):
+ child_li = child_ul.find("./xhtml:li[@nesting-level='1'][%i]" % (i +
1), ns)
+ assert child_li is not None
+ child = child_li.find("./xhtml:div[@id='gcc-diag-%i']" % msg_id, ns)
+ assert child is not None
+ message = child.find("./xhtml:div[@class='gcc-message']", ns)
+ assert message.attrib['id'] == 'gcc-diag-%i-message' % msg_id
+ assert message.text == 'child %i' % i
+ msg_id += 1
+ grandchild_ul = child_ul.find("./xhtml:ul[@nesting-level='2'][%i]" %
(i + 1), ns)
+ assert grandchild_ul is not None
+ for j in range(3):
+ grandchild_li =
grandchild_ul.find("./xhtml:li[@nesting-level='2'][%i]" % (j + 1), ns)
+ assert grandchild_li is not None
+ grandchild = grandchild_li.find("./xhtml:div[@id='gcc-diag-%i']" %
msg_id, ns)
+ assert grandchild is not None
+ message = grandchild.find("./xhtml:div[@class='gcc-message']", ns)
+ assert message.attrib['id'] == 'gcc-diag-%i-message' % msg_id
+ assert message.text == 'grandchild %i %i' % (i, j)
+ msg_id += 1
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp
b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index ce25c0ab3abd..3bb6063c3a9e 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -116,6 +116,7 @@ set plugin_test_list [list \
diagnostic-test-nesting-text-indented.c \
diagnostic-test-nesting-text-indented-show-levels.c \
diagnostic-test-nesting-text-indented-unicode.c \
+ diagnostic-test-nesting-html.c \
diagnostic-test-nesting-sarif.c } \
{ diagnostic_plugin_test_paths.cc \
diagnostic-test-paths-1.c \
diff --git a/gcc/testsuite/libgdiagnostics.dg/test-multiple-lines.c
b/gcc/testsuite/libgdiagnostics.dg/test-multiple-lines.c
index e76111093427..39af8105e6bd 100644
--- a/gcc/testsuite/libgdiagnostics.dg/test-multiple-lines.c
+++ b/gcc/testsuite/libgdiagnostics.dg/test-multiple-lines.c
@@ -66,6 +66,7 @@ main ()
| ~~~~~
23 | "bar"
| ~~~~~^
+ | ,
24 | "baz"};
| ~~~~~
{ dg-end-multiline-output "" } */
diff --git
a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/nested-diagnostics-1.sarif
b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/nested-diagnostics-1.sarif
new file mode 100644
index 000000000000..b924012eaf71
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/nested-diagnostics-1.sarif
@@ -0,0 +1,164 @@
+/* Test a replay of a .sarif file generated from GCC testsuite.
+
+ The dg directives were stripped out from the generated .sarif
+ to avoid confusing DejaGnu for this test. */
+/* { dg-additional-options
"-fdiagnostics-add-output=sarif:file=nested-diagnostics-1.roundtrip.sarif" } */
+/* { dg-additional-options
"-fdiagnostics-add-output=experimental-html:file=nested-diagnostics-1.sarif.html,javascript=no"
} */
+
+{"$schema":
"https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json",
+ "version": "2.1.0",
+ "runs": [{"tool": {"driver": {"name": "GNU C23",
+ "fullName": "GNU C23 (GCC) version 16.0.0
20250723 (experimental) (x86_64-pc-linux-gnu)",
+ "version": "16.0.0 20250723 (experimental)",
+ "informationUri": "https://gcc.gnu.org/gcc-16/",
+ "rules": []},
+ "extensions": [{"name": "diagnostic_plugin_test_nesting",
+ "fullName":
"./diagnostic_plugin_test_nesting.so"}]},
+ "invocations": [{"executionSuccessful": false,
+ "toolExecutionNotifications": []}],
+ "artifacts": [{"location": {"uri":
"diagnostic-test-nesting-sarif.c"},
+ "sourceLanguage": "c",
+ "contents": {"text": "\n\nextern void foo
(void);\n\nvoid test_nesting (void)\n{\n foo ();\n}\n"},
+ "roles": ["analysisTarget"]}],
+ "results": [{"ruleId": "error",
+ "level": "error",
+ "message": {"text": "top-level error"},
+ "locations": [{"physicalLocation":
{"artifactLocation": {"uri": "diagnostic-test-nesting-sarif.c"},
+ "region":
{"startLine": 8,
+
"startColumn": 3,
+
"endColumn": 9},
+ "contextRegion":
{"startLine": 8,
+
"snippet": {"text": " foo ();\n"}}},
+ "logicalLocations": [{"index": 0,
+
"fullyQualifiedName": "test_nesting"}]}],
+ "relatedLocations": [{"physicalLocation":
{"artifactLocation": {"uri": "diagnostic-test-nesting-sarif.c"},
+ "region":
{"startLine": 8,
+
"startColumn": 3,
+
"endColumn": 9},
+
"contextRegion": {"startLine": 8,
+
"snippet": {"text": " foo ();\n"}}},
+ "message": {"text": "child 0"},
+ "properties": {"nestingLevel":
1}},
+ {"physicalLocation":
{"artifactLocation": {"uri": "diagnostic-test-nesting-sarif.c"},
+ "region":
{"startLine": 8,
+
"startColumn": 3,
+
"endColumn": 9},
+
"contextRegion": {"startLine": 8,
+
"snippet": {"text": " foo ();\n"}}},
+ "message": {"text": "grandchild
0 0"},
+ "properties": {"nestingLevel":
2}},
+ {"physicalLocation":
{"artifactLocation": {"uri": "diagnostic-test-nesting-sarif.c"},
+ "region":
{"startLine": 8,
+
"startColumn": 3,
+
"endColumn": 9},
+
"contextRegion": {"startLine": 8,
+
"snippet": {"text": " foo ();\n"}}},
+ "message": {"text": "grandchild
0 1"},
+ "properties": {"nestingLevel":
2}},
+ {"physicalLocation":
{"artifactLocation": {"uri": "diagnostic-test-nesting-sarif.c"},
+ "region":
{"startLine": 8,
+
"startColumn": 3,
+
"endColumn": 9},
+
"contextRegion": {"startLine": 8,
+
"snippet": {"text": " foo ();\n"}}},
+ "message": {"text": "grandchild
0 2"},
+ "properties": {"nestingLevel":
2}},
+ {"physicalLocation":
{"artifactLocation": {"uri": "diagnostic-test-nesting-sarif.c"},
+ "region":
{"startLine": 8,
+
"startColumn": 3,
+
"endColumn": 9},
+
"contextRegion": {"startLine": 8,
+
"snippet": {"text": " foo ();\n"}}},
+ "message": {"text": "child 1"},
+ "properties": {"nestingLevel":
1}},
+ {"physicalLocation":
{"artifactLocation": {"uri": "diagnostic-test-nesting-sarif.c"},
+ "region":
{"startLine": 8,
+
"startColumn": 3,
+
"endColumn": 9},
+
"contextRegion": {"startLine": 8,
+
"snippet": {"text": " foo ();\n"}}},
+ "message": {"text": "grandchild
1 0"},
+ "properties": {"nestingLevel":
2}},
+ {"physicalLocation":
{"artifactLocation": {"uri": "diagnostic-test-nesting-sarif.c"},
+ "region":
{"startLine": 8,
+
"startColumn": 3,
+
"endColumn": 9},
+
"contextRegion": {"startLine": 8,
+
"snippet": {"text": " foo ();\n"}}},
+ "message": {"text": "grandchild
1 1"},
+ "properties": {"nestingLevel":
2}},
+ {"physicalLocation":
{"artifactLocation": {"uri": "diagnostic-test-nesting-sarif.c"},
+ "region":
{"startLine": 8,
+
"startColumn": 3,
+
"endColumn": 9},
+
"contextRegion": {"startLine": 8,
+
"snippet": {"text": " foo ();\n"}}},
+ "message": {"text": "grandchild
1 2"},
+ "properties": {"nestingLevel":
2}},
+ {"physicalLocation":
{"artifactLocation": {"uri": "diagnostic-test-nesting-sarif.c"},
+ "region":
{"startLine": 8,
+
"startColumn": 3,
+
"endColumn": 9},
+
"contextRegion": {"startLine": 8,
+
"snippet": {"text": " foo ();\n"}}},
+ "message": {"text": "child 2"},
+ "properties": {"nestingLevel":
1}},
+ {"physicalLocation":
{"artifactLocation": {"uri": "diagnostic-test-nesting-sarif.c"},
+ "region":
{"startLine": 8,
+
"startColumn": 3,
+
"endColumn": 9},
+
"contextRegion": {"startLine": 8,
+
"snippet": {"text": " foo ();\n"}}},
+ "message": {"text": "grandchild
2 0"},
+ "properties": {"nestingLevel":
2}},
+ {"physicalLocation":
{"artifactLocation": {"uri": "diagnostic-test-nesting-sarif.c"},
+ "region":
{"startLine": 8,
+
"startColumn": 3,
+
"endColumn": 9},
+
"contextRegion": {"startLine": 8,
+
"snippet": {"text": " foo ();\n"}}},
+ "message": {"text": "grandchild
2 1"},
+ "properties": {"nestingLevel":
2}},
+ {"physicalLocation":
{"artifactLocation": {"uri": "diagnostic-test-nesting-sarif.c"},
+ "region":
{"startLine": 8,
+
"startColumn": 3,
+
"endColumn": 9},
+
"contextRegion": {"startLine": 8,
+
"snippet": {"text": " foo ();\n"}}},
+ "message": {"text": "grandchild
2 2"},
+ "properties": {"nestingLevel":
2}}]}],
+ "logicalLocations": [{"name": "test_nesting",
+ "fullyQualifiedName": "test_nesting",
+ "decoratedName": "test_nesting",
+ "kind": "function",
+ "index": 0}]}]}
+
+/* For now, we don't have a way of enabling showing the nesting
+ on the default text output. However we do test the nesting
+ in the SARIF and HTML outputs below. */
+/* { dg-begin-multiline-output "" }
+In function 'test_nesting':
+diagnostic-test-nesting-sarif.c:8:3: error: top-level error
+ 8 | }
+ | ^
+diagnostic-test-nesting-sarif.c:8:3: note: child 0
+diagnostic-test-nesting-sarif.c:8:3: note: grandchild 0 0
+diagnostic-test-nesting-sarif.c:8:3: note: grandchild 0 1
+diagnostic-test-nesting-sarif.c:8:3: note: grandchild 0 2
+diagnostic-test-nesting-sarif.c:8:3: note: child 1
+diagnostic-test-nesting-sarif.c:8:3: note: grandchild 1 0
+diagnostic-test-nesting-sarif.c:8:3: note: grandchild 1 1
+diagnostic-test-nesting-sarif.c:8:3: note: grandchild 1 2
+diagnostic-test-nesting-sarif.c:8:3: note: child 2
+diagnostic-test-nesting-sarif.c:8:3: note: grandchild 2 0
+diagnostic-test-nesting-sarif.c:8:3: note: grandchild 2 1
+diagnostic-test-nesting-sarif.c:8:3: note: grandchild 2 2
+ { dg-end-multiline-output "" } */
+
+/* Use a Python script to verify various properties about the *generated*
+ .sarif file:
+ { dg-final { run-sarif-pytest nested-diagnostics-1.roundtrip
"../gcc.dg/plugin/diagnostic-test-nesting-sarif.py" } } */
+
+/* Use a Python script to verify various properties about the generated
+ .html file:
+ { dg-final { run-html-pytest nested-diagnostics-1.sarif
"../gcc.dg/plugin/diagnostic-test-nesting-html.py" } } */
--
2.26.3