In r15-3752-g48261bd26df624 I added a test plugin that overrode the regular output, instead emitting diagnostics in crude HTML form.
In r15-4760-g0b73e9382ab51c I added support for multiple kinds of diagnostic output simultaneously, adding -fdiagnostics-add-output=DIAGNOSTICS-OUTPUT-SPEC -fdiagnostics-set-output=DIAGNOSTICS-OUTPUT-SPEC for adding/changing the kind of diagnostics output, supporting "text" and "sarif" output schemes. This patch promotes the HTML output code from the test plugins so that it is available from "-fdiagnostics-add-output=", using a new "experimental-html" scheme, to allow simultaneous text, sarif and html output, and to make it easier to experiment with. The patch adds Python-based testing of the emitted HTML. The patch does not affect the generated HTML, which is still crude, and not yet ready for end-users. I hope to improve it in followups. Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu. Pushed to trunk as r16-487-g1a2c62212bd912. gcc/ChangeLog: PR other/116792 * Makefile.in (OBJS-libcommon): Add diagnostic-format-html.o. * diagnostic-format-html.cc: Move here from testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.cc. Simplify includes. Rename "xhtml" to "html" throughout. (write_escaped_text): Drop. (class xhtml_stream_output_format): Drop. (class html_file_output_format): Reimplement using diagnostic_output_file. (diagnostic_output_format_init_xhtml): Drop. (diagnostic_output_format_init_xhtml_stderr): Drop. (diagnostic_output_format_init_xhtml_file): Drop. (diagnostic_output_format_open_html_file): New. (make_html_sink): New. (xhtml_format_selftests): Convert to... (diagnostic_format_html_cc_tests): ...this. (plugin_is_GPL_compatible): Drop. (plugin_init): Drop. * diagnostic-format-html.h: New file. * doc/invoke.texi (-fdiagnostics-add-output=): Add "experimental-html" scheme. * opts-diagnostic.cc: Include "diagnostic-format-html.h". (class html_scheme_handler): New. (output_factory::output_factory): Add html_scheme_handler. (html_scheme_handler::make_sink): New. * selftest-run-tests.cc (selftest::run_tests): Call the new selftests. * selftest.h (selftest::diagnostic_format_html_cc_tests): New decl. gcc/testsuite/ChangeLog: PR other/116792 * gcc.dg/plugin/diagnostic_plugin_xhtml_format.cc: Move to gcc/diagnostic-format-html.cc. * gcc.dg/html-output/html-output.exp: New support script. * gcc.dg/html-output/missing-semicolon.c: New test. * gcc.dg/html-output/missing-semicolon.py: New test script. * gcc.dg/plugin/diagnostic-test-xhtml-1.c: Deleted test. * gcc.dg/plugin/plugin.exp (plugin_test_list): Drop moved plugin and its deleted test. * lib/gcc-dg.exp (load_lib): Add load_lib of scanhtml.exp. * lib/htmltest.py: New support script. * lib/scanhtml.exp: New support script, based on scansarif.exp. libatomic/ChangeLog: PR other/116792 * testsuite/lib/libatomic.exp: Add load_lib of scanhtml.exp. libgomp/ChangeLog: PR other/116792 * testsuite/lib/libgomp.exp: Add load_lib of scanhtml.exp. libitm/ChangeLog: PR other/116792 * testsuite/lib/libitm.exp: Add load_lib of scanhtml.exp. libphobos/ChangeLog: PR other/116792 * testsuite/lib/libphobos-dg.exp: Add load_lib of scanhtml.exp. libvtv/ChangeLog: PR other/116792 * testsuite/lib/libvtv-dg.exp: Add load_lib of scanhtml.exp. --- gcc/Makefile.in | 1 + ...ml_format.cc => diagnostic-format-html.cc} | 323 +++++++----------- gcc/diagnostic-format-html.h | 37 ++ gcc/doc/invoke.texi | 18 +- gcc/opts-diagnostic.cc | 60 ++++ gcc/selftest-run-tests.cc | 1 + gcc/selftest.h | 1 + .../gcc.dg/html-output/html-output.exp | 31 ++ .../gcc.dg/html-output/missing-semicolon.c | 13 + .../gcc.dg/html-output/missing-semicolon.py | 84 +++++ .../gcc.dg/plugin/diagnostic-test-xhtml-1.c | 19 -- gcc/testsuite/gcc.dg/plugin/plugin.exp | 2 - gcc/testsuite/lib/gcc-dg.exp | 1 + gcc/testsuite/lib/htmltest.py | 9 + gcc/testsuite/lib/scanhtml.exp | 90 +++++ libatomic/testsuite/lib/libatomic.exp | 1 + libgomp/testsuite/lib/libgomp.exp | 1 + libitm/testsuite/lib/libitm.exp | 1 + libphobos/testsuite/lib/libphobos-dg.exp | 1 + libvtv/testsuite/lib/libvtv-dg.exp | 1 + 20 files changed, 479 insertions(+), 216 deletions(-) rename gcc/{testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.cc => diagnostic-format-html.cc} (72%) create mode 100644 gcc/diagnostic-format-html.h create mode 100644 gcc/testsuite/gcc.dg/html-output/html-output.exp create mode 100644 gcc/testsuite/gcc.dg/html-output/missing-semicolon.c create mode 100644 gcc/testsuite/gcc.dg/html-output/missing-semicolon.py delete mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-xhtml-1.c create mode 100644 gcc/testsuite/lib/htmltest.py create mode 100644 gcc/testsuite/lib/scanhtml.exp diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 55b4cd7dbed3..e3af923e0e04 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1850,6 +1850,7 @@ OBJS = \ # Objects in libcommon.a, potentially used by all host binaries and with # no target dependencies. OBJS-libcommon = diagnostic-spec.o diagnostic.o diagnostic-color.o \ + diagnostic-format-html.o \ diagnostic-format-json.o \ diagnostic-format-sarif.o \ diagnostic-format-text.o \ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.cc b/gcc/diagnostic-format-html.cc similarity index 72% rename from gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.cc rename to gcc/diagnostic-format-html.cc index 24c6f8ce557b..2d642dfc33cb 100644 --- a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.cc +++ b/gcc/diagnostic-format-html.cc @@ -1,6 +1,5 @@ -/* Verify that we can write a non-trivial diagnostic output format - as a plugin (XHTML). - Copyright (C) 2018-2024 Free Software Foundation, Inc. +/* HTML output for diagnostics. + Copyright (C) 2024-2025 Free Software Foundation, Inc. Contributed by David Malcolm <dmalc...@redhat.com>. This file is part of GCC. @@ -19,35 +18,21 @@ You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see <http://www.gnu.org/licenses/>. */ - #include "config.h" -#define INCLUDE_LIST #define INCLUDE_MAP -#define INCLUDE_MEMORY #define INCLUDE_VECTOR #include "system.h" #include "coretypes.h" #include "diagnostic.h" #include "diagnostic-metadata.h" -#include "diagnostic-path.h" -#include "cpplib.h" -#include "logical-location.h" -#include "diagnostic-client-data-hooks.h" -#include "diagnostic-diagram.h" -#include "text-art/canvas.h" #include "diagnostic-format.h" +#include "diagnostic-format-html.h" +#include "diagnostic-output-file.h" #include "diagnostic-buffer.h" -#include "ordered-hash-map.h" -#include "sbitmap.h" #include "selftest.h" #include "selftest-diagnostic.h" -#include "selftest-diagnostic-show-locus.h" -#include "text-range-label.h" #include "pretty-print-format-impl.h" -#include "pretty-print-urlifier.h" #include "intl.h" -#include "gcc-plugin.h" -#include "plugin-version.h" namespace xml { @@ -58,8 +43,6 @@ namespace xml { # pragma GCC diagnostic ignored "-Wformat-diag" #endif -static void write_escaped_text (const char *text); - struct node { virtual ~node () {} @@ -246,17 +229,17 @@ element::set_attr (const char *name, label_text value) } // namespace xml -class xhtml_builder; +class html_builder; /* Concrete buffering implementation subclass for HTML output. */ -class diagnostic_xhtml_format_buffer : public diagnostic_per_format_buffer +class diagnostic_html_format_buffer : public diagnostic_per_format_buffer { public: - friend class xhtml_builder; - friend class xhtml_output_format; + friend class html_builder; + friend class html_output_format; - diagnostic_xhtml_format_buffer (xhtml_builder &builder) + diagnostic_html_format_buffer (html_builder &builder) : m_builder (builder) {} @@ -272,11 +255,11 @@ public: } private: - xhtml_builder &m_builder; + html_builder &m_builder; std::vector<std::unique_ptr<xml::element>> m_results; }; -/* A class for managing XHTML output of diagnostics. +/* A class for managing HTML output of diagnostics. Implemented: - message text @@ -291,18 +274,18 @@ private: - paths */ -class xhtml_builder +class html_builder { public: - friend class diagnostic_xhtml_format_buffer; + friend class diagnostic_html_format_buffer; - xhtml_builder (diagnostic_context &context, + html_builder (diagnostic_context &context, pretty_printer &pp, const line_maps *line_maps); void on_report_diagnostic (const diagnostic_info &diagnostic, diagnostic_t orig_diag_kind, - diagnostic_xhtml_format_buffer *buffer); + diagnostic_html_format_buffer *buffer); void emit_diagram (const diagnostic_diagram &diagram); void end_group (); @@ -350,12 +333,12 @@ make_span (label_text class_) return span; } -/* class diagnostic_xhtml_format_buffer : public diagnostic_per_format_buffer. */ +/* class diagnostic_html_format_buffer : public diagnostic_per_format_buffer. */ void -diagnostic_xhtml_format_buffer::dump (FILE *out, int indent) const +diagnostic_html_format_buffer::dump (FILE *out, int indent) const { - fprintf (out, "%*sdiagnostic_xhtml_format_buffer:\n", indent, ""); + fprintf (out, "%*sdiagnostic_html_format_buffer:\n", indent, ""); int idx = 0; for (auto &result : m_results) { @@ -367,40 +350,40 @@ diagnostic_xhtml_format_buffer::dump (FILE *out, int indent) const } bool -diagnostic_xhtml_format_buffer::empty_p () const +diagnostic_html_format_buffer::empty_p () const { return m_results.empty (); } void -diagnostic_xhtml_format_buffer::move_to (diagnostic_per_format_buffer &base) +diagnostic_html_format_buffer::move_to (diagnostic_per_format_buffer &base) { - diagnostic_xhtml_format_buffer &dest - = static_cast<diagnostic_xhtml_format_buffer &> (base); + diagnostic_html_format_buffer &dest + = static_cast<diagnostic_html_format_buffer &> (base); for (auto &&result : m_results) dest.m_results.push_back (std::move (result)); m_results.clear (); } void -diagnostic_xhtml_format_buffer::clear () +diagnostic_html_format_buffer::clear () { m_results.clear (); } void -diagnostic_xhtml_format_buffer::flush () +diagnostic_html_format_buffer::flush () { for (auto &&result : m_results) m_builder.m_diagnostics_element->add_child (std::move (result)); m_results.clear (); } -/* class xhtml_builder. */ +/* class html_builder. */ -/* xhtml_builder's ctor. */ +/* html_builder's ctor. */ -xhtml_builder::xhtml_builder (diagnostic_context &context, +html_builder::html_builder (diagnostic_context &context, pretty_printer &pp, const line_maps *line_maps) : m_context (context), @@ -440,12 +423,12 @@ xhtml_builder::xhtml_builder (diagnostic_context &context, } } -/* Implementation of "on_report_diagnostic" for XHTML output. */ +/* Implementation of "on_report_diagnostic" for HTML output. */ void -xhtml_builder::on_report_diagnostic (const diagnostic_info &diagnostic, - diagnostic_t orig_diag_kind, - diagnostic_xhtml_format_buffer *buffer) +html_builder::on_report_diagnostic (const diagnostic_info &diagnostic, + diagnostic_t orig_diag_kind, + diagnostic_html_format_buffer *buffer) { if (diagnostic.kind == DK_ICE || diagnostic.kind == DK_ICE_NOBT) { @@ -476,13 +459,13 @@ xhtml_builder::on_report_diagnostic (const diagnostic_info &diagnostic, } std::unique_ptr<xml::element> -xhtml_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic, - diagnostic_t orig_diag_kind) +html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic, + diagnostic_t orig_diag_kind) { - class xhtml_token_printer : public token_printer + class html_token_printer : public token_printer { public: - xhtml_token_printer (xhtml_builder &builder, + html_token_printer (html_builder &builder, xml::element &parent_element) : m_builder (builder) { @@ -557,7 +540,7 @@ xhtml_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic, m_open_elements.pop_back (); } - xhtml_builder &m_builder; + html_builder &m_builder; /* We maintain a stack of currently "open" elements. Children are added to the topmost open element. */ std::vector<xml::element *> m_open_elements; @@ -568,7 +551,7 @@ xhtml_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic, // TODO: might be nice to emulate the text output format, but colorize it auto message_span = make_span (label_text::borrow ("gcc-message")); - xhtml_token_printer tok_printer (*this, *message_span.get ()); + html_token_printer tok_printer (*this, *message_span.get ()); m_printer->set_token_printer (&tok_printer); pp_output_formatted_text (m_printer, m_context.get_urlifier ()); m_printer->set_token_printer (nullptr); @@ -642,10 +625,10 @@ xhtml_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic, } /* Implementation of diagnostic_context::m_diagrams.m_emission_cb - for XHTML output. */ + for HTML output. */ void -xhtml_builder::emit_diagram (const diagnostic_diagram &/*diagram*/) +html_builder::emit_diagram (const diagnostic_diagram &/*diagram*/) { /* We must be within the emission of a top-level diagnostic. */ gcc_assert (m_cur_diagnostic_element); @@ -653,10 +636,10 @@ xhtml_builder::emit_diagram (const diagnostic_diagram &/*diagram*/) // TODO } -/* Implementation of "end_group_cb" for XHTML output. */ +/* Implementation of "end_group_cb" for HTML output. */ void -xhtml_builder::end_group () +html_builder::end_group () { if (m_cur_diagnostic_element) m_diagnostics_element->add_child (std::move (m_cur_diagnostic_element)); @@ -668,17 +651,17 @@ xhtml_builder::end_group () Flush it all to OUTF. */ void -xhtml_builder::flush_to_file (FILE *outf) +html_builder::flush_to_file (FILE *outf) { auto top = m_document.get (); top->dump (outf); fprintf (outf, "\n"); } -class xhtml_output_format : public diagnostic_output_format +class html_output_format : public diagnostic_output_format { public: - ~xhtml_output_format () + ~html_output_format () { /* Any diagnostics should have been handled by now. If not, then something's gone wrong with diagnostic @@ -690,19 +673,19 @@ public: void dump (FILE *out, int indent) const override { - fprintf (out, "%*sxhtml_output_format\n", indent, ""); + fprintf (out, "%*shtml_output_format\n", indent, ""); diagnostic_output_format::dump (out, indent); } std::unique_ptr<diagnostic_per_format_buffer> make_per_format_buffer () final override { - return std::make_unique<diagnostic_xhtml_format_buffer> (m_builder); + return std::make_unique<diagnostic_html_format_buffer> (m_builder); } void set_buffer (diagnostic_per_format_buffer *base_buffer) final override { - diagnostic_xhtml_format_buffer *buffer - = static_cast<diagnostic_xhtml_format_buffer *> (base_buffer); + diagnostic_html_format_buffer *buffer + = static_cast<diagnostic_html_format_buffer *> (base_buffer); m_buffer = buffer; } @@ -752,66 +735,39 @@ public: } protected: - xhtml_output_format (diagnostic_context &context, - const line_maps *line_maps) + html_output_format (diagnostic_context &context, + const line_maps *line_maps) : diagnostic_output_format (context), m_builder (context, *get_printer (), line_maps), m_buffer (nullptr) {} - xhtml_builder m_builder; - diagnostic_xhtml_format_buffer *m_buffer; + html_builder m_builder; + diagnostic_html_format_buffer *m_buffer; }; -class xhtml_stream_output_format : public xhtml_output_format +class html_file_output_format : public html_output_format { public: - xhtml_stream_output_format (diagnostic_context &context, - const line_maps *line_maps, - FILE *stream) - : xhtml_output_format (context, line_maps), - m_stream (stream) - { - } - ~xhtml_stream_output_format () + html_file_output_format (diagnostic_context &context, + const line_maps *line_maps, + diagnostic_output_file output_file) + : html_output_format (context, line_maps), + m_output_file (std::move (output_file)) { - m_builder.flush_to_file (m_stream); + gcc_assert (m_output_file.get_open_file ()); + gcc_assert (m_output_file.get_filename ()); } - bool machine_readable_stderr_p () const final override + ~html_file_output_format () { - return m_stream == stderr; + m_builder.flush_to_file (m_output_file.get_open_file ()); } -private: - FILE *m_stream; -}; - -class xhtml_file_output_format : public xhtml_output_format -{ -public: - xhtml_file_output_format (diagnostic_context &context, - const line_maps *line_maps, - const char *base_file_name) - : xhtml_output_format (context, line_maps), - m_base_file_name (xstrdup (base_file_name)) - { - } - ~xhtml_file_output_format () + void dump (FILE *out, int indent) const override { - char *filename = concat (m_base_file_name, ".xhtml", nullptr); - free (m_base_file_name); - m_base_file_name = nullptr; - FILE *outf = fopen (filename, "w"); - if (!outf) - { - const char *errstr = xstrerror (errno); - fnotice (stderr, "error: unable to open '%s' for writing: %s\n", - filename, errstr); - free (filename); - return; - } - m_builder.flush_to_file (outf); - fclose (outf); - free (filename); + fprintf (out, "%*shtml_file_output_format: %s\n", + indent, "", + m_output_file.get_filename ()); + diagnostic_output_format::dump (out, indent); } bool machine_readable_stderr_p () const final override { @@ -819,68 +775,76 @@ public: } private: - char *m_base_file_name; + diagnostic_output_file m_output_file; }; -/* Populate CONTEXT in preparation for XHTML output (either to stderr, or - to a file). */ +/* Attempt to open BASE_FILE_NAME.html for writing. + Return a non-null diagnostic_output_file, + or return a null diagnostic_output_file and complain to CONTEXT + using LINE_MAPS. */ -static void -diagnostic_output_format_init_xhtml (diagnostic_context &context, - std::unique_ptr<xhtml_output_format> fmt) +diagnostic_output_file +diagnostic_output_format_open_html_file (diagnostic_context &context, + line_maps *line_maps, + const char *base_file_name) { - /* Don't colorize the text. */ - pp_show_color (fmt->get_printer ()) = false; - context.set_show_highlight_colors (false); - - context.set_output_format (std::move (fmt)); -} - -/* Populate CONTEXT in preparation for XHTML output to stderr. */ + if (!base_file_name) + { + rich_location richloc (line_maps, UNKNOWN_LOCATION); + context.emit_diagnostic_with_group + (DK_ERROR, richloc, nullptr, 0, + "unable to determine filename for HTML output"); + return diagnostic_output_file (); + } -void -diagnostic_output_format_init_xhtml_stderr (diagnostic_context &context, - const line_maps *line_maps) -{ - gcc_assert (line_maps); - auto format = std::make_unique<xhtml_stream_output_format> (context, - line_maps, - stderr); - diagnostic_output_format_init_xhtml (context, std::move (format)); + label_text filename = label_text::take (concat (base_file_name, + ".html", + nullptr)); + FILE *outf = fopen (filename.get (), "w"); + if (!outf) + { + rich_location richloc (line_maps, UNKNOWN_LOCATION); + context.emit_diagnostic_with_group + (DK_ERROR, richloc, nullptr, 0, + "unable to open %qs for HTML output: %m", + filename.get ()); + return diagnostic_output_file (); + } + return diagnostic_output_file (outf, true, std::move (filename)); } -/* Populate CONTEXT in preparation for XHTML output to a file named - BASE_FILE_NAME.xhtml. */ - -void -diagnostic_output_format_init_xhtml_file (diagnostic_context &context, - const line_maps *line_maps, - const char *base_file_name) -{ - gcc_assert (line_maps); - auto format = std::make_unique<xhtml_file_output_format> (context, - line_maps, - base_file_name); - diagnostic_output_format_init_xhtml (context, std::move (format)); +std::unique_ptr<diagnostic_output_format> +make_html_sink (diagnostic_context &context, + const line_maps &line_maps, + diagnostic_output_file output_file) +{ + auto sink + = std::make_unique<html_file_output_format> (context, + &line_maps, + std::move (output_file)); + sink->update_printer (); + return sink; } #if CHECKING_P namespace selftest { -/* A subclass of xhtml_output_format for writing selftests. +/* A subclass of html_output_format for writing selftests. The XML output is cached internally, rather than written out to a file. */ -class test_xhtml_diagnostic_context : public test_diagnostic_context +class test_html_diagnostic_context : public test_diagnostic_context { public: - test_xhtml_diagnostic_context () + test_html_diagnostic_context () { - auto format = std::make_unique<xhtml_buffered_output_format> (*this, - line_table); - m_format = format.get (); // borrowed - diagnostic_output_format_init_xhtml (*this, std::move (format)); + auto sink = std::make_unique<html_buffered_output_format> (*this, + line_table); + sink->update_printer (); + m_format = sink.get (); // borrowed + + set_output_format (std::move (sink)); } const xml::document &get_document () const @@ -889,12 +853,12 @@ public: } private: - class xhtml_buffered_output_format : public xhtml_output_format + class html_buffered_output_format : public html_output_format { public: - xhtml_buffered_output_format (diagnostic_context &context, - const line_maps *line_maps) - : xhtml_output_format (context, line_maps) + html_buffered_output_format (diagnostic_context &context, + const line_maps *line_maps) + : html_output_format (context, line_maps) { } bool machine_readable_stderr_p () const final override @@ -903,17 +867,17 @@ private: } }; - xhtml_output_format *m_format; // borrowed + html_output_format *m_format; // borrowed }; - /* Test of reporting a diagnostic at UNKNOWN_LOCATION to a - diagnostic_context and examining the generated XML document. - Verify various basic properties. */ +/* Test of reporting a diagnostic at UNKNOWN_LOCATION to a + diagnostic_context and examining the generated XML document. + Verify various basic properties. */ static void test_simple_log () { - test_xhtml_diagnostic_context dc; + 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); @@ -945,8 +909,8 @@ test_simple_log () /* Run all of the selftests within this file. */ -static void -xhtml_format_selftests () +void +diagnostic_format_html_cc_tests () { test_simple_log (); } @@ -954,32 +918,3 @@ xhtml_format_selftests () } // namespace selftest #endif /* CHECKING_P */ - -/* Plugin hooks. */ - -int plugin_is_GPL_compatible; - -/* Entrypoint for the plugin. */ - -int -plugin_init (struct plugin_name_args *plugin_info, - struct plugin_gcc_version *version) -{ - const char *plugin_name = plugin_info->base_name; - int argc = plugin_info->argc; - struct plugin_argument *argv = plugin_info->argv; - - if (!plugin_default_version_check (version, &gcc_version)) - return 1; - - global_dc->set_output_format - (std::make_unique<xhtml_stream_output_format> (*global_dc, - line_table, - stderr)); - -#if CHECKING_P - selftest::xhtml_format_selftests (); -#endif - - return 0; -} diff --git a/gcc/diagnostic-format-html.h b/gcc/diagnostic-format-html.h new file mode 100644 index 000000000000..ff5edcaf1749 --- /dev/null +++ b/gcc/diagnostic-format-html.h @@ -0,0 +1,37 @@ +/* HTML output for diagnostics. + Copyright (C) 2024-2025 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalc...@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 3, or (at your option) any later +version. + +GCC is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#ifndef GCC_DIAGNOSTIC_FORMAT_HTML_H +#define GCC_DIAGNOSTIC_FORMAT_HTML_H + +#include "diagnostic-format.h" +#include "diagnostic-output-file.h" + +extern diagnostic_output_file +diagnostic_output_format_open_html_file (diagnostic_context &context, + line_maps *line_maps, + const char *base_file_name); + +extern std::unique_ptr<diagnostic_output_format> +make_html_sink (diagnostic_context &context, + const line_maps &line_maps, + diagnostic_output_file output_file); + +#endif /* ! GCC_DIAGNOSTIC_FORMAT_HTML_H */ diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 32bc45725de9..ecc407cc1d0e 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -6072,6 +6072,22 @@ in this release. @end table +@item experimental-html +Emit diagnostics to a file in HTML format. This scheme is experimental, +and may go away in future GCC releases. The details of the output are +also subject to change. + +Supported keys are: + +@table @gcctabopt + +@item file=@var{FILENAME} +Specify the filename to write the HTML output to, potentially with a +leading absolute or relative path. If not specified, it defaults to +@file{@var{source}.html}. + +@end table + @end table For example, @@ -6091,7 +6107,7 @@ In EBNF: @var{diagnostics-output-specifier} = @var{diagnostics-output-name} | @var{diagnostics-output-name}, ":", @var{key-value-pairs}; -@var{diagnostics-output-name} = "text" | "sarif"; +@var{diagnostics-output-name} = "text" | "sarif" | "experimental-html"; @var{key-value-pairs} = @var{key-value-pair} | @var{key-value-pair} "," @var{key-value-pairs}; diff --git a/gcc/opts-diagnostic.cc b/gcc/opts-diagnostic.cc index 1eec0103d3b6..34c390695b95 100644 --- a/gcc/opts-diagnostic.cc +++ b/gcc/opts-diagnostic.cc @@ -32,6 +32,7 @@ along with GCC; see the file COPYING3. If not see #include "diagnostic.h" #include "diagnostic-color.h" #include "diagnostic-format.h" +#include "diagnostic-format-html.h" #include "diagnostic-format-text.h" #include "diagnostic-format-sarif.h" #include "selftest.h" @@ -217,6 +218,17 @@ public: const scheme_name_and_params &parsed_arg) const final override; }; +class html_scheme_handler : public output_factory::scheme_handler +{ +public: + html_scheme_handler () : scheme_handler ("experimental-html") {} + + std::unique_ptr<diagnostic_output_format> + make_sink (const context &ctxt, + const char *unparsed_arg, + const scheme_name_and_params &parsed_arg) const final override; +}; + /* struct context. */ void @@ -318,6 +330,7 @@ output_factory::output_factory () { m_scheme_handlers.push_back (std::make_unique<text_scheme_handler> ()); m_scheme_handlers.push_back (std::make_unique<sarif_scheme_handler> ()); + m_scheme_handlers.push_back (std::make_unique<html_scheme_handler> ()); } const output_factory::scheme_handler * @@ -525,6 +538,53 @@ sarif_scheme_handler::make_sink (const context &ctxt, return sink; } +/* class html_scheme_handler : public output_factory::scheme_handler. */ + +std::unique_ptr<diagnostic_output_format> +html_scheme_handler::make_sink (const context &ctxt, + const char *unparsed_arg, + const scheme_name_and_params &parsed_arg) const +{ + label_text filename; + for (auto& iter : parsed_arg.m_kvs) + { + const std::string &key = iter.first; + const std::string &value = iter.second; + if (key == "file") + { + filename = label_text::take (xstrdup (value.c_str ())); + continue; + } + + /* Key not found. */ + auto_vec<const char *> known_keys; + known_keys.safe_push ("file"); + ctxt.report_unknown_key (unparsed_arg, key, get_scheme_name (), known_keys); + return nullptr; + } + + diagnostic_output_file output_file; + if (filename.get ()) + output_file = ctxt.open_output_file (std::move (filename)); + else + // Default filename + { + const char *basename = (ctxt.m_opts.x_dump_base_name + ? ctxt.m_opts.x_dump_base_name + : ctxt.m_opts.x_main_input_basename); + output_file = diagnostic_output_format_open_html_file (ctxt.m_dc, + line_table, + basename); + } + if (!output_file) + return nullptr; + + auto sink = make_html_sink (ctxt.m_dc, + *line_table, + std::move (output_file)); + return sink; +} + } // namespace diagnostics_output_spec } // namespace gcc diff --git a/gcc/selftest-run-tests.cc b/gcc/selftest-run-tests.cc index 3c12e8a3ebd9..959e5d0a0f8d 100644 --- a/gcc/selftest-run-tests.cc +++ b/gcc/selftest-run-tests.cc @@ -98,6 +98,7 @@ selftest::run_tests () rely on. */ diagnostic_color_cc_tests (); diagnostic_show_locus_cc_tests (); + diagnostic_format_html_cc_tests (); diagnostic_format_json_cc_tests (); diagnostic_format_sarif_cc_tests (); edit_context_cc_tests (); diff --git a/gcc/selftest.h b/gcc/selftest.h index a0d247342427..7e1d94cd39ef 100644 --- a/gcc/selftest.h +++ b/gcc/selftest.h @@ -221,6 +221,7 @@ extern void bitmap_cc_tests (); extern void cgraph_cc_tests (); extern void convert_cc_tests (); extern void diagnostic_color_cc_tests (); +extern void diagnostic_format_html_cc_tests (); extern void diagnostic_format_json_cc_tests (); extern void diagnostic_format_sarif_cc_tests (); extern void diagnostic_path_cc_tests (); diff --git a/gcc/testsuite/gcc.dg/html-output/html-output.exp b/gcc/testsuite/gcc.dg/html-output/html-output.exp new file mode 100644 index 000000000000..1f977ca1f568 --- /dev/null +++ b/gcc/testsuite/gcc.dg/html-output/html-output.exp @@ -0,0 +1,31 @@ +# Copyright (C) 2012-2024 Free Software Foundation, Inc. +# +# This file is part of GCC. +# +# GCC is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GCC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING3. If not see +# <http://www.gnu.org/licenses/>. + +# GCC testsuite that uses the `dg.exp' driver. + +# Load support procs. +load_lib gcc-dg.exp + +# Initialize `dg'. +dg-init + +# Main loop. +dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.c]] "" "" + +# All done. +dg-finish diff --git a/gcc/testsuite/gcc.dg/html-output/missing-semicolon.c b/gcc/testsuite/gcc.dg/html-output/missing-semicolon.c new file mode 100644 index 000000000000..c211f4f76e71 --- /dev/null +++ b/gcc/testsuite/gcc.dg/html-output/missing-semicolon.c @@ -0,0 +1,13 @@ +/* { dg-do compile } */ +/* { dg-options "-fdiagnostics-add-output=experimental-html" } */ + +/* Verify that basics of HTML output work. */ + +int missing_semicolon (void) +{ + return 42 /* { dg-error "expected ';' before '.' token" } */ +} + +/* Use a Python script to verify various properties about the generated + .html file: + { dg-final { run-html-pytest missing-semicolon.c "missing-semicolon.py" } } */ diff --git a/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py b/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py new file mode 100644 index 000000000000..8687168e8841 --- /dev/null +++ b/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py @@ -0,0 +1,84 @@ +# Verify that basics of HTML output work. +# +# For reference, we expect this textual output: +# +# PATH/missing-semicolon.c: In function ‘missing_semicolon’: +# PATH/missing-semicolon.c:8:12: error: expected ‘;’ before ‘}’ token +# 8 | return 42 /* { dg-error "expected ';' before '.' token" } */ +# | ^ +# | ; +# 9 | } +# | ~ + +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_basics(html_tree): + root = html_tree.getroot () + assert root.tag == make_tag('html') + + head = root.find('xhtml:head', ns) + assert head is not None + + title = head.find('xhtml:title', ns) + assert title.text == 'Title goes here' + + 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' + + message = diag.find('xhtml:span', ns) + assert message is not None + assert message.attrib['class'] == 'gcc-message' + assert message.text == "expected '" + assert message[0].tag == make_tag('span') + assert message[0].attrib['class'] == 'gcc-quoted-text' + assert message[0].text == ';' + assert message[0].tail == "' before '" + assert message[1].tag == make_tag('span') + assert message[1].attrib['class'] == 'gcc-quoted-text' + assert message[1].text == '}' + assert message[1].tail == "' token" + + pre = diag.find('xhtml:pre', ns) + assert pre is not None + assert pre.attrib['class'] == 'gcc-annotated-source' + +# For reference, here's the generated HTML: +""" +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>Title goes here</title> + </head> + <body> + <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> + </div> + </div> + </body> +</html> +""" diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-xhtml-1.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-xhtml-1.c deleted file mode 100644 index da069ff4789d..000000000000 --- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-xhtml-1.c +++ /dev/null @@ -1,19 +0,0 @@ -/* { dg-do compile } */ - -int missing_semicolon (void) -{ - return 42 -} - -/* Verify some properties of the generated HTML. */ - -/* { dg-begin-multiline-output "" } -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE html - PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml"> - <head> - { dg-end-multiline-output "" } */ - -/* { dg-excess-errors "" } */ diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp index 90c91621d0aa..26326cc2e5fd 100644 --- a/gcc/testsuite/gcc.dg/plugin/plugin.exp +++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp @@ -76,8 +76,6 @@ set plugin_test_list [list \ crash-test-ice-in-header-sarif-2.1.c \ crash-test-ice-in-header-sarif-2.2.c \ crash-test-write-though-null-sarif.c } \ - { diagnostic_plugin_xhtml_format.cc \ - diagnostic-test-xhtml-1.c } \ { diagnostic_group_plugin.cc \ diagnostic-group-test-1.c } \ { diagnostic_plugin_test_show_locus.cc \ diff --git a/gcc/testsuite/lib/gcc-dg.exp b/gcc/testsuite/lib/gcc-dg.exp index 6dd8fa3fce9c..312c4b8659a4 100644 --- a/gcc/testsuite/lib/gcc-dg.exp +++ b/gcc/testsuite/lib/gcc-dg.exp @@ -26,6 +26,7 @@ load_lib scanipa.exp load_lib scanwpaipa.exp load_lib scanlang.exp load_lib scansarif.exp +load_lib scanhtml.exp load_lib timeout.exp load_lib timeout-dg.exp load_lib prune.exp diff --git a/gcc/testsuite/lib/htmltest.py b/gcc/testsuite/lib/htmltest.py new file mode 100644 index 000000000000..b249ea691f19 --- /dev/null +++ b/gcc/testsuite/lib/htmltest.py @@ -0,0 +1,9 @@ +import os +import xml.etree.ElementTree as ET + +def html_tree_from_env(): + # return parsed HTML content as an ET from an HTML_PATH file + html_filename = os.environ['HTML_PATH'] + html_filename += '.html' + print('html_filename: %r' % html_filename) + return ET.parse(html_filename) diff --git a/gcc/testsuite/lib/scanhtml.exp b/gcc/testsuite/lib/scanhtml.exp new file mode 100644 index 000000000000..6d8f39813345 --- /dev/null +++ b/gcc/testsuite/lib/scanhtml.exp @@ -0,0 +1,90 @@ +# Copyright (C) 2000-2025 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING3. If not see +# <http://www.gnu.org/licenses/>. + +# Various utilities for scanning HTML output, used by gcc-dg.exp and +# g++-dg.exp. +# +# This is largely borrowed from scansarif.exp. + +proc html-pytest-format-line { args } { + global subdir + + set testcase [lindex $args 0] + set pytest_script [lindex $args 1] + set output_line [lindex $args 2] + + set index [string first "::" $output_line] + set test_output [string range $output_line [expr $index + 2] [string length $output_line]] + + return "$subdir/$testcase ${pytest_script}::${test_output}" +} + +# Call by dg-final to run a pytest Python script. +# We pass filename of a test via HTML_PATH environment variable. + +proc run-html-pytest { args } { + global srcdir subdir + # Extract the test file name from the arguments. + set testcase [lindex $args 0] + + verbose "Running HTML $testcase in $srcdir/$subdir" 2 + set testcase [remote_download host $testcase] + + set pytest_script [lindex $args 1] + if { ![check_effective_target_pytest3] } { + unsupported "$pytest_script pytest python3 is missing" + return + } + + setenv HTML_PATH $testcase + set libdir "${srcdir}/lib" + + # Set/prepend libdir to PYTHONPATH + if [info exists ::env(PYTHONPATH)] { + set old_PYTHONPATH $::env(PYTHONPATH) + setenv PYTHONPATH "${libdir}:${old_PYTHONPATH}" + } else { + setenv PYTHONPATH "${libdir}" + } + + verbose "PYTHONPATH=[getenv PYTHONPATH]" 2 + + spawn -noecho python3 -m pytest --color=no -rap -s --tb=no $srcdir/$subdir/$pytest_script + + if [info exists old_PYTHONPATH] { + setenv PYTHONPATH ${old_PYTHONPATH} + } + + set prefix "\[^\r\n\]*" + expect { + -re "FAILED($prefix)\[^\r\n\]+\r\n" { + set output [html-pytest-format-line $testcase $pytest_script $expect_out(1,string)] + fail $output + exp_continue + } + -re "ERROR($prefix)\[^\r\n\]+\r\n" { + set output [html-pytest-format-line $testcase $pytest_script $expect_out(1,string)] + fail $output + exp_continue + } + -re "PASSED($prefix)\[^\r\n\]+\r\n" { + set output [html-pytest-format-line $testcase $pytest_script $expect_out(1,string)] + pass $output + exp_continue + } + } +} + diff --git a/libatomic/testsuite/lib/libatomic.exp b/libatomic/testsuite/lib/libatomic.exp index 4b52a6f74876..456493c3a797 100644 --- a/libatomic/testsuite/lib/libatomic.exp +++ b/libatomic/testsuite/lib/libatomic.exp @@ -37,6 +37,7 @@ load_gcc_lib scandump.exp load_gcc_lib scanlang.exp load_gcc_lib scanrtl.exp load_gcc_lib scansarif.exp +load_gcc_lib scanhtml.exp load_gcc_lib scantree.exp load_gcc_lib scanltrans.exp load_gcc_lib scanipa.exp diff --git a/libgomp/testsuite/lib/libgomp.exp b/libgomp/testsuite/lib/libgomp.exp index 54f2f708b1ba..fd475ac3fe63 100644 --- a/libgomp/testsuite/lib/libgomp.exp +++ b/libgomp/testsuite/lib/libgomp.exp @@ -30,6 +30,7 @@ load_gcc_lib scandump.exp load_gcc_lib scanlang.exp load_gcc_lib scanrtl.exp load_gcc_lib scansarif.exp +load_gcc_lib scanhtml.exp load_gcc_lib scantree.exp load_gcc_lib scanltrans.exp load_gcc_lib scanoffload.exp diff --git a/libitm/testsuite/lib/libitm.exp b/libitm/testsuite/lib/libitm.exp index ac390d6d0dd0..0b3301537cee 100644 --- a/libitm/testsuite/lib/libitm.exp +++ b/libitm/testsuite/lib/libitm.exp @@ -43,6 +43,7 @@ load_gcc_lib scandump.exp load_gcc_lib scanlang.exp load_gcc_lib scanrtl.exp load_gcc_lib scansarif.exp +load_gcc_lib scanhtml.exp load_gcc_lib scantree.exp load_gcc_lib scanltrans.exp load_gcc_lib scanipa.exp diff --git a/libphobos/testsuite/lib/libphobos-dg.exp b/libphobos/testsuite/lib/libphobos-dg.exp index 2cac87feffd4..f2c38a2cbd68 100644 --- a/libphobos/testsuite/lib/libphobos-dg.exp +++ b/libphobos/testsuite/lib/libphobos-dg.exp @@ -24,6 +24,7 @@ load_gcc_lib scanasm.exp load_gcc_lib scanlang.exp load_gcc_lib scanrtl.exp load_gcc_lib scansarif.exp +load_gcc_lib scanhtml.exp load_gcc_lib scantree.exp load_gcc_lib scanipa.exp load_gcc_lib torture-options.exp diff --git a/libvtv/testsuite/lib/libvtv-dg.exp b/libvtv/testsuite/lib/libvtv-dg.exp index 454d916e556c..2a45f5cc4dbf 100644 --- a/libvtv/testsuite/lib/libvtv-dg.exp +++ b/libvtv/testsuite/lib/libvtv-dg.exp @@ -13,6 +13,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. load_gcc_lib scansarif.exp +load_gcc_lib scanhtml.exp proc libvtv-dg-test { prog do_what extra_tool_flags } { return [gcc-dg-test-1 libvtv_target_compile $prog $do_what $extra_tool_flags] -- 2.26.3