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>
This commit is contained in:
David Malcolm 2025-05-08 20:41:35 -04:00
parent 8dba9c7ec9
commit 1a2c62212b
20 changed files with 478 additions and 215 deletions

View File

@ -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 \

View File

@ -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)
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))
{
gcc_assert (m_output_file.get_open_file ());
gcc_assert (m_output_file.get_filename ());
}
~xhtml_stream_output_format ()
~html_file_output_format ()
{
m_builder.flush_to_file (m_stream);
m_builder.flush_to_file (m_output_file.get_open_file ());
}
bool machine_readable_stderr_p () const final override
void dump (FILE *out, int indent) const override
{
return m_stream == stderr;
}
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 ()
{
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);
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 ();
}
context.set_output_format (std::move (fmt));
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 stderr. */
void
diagnostic_output_format_init_xhtml_stderr (diagnostic_context &context,
const line_maps *line_maps)
std::unique_ptr<diagnostic_output_format>
make_html_sink (diagnostic_context &context,
const line_maps &line_maps,
diagnostic_output_file output_file)
{
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));
}
/* 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));
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;
}

View File

@ -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 */

View File

@ -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};

View File

@ -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

View File

@ -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 ();

View File

@ -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 ();

View File

@ -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

View File

@ -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" } } */

View File

@ -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>
"""

View File

@ -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 "" } */

View File

@ -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 \

View File

@ -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

View File

@ -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)

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]