aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Malcolm <dmalcolm@redhat.com>2025-05-08 20:41:35 -0400
committerDavid Malcolm <dmalcolm@redhat.com>2025-05-08 20:41:35 -0400
commit1a2c62212bd912f5c8130e992ce282b542599f98 (patch)
tree09684c2d1f8d051caf2af9dda4cdd88618dbcd50
parent8dba9c7ec97ef6e5e891c77a0f0d536860172beb (diff)
downloadgcc-1a2c62212bd912f5c8130e992ce282b542599f98.zip
gcc-1a2c62212bd912f5c8130e992ce282b542599f98.tar.gz
gcc-1a2c62212bd912f5c8130e992ce282b542599f98.tar.bz2
diagnostics: convert HTML output test plugin to 'experimental-html' sink [PR116792]
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. 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. Signed-off-by: David Malcolm <dmalcolm@redhat.com>
-rw-r--r--gcc/Makefile.in1
-rw-r--r--gcc/diagnostic-format-html.cc (renamed from gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.cc)323
-rw-r--r--gcc/diagnostic-format-html.h37
-rw-r--r--gcc/doc/invoke.texi18
-rw-r--r--gcc/opts-diagnostic.cc60
-rw-r--r--gcc/selftest-run-tests.cc1
-rw-r--r--gcc/selftest.h1
-rw-r--r--gcc/testsuite/gcc.dg/html-output/html-output.exp31
-rw-r--r--gcc/testsuite/gcc.dg/html-output/missing-semicolon.c13
-rw-r--r--gcc/testsuite/gcc.dg/html-output/missing-semicolon.py84
-rw-r--r--gcc/testsuite/gcc.dg/plugin/diagnostic-test-xhtml-1.c19
-rw-r--r--gcc/testsuite/gcc.dg/plugin/plugin.exp2
-rw-r--r--gcc/testsuite/lib/gcc-dg.exp1
-rw-r--r--gcc/testsuite/lib/htmltest.py9
-rw-r--r--gcc/testsuite/lib/scanhtml.exp90
-rw-r--r--libatomic/testsuite/lib/libatomic.exp1
-rw-r--r--libgomp/testsuite/lib/libgomp.exp1
-rw-r--r--libitm/testsuite/lib/libitm.exp1
-rw-r--r--libphobos/testsuite/lib/libphobos-dg.exp1
-rw-r--r--libvtv/testsuite/lib/libvtv-dg.exp1
20 files changed, 479 insertions, 216 deletions
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 55b4cd7..e3af923 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
index 24c6f8c..2d642df 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 <dmalcolm@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 0000000..ff5edca
--- /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 <dmalcolm@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 b1964b3..f31d504 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 1eec010..34c3906 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 3c12e8a..959e5d0 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 a0d2473..7e1d94c 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 0000000..1f977ca
--- /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 0000000..c211f4f
--- /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 0000000..8687168
--- /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 &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>
+ </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 da069ff..0000000
--- 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 96e76d2..a84fbae 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 6dd8fa3..312c4b8 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 0000000..b249ea6
--- /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 0000000..6d8f398
--- /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 4b52a6f..456493c 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 54f2f708..fd475ac 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 ac390d6d..0b33015 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 2cac87f..f2c38a2 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 454d916..2a45f5c 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]