diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-xhtml-1.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-xhtml-1.c
new file mode 100644
index 00000000000..da069ff4789
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-xhtml-1.c
@@ -0,0 +1,19 @@
+/* { dg-do compile } */
+
+int missing_semicolon (void)
+{
+ return 42
+}
+
+/* Verify some properties of the generated HTML. */
+
+/* { dg-begin-multiline-output "" }
+
+
+
+
+ { dg-end-multiline-output "" } */
+
+/* { dg-excess-errors "" } */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.c
new file mode 100644
index 00000000000..192288aff1b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.c
@@ -0,0 +1,866 @@
+/* Verify that we can write a non-trivial diagnostic output format
+ as a plugin (XHTML).
+ Copyright (C) 2018-2024 Free Software Foundation, Inc.
+ Contributed by David Malcolm .
+
+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
+. */
+
+
+#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 "ordered-hash-map.h"
+#include "sbitmap.h"
+#include "make-unique.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 {
+
+/* Disable warnings about quoting issues in the pp_xxx calls below
+ that (intentionally) don't follow GCC diagnostic conventions. */
+#if __GNUC__ >= 10
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat-diag"
+#endif
+
+static void write_escaped_text (const char *text);
+
+struct node
+{
+ virtual ~node () {}
+ virtual void write_as_xml (pretty_printer *pp,
+ int depth, bool indent) const = 0;
+ void dump (FILE *out) const;
+ void DEBUG_FUNCTION dump () const { dump (stderr); }
+};
+
+struct text : public node
+{
+ text (label_text str)
+ : m_str (std::move (str))
+ {}
+
+ void write_as_xml (pretty_printer *pp,
+ int depth, bool indent) const final override;
+
+ label_text m_str;
+};
+
+struct node_with_children : public node
+{
+ void add_child (std::unique_ptr node);
+ void add_text (label_text str);
+
+ std::vector> m_children;
+};
+
+struct document : public node_with_children
+{
+ void write_as_xml (pretty_printer *pp,
+ int depth, bool indent) const final override;
+};
+
+struct element : public node_with_children
+{
+ element (const char *kind, bool preserve_whitespace)
+ : m_kind (kind),
+ m_preserve_whitespace (preserve_whitespace)
+ {}
+
+ void write_as_xml (pretty_printer *pp,
+ int depth, bool indent) const final override;
+
+ void set_attr (const char *name, label_text value);
+
+ const char *m_kind;
+ bool m_preserve_whitespace;
+ std::map m_attributes;
+};
+
+/* Implementation. */
+
+static void
+write_escaped_text (pretty_printer *pp, const char *text)
+{
+ gcc_assert (text);
+
+ for (const char *p = text; *p; ++p)
+ {
+ char ch = *p;
+ switch (ch)
+ {
+ default:
+ pp_character (pp, ch);
+ break;
+ case '\'':
+ pp_string (pp, "'");
+ break;
+ case '"':
+ pp_string (pp, """);
+ break;
+ case '&':
+ pp_string (pp, "&");
+ break;
+ case '<':
+ pp_string (pp, "<");
+ break;
+ case '>':
+ pp_string (pp, ">");
+ break;
+ }
+ }
+}
+
+/* struct node. */
+
+void
+node::dump (FILE *out) const
+{
+ pretty_printer pp;
+ pp.set_output_stream (out);
+ write_as_xml (&pp, 0, true);
+ pp_flush (&pp);
+}
+
+/* struct text : public node. */
+
+void
+text::write_as_xml (pretty_printer *pp, int /*depth*/, bool /*indent*/) const
+{
+ write_escaped_text (pp, m_str.get ());
+}
+
+/* struct node_with_children : public node. */
+
+void
+node_with_children::add_child (std::unique_ptr node)
+{
+ gcc_assert (node.get ());
+ m_children.push_back (std::move (node));
+}
+
+void
+node_with_children::add_text (label_text str)
+{
+ gcc_assert (str.get ());
+ add_child (::make_unique (std::move (str)));
+}
+
+
+/* struct document : public node_with_children. */
+
+void
+document::write_as_xml (pretty_printer *pp, int depth, bool indent) const
+{
+ pp_string (pp, "\n");
+ pp_string (pp, "");
+ for (auto &iter : m_children)
+ iter->write_as_xml (pp, depth, indent);
+}
+
+/* struct element : public node_with_children. */
+
+void
+element::write_as_xml (pretty_printer *pp, int depth, bool indent) const
+{
+ if (indent)
+ {
+ pp_newline (pp);
+ for (int i = 0; i < depth; ++i)
+ pp_string (pp, " ");
+ }
+
+ if (m_preserve_whitespace)
+ indent = false;
+
+ pp_printf (pp, "<%s", m_kind);
+ for (auto &attr : m_attributes)
+ {
+ pp_printf (pp, " %s=\"", attr.first);
+ write_escaped_text (pp, attr.second.get ());
+ pp_string (pp, "\"");
+ }
+ if (m_children.empty ())
+ pp_string (pp, " />");
+ else
+ {
+ pp_string (pp, ">");
+ for (auto &child : m_children)
+ child->write_as_xml (pp, depth + 1, indent);
+ if (indent)
+ {
+ pp_newline (pp);
+ for (int i = 0; i < depth; ++i)
+ pp_string (pp, " ");
+ }
+ pp_printf (pp, "%s>", m_kind);
+ }
+}
+
+void
+element::set_attr (const char *name, label_text value)
+{
+ m_attributes[name] = std::move (value);
+}
+
+#if __GNUC__ >= 10
+# pragma GCC diagnostic pop
+#endif
+
+} // namespace xml
+
+/* A class for managing XHTML output of diagnostics.
+
+ Implemented:
+ - message text
+
+ Known limitations/missing functionality:
+ - title for page
+ - file/line/column
+ - error vs warning
+ - CWEs
+ - rules
+ - fix-it hints
+ - paths
+*/
+
+class xhtml_builder
+{
+public:
+ xhtml_builder (diagnostic_context &context,
+ const line_maps *line_maps);
+
+ void on_report_diagnostic (diagnostic_context &context,
+ const diagnostic_info &diagnostic,
+ diagnostic_t orig_diag_kind);
+ void emit_diagram (diagnostic_context &context,
+ const diagnostic_diagram &diagram);
+ void end_group ();
+
+ std::unique_ptr take_current_diagnostic ()
+ {
+ return std::move (m_cur_diagnostic_element);
+ }
+
+ void flush_to_file (FILE *outf);
+
+ const xml::document &get_document () const { return *m_document; }
+
+private:
+ std::unique_ptr
+ make_element_for_diagnostic (diagnostic_context &context,
+ const diagnostic_info &diagnostic,
+ diagnostic_t orig_diag_kind);
+
+ diagnostic_context &m_context;
+ const line_maps *m_line_maps;
+
+ std::unique_ptr m_document;
+ xml::element *m_diagnostics_element;
+ std::unique_ptr m_cur_diagnostic_element;
+};
+
+static std::unique_ptr
+make_div (label_text class_)
+{
+ auto div = ::make_unique ("div", false);
+ div->set_attr ("class", std::move (class_));
+ return div;
+}
+
+static std::unique_ptr
+make_span (label_text class_)
+{
+ auto span = ::make_unique ("span", true);
+ span->set_attr ("class", std::move (class_));
+ return span;
+}
+
+/* class xhtml_builder. */
+
+/* xhtml_builder's ctor. */
+
+xhtml_builder::xhtml_builder (diagnostic_context &context,
+ const line_maps *line_maps)
+: m_context (context),
+ m_line_maps (line_maps)
+{
+ gcc_assert (m_line_maps);
+
+ m_document = ::make_unique ();
+ {
+ auto html_element = ::make_unique ("html", false);
+ html_element->set_attr
+ ("xmlns",
+ label_text::borrow ("http://www.w3.org/1999/xhtml"));
+ {
+ {
+ auto head_element = ::make_unique ("head", false);
+ {
+ auto title_element = ::make_unique ("title", true);
+ label_text title (label_text::borrow ("Title goes here")); // TODO
+ title_element->add_text (std::move (title));
+ head_element->add_child (std::move (title_element));
+ }
+ html_element->add_child (std::move (head_element));
+
+ auto body_element = ::make_unique ("body", false);
+ {
+ auto diagnostics_element
+ = make_div (label_text::borrow ("gcc-diagnostic-list"));
+ m_diagnostics_element = diagnostics_element.get ();
+ body_element->add_child (std::move (diagnostics_element));
+ }
+ html_element->add_child (std::move (body_element));
+ }
+ }
+ m_document->add_child (std::move (html_element));
+ }
+}
+
+/* Implementation of "on_report_diagnostic" for XHTML output. */
+
+void
+xhtml_builder::on_report_diagnostic (diagnostic_context &context,
+ const diagnostic_info &diagnostic,
+ diagnostic_t orig_diag_kind)
+{
+ // TODO: handle (diagnostic.kind == DK_ICE || diagnostic.kind == DK_ICE_NOBT)
+
+ auto diag_element
+ = make_element_for_diagnostic (context, diagnostic, orig_diag_kind);
+ if (m_cur_diagnostic_element)
+ /* Nested diagnostic. */
+ m_cur_diagnostic_element->add_child (std::move (diag_element));
+ else
+ /* Top-level diagnostic. */
+ m_cur_diagnostic_element = std::move (diag_element);
+}
+
+std::unique_ptr
+xhtml_builder::make_element_for_diagnostic (diagnostic_context &context,
+ const diagnostic_info &diagnostic,
+ diagnostic_t orig_diag_kind)
+{
+ class xhtml_token_printer : public token_printer
+ {
+ public:
+ xhtml_token_printer (xhtml_builder &builder,
+ xml::element &parent_element)
+ : m_builder (builder)
+ {
+ m_open_elements.push_back (&parent_element);
+ }
+ void print_tokens (pretty_printer */*pp*/,
+ const pp_token_list &tokens) final override
+ {
+ /* Implement print_tokens by adding child elements to
+ m_parent_element. */
+ for (auto iter = tokens.m_first; iter; iter = iter->m_next)
+ switch (iter->m_kind)
+ {
+ default:
+ gcc_unreachable ();
+
+ case pp_token::kind::text:
+ {
+ pp_token_text *sub = as_a (iter);
+ /* The value might be in the obstack, so we may need to
+ copy it. */
+ insertion_element ().add_text
+ (label_text::take (xstrdup (sub->m_value.get ())));
+ }
+ break;
+
+ case pp_token::kind::begin_color:
+ case pp_token::kind::end_color:
+ /* These are no-ops. */
+ break;
+
+ case pp_token::kind::begin_quote:
+ {
+ insertion_element ().add_text (label_text::borrow (open_quote));
+ push_element (make_span (label_text::borrow ("gcc-quoted-text")));
+ }
+ break;
+ case pp_token::kind::end_quote:
+ {
+ pop_element ();
+ insertion_element ().add_text (label_text::borrow (close_quote));
+ }
+ break;
+
+ case pp_token::kind::begin_url:
+ {
+ pp_token_begin_url *sub = as_a (iter);
+ auto anchor = ::make_unique ("a", true);
+ anchor->set_attr ("href", std::move (sub->m_value));
+ push_element (std::move (anchor));
+ }
+ break;
+ case pp_token::kind::end_url:
+ pop_element ();
+ break;
+ }
+ }
+
+ private:
+ xml::element &insertion_element () const
+ {
+ return *m_open_elements.back ();
+ }
+ void push_element (std::unique_ptr new_element)
+ {
+ xml::element ¤t_top = insertion_element ();
+ m_open_elements.push_back (new_element.get ());
+ current_top.add_child (std::move (new_element));
+ }
+ void pop_element ()
+ {
+ m_open_elements.pop_back ();
+ }
+
+ xhtml_builder &m_builder;
+ /* We maintain a stack of currently "open" elements.
+ Children are added to the topmost open element. */
+ std::vector m_open_elements;
+ };
+
+ auto diag_element = make_div (label_text::borrow ("gcc-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 ());
+ context.m_printer->set_token_printer (&tok_printer);
+ pp_output_formatted_text (context.m_printer, context.get_urlifier ());
+ context.m_printer->set_token_printer (nullptr);
+ pp_clear_output_area (context.m_printer);
+ diag_element->add_child (std::move (message_span));
+
+ if (diagnostic.metadata)
+ {
+ int cwe = diagnostic.metadata->get_cwe ();
+ if (cwe)
+ {
+ diag_element->add_text (label_text::borrow (" "));
+ auto cwe_span = make_span (label_text::borrow ("gcc-cwe-metadata"));
+ cwe_span->add_text (label_text::borrow ("["));
+ {
+ auto anchor = ::make_unique ("a", true);
+ anchor->set_attr ("href", label_text::take (get_cwe_url (cwe)));
+ pretty_printer pp;
+ pp_printf (&pp, "CWE-%i", cwe);
+ anchor->add_text
+ (label_text::take (xstrdup (pp_formatted_text (&pp))));
+ cwe_span->add_child (std::move (anchor));
+ }
+ cwe_span->add_text (label_text::borrow ("]"));
+ diag_element->add_child (std::move (cwe_span));
+ }
+ }
+
+ // TODO: show any rules
+
+ label_text option_text = label_text::take
+ (m_context.make_option_name (diagnostic.option_id,
+ orig_diag_kind, diagnostic.kind));
+ if (option_text.get ())
+ {
+ label_text option_url = label_text::take
+ (m_context.make_option_url (diagnostic.option_id));
+
+ diag_element->add_text (label_text::borrow (" "));
+ auto option_span = make_span (label_text::borrow ("gcc-option"));
+ option_span->add_text (label_text::borrow ("["));
+ {
+ if (option_url.get ())
+ {
+ auto anchor = ::make_unique ("a", true);
+ anchor->set_attr ("href", std::move (option_url));
+ anchor->add_text (std::move (option_text));
+ option_span->add_child (std::move (anchor));
+ }
+ else
+ option_span->add_text (std::move (option_text));
+ option_span->add_text (label_text::borrow ("]"));
+ }
+ diag_element->add_child (std::move (option_span));
+ }
+
+ {
+ auto pre = ::make_unique ("pre", true);
+ pre->set_attr ("class", label_text::borrow ("gcc-annotated-source"));
+ // TODO: ideally we'd like to capture elements within the following:
+ diagnostic_show_locus (&context, diagnostic.richloc, diagnostic.kind);
+ pre->add_text
+ (label_text::take (xstrdup (pp_formatted_text (context.m_printer))));
+ pp_clear_output_area (context.m_printer);
+ diag_element->add_child (std::move (pre));
+ }
+
+ return diag_element;
+}
+
+/* Implementation of diagnostic_context::m_diagrams.m_emission_cb
+ for XHTML output. */
+
+void
+xhtml_builder::emit_diagram (diagnostic_context &/*context*/,
+ const diagnostic_diagram &/*diagram*/)
+{
+ /* We must be within the emission of a top-level diagnostic. */
+ gcc_assert (m_cur_diagnostic_element);
+
+ // TODO
+}
+
+/* Implementation of "end_group_cb" for XHTML output. */
+
+void
+xhtml_builder::end_group ()
+{
+ if (m_cur_diagnostic_element)
+ m_diagnostics_element->add_child (std::move (m_cur_diagnostic_element));
+}
+
+/* Create a top-level object, and add it to all the results
+ (and other entities) we've seen so far.
+
+ Flush it all to OUTF. */
+
+void
+xhtml_builder::flush_to_file (FILE *outf)
+{
+ auto top = m_document.get ();
+ top->dump (outf);
+ fprintf (outf, "\n");
+}
+
+/* Callback for diagnostic_context::ice_handler_cb for when an ICE
+ occurs. */
+
+static void
+xhtml_ice_handler (diagnostic_context *context)
+{
+ /* Attempt to ensure that a .xhtml file is written out. */
+ diagnostic_finish (context);
+
+ /* Print a header for the remaining output to stderr, and
+ return, attempting to print the usual ICE messages to
+ stderr. Hopefully this will be helpful to the user in
+ indicating what's gone wrong (also for DejaGnu, for pruning
+ those messages). */
+ fnotice (stderr, "Internal compiler error:\n");
+}
+
+class xhtml_output_format : public diagnostic_output_format
+{
+public:
+ ~xhtml_output_format ()
+ {
+ /* Any diagnostics should have been handled by now.
+ If not, then something's gone wrong with diagnostic
+ groupings. */
+ std::unique_ptr pending_diag
+ = m_builder.take_current_diagnostic ();
+ gcc_assert (!pending_diag);
+ }
+
+ void on_begin_group () final override
+ {
+ /* No-op, */
+ }
+ void on_end_group () final override
+ {
+ m_builder.end_group ();
+ }
+ void
+ on_report_diagnostic (const diagnostic_info &diagnostic,
+ diagnostic_t orig_diag_kind) final override
+ {
+ m_builder.on_report_diagnostic (m_context, diagnostic, orig_diag_kind);
+ }
+ void on_diagram (const diagnostic_diagram &diagram) final override
+ {
+ m_builder.emit_diagram (m_context, diagram);
+ }
+ void after_diagnostic (const diagnostic_info &)
+ {
+ /* No-op, but perhaps could show paths here. */
+ }
+
+ const xml::document &get_document () const
+ {
+ return m_builder.get_document ();
+ }
+
+protected:
+ xhtml_output_format (diagnostic_context &context,
+ const line_maps *line_maps)
+ : diagnostic_output_format (context),
+ m_builder (context, line_maps)
+ {}
+
+ xhtml_builder m_builder;
+};
+
+class xhtml_stream_output_format : public xhtml_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 ()
+ {
+ m_builder.flush_to_file (m_stream);
+ }
+ bool machine_readable_stderr_p () const final 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);
+ }
+ bool machine_readable_stderr_p () const final override
+ {
+ return false;
+ }
+
+private:
+ char *m_base_file_name;
+};
+
+/* Populate CONTEXT in preparation for XHTML output (either to stderr, or
+ to a file). */
+
+static void
+diagnostic_output_format_init_xhtml (diagnostic_context &context,
+ std::unique_ptr fmt)
+{
+ /* Override callbacks. */
+ context.set_ice_handler_callback (xhtml_ice_handler);
+
+ /* Don't colorize the text. */
+ pp_show_color (context.m_printer) = false;
+ context.set_show_highlight_colors (false);
+
+ context.set_output_format (fmt.release ());
+}
+
+/* Populate CONTEXT in preparation for XHTML output to stderr. */
+
+void
+diagnostic_output_format_init_xhtml_stderr (diagnostic_context &context,
+ const line_maps *line_maps)
+{
+ gcc_assert (line_maps);
+ auto format = ::make_unique (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 = ::make_unique (context,
+ line_maps,
+ base_file_name);
+ diagnostic_output_format_init_xhtml (context, std::move (format));
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* A subclass of xhtml_output_format for writing selftests.
+ The JSON output is cached internally, rather than written
+ out to a file. */
+
+class test_xhtml_diagnostic_context : public test_diagnostic_context
+{
+public:
+ test_xhtml_diagnostic_context ()
+ {
+ auto format = ::make_unique (*this,
+ line_table);
+ m_format = format.get (); // borrowed
+ diagnostic_output_format_init_xhtml (*this, std::move (format));
+ }
+
+ const xml::document &get_document () const
+ {
+ return m_format->get_document ();
+ }
+
+private:
+ class xhtml_buffered_output_format : public xhtml_output_format
+ {
+ public:
+ xhtml_buffered_output_format (diagnostic_context &context,
+ const line_maps *line_maps)
+ : xhtml_output_format (context, line_maps)
+ {
+ }
+ bool machine_readable_stderr_p () const final override
+ {
+ return true;
+ }
+ };
+
+ xhtml_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. */
+
+static void
+test_simple_log ()
+{
+ test_xhtml_diagnostic_context dc;
+
+ rich_location richloc (line_table, UNKNOWN_LOCATION);
+ dc.report (DK_ERROR, richloc, nullptr, 0, "this is a test: %i", 42);
+
+ const xml::document &doc = dc.get_document ();
+
+ pretty_printer pp;
+ doc.write_as_xml (&pp, 0, true);
+ ASSERT_STREQ
+ (pp_formatted_text (&pp),
+ ("\n"
+ "\n"
+ "\n"
+ " \n"
+ " Title goes here\n"
+ " \n"
+ " \n"
+ " \n"
+ "
\n"
+ "
this is a test: 42\n"
+ "
\n"
+ "
\n"
+ "
\n"
+ " \n"
+ ""));
+}
+
+/* Run all of the selftests within this file. */
+
+static void
+xhtml_format_selftests ()
+{
+ test_simple_log ();
+}
+
+} // 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 (new xhtml_stream_output_format (*global_dc,
+ line_table,
+ stderr));
+
+#if CHECKING_P
+ selftest::xhtml_format_selftests ();
+#endif
+
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index 2c2d919eddf..8ac1bfa7f77 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -74,6 +74,8 @@ set plugin_test_list [list \
crash-test-write-though-null-stderr.c \
crash-test-ice-sarif.c \
crash-test-write-though-null-sarif.c } \
+ { diagnostic_plugin_xhtml_format.c \
+ diagnostic-test-xhtml-1.c } \
{ diagnostic_group_plugin.c \
diagnostic-group-test-1.c } \
{ diagnostic_plugin_test_show_locus.c \