Add barebones support for
* diagnostic metadata rules
* quoted source
* generated patches
* execution paths

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r16-579-ge4ccad8faf5266.

gcc/ChangeLog:
        PR other/116792
        * diagnostic-format-html.cc: Include "diagnostic-format-text.h",
        "pretty-print-urlifier.h" and "edit-context.h".
        (html_builder::html_builder): Fix indentation in decl.
        (html_builder::make_element_for_diagnostic): Split out metadata
        code into make_element_for_metadata.  Call
        make_element_for_source, make_element_for_path, and
        make_element_for_patch.
        (html_builder::make_element_for_source): New.
        (html_builder::make_element_for_path): New.
        (html_builder::make_element_for_patch): New.
        (html_builder::make_metadata_element): New.
        (html_builder::make_element_for_metadata): New.
        (html_output_format::get_builder): New.
        (selftest::test_html_diagnostic_context::get_builder): New.
        (selftest::test_simple_log): Update test to print a quoted string,
        and verify that it uses a "gcc-quoted-text" span.
        (selftest::test_metadata): New.
        (selftest::diagnostic_format_html_cc_tests): Call it.

gcc/testsuite/ChangeLog:
        PR other/116792
        * gcc.dg/html-output/missing-semicolon.py: Verify that we don't
        have an empty "gcc-annotated-source" and we do have a
        "gcc-generated-patch".
        * gcc.dg/plugin/diagnostic-test-metadata-html.c: New test.
        * gcc.dg/plugin/diagnostic-test-metadata-html.py: New test script.
        * gcc.dg/plugin/diagnostic-test-paths-2.c: Add
        "-fdiagnostics-add-output=experimental-html" to options. Add
        invocation of diagnostic-test-paths-2.py.
        * gcc.dg/plugin/diagnostic-test-paths-2.py: New test script.
        * gcc.dg/plugin/plugin.exp (plugin_test_list): Add
        diagnostic-test-metadata-html.c.
---
 gcc/diagnostic-format-html.cc                 | 233 +++++++++++++++---
 .../gcc.dg/html-output/missing-semicolon.py   |   7 +-
 .../plugin/diagnostic-test-metadata-html.c    |  15 ++
 .../plugin/diagnostic-test-metadata-html.py   |  68 +++++
 .../gcc.dg/plugin/diagnostic-test-paths-2.c   |   6 +-
 .../gcc.dg/plugin/diagnostic-test-paths-2.py  |  35 +++
 gcc/testsuite/gcc.dg/plugin/plugin.exp        |   1 +
 7 files changed, 326 insertions(+), 39 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.py
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.py

diff --git a/gcc/diagnostic-format-html.cc b/gcc/diagnostic-format-html.cc
index 2d642dfc33c..6bb1caf41d1 100644
--- a/gcc/diagnostic-format-html.cc
+++ b/gcc/diagnostic-format-html.cc
@@ -27,11 +27,14 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-metadata.h"
 #include "diagnostic-format.h"
 #include "diagnostic-format-html.h"
+#include "diagnostic-format-text.h"
 #include "diagnostic-output-file.h"
 #include "diagnostic-buffer.h"
 #include "selftest.h"
 #include "selftest-diagnostic.h"
 #include "pretty-print-format-impl.h"
+#include "pretty-print-urlifier.h"
+#include "edit-context.h"
 #include "intl.h"
 
 namespace xml {
@@ -280,8 +283,8 @@ public:
   friend class diagnostic_html_format_buffer;
 
   html_builder (diagnostic_context &context,
-                pretty_printer &pp,
-                const line_maps *line_maps);
+               pretty_printer &pp,
+               const line_maps *line_maps);
 
   void on_report_diagnostic (const diagnostic_info &diagnostic,
                             diagnostic_t orig_diag_kind,
@@ -303,11 +306,27 @@ public:
     m_printer = &pp;
   }
 
+  std::unique_ptr<xml::element>
+  make_element_for_metadata (const diagnostic_metadata &metadata);
+
+  std::unique_ptr<xml::element>
+  make_element_for_source (const diagnostic_info &diagnostic);
+
+  std::unique_ptr<xml::element>
+  make_element_for_path (const diagnostic_path &path);
+
+  std::unique_ptr<xml::element>
+  make_element_for_patch (const diagnostic_info &diagnostic);
+
 private:
   std::unique_ptr<xml::element>
   make_element_for_diagnostic (const diagnostic_info &diagnostic,
                               diagnostic_t orig_diag_kind);
 
+  std::unique_ptr<xml::element>
+  make_metadata_element (label_text label,
+                        label_text url);
+
   diagnostic_context &m_context;
   pretty_printer *m_printer;
   const line_maps *m_line_maps;
@@ -560,28 +579,11 @@ html_builder::make_element_for_diagnostic (const 
diagnostic_info &diagnostic,
 
   if (diagnostic.metadata)
     {
-      int cwe = diagnostic.metadata->get_cwe ();
-      if (cwe)
-       {
-         diag_element->add_text (label_text::borrow (" "));
-         auto cwe_span = make_span (label_text::borrow ("gcc-cwe-metadata"));
-         cwe_span->add_text (label_text::borrow ("["));
-         {
-           auto anchor = std::make_unique<xml::element> ("a", true);
-           anchor->set_attr ("href", label_text::take (get_cwe_url (cwe)));
-           pretty_printer pp;
-           pp_printf (&pp, "CWE-%i", cwe);
-           anchor->add_text
-             (label_text::take (xstrdup (pp_formatted_text (&pp))));
-           cwe_span->add_child (std::move (anchor));
-         }
-         cwe_span->add_text (label_text::borrow ("]"));
-         diag_element->add_child (std::move (cwe_span));
-       }
+      diag_element->add_text (label_text::borrow (" "));
+      diag_element->add_child
+       (make_element_for_metadata (*diagnostic.metadata));
     }
 
-  // TODO: show any rules
-
   label_text option_text = label_text::take
     (m_context.make_option_name (diagnostic.option_id,
                                 orig_diag_kind, diagnostic.kind));
@@ -608,20 +610,122 @@ html_builder::make_element_for_diagnostic (const 
diagnostic_info &diagnostic,
       diag_element->add_child (std::move (option_span));
     }
 
+  /* Source (and fix-it hints).  */
+  if (auto source_element = make_element_for_source (diagnostic))
+    diag_element->add_child (std::move (source_element));
+
+  /* Execution path.  */
+  if (auto path = diagnostic.richloc->get_path ())
+    if (auto path_element = make_element_for_path (*path))
+      diag_element->add_child (std::move (path_element));
+
+  if (auto patch_element = make_element_for_patch (diagnostic))
+    diag_element->add_child (std::move (patch_element));
+
+  return diag_element;
+}
+
+std::unique_ptr<xml::element>
+html_builder::make_element_for_source (const diagnostic_info &diagnostic)
+{
+  // TODO: ideally we'd like to capture elements within the following:
+  m_context.m_last_location = UNKNOWN_LOCATION;
+  pp_clear_output_area (m_printer);
+  diagnostic_show_locus (&m_context,
+                        m_context.m_source_printing,
+                        diagnostic.richloc, diagnostic.kind,
+                        m_printer);
+  auto text = label_text::take (xstrdup (pp_formatted_text (m_printer)));
+  pp_clear_output_area (m_printer);
+
+  if (strlen (text.get ()) == 0)
+    return nullptr;
+
+  auto pre = std::make_unique<xml::element> ("pre", true);
+  pre->set_attr ("class", label_text::borrow ("gcc-annotated-source"));
+  pre->add_text (std::move (text));
+  return pre;
+}
+
+std::unique_ptr<xml::element>
+html_builder::make_element_for_path (const diagnostic_path &path)
+{
+  m_context.m_last_location = UNKNOWN_LOCATION;
+  diagnostic_text_output_format text_format (m_context);
+  pp_show_color (text_format.get_printer ()) = false;
+  pp_buffer (text_format.get_printer ())->m_flush_p = false;
+  // TODO: ideally we'd like to capture elements within the following:
+  text_format.print_path (path);
+  auto text = label_text::take
+    (xstrdup (pp_formatted_text (text_format.get_printer ())));
+
+  if (strlen (text.get ()) == 0)
+    return nullptr;
+
+  auto pre = std::make_unique<xml::element> ("pre", true);
+  pre->set_attr ("class", label_text::borrow ("gcc-execution-path"));
+  pre->add_text (std::move (text));
+  return pre;
+}
+
+std::unique_ptr<xml::element>
+html_builder::make_element_for_patch (const diagnostic_info &diagnostic)
+{
+  edit_context ec (m_context.get_file_cache ());
+  ec.add_fixits (diagnostic.richloc);
+  if (char *diff = ec.generate_diff (true))
+    if (strlen (diff) > 0)
+      {
+       auto element = std::make_unique<xml::element> ("pre", true);
+       element->set_attr ("class", label_text::borrow ("gcc-generated-patch"));
+       element->add_text (label_text::take (diff));
+       return element;
+      }
+  return nullptr;
+}
+
+std::unique_ptr<xml::element>
+html_builder::make_metadata_element (label_text label,
+                                    label_text url)
+{
+  auto item = make_span (label_text::borrow ("gcc-metadata-item"));
+  item->add_text (label_text::borrow ("["));
   {
-    auto pre = std::make_unique<xml::element> ("pre", true);
-    pre->set_attr ("class", label_text::borrow ("gcc-annotated-source"));
-    // TODO: ideally we'd like to capture elements within the following:
-    diagnostic_show_locus (&m_context, m_context.m_source_printing,
-                          diagnostic.richloc, diagnostic.kind,
-                          m_printer);
-    pre->add_text
-      (label_text::take (xstrdup (pp_formatted_text (m_printer))));
-    pp_clear_output_area (m_printer);
-    diag_element->add_child (std::move (pre));
+    auto anchor = std::make_unique<xml::element> ("a", true);
+    anchor->set_attr ("href", std::move (url));
+    anchor->add_child (std::make_unique<xml::text> (std::move (label)));
+    item->add_child (std::move (anchor));
   }
+  item->add_text (label_text::borrow ("]"));
+  return item;
+}
 
-  return diag_element;
+std::unique_ptr<xml::element>
+html_builder::make_element_for_metadata (const diagnostic_metadata &metadata)
+{
+  auto span_metadata = make_span (label_text::borrow ("gcc-metadata"));
+
+  int cwe = metadata.get_cwe ();
+  if (cwe)
+    {
+      pretty_printer pp;
+      pp_printf (&pp, "CWE-%i", cwe);
+      label_text label = label_text::take (xstrdup (pp_formatted_text (&pp)));
+      label_text url = label_text::take (get_cwe_url (cwe));
+      span_metadata->add_child
+       (make_metadata_element (std::move (label), std::move (url)));
+    }
+
+  for (unsigned idx = 0; idx < metadata.get_num_rules (); ++idx)
+    {
+      auto &rule = metadata.get_rule (idx);
+      label_text label = label_text::take (rule.make_description ());
+      label_text url = label_text::take (rule.make_url ());
+      span_metadata->add_child
+       (make_metadata_element (std::move (label), std::move (url)));
+    }
+
+  return span_metadata;
 }
 
 /* Implementation of diagnostic_context::m_diagrams.m_emission_cb
@@ -734,6 +838,8 @@ public:
     return m_builder.get_document ();
   }
 
+  html_builder &get_builder () { return m_builder; }
+
 protected:
   html_output_format (diagnostic_context &context,
                      const line_maps *line_maps)
@@ -852,6 +958,11 @@ public:
     return m_format->get_document ();
   }
 
+  html_builder &get_builder () const
+  {
+    return m_format->get_builder ();
+  }
+
 private:
   class html_buffered_output_format : public html_output_format
   {
@@ -880,7 +991,7 @@ test_simple_log ()
   test_html_diagnostic_context dc;
 
   rich_location richloc (line_table, UNKNOWN_LOCATION);
-  dc.report (DK_ERROR, richloc, nullptr, 0, "this is a test: %i", 42);
+  dc.report (DK_ERROR, richloc, nullptr, 0, "this is a test: %qs", "foo");
 
   const xml::document &doc  = dc.get_document ();
 
@@ -899,20 +1010,70 @@ test_simple_log ()
       "  <body>\n"
       "    <div class=\"gcc-diagnostic-list\">\n"
       "      <div class=\"gcc-diagnostic\">\n"
-      "        <span class=\"gcc-message\">this is a test: 42</span>\n"
-      "        <pre class=\"gcc-annotated-source\"></pre>\n"
+      "        <span class=\"gcc-message\">this is a test: `<span 
class=\"gcc-quoted-text\">foo</span>&apos;</span>\n"
       "      </div>\n"
       "    </div>\n"
       "  </body>\n"
       "</html>"));
 }
 
+static void
+test_metadata ()
+{
+  test_html_diagnostic_context dc;
+  html_builder &b = dc.get_builder ();
+
+  {
+    diagnostic_metadata metadata;
+    metadata.add_cwe (415);
+    auto element = b.make_element_for_metadata (metadata);
+    pretty_printer pp;
+    element->write_as_xml (&pp, 0, true);
+    ASSERT_STREQ
+      (pp_formatted_text (&pp),
+       "\n"
+       "<span class=\"gcc-metadata\">"
+       "<span class=\"gcc-metadata-item\">"
+       "["
+       "<a href=\"https://cwe.mitre.org/data/definitions/415.html\";>"
+       "CWE-415"
+       "</a>"
+       "]"
+       "</span>"
+       "</span>");
+  }
+
+  {
+    diagnostic_metadata metadata;
+    diagnostic_metadata::precanned_rule rule ("MISC-42",
+                                             "http://example.com";);
+    metadata.add_rule (rule);
+    auto element = b.make_element_for_metadata (metadata);
+    pretty_printer pp;
+    element->write_as_xml (&pp, 0, true);
+    ASSERT_STREQ
+      (pp_formatted_text (&pp),
+       "\n"
+       "<span class=\"gcc-metadata\">"
+       "<span class=\"gcc-metadata-item\">"
+       "["
+       "<a href=\"http://example.com\";>"
+       "MISC-42"
+       "</a>"
+       "]"
+       "</span>"
+       "</span>");
+  }
+}
+
 /* Run all of the selftests within this file.  */
 
 void
 diagnostic_format_html_cc_tests ()
 {
+  auto_fix_quotes fix_quotes;
   test_simple_log ();
+  test_metadata ();
 }
 
 } // namespace selftest
diff --git a/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py 
b/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py
index 8687168e884..8ac1f142388 100644
--- a/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py
+++ b/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py
@@ -60,7 +60,8 @@ def test_basics(html_tree):
 
     pre = diag.find('xhtml:pre', ns)
     assert pre is not None
-    assert pre.attrib['class'] == 'gcc-annotated-source'
+    assert pre.attrib['class'] == 'gcc-generated-patch'
+    assert pre.text.startswith('--- ')
 
 # For reference, here's the generated HTML:
 """
@@ -76,7 +77,9 @@ def test_basics(html_tree):
     <div class="gcc-diagnostic-list">
       <div class="gcc-diagnostic">
         <span class="gcc-message">expected &apos;<span 
class="gcc-quoted-text">;</span>&apos; before &apos;<span 
class="gcc-quoted-text">}</span>&apos; token</span>
-        <pre class="gcc-annotated-source"></pre>
+        <pre class="gcc-generated-patch">
+        [...snip...]
+        </pre>
       </div>
     </div>
   </body>
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.c 
b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.c
new file mode 100644
index 00000000000..2499e8d288a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.c
@@ -0,0 +1,15 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-set-output=experimental-html" } */
+/* { dg-additional-options "-fdiagnostics-show-caret" } */
+
+extern char *gets (char *s);
+
+void test_cwe (void)
+{
+  char buf[1024];
+  gets (buf);
+}
+
+/* Use a Python script to verify various properties about the generated
+   HTML file:
+   { dg-final { run-html-pytest diagnostic-test-metadata-html.c 
"diagnostic-test-metadata-html.py" } } */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.py 
b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.py
new file mode 100644
index 00000000000..e475e95058b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.py
@@ -0,0 +1,68 @@
+# Verify that metadata works in HTML output.
+
+from htmltest import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def html_tree():
+    return html_tree_from_env()
+
+XHTML = 'http://www.w3.org/1999/xhtml'
+ns = {'xhtml': XHTML}
+
+def make_tag(local_name):
+    return f'{{{XHTML}}}' + local_name
+
+def test_metadata(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
+    assert diag.attrib['class'] == 'gcc-diagnostic'
+
+    spans = diag.findall('xhtml:span', ns)
+    metadata = spans[1]
+    assert metadata.attrib['class'] == 'gcc-metadata'
+    assert metadata[0].tag == make_tag('span')
+    assert metadata[0].attrib['class'] == 'gcc-metadata-item'
+    assert metadata[0].text == '['
+    assert metadata[0][0].tag == make_tag('a')
+    assert metadata[0][0].attrib['href'] == 
'https://cwe.mitre.org/data/definitions/242.html'
+    assert metadata[0][0].text == 'CWE-242'
+    assert metadata[0][0].tail == ']'
+
+    assert metadata[1].tag == make_tag('span')
+    assert metadata[1].attrib['class'] == 'gcc-metadata-item'
+    assert metadata[1].text == '['
+    assert metadata[1][0].tag == make_tag('a')
+    assert metadata[1][0].attrib['href'] == 'https://example.com/'
+    assert metadata[1][0].text == 'STR34-C'
+    assert metadata[1][0].tail == ']'
+
+    src = diag.find('xhtml:pre', ns)
+    assert src.attrib['class'] == 'gcc-annotated-source'
+    assert src.text == (
+        '   gets (buf);\n'
+        '   ^~~~~~~~~~\n')
+
+# For reference, here's the generated HTML:
+"""
+  <body>
+    <div class="gcc-diagnostic-list">
+      <div class="gcc-diagnostic">
+        <span class="gcc-message">never use &apos;<span 
class="gcc-quoted-text">gets</span>&apos;</span> 
+        <span class="gcc-metadata"><span class="gcc-metadata-item">[<a 
href="https://cwe.mitre.org/data/definitions/242.html";>CWE-242</a>]</span><span 
class="gcc-metadata-item">[<a 
href="https://example.com/";>STR34-C</a>]</span></span>
+        ...etc...
+      </div>
+    </div>
+  </body>
+"""
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c 
b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c
index b8134aebfda..26605f7607d 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-fdiagnostics-show-caret -fdiagnostics-show-line-numbers 
-fdiagnostics-path-format=inline-events" } */
+/* { dg-options "-fdiagnostics-show-caret -fdiagnostics-show-line-numbers 
-fdiagnostics-path-format=inline-events 
-fdiagnostics-add-output=experimental-html" } */
 
 #include <stddef.h>
 #include <stdlib.h>
@@ -52,3 +52,7 @@ make_a_list_of_random_ints_badly(PyObject *self,
       |     (3) when calling 'PyList_Append', passing NULL from (1) as 
argument 1
      { dg-end-multiline-output "" } */
 }
+
+/* Use a Python script to verify various properties about the generated
+   HTML file:
+   { dg-final { run-html-pytest diagnostic-test-paths-2.c 
"diagnostic-test-paths-2.py" } } */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.py 
b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.py
new file mode 100644
index 00000000000..c212e4906bb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.py
@@ -0,0 +1,35 @@
+# Verify that execution paths work in HTML output.
+
+from htmltest import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def html_tree():
+    return html_tree_from_env()
+
+XHTML = 'http://www.w3.org/1999/xhtml'
+ns = {'xhtml': XHTML}
+
+def make_tag(local_name):
+    return f'{{{XHTML}}}' + local_name
+
+def test_paths(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
+    assert diag.attrib['class'] == 'gcc-diagnostic'
+
+    pre = diag.findall('xhtml:pre', ns)
+    assert pre[0].attrib['class'] == 'gcc-annotated-source'
+    assert pre[1].attrib['class'] == 'gcc-execution-path'
+    assert pre[1].text.startswith("  'make_a_list_of_random_ints_badly': 
events 1-3")
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp 
b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index a84fbae7d3a..a066b67bb9a 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -105,6 +105,7 @@ set plugin_test_list [list \
          diagnostic-test-inlining-4.c } \
     { diagnostic_plugin_test_metadata.cc
          diagnostic-test-metadata.c \
+         diagnostic-test-metadata-html.c \
          diagnostic-test-metadata-sarif.c } \
     { diagnostic_plugin_test_nesting.cc \
          diagnostic-test-nesting-text-plain.c \
-- 
2.49.0

Reply via email to