diff mbox series

[pushed:,r15-4760] diagnostics: support multiple output formats simultaneously [PR116613]

Message ID 20241029231952.2142213-1-dmalcolm@redhat.com
State New
Headers show
Series [pushed:,r15-4760] diagnostics: support multiple output formats simultaneously [PR116613] | expand

Commit Message

David Malcolm Oct. 29, 2024, 11:19 p.m. UTC
This patch generalizes diagnostic_context so that rather than having
a single output format, it has a vector of zero or more.

It adds new two options:
 -fdiagnostics-add-output=DIAGNOSTICS-OUTPUT-SPEC
 -fdiagnostics-set-output=DIAGNOSTICS-OUTPUT-SPEC
which both take a new configuration syntax of the form SCHEME ("text" or
"sarif"), optionally followed by ":" and one or more KEY=VALUE pairs,
in this form:

  <SCHEME>
  <SCHEME>:<KEY>=<VALUE>
  <SCHEME>:<KEY>=<VALUE>,<KEY2>=<VALUE2>
  ...etc

where each SCHEME supports some set of keys.  For example, it's now
possible to use:

  -fdiagnostics-add-output=sarif:version=2.1,file=foo.2.1.sarif \
  -fdiagnostics-add-output=sarif:version=2.2-prerelease,file=foo.2.2.sarif

to add a pair of outputs, each writing to a different file, using
versions 2.1 and 2.2 of the SARIF standard respectively, whilst also
emitting the classic text form of the diagnostics to stderr.

I hope the new syntax gives us room to potentially add new kinds of
output sink in the future (e.g. RPC notifications), and to add new
key/value pairs as needed by the different sinks.

Implementation-wise, the diagnostic_context's m_printer which previously
was used directly by the single output format now becomes a "reference
printer", created by the client (such as the frontend), with defaults
modified by command-line options.  Each of the multiple output sinks has
its own pretty_printer instance, created by cloning the context's
reference printer.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r15-4760-g0b73e9382ab51c.

gcc/ChangeLog:
	PR other/116613
	* Makefile.in (OBJS-libcommon-target): Add opts-diagnostic.o.
	* common.opt (fdiagnostics-add-output=): New.
	(fdiagnostics-set-output=): New.
	(diagnostics_output_format): Drop sarif-file-2.2-prerelease from
	enum.
	* common.opt.urls: Regenerate.
	* diagnostic-buffer.h (diagnostic_buffer::~diagnostic_buffer): New.
	(diagnostic_buffer::ensure_per_format_buffer): Rename to...
	(diagnostic_buffer::ensure_per_format_buffers): ...this.
	(diagnostic_buffer::m_per_format_buffer): Replace with...
	(diagnostic_buffer::m_per_format_buffers): ...this, updating type.
	* diagnostic-format-json.cc (json_output_format::update_printer):
	New.
	(json_output_format::follows_reference_printer_p): New.
	(diagnostic_output_format_init_json): Drop redundant call to
	set_path_format, as this is not a text output format.
	* diagnostic-format-sarif.cc: Include "diagnostic-format-text.h".
	(sarif_builder::set_printer): New.
	(sarif_builder::sarif_builder): Add "printer" param and use it for
	m_printer.
	(sarif_builder::make_location_object::escape_nonascii_renderer::render):
	Rather than using dc.m_printer, create a
	diagnostic_text_output_format instance and use its printer.
	(sarif_output_format::follows_reference_printer_p): New.
	(sarif_output_format::update_printer): New.
	(sarif_output_format::sarif_output_format): Pass in correct
	printer to m_builder's ctor.
	(diagnostic_output_format_init_sarif): Drop redundant call to
	set_path_format, as this is not a text output format.  Replace
	calls to pp_show_color and set_token_printer with call to
	update_printer.  Drop redundant call to set_show_highlight_colors,
	as this printer does not show colors.
	(diagnostic_output_format_init_sarif_file): Split out file opening
	into...
	(diagnostic_output_format_open_sarif_file): ...this new function.
	(make_sarif_sink): New.
	(selftest::test_make_location_object): Provide a pp for the
	builder.
	* diagnostic-format-sarif.h
	(diagnostic_output_format_open_sarif_file): New decl.
	(make_sarif_sink): New decl.
	* diagnostic-format-text.cc (diagnostic_text_output_format::dump):
	Dump sm_follows_reference_printer.
	(diagnostic_text_output_format::on_report_verbatim): New.
	(diagnostic_text_output_format::follows_reference_printer_p): New.
	(diagnostic_text_output_format::update_printer): New.
	* diagnostic-format-text.h
	(diagnostic_text_output_format::diagnostic_text_output_format):
	Add optional "follows_reference_printer" param.
	(diagnostic_text_output_format::on_report_verbatim): New decl.
	(diagnostic_text_output_format::after_diagnostic): Drop "final".
	(diagnostic_text_output_format::follows_reference_printer_p): New
	decl.
	(class diagnostic_text_output_format): Convert private members to
	protected.
	(diagnostic_text_output_format::m_follows_reference_printer): New
	field.
	* diagnostic-format.h
	(diagnostic_output_format::on_report_verbatim): New vfunc.
	(diagnostic_output_format::follows_reference_printer_p): New vfunc.
	(diagnostic_output_format::update_printer): New vfunc.
	(diagnostic_output_format::get_printer): Use m_printer rather than
	a printer from m_context.
	(diagnostic_output_format::diagnostic_output_format): Initialize
	m_printer by cloning the context's printer.
	(diagnostic_output_format::m_printer): New field.
	* diagnostic-global-context.cc (verbatim): Reimplement in terms of
	global_dc->report_verbatim, moving existing implementation to
	diagnostic_text_output_format::on_report_verbatim.
	(fnotice): Support multiple output sinks by using a new
	global_dc->supports_fnotice_on_stderr_p.
	* diagnostic-output-file.h
	(diagnostic_output_file::diagnostic_output_file): New default ctor.
	(diagnostic_output_file::operator=): Implement move assignment.
	* diagnostic-path.cc (selftest::test_interprocedural_path_1): Pass
	false for new param of text_output's ctor.
	* diagnostic-show-locus.cc
	(selftest::test_layout_x_offset_display_utf8): Use reference
	printer.
	(selftest::test_layout_x_offset_display_tab): Likewise.
	(selftest::test_one_liner_fixit_remove): Likewise.
	* diagnostic.cc: Include "pretty-print-urlifier.h".
	(diagnostic_set_caret_max_width): Update for global_dc's m_printer
	becoming reference printer.
	(diagnostic_context::initialize): Update for m_printer becoming
	m_reference_printer.  Use ::make_unique to create it.  Update for
	m_output_format becoming m_output_sinks.
	(diagnostic_context::color_init): Update the reference printer,
	then update the printers for any output sinks that follow it.
	(diagnostic_context::urls_init): Likewise.
	(diagnostic_context::finish): Update comment.  Update for
	m_output_format becoming m_output_sinks.  Update for m_printer
	becoming m_reference_printer and use "delete" on it rather than
	XDELETE.
	(diagnostic_context::dump): Update for m_printer becoming
	reference printer, and for multiple output sinks.
	(diagnostic_context::set_output_format): Reimplement for
	supporting multiple output sinks.
	(diagnostic_context::get_output_format): Likewise.
	(diagnostic_context::add_sink): New.
	(diagnostic_context::supports_fnotice_on_stderr_p): New.
	(diagnostic_context::set_pretty_printer): New.
	(diagnostic_context::refresh_output_sinks): New.
	(diagnostic_context::set_format_decoder): New.
	(diagnostic_context::set_show_highlight_colors): New.
	(diagnostic_context::set_prefixing_rule): New.
	(diagnostic_context::report_diagnostic): Update to support
	multiple output sinks.
	(diagnostic_context::report_verbatim): New.
	(diagnostic_context::emit_diagram): Update to support multiple
	output sinks.
	(diagnostic_context::error_recursion): Update to use
	m_reference_printer.
	(fancy_abort): Likewise.
	(diagnostic_context::end_group): Update to support multiple
	output sinks.
	(diagnostic_output_format::dump): Implement.
	(diagnostic_output_format::on_report_verbatim): Likewise.
	(diagnostic_output_format_init): Drop
	DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE_2_2_PRERELEASE.
	(diagnostic_context::set_diagnostic_buffer): Reimplement to
	support multiple output sinks.
	(diagnostic_context::clear_diagnostic_buffer): Likewise.
	(diagnostic_context::flush_diagnostic_buffer): Likewise.
	(diagnostic_buffer::diagnostic_buffer): Initialize
	m_per_format_buffers.
	(diagnostic_buffer::~diagnostic_buffer): New dtor.
	(diagnostic_buffer::dump): Reimplement to support multiple output
	sinks.
	(diagnostic_buffer::empty_p): Likewise.
	(diagnostic_buffer::move_to): Likewise.
	(diagnostic_buffer::ensure_per_format_buffer): Likewise, renaming
	to...
	(diagnostic_buffer::ensure_per_format_buffers): ...this.
	* diagnostic.h
	(DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE_2_2_PRERELEASE): Delete.
	(class diagnostic_context): Add friend class diagnostic_buffer.
	(diagnostic_context::set_pretty_printer): New decl.
	(diagnostic_context::refresh_output_sinks): New decl.
	(diagnostic_context::report_verbatim): New decl.
	(diagnostic_context::get_output_format): Drop.
	(diagnostic_context::set_show_highlight_colors): Drop body.
	(diagnostic_context::set_format_decoder): New decl.
	(diagnostic_context::set_prefixing_rule): New decl.
	(diagnostic_context::clone_printer): Reimplement.
	(diagnostic_context::get_reference_printer): New accessor.
	(diagnostic_context::add_sink): New decl.
	(diagnostic_context::supports_fnotice_on_stderr_p): New decl.
	(diagnostic_context::m_printer): Replace with...
	(diagnostic_context::m_reference_printer): ...this, and make
	private.
	(diagnostic_context::m_output_format): Replace with...
	(diagnostic_context::m_output_sinks): ...this.
	(diagnostic_format_decoder): Delete.
	(diagnostic_prefixing_rule): Delete.
	(diagnostic_ready_p): Delete.
	* doc/invoke.texi: Document -fdiagnostics-add-output= and
	-fdiagnostics-set-output=.
	* gcc.cc: Include "opts-diagnostic.h".
	(driver_handle_option): Handle cases OPT_fdiagnostics_add_output_
	and OPT_fdiagnostics_set_output_.
	* opts-diagnostic.cc: New file.
	* opts-diagnostic.h (handle_OPT_fdiagnostics_add_output_): New decl.
	(handle_OPT_fdiagnostics_set_output_): New decl.
	* opts-global.cc (init_options_once): Update for global_dc's
	m_printer becoming reference printer.  Call
	global_dc->refresh_output_sinks.
	* opts.cc (common_handle_option): Replace use of
	diagnostic_prefixing_rule with dc->set_prefixing_rule.  Handle
	cases OPT_fdiagnostics_add_output_ and
	OPT_fdiagnostics_set_output_.  Update for m_printer becoming
	reference printer.
	* selftest-diagnostic.cc
	(selftest::test_diagnostic_context::test_diagnostic_context):
	Update for m_printer becoming reference printer.
	(test_diagnostic_context::test_show_locus): Likewise.
	* selftest-run-tests.cc (selftest::run_tests): Call
	selftest::opts_diagnostic_cc_tests.
	* selftest.h (selftest::opts_diagnostic_cc_tests): New decl.
	* simple-diagnostic-path.cc
	(selftest::simple_diagnostic_path_cc_tests): Use reference
	printer.
	* toplev.cc (announce_function): Update for global_dc's m_printer
	becoming reference printer.
	(toplev::main): Likewise.
	* tree-diagnostic.cc (tree_diagnostics_defaults): Replace use of
	diagnostic_format_decoder with context->set_format_decoder.
	* tree-diagnostic.h
	(tree_dump_pretty_printer::tree_dump_pretty_printer): Update for
	global_dc's m_printer becoming reference printer.
	* tree.cc (escaped_string::escape): Likewise.
	(selftest::test_escaped_strings): Likewise.

gcc/ada/ChangeLog:
	PR other/116613
	* gcc-interface/misc.cc (internal_error_function): Update for
	m_printer becoming reference printer.

gcc/analyzer/ChangeLog:
	PR other/116613
	* analyzer-language.cc (on_finish_translation_unit): Update for
	m_printer becoming reference printer.
	* engine.cc (run_checkers): Likewise.
	* program-point.cc (function_point::print_source_line): Likewise.

gcc/c-family/ChangeLog:
	PR other/116613
	* c-format.cc (selftest::test_type_mismatch_range_labels): Update
	for m_printer becoming reference printer.
	(selftest::test_type_mismatch_range_labels): Likewise.

gcc/c/ChangeLog:
	PR other/116613
	* c-objc-common.cc: Include "make-unique.h".
	(c_initialize_diagnostics): Use unique_ptr for pretty_printer.
	Use context->set_format_decoder.

gcc/cp/ChangeLog:
	PR other/116613
	* error.cc (cxx_initialize_diagnostics): Use unique_ptr for
	pretty_printer.  Use context->set_format_decoder.
	* module.cc (noisy_p): Update for global_dc's m_printer becoming
	reference printer.

gcc/d/ChangeLog:
	PR other/116613
	* d-diagnostic.cc (d_diagnostic_report_diagnostic): Update for
	m_printer becoming reference printer.

gcc/fortran/ChangeLog:
	PR other/116613
	* error.cc (gfc_diagnostic_build_kind_prefix): Update for
	global_dc's m_printer becoming reference printer.
	(gfc_diagnostics_init): Replace usage of diagnostic_format_decoder
	with global_dc->set_format_decoder.

gcc/jit/ChangeLog:
	PR other/116613
	* dummy-frontend.cc: Include "make-unique.h".
	(class jit_diagnostic_listener): New.
	(jit_begin_diagnostic): Update comment.
	(jit_end_diagnostic): Drop call to add_diagnostic.
	(jit_langhook_init): Set the output format to a new
	jit_diagnostic_listener.
	* jit-playback.cc (playback::context::add_diagnostic): Add "text"
	param and use that rather than trying to get the text from a
	pretty_printer.
	* jit-playback.h (playback::context::add_diagnostic): Add "text"
	param.

gcc/testsuite/ChangeLog:
	PR other/116613
	* gcc.dg/plugin/analyzer_cpython_plugin.c (dump_refcnt_info):
	Update for global_dc's m_printer becoming reference printer.
	* gcc.dg/plugin/crash-test-ice-in-header-sarif-2.2.c: Replace usage
	of -fdiagnostics-format=sarif-file-2.2-prerelease with
	-fdiagnostics-set-output=sarif:version=2.2-prerelease.
	* gcc.dg/plugin/diagnostic_plugin_test_paths.c: Update for
	global_dc's m_printer becoming reference printer.
	* gcc.dg/plugin/diagnostic_plugin_xhtml_format.c: Update for
	changes to output formats.
	* gcc.dg/plugin/expensive_selftests_plugin.c: Update for
	global_dc's m_printer becoming reference printer.
	* gcc.dg/sarif-output/add-output-sarif-defaults.c: New test.
	* gcc.dg/sarif-output/bad-binary-op.c: New test.
	* gcc.dg/sarif-output/bad-binary-op.py: New support script.
	* gcc.dg/sarif-output/multiple-outputs.c: New test.
	* gcc.dg/sarif-output/multiple-outputs.py: New support script.
	* lib/scansarif.exp (verify-sarif-file): Add an optional second
	argument specifying the expected filename of the .sarif file.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/Makefile.in                               |   3 +-
 gcc/ada/gcc-interface/misc.cc                 |   6 +-
 gcc/analyzer/analyzer-language.cc             |   2 +-
 gcc/analyzer/engine.cc                        |   2 +-
 gcc/analyzer/program-point.cc                 |   2 +-
 gcc/c-family/c-format.cc                      |   6 +-
 gcc/c/c-objc-common.cc                        |  12 +-
 gcc/common.opt                                |  12 +-
 gcc/common.opt.urls                           |   6 +
 gcc/cp/error.cc                               |  13 +-
 gcc/cp/module.cc                              |   2 +-
 gcc/d/d-diagnostic.cc                         |   2 +-
 gcc/diagnostic-buffer.h                       |   5 +-
 gcc/diagnostic-format-json.cc                 |  12 +-
 gcc/diagnostic-format-sarif.cc                | 115 ++-
 gcc/diagnostic-format-sarif.h                 |  11 +
 gcc/diagnostic-format-text.cc                 |  34 +
 gcc/diagnostic-format-text.h                  |  22 +-
 gcc/diagnostic-format.h                       |  16 +-
 gcc/diagnostic-global-context.cc              |  12 +-
 gcc/diagnostic-output-file.h                  |  27 +-
 gcc/diagnostic-path.cc                        |   2 +-
 gcc/diagnostic-show-locus.cc                  |  28 +-
 gcc/diagnostic.cc                             | 338 ++++++---
 gcc/diagnostic.h                              |  70 +-
 gcc/doc/invoke.texi                           |  97 +++
 gcc/fortran/error.cc                          |   4 +-
 gcc/gcc.cc                                    |   9 +
 gcc/jit/dummy-frontend.cc                     |  62 +-
 gcc/jit/jit-playback.cc                       |   8 +-
 gcc/jit/jit-playback.h                        |   2 +-
 gcc/opts-diagnostic.cc                        | 680 ++++++++++++++++++
 gcc/opts-diagnostic.h                         |  11 +
 gcc/opts-global.cc                            |   3 +-
 gcc/opts.cc                                   |  12 +-
 gcc/selftest-diagnostic.cc                    |   9 +-
 gcc/selftest-run-tests.cc                     |   1 +
 gcc/selftest.h                                |   1 +
 gcc/simple-diagnostic-path.cc                 |   2 +-
 .../gcc.dg/plugin/analyzer_cpython_plugin.c   |   2 +-
 .../crash-test-ice-in-header-sarif-2.2.c      |   2 +-
 .../plugin/diagnostic_plugin_test_paths.c     |   8 +-
 .../plugin/diagnostic_plugin_xhtml_format.c   |  40 +-
 .../plugin/expensive_selftests_plugin.c       |   2 +-
 .../sarif-output/add-output-sarif-defaults.c  |  16 +
 .../gcc.dg/sarif-output/bad-binary-op.c       |  30 +
 .../gcc.dg/sarif-output/bad-binary-op.py      |  70 ++
 .../gcc.dg/sarif-output/multiple-outputs.c    |  27 +
 .../gcc.dg/sarif-output/multiple-outputs.py   |  50 ++
 gcc/testsuite/lib/scansarif.exp               |   9 +-
 gcc/toplev.cc                                 |   4 +-
 gcc/tree-diagnostic.cc                        |   2 +-
 gcc/tree-diagnostic.h                         |   2 +-
 gcc/tree.cc                                   |   4 +-
 54 files changed, 1662 insertions(+), 267 deletions(-)
 create mode 100644 gcc/opts-diagnostic.cc
 create mode 100644 gcc/testsuite/gcc.dg/sarif-output/add-output-sarif-defaults.c
 create mode 100644 gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.c
 create mode 100644 gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.py
 create mode 100644 gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.c
 create mode 100644 gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.py

Comments

Jonathan Wakely Oct. 30, 2024, 12:22 p.m. UTC | #1
On 29/10/24 19:19 -0400, David Malcolm wrote:
>This patch generalizes diagnostic_context so that rather than having
>a single output format, it has a vector of zero or more.

[snip]

>+/* Class for parsing the arguments of -fdiagnostics-add-output= and
>+   -fdiagnostics-set-output=, and making diagnostic_output_format
>+   instances (or issuing errors).  */
>+
>+class output_factory
>+{
>+public:
>+  class handler
>+  {
>+  public:
>+    handler (std::string name) : m_name (name) {}

How long are these names?

If they don't fit in 15 chars, then this should be std::move(name).

So for a name like "sarif:version=2.1" it should be moved, otherwise
you make a deep copy and reallocate a new string.
David Malcolm Oct. 30, 2024, 2:02 p.m. UTC | #2
On Wed, 2024-10-30 at 12:22 +0000, Jonathan Wakely wrote:
> On 29/10/24 19:19 -0400, David Malcolm wrote:
> > This patch generalizes diagnostic_context so that rather than
> > having
> > a single output format, it has a vector of zero or more.
> 
> [snip]
> 
> > +/* Class for parsing the arguments of -fdiagnostics-add-output=
> > and
> > +   -fdiagnostics-set-output=, and making diagnostic_output_format
> > +   instances (or issuing errors).  */
> > +
> > +class output_factory
> > +{
> > +public:
> > +  class handler
> > +  {
> > +  public:
> > +    handler (std::string name) : m_name (name) {}
> 
> How long are these names?
> 
> If they don't fit in 15 chars, then this should be std::move(name).
> 
> So for a name like "sarif:version=2.1" it should be moved, otherwise
> you make a deep copy and reallocate a new string.

These are the names of the output schemes, which currently are just
"text" and "sarif" [1], well under 15 chars - but there might be other
schemes with longer names in the future [2] so I can use std::move here
(and reinforce the habit of doing it).

Dave

[1] see
https://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-add-output

[2] I'm playing with "experimental-html" which is 17 chars
David Malcolm Oct. 31, 2024, 4:38 p.m. UTC | #3
On Wed, 2024-10-30 at 10:02 -0400, David Malcolm wrote:
> On Wed, 2024-10-30 at 12:22 +0000, Jonathan Wakely wrote:
> > On 29/10/24 19:19 -0400, David Malcolm wrote:
> > > This patch generalizes diagnostic_context so that rather than
> > > having
> > > a single output format, it has a vector of zero or more.
> > 
> > [snip]
> > 
> > > +/* Class for parsing the arguments of -fdiagnostics-add-output=
> > > and
> > > +   -fdiagnostics-set-output=, and making
> > > diagnostic_output_format
> > > +   instances (or issuing errors).  */
> > > +
> > > +class output_factory
> > > +{
> > > +public:
> > > +  class handler
> > > +  {
> > > +  public:
> > > +    handler (std::string name) : m_name (name) {}
> > 
> > How long are these names?
> > 
> > If they don't fit in 15 chars, then this should be std::move(name).
> > 
> > So for a name like "sarif:version=2.1" it should be moved,
> > otherwise
> > you make a deep copy and reallocate a new string.
> 
> These are the names of the output schemes, which currently are just
> "text" and "sarif" [1], well under 15 chars - but there might be
> other
> schemes with longer names in the future [2] so I can use std::move
> here
> (and reinforce the habit of doing it).
> 
> Dave
> 
> [1] see
> https://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-add-output
> 
> [2] I'm playing with "experimental-html" which is 17 chars

FWIW I've added std::move here as r15-4806:
https://gcc.gnu.org/pipermail/gcc-patches/2024-October/667069.html

Dave
Xi Ruoyao Oct. 31, 2024, 5:02 p.m. UTC | #4
On Tue, 2024-10-29 at 19:19 -0400, David Malcolm wrote:
> +static void
> +test_output_arg_parsing ()
> +{
> +  auto_fix_quotes fix_quotes;
> +  auto_fix_progname fix_progname;
> +
> +  /* Minimal correct example.  */
> +  {
> +    parser_test pt;
> +    auto result = pt.parse ("foo");
> +    ASSERT_EQ (result->m_format, "foo");
> +    ASSERT_EQ (result->m_kvs.size (), 0);
> +    ASSERT_FALSE (pt.execution_failed_p ());
> +  }
> +
> +  /* Stray trailing colon with no key/value pairs.  */
> +  {
> +    parser_test pt;
> +    auto result = pt.parse ("foo:");
> +    ASSERT_EQ (result, nullptr);
> +    ASSERT_TRUE (pt.execution_failed_p ());
> +    ASSERT_STREQ (pt.get_diagnostic_text (),
> +		  "PROGNAME: error: `-fOPTION=foo:':"
> +		  " expected KEY=VALUE-style parameter for format `foo'"
> +		  " after `:';"
> +		  " got `'\n");
> +  }

Hi David,

Unfortunately this test fails with LANG=zh_CN.UTF-8, breaking bootstrap:

../../gcc-upstream/gcc/opts-diagnostic.cc:599: test_output_arg_parsing: FAIL: ASSERT_STREQ (pt.get_diagnostic_text (), "PROGNAME: error: `-fOPTION=foo:':" " expected KEY=VALUE-style parameter for format `foo'" " after `:';" " got `'\n")
 val1="PROGNAME: 错误:`-fOPTION=foo:': expected KEY=VALUE-style parameter for format `foo' after `:'; got `'
"
 val2="PROGNAME: error: `-fOPTION=foo:': expected KEY=VALUE-style parameter for format `foo' after `:'; got `'
"

("错误" just means "error" in Chinese.)

I'm not sure what the best way is to fix the issue.
Andrew Pinski Oct. 31, 2024, 6:36 p.m. UTC | #5
On Thu, Oct 31, 2024 at 10:02 AM Xi Ruoyao <xry111@xry111.site> wrote:
>
> On Tue, 2024-10-29 at 19:19 -0400, David Malcolm wrote:
> > +static void
> > +test_output_arg_parsing ()
> > +{
> > +  auto_fix_quotes fix_quotes;
> > +  auto_fix_progname fix_progname;
> > +
> > +  /* Minimal correct example.  */
> > +  {
> > +    parser_test pt;
> > +    auto result = pt.parse ("foo");
> > +    ASSERT_EQ (result->m_format, "foo");
> > +    ASSERT_EQ (result->m_kvs.size (), 0);
> > +    ASSERT_FALSE (pt.execution_failed_p ());
> > +  }
> > +
> > +  /* Stray trailing colon with no key/value pairs.  */
> > +  {
> > +    parser_test pt;
> > +    auto result = pt.parse ("foo:");
> > +    ASSERT_EQ (result, nullptr);
> > +    ASSERT_TRUE (pt.execution_failed_p ());
> > +    ASSERT_STREQ (pt.get_diagnostic_text (),
> > +               "PROGNAME: error: `-fOPTION=foo:':"
> > +               " expected KEY=VALUE-style parameter for format `foo'"
> > +               " after `:';"
> > +               " got `'\n");
> > +  }
>
> Hi David,
>
> Unfortunately this test fails with LANG=zh_CN.UTF-8, breaking bootstrap:
>
> ../../gcc-upstream/gcc/opts-diagnostic.cc:599: test_output_arg_parsing: FAIL: ASSERT_STREQ (pt.get_diagnostic_text (), "PROGNAME: error: `-fOPTION=foo:':" " expected KEY=VALUE-style parameter for format `foo'" " after `:';" " got `'\n")
>  val1="PROGNAME: 错误:`-fOPTION=foo:': expected KEY=VALUE-style parameter for format `foo' after `:'; got `'
> "
>  val2="PROGNAME: error: `-fOPTION=foo:': expected KEY=VALUE-style parameter for format `foo' after `:'; got `'
> "
>
> ("错误" just means "error" in Chinese.)
>
> I'm not sure what the best way is to fix the issue.

This is recorded as https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117361 already.
I think one idea is to set LANG=C before doing the self-check. But I
will allow David and other think of better ways of fixing it.

Thanks,
Andrew

>
> --
> Xi Ruoyao <xry111@xry111.site>
> School of Aerospace Science and Technology, Xidian University
David Malcolm Oct. 31, 2024, 8:57 p.m. UTC | #6
On Thu, 2024-10-31 at 11:36 -0700, Andrew Pinski wrote:
> On Thu, Oct 31, 2024 at 10:02 AM Xi Ruoyao <xry111@xry111.site>
> wrote:
> > 
> > On Tue, 2024-10-29 at 19:19 -0400, David Malcolm wrote:
> > > +static void
> > > +test_output_arg_parsing ()
> > > +{
> > > +  auto_fix_quotes fix_quotes;
> > > +  auto_fix_progname fix_progname;
> > > +
> > > +  /* Minimal correct example.  */
> > > +  {
> > > +    parser_test pt;
> > > +    auto result = pt.parse ("foo");
> > > +    ASSERT_EQ (result->m_format, "foo");
> > > +    ASSERT_EQ (result->m_kvs.size (), 0);
> > > +    ASSERT_FALSE (pt.execution_failed_p ());
> > > +  }
> > > +
> > > +  /* Stray trailing colon with no key/value pairs.  */
> > > +  {
> > > +    parser_test pt;
> > > +    auto result = pt.parse ("foo:");
> > > +    ASSERT_EQ (result, nullptr);
> > > +    ASSERT_TRUE (pt.execution_failed_p ());
> > > +    ASSERT_STREQ (pt.get_diagnostic_text (),
> > > +               "PROGNAME: error: `-fOPTION=foo:':"
> > > +               " expected KEY=VALUE-style parameter for format
> > > `foo'"
> > > +               " after `:';"
> > > +               " got `'\n");
> > > +  }
> > 
> > Hi David,
> > 
> > Unfortunately this test fails with LANG=zh_CN.UTF-8, breaking
> > bootstrap:
> > 
> > ../../gcc-upstream/gcc/opts-diagnostic.cc:599:
> > test_output_arg_parsing: FAIL: ASSERT_STREQ (pt.get_diagnostic_text
> > (), "PROGNAME: error: `-fOPTION=foo:':" " expected KEY=VALUE-style
> > parameter for format `foo'" " after `:';" " got `'\n")
> >  val1="PROGNAME: 错误:`-fOPTION=foo:': expected KEY=VALUE-style
> > parameter for format `foo' after `:'; got `'
> > "
> >  val2="PROGNAME: error: `-fOPTION=foo:': expected KEY=VALUE-style
> > parameter for format `foo' after `:'; got `'
> > "
> > 
> > ("错误" just means "error" in Chinese.)
> > 
> > I'm not sure what the best way is to fix the issue.
> 
> This is recorded as
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117361 already.
> I think one idea is to set LANG=C before doing the self-check. But I
> will allow David and other think of better ways of fixing it.

Sorry about this.  For some reason this isn't reproducing for me.

That said, I think setting LANG=C when running selftests is probably
the best solution.  How does the attached (untested) patch look?

Dave
Joseph Myers Oct. 31, 2024, 11:18 p.m. UTC | #7
On Thu, 31 Oct 2024, David Malcolm wrote:

> That said, I think setting LANG=C when running selftests is probably
> the best solution.  How does the attached (untested) patch look?

LC_ALL takes precedence over LANG.  OK with LC_ALL instead of or alongside 
LANG (assuming that passes testing).

(Note: some platforms may use UTF-8 for the C locale; there's support in 
the testsuite for using C.ASCII instead on Cygwin for that reason.  This 
matters if you're testing the use of ASCII versus Unicode quotes in 
diagnostics.)
Xi Ruoyao Nov. 1, 2024, 3:22 a.m. UTC | #8
On Thu, 2024-10-31 at 23:18 +0000, Joseph Myers wrote:
> On Thu, 31 Oct 2024, David Malcolm wrote:
> 
> > That said, I think setting LANG=C when running selftests is probably
> > the best solution.  How does the attached (untested) patch look?
> 
> LC_ALL takes precedence over LANG.  OK with LC_ALL instead of or alongside 
> LANG (assuming that passes testing).

I agree, for example

LANG=C LC_MESSAGES=zh_CN.UTF-8 LC_CTYPE=C.UTF-8 gcc

still outputs Chinese messages.

LC_ALL=C should be OK (I used LC_ALL=C make -j8 for building GCC
yesterday to work around this issue).

> (Note: some platforms may use UTF-8 for the C locale; there's support in 
> the testsuite for using C.ASCII instead on Cygwin for that reason.  This 
> matters if you're testing the use of ASCII versus Unicode quotes in 
> diagnostics.)
Tobias Burnus Nov. 1, 2024, 6:36 a.m. UTC | #9
Am 31.10.24 um 21:57 schrieb David Malcolm:
> On Thu, 2024-10-31 at 11:36 -0700, Andrew Pinski wrote:
>> This is recorded as
>> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117361 already.
>> I think one idea is to set LANG=C before doing the self-check. But I
>> will allow David and other think of better ways of fixing it.
> 
> Sorry about this.  For some reason this isn't reproducing for me.
> 
> That said, I think setting LANG=C when running selftests is probably
> the best solution.  How does the attached (untested) patch look?

Or you do the same as for https://gcc.gnu.org/PR115203 ; namely,
your commit 
https://gcc.gnu.org/r15-866-g2dbb1c124c1e585dc413132d7a8d4be62c6b7baa

added:

    test_diagnostic_path (pretty_printer *event_pp)
    : simple_diagnostic_path (event_pp)
    {
+    disable_event_localization ();
    }

and then disabled the _(...) localization for those:

-  text_info ti (_(fmt), &ap, 0, nullptr, &rich_loc);
+  text_info ti (m_localize_events ? _(fmt) : fmt,
+               &ap, 0, nullptr, &rich_loc);


In any case, I think it would be good to either use LC_ALL or
code like disable_event_localization but not both, depending
on the code path.

Tobias
David Malcolm Nov. 1, 2024, 4:29 p.m. UTC | #10
On Fri, 2024-11-01 at 11:22 +0800, Xi Ruoyao wrote:
> On Thu, 2024-10-31 at 23:18 +0000, Joseph Myers wrote:
> > On Thu, 31 Oct 2024, David Malcolm wrote:
> > 
> > > That said, I think setting LANG=C when running selftests is
> > > probably
> > > the best solution.  How does the attached (untested) patch look?
> > 
> > LC_ALL takes precedence over LANG.  OK with LC_ALL instead of or
> > alongside 
> > LANG (assuming that passes testing).
> 
> I agree, for example
> 
> LANG=C LC_MESSAGES=zh_CN.UTF-8 LC_CTYPE=C.UTF-8 gcc
> 
> still outputs Chinese messages.
> 
> LC_ALL=C should be OK (I used LC_ALL=C make -j8 for building GCC
> yesterday to work around this issue).

Thanks. I updated the patch to use LC_ALL, successfully bootstrapped
and regression-tested with it, and have pushed it as r15-4840-
g8e95e064ea73d4.

Dave

> 
> > (Note: some platforms may use UTF-8 for the C locale; there's
> > support in 
> > the testsuite for using C.ASCII instead on Cygwin for that reason. 
> > This 
> > matters if you're testing the use of ASCII versus Unicode quotes in
> > diagnostics.)
>
David Malcolm Nov. 1, 2024, 4:31 p.m. UTC | #11
On Fri, 2024-11-01 at 07:36 +0100, Tobias Burnus wrote:
> Am 31.10.24 um 21:57 schrieb David Malcolm:
> > On Thu, 2024-10-31 at 11:36 -0700, Andrew Pinski wrote:
> > > This is recorded as
> > > https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117361 already.
> > > I think one idea is to set LANG=C before doing the self-check.
> > > But I
> > > will allow David and other think of better ways of fixing it.
> > 
> > Sorry about this.  For some reason this isn't reproducing for me.
> > 
> > That said, I think setting LANG=C when running selftests is
> > probably
> > the best solution.  How does the attached (untested) patch look?
> 
> Or you do the same as for https://gcc.gnu.org/PR115203 ; namely,
> your commit 
> https://gcc.gnu.org/r15-866-g2dbb1c124c1e585dc413132d7a8d4be62c6b7baa
> 
> added:
> 
>     test_diagnostic_path (pretty_printer *event_pp)
>     : simple_diagnostic_path (event_pp)
>     {
> +    disable_event_localization ();
>     }
> 
> and then disabled the _(...) localization for those:
> 
> -  text_info ti (_(fmt), &ap, 0, nullptr, &rich_loc);
> +  text_info ti (m_localize_events ? _(fmt) : fmt,
> +               &ap, 0, nullptr, &rich_loc);
> 
> 
> In any case, I think it would be good to either use LC_ALL or
> code like disable_event_localization but not both, depending
> on the code path.

Doing so for this case would require implementing a new feature in the
diagnostic_context to suppress message translation, so I went for the
simpler option (bigger hammer?) of using LC_ALL=C.

Dave
Tobias Burnus Nov. 4, 2024, 10:41 a.m. UTC | #12
Hi David

David Malcolm wrote:
> On Fri, 2024-11-01 at 07:36 +0100, Tobias Burnus wrote:
>> Or you do the same as forhttps://gcc.gnu.org/PR115203 ; namely,
>> your commit
>> https://gcc.gnu.org/r15-866-g2dbb1c124c1e585dc413132d7a8d4be62c6b7baa
>> added: […]
>> +    disable_event_localization ();
> Doing so for this case would require implementing a new feature in the
> diagnostic_context to suppress message translation, so I went for the
> simpler option (bigger hammer?) of using LC_ALL=C.

As this should also fix PR115203, shouldn't r15-866-g2dbb1c124 then be 
reverted to have only one means (LC_ALL) to fix this instead of two 
(disable_event_localization — only covering part of the code — and LC_ALL)?

Tobias
diff mbox series

Patch

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 1be4ea02992c..798d4302fa78 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1849,7 +1849,8 @@  OBJS-libcommon = diagnostic-spec.o diagnostic.o diagnostic-color.o \
 # Objects in libcommon-target.a, used by drivers and by the core
 # compiler and containing target-dependent code.
 OBJS-libcommon-target = $(common_out_object_file) prefix.o \
-	opts.o opts-common.o options.o vec.o hooks.o common/common-targhooks.o \
+	opts.o opts-common.o opts-diagnostic.o options.o \
+	vec.o hooks.o common/common-targhooks.o \
 	hash-table.o file-find.o spellcheck.o selftest.o opt-suggestions.o \
 	options-urls.o
 
diff --git a/gcc/ada/gcc-interface/misc.cc b/gcc/ada/gcc-interface/misc.cc
index 8c921db7dcdd..56742e75dde2 100644
--- a/gcc/ada/gcc-interface/misc.cc
+++ b/gcc/ada/gcc-interface/misc.cc
@@ -308,14 +308,14 @@  internal_error_function (diagnostic_context *context, const char *msgid,
   emergency_dump_function ();
 
   /* Reset the pretty-printer.  */
-  pp_clear_output_area (context->m_printer);
+  pp_clear_output_area (context->get_reference_printer ());
 
   /* Format the message into the pretty-printer.  */
   text_info tinfo (msgid, ap, errno);
-  pp_format_verbatim (context->m_printer, &tinfo);
+  pp_format_verbatim (context->get_reference_printer (), &tinfo);
 
   /* Extract a (writable) pointer to the formatted text.  */
-  buffer = xstrdup (pp_formatted_text (context->m_printer));
+  buffer = xstrdup (pp_formatted_text (context->get_reference_printer ()));
 
   /* Go up to the first newline.  */
   for (p = buffer; *p; p++)
diff --git a/gcc/analyzer/analyzer-language.cc b/gcc/analyzer/analyzer-language.cc
index b4eea6b6581a..e66ab38c9e77 100644
--- a/gcc/analyzer/analyzer-language.cc
+++ b/gcc/analyzer/analyzer-language.cc
@@ -120,7 +120,7 @@  on_finish_translation_unit (const translation_unit &tu)
   log_user the_logger (NULL);
   if (logfile)
     the_logger.set_logger (new logger (logfile, 0, 0,
-				       *global_dc->m_printer));
+				       *global_dc->get_reference_printer ()));
   stash_named_constants (the_logger.get_logger (), tu);
 
   run_callbacks (the_logger.get_logger (), tu);
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 3b23990ef80f..ca81285175b6 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -6327,7 +6327,7 @@  run_checkers ()
     get_or_create_any_logfile ();
     if (dump_fout)
       the_logger.set_logger (new logger (dump_fout, 0, 0,
-					 *global_dc->m_printer));
+					 *global_dc->get_reference_printer ()));
     LOG_SCOPE (the_logger.get_logger ());
 
     impl_run_checkers (the_logger.get_logger ());
diff --git a/gcc/analyzer/program-point.cc b/gcc/analyzer/program-point.cc
index 673933307a82..313df9af1d31 100644
--- a/gcc/analyzer/program-point.cc
+++ b/gcc/analyzer/program-point.cc
@@ -280,7 +280,7 @@  function_point::print_source_line (pretty_printer *pp) const
   diagnostic_source_print_policy source_policy (tmp_dc);
   gcc_assert (pp);
   source_policy.print (*pp, richloc, DK_ERROR, nullptr);
-  pp_string (pp, pp_formatted_text (tmp_dc.m_printer));
+  pp_string (pp, pp_formatted_text (tmp_dc.get_reference_printer ()));
 }
 
 /* class program_point.  */
diff --git a/gcc/c-family/c-format.cc b/gcc/c-family/c-format.cc
index 035080e90aa1..119859b66d92 100644
--- a/gcc/c-family/c-format.cc
+++ b/gcc/c-family/c-format.cc
@@ -5579,14 +5579,14 @@  test_type_mismatch_range_labels ()
   richloc.add_range (param, SHOW_RANGE_WITHOUT_CARET, &param_label);
 
   test_diagnostic_context dc;
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR, dc.m_printer);
+  diagnostic_show_locus (&dc, &richloc, DK_ERROR, dc.get_reference_printer ());
   if (c_dialect_cxx ())
     /* "char*", without a space.  */
     ASSERT_STREQ ("   printf (\"msg: %i\\n\", msg);\n"
 		  "                 ~^     ~~~\n"
 		  "                  |     |\n"
 		  "                  char* int\n",
-		  pp_formatted_text (dc.m_printer));
+		  pp_formatted_text (dc.get_reference_printer ()));
   else
     /* "char *", with a space.  */
     ASSERT_STREQ ("   printf (\"msg: %i\\n\", msg);\n"
@@ -5594,7 +5594,7 @@  test_type_mismatch_range_labels ()
 		  "                  |     |\n"
 		  "                  |     int\n"
 		  "                  char *\n",
-		  pp_formatted_text (dc.m_printer));
+		  pp_formatted_text (dc.get_reference_printer ()));
 }
 
 /* Run all of the selftests within this file.  */
diff --git a/gcc/c/c-objc-common.cc b/gcc/c/c-objc-common.cc
index dc91373f5b98..dcabab0581cb 100644
--- a/gcc/c/c-objc-common.cc
+++ b/gcc/c/c-objc-common.cc
@@ -33,6 +33,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "stringpool.h"
 #include "attribs.h"
 #include "dwarf2.h"
+#include "make-unique.h"
 
 static bool c_tree_printer (pretty_printer *, text_info *, const char *,
 			    int, bool, bool, bool, bool *, pp_token_list &);
@@ -412,16 +413,9 @@  has_c_linkage (const_tree decl ATTRIBUTE_UNUSED)
 void
 c_initialize_diagnostics (diagnostic_context *context)
 {
-  pretty_printer *base = context->m_printer;
-  c_pretty_printer *pp = XNEW (c_pretty_printer);
-  context->m_printer = new (pp) c_pretty_printer ();
-
-  /* It is safe to free this object because it was previously XNEW()'d.  */
-  base->~pretty_printer ();
-  XDELETE (base);
-
+  context->set_pretty_printer (::make_unique<c_pretty_printer> ());
   c_common_diagnostics_set_defaults (context);
-  diagnostic_format_decoder (context) = &c_tree_printer;
+  context->set_format_decoder (&c_tree_printer);
 }
 
 int
diff --git a/gcc/common.opt b/gcc/common.opt
index 12b25ff486de..dceaa6e8dbf7 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1440,6 +1440,14 @@  fdiagnostics-format=
 Common Joined RejectNegative Enum(diagnostics_output_format)
 -fdiagnostics-format=[text|sarif-stderr|sarif-file|json|json-stderr|json-file]	Select output format.
 
+fdiagnostics-add-output=
+Common Joined RejectNegative
+Add output format.
+
+fdiagnostics-set-output=
+Common Joined RejectNegative
+Set output format.
+
 fdiagnostics-escape-format=
 Common Joined RejectNegative Enum(diagnostics_escape_format)
 -fdiagnostics-escape-format=[unicode|bytes]	Select how to escape non-printable-ASCII bytes in the source for diagnostics that suggest it.
@@ -1487,10 +1495,6 @@  Enum(diagnostics_output_format) String(sarif-stderr) Value(DIAGNOSTICS_OUTPUT_FO
 EnumValue
 Enum(diagnostics_output_format) String(sarif-file) Value(DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE)
 
-EnumValue
-Enum(diagnostics_output_format) String(sarif-file-2.2-prerelease) Value(DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE_2_2_PRERELEASE)
-; undocumented
-
 fdiagnostics-parseable-fixits
 Common Var(flag_diagnostics_parseable_fixits)
 Print fix-it hints in machine-readable form.
diff --git a/gcc/common.opt.urls b/gcc/common.opt.urls
index e31736cd9945..78e0dc209d14 100644
--- a/gcc/common.opt.urls
+++ b/gcc/common.opt.urls
@@ -564,6 +564,12 @@  UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-colu
 fdiagnostics-format=
 UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-format)
 
+fdiagnostics-add-output=
+UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-add-output)
+
+fdiagnostics-set-output=
+UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-set-output)
+
 fdiagnostics-escape-format=
 UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-escape-format)
 
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index 8381f9504880..7c259fbbb8af 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -276,20 +276,15 @@  cp_seen_error ()
 void
 cxx_initialize_diagnostics (diagnostic_context *context)
 {
-  pretty_printer *base = context->m_printer;
-  cxx_pretty_printer *pp = XNEW (cxx_pretty_printer);
-  context->m_printer = new (pp) cxx_pretty_printer ();
-
-  /* It is safe to free this object because it was previously XNEW()'d.  */
-  base->~pretty_printer ();
-  XDELETE (base);
+  cxx_pretty_printer *pp = new cxx_pretty_printer ();
+  pp_format_postprocessor (pp) = new cxx_format_postprocessor ();
+  context->set_pretty_printer (std::unique_ptr<pretty_printer> (pp));
 
   c_common_diagnostics_set_defaults (context);
   diagnostic_text_starter (context) = cp_diagnostic_text_starter;
   /* diagnostic_finalizer is already c_diagnostic_text_finalizer.  */
-  diagnostic_format_decoder (context) = cp_printer;
+  context->set_format_decoder (cp_printer);
   context->m_adjust_diagnostic_info = cp_adjust_diagnostic_info;
-  pp_format_postprocessor (pp) = new cxx_format_postprocessor ();
 }
 
 /* Dump an '@module' name suffix for DECL, if any.  */
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index f72e0154dd62..9323023ff7fd 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -4698,7 +4698,7 @@  noisy_p ()
   if (quiet_flag)
     return false;
 
-  pp_needs_newline (global_dc->m_printer) = true;
+  pp_needs_newline (global_dc->get_reference_printer ()) = true;
   diagnostic_set_last_function (global_dc, (diagnostic_info *) NULL);
 
   return true;
diff --git a/gcc/d/d-diagnostic.cc b/gcc/d/d-diagnostic.cc
index d584d2ac2b23..f8c32ae918de 100644
--- a/gcc/d/d-diagnostic.cc
+++ b/gcc/d/d-diagnostic.cc
@@ -208,7 +208,7 @@  d_diagnostic_report_diagnostic (const Loc &loc, int opt, const char *format,
       /* Write verbatim messages with no location direct to stream.  */
       text_info text (expand_d_format (format), &argp, errno, nullptr);
 
-      pretty_printer *const pp = global_dc->m_printer;
+      pretty_printer *const pp = global_dc->get_reference_printer ();
       pp_format_verbatim (pp, &text);
       pp_newline_and_flush (pp);
     }
diff --git a/gcc/diagnostic-buffer.h b/gcc/diagnostic-buffer.h
index deb933a87630..07ccebf9357f 100644
--- a/gcc/diagnostic-buffer.h
+++ b/gcc/diagnostic-buffer.h
@@ -59,6 +59,7 @@  class diagnostic_buffer
   friend class diagnostic_context;
 
   diagnostic_buffer (diagnostic_context &ctxt);
+  ~diagnostic_buffer ();
 
   void dump (FILE *out, int indent) const;
   void DEBUG_FUNCTION dump () const { dump (stderr, 0); }
@@ -73,10 +74,10 @@  class diagnostic_buffer
   void move_to (diagnostic_buffer &dest);
 
  private:
-  void ensure_per_format_buffer ();
+  void ensure_per_format_buffers ();
 
   diagnostic_context &m_ctxt;
-  std::unique_ptr<diagnostic_per_format_buffer> m_per_format_buffer;
+  auto_vec<diagnostic_per_format_buffer *> *m_per_format_buffers;
 
   /* The number of buffered diagnostics of each kind.  */
   diagnostic_counters m_diagnostic_counters;
diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc
index fdc55b364164..ab51fce8ad77 100644
--- a/gcc/diagnostic-format-json.cc
+++ b/gcc/diagnostic-format-json.cc
@@ -104,6 +104,15 @@  public:
   {
     /* No-op.  */
   }
+  void update_printer () final override
+  {
+    m_printer = m_context.clone_printer ();
+    pp_show_color (m_printer.get ()) = false;
+  }
+  bool follows_reference_printer_p () const final override
+  {
+    return false;
+  }
 
 protected:
   json_output_format (diagnostic_context &context,
@@ -501,9 +510,6 @@  static void
 diagnostic_output_format_init_json (diagnostic_context &context,
 				    std::unique_ptr<json_output_format> fmt)
 {
-  /* Suppress normal textual path output.  */
-  context.set_path_format (DPF_NONE);
-
   /* Don't colorize the text.  */
   pp_show_color (fmt->get_printer ()) = false;
   context.set_show_highlight_colors (false);
diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc
index 9973301cb29a..5a3fd3f5dac8 100644
--- a/gcc/diagnostic-format-sarif.cc
+++ b/gcc/diagnostic-format-sarif.cc
@@ -38,6 +38,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-diagram.h"
 #include "text-art/canvas.h"
 #include "diagnostic-format-sarif.h"
+#include "diagnostic-format-text.h"
 #include "ordered-hash-map.h"
 #include "sbitmap.h"
 #include "make-unique.h"
@@ -680,12 +681,18 @@  public:
   friend class diagnostic_sarif_format_buffer;
 
   sarif_builder (diagnostic_context &context,
+		 pretty_printer &printer,
 		 const line_maps *line_maps,
 		 const char *main_input_filename_,
 		 bool formatted,
 		 enum sarif_version version);
   ~sarif_builder ();
 
+  void set_printer (pretty_printer &printer)
+  {
+    m_printer = &printer;
+  }
+
   void on_report_diagnostic (const diagnostic_info &diagnostic,
 			     diagnostic_t orig_diag_kind,
 			     diagnostic_sarif_format_buffer *buffer);
@@ -1540,12 +1547,13 @@  sarif_thread_flow::add_location ()
 /* sarif_builder's ctor.  */
 
 sarif_builder::sarif_builder (diagnostic_context &context,
+			      pretty_printer &printer,
 			      const line_maps *line_maps,
 			      const char *main_input_filename_,
 			      bool formatted,
 			      enum sarif_version version)
 : m_context (context),
-  m_printer (context.m_printer),
+  m_printer (&printer),
   m_line_maps (line_maps),
   m_token_printer (*this),
   m_version (version),
@@ -2123,11 +2131,13 @@  sarif_builder::make_location_object (sarif_location_manager &loc_mgr,
 
       diagnostic_source_print_policy source_policy (dc);
       dc.set_escape_format (m_escape_format);
-      source_policy.print (*dc.m_printer, my_rich_loc, DK_ERROR, nullptr);
+      diagnostic_text_output_format text_output (dc);
+      source_policy.print (*text_output.get_printer (),
+			   my_rich_loc, DK_ERROR, nullptr);
 
+      const char *buf = pp_formatted_text (text_output.get_printer ());
       std::unique_ptr<sarif_multiformat_message_string> result
-	= builder.make_multiformat_message_string
-	    (pp_formatted_text (dc.m_printer));
+	= builder.make_multiformat_message_string (buf);
 
       diagnostic_finish (&dc);
 
@@ -3423,6 +3433,28 @@  public:
     m_buffer = buffer;
   }
 
+  bool follows_reference_printer_p () const final override
+  {
+    return false;
+  }
+
+  void update_printer () final override
+  {
+    m_printer = m_context.clone_printer ();
+
+    /* Don't colorize the text.  */
+    pp_show_color (m_printer.get ()) = false;
+
+    /* No textual URLs.  */
+    m_printer->set_url_format (URL_FORMAT_NONE);
+
+    /* Use builder's token printer.  */
+    get_printer ()->set_token_printer (&m_builder.get_token_printer ());
+
+    /* Update the builder to use the new printer.  */
+    m_builder.set_printer (*get_printer ());
+  }
+
   void on_begin_group () final override
   {
     /* No-op,  */
@@ -3458,7 +3490,8 @@  protected:
 		       bool formatted,
 		       enum sarif_version version)
   : diagnostic_output_format (context),
-    m_builder (context, line_maps, main_input_filename_, formatted, version),
+    m_builder (context, *get_printer (), line_maps, main_input_filename_,
+	       formatted, version),
     m_buffer (nullptr)
   {}
 
@@ -3651,15 +3684,8 @@  static void
 diagnostic_output_format_init_sarif (diagnostic_context &context,
 				     std::unique_ptr<sarif_output_format> fmt)
 {
-  /* Suppress normal textual path output.  */
-  context.set_path_format (DPF_NONE);
-
-  /* Don't colorize the text.  */
-  pp_show_color (fmt->get_printer ()) = false;
-  context.set_show_highlight_colors (false);
+  fmt->update_printer ();
 
-  context.m_printer->set_token_printer
-    (&fmt->get_builder ().get_token_printer ());
   context.set_output_format (std::move (fmt));
 }
 
@@ -3683,26 +3709,23 @@  diagnostic_output_format_init_sarif_stderr (diagnostic_context &context,
 						stderr));
 }
 
-/* Populate CONTEXT in preparation for SARIF output to a file named
-   BASE_FILE_NAME.sarif.  */
+/* Attempt to open BASE_FILE_NAME.sarif for writing.
+   Return a non-null diagnostic_output_file,
+   or return a null diagnostic_output_file and complain to CONTEXT
+   using LINE_MAPS.  */
 
-void
-diagnostic_output_format_init_sarif_file (diagnostic_context &context,
+diagnostic_output_file
+diagnostic_output_format_open_sarif_file (diagnostic_context &context,
 					  line_maps *line_maps,
-					  const char *main_input_filename_,
-					  bool formatted,
-					  enum sarif_version version,
 					  const char *base_file_name)
 {
-  gcc_assert (line_maps);
-
   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 SARIF output");
-      return;
+      return diagnostic_output_file ();
     }
 
   label_text filename = label_text::take (concat (base_file_name,
@@ -3716,9 +3739,29 @@  diagnostic_output_format_init_sarif_file (diagnostic_context &context,
 	(DK_ERROR, richloc, nullptr, 0,
 	 "unable to open %qs for SARIF output: %m",
 	 filename.get ());
-      return;
+      return diagnostic_output_file ();
     }
-  diagnostic_output_file output_file (outf, true, std::move (filename));
+  return diagnostic_output_file (outf, true, std::move (filename));
+}
+
+/* Populate CONTEXT in preparation for SARIF output to a file named
+   BASE_FILE_NAME.sarif.  */
+
+void
+diagnostic_output_format_init_sarif_file (diagnostic_context &context,
+					  line_maps *line_maps,
+					  const char *main_input_filename_,
+					  bool formatted,
+					  enum sarif_version version,
+					  const char *base_file_name)
+{
+  gcc_assert (line_maps);
+
+  diagnostic_output_file output_file
+    = diagnostic_output_format_open_sarif_file (context,
+						line_maps,
+						base_file_name);
+
   diagnostic_output_format_init_sarif
     (context,
      ::make_unique<sarif_file_output_format> (context,
@@ -3750,6 +3793,23 @@  diagnostic_output_format_init_sarif_stream (diagnostic_context &context,
 						stream));
 }
 
+std::unique_ptr<diagnostic_output_format>
+make_sarif_sink (diagnostic_context &context,
+		 const line_maps &line_maps,
+		 const char *main_input_filename_,
+		 enum sarif_version version,
+		 diagnostic_output_file output_file)
+{
+  auto sink = ::make_unique<sarif_file_output_format> (context,
+						       &line_maps,
+						       main_input_filename_,
+						       true,
+						       version,
+						       std::move (output_file));
+  sink->update_printer ();
+  return sink;
+}
+
 #if CHECKING_P
 
 namespace selftest {
@@ -3822,8 +3882,9 @@  test_make_location_object (const line_table_case &case_,
     return;
 
   test_diagnostic_context dc;
-
-  sarif_builder builder (dc, line_table, "MAIN_INPUT_FILENAME", true, version);
+  pretty_printer pp;
+  sarif_builder builder (dc, pp, line_table, "MAIN_INPUT_FILENAME",
+			 true, version);
 
   /* These "columns" are byte offsets, whereas later on the columns
      in the generated SARIF use sarif_builder::get_sarif_column and
diff --git a/gcc/diagnostic-format-sarif.h b/gcc/diagnostic-format-sarif.h
index 5f8751aa3505..f149d2983a42 100644
--- a/gcc/diagnostic-format-sarif.h
+++ b/gcc/diagnostic-format-sarif.h
@@ -35,6 +35,11 @@  enum class sarif_version
   num_versions
 };
 
+extern diagnostic_output_file
+diagnostic_output_format_open_sarif_file (diagnostic_context &context,
+					  line_maps *line_maps,
+					  const char *base_file_name);
+
 extern void
 diagnostic_output_format_init_sarif_stderr (diagnostic_context &context,
 					    const line_maps *line_maps,
@@ -55,6 +60,12 @@  diagnostic_output_format_init_sarif_stream (diagnostic_context &context,
 					    bool formatted,
 					    enum sarif_version version,
 					    FILE *stream);
+extern std::unique_ptr<diagnostic_output_format>
+make_sarif_sink (diagnostic_context &context,
+		 const line_maps &line_maps,
+		 const char *main_input_filename_,
+		 enum sarif_version version,
+		 diagnostic_output_file output_file);
 
 /* Concrete subclass of json::object for SARIF property bags
    (SARIF v2.1.0 section 3.8).  */
diff --git a/gcc/diagnostic-format-text.cc b/gcc/diagnostic-format-text.cc
index c23ac6c505d8..fd47ca4a15b2 100644
--- a/gcc/diagnostic-format-text.cc
+++ b/gcc/diagnostic-format-text.cc
@@ -160,6 +160,9 @@  void
 diagnostic_text_output_format::dump (FILE *out, int indent) const
 {
   fprintf (out, "%*sdiagnostic_text_output_format\n", indent, "");
+  fprintf (out, "%*sm_follows_reference_printer: %s\n",
+	   indent, "",
+	   m_follows_reference_printer ? "true" : "false");
   diagnostic_output_format::dump (out, indent);
   fprintf (out, "%*ssaved_output_buffer:\n", indent + 2, "");
   if (m_saved_output_buffer)
@@ -222,6 +225,13 @@  on_report_diagnostic (const diagnostic_info &diagnostic,
 					     orig_diag_kind);
 }
 
+void
+diagnostic_text_output_format::on_report_verbatim (text_info &text)
+{
+  pp_format_verbatim (get_printer (), &text);
+  pp_newline_and_flush (get_printer ());
+}
+
 void
 diagnostic_text_output_format::on_diagram (const diagnostic_diagram &diagram)
 {
@@ -315,6 +325,30 @@  diagnostic_text_output_format::append_note (location_t location,
   va_end (ap);
 }
 
+bool
+diagnostic_text_output_format::follows_reference_printer_p () const
+{
+  return m_follows_reference_printer;
+}
+
+void
+diagnostic_text_output_format::
+update_printer ()
+{
+  pretty_printer *copy_from_pp
+    = (m_follows_reference_printer
+       ? get_context ().get_reference_printer ()
+       : m_printer.get ());
+  const bool show_color = pp_show_color (copy_from_pp);
+  const diagnostic_url_format url_format = copy_from_pp->get_url_format ();
+
+  m_printer = get_context ().clone_printer ();
+
+  pp_show_color (m_printer.get ()) = show_color;
+  m_printer->set_url_format (url_format);
+  // ...etc
+}
+
 /* If DIAGNOSTIC has a CWE identifier, print it.
 
    For example, if the diagnostic metadata associates it with CWE-119,
diff --git a/gcc/diagnostic-format-text.h b/gcc/diagnostic-format-text.h
index b43501eaa4ab..78d1b2da0a0c 100644
--- a/gcc/diagnostic-format-text.h
+++ b/gcc/diagnostic-format-text.h
@@ -32,12 +32,14 @@  along with GCC; see the file COPYING3.  If not see
 class diagnostic_text_output_format : public diagnostic_output_format
 {
 public:
-  diagnostic_text_output_format (diagnostic_context &context)
+  diagnostic_text_output_format (diagnostic_context &context,
+				 bool follows_reference_printer = false)
   : diagnostic_output_format (context),
     m_saved_output_buffer (nullptr),
     m_column_policy (context),
     m_last_module (nullptr),
-    m_includes_seen (nullptr)
+    m_includes_seen (nullptr),
+    m_follows_reference_printer (follows_reference_printer)
   {}
   ~diagnostic_text_output_format ();
 
@@ -51,12 +53,16 @@  public:
   void on_end_group () override {}
   void on_report_diagnostic (const diagnostic_info &,
 			     diagnostic_t orig_diag_kind) override;
+  void on_report_verbatim (text_info &) final override;
   void on_diagram (const diagnostic_diagram &diagram) override;
-  void after_diagnostic (const diagnostic_info &) final override;
+  void after_diagnostic (const diagnostic_info &) override;
   bool machine_readable_stderr_p () const final override
   {
     return false;
   }
+  bool follows_reference_printer_p () const final override;
+
+  void update_printer () override;
 
   /* Helpers for writing lang-specific starters/finalizers for text output.  */
   char *build_prefix (const diagnostic_info &) const;
@@ -77,7 +83,7 @@  public:
   }
   diagnostic_location_print_policy get_location_print_policy () const;
 
-private:
+protected:
   void print_any_cwe (const diagnostic_info &diagnostic);
   void print_any_rules (const diagnostic_info &diagnostic);
   void print_option_information (const diagnostic_info &diagnostic,
@@ -98,6 +104,14 @@  private:
   /* Include files that report_current_module has already listed the
      include path for.  */
   hash_set<location_t, false, location_hash> *m_includes_seen;
+
+  /* If true, this is the initial default text output format created
+     when the diagnostic_context was created, and, in particular, before
+     initializations of color and m_url_format.  Hence this should follow
+     the dc's reference printer for these.
+     If false, this text output was created after the dc was created, and
+     thus tracks its own values for color and m_url_format.  */
+  bool m_follows_reference_printer;
 };
 
 #endif /* ! GCC_DIAGNOSTIC_FORMAT_TEXT_H */
diff --git a/gcc/diagnostic-format.h b/gcc/diagnostic-format.h
index a38d177ce8d1..11238cd95283 100644
--- a/gcc/diagnostic-format.h
+++ b/gcc/diagnostic-format.h
@@ -55,12 +55,21 @@  public:
   virtual void on_report_diagnostic (const diagnostic_info &,
 				     diagnostic_t orig_diag_kind) = 0;
 
+  virtual void on_report_verbatim (text_info &);
+
   virtual void on_diagram (const diagnostic_diagram &diagram) = 0;
   virtual void after_diagnostic (const diagnostic_info &) = 0;
   virtual bool machine_readable_stderr_p () const = 0;
+  virtual bool follows_reference_printer_p () const = 0;
+
+  /* Vfunc called when the diagnostic_context changes its
+     reference printer (either to a new subclass of pretty_printer
+     or when color/url options change).
+     Subclasses should update their m_printer accordingly.  */
+  virtual void update_printer () = 0;
 
   diagnostic_context &get_context () const { return m_context; }
-  pretty_printer *get_printer () const { return m_context.m_printer; }
+  pretty_printer *get_printer () const { return m_printer.get (); }
 
   text_art::theme *get_diagram_theme () const
   {
@@ -71,10 +80,13 @@  public:
 
 protected:
   diagnostic_output_format (diagnostic_context &context)
-  : m_context (context)
+  : m_context (context),
+    m_printer (context.clone_printer ())
   {}
 
+protected:
   diagnostic_context &m_context;
+  std::unique_ptr<pretty_printer> m_printer;
 };
 
 extern void
diff --git a/gcc/diagnostic-global-context.cc b/gcc/diagnostic-global-context.cc
index e82588e54047..bd299d08429b 100644
--- a/gcc/diagnostic-global-context.cc
+++ b/gcc/diagnostic-global-context.cc
@@ -37,7 +37,8 @@  diagnostic_context *global_dc = &global_diagnostic_context;
 /* Standard error reporting routines in increasing order of severity.  */
 
 /* Text to be emitted verbatim to the error message stream; this
-   produces no prefix and disables line-wrapping.  Use rarely.  */
+   produces no prefix and disables line-wrapping.  Use rarely.
+   It is ignored for machine-readable output formats.  */
 void
 verbatim (const char *gmsgid, ...)
 {
@@ -45,8 +46,7 @@  verbatim (const char *gmsgid, ...)
 
   va_start (ap, gmsgid);
   text_info text (_(gmsgid), &ap, errno);
-  pp_format_verbatim (global_dc->m_printer, &text);
-  pp_newline_and_flush (global_dc->m_printer);
+  global_dc->report_verbatim (text);
   va_end (ap);
 }
 
@@ -551,10 +551,8 @@  fnotice (FILE *file, const char *cmsgid, ...)
      emitting free-form text on stderr will lead to corrupt output.
      Skip the message for such cases.  */
   if (file == stderr && global_dc)
-    if (const diagnostic_output_format *output_format
-	  = global_dc->get_output_format ())
-      if (output_format->machine_readable_stderr_p ())
-	return;
+    if (!global_dc->supports_fnotice_on_stderr_p ())
+      return;
 
   va_list ap;
 
diff --git a/gcc/diagnostic-output-file.h b/gcc/diagnostic-output-file.h
index f0ae5e1915ec..6e7b41737cd4 100644
--- a/gcc/diagnostic-output-file.h
+++ b/gcc/diagnostic-output-file.h
@@ -27,6 +27,12 @@  along with GCC; see the file COPYING3.  If not see
 class diagnostic_output_file
 {
 public:
+  diagnostic_output_file ()
+  : m_outf (nullptr),
+    m_owned (false),
+    m_filename ()
+  {
+  }
   diagnostic_output_file (FILE *outf, bool owned, label_text filename)
   : m_outf (outf),
     m_owned (owned),
@@ -60,7 +66,26 @@  public:
   diagnostic_output_file &
   operator= (const diagnostic_output_file &other) = delete;
   diagnostic_output_file &
-  operator= (diagnostic_output_file &&other) = delete;
+  operator= (diagnostic_output_file &&other)
+  {
+    if (m_owned)
+      {
+	gcc_assert (m_outf);
+	fclose (m_outf);
+      }
+
+    m_outf = other.m_outf;
+    other.m_outf = nullptr;
+
+    m_owned = other.m_owned;
+    other.m_owned = false;
+
+    m_filename = std::move (other.m_filename);
+
+    if (m_owned)
+      gcc_assert (m_outf);
+    return *this;
+  }
 
   operator bool () const { return m_outf != nullptr; }
   FILE *get_open_file () const { return m_outf; }
diff --git a/gcc/diagnostic-path.cc b/gcc/diagnostic-path.cc
index 8a6d516ce416..2b1a093f3f22 100644
--- a/gcc/diagnostic-path.cc
+++ b/gcc/diagnostic-path.cc
@@ -1297,7 +1297,7 @@  test_interprocedural_path_1 (pretty_printer *event_pp)
 
   {
     test_diagnostic_context dc;
-    diagnostic_text_output_format text_output (dc);
+    diagnostic_text_output_format text_output (dc, false);
     path_print_policy policy (text_output);
     path_summary summary (policy, path, false);
     ASSERT_EQ (summary.get_num_ranges (), 9);
diff --git a/gcc/diagnostic-show-locus.cc b/gcc/diagnostic-show-locus.cc
index 79499dc549d3..a2a4b047ff9c 100644
--- a/gcc/diagnostic-show-locus.cc
+++ b/gcc/diagnostic-show-locus.cc
@@ -3582,7 +3582,7 @@  test_layout_x_offset_display_utf8 (const line_table_case &case_)
 			   linemap_position_for_column (line_table,
 							emoji_col));
     layout test_layout (policy, richloc, nullptr);
-    layout_printer lp (*dc.m_printer, test_layout, richloc, DK_ERROR);
+    layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR);
     lp.print (policy);
     ASSERT_STREQ ("     |         1         \n"
 		  "     |         1         \n"
@@ -3591,7 +3591,7 @@  test_layout_x_offset_display_utf8 (const line_table_case &case_)
 		  "that occupies 8 bytes and 4 display columns, starting at "
 		  "column #102.\n"
 		  "     | ^\n",
-		  pp_formatted_text (dc.m_printer));
+		  pp_formatted_text (dc.get_reference_printer ()));
   }
 
   /* Similar to the previous example, but now the offset called for would split
@@ -3609,7 +3609,7 @@  test_layout_x_offset_display_utf8 (const line_table_case &case_)
 			   linemap_position_for_column (line_table,
 							emoji_col + 2));
     layout test_layout (dc, richloc, nullptr);
-    layout_printer lp (*dc.m_printer, test_layout, richloc, DK_ERROR);
+    layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR);
     lp.print (policy);
     ASSERT_STREQ ("     |        1         1 \n"
 		  "     |        1         2 \n"
@@ -3618,7 +3618,7 @@  test_layout_x_offset_display_utf8 (const line_table_case &case_)
 		  "that occupies 8 bytes and 4 display columns, starting at "
 		  "column #102.\n"
 		  "     |  ^\n",
-		  pp_formatted_text (dc.m_printer));
+		  pp_formatted_text (dc.get_reference_printer ()));
   }
 
 }
@@ -3690,9 +3690,9 @@  test_layout_x_offset_display_tab (const line_table_case &case_)
       dc.m_tabstop = tabstop;
       diagnostic_source_print_policy policy (dc);
       layout test_layout (policy, richloc, nullptr);
-      layout_printer lp (*dc.m_printer, test_layout, richloc, DK_ERROR);
+      layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR);
       lp.print (policy);
-      const char *out = pp_formatted_text (dc.m_printer);
+      const char *out = pp_formatted_text (dc.get_reference_printer ());
       ASSERT_EQ (NULL, strchr (out, '\t'));
       const char *left_quote = strchr (out, '`');
       const char *right_quote = strchr (out, '\'');
@@ -3715,7 +3715,7 @@  test_layout_x_offset_display_tab (const line_table_case &case_)
       dc.m_source_printing.show_line_numbers_p = true;
       diagnostic_source_print_policy policy (dc);
       layout test_layout (policy, richloc, nullptr);
-      layout_printer lp (*dc.m_printer, test_layout, richloc, DK_ERROR);
+      layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR);
       lp.print (policy);
 
       /* We have arranged things so that two columns will be printed before
@@ -3731,7 +3731,7 @@  test_layout_x_offset_display_tab (const line_table_case &case_)
 	  "display columns, starting at column #103.\n"
 	  "     |   ^\n";
       const char *expected_output = (extra_width[tabstop] ? output1 : output2);
-      ASSERT_STREQ (expected_output, pp_formatted_text (dc.m_printer));
+      ASSERT_STREQ (expected_output, pp_formatted_text (dc.get_reference_printer ()));
     }
 }
 
@@ -3887,8 +3887,8 @@  test_one_liner_fixit_remove ()
   /* Test of adding a prefix.  */
   {
     test_diagnostic_context dc;
-    pp_prefixing_rule (dc.m_printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
-    pp_set_prefix (dc.m_printer, xstrdup ("TEST PREFIX:"));
+    pp_prefixing_rule (dc.get_reference_printer ()) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+    pp_set_prefix (dc.get_reference_printer (), xstrdup ("TEST PREFIX:"));
     ASSERT_STREQ ("TEST PREFIX: foo = bar.field;\n"
 		  "TEST PREFIX:          ^~~~~~\n"
 		  "TEST PREFIX:          ------\n",
@@ -3914,8 +3914,8 @@  test_one_liner_fixit_remove ()
     test_diagnostic_context dc;
     dc.m_source_printing.show_ruler_p = true;
     dc.m_source_printing.max_width = 50;
-    pp_prefixing_rule (dc.m_printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
-    pp_set_prefix (dc.m_printer, xstrdup ("TEST PREFIX:"));
+    pp_prefixing_rule (dc.get_reference_printer ()) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+    pp_set_prefix (dc.get_reference_printer (), xstrdup ("TEST PREFIX:"));
     ASSERT_STREQ ("TEST PREFIX:          1         2         3         4         5\n"
 		  "TEST PREFIX: 12345678901234567890123456789012345678901234567890\n"
 		  "TEST PREFIX: foo = bar.field;\n"
@@ -3930,8 +3930,8 @@  test_one_liner_fixit_remove ()
     dc.m_source_printing.show_ruler_p = true;
     dc.m_source_printing.max_width = 50;
     dc.m_source_printing.show_line_numbers_p = true;
-    pp_prefixing_rule (dc.m_printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
-    pp_set_prefix (dc.m_printer, xstrdup ("TEST PREFIX:"));
+    pp_prefixing_rule (dc.get_reference_printer ()) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
+    pp_set_prefix (dc.get_reference_printer (), xstrdup ("TEST PREFIX:"));
     ASSERT_STREQ ("TEST PREFIX:      |          1         2         3         4         5\n"
 		  "TEST PREFIX:      | 12345678901234567890123456789012345678901234567890\n"
 		  "TEST PREFIX:    1 | foo = bar.field;\n"
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index 4791af9c66e2..477214c15f2b 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -50,6 +50,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "pretty-print-urlifier.h"
 #include "logical-location.h"
 #include "diagnostic-buffer.h"
+#include "make-unique.h"
 
 #ifdef HAVE_TERMIOS_H
 # include <termios.h>
@@ -118,7 +119,7 @@  diagnostic_set_caret_max_width (diagnostic_context *context, int value)
 {
   /* One minus to account for the leading empty space.  */
   value = value ? value - 1
-    : (isatty (fileno (pp_buffer (context->m_printer)->m_stream))
+    : (isatty (fileno (pp_buffer (context->get_reference_printer ())->m_stream))
        ? get_terminal_width () - 1 : INT_MAX);
 
   if (value <= 0)
@@ -223,8 +224,7 @@  diagnostic_context::initialize (int n_opts)
 {
   /* Allocate a basic pretty-printer.  Clients will replace this a
      much more elaborated pretty-printer if they wish.  */
-  m_printer = XNEW (pretty_printer);
-  new (m_printer) pretty_printer ();
+  m_reference_printer = ::make_unique<pretty_printer> ().release ();
 
   m_file_cache = new file_cache ();
   m_diagnostic_counters.clear ();
@@ -232,7 +232,7 @@  diagnostic_context::initialize (int n_opts)
   m_n_opts = n_opts;
   m_option_classifier.init (n_opts);
   m_source_printing.enabled = false;
-  diagnostic_set_caret_max_width (this, pp_line_cutoff (m_printer));
+  diagnostic_set_caret_max_width (this, pp_line_cutoff (get_reference_printer ()));
   for (int i = 0; i < rich_location::STATICALLY_ALLOCATED_RANGES; i++)
     m_source_printing.caret_chars[i] = '^';
   m_show_cwe = false;
@@ -283,7 +283,7 @@  diagnostic_context::initialize (int n_opts)
   m_edit_context_ptr = nullptr;
   m_diagnostic_groups.m_nesting_depth = 0;
   m_diagnostic_groups.m_emission_count = 0;
-  m_output_format = new diagnostic_text_output_format (*this);
+  m_output_sinks.safe_push (new diagnostic_text_output_format (*this, true));
   m_set_locations_cb = nullptr;
   m_client_data_hooks = nullptr;
   m_diagrams.m_theme = nullptr;
@@ -326,8 +326,12 @@  diagnostic_context::color_init (int value)
       else
 	value = DIAGNOSTICS_COLOR_DEFAULT;
     }
-  pp_show_color (m_printer)
+  pp_show_color (m_reference_printer)
     = colorize_init ((diagnostic_color_rule_t) value);
+  for (auto sink : m_output_sinks)
+    if (sink->follows_reference_printer_p ())
+      pp_show_color (sink->get_printer ())
+	= pp_show_color (m_reference_printer);
 }
 
 /* Initialize URL support within this context based on VALUE,
@@ -354,8 +358,12 @@  diagnostic_context::urls_init (int value)
 	value = DIAGNOSTICS_URLS_DEFAULT;
     }
 
-  m_printer->set_url_format
+  m_reference_printer->set_url_format
     (determine_url_format ((diagnostic_url_rule_t) value));
+  for (auto sink : m_output_sinks)
+    if (sink->follows_reference_printer_p ())
+      sink->get_printer ()->set_url_format
+	(m_reference_printer->get_url_format ());
 }
 
 /* Create the file_cache, if not already created, and tell it how to
@@ -375,7 +383,7 @@  diagnostic_context::finish ()
 {
   /* We might be handling a fatal error.
      Close any active diagnostic groups, which may trigger flushing
-     the output format.  */
+     sinks.  */
   while (m_diagnostic_groups.m_nesting_depth > 0)
     end_group ();
 
@@ -383,8 +391,8 @@  diagnostic_context::finish ()
 
   /* Clean ups.  */
 
-  delete m_output_format;
-  m_output_format= nullptr;
+  while (!m_output_sinks.is_empty ())
+    delete m_output_sinks.pop ();
 
   if (m_diagrams.m_theme)
     {
@@ -397,11 +405,8 @@  diagnostic_context::finish ()
 
   m_option_classifier.fini ();
 
-  /* diagnostic_context::initialize allocates this->printer using XNEW
-     and placement-new.  */
-  m_printer->~pretty_printer ();
-  XDELETE (m_printer);
-  m_printer = nullptr;
+  delete m_reference_printer;
+  m_reference_printer = nullptr;
 
   if (m_edit_context_ptr)
     {
@@ -429,10 +434,13 @@  diagnostic_context::dump (FILE *out) const
 {
   fprintf (out, "diagnostic_context:\n");
   m_diagnostic_counters.dump (out, 2);
-  fprintf (out, "  output format:\n");
-  m_output_format->dump (out, 4);
-  fprintf (out, "  printer:\n");
-  m_printer->dump (out, 4);
+  fprintf (out, "  reference printer:\n");
+  m_reference_printer->dump (out, 4);
+  for (unsigned i = 0; i < m_output_sinks.length (); ++i)
+    {
+      fprintf (out, "  sink %i:\n", i);
+      m_output_sinks[i]->dump (out, 4);
+    }
   fprintf (out, "  diagnostic buffer:\n");
   if (m_diagnostic_buffer)
     m_diagnostic_buffer->dump (out, 4);
@@ -457,9 +465,34 @@  void
 diagnostic_context::
 set_output_format (std::unique_ptr<diagnostic_output_format> output_format)
 {
-  delete m_output_format;
-  /* Ideally this field would be a std::unique_ptr.  */
-  m_output_format = output_format.release ();
+  while (!m_output_sinks.is_empty ())
+    delete m_output_sinks.pop ();
+  m_output_sinks.safe_push (output_format.release ());
+}
+
+diagnostic_output_format &
+diagnostic_context::get_output_format (size_t idx) const
+{
+  gcc_assert (idx < m_output_sinks.length ());
+  gcc_assert (m_output_sinks[idx]);
+  return *m_output_sinks[idx];
+}
+
+void
+diagnostic_context::add_sink (std::unique_ptr<diagnostic_output_format> sink)
+{
+  m_output_sinks.safe_push (sink.release ());
+}
+
+/* Return true if there are no machine-readable formats writing to stderr.  */
+
+bool
+diagnostic_context::supports_fnotice_on_stderr_p () const
+{
+  for (auto sink : m_output_sinks)
+    if (sink->machine_readable_stderr_p ())
+      return false;
+  return true;
 }
 
 void
@@ -502,6 +535,55 @@  diagnostic_context::set_urlifier (std::unique_ptr<urlifier> urlifier)
   m_urlifier = urlifier.release ();
 }
 
+/* Set PP as the reference printer for this context.
+   Refresh all output sinks.  */
+
+void
+diagnostic_context::set_pretty_printer (std::unique_ptr<pretty_printer> pp)
+{
+  delete m_reference_printer;
+  m_reference_printer = pp.release ();
+  refresh_output_sinks ();
+}
+
+/* Give all output sinks a chance to rebuild their pretty_printer.  */
+
+void
+diagnostic_context::refresh_output_sinks ()
+{
+  for (auto sink : m_output_sinks)
+    sink->update_printer ();
+}
+
+/* Set FORMAT_DECODER on the reference printer and on the pretty_printer
+   of all output sinks.  */
+
+void
+diagnostic_context::set_format_decoder (printer_fn format_decoder)
+{
+  pp_format_decoder (m_reference_printer) = format_decoder;
+  for (auto sink : m_output_sinks)
+    pp_format_decoder (sink->get_printer ()) = format_decoder;
+}
+
+void
+diagnostic_context::set_show_highlight_colors (bool val)
+{
+  pp_show_highlight_colors (m_reference_printer) = val;
+  for (auto sink : m_output_sinks)
+    if (sink->follows_reference_printer_p ())
+      pp_show_highlight_colors (sink->get_printer ()) = val;
+}
+
+void
+diagnostic_context::set_prefixing_rule (diagnostic_prefixing_rule_t rule)
+{
+  pp_prefixing_rule (m_reference_printer) = rule;
+  for (auto sink : m_output_sinks)
+    if (sink->follows_reference_printer_p ())
+      pp_prefixing_rule (sink->get_printer ()) = rule;
+}
+
 void
 diagnostic_context::create_edit_context ()
 {
@@ -1234,8 +1316,6 @@  diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
 {
   diagnostic_t orig_diag_kind = diagnostic->kind;
 
-  gcc_assert (m_output_format);
-
   /* Every call to report_diagnostic should be within a
      begin_group/end_group pair so that output formats can reliably
      flush diagnostics with on_end_group when the topmost group is ended.  */
@@ -1269,7 +1349,7 @@  diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
 	 through.  Don't do this more than once.  */
       if ((diagnostic->kind == DK_ICE || diagnostic->kind == DK_ICE_NOBT)
 	  && m_lock == 1)
-	pp_newline_and_flush (m_printer);
+	pp_newline_and_flush (m_reference_printer);
       else
 	error_recursion ();
     }
@@ -1341,20 +1421,38 @@  diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
 
   /* Is this the initial diagnostic within the stack of groups?  */
   if (m_diagnostic_groups.m_emission_count == 0)
-    m_output_format->on_begin_group ();
+    for (auto sink : m_output_sinks)
+      sink->on_begin_group ();
   m_diagnostic_groups.m_emission_count++;
 
-  /* Run phases 1 and 2 of formatting the message.
-     In particular, some format codes may have side-effects here which need to
-     happen before sending the diagnostic to the output format.
-
-     For example, Fortran's %C and %L formatting codes populate the
-     rich_location.  */
-  pp_format (m_printer, &diagnostic->message);
-
-  /* Call vfunc in the output format.  This is responsible for
-     phase 3 of formatting, and for printing the result.  */
-  m_output_format->on_report_diagnostic (*diagnostic, orig_diag_kind);
+  va_list *orig_args = diagnostic->message.m_args_ptr;
+  for (auto sink : m_output_sinks)
+    {
+      /* Formatting the message is done per-output-format,
+	 so that each output format gets its own set of pp_token_lists
+	 to work with.
+
+	 Run phases 1 and 2 of formatting the message before calling
+	 the format's on_report_diagnostic.
+	 In particular, some format codes may have side-effects here which
+	 need to happen before sending the diagnostic to the output format.
+	 For example, Fortran's %C and %L formatting codes populate the
+	 rich_location.
+	 Such side-effects must be idempotent, since they are run per
+	 output-format.
+
+	 Make a duplicate of the varargs for each call to pp_format,
+	 so that each has its own set to consume.  */
+      va_list copied_args;
+      va_copy (copied_args, *orig_args);
+      diagnostic->message.m_args_ptr = &copied_args;
+      pp_format (sink->get_printer (), &diagnostic->message);
+      va_end (copied_args);
+
+      /* Call vfunc in the output format.  This is responsible for
+	 phase 3 of formatting, and for printing the result.  */
+      sink->on_report_diagnostic (*diagnostic, orig_diag_kind);
+    }
 
   switch (m_extra_output_kind)
     {
@@ -1362,17 +1460,17 @@  diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
       break;
     case EXTRA_DIAGNOSTIC_OUTPUT_fixits_v1:
       print_parseable_fixits (get_file_cache (),
-			      m_printer, diagnostic->richloc,
+			      m_reference_printer, diagnostic->richloc,
 			      DIAGNOSTICS_COLUMN_UNIT_BYTE,
 			      m_tabstop);
-      pp_flush (m_printer);
+      pp_flush (m_reference_printer);
       break;
     case EXTRA_DIAGNOSTIC_OUTPUT_fixits_v2:
       print_parseable_fixits (get_file_cache (),
-			      m_printer, diagnostic->richloc,
+			      m_reference_printer, diagnostic->richloc,
 			      DIAGNOSTICS_COLUMN_UNIT_DISPLAY,
 			      m_tabstop);
-      pp_flush (m_printer);
+      pp_flush (m_reference_printer);
       break;
     }
   if (m_diagnostic_buffer == nullptr
@@ -1389,11 +1487,26 @@  diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
   m_lock--;
 
   if (!m_diagnostic_buffer)
-    m_output_format->after_diagnostic (*diagnostic);
+    for (auto sink : m_output_sinks)
+      sink->after_diagnostic (*diagnostic);
 
   return true;
 }
 
+void
+diagnostic_context::report_verbatim (text_info &text)
+{
+  va_list *orig_args = text.m_args_ptr;
+  for (auto sink : m_output_sinks)
+    {
+      va_list copied_args;
+      va_copy (copied_args, *orig_args);
+      text.m_args_ptr = &copied_args;
+      sink->on_report_verbatim (text);
+      va_end (copied_args);
+    }
+}
+
 /* Get the number of digits in the decimal representation of VALUE.  */
 
 int
@@ -1511,8 +1624,8 @@  diagnostic_context::emit_diagram (const diagnostic_diagram &diagram)
   if (m_diagrams.m_theme == nullptr)
     return;
 
-  gcc_assert (m_output_format);
-  m_output_format->on_diagram (diagram);
+  for (auto sink : m_output_sinks)
+    sink->on_diagram (diagram);
 }
 
 /* Inform the user that an error occurred while trying to report some
@@ -1524,7 +1637,7 @@  void
 diagnostic_context::error_recursion ()
 {
   if (m_lock < 3)
-    pp_newline_and_flush (m_printer);
+    pp_newline_and_flush (m_reference_printer);
 
   fnotice (stderr,
 	   "internal compiler error: error reporting routines re-entered.\n");
@@ -1554,7 +1667,7 @@  fancy_abort (const char *file, int line, const char *function)
      initialized yet, or might be in use by another thread).
      Handle such cases as gracefully as possible by falling back to a
      minimal abort handler that only relies on i18n.  */
-  if (global_dc->m_printer == nullptr)
+  if (global_dc->get_reference_printer () == nullptr)
     {
       /* Print the error message.  */
       fnotice (stderr, diagnostic_kind_text[DK_ICE]);
@@ -1597,15 +1710,23 @@  diagnostic_context::end_group ()
 	 If any diagnostics were emitted, give the context a chance
 	 to do something.  */
       if (m_diagnostic_groups.m_emission_count > 0)
-	m_output_format->on_end_group ();
+	for (auto sink : m_output_sinks)
+	  sink->on_end_group ();
       m_diagnostic_groups.m_emission_count = 0;
     }
 }
 
 void
-diagnostic_output_format::dump (FILE *, int) const
+diagnostic_output_format::dump (FILE *out, int indent) const
+{
+  fprintf (out, "%*sprinter:\n", indent, "");
+  m_printer->dump (out, indent + 2);
+}
+
+void
+diagnostic_output_format::on_report_verbatim (text_info &)
 {
-  /* No-op for now.  */
+  /* No-op.  */
 }
 
 /* Set the output format for CONTEXT to FORMAT, using BASE_FILE_NAME for
@@ -1653,15 +1774,6 @@  diagnostic_output_format_init (diagnostic_context &context,
 						sarif_version::v2_1_0,
 						base_file_name);
       break;
-    case DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE_2_2_PRERELEASE:
-      diagnostic_output_format_init_sarif_file
-	(context,
-	 line_table,
-	 main_input_filename_,
-	 json_formatting,
-	 sarif_version::v2_2_prerelease_2024_08_08,
-	 base_file_name);
-      break;
     }
 }
 
@@ -1713,15 +1825,22 @@  diagnostic_context::set_diagnostic_buffer (diagnostic_buffer *buffer)
 
   m_diagnostic_buffer = buffer;
 
-  gcc_assert (m_output_format);
   if (buffer)
     {
-      buffer->ensure_per_format_buffer ();
-      gcc_assert (buffer->m_per_format_buffer);
-      m_output_format->set_buffer (buffer->m_per_format_buffer.get ());
+      buffer->ensure_per_format_buffers ();
+      gcc_assert (buffer->m_per_format_buffers);
+      gcc_assert (buffer->m_per_format_buffers->length ()
+		  == m_output_sinks.length ());
+      for (unsigned idx = 0; idx < m_output_sinks.length (); ++idx)
+	{
+	  auto sink = m_output_sinks[idx];
+	  auto per_format_buffer = (*buffer->m_per_format_buffers)[idx];
+	  sink->set_buffer (per_format_buffer);
+	}
     }
   else
-    m_output_format->set_buffer (nullptr);
+    for (auto sink : m_output_sinks)
+      sink->set_buffer (nullptr);
 }
 
 /* Clear BUFFER without flushing it.  */
@@ -1729,8 +1848,10 @@  diagnostic_context::set_diagnostic_buffer (diagnostic_buffer *buffer)
 void
 diagnostic_context::clear_diagnostic_buffer (diagnostic_buffer &buffer)
 {
-  if (buffer.m_per_format_buffer)
-    buffer.m_per_format_buffer->clear ();
+  if (buffer.m_per_format_buffers)
+    for (auto per_format_buffer : *buffer.m_per_format_buffers)
+      per_format_buffer->clear ();
+
   buffer.m_diagnostic_counters.clear ();
 
   /* We need to reset last_location, otherwise we may skip caret lines
@@ -1746,8 +1867,9 @@  diagnostic_context::flush_diagnostic_buffer (diagnostic_buffer &buffer)
   bool had_errors
     = (buffer.m_diagnostic_counters.m_count_for_kind[DK_ERROR] > 0
        || buffer.m_diagnostic_counters.m_count_for_kind[DK_WERROR] > 0);
-  if (buffer.m_per_format_buffer)
-    buffer.m_per_format_buffer->flush ();
+  if (buffer.m_per_format_buffers)
+    for (auto per_format_buffer : *buffer.m_per_format_buffers)
+      per_format_buffer->flush ();
   buffer.m_diagnostic_counters.move_to (m_diagnostic_counters);
 
   action_after_output (had_errors ? DK_ERROR : DK_WARNING);
@@ -1796,17 +1918,29 @@  diagnostic_counters::clear ()
 /* class diagnostic_buffer.  */
 
 diagnostic_buffer::diagnostic_buffer (diagnostic_context &ctxt)
-: m_ctxt (ctxt)
+: m_ctxt (ctxt),
+  m_per_format_buffers (nullptr)
 {
 }
 
+diagnostic_buffer::~diagnostic_buffer ()
+{
+  if (m_per_format_buffers)
+    {
+      for (auto iter : *m_per_format_buffers)
+	delete iter;
+      delete m_per_format_buffers;
+    }
+}
+
 void
 diagnostic_buffer::dump (FILE *out, int indent) const
 {
-  fprintf (out, "%*sm_per_format_buffer:\n", indent, "");
   m_diagnostic_counters.dump (out, indent + 2);
-  if (m_per_format_buffer)
-    m_per_format_buffer->dump (out, indent + 2);
+  fprintf (out, "%*sm_per_format_buffers:\n", indent, "");
+  if (m_per_format_buffers)
+    for (auto per_format_buffer : *m_per_format_buffers)
+      per_format_buffer->dump (out, indent + 2);
   else
     fprintf (out, "%*s(none)\n", indent + 2, "");
 }
@@ -1814,33 +1948,67 @@  diagnostic_buffer::dump (FILE *out, int indent) const
 bool
 diagnostic_buffer::empty_p () const
 {
-  if (m_per_format_buffer)
-    return m_per_format_buffer->empty_p ();
-  else
-    return true;
+  if (m_per_format_buffers)
+    for (auto per_format_buffer : *m_per_format_buffers)
+      /* Query initial buffer.  */
+      return per_format_buffer->empty_p ();
+  return true;
 }
 
 void
 diagnostic_buffer::move_to (diagnostic_buffer &dest)
 {
-  ensure_per_format_buffer ();
-  dest.ensure_per_format_buffer ();
-  m_per_format_buffer->move_to (*dest.m_per_format_buffer);
+  /* Bail if there's nothing to move.  */
+  if (!m_per_format_buffers)
+    return;
+
   m_diagnostic_counters.move_to (dest.m_diagnostic_counters);
+
+  if (!dest.m_per_format_buffers)
+    {
+      /* Optimization for the "move to empty" case:
+	 simply move the vec to the dest.  */
+      dest.m_per_format_buffers = m_per_format_buffers;
+      m_per_format_buffers = nullptr;
+      return;
+    }
+
+  dest.ensure_per_format_buffers ();
+  gcc_assert (m_per_format_buffers);
+  gcc_assert (m_per_format_buffers->length ()
+	      == m_ctxt.m_output_sinks.length ());
+  gcc_assert (dest.m_per_format_buffers);
+  gcc_assert (dest.m_per_format_buffers->length ()
+	      == m_ctxt.m_output_sinks.length ());
+  for (unsigned idx = 0; idx < m_ctxt.m_output_sinks.length (); ++idx)
+    {
+      auto per_format_buffer_src = (*m_per_format_buffers)[idx];
+      auto per_format_buffer_dest = (*dest.m_per_format_buffers)[idx];
+      per_format_buffer_src->move_to (*per_format_buffer_dest);
+    }
 }
 
-/* Lazily get output format to create its own kind of buffer.  */
+/* Lazily get the output formats to create their own kind of buffers.
+   We can't change the output sinks on a context once this has been called
+   on any diagnostic_buffer instances for that context, since there's no
+   way to update all diagnostic_buffer instances for that context.  */
 
 void
-diagnostic_buffer::ensure_per_format_buffer ()
+diagnostic_buffer::ensure_per_format_buffers ()
 {
-  if (!m_per_format_buffer)
+  if (!m_per_format_buffers)
     {
-      gcc_assert (m_ctxt.get_output_format ());
-      m_per_format_buffer
-	= m_ctxt.get_output_format ()->make_per_format_buffer ();
+      m_per_format_buffers = new auto_vec<diagnostic_per_format_buffer *> ();
+      for (unsigned idx = 0; idx < m_ctxt.m_output_sinks.length (); ++idx)
+	{
+	  auto sink = m_ctxt.m_output_sinks[idx];
+	  auto per_format_buffer = sink->make_per_format_buffer ();
+	  m_per_format_buffers->safe_push (per_format_buffer.release ());
+	}
     }
-  gcc_assert (m_per_format_buffer);
+  gcc_assert (m_per_format_buffers);
+  gcc_assert (m_per_format_buffers->length ()
+	      == m_ctxt.m_output_sinks.length ());
 }
 
 /* Really call the system 'abort'.  This has to go right at the end of
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 3da1ae9c8019..46478c9a52b7 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -84,11 +84,7 @@  enum diagnostics_output_format
   DIAGNOSTICS_OUTPUT_FORMAT_SARIF_STDERR,
 
   /* SARIF-based output, to a file.  */
-  DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE,
-
-  /* Undocumented, for use by test suite.
-     SARIF-based output, to a file, using a prerelease of the 2.2 schema.  */
-  DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE_2_2_PRERELEASE
+  DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE
 };
 
 /* An enum for controlling how diagnostic_paths should be printed.  */
@@ -494,6 +490,7 @@  public:
 
   friend class diagnostic_source_print_policy;
   friend class diagnostic_text_output_format;
+  friend class diagnostic_buffer;
 
   typedef void (*set_locations_callback_t) (diagnostic_context *,
 					    diagnostic_info *);
@@ -501,6 +498,8 @@  public:
   void initialize (int n_opts);
   void color_init (int value);
   void urls_init (int value);
+  void set_pretty_printer (std::unique_ptr<pretty_printer> pp);
+  void refresh_output_sinks ();
 
   void finish ();
 
@@ -548,6 +547,7 @@  public:
     ATTRIBUTE_GCC_DIAG(6,0);
 
   bool report_diagnostic (diagnostic_info *);
+  void report_verbatim (text_info &);
 
   diagnostic_t
   classify_diagnostic (diagnostic_option_id option_id,
@@ -576,11 +576,6 @@  public:
 
   void emit_diagram (const diagnostic_diagram &diagram);
 
-  diagnostic_output_format *get_output_format () const
-  {
-    return m_output_format;
-  }
-
   /* Various setters for use by option-handling logic.  */
   void set_output_format (std::unique_ptr<diagnostic_output_format> output_format);
   void set_text_art_charset (enum diagnostic_text_art_charset charset);
@@ -598,10 +593,7 @@  public:
   }
   void set_show_cwe (bool val) { m_show_cwe = val;  }
   void set_show_rules (bool val) { m_show_rules = val; }
-  void set_show_highlight_colors (bool val)
-  {
-    pp_show_highlight_colors (m_printer) = val;
-  }
+  void set_show_highlight_colors (bool val);
   void set_path_format (enum diagnostic_path_format val)
   {
     m_path_format = val;
@@ -614,12 +606,16 @@  public:
     m_escape_format = val;
   }
 
+  void set_format_decoder (printer_fn format_decoder);
+  void set_prefixing_rule (diagnostic_prefixing_rule_t rule);
+
   /* Various accessors.  */
   bool warning_as_error_requested_p () const
   {
     return m_warning_as_error_requested;
   }
   bool show_path_depths_p () const { return m_show_path_depths; }
+  diagnostic_output_format &get_output_format (size_t idx) const;
   enum diagnostic_path_format get_path_format () const { return m_path_format; }
   enum diagnostics_escape_format get_escape_format () const
   {
@@ -719,9 +715,19 @@  public:
 
   std::unique_ptr<pretty_printer> clone_printer () const
   {
-    return m_printer->clone ();
+    return m_reference_printer->clone ();
   }
 
+  pretty_printer *get_reference_printer () const
+  {
+    return m_reference_printer;
+  }
+
+  void
+  add_sink (std::unique_ptr<diagnostic_output_format>);
+
+  bool supports_fnotice_on_stderr_p () const;
+
 private:
   void error_recursion () ATTRIBUTE_NORETURN;
 
@@ -735,13 +741,14 @@  private:
   /* Data members.
      Ideally, all of these would be private.  */
 
-public:
-  /* Where most of the diagnostic formatting work is done.
+private:
+  /* A reference instance of pretty_printer created by the client
+     and owned by the context.  Used for cloning when creating/adding
+     output formats.
      Owned by the context; this would be a std::unique_ptr if
      diagnostic_context had a proper ctor.  */
-  pretty_printer *m_printer;
+  pretty_printer *m_reference_printer;
 
-private:
   /* Cache of source code.
      Owned by the context; this would be a std::unique_ptr if
      diagnostic_context had a proper ctor.  */
@@ -902,11 +909,12 @@  private:
     int m_emission_count;
   } m_diagnostic_groups;
 
-  /* How to output diagnostics (text vs a structured format such as JSON).
-     Must be non-NULL; owned by context.
-     This would be a std::unique_ptr if diagnostic_context had a proper
-     ctor.  */
-  diagnostic_output_format *m_output_format;
+  /* The various sinks to which diagnostics are to be outputted
+     (text vs structured formats such as SARIF).
+     The sinks are owned by the context; this would be a
+     std::vector<std::unique_ptr> if diagnostic_context had a
+     proper ctor.  */
+  auto_vec<diagnostic_output_format *> m_output_sinks;
 
   /* Callback to set the locations of call sites along the inlining
      stack corresponding to a diagnostic location.  Needed to traverse
@@ -982,12 +990,6 @@  diagnostic_text_finalizer (diagnostic_context *context)
 #define diagnostic_context_auxiliary_data(DC) (DC)->m_client_aux_data
 #define diagnostic_info_auxiliary_data(DI) (DI)->x_data
 
-/* Same as pp_format_decoder.  Works on 'diagnostic_context *'.  */
-#define diagnostic_format_decoder(DC) pp_format_decoder ((DC)->m_printer)
-
-/* Same as pp_prefixing_rule.  Works on 'diagnostic_context *'.  */
-#define diagnostic_prefixing_rule(DC) pp_prefixing_rule ((DC)->m_printer)
-
 /* Raise SIGABRT on any diagnostic of severity DK_ERROR or higher.  */
 inline void
 diagnostic_abort_on_error (diagnostic_context *context)
@@ -1000,14 +1002,6 @@  diagnostic_abort_on_error (diagnostic_context *context)
    and similar functions.  */
 extern diagnostic_context *global_dc;
 
-/* Returns whether the diagnostic framework has been intialized already and is
-   ready for use.  */
-inline bool
-diagnostic_ready_p ()
-{
-  return global_dc->m_printer != nullptr;
-}
-
 /* The number of errors that have been issued so far.  Ideally, these
    would take a diagnostic_context as an argument.  */
 #define errorcount global_dc->diagnostic_count (DK_ERROR)
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index ae52cc68c511..07920e07b4d1 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -307,6 +307,8 @@  Objective-C and Objective-C++ Dialects}.
 -fdiagnostics-color=@r{[}auto@r{|}never@r{|}always@r{]}
 -fdiagnostics-urls=@r{[}auto@r{|}never@r{|}always@r{]}
 -fdiagnostics-format=@r{[}text@r{|}sarif-stderr@r{|}sarif-file@r{|}json@r{|}json-stderr@r{|}json-file@r{]}
+-fdiagnostics-add-output=@var{DIAGNOSTICS-OUTPUT-SPEC}
+-fdiagnostics-set-output=@var{DIAGNOSTICS-OUTPUT-SPEC}
 -fno-diagnostics-json-formatting
 -fno-diagnostics-show-option  -fno-diagnostics-show-caret
 -fno-diagnostics-show-event-links
@@ -5902,6 +5904,10 @@  Select a different format for printing diagnostics.
 @var{FORMAT} is @samp{text}, @samp{sarif-stderr}, @samp{sarif-file},
 @samp{json}, @samp{json-stderr}, or @samp{json-file}.
 
+Using this option replaces any additional ``output sinks'' added by
+@option{-fdiagnostics-add-output=}, or that set by
+@option{-fdiagnostics-set-output=}.
+
 The default is @samp{text}.
 
 The @samp{sarif-stderr} and @samp{sarif-file} formats both emit
@@ -5916,6 +5922,97 @@  where the JSON is emitted to.  With @samp{json-stderr}, the JSON is emitted
 to stderr, whereas with @samp{json-file} it is written to
 @file{@var{source}.gcc.json}.
 
+@opindex fdiagnostics-add-output
+@item -fdiagnostics-add-output=@var{DIAGNOSTICS-OUTPUT-SPEC}
+Add an additional ``output sink'' for emitting diagnostics.
+
+@var{DIAGNOSTICS-OUTPUT-SPEC} should specify a scheme, optionally followed
+by @code{:} and one or more @var{KEY}=@var{VALUE} pairs, in this form:
+
+@smallexample
+@var{SCHEME}
+@var{SCHEME}:@var{KEY}=@var{VALUE}
+@var{SCHEME}:@var{KEY}=@var{VALUE},@var{KEY2}=@var{VALUE2}
+@end smallexample
+
+etc.
+
+@var{SCHEME} can be
+
+@table @gcctabopt
+
+@item text
+Emit diagnostics to stderr using GCC's classic text output format.
+
+Supported keys are:
+
+@table @gcctabopt
+
+@item color=@r{[}yes@r{|}no@r{]}
+Override colorization settings from @option{-fdiagnostics-color} for this
+text output.
+
+@end table
+
+@item sarif
+Emit diagnostics to a file in SARIF format.
+
+Supported keys are:
+
+@table @gcctabopt
+
+@item file=@var{FILENAME}
+Specify the filename to write the SARIF output to, potentially with a
+leading absolute or relative path.  If not specified, it defaults to
+@file{@var{source}.sarif}.
+
+@item version=@r{[}2.1@r{|}2.2-prerelease@r{]}
+Specify the version of SARIF to use for the output.  If not specified,
+defaults to 2.1.  @code{2.2-prerelease} uses an unofficial draft of the
+future SARIF 2.2 specification and should only be used for experimentation
+in this release.
+
+@end table
+
+@end table
+
+For example,
+
+@smallexample
+-fdiagnostics-add-output=sarif:version=2.1,file=foo.2.1.sarif
+-fdiagnostics-add-output=sarif:version=2.2-prerelease,file=foo.2.2.sarif
+@end smallexample
+
+would add a pair of outputs, each writing to a different file, using
+versions 2.1 and 2.2 of the SARIF standard respectively.
+
+In EBNF:
+
+@smallexample
+
+@var{diagnostics-output-specifier} = @var{diagnostics-output-name}
+                                   | @var{diagnostics-output-name}, ":", @var{key-value-pairs};
+
+@var{diagnostics-output-name} = "text" | "sarif";
+
+@var{key-value-pairs} = @var{key-value-pair}
+                      | @var{key-value-pair} "," @var{key-value-pairs};
+
+@var{key-value-pair} = @var{key} "=" @var{value};
+
+@var{key} = ? string without a '=' ? ;
+@var{value} = ? string without a ',' ? ;
+
+@end smallexample
+
+@opindex fdiagnostics-set-output
+@item -fdiagnostics-set-output=@var{DIAGNOSTICS-OUTPUT-SPEC}
+This works in a similar way to @option{-fdiagnostics-add-output=} except
+that instead of adding an additional ``output sink'' for diagnostics, it
+replaces all existing output sinks, such as from @option{-fdiagnostics-format=},
+@option{-fdiagnostics-add-output=}, or a prior call to
+@option{-fdiagnostics-set-output=}.
+
 @opindex fno-diagnostics-json-formatting
 @opindex fdiagnostics-json-formatting
 @item -fno-diagnostics-json-formatting
diff --git a/gcc/fortran/error.cc b/gcc/fortran/error.cc
index b1eda94ccb68..050a8f286efd 100644
--- a/gcc/fortran/error.cc
+++ b/gcc/fortran/error.cc
@@ -479,7 +479,7 @@  gfc_diagnostic_build_kind_prefix (diagnostic_context *context,
   gcc_assert (diagnostic->kind < DK_LAST_DIAGNOSTIC_KIND);
   const char *text = _(diagnostic_kind_text[diagnostic->kind]);
   const char *text_cs = "", *text_ce = "";
-  pretty_printer *const pp = context->m_printer;
+  pretty_printer *const pp = context->get_reference_printer ();
 
   if (diagnostic_kind_color[diagnostic->kind])
     {
@@ -951,7 +951,7 @@  gfc_diagnostics_init (void)
   diagnostic_text_starter (global_dc) = gfc_diagnostic_text_starter;
   diagnostic_start_span (global_dc) = gfc_diagnostic_start_span;
   diagnostic_text_finalizer (global_dc) = gfc_diagnostic_text_finalizer;
-  diagnostic_format_decoder (global_dc) = gfc_format_decoder;
+  global_dc->set_format_decoder (gfc_format_decoder);
   global_dc->m_source_printing.caret_chars[0] = '1';
   global_dc->m_source_printing.caret_chars[1] = '2';
   pp_warning_buffer = new diagnostic_buffer (*global_dc);
diff --git a/gcc/gcc.cc b/gcc/gcc.cc
index 30f89a62f4f4..5fe5fd86a98e 100644
--- a/gcc/gcc.cc
+++ b/gcc/gcc.cc
@@ -50,6 +50,7 @@  compilation is specified by a string called a "spec".  */
 #include "opts-jobserver.h"
 #include "common/common-target.h"
 #include "gcc-urlifier.h"
+#include "opts-diagnostic.h"
 
 #ifndef MATH_LIBRARY
 #define MATH_LIBRARY "m"
@@ -4369,6 +4370,14 @@  driver_handle_option (struct gcc_options *opts,
 	  break;
 	}
 
+    case OPT_fdiagnostics_add_output_:
+      handle_OPT_fdiagnostics_add_output_ (*opts, *dc, arg, loc);
+      break;
+
+    case OPT_fdiagnostics_set_output_:
+      handle_OPT_fdiagnostics_set_output_ (*opts, *dc, arg, loc);
+      break;
+
     case OPT_fdiagnostics_text_art_charset_:
       dc->set_text_art_charset ((enum diagnostic_text_art_charset)value);
       break;
diff --git a/gcc/jit/dummy-frontend.cc b/gcc/jit/dummy-frontend.cc
index 12167829b277..35475b5ad05d 100644
--- a/gcc/jit/dummy-frontend.cc
+++ b/gcc/jit/dummy-frontend.cc
@@ -33,6 +33,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "cgraph.h"
 #include "target.h"
 #include "diagnostic-format-text.h"
+#include "make-unique.h"
 
 #include <mpfr.h>
 
@@ -981,6 +982,49 @@  struct ggc_root_tab jit_root_tab[] =
     LAST_GGC_ROOT_TAB
   };
 
+/* Subclass of diagnostic_output_format for libgccjit: like text
+   output, but capture the message and call add_diagnostic with it
+   on the active playback context.  */
+
+class jit_diagnostic_listener : public diagnostic_text_output_format
+{
+public:
+  jit_diagnostic_listener (diagnostic_context &dc,
+			   gcc::jit::playback::context &playback_ctxt)
+  : diagnostic_text_output_format (dc),
+    m_playback_ctxt (playback_ctxt)
+  {
+  }
+
+  void dump (FILE *out, int indent) const final override
+  {
+    fprintf (out, "%*sjit_diagnostic_listener\n", indent, "");
+    fprintf (out, "%*sm_playback_context: %p\n",
+	     indent + 2, "",
+	     (void *)&m_playback_ctxt);
+  }
+
+  void on_report_diagnostic (const diagnostic_info &info,
+			     diagnostic_t orig_diag_kind)
+  {
+    JIT_LOG_SCOPE (gcc::jit::active_playback_ctxt->get_logger ());
+
+    /* Let the text output format do most of the work.  */
+    diagnostic_text_output_format::on_report_diagnostic (info, orig_diag_kind);
+
+    const char *text = pp_formatted_text (get_printer ());
+
+    /* Delegate to the playback context (and thence to the
+       recording context).  */
+    gcc::jit::active_playback_ctxt->add_diagnostic (text, info);
+
+    pp_clear_output_area (get_printer ());
+  }
+
+private:
+  gcc::jit::playback::context &m_playback_ctxt;
+};
+
 /* JIT-specific implementation of diagnostic callbacks.  */
 
 /* Implementation of "begin_diagnostic".  */
@@ -992,24 +1036,22 @@  jit_begin_diagnostic (diagnostic_text_output_format &,
   gcc_assert (gcc::jit::active_playback_ctxt);
   JIT_LOG_SCOPE (gcc::jit::active_playback_ctxt->get_logger ());
 
-  /* No-op (apart from logging); the real error-handling is done in the
-     "end_diagnostic" hook.  */
+  /* No-op (apart from logging); the real error-handling is done by the
+     jit_diagnostic_listener.  */
 }
 
 /* Implementation of "end_diagnostic".  */
 
 static void
-jit_end_diagnostic (diagnostic_text_output_format &text_output,
-		    const diagnostic_info *diagnostic,
+jit_end_diagnostic (diagnostic_text_output_format &,
+		    const diagnostic_info *,
 		    diagnostic_t)
 {
   gcc_assert (gcc::jit::active_playback_ctxt);
   JIT_LOG_SCOPE (gcc::jit::active_playback_ctxt->get_logger ());
 
-  /* Delegate to the playback context (and thence to the
-     recording context).  */
-  gcc_assert (diagnostic);
-  gcc::jit::active_playback_ctxt->add_diagnostic (&text_output.get_context (), *diagnostic); // FIXME
+  /* No-op (apart from logging); the real error-handling is done by the
+     jit_diagnostic_listener.  */
 }
 
 /* Language hooks.  */
@@ -1030,6 +1072,10 @@  jit_langhook_init (void)
   gcc_assert (global_dc);
   diagnostic_text_starter (global_dc) = jit_begin_diagnostic;
   diagnostic_text_finalizer (global_dc) = jit_end_diagnostic;
+  auto sink
+    = ::make_unique<jit_diagnostic_listener> (*global_dc,
+					      *gcc::jit::active_playback_ctxt);
+  global_dc->set_output_format (std::move (sink));
 
   build_common_tree_nodes (false);
 
diff --git a/gcc/jit/jit-playback.cc b/gcc/jit/jit-playback.cc
index e732c2287518..8dd77e4d5871 100644
--- a/gcc/jit/jit-playback.cc
+++ b/gcc/jit/jit-playback.cc
@@ -3687,14 +3687,9 @@  add_error_va (location *loc, const char *fmt, va_list ap)
 
 void
 playback::context::
-add_diagnostic (diagnostic_context *diag_context,
+add_diagnostic (const char *text,
 		const diagnostic_info &diagnostic)
 {
-  /* At this point the text has been formatted into the pretty-printer's
-     output buffer.  */
-  pretty_printer *pp = diag_context->m_printer;
-  const char *text = pp_formatted_text (pp);
-
   /* Get location information (if any) from the diagnostic.
      The recording::context::add_error[_va] methods require a
      recording::location.  We can't lookup the playback::location
@@ -3714,7 +3709,6 @@  add_diagnostic (diagnostic_context *diag_context,
     }
 
   m_recording_ctxt->add_error (rec_loc, "%s", text);
-  pp_clear_output_area (pp);
 }
 
 /* Dealing with the linemap API.  */
diff --git a/gcc/jit/jit-playback.h b/gcc/jit/jit-playback.h
index 6e97b389cbb0..77ae2add4790 100644
--- a/gcc/jit/jit-playback.h
+++ b/gcc/jit/jit-playback.h
@@ -276,7 +276,7 @@  public:
   get_first_error () const;
 
   void
-  add_diagnostic (diagnostic_context *context,
+  add_diagnostic (const char *text,
 		  const diagnostic_info &diagnostic);
 
   void
diff --git a/gcc/opts-diagnostic.cc b/gcc/opts-diagnostic.cc
new file mode 100644
index 000000000000..8dd4234ab0f4
--- /dev/null
+++ b/gcc/opts-diagnostic.cc
@@ -0,0 +1,680 @@ 
+/* Support for -fdiagnostics-add-output= and -fdiagnostics-set-output=.
+   Copyright (C) 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/>.  */
+
+
+/* This file implements the options -fdiagnostics-add-output=,
+   -fdiagnostics-set-output=, and their domain-specific language.  */
+
+#include "config.h"
+#define INCLUDE_ARRAY
+#define INCLUDE_MEMORY
+#define INCLUDE_STRING
+#define INCLUDE_VECTOR
+#include "system.h"
+#include "coretypes.h"
+#include "version.h"
+#include "intl.h"
+#include "diagnostic.h"
+#include "diagnostic-color.h"
+#include "diagnostic-format.h"
+#include "diagnostic-format-text.h"
+#include "diagnostic-format-sarif.h"
+#include "selftest.h"
+#include "selftest-diagnostic.h"
+#include "pretty-print-markup.h"
+#include "opts.h"
+#include "options.h"
+#include "make-unique.h"
+
+/* A namespace for handling the DSL of the arguments of
+   -fdiagnostics-add-output= and -fdiagnostics-set-output=.  */
+
+namespace gcc {
+namespace diagnostics_output_spec {
+
+/* Decls.  */
+
+struct context
+{
+public:
+  context (const gcc_options &opts,
+	   diagnostic_context &dc,
+	   line_maps *location_mgr,
+	   location_t loc,
+	   const char *option_name)
+  : m_opts (opts), m_dc (dc), m_location_mgr (location_mgr), m_loc (loc),
+    m_option_name (option_name)
+  {}
+
+  void
+  report_error (const char *gmsgid, ...) const
+    ATTRIBUTE_GCC_DIAG(2,3);
+
+  void
+  report_unknown_key (const char *unparsed_arg,
+		      const std::string &key,
+		      const std::string &format_name,
+		      auto_vec<const char *> &known_keys) const;
+
+  void
+  report_missing_key (const char *unparsed_arg,
+		      const std::string &key,
+		      const std::string &format_name,
+		      const char *metavar) const;
+
+  diagnostic_output_file
+  open_output_file (label_text &&filename) const;
+
+  const gcc_options &m_opts;
+  diagnostic_context &m_dc;
+  line_maps *m_location_mgr;
+  location_t m_loc;
+  const char *m_option_name;
+};
+
+struct name_and_params
+{
+  std::string m_format;
+  std::vector<std::pair<std::string, std::string>> m_kvs;
+};
+
+static std::unique_ptr<name_and_params>
+parse (const context &ctxt, const char *unparsed_arg);
+
+/* Class for parsing the arguments of -fdiagnostics-add-output= and
+   -fdiagnostics-set-output=, and making diagnostic_output_format
+   instances (or issuing errors).  */
+
+class output_factory
+{
+public:
+  class handler
+  {
+  public:
+    handler (std::string name) : m_name (name) {}
+    virtual ~handler () {}
+
+    const std::string &get_name () const { return m_name; }
+
+    virtual std::unique_ptr<diagnostic_output_format>
+    make_sink (const context &ctxt,
+	       const char *unparsed_arg,
+	       const name_and_params &parsed_arg) const = 0;
+
+  protected:
+    bool
+    parse_bool_value (const context &ctxt,
+		      const char *unparsed_arg,
+		      const std::string &key,
+		      const std::string &value,
+		      bool &out) const
+    {
+      if (value == "yes")
+	{
+	  out = true;
+	  return true;
+	}
+      else if (value == "no")
+	{
+	  out = false;
+	  return true;
+	}
+      else
+	{
+	  ctxt.report_error
+	    ("%<%s%s%>:"
+	     " unexpected value %qs for key %qs; expected %qs or %qs",
+	     ctxt.m_option_name, unparsed_arg,
+	     value.c_str (),
+	     key.c_str (),
+	     "yes", "no");
+
+	  return false;
+	}
+    }
+    template <typename EnumType, size_t NumValues>
+    bool
+    parse_enum_value (const context &ctxt,
+		      const char *unparsed_arg,
+		      const std::string &key,
+		      const std::string &value,
+		      const std::array<std::pair<const char *, EnumType>, NumValues> &value_names,
+		      EnumType &out) const
+    {
+      for (auto &iter : value_names)
+	if (value == iter.first)
+	  {
+	    out = iter.second;
+	    return true;
+	  }
+
+      auto_vec<const char *> known_values;
+      for (auto iter : value_names)
+	known_values.safe_push (iter.first);
+      pp_markup::comma_separated_quoted_strings e (known_values);
+      ctxt.report_error
+	("%<%s%s%>:"
+	 " unexpected value %qs for key %qs; known values: %e",
+	 ctxt.m_option_name, unparsed_arg,
+	 value.c_str (),
+	 key.c_str (),
+	 &e);
+      return false;
+    }
+
+  private:
+    const std::string m_name;
+  };
+
+  output_factory ();
+
+  std::unique_ptr<diagnostic_output_format>
+  make_sink (const context &ctxt,
+	     const char *unparsed_arg,
+	     const name_and_params &parsed_arg);
+
+  const handler *get_handler (std::string name);
+
+private:
+  std::vector<std::unique_ptr<handler>> m_handlers;
+};
+
+class text_handler : public output_factory::handler
+{
+public:
+  text_handler () : handler ("text") {}
+
+  std::unique_ptr<diagnostic_output_format>
+  make_sink (const context &ctxt,
+	     const char *unparsed_arg,
+	     const name_and_params &parsed_arg) const final override;
+};
+
+class sarif_handler : public output_factory::handler
+{
+public:
+  sarif_handler () : handler ("sarif") {}
+
+  std::unique_ptr<diagnostic_output_format>
+  make_sink (const context &ctxt,
+	     const char *unparsed_arg,
+	     const name_and_params &parsed_arg) const final override;
+};
+
+/* struct context.  */
+
+void
+context::report_error (const char *gmsgid, ...) const
+{
+  m_dc.begin_group ();
+  va_list ap;
+  va_start (ap, gmsgid);
+  rich_location richloc (m_location_mgr, m_loc);
+  m_dc.diagnostic_impl (&richloc, nullptr, -1, gmsgid, &ap, DK_ERROR);
+  va_end (ap);
+  m_dc.end_group ();
+}
+
+void
+context::report_unknown_key (const char *unparsed_arg,
+			     const std::string &key,
+			     const std::string &format_name,
+			     auto_vec<const char *> &known_keys) const
+{
+  pp_markup::comma_separated_quoted_strings e (known_keys);
+  report_error
+    ("%<%s%s%>:"
+     " unknown key %qs for format %qs; known keys: %e",
+     m_option_name, unparsed_arg,
+     key.c_str (), format_name.c_str (), &e);
+}
+
+void
+context::report_missing_key (const char *unparsed_arg,
+			     const std::string &key,
+			     const std::string &format_name,
+			     const char *metavar) const
+{
+  report_error
+    ("%<%s%s%>:"
+     " missing required key %qs for format %qs;"
+     " try %<%s%s:%s=%s%>",
+     m_option_name, unparsed_arg,
+     key.c_str (), format_name.c_str (),
+     m_option_name, format_name.c_str (), key.c_str (), metavar);
+}
+
+std::unique_ptr<name_and_params>
+parse (const context &ctxt, const char *unparsed_arg)
+{
+  name_and_params result;
+  if (const char *const colon = strchr (unparsed_arg, ':'))
+    {
+      result.m_format = std::string (unparsed_arg, colon - unparsed_arg);
+      /* Expect zero of more of KEY=VALUE,KEY=VALUE, etc  .*/
+      const char *iter = colon + 1;
+      const char *last_separator = ":";
+      while (iter)
+	{
+	  /* Look for a non-empty key string followed by '='.  */
+	  const char *eq = strchr (iter, '=');
+	  if (eq == nullptr || eq == iter)
+	    {
+	      /* Missing '='.  */
+	      ctxt.report_error
+		("%<%s%s%>:"
+		 " expected KEY=VALUE-style parameter for format %qs"
+		 " after %qs;"
+		 " got %qs",
+		 ctxt.m_option_name, unparsed_arg,
+		 result.m_format.c_str (),
+		 last_separator,
+		 iter);
+	      return nullptr;
+	    }
+	  std::string key = std::string (iter, eq - iter);
+	  std::string value;
+	  const char *comma = strchr (iter, ',');
+	  if (comma)
+	    {
+	      value = std::string (eq + 1, comma - (eq + 1));
+	      iter = comma + 1;
+	      last_separator = ",";
+	    }
+	  else
+	    {
+	      value = std::string (eq + 1);
+	      iter = nullptr;
+	    }
+	  result.m_kvs.push_back ({std::move (key), std::move (value)});
+	}
+    }
+  else
+    result.m_format = unparsed_arg;
+  return ::make_unique<name_and_params> (std::move (result));
+}
+
+/* class output_factory::handler.  */
+
+/* class output_factory.  */
+
+output_factory::output_factory ()
+{
+  m_handlers.push_back (::make_unique<text_handler> ());
+  m_handlers.push_back (::make_unique<sarif_handler> ());
+}
+
+const output_factory::handler *
+output_factory::get_handler (std::string name)
+{
+  for (auto &iter : m_handlers)
+    if (iter->get_name () == name)
+      return iter.get ();
+  return nullptr;
+}
+
+std::unique_ptr<diagnostic_output_format>
+output_factory::make_sink (const context &ctxt,
+			   const char *unparsed_arg,
+			   const name_and_params &parsed_arg)
+{
+  auto handler = get_handler (parsed_arg.m_format);
+  if (!handler)
+    {
+      auto_vec<const char *> strings;
+      for (auto &iter : m_handlers)
+	strings.safe_push (iter->get_name ().c_str ());
+      pp_markup::comma_separated_quoted_strings e (strings);
+      ctxt.report_error ("%<%s%s%>:"
+			 " unrecognized format %qs; known formats: %e",
+			 ctxt.m_option_name, unparsed_arg,
+			 parsed_arg.m_format.c_str (), &e);
+      return nullptr;
+    }
+
+  return handler->make_sink (ctxt, unparsed_arg, parsed_arg);
+}
+
+/* class text_handler : public output_factory::handler.  */
+
+std::unique_ptr<diagnostic_output_format>
+text_handler::make_sink (const context &ctxt,
+			 const char *unparsed_arg,
+			 const name_and_params &parsed_arg) const
+{
+  bool show_color = pp_show_color (ctxt.m_dc.get_reference_printer ());
+  for (auto& iter : parsed_arg.m_kvs)
+    {
+      const std::string &key = iter.first;
+      const std::string &value = iter.second;
+      if (key == "color")
+	{
+	  if (!parse_bool_value (ctxt, unparsed_arg, key, value, show_color))
+	    return nullptr;
+	  continue;
+	}
+
+      /* Key not found.  */
+      auto_vec<const char *> known_keys;
+      known_keys.safe_push ("color");
+      ctxt.report_unknown_key (unparsed_arg, key, get_name (), known_keys);
+      return nullptr;
+    }
+
+  std::unique_ptr<diagnostic_output_format> sink;
+  sink = ::make_unique<diagnostic_text_output_format> (ctxt.m_dc);
+  return sink;
+}
+
+diagnostic_output_file
+context::open_output_file (label_text &&filename) const
+{
+  FILE *outf = fopen (filename.get (), "w");
+  if (!outf)
+    {
+      rich_location richloc (m_location_mgr, m_loc);
+      m_dc.emit_diagnostic_with_group
+	(DK_ERROR, richloc, nullptr, 0,
+	 "unable to open %qs: %m", filename.get ());
+      return diagnostic_output_file (nullptr, false, std::move (filename));
+    }
+  return diagnostic_output_file (outf, true, std::move (filename));
+}
+
+/* class sarif_handler : public output_factory::handler.  */
+
+std::unique_ptr<diagnostic_output_format>
+sarif_handler::make_sink (const context &ctxt,
+			  const char *unparsed_arg,
+			  const name_and_params &parsed_arg) const
+{
+  enum sarif_version version = sarif_version::v2_1_0;
+  label_text filename;
+  for (auto& iter : parsed_arg.m_kvs)
+    {
+      const std::string &key = iter.first;
+      const std::string &value = iter.second;
+      if (key == "version")
+	{
+	  static const std::array<std::pair<const char *, enum sarif_version>,
+				  (size_t)sarif_version::num_versions> value_names
+	    {{{"2.1", sarif_version::v2_1_0},
+	      {"2.2-prerelease", sarif_version::v2_2_prerelease_2024_08_08}}};
+
+	    if (!parse_enum_value<enum sarif_version> (ctxt, unparsed_arg,
+						       key, value,
+						       value_names,
+						       version))
+	    return nullptr;
+	  continue;
+	}
+      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");
+      known_keys.safe_push ("version");
+      ctxt.report_unknown_key (unparsed_arg, key, get_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_sarif_file (ctxt.m_dc,
+							      line_table,
+							      basename);
+    }
+  if (!output_file)
+    return nullptr;
+
+  auto sink = make_sarif_sink (ctxt.m_dc,
+			       *line_table,
+			       ctxt.m_opts.x_main_input_filename,
+			       version,
+			       std::move (output_file));
+  return sink;
+}
+
+} // namespace diagnostics_output_spec
+} // namespace gcc
+
+void
+handle_OPT_fdiagnostics_add_output_ (const gcc_options &opts,
+				     diagnostic_context &dc,
+				     const char *arg,
+				     location_t loc)
+{
+  gcc_assert (arg);
+  gcc_assert (line_table);
+
+  const char *const option_name = "-fdiagnostics-add-output=";
+  gcc::diagnostics_output_spec::context ctxt (opts, dc, line_table, loc,
+					      option_name);
+  auto result = gcc::diagnostics_output_spec::parse (ctxt, arg);
+  if (!result)
+    return;
+
+  gcc::diagnostics_output_spec::output_factory factory;
+  auto sink = factory.make_sink (ctxt, arg, *result);
+  if (!sink)
+    return;
+
+  dc.add_sink (std::move (sink));
+}
+
+void
+handle_OPT_fdiagnostics_set_output_ (const gcc_options &opts,
+				     diagnostic_context &dc,
+				     const char *arg,
+				     location_t loc)
+{
+  gcc_assert (arg);
+  gcc_assert (line_table);
+
+  const char *const option_name = "-fdiagnostics-set-output=";
+  gcc::diagnostics_output_spec::context ctxt (opts, dc, line_table, loc,
+					      option_name);
+  auto result = gcc::diagnostics_output_spec::parse (ctxt, arg);
+  if (!result)
+    return;
+
+  gcc::diagnostics_output_spec::output_factory factory;
+  auto sink = factory.make_sink (ctxt, arg, *result);
+  if (!sink)
+    return;
+
+  dc.set_output_format (std::move (sink));
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* RAII class to temporarily override "progname" to the
+   string "PROGNAME".  */
+
+class auto_fix_progname
+{
+public:
+  auto_fix_progname ()
+  {
+    m_old_progname = progname;
+    progname = "PROGNAME";
+  }
+
+  ~auto_fix_progname ()
+  {
+    progname = m_old_progname;
+  }
+
+private:
+  const char *m_old_progname;
+};
+
+struct parser_test
+{
+  parser_test ()
+  : m_opts (),
+    m_dc (),
+    m_ctxt (m_opts, m_dc, line_table, UNKNOWN_LOCATION, "-fOPTION="),
+    m_fmt (m_dc.get_output_format (0))
+  {
+    pp_buffer (m_fmt.get_printer ())->m_flush_p = false;
+  }
+
+  std::unique_ptr<gcc::diagnostics_output_spec::name_and_params>
+  parse (const char *unparsed_arg)
+  {
+    return gcc::diagnostics_output_spec::parse (m_ctxt, unparsed_arg);
+  }
+
+  bool execution_failed_p () const
+  {
+    return m_dc.execution_failed_p ();
+  }
+
+  const char *
+  get_diagnostic_text () const
+  {
+    return pp_formatted_text (m_fmt.get_printer ());
+  }
+
+private:
+  const gcc_options m_opts;
+  test_diagnostic_context m_dc;
+  gcc::diagnostics_output_spec::context m_ctxt;
+  diagnostic_output_format &m_fmt;
+};
+
+/* Selftests.  */
+
+static void
+test_output_arg_parsing ()
+{
+  auto_fix_quotes fix_quotes;
+  auto_fix_progname fix_progname;
+
+  /* Minimal correct example.  */
+  {
+    parser_test pt;
+    auto result = pt.parse ("foo");
+    ASSERT_EQ (result->m_format, "foo");
+    ASSERT_EQ (result->m_kvs.size (), 0);
+    ASSERT_FALSE (pt.execution_failed_p ());
+  }
+
+  /* Stray trailing colon with no key/value pairs.  */
+  {
+    parser_test pt;
+    auto result = pt.parse ("foo:");
+    ASSERT_EQ (result, nullptr);
+    ASSERT_TRUE (pt.execution_failed_p ());
+    ASSERT_STREQ (pt.get_diagnostic_text (),
+		  "PROGNAME: error: `-fOPTION=foo:':"
+		  " expected KEY=VALUE-style parameter for format `foo'"
+		  " after `:';"
+		  " got `'\n");
+  }
+
+  /* No key before '='.  */
+  {
+    parser_test pt;
+    auto result = pt.parse ("foo:=");
+    ASSERT_EQ (result, nullptr);
+    ASSERT_TRUE (pt.execution_failed_p ());
+    ASSERT_STREQ (pt.get_diagnostic_text (),
+		  "PROGNAME: error: `-fOPTION=foo:=':"
+		  " expected KEY=VALUE-style parameter for format `foo'"
+		  " after `:';"
+		  " got `='\n");
+  }
+
+  /* No value for key.  */
+  {
+    parser_test pt;
+    auto result = pt.parse ("foo:key,");
+    ASSERT_EQ (result, nullptr);
+    ASSERT_TRUE (pt.execution_failed_p ());
+    ASSERT_STREQ (pt.get_diagnostic_text (),
+		  "PROGNAME: error: `-fOPTION=foo:key,':"
+		  " expected KEY=VALUE-style parameter for format `foo'"
+		  " after `:';"
+		  " got `key,'\n");
+  }
+
+  /* Correct example, with one key/value pair.  */
+  {
+    parser_test pt;
+    auto result = pt.parse ("foo:key=value");
+    ASSERT_EQ (result->m_format, "foo");
+    ASSERT_EQ (result->m_kvs.size (), 1);
+    ASSERT_EQ (result->m_kvs[0].first, "key");
+    ASSERT_EQ (result->m_kvs[0].second, "value");
+    ASSERT_FALSE (pt.execution_failed_p ());
+  }
+
+  /* Stray trailing comma.  */
+  {
+    parser_test pt;
+    auto result = pt.parse ("foo:key=value,");
+    ASSERT_EQ (result, nullptr);
+    ASSERT_TRUE (pt.execution_failed_p ());
+    ASSERT_STREQ (pt.get_diagnostic_text (),
+		  "PROGNAME: error: `-fOPTION=foo:key=value,':"
+		  " expected KEY=VALUE-style parameter for format `foo'"
+		  " after `,';"
+		  " got `'\n");
+  }
+
+  /* Correct example, with two key/value pairs.  */
+  {
+    parser_test pt;
+    auto result = pt.parse ("foo:color=red,shape=circle");
+    ASSERT_EQ (result->m_format, "foo");
+    ASSERT_EQ (result->m_kvs.size (), 2);
+    ASSERT_EQ (result->m_kvs[0].first, "color");
+    ASSERT_EQ (result->m_kvs[0].second, "red");
+    ASSERT_EQ (result->m_kvs[1].first, "shape");
+    ASSERT_EQ (result->m_kvs[1].second, "circle");
+    ASSERT_FALSE (pt.execution_failed_p ());
+  }
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+opts_diagnostic_cc_tests ()
+{
+  test_output_arg_parsing ();
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
diff --git a/gcc/opts-diagnostic.h b/gcc/opts-diagnostic.h
index 48cc21e31a48..95fc16bb3232 100644
--- a/gcc/opts-diagnostic.h
+++ b/gcc/opts-diagnostic.h
@@ -59,4 +59,15 @@  private:
   void *m_opts;
 };
 
+extern void
+handle_OPT_fdiagnostics_add_output_ (const gcc_options &opts,
+				     diagnostic_context &dc,
+				     const char *arg,
+				     location_t loc);
+
+extern void
+handle_OPT_fdiagnostics_set_output_ (const gcc_options &opts,
+				     diagnostic_context &dc,
+				     const char *arg,
+				     location_t loc);
 #endif
diff --git a/gcc/opts-global.cc b/gcc/opts-global.cc
index 9593ec8a8fde..cbf4018b1202 100644
--- a/gcc/opts-global.cc
+++ b/gcc/opts-global.cc
@@ -258,7 +258,7 @@  init_options_once (void)
   initial_lang_mask = lang_hooks.option_lang_mask ();
 
   const bool show_highlight_colors
-    = pp_show_highlight_colors (global_dc->m_printer);
+    = pp_show_highlight_colors (global_dc->get_reference_printer ());
 
   lang_hooks.initialize_diagnostics (global_dc);
   /* ??? Ideally, we should do this earlier and the FEs will override
@@ -269,6 +269,7 @@  init_options_once (void)
 
   diagnostic_color_init (global_dc);
   diagnostic_urls_init (global_dc);
+  global_dc->refresh_output_sinks ();
 }
 
 /* Decode command-line options to an array, like
diff --git a/gcc/opts.cc b/gcc/opts.cc
index e810e30961b9..24d83393a57e 100644
--- a/gcc/opts.cc
+++ b/gcc/opts.cc
@@ -2934,7 +2934,7 @@  common_handle_option (struct gcc_options *opts,
       break;
 
     case OPT_fdiagnostics_show_location_:
-      diagnostic_prefixing_rule (dc) = (diagnostic_prefixing_rule_t) value;
+      dc->set_prefixing_rule ((diagnostic_prefixing_rule_t) value);
       break;
 
     case OPT_fdiagnostics_show_caret:
@@ -2973,6 +2973,14 @@  common_handle_option (struct gcc_options *opts,
 	  break;
 	}
 
+    case OPT_fdiagnostics_add_output_:
+      handle_OPT_fdiagnostics_add_output_ (*opts, *dc, arg, loc);
+      break;
+
+    case OPT_fdiagnostics_set_output_:
+      handle_OPT_fdiagnostics_set_output_ (*opts, *dc, arg, loc);
+      break;
+
     case OPT_fdiagnostics_text_art_charset_:
       dc->set_text_art_charset ((enum diagnostic_text_art_charset)value);
       break;
@@ -3057,7 +3065,7 @@  common_handle_option (struct gcc_options *opts,
       break;
 
     case OPT_fmessage_length_:
-      pp_set_line_maximum_length (dc->m_printer, value);
+      pp_set_line_maximum_length (dc->get_reference_printer (), value);
       diagnostic_set_caret_max_width (dc, value);
       break;
 
diff --git a/gcc/selftest-diagnostic.cc b/gcc/selftest-diagnostic.cc
index a066fe20066b..a9118b55f18f 100644
--- a/gcc/selftest-diagnostic.cc
+++ b/gcc/selftest-diagnostic.cc
@@ -37,7 +37,7 @@  namespace selftest {
 test_diagnostic_context::test_diagnostic_context ()
 {
   diagnostic_initialize (this, 0);
-  pp_show_color (m_printer) = false;
+  pp_show_color (get_reference_printer ()) = false;
   m_source_printing.enabled = true;
   m_source_printing.show_labels_p = true;
   m_show_column = true;
@@ -86,10 +86,11 @@  test_diagnostic_context::report (diagnostic_t kind,
 const char *
 test_diagnostic_context::test_show_locus (rich_location &richloc)
 {
-  gcc_assert (m_printer);
+  pretty_printer *pp = get_reference_printer ();
+  gcc_assert (pp);
   diagnostic_source_print_policy source_policy (*this);
-  source_policy.print (*m_printer, richloc, DK_ERROR, nullptr);
-  return pp_formatted_text (m_printer);
+  source_policy.print (*pp, richloc, DK_ERROR, nullptr);
+  return pp_formatted_text (pp);
 }
 
 } // namespace selftest
diff --git a/gcc/selftest-run-tests.cc b/gcc/selftest-run-tests.cc
index d6c88f864ba7..6085e334b917 100644
--- a/gcc/selftest-run-tests.cc
+++ b/gcc/selftest-run-tests.cc
@@ -106,6 +106,7 @@  selftest::run_tests ()
   diagnostic_path_cc_tests ();
   simple_diagnostic_path_cc_tests ();
   attribs_cc_tests ();
+  opts_diagnostic_cc_tests ();
 
   /* This one relies on most of the above.  */
   function_tests_cc_tests ();
diff --git a/gcc/selftest.h b/gcc/selftest.h
index 5afc9399c619..5109128f7f30 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -241,6 +241,7 @@  extern void input_cc_tests ();
 extern void json_cc_tests ();
 extern void optinfo_emit_json_cc_tests ();
 extern void opts_cc_tests ();
+extern void opts_diagnostic_cc_tests ();
 extern void ordered_hash_map_tests_cc_tests ();
 extern void predict_cc_tests ();
 extern void pretty_print_cc_tests ();
diff --git a/gcc/simple-diagnostic-path.cc b/gcc/simple-diagnostic-path.cc
index 0592080f3ae4..6414cf2c82e8 100644
--- a/gcc/simple-diagnostic-path.cc
+++ b/gcc/simple-diagnostic-path.cc
@@ -226,7 +226,7 @@  simple_diagnostic_path_cc_tests ()
 {
   /* In a few places we use the global dc's printer to determine
      colorization so ensure this off during the tests.  */
-  pretty_printer *global_pp = global_dc->m_printer;
+  pretty_printer *global_pp = global_dc->get_reference_printer ();
   const bool saved_show_color = pp_show_color (global_pp);
   pp_show_color (global_pp) = false;
 
diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c b/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c
index 18153b0733c4..467af16c3d1e 100644
--- a/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c
+++ b/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c
@@ -529,7 +529,7 @@  dump_refcnt_info (const hash_map<const region *, int> &region_to_refcnt,
   region_model_manager *mgr = model->get_manager ();
   pretty_printer pp;
   pp_format_decoder (&pp) = default_tree_printer;
-  pp_show_color (&pp) = pp_show_color (global_dc->m_printer);
+  pp_show_color (&pp) = pp_show_color (global_dc->get_reference_printer ());
   pp.set_output_stream (stderr);
 
   for (const auto &region_refcnt : region_to_refcnt)
diff --git a/gcc/testsuite/gcc.dg/plugin/crash-test-ice-in-header-sarif-2.2.c b/gcc/testsuite/gcc.dg/plugin/crash-test-ice-in-header-sarif-2.2.c
index b5328060c394..1c4cfbe07239 100644
--- a/gcc/testsuite/gcc.dg/plugin/crash-test-ice-in-header-sarif-2.2.c
+++ b/gcc/testsuite/gcc.dg/plugin/crash-test-ice-in-header-sarif-2.2.c
@@ -1,7 +1,7 @@ 
 /* Test of an ICE triggered within a header file with SARIF 2.2  */
 
 /* { dg-do compile } */
-/* { dg-options "-fdiagnostics-format=sarif-file-2.2-prerelease" } */
+/* { dg-options "-fdiagnostics-set-output=sarif:version=2.2-prerelease" } */
 /* { dg-additional-options "-fno-report-bug" } */
 
 #include "crash-test-ice-in-header.h"  /* { dg-ice "" } */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
index f7952e7d1d12..954538f962bb 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c
@@ -147,7 +147,7 @@  example_1 ()
     {
       auto_diagnostic_group d;
       gcc_rich_location richloc (gimple_location (call_to_PyList_Append));
-      simple_diagnostic_path path (global_dc->m_printer);
+      simple_diagnostic_path path (global_dc->get_reference_printer ());
       diagnostic_event_id_t alloc_event_id
 	= path.add_event (gimple_location (call_to_PyList_New),
 			  example_a_fun->decl, 0,
@@ -335,7 +335,7 @@  example_2 ()
       auto_diagnostic_group d;
 
       gcc_rich_location richloc (call_to_free.m_loc);
-      test_diagnostic_path path (global_dc->m_printer);
+      test_diagnostic_path path (global_dc->get_reference_printer ());
       path.add_entry (entry_to_test, 0, "test");
       path.add_call (call_to_make_boxed_int, 0,
 		     entry_to_make_boxed_int, "make_boxed_int");
@@ -420,7 +420,7 @@  example_3 ()
       auto_diagnostic_group d;
 
       gcc_rich_location richloc (call_to_fprintf.m_loc);
-      test_diagnostic_path path (global_dc->m_printer);
+      test_diagnostic_path path (global_dc->get_reference_printer ());
       path.add_entry (entry_to_test, 1, "test");
       path.add_call (call_to_register_handler, 1,
 		     entry_to_register_handler, "register_handler");
@@ -495,7 +495,7 @@  example_4 ()
       auto_diagnostic_group d;
 
       gcc_rich_location richloc (call_to_acquire_lock_a_in_bar.m_loc);
-      test_diagnostic_path path (global_dc->m_printer);
+      test_diagnostic_path path (global_dc->get_reference_printer ());
       diagnostic_thread_id_t thread_1 = path.add_thread ("Thread 1");
       diagnostic_thread_id_t thread_2 = path.add_thread ("Thread 2");
       path.add_entry (entry_to_foo, 0, "foo", thread_1);
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.c
index 2f95e4a0ceb8..aa03b7d5d2cc 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.c
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.c
@@ -316,13 +316,18 @@  public:
 
   const xml::document &get_document () const { return *m_document; }
 
+  void set_printer (pretty_printer &pp)
+  {
+    m_printer = &pp;
+  }
+
 private:
   std::unique_ptr<xml::element>
   make_element_for_diagnostic (const diagnostic_info &diagnostic,
 			       diagnostic_t orig_diag_kind);
 
   diagnostic_context &m_context;
-  pretty_printer &m_printer;
+  pretty_printer *m_printer;
   const line_maps *m_line_maps;
 
   std::unique_ptr<xml::document> m_document;
@@ -400,7 +405,7 @@  xhtml_builder::xhtml_builder (diagnostic_context &context,
 			      pretty_printer &pp,
 			      const line_maps *line_maps)
 : m_context (context),
-  m_printer (pp),
+  m_printer (&pp),
   m_line_maps (line_maps)
 {
   gcc_assert (m_line_maps);
@@ -565,10 +570,10 @@  xhtml_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic,
 
   auto message_span = make_span (label_text::borrow ("gcc-message"));
   xhtml_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);
-  pp_clear_output_area (&m_printer);
+  m_printer->set_token_printer (&tok_printer);
+  pp_output_formatted_text (m_printer, m_context.get_urlifier ());
+  m_printer->set_token_printer (nullptr);
+  pp_clear_output_area (m_printer);
   diag_element->add_child (std::move (message_span));
 
   if (diagnostic.metadata)
@@ -626,10 +631,10 @@  xhtml_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic,
     pre->set_attr ("class", label_text::borrow ("gcc-annotated-source"));
     // TODO: ideally we'd like to capture elements within the following:
     diagnostic_show_locus (&m_context, diagnostic.richloc, diagnostic.kind,
-			   &m_printer);
+			   m_printer);
     pre->add_text
-      (label_text::take (xstrdup (pp_formatted_text (&m_printer))));
-    pp_clear_output_area (&m_printer);
+      (label_text::take (xstrdup (pp_formatted_text (m_printer))));
+    pp_clear_output_area (m_printer);
     diag_element->add_child (std::move (pre));
   }
 
@@ -723,6 +728,23 @@  public:
   {
     /* No-op, but perhaps could show paths here.  */
   }
+  bool follows_reference_printer_p () const final override
+  {
+    return false;
+  }
+  void update_printer () final override
+  {
+    m_printer = m_context.clone_printer ();
+
+    /* Don't colorize the text.  */
+    pp_show_color (m_printer.get ()) = false;
+
+    /* No textual URLs.  */
+    m_printer->set_url_format (URL_FORMAT_NONE);
+
+    /* Update the builder to use the new printer.  */
+    m_builder.set_printer (*get_printer ());
+  }
 
   const xml::document &get_document () const
   {
diff --git a/gcc/testsuite/gcc.dg/plugin/expensive_selftests_plugin.c b/gcc/testsuite/gcc.dg/plugin/expensive_selftests_plugin.c
index 554dad6fa35a..e96efe0bed50 100644
--- a/gcc/testsuite/gcc.dg/plugin/expensive_selftests_plugin.c
+++ b/gcc/testsuite/gcc.dg/plugin/expensive_selftests_plugin.c
@@ -48,7 +48,7 @@  test_richloc (rich_location *richloc)
 {
   /* Run the diagnostic and fix-it printing code.  */
   test_diagnostic_context dc;
-  diagnostic_show_locus (&dc, richloc, DK_ERROR, dc.m_printer);
+  diagnostic_show_locus (&dc, richloc, DK_ERROR, dc.get_reference_printer ());
 
   /* Generate a diff.  */
   edit_context ec (global_dc->get_file_cache ());
diff --git a/gcc/testsuite/gcc.dg/sarif-output/add-output-sarif-defaults.c b/gcc/testsuite/gcc.dg/sarif-output/add-output-sarif-defaults.c
new file mode 100644
index 000000000000..ce6505824882
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/add-output-sarif-defaults.c
@@ -0,0 +1,16 @@ 
+/* Verify using -fdiagnostics-add-output=sarif with the defaults.  */
+
+/* { dg-do compile } */
+/* { dg-additional-options "-fdiagnostics-add-output=sarif" } */
+
+/* Verify that SARIF output can capture secondary locations
+   relating to a diagnostic.  */
+
+int missing_semicolon (void)
+{
+  return 42 /* { dg-error "expected ';' before '.' token" } */
+}
+
+/* Verify that JSON was written to the output file with the
+   expected version and expected name:
+   { dg-final { verify-sarif-file "2.1" }  } */
diff --git a/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.c b/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.c
new file mode 100644
index 000000000000..1d87bb81e976
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.c
@@ -0,0 +1,30 @@ 
+/* { dg-options "-fdiagnostics-show-caret -fdiagnostics-add-output=sarif" } */
+
+struct s {};
+struct t {};
+
+typedef struct s S;
+typedef struct t T;
+
+extern S callee_4a (void);
+extern T callee_4b (void);
+
+int test_4 (void)
+{
+  return callee_4a () + callee_4b (); /* { dg-error "invalid operands to binary \+" } */
+
+/* { dg-begin-multiline-output "" }
+   return callee_4a () + callee_4b ();
+          ~~~~~~~~~~~~ ^ ~~~~~~~~~~~~
+          |              |
+          |              T {aka struct t}
+          S {aka struct s}
+   { dg-end-multiline-output "" } */
+}
+
+/* Verify that some JSON was written to a file with the expected name.  */
+/* { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+   .sarif file:
+   { dg-final { run-sarif-pytest bad-binary-op.c "bad-binary-op.py" } } */
diff --git a/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.py b/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.py
new file mode 100644
index 000000000000..fe139e62e417
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.py
@@ -0,0 +1,70 @@ 
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+    return sarif_from_env()
+
+def test_basics(sarif):
+    schema = sarif['$schema']
+    assert schema == "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json"
+
+    version = sarif['version']
+    assert version == "2.1.0"
+
+def test_execution_unsuccessful(sarif):
+    runs = sarif['runs']
+    run = runs[0]
+
+    invocations = run['invocations']
+    assert len(invocations) == 1
+    invocation = invocations[0]
+
+    # We expect the 'error' to make executionSuccessful be false
+    assert invocation['executionSuccessful'] == False
+
+def test_error_location(sarif):
+    runs = sarif['runs']
+    run = runs[0]
+    results = run['results']
+
+    # We expect a single error with annotations.
+    #
+    # The textual form of the diagnostic looks like this:
+    # . PATH/bad-binary-ops.c: In function 'test_4':
+    # . PATH/bad-binary-ops.c:64:23: error: invalid operands to binary + (have 'S' {aka 'struct s'} and 'T' {aka 'struct t'})
+    # .    return callee_4a () + callee_4b (); /* { dg-error "invalid operands to binary \+" } */
+    # .           ~~~~~~~~~~~~ ^ ~~~~~~~~~~~~
+    # .           |              |
+    # .           |              T {aka struct t}
+    # .           S {aka struct s}
+    assert len(results) == 1
+
+    result = results[0]
+    assert result['level'] == 'error'
+
+    assert result['message']['text'] \
+        == "invalid operands to binary + (have 'S' {aka 'struct s'} and 'T' {aka 'struct t'})"
+    locations = result['locations']
+    assert len(locations) == 1
+
+    location = locations[0]
+    assert get_location_artifact_uri(location).endswith('bad-binary-op.c')
+    assert get_location_snippet_text(location) \
+        == "  return callee_4a () + callee_4b (); /* { dg-error \"invalid operands to binary \\+\" } */\n"
+    EXPECTED_LINE = 14
+    assert get_location_physical_region(location)['startLine'] == EXPECTED_LINE
+    assert get_location_physical_region(location)['startColumn'] == 23
+    assert get_location_physical_region(location)['endColumn'] == 24
+
+    annotations = location['annotations']
+    assert len(annotations) == 2
+    assert annotations[0]['startLine'] == EXPECTED_LINE
+    assert annotations[0]['startColumn'] == 10
+    assert annotations[0]['endColumn'] == 22
+    assert annotations[0]['message']['text'] == "S {aka struct s}"
+    assert annotations[1]['startLine'] == EXPECTED_LINE
+    assert annotations[1]['startColumn'] == 25
+    assert annotations[1]['endColumn'] == 37
+    assert annotations[1]['message']['text'] == "T {aka struct t}"
diff --git a/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.c b/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.c
new file mode 100644
index 000000000000..6a673a5f8083
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.c
@@ -0,0 +1,27 @@ 
+/* Verify that we can output multiple different versions of SARIF
+   with -fdiagnostics-add-output (specifying version and filename),
+   as well as usual text output.  */
+
+/* { dg-do compile } */
+/* { dg-additional-options "-fdiagnostics-add-output=sarif:file=multiple-outputs-c.2.1.sarif,version=2.1" } */
+/* { dg-additional-options "-fdiagnostics-add-output=sarif:file=multiple-outputs-c.2.2.sarif,version=2.2-prerelease" } */
+
+/* Verify that SARIF output can capture secondary locations
+   relating to a diagnostic.  */
+
+int missing_semicolon (void)
+{
+  return 42 /* { dg-error "expected ';' before '.' token" } */
+}
+
+/* Verify that JSON was written to the output files with the
+   expected version and expected names:
+   { dg-final { verify-sarif-file "2.1" "multiple-outputs-c.2.1.sarif" }  }
+   { dg-final { verify-sarif-file "2.2" "multiple-outputs-c.2.2.sarif" }  }
+*/
+
+/* Use a Python script to verify various properties about the generated
+   .sarif files:
+   { dg-final { run-sarif-pytest multiple-outputs-c.2.1 "multiple-outputs.py" } }
+   { dg-final { run-sarif-pytest multiple-outputs-c.2.2 "multiple-outputs.py" } }
+*/
diff --git a/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.py b/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.py
new file mode 100644
index 000000000000..8febfac4c7bf
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.py
@@ -0,0 +1,50 @@ 
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+    return sarif_from_env()
+
+def test_execution_unsuccessful(sarif):
+    runs = sarif['runs']
+    run = runs[0]
+
+    invocations = run['invocations']
+    assert len(invocations) == 1
+    invocation = invocations[0]
+
+    # We expect the 'error' to make executionSuccessful be false
+    assert invocation['executionSuccessful'] == False
+
+def test_result(sarif):
+    runs = sarif['runs']
+    run = runs[0]
+    results = run['results']
+
+    # We expect a single error with a secondary location and a fix-it hint.
+    #
+    # The textual form of the diagnostic would look like this:
+    #  . PATH/missing-semicolon.c: In function 'missing_semicolon':
+    #  . PATH/missing-semicolon.c:19:12: error: expected ';' before '}' token
+    #  .    19 |   return 42
+    #  .       |            ^
+    #  .       |            ;
+    #  .    20 | }
+    #  .       | ~           
+    assert len(results) == 1
+    
+    result = results[0]
+    assert result['level'] == 'error'
+    assert result['message']['text'] == "expected ';' before '}' token"
+    locations = result['locations']
+    assert len(locations) == 1
+
+    assert len(result['fixes']) == 1
+    assert len(result['fixes'][0]['artifactChanges']) == 1
+    change = result['fixes'][0]['artifactChanges'][0]
+    assert len(change['replacements']) == 1
+    replacement = change['replacements'][0]
+    assert replacement['deletedRegion']['startColumn'] == 12
+    assert replacement['deletedRegion']['endColumn'] == 12
+    assert replacement['insertedContent']['text'] == ';'
diff --git a/gcc/testsuite/lib/scansarif.exp b/gcc/testsuite/lib/scansarif.exp
index f27f3ee2164b..91801fc1157e 100644
--- a/gcc/testsuite/lib/scansarif.exp
+++ b/gcc/testsuite/lib/scansarif.exp
@@ -63,17 +63,24 @@  proc scan-sarif-file-not { args } {
 # The first argument is the version of the SARIF schema to validate against
 # If present can be "2.1" or "2.2"
 # If absent, validate against 2.1
+#
+# If present, the second argument is the expected filename of the .sarif file
 
 proc verify-sarif-file { args } {
     global srcdir subdir
 
     set testcase [testname-for-summary]
     set filename [lindex $testcase 0]
-    set output_file "[file tail $filename].sarif"
 
     set version [lindex $args 0]
     verbose "sarif version: $version" 2
 
+    set output_file [lindex $args 1]
+    verbose "output_file: $output_file" 2
+    if { $output_file == "" } {
+	set output_file "[file tail $filename].sarif"
+    }
+
     if { ![check_effective_target_recent_python3] } {
 	unsupported "$testcase verify-sarif-file: python3 is missing"
 	return
diff --git a/gcc/toplev.cc b/gcc/toplev.cc
index c60616bccdb6..779049674b4f 100644
--- a/gcc/toplev.cc
+++ b/gcc/toplev.cc
@@ -231,7 +231,7 @@  announce_function (tree decl)
 	fprintf (stderr, " %s",
 		 identifier_to_locale (lang_hooks.decl_printable_name (decl, 2)));
       fflush (stderr);
-      pp_needs_newline (global_dc->m_printer) = true;
+      pp_needs_newline (global_dc->get_reference_printer ()) = true;
       diagnostic_set_last_function (global_dc, (diagnostic_info *) NULL);
     }
 }
@@ -2389,7 +2389,7 @@  toplev::main (int argc, char **argv)
   if (auto edit_context_ptr = global_dc->get_edit_context ())
     {
       pretty_printer pp;
-      pp_show_color (&pp) = pp_show_color (global_dc->m_printer);
+      pp_show_color (&pp) = pp_show_color (global_dc->get_reference_printer ());
       edit_context_ptr->print_diff (&pp, true);
       pp_flush (&pp);
     }
diff --git a/gcc/tree-diagnostic.cc b/gcc/tree-diagnostic.cc
index b7d2c51c984c..84e7ae7721fc 100644
--- a/gcc/tree-diagnostic.cc
+++ b/gcc/tree-diagnostic.cc
@@ -180,7 +180,7 @@  tree_diagnostics_defaults (diagnostic_context *context)
 {
   diagnostic_text_starter (context) = default_tree_diagnostic_text_starter;
   diagnostic_text_finalizer (context) = default_diagnostic_text_finalizer;
-  diagnostic_format_decoder (context) = default_tree_printer;
+  context->set_format_decoder (default_tree_printer);
   context->set_set_locations_callback (set_inlining_locations);
   context->set_client_data_hooks (make_compiler_data_hooks ());
 }
diff --git a/gcc/tree-diagnostic.h b/gcc/tree-diagnostic.h
index dbc8ef6ae013..aee780f4b8e3 100644
--- a/gcc/tree-diagnostic.h
+++ b/gcc/tree-diagnostic.h
@@ -66,7 +66,7 @@  public:
   {
     pp_format_decoder (this) = default_tree_printer;
     if (outf == stderr)
-      pp_show_color (this) = pp_show_color (global_dc->m_printer);
+      pp_show_color (this) = pp_show_color (global_dc->get_reference_printer ());
     set_output_stream (outf);
   }
   ~tree_dump_pretty_printer ()
diff --git a/gcc/tree.cc b/gcc/tree.cc
index 624c50db165f..45f0474f9e38 100644
--- a/gcc/tree.cc
+++ b/gcc/tree.cc
@@ -12429,7 +12429,7 @@  escaped_string::escape (const char *unescaped)
 	  continue;
 	}
 
-      if (c != '\n' || !pp_is_wrapping_line (global_dc->m_printer))
+      if (c != '\n' || !pp_is_wrapping_line (global_dc->get_reference_printer ()))
 	{
 	  if (escaped == NULL)
 	    {
@@ -15901,7 +15901,7 @@  test_escaped_strings (void)
   ASSERT_STREQ ("foobar", (const char *) msg);
 
   /* Ensure that we have -fmessage-length set to 0.  */
-  pretty_printer *pp = global_dc->m_printer;
+  pretty_printer *pp = global_dc->get_reference_printer ();
   saved_cutoff = pp_line_cutoff (pp);
   pp_line_cutoff (pp) = 0;