https://gcc.gnu.org/g:7969e4859ed0079b4a1462ea82aa6b2dd3d17f72

commit r16-2766-g7969e4859ed0079b4a1462ea82aa6b2dd3d17f72
Author: David Malcolm <dmalc...@redhat.com>
Date:   Mon Aug 4 10:45:31 2025 -0400

    diagnostics: improve support for nesting levels [PR116253]
    
    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).
    
    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.
    
    Signed-off-by: David Malcolm <dmalc...@redhat.com>

Diff:
---
 gcc/diagnostics/context.cc                         |   6 +
 gcc/diagnostics/context.h                          |   1 +
 gcc/doc/libgdiagnostics/topics/compatibility.rst   |   9 ++
 .../libgdiagnostics/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 +-
 .../gcc.dg/plugin/diagnostic-test-nesting-html.c   |  13 ++
 .../gcc.dg/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(-)

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
+
+@pytest.fixture(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" } } */

Reply via email to