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>'</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 '<span class="gcc-quoted-text">;</span>' before '<span class="gcc-quoted-text">}</span>' 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 '<span class="gcc-quoted-text">gets</span>'</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