[pushed] diagnostics, analyzer: add optional per-diagnostic property bags to SARIF

Message ID 20231201135851.1841421-2-dmalcolm@redhat.com
State Committed
Commit 12b67d1e13b3cf14fb24cf2a1c008b73d377ff6d
Headers
Series [pushed] diagnostics, analyzer: add optional per-diagnostic property bags to SARIF |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm warning Patch is already merged
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 warning Patch is already merged

Commit Message

David Malcolm Dec. 1, 2023, 1:58 p.m. UTC
  I've found it useful in debugging the analyzer for the SARIF output to
contain extra analyzer-specific data in each diagnostic.

This patch:
* adds a way for a diagnostic_metadata to populate a property
bag within a SARIF "result" object based on a new vfunc
* reworks how diagnostics are emitted within the analyzer so
that a custom diagnostic_metadata subclass is used, which populates
the property bag with information from the saved_diagnostic, and with
a vfunc hook allowing for per-pending_diagnotic-subclass extra
properties.

Doing so makes it trivial to go from the SARIF output back to
pertinent parts of the analyzer's internals (e.g. the index of
the diagnostic within the ana::diagnostic_manager, the index of
the ana::exploded_node, etc).

It also replaces a lot of boilerplate in the "emit" implementations
in the various pending_diagnostics subclasses.  In particular, doing
so fixes missing CVE metadata for -Wanalyzer-fd-phase-mismatch (where
sm-fd.cc's fd_phase_mismatch::emit was failing to use its
diagnostic_metadata instance).

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Successful run of analyzer integration tests on x86_64-pc-linux-gnu.

Pushed to trunk as r14-6057-g12b67d1e13b3cf.

gcc/analyzer/ChangeLog:
	* analyzer.h (class saved_diagnostic): New forward decl.
	* bounds-checking.cc: Update for changes to
	pending_diagnostic::emit.
	* call-details.cc: Likewise.
	* diagnostic-manager.cc: Include "diagnostic-format-sarif.h".
	(saved_diagnostic::maybe_add_sarif_properties): New.
	(class pending_diagnostic_metadata): New.
	(diagnostic_manager::emit_saved_diagnostic): Create a
	pending_diagnostic_metadata and a diagnostic_emission_context.
	Pass the latter to the pending_diagnostic::emit vfunc.
	* diagnostic-manager.h
	(saved_diagnostic::maybe_add_sarif_properties): New decl.
	* engine.cc: Update for changes to pending_diagnostic::emit.
	* infinite-loop.cc: Likewise.
	* infinite-recursion.cc: Likewise.
	* kf-analyzer.cc: Likewise.
	* kf.cc: Likewise.
	* pending-diagnostic.cc
	(diagnostic_emission_context::get_pending_diagnostic): New.
	(diagnostic_emission_context::warn): New.
	(diagnostic_emission_context::inform): New.
	* pending-diagnostic.h (class diagnostic_emission_context): New.
	(pending_diagnostic::emit): Update params.
	(pending_diagnostic::maybe_add_sarif_properties): New vfunc.
	* region.cc: Don't include "diagnostic-metadata.h".
	* region-model.cc: Include "diagnostic-format-sarif.h".  Update
	for changes to pending_diagnostic::emit.
	(exposure_through_uninit_copy::maybe_add_sarif_properties): New.
	* sm-fd.cc: Update for changes to pending_diagnostic::emit.
	* sm-file.cc: Likewise.
	* sm-malloc.cc: Likewise.
	* sm-pattern-test.cc: Likewise.
	* sm-sensitive.cc: Likewise.
	* sm-signal.cc: Likewise.
	* sm-taint.cc: Likewise.
	* store.cc: Don't include "diagnostic-metadata.h".
	* varargs.cc: Update for changes to pending_diagnostic::emit.

gcc/ChangeLog:
	* diagnostic-core.h (emit_diagnostic_valist): New overload decl.
	* diagnostic-format-sarif.cc (sarif_builder::make_result_object):
	When we have metadata, call its maybe_add_sarif_properties vfunc.
	* diagnostic-metadata.h (class sarif_object): Forward decl.
	(diagnostic_metadata::~diagnostic_metadata): New.
	(diagnostic_metadata::maybe_add_sarif_properties): New vfunc.
	* diagnostic.cc (emit_diagnostic_valist): New overload.

gcc/testsuite/ChangeLog:
	* gcc.dg/analyzer/fd-accept.c: Update for fix to missing CWE
	metadata for -Wanalyzer-fd-phase-mismatch.
	* gcc.dg/analyzer/fd-bind.c: Likewise.
	* gcc.dg/analyzer/fd-socket-misuse.c: Likewise.
	* gcc.dg/plugin/analyzer_cpython_plugin.c: Update for changes to
	pending_diagnostic::emit.
	* gcc.dg/plugin/analyzer_gil_plugin.c: Likewise.
---
 gcc/analyzer/analyzer.h                       |   1 +
 gcc/analyzer/bounds-checking.cc               | 130 +++++------
 gcc/analyzer/call-details.cc                  |   8 +-
 gcc/analyzer/diagnostic-manager.cc            |  53 ++++-
 gcc/analyzer/diagnostic-manager.h             |   2 +
 gcc/analyzer/engine.cc                        |  15 +-
 gcc/analyzer/infinite-loop.cc                 |   9 +-
 gcc/analyzer/infinite-recursion.cc            |   9 +-
 gcc/analyzer/kf-analyzer.cc                   |   4 +-
 gcc/analyzer/kf.cc                            |  32 ++-
 gcc/analyzer/pending-diagnostic.cc            |  45 ++++
 gcc/analyzer/pending-diagnostic.h             |  56 ++++-
 gcc/analyzer/region-model.cc                  | 123 +++++-----
 gcc/analyzer/region.cc                        |   1 -
 gcc/analyzer/sm-fd.cc                         |  75 +++----
 gcc/analyzer/sm-file.cc                       |  23 +-
 gcc/analyzer/sm-malloc.cc                     | 118 ++++------
 gcc/analyzer/sm-pattern-test.cc               |   8 +-
 gcc/analyzer/sm-sensitive.cc                  |  12 +-
 gcc/analyzer/sm-signal.cc                     |  11 +-
 gcc/analyzer/sm-taint.cc                      | 212 ++++++++----------
 gcc/analyzer/store.cc                         |   1 -
 gcc/analyzer/varargs.cc                       |  39 ++--
 gcc/diagnostic-core.h                         |   6 +
 gcc/diagnostic-format-sarif.cc                |  22 +-
 gcc/diagnostic-metadata.h                     |  10 +
 gcc/diagnostic.cc                             |  12 +
 gcc/testsuite/gcc.dg/analyzer/fd-accept.c     |   2 +-
 gcc/testsuite/gcc.dg/analyzer/fd-bind.c       |   4 +-
 .../gcc.dg/analyzer/fd-socket-misuse.c        |   2 +-
 .../gcc.dg/plugin/analyzer_cpython_plugin.c   |  10 +-
 .../gcc.dg/plugin/analyzer_gil_plugin.c       |  28 +--
 32 files changed, 550 insertions(+), 533 deletions(-)
  

Comments

Joseph Myers Feb. 9, 2024, 4:53 p.m. UTC | #1
On Fri, 1 Dec 2023, David Malcolm wrote:

> 	* diagnostic-core.h (emit_diagnostic_valist): New overload decl.

This has broken regeneration of gcc.pot (overloads can't have the message 
extracted for translation in different argument positions).

emit_diagnostic_valist used incompatibly as both --keyword=emit_diagnostic_valist:4
--flag=emit_diagnostic_valist:4:gcc-internal-format and --keyword=emit_diagnostic_valist:5
--flag=emit_diagnostic_valist:5:gcc-internal-format

See commit 6c8e584430bc5dc01b4438f3c38a2a10fcba7685 for previous fixes for 
this involving the same function, or 
40fecdd62f7d293a214dd71b81de5e0f1099bba7 and 
db30e21cbff7b9b2acd13ab83e25e3bf52f9696f for older fixes for similar 
issues.
  

Patch

diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index cf32d4b85b1..3115f878573 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -94,6 +94,7 @@  class bounded_ranges_manager;
 struct pending_location;
 class pending_diagnostic;
 class pending_note;
+class saved_diagnostic;
 struct event_loc_info;
 class checker_event;
   class state_change_event;
diff --git a/gcc/analyzer/bounds-checking.cc b/gcc/analyzer/bounds-checking.cc
index 583b5ab36a0..cc43ecc5468 100644
--- a/gcc/analyzer/bounds-checking.cc
+++ b/gcc/analyzer/bounds-checking.cc
@@ -30,7 +30,6 @@  along with GCC; see the file COPYING3.  If not see
 #include "gimple.h"
 #include "gimple-iterator.h"
 #include "diagnostic-core.h"
-#include "diagnostic-metadata.h"
 #include "diagnostic-diagram.h"
 #include "analyzer/analyzer.h"
 #include "analyzer/analyzer-logging.h"
@@ -119,10 +118,10 @@  protected:
   }
 
   void
-  maybe_show_notes (location_t loc, logger *logger) const
+  maybe_show_notes (diagnostic_emission_context &ctxt) const
   {
-    maybe_describe_array_bounds (loc);
-    maybe_show_diagram (logger);
+    maybe_describe_array_bounds (ctxt.get_location ());
+    maybe_show_diagram (ctxt.get_logger ());
   }
 
   /* Potentially add a note about valid ways to index this array, such
@@ -281,27 +280,22 @@  public:
     return "concrete_buffer_overflow";
   }
 
-  bool emit (rich_location *rich_loc,
-	     logger *logger) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
     bool warned;
     switch (get_memory_space ())
       {
       default:
-	m.add_cwe (787);
-	warned = warning_meta (rich_loc, m, get_controlling_option (),
-			       "buffer overflow");
+	ctxt.add_cwe (787);
+	warned = ctxt.warn ("buffer overflow");
 	break;
       case MEMSPACE_STACK:
-	m.add_cwe (121);
-	warned = warning_meta (rich_loc, m, get_controlling_option (),
-			       "stack-based buffer overflow");
+	ctxt.add_cwe (121);
+	warned = ctxt.warn ("stack-based buffer overflow");
 	break;
       case MEMSPACE_HEAP:
-	m.add_cwe (122);
-	warned = warning_meta (rich_loc, m, get_controlling_option (),
-			       "heap-based buffer overflow");
+	ctxt.add_cwe (122);
+	warned = ctxt.warn ("heap-based buffer overflow");
 	break;
       }
 
@@ -312,25 +306,25 @@  public:
 	    unsigned HOST_WIDE_INT num_bad_bytes
 	      = m_out_of_bounds_range.m_size_in_bytes.to_uhwi ();
 	    if (m_diag_arg)
-	      inform_n (rich_loc->get_loc (),
+	      inform_n (ctxt.get_location (),
 			num_bad_bytes,
 			"write of %wu byte to beyond the end of %qE",
 			"write of %wu bytes to beyond the end of %qE",
 			num_bad_bytes,
 			m_diag_arg);
 	    else
-	      inform_n (rich_loc->get_loc (),
+	      inform_n (ctxt.get_location (),
 			num_bad_bytes,
 			"write of %wu byte to beyond the end of the region",
 			"write of %wu bytes to beyond the end of the region",
 			num_bad_bytes);
 	  }
 	else if (m_diag_arg)
-	  inform (rich_loc->get_loc (),
+	  inform (ctxt.get_location (),
 		  "write to beyond the end of %qE",
 		  m_diag_arg);
 
-	maybe_show_notes (rich_loc->get_loc (), logger);
+	maybe_show_notes (ctxt);
       }
 
     return warned;
@@ -388,24 +382,20 @@  public:
     return "concrete_buffer_over_read";
   }
 
-  bool emit (rich_location *rich_loc, logger *logger) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
     bool warned;
-    m.add_cwe (126);
+    ctxt.add_cwe (126);
     switch (get_memory_space ())
       {
       default:
-	warned = warning_meta (rich_loc, m, get_controlling_option (),
-			       "buffer over-read");
+	warned = ctxt.warn ("buffer over-read");
 	break;
       case MEMSPACE_STACK:
-	warned = warning_meta (rich_loc, m, get_controlling_option (),
-			       "stack-based buffer over-read");
+	warned = ctxt.warn ("stack-based buffer over-read");
 	break;
       case MEMSPACE_HEAP:
-	warned = warning_meta (rich_loc, m, get_controlling_option (),
-			       "heap-based buffer over-read");
+	warned = ctxt.warn ("heap-based buffer over-read");
 	break;
       }
 
@@ -416,25 +406,25 @@  public:
 	    unsigned HOST_WIDE_INT num_bad_bytes
 	      = m_out_of_bounds_range.m_size_in_bytes.to_uhwi ();
 	    if (m_diag_arg)
-	      inform_n (rich_loc->get_loc (),
+	      inform_n (ctxt.get_location (),
 			num_bad_bytes,
 			"read of %wu byte from after the end of %qE",
 			"read of %wu bytes from after the end of %qE",
 			num_bad_bytes,
 			m_diag_arg);
 	    else
-	      inform_n (rich_loc->get_loc (),
+	      inform_n (ctxt.get_location (),
 			num_bad_bytes,
 			"read of %wu byte from after the end of the region",
 			"read of %wu bytes from after the end of the region",
 			num_bad_bytes);
 	  }
 	else if (m_diag_arg)
-	  inform (rich_loc->get_loc (),
+	  inform (ctxt.get_location (),
 		  "read from after the end of %qE",
 		  m_diag_arg);
 
-	maybe_show_notes (rich_loc->get_loc (), logger);
+	maybe_show_notes (ctxt);
       }
 
     return warned;
@@ -493,28 +483,24 @@  public:
     return "concrete_buffer_underwrite";
   }
 
-  bool emit (rich_location *rich_loc, logger *logger) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
     bool warned;
-    m.add_cwe (124);
+    ctxt.add_cwe (124);
     switch (get_memory_space ())
       {
       default:
-	warned = warning_meta (rich_loc, m, get_controlling_option (),
-			       "buffer underwrite");
+	warned = ctxt.warn ("buffer underwrite");
 	break;
       case MEMSPACE_STACK:
-	warned = warning_meta (rich_loc, m, get_controlling_option (),
-			       "stack-based buffer underwrite");
+	warned = ctxt.warn ("stack-based buffer underwrite");
 	break;
       case MEMSPACE_HEAP:
-	warned = warning_meta (rich_loc, m, get_controlling_option (),
-			       "heap-based buffer underwrite");
+	warned = ctxt.warn ("heap-based buffer underwrite");
 	break;
       }
     if (warned)
-      maybe_show_notes (rich_loc->get_loc (), logger);
+      maybe_show_notes (ctxt);
     return warned;
   }
 
@@ -568,28 +554,24 @@  public:
     return "concrete_buffer_under_read";
   }
 
-  bool emit (rich_location *rich_loc, logger *logger) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
     bool warned;
-    m.add_cwe (127);
+    ctxt.add_cwe (127);
     switch (get_memory_space ())
       {
       default:
-	warned = warning_meta (rich_loc, m, get_controlling_option (),
-			       "buffer under-read");
+	warned = ctxt.warn ("buffer under-read");
 	break;
       case MEMSPACE_STACK:
-	warned = warning_meta (rich_loc, m, get_controlling_option (),
-			       "stack-based buffer under-read");
+	warned = ctxt.warn ("stack-based buffer under-read");
 	break;
       case MEMSPACE_HEAP:
-	warned = warning_meta (rich_loc, m, get_controlling_option (),
-			       "heap-based buffer under-read");
+	warned = ctxt.warn ("heap-based buffer under-read");
 	break;
       }
     if (warned)
-      maybe_show_notes (rich_loc->get_loc (), logger);
+      maybe_show_notes (ctxt);
     return warned;
   }
 
@@ -679,30 +661,26 @@  public:
     return "symbolic_buffer_overflow";
   }
 
-  bool emit (rich_location *rich_loc, logger *logger) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
     bool warned;
     switch (get_memory_space ())
       {
       default:
-	m.add_cwe (787);
-	warned = warning_meta (rich_loc, m, get_controlling_option (),
-			       "buffer overflow");
+	ctxt.add_cwe (787);
+	warned = ctxt.warn ("buffer overflow");
 	break;
       case MEMSPACE_STACK:
-	m.add_cwe (121);
-	warned = warning_meta (rich_loc, m, get_controlling_option (),
-			       "stack-based buffer overflow");
+	ctxt.add_cwe (121);
+	warned = ctxt.warn ("stack-based buffer overflow");
 	break;
       case MEMSPACE_HEAP:
-	m.add_cwe (122);
-	warned =  warning_meta (rich_loc, m, get_controlling_option (),
-				"heap-based buffer overflow");
+	ctxt.add_cwe (122);
+	warned =  ctxt.warn ("heap-based buffer overflow");
 	break;
       }
     if (warned)
-      maybe_show_notes (rich_loc->get_loc (), logger);
+      maybe_show_notes (ctxt);
     return warned;
   }
 
@@ -796,31 +774,27 @@  public:
     return "symbolic_buffer_over_read";
   }
 
-  bool emit (rich_location *rich_loc, logger *logger) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
-    m.add_cwe (126);
+    ctxt.add_cwe (126);
     bool warned;
     switch (get_memory_space ())
       {
       default:
-	m.add_cwe (787);
-	warned = warning_meta (rich_loc, m, get_controlling_option (),
-			       "buffer over-read");
+	ctxt.add_cwe (787);
+	warned = ctxt.warn ("buffer over-read");
 	break;
       case MEMSPACE_STACK:
-	m.add_cwe (121);
-	warned = warning_meta (rich_loc, m, get_controlling_option (),
-			       "stack-based buffer over-read");
+	ctxt.add_cwe (121);
+	warned = ctxt.warn ("stack-based buffer over-read");
 	break;
       case MEMSPACE_HEAP:
-	m.add_cwe (122);
-	warned = warning_meta (rich_loc, m, get_controlling_option (),
-			       "heap-based buffer over-read");
+	ctxt.add_cwe (122);
+	warned = ctxt.warn ("heap-based buffer over-read");
 	break;
       }
     if (warned)
-      maybe_show_notes (rich_loc->get_loc (), logger);
+      maybe_show_notes (ctxt);
     return warned;
   }
 
diff --git a/gcc/analyzer/call-details.cc b/gcc/analyzer/call-details.cc
index 9480f037eaa..c5ae2dcb13a 100644
--- a/gcc/analyzer/call-details.cc
+++ b/gcc/analyzer/call-details.cc
@@ -445,14 +445,12 @@  public:
     return OPT_Wanalyzer_overlapping_buffers;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     auto_diagnostic_group d;
 
-    bool warned;
-    warned = warning_at (rich_loc, get_controlling_option (),
-			 "overlapping buffers passed as arguments to %qD",
-			 m_fndecl);
+    bool warned = ctxt.warn ("overlapping buffers passed as arguments to %qD",
+			     m_fndecl);
 
     // TODO: draw a picture?
 
diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
index a6755f2193f..ecd57376b54 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -58,6 +58,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "analyzer/checker-path.h"
 #include "analyzer/reachability.h"
 #include "make-unique.h"
+#include "diagnostic-format-sarif.h"
 
 #if ENABLE_ANALYZER
 
@@ -1018,6 +1019,31 @@  saved_diagnostic::emit_any_notes () const
     pn->emit ();
 }
 
+/* For SARIF output, add additional properties to the "result" object
+   for this diagnostic.
+   This extra data is intended for use when debugging the analyzer.  */
+
+void
+saved_diagnostic::maybe_add_sarif_properties (sarif_object &result_obj) const
+{
+  sarif_property_bag &props = result_obj.get_or_create_properties ();
+#define PROPERTY_PREFIX "gcc/analyzer/saved_diagnostic/"
+  if (m_sm)
+    props.set_string (PROPERTY_PREFIX "sm", m_sm->get_name ());
+  props.set_integer (PROPERTY_PREFIX "enode", m_enode->m_index);
+  props.set_integer (PROPERTY_PREFIX "snode", m_snode->m_index);
+  if (m_sval)
+    props.set (PROPERTY_PREFIX "sval", m_sval->to_json ());
+  if (m_state)
+    props.set (PROPERTY_PREFIX "state", m_state->to_json ());
+  if (m_best_epath)
+  props.set (PROPERTY_PREFIX "idx", new json::integer_number (m_idx));
+#undef PROPERTY_PREFIX
+
+  /* Potentially add pending_diagnostic-specific properties.  */
+  m_d->maybe_add_sarif_properties (result_obj);
+}
+
 /* State for building a checker_path from a particular exploded_path.
    In particular, this precomputes reachability information: the set of
    source enodes for which a path be found to the diagnostic enode.  */
@@ -1498,6 +1524,29 @@  diagnostic_manager::emit_saved_diagnostics (const exploded_graph &eg)
   best_candidates.emit_best (this, eg);
 }
 
+/* Custom subclass of diagnostic_metadata which, for SARIF output,
+   populates the property bag of the diagnostic's "result" object
+   with information from the saved_diagnostic and the
+   pending_diagnostic.  */
+
+class pending_diagnostic_metadata : public diagnostic_metadata
+{
+public:
+  pending_diagnostic_metadata (const saved_diagnostic &sd)
+  : m_sd (sd)
+  {
+  }
+
+  void
+  maybe_add_sarif_properties (sarif_object &result_obj) const override
+  {
+    m_sd.maybe_add_sarif_properties (result_obj);
+  }
+
+private:
+  const saved_diagnostic &m_sd;
+};
+
 /* Given a saved_diagnostic SD with m_best_epath through EG,
    create an checker_path of suitable events and use it to call
    SD's underlying pending_diagnostic "emit" vfunc to emit a diagnostic.  */
@@ -1563,7 +1612,9 @@  diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
 
   auto_diagnostic_group d;
   auto_cfun sentinel (sd.m_snode->m_fun);
-  if (sd.m_d->emit (&rich_loc, get_logger ()))
+  pending_diagnostic_metadata m (sd);
+  diagnostic_emission_context diag_ctxt (sd, rich_loc, m, get_logger ());
+  if (sd.m_d->emit (diag_ctxt))
     {
       sd.emit_any_notes ();
 
diff --git a/gcc/analyzer/diagnostic-manager.h b/gcc/analyzer/diagnostic-manager.h
index 27ab9ed068e..b6d6f087732 100644
--- a/gcc/analyzer/diagnostic-manager.h
+++ b/gcc/analyzer/diagnostic-manager.h
@@ -67,6 +67,8 @@  public:
 
   void emit_any_notes () const;
 
+  void maybe_add_sarif_properties (sarif_object &result_obj) const;
+
   //private:
   const state_machine *m_sm;
   const exploded_node *m_enode;
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index b4e855fcf24..1f930a21eb3 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -1811,13 +1811,11 @@  public:
     return OPT_Wanalyzer_stale_setjmp_buffer;
   }
 
-  bool emit (rich_location *richloc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    return warning_at
-      (richloc, get_controlling_option (),
-       "%qs called after enclosing function of %qs has returned",
-       get_user_facing_name (m_longjmp_call),
-       get_user_facing_name (m_setjmp_call));
+    return ctxt.warn ("%qs called after enclosing function of %qs has returned",
+		      get_user_facing_name (m_longjmp_call),
+		      get_user_facing_name (m_setjmp_call));
   }
 
   const char *get_kind () const final override
@@ -3982,10 +3980,9 @@  public:
     return OPT_Wanalyzer_jump_through_null;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    return warning_at (rich_loc, get_controlling_option (),
-		       "jump through null pointer");
+    return ctxt.warn ("jump through null pointer");
   }
 
   label_text describe_final_event (const evdesc::final_event &ev) final override
diff --git a/gcc/analyzer/infinite-loop.cc b/gcc/analyzer/infinite-loop.cc
index 771d698a60b..c47ce1c8908 100644
--- a/gcc/analyzer/infinite-loop.cc
+++ b/gcc/analyzer/infinite-loop.cc
@@ -32,7 +32,6 @@  along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "diagnostic-event-id.h"
 #include "diagnostic-path.h"
-#include "diagnostic-metadata.h"
 #include "function.h"
 #include "pretty-print.h"
 #include "sbitmap.h"
@@ -178,13 +177,11 @@  public:
     return OPT_Wanalyzer_infinite_loop;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     /* "CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop')". */
-    diagnostic_metadata m;
-    m.add_cwe (835);
-    return warning_meta (rich_loc, m, get_controlling_option (),
-			 "infinite loop");
+    ctxt.add_cwe (835);
+    return ctxt.warn ("infinite loop");
   }
 
   bool maybe_add_custom_events_for_superedge (const exploded_edge &,
diff --git a/gcc/analyzer/infinite-recursion.cc b/gcc/analyzer/infinite-recursion.cc
index 9576ff5f58d..0fab9b702a8 100644
--- a/gcc/analyzer/infinite-recursion.cc
+++ b/gcc/analyzer/infinite-recursion.cc
@@ -31,7 +31,6 @@  along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "diagnostic-event-id.h"
 #include "diagnostic-path.h"
-#include "diagnostic-metadata.h"
 #include "function.h"
 #include "pretty-print.h"
 #include "sbitmap.h"
@@ -95,13 +94,11 @@  public:
     return OPT_Wanalyzer_infinite_recursion;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     /* "CWE-674: Uncontrolled Recursion".  */
-    diagnostic_metadata m;
-    m.add_cwe (674);
-    return warning_meta (rich_loc, m, get_controlling_option (),
-			 "infinite recursion");
+    ctxt.add_cwe (674);
+    return ctxt.warn ("infinite recursion");
   }
 
   label_text describe_final_event (const evdesc::final_event &ev) final override
diff --git a/gcc/analyzer/kf-analyzer.cc b/gcc/analyzer/kf-analyzer.cc
index 7ae598a8912..01e2c46d5ad 100644
--- a/gcc/analyzer/kf-analyzer.cc
+++ b/gcc/analyzer/kf-analyzer.cc
@@ -255,9 +255,9 @@  public:
     return 0;
   }
 
-  bool emit (rich_location *richloc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    inform (richloc, "path");
+    ctxt.inform ("path");
     return true;
   }
 
diff --git a/gcc/analyzer/kf.cc b/gcc/analyzer/kf.cc
index 5d8e04d9726..a69f084bbf3 100644
--- a/gcc/analyzer/kf.cc
+++ b/gcc/analyzer/kf.cc
@@ -719,32 +719,29 @@  public:
     return OPT_Wanalyzer_putenv_of_auto_var;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     auto_diagnostic_group d;
-    diagnostic_metadata m;
 
     /* SEI CERT C Coding Standard: "POS34-C. Do not call putenv() with a
        pointer to an automatic variable as the argument".  */
     diagnostic_metadata::precanned_rule
       rule ("POS34-C", "https://wiki.sei.cmu.edu/confluence/x/6NYxBQ");
-    m.add_rule (rule);
+    ctxt.add_rule (rule);
 
     bool warned;
     if (m_var_decl)
-      warned = warning_meta (rich_loc, m, get_controlling_option (),
-			     "%qE on a pointer to automatic variable %qE",
-			     m_fndecl, m_var_decl);
+      warned = ctxt.warn ("%qE on a pointer to automatic variable %qE",
+			  m_fndecl, m_var_decl);
     else
-      warned = warning_meta (rich_loc, m, get_controlling_option (),
-			     "%qE on a pointer to an on-stack buffer",
-			     m_fndecl);
+      warned = ctxt.warn ("%qE on a pointer to an on-stack buffer",
+			  m_fndecl);
     if (warned)
       {
 	if (m_var_decl)
 	  inform (DECL_SOURCE_LOCATION (m_var_decl),
 		  "%qE declared on stack here", m_var_decl);
-	inform (rich_loc->get_loc (), "perhaps use %qs rather than %qE",
+	inform (ctxt.get_location (), "perhaps use %qs rather than %qE",
 		"setenv", m_fndecl);
       }
 
@@ -1733,18 +1730,15 @@  public:
       return OPT_Wanalyzer_undefined_behavior_strtok;
     }
 
-    bool emit (rich_location *rich_loc, logger *) final override
+    bool emit (diagnostic_emission_context &ctxt) final override
     {
       /* CWE-476: NULL Pointer Dereference.  */
-      diagnostic_metadata m;
-      m.add_cwe (476);
-      if (warning_meta
-	  (rich_loc, m, get_controlling_option (),
-	   "calling %qD for first time with NULL as argument 1"
-	   " has undefined behavior",
-	   get_callee_fndecl ()))
+      ctxt.add_cwe (476);
+      if (ctxt.warn ("calling %qD for first time with NULL as argument 1"
+		     " has undefined behavior",
+		     get_callee_fndecl ()))
 	{
-	  inform (rich_loc->get_loc (),
+	  inform (ctxt.get_location (),
 		  "some implementations of %qD may crash on such input",
 		  get_callee_fndecl ());
 	  return true;
diff --git a/gcc/analyzer/pending-diagnostic.cc b/gcc/analyzer/pending-diagnostic.cc
index c7d337033f1..48d9be9ba2d 100644
--- a/gcc/analyzer/pending-diagnostic.cc
+++ b/gcc/analyzer/pending-diagnostic.cc
@@ -109,6 +109,51 @@  evdesc::event_desc::formatted_print (const char *fmt, ...) const
   return result;
 }
 
+/* class diagnostic_emission_context.  */
+
+/* Get the pending_diagnostic being emitted.  */
+
+const pending_diagnostic &
+diagnostic_emission_context::get_pending_diagnostic () const
+{
+  return *m_sd.m_d.get ();
+}
+
+/* Emit a warning, using the rich_location, metadata, and the
+   pending_diagnostic's option.  */
+
+bool
+diagnostic_emission_context::warn (const char *gmsgid, ...)
+{
+  const pending_diagnostic &pd = get_pending_diagnostic ();
+  auto_diagnostic_group d;
+  va_list ap;
+  va_start (ap, gmsgid);
+  const bool result = emit_diagnostic_valist (DK_WARNING,
+					      &m_rich_loc, &m_metadata,
+					      pd.get_controlling_option (),
+					      gmsgid, &ap);
+  va_end (ap);
+  return result;
+}
+
+/* Emit a note, using the rich_location and metadata (and the
+   pending_diagnostic's option).  */
+
+void
+diagnostic_emission_context::inform (const char *gmsgid, ...)
+{
+  const pending_diagnostic &pd = get_pending_diagnostic ();
+  auto_diagnostic_group d;
+  va_list ap;
+  va_start (ap, gmsgid);
+  emit_diagnostic_valist (DK_NOTE,
+			  &m_rich_loc, &m_metadata,
+			  pd.get_controlling_option (),
+			  gmsgid, &ap);
+  va_end (ap);
+}
+
 /* Return true if T1 and T2 are "the same" for the purposes of
    diagnostic deduplication.  */
 
diff --git a/gcc/analyzer/pending-diagnostic.h b/gcc/analyzer/pending-diagnostic.h
index 7582b37efe7..e393f9ae24e 100644
--- a/gcc/analyzer/pending-diagnostic.h
+++ b/gcc/analyzer/pending-diagnostic.h
@@ -21,6 +21,7 @@  along with GCC; see the file COPYING3.  If not see
 #ifndef GCC_ANALYZER_PENDING_DIAGNOSTIC_H
 #define GCC_ANALYZER_PENDING_DIAGNOSTIC_H
 
+#include "diagnostic-metadata.h"
 #include "diagnostic-path.h"
 #include "analyzer/sm.h"
 
@@ -144,6 +145,47 @@  struct final_event : public event_desc
 
 } /* end of namespace evdesc */
 
+/*  A bundle of information for use by implementations of the
+    pending_diagnostic::emit vfunc.
+
+    The rich_location will have already been populated with a
+    diagnostic_path.  */
+
+class diagnostic_emission_context
+{
+public:
+  diagnostic_emission_context (const saved_diagnostic &sd,
+			       rich_location &rich_loc,
+			       diagnostic_metadata &metadata,
+			       logger *logger)
+  : m_sd (sd),
+    m_rich_loc (rich_loc),
+    m_metadata (metadata),
+    m_logger (logger)
+  {
+  }
+
+  const pending_diagnostic &get_pending_diagnostic () const;
+
+  bool warn (const char *, ...) ATTRIBUTE_GCC_DIAG (2,3);
+  void inform (const char *, ...) ATTRIBUTE_GCC_DIAG (2,3);
+
+  location_t get_location () const { return m_rich_loc.get_loc (); }
+  logger *get_logger () const { return m_logger; }
+
+  void add_cwe (int cwe) { m_metadata.add_cwe (cwe); }
+  void add_rule (const diagnostic_metadata::rule &r)
+  {
+    m_metadata.add_rule (r);
+  }
+
+private:
+  const saved_diagnostic &m_sd;
+  rich_location &m_rich_loc;
+  diagnostic_metadata &m_metadata;
+  logger *m_logger;
+};
+
 /* An abstract base class for capturing information about a diagnostic in
    a form that is ready to emit at a later point (or be rejected).
    Each kind of diagnostic will have a concrete subclass of
@@ -177,10 +219,9 @@  class pending_diagnostic
      path being explored.  By default, don't terminate the path.  */
   virtual bool terminate_path_p () const { return false; }
 
-  /* Vfunc for emitting the diagnostic.  The rich_location will have been
-     populated with a diagnostic_path.
+  /* Vfunc for emitting the diagnostic.
      Return true if a diagnostic is actually emitted.  */
-  virtual bool emit (rich_location *, logger *) = 0;
+  virtual bool emit (diagnostic_emission_context &) = 0;
 
   /* Hand-coded RTTI: get an ID for the subclass.  */
   virtual const char *get_kind () const = 0;
@@ -361,6 +402,15 @@  class pending_diagnostic
     /* Default implementation: accept this path.  */
     return true;
   }
+
+  /* Vfunc for use in SARIF output to give pending_diagnostic subclasses
+     the opportunity to add diagnostic-specific properties to the SARIF
+     "result" object for the diagnostic.
+     This is intended for use when debugging a diagnostic.  */
+  virtual void maybe_add_sarif_properties (sarif_object &/*result_obj*/) const
+  {
+    /* Default no-op implementation.  */
+  }
 };
 
 /* A template to make it easier to make subclasses of pending_diagnostic.
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 420c10380a4..2157ad2578b 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -40,7 +40,6 @@  along with GCC; see the file COPYING3.  If not see
 #include "fold-const.h"
 #include "tree-pretty-print.h"
 #include "diagnostic-color.h"
-#include "diagnostic-metadata.h"
 #include "bitmap.h"
 #include "selftest.h"
 #include "analyzer/analyzer.h"
@@ -79,6 +78,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "analyzer/checker-path.h"
 #include "analyzer/feasible-graph.h"
 #include "analyzer/record-layout.h"
+#include "diagnostic-format-sarif.h"
 
 #if ENABLE_ANALYZER
 
@@ -512,7 +512,7 @@  public:
 
   bool terminate_path_p () const final override { return true; }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     switch (m_pkind)
       {
@@ -520,37 +520,30 @@  public:
 	gcc_unreachable ();
       case POISON_KIND_UNINIT:
 	{
-	  diagnostic_metadata m;
-	  m.add_cwe (457); /* "CWE-457: Use of Uninitialized Variable".  */
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of uninitialized value %qE",
-			       m_expr);
+	  ctxt.add_cwe (457); /* "CWE-457: Use of Uninitialized Variable".  */
+	  return ctxt.warn ("use of uninitialized value %qE",
+			    m_expr);
 	}
 	break;
       case POISON_KIND_FREED:
 	{
-	  diagnostic_metadata m;
-	  m.add_cwe (416); /* "CWE-416: Use After Free".  */
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use after %<free%> of %qE",
-			       m_expr);
+	  ctxt.add_cwe (416); /* "CWE-416: Use After Free".  */
+	  return ctxt.warn ("use after %<free%> of %qE",
+			    m_expr);
 	}
 	break;
       case POISON_KIND_DELETED:
 	{
-	  diagnostic_metadata m;
-	  m.add_cwe (416); /* "CWE-416: Use After Free".  */
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use after %<delete%> of %qE",
-			       m_expr);
+	  ctxt.add_cwe (416); /* "CWE-416: Use After Free".  */
+	  return ctxt.warn ("use after %<delete%> of %qE",
+			    m_expr);
 	}
 	break;
       case POISON_KIND_POPPED_STACK:
 	{
 	  /* TODO: which CWE?  */
-	  return warning_at
-	    (rich_loc, get_controlling_option (),
-	     "dereferencing pointer %qE to within stale stack frame",
+	  return ctxt.warn
+	    ("dereferencing pointer %qE to within stale stack frame",
 	     m_expr);
 	}
 	break;
@@ -655,10 +648,9 @@  public:
     return OPT_Wanalyzer_shift_count_negative;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    return warning_at (rich_loc, get_controlling_option (),
-		       "shift by negative count (%qE)", m_count_cst);
+    return ctxt.warn ("shift by negative count (%qE)", m_count_cst);
   }
 
   label_text describe_final_event (const evdesc::final_event &ev) final override
@@ -702,11 +694,10 @@  public:
     return OPT_Wanalyzer_shift_count_overflow;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    return warning_at (rich_loc, get_controlling_option (),
-		       "shift by count (%qE) >= precision of type (%qi)",
-		       m_count_cst, m_operand_precision);
+    return ctxt.warn ("shift by count (%qE) >= precision of type (%qi)",
+		      m_count_cst, m_operand_precision);
   }
 
   label_text describe_final_event (const evdesc::final_event &ev) final override
@@ -2840,23 +2831,20 @@  public:
     return OPT_Wanalyzer_write_to_const;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     auto_diagnostic_group d;
     bool warned;
     switch (m_reg->get_kind ())
       {
       default:
-	warned = warning_at (rich_loc, get_controlling_option (),
-			     "write to %<const%> object %qE", m_decl);
+	warned = ctxt.warn ("write to %<const%> object %qE", m_decl);
 	break;
       case RK_FUNCTION:
-	warned = warning_at (rich_loc, get_controlling_option (),
-			     "write to function %qE", m_decl);
+	warned = ctxt.warn ("write to function %qE", m_decl);
 	break;
       case RK_LABEL:
-	warned = warning_at (rich_loc, get_controlling_option (),
-			     "write to label %qE", m_decl);
+	warned = ctxt.warn ("write to label %qE", m_decl);
 	break;
       }
     if (warned)
@@ -2908,10 +2896,9 @@  public:
     return OPT_Wanalyzer_write_to_string_literal;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    return warning_at (rich_loc, get_controlling_option (),
-		       "write to string literal");
+    return ctxt.warn ("write to string literal");
     /* Ideally we would show the location of the STRING_CST as well,
        but it is not available at this point.  */
   }
@@ -3112,14 +3099,12 @@  public:
     return OPT_Wanalyzer_allocation_size;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
-    m.add_cwe (131);
+    ctxt.add_cwe (131);
 
-    return warning_meta (rich_loc, m, get_controlling_option (),
-			 "allocated buffer size is not a multiple"
-			 " of the pointee's size");
+    return ctxt.warn ("allocated buffer size is not a multiple"
+		      " of the pointee's size");
   }
 
   label_text describe_final_event (const evdesc::final_event &ev) final
@@ -5970,15 +5955,14 @@  public:
     return same_tree_p (m_arg, ((const float_as_size_arg &) other).m_arg);
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
-    bool warned = warning_meta (rich_loc, m, get_controlling_option (),
-				"use of floating-point arithmetic here might"
-				" yield unexpected results");
+    bool warned = ctxt.warn ("use of floating-point arithmetic here might"
+			     " yield unexpected results");
     if (warned)
-      inform (rich_loc->get_loc (), "only use operands of an integer type"
-				    " inside the size argument");
+      inform (ctxt.get_location (),
+	      "only use operands of an integer type"
+	      " inside the size argument");
     return warned;
   }
 
@@ -6214,37 +6198,33 @@  public:
     return OPT_Wanalyzer_exposure_through_uninit_copy;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
     /* CWE-200: Exposure of Sensitive Information to an Unauthorized Actor.  */
-    m.add_cwe (200);
+    ctxt.add_cwe (200);
     enum memory_space mem_space = get_src_memory_space ();
     bool warned;
     switch (mem_space)
       {
       default:
-	warned = warning_meta
-	  (rich_loc, m, get_controlling_option (),
-	   "potential exposure of sensitive information"
-	   " by copying uninitialized data across trust boundary");
+	warned = ctxt.warn ("potential exposure of sensitive information"
+			    " by copying uninitialized data"
+			    " across trust boundary");
 	break;
       case MEMSPACE_STACK:
-	warned = warning_meta
-	  (rich_loc, m, get_controlling_option (),
-	   "potential exposure of sensitive information"
-	   " by copying uninitialized data from stack across trust boundary");
+	warned = ctxt.warn ("potential exposure of sensitive information"
+			    " by copying uninitialized data from stack"
+			    " across trust boundary");
 	break;
       case MEMSPACE_HEAP:
-	warned = warning_meta
-	  (rich_loc, m, get_controlling_option (),
-	   "potential exposure of sensitive information"
-	   " by copying uninitialized data from heap across trust boundary");
+	warned = ctxt.warn ("potential exposure of sensitive information"
+			    " by copying uninitialized data from heap"
+			    " across trust boundary");
 	break;
       }
     if (warned)
       {
-	location_t loc = rich_loc->get_loc ();
+	const location_t loc = ctxt.get_location ();
 	inform_number_of_uninit_bits (loc);
 	complain_about_uninit_ranges (loc);
 
@@ -6276,6 +6256,17 @@  public:
       interest->add_region_creation (m_src_region);
   }
 
+  void
+  maybe_add_sarif_properties (sarif_object &result_obj) const final override
+  {
+    sarif_property_bag &props = result_obj.get_or_create_properties ();
+#define PROPERTY_PREFIX "gcc/-Wanalyzer-exposure-through-uninit-copy/"
+    props.set (PROPERTY_PREFIX "src_region", m_src_region->to_json ());
+    props.set (PROPERTY_PREFIX "dest_region", m_dest_region->to_json ());
+    props.set (PROPERTY_PREFIX "copied_sval", m_copied_sval->to_json ());
+#undef PROPERTY_PREFIX
+  }
+
 private:
   enum memory_space get_src_memory_space () const
   {
diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc
index 4feb9721ae7..9b27e8febcc 100644
--- a/gcc/analyzer/region.cc
+++ b/gcc/analyzer/region.cc
@@ -40,7 +40,6 @@  along with GCC; see the file COPYING3.  If not see
 #include "fold-const.h"
 #include "tree-pretty-print.h"
 #include "diagnostic-color.h"
-#include "diagnostic-metadata.h"
 #include "bitmap.h"
 #include "analyzer/analyzer.h"
 #include "analyzer/analyzer-logging.h"
diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc
index 34bbd84f6e7..7f8a1d9b949 100644
--- a/gcc/analyzer/sm-fd.cc
+++ b/gcc/analyzer/sm-fd.cc
@@ -29,7 +29,6 @@  along with GCC; see the file COPYING3.  If not see
 #include "gimple.h"
 #include "options.h"
 #include "diagnostic-path.h"
-#include "diagnostic-metadata.h"
 #include "analyzer/analyzer.h"
 #include "diagnostic-event-id.h"
 #include "analyzer/analyzer-logging.h"
@@ -465,19 +464,16 @@  public:
   }
 
   bool
-  emit (rich_location *rich_loc, logger *) final override
+  emit (diagnostic_emission_context &ctxt) final override
   {
     /*CWE-775: Missing Release of File Descriptor or Handle after Effective
       Lifetime
      */
-    diagnostic_metadata m;
-    m.add_cwe (775);
+    ctxt.add_cwe (775);
     if (m_arg)
-      return warning_meta (rich_loc, m, get_controlling_option (),
-			   "leak of file descriptor %qE", m_arg);
+      return ctxt.warn ("leak of file descriptor %qE", m_arg);
     else
-      return warning_meta (rich_loc, m, get_controlling_option (),
-			   "leak of file descriptor");
+      return ctxt.warn ("leak of file descriptor");
   }
 
   label_text
@@ -550,20 +546,18 @@  public:
   }
 
   bool
-  emit (rich_location *rich_loc, logger *) final override
+  emit (diagnostic_emission_context &ctxt) final override
   {
     bool warned;
     switch (m_fd_dir)
       {
       case DIRS_READ:
-	warned =  warning_at (rich_loc, get_controlling_option (),
-			   "%qE on read-only file descriptor %qE",
-			   m_callee_fndecl, m_arg);
+	warned =  ctxt.warn ("%qE on read-only file descriptor %qE",
+			     m_callee_fndecl, m_arg);
 	break;
       case DIRS_WRITE:
-	warned = warning_at (rich_loc, get_controlling_option (),
-			   "%qE on write-only file descriptor %qE",
-			   m_callee_fndecl, m_arg);
+	warned = ctxt.warn ("%qE on write-only file descriptor %qE",
+			    m_callee_fndecl, m_arg);
 	break;
       default:
 	gcc_unreachable ();
@@ -612,13 +606,11 @@  public:
     return OPT_Wanalyzer_fd_double_close;
   }
   bool
-  emit (rich_location *rich_loc, logger *) final override
+  emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
     // CWE-1341: Multiple Releases of Same Resource or Handle
-    m.add_cwe (1341);
-    return warning_meta (rich_loc, m, get_controlling_option (),
-			 "double %<close%> of file descriptor %qE", m_arg);
+    ctxt.add_cwe (1341);
+    return ctxt.warn ("double %<close%> of file descriptor %qE", m_arg);
   }
 
   label_text
@@ -677,12 +669,10 @@  public:
   }
 
   bool
-  emit (rich_location *rich_loc, logger *) final override
+  emit (diagnostic_emission_context &ctxt) final override
   {
-    bool warned;
-    warned = warning_at (rich_loc, get_controlling_option (),
-		       "%qE on closed file descriptor %qE", m_callee_fndecl,
-		       m_arg);
+    bool warned = ctxt.warn ("%qE on closed file descriptor %qE",
+			     m_callee_fndecl, m_arg);
     if (warned)
       inform_filedescriptor_attribute (DIRS_READ_WRITE);
     return warned;
@@ -748,12 +738,10 @@  public:
   }
 
   bool
-  emit (rich_location *rich_loc, logger *) final override
+  emit (diagnostic_emission_context &ctxt) final override
   {
-    bool warned;
-    warned = warning_at (rich_loc, get_controlling_option (),
-			"%qE on possibly invalid file descriptor %qE",
-			m_callee_fndecl, m_arg);
+    bool warned = ctxt.warn ("%qE on possibly invalid file descriptor %qE",
+			     m_callee_fndecl, m_arg);
     if (warned)
      inform_filedescriptor_attribute (DIRS_READ_WRITE);
     return warned;
@@ -859,14 +847,12 @@  public:
   }
 
   bool
-  emit (rich_location *rich_loc, logger *) final override
+  emit (diagnostic_emission_context &ctxt) final override
   {
     /* CWE-666: Operation on Resource in Wrong Phase of Lifetime.  */
-    diagnostic_metadata m;
-    m.add_cwe (666);
-    return warning_at (rich_loc, get_controlling_option (),
-		       "%qE on file descriptor %qE in wrong phase",
-		       m_callee_fndecl, m_arg);
+    ctxt.add_cwe (666);
+    return ctxt.warn ("%qE on file descriptor %qE in wrong phase",
+		      m_callee_fndecl, m_arg);
   }
 
   label_text
@@ -1019,25 +1005,22 @@  public:
   }
 
   bool
-  emit (rich_location *rich_loc, logger *) final override
+  emit (diagnostic_emission_context &ctxt) final override
   {
     switch (m_expected_type)
       {
       default:
 	gcc_unreachable ();
       case EXPECTED_TYPE_SOCKET:
-	return warning_at (rich_loc, get_controlling_option (),
-			   "%qE on non-socket file descriptor %qE",
-			   m_callee_fndecl, m_arg);
+	return ctxt.warn ("%qE on non-socket file descriptor %qE",
+			  m_callee_fndecl, m_arg);
       case EXPECTED_TYPE_STREAM_SOCKET:
 	if (m_sm.is_datagram_socket_fd_p (m_actual_state))
-	  return warning_at (rich_loc, get_controlling_option (),
-			     "%qE on datagram socket file descriptor %qE",
-			     m_callee_fndecl, m_arg);
+	  return ctxt.warn ("%qE on datagram socket file descriptor %qE",
+			    m_callee_fndecl, m_arg);
 	else
-	  return warning_at (rich_loc, get_controlling_option (),
-			     "%qE on non-stream-socket file descriptor %qE",
-			     m_callee_fndecl, m_arg);
+	  return ctxt.warn ("%qE on non-stream-socket file descriptor %qE",
+			    m_callee_fndecl, m_arg);
       }
   }
 
diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc
index 0252b3922d4..f8e31f873a5 100644
--- a/gcc/analyzer/sm-file.cc
+++ b/gcc/analyzer/sm-file.cc
@@ -29,7 +29,6 @@  along with GCC; see the file COPYING3.  If not see
 #include "gimple.h"
 #include "options.h"
 #include "diagnostic-path.h"
-#include "diagnostic-metadata.h"
 #include "analyzer/analyzer.h"
 #include "diagnostic-event-id.h"
 #include "analyzer/analyzer-logging.h"
@@ -176,14 +175,12 @@  public:
     return OPT_Wanalyzer_double_fclose;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
     /* CWE-1341: Multiple Releases of Same Resource or Handle.  */
-    m.add_cwe (1341);
-    return warning_meta (rich_loc, m, get_controlling_option (),
-			 "double %<fclose%> of FILE %qE",
-			 m_arg);
+    ctxt.add_cwe (1341);
+    return ctxt.warn ("double %<fclose%> of FILE %qE",
+		      m_arg);
   }
 
   label_text describe_state_change (const evdesc::state_change &change)
@@ -224,19 +221,15 @@  public:
     return OPT_Wanalyzer_file_leak;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
     /* CWE-775: "Missing Release of File Descriptor or Handle after
        Effective Lifetime". */
-    m.add_cwe (775);
+    ctxt.add_cwe (775);
     if (m_arg)
-      return warning_meta (rich_loc, m, get_controlling_option (),
-			   "leak of FILE %qE",
-			   m_arg);
+      return ctxt.warn ("leak of FILE %qE", m_arg);
     else
-      return warning_meta (rich_loc, m, get_controlling_option (),
-			   "leak of FILE");
+      return ctxt.warn ("leak of FILE");
   }
 
   label_text describe_state_change (const evdesc::state_change &change)
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index 5af654414b4..bb784448397 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -30,7 +30,6 @@  along with GCC; see the file COPYING3.  If not see
 #include "options.h"
 #include "bitmap.h"
 #include "diagnostic-path.h"
-#include "diagnostic-metadata.h"
 #include "analyzer/analyzer.h"
 #include "diagnostic-event-id.h"
 #include "analyzer/analyzer-logging.h"
@@ -840,23 +839,20 @@  public:
     return OPT_Wanalyzer_mismatching_deallocation;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     auto_diagnostic_group d;
-    diagnostic_metadata m;
-    m.add_cwe (762); /* CWE-762: Mismatched Memory Management Routines.  */
+    ctxt.add_cwe (762); /* CWE-762: Mismatched Memory Management Routines.  */
     if (const deallocator *expected_dealloc
 	  = m_expected_deallocators->maybe_get_single ())
-      return warning_meta (rich_loc, m, get_controlling_option (),
-			   "%qE should have been deallocated with %qs"
-			   " but was deallocated with %qs",
-			   m_arg, expected_dealloc->m_name,
-			   m_actual_dealloc->m_name);
+      return ctxt.warn ("%qE should have been deallocated with %qs"
+			" but was deallocated with %qs",
+			m_arg, expected_dealloc->m_name,
+			m_actual_dealloc->m_name);
     else
-      return warning_meta (rich_loc, m, get_controlling_option (),
-			   "%qs called on %qE returned from a mismatched"
-			   " allocation function",
-			   m_actual_dealloc->m_name, m_arg);
+      return ctxt.warn ("%qs called on %qE returned from a mismatched"
+			" allocation function",
+			m_actual_dealloc->m_name, m_arg);
   }
 
   label_text describe_state_change (const evdesc::state_change &change)
@@ -919,13 +915,11 @@  public:
     return OPT_Wanalyzer_double_free;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     auto_diagnostic_group d;
-    diagnostic_metadata m;
-    m.add_cwe (415); /* CWE-415: Double Free.  */
-    return warning_meta (rich_loc, m, get_controlling_option (),
-			 "double-%qs of %qE", m_funcname, m_arg);
+    ctxt.add_cwe (415); /* CWE-415: Double Free.  */
+    return ctxt.warn ("double-%qs of %qE", m_funcname, m_arg);
   }
 
   label_text describe_state_change (const evdesc::state_change &change)
@@ -1015,13 +1009,11 @@  public:
     return OPT_Wanalyzer_possible_null_dereference;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
-    diagnostic_metadata m;
-    m.add_cwe (690);
-    return warning_meta (rich_loc, m, get_controlling_option (),
-			 "dereference of possibly-NULL %qE", m_arg);
+    ctxt.add_cwe (690);
+    return ctxt.warn ("dereference of possibly-NULL %qE", m_arg);
   }
 
   label_text describe_final_event (const evdesc::final_event &ev) final override
@@ -1104,16 +1096,14 @@  public:
     return OPT_Wanalyzer_possible_null_argument;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
     auto_diagnostic_group d;
-    diagnostic_metadata m;
-    m.add_cwe (690);
+    ctxt.add_cwe (690);
     bool warned
-      = warning_meta (rich_loc, m, get_controlling_option (),
-		      "use of possibly-NULL %qE where non-null expected",
-		      m_arg);
+      = ctxt.warn ("use of possibly-NULL %qE where non-null expected",
+		   m_arg);
     if (warned)
       inform_nonnull_attribute (m_fndecl, m_arg_idx);
     return warned;
@@ -1157,13 +1147,11 @@  public:
 
   bool terminate_path_p () const final override { return true; }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     /* CWE-476: NULL Pointer Dereference.  */
-    diagnostic_metadata m;
-    m.add_cwe (476);
-    return warning_meta (rich_loc, m, get_controlling_option (),
-			 "dereference of NULL %qE", m_arg);
+    ctxt.add_cwe (476);
+    return ctxt.warn ("dereference of NULL %qE", m_arg);
   }
 
   label_text describe_return_of_state (const evdesc::return_of_state &info)
@@ -1227,21 +1215,18 @@  public:
 
   bool terminate_path_p () const final override { return true; }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     /* CWE-476: NULL Pointer Dereference.  */
     auto_diagnostic_group d;
-    diagnostic_metadata m;
-    m.add_cwe (476);
+    ctxt.add_cwe (476);
 
     bool warned;
     if (zerop (m_arg))
-      warned = warning_meta (rich_loc, m, get_controlling_option (),
-			     "use of NULL where non-null expected");
+      warned = ctxt.warn ("use of NULL where non-null expected");
     else
-      warned = warning_meta (rich_loc, m, get_controlling_option (),
-			     "use of NULL %qE where non-null expected",
-			     m_arg);
+      warned = ctxt.warn ("use of NULL %qE where non-null expected",
+			  m_arg);
     if (warned)
       inform_nonnull_attribute (m_fndecl, m_arg_idx);
     return warned;
@@ -1284,14 +1269,12 @@  public:
     return OPT_Wanalyzer_use_after_free;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     /* CWE-416: Use After Free.  */
-    diagnostic_metadata m;
-    m.add_cwe (416);
-    return warning_meta (rich_loc, m, get_controlling_option (),
-			 "use after %<%s%> of %qE",
-			 m_deallocator->m_name, m_arg);
+    ctxt.add_cwe (416);
+    return ctxt.warn ("use after %<%s%> of %qE",
+		      m_deallocator->m_name, m_arg);
   }
 
   label_text describe_state_change (const evdesc::state_change &change)
@@ -1378,17 +1361,14 @@  public:
     return OPT_Wanalyzer_malloc_leak;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     /* "CWE-401: Missing Release of Memory after Effective Lifetime".  */
-    diagnostic_metadata m;
-    m.add_cwe (401);
+    ctxt.add_cwe (401);
     if (m_arg)
-      return warning_meta (rich_loc, m, get_controlling_option (),
-			   "leak of %qE", m_arg);
+      return ctxt.warn ("leak of %qE", m_arg);
     else
-      return warning_meta (rich_loc, m, get_controlling_option (),
-			   "leak of %qs", "<unknown>");
+      return ctxt.warn ("leak of %qs", "<unknown>");
   }
 
   label_text describe_state_change (const evdesc::state_change &change)
@@ -1452,11 +1432,10 @@  public:
     return OPT_Wanalyzer_free_of_non_heap;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     auto_diagnostic_group d;
-    diagnostic_metadata m;
-    m.add_cwe (590); /* CWE-590: Free of Memory not on the Heap.  */
+    ctxt.add_cwe (590); /* CWE-590: Free of Memory not on the Heap.  */
     switch (get_memory_space ())
       {
       default:
@@ -1466,16 +1445,14 @@  public:
       case MEMSPACE_CODE:
       case MEMSPACE_GLOBALS:
       case MEMSPACE_READONLY_DATA:
-	return warning_meta (rich_loc, m, get_controlling_option (),
-			     "%<%s%> of %qE which points to memory"
-			     " not on the heap",
-			     m_funcname, m_arg);
+	return ctxt.warn ("%<%s%> of %qE which points to memory"
+			  " not on the heap",
+			  m_funcname, m_arg);
 	break;
       case MEMSPACE_STACK:
-	return warning_meta (rich_loc, m, get_controlling_option (),
-			     "%<%s%> of %qE which points to memory"
-			     " on the stack",
-			     m_funcname, m_arg);
+	return ctxt.warn ("%<%s%> of %qE which points to memory"
+			  " on the stack",
+			  m_funcname, m_arg);
 	break;
       }
   }
@@ -1531,7 +1508,7 @@  public:
     return OPT_Wanalyzer_deref_before_check;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     /* Don't emit the warning if we can't show where the deref
        and the check occur.  */
@@ -1605,10 +1582,9 @@  public:
 			 m_deref_enode->get_supernode ()->m_bb))
       return false;
 
-    return warning_at (rich_loc, get_controlling_option (),
-		       "check of %qE for NULL after already"
-		       " dereferencing it",
-		       m_arg);
+    return ctxt.warn ("check of %qE for NULL after already"
+		      " dereferencing it",
+		      m_arg);
   }
 
   label_text describe_state_change (const evdesc::state_change &change)
diff --git a/gcc/analyzer/sm-pattern-test.cc b/gcc/analyzer/sm-pattern-test.cc
index 4c88bcaeb2b..cd594e09c00 100644
--- a/gcc/analyzer/sm-pattern-test.cc
+++ b/gcc/analyzer/sm-pattern-test.cc
@@ -31,7 +31,6 @@  along with GCC; see the file COPYING3.  If not see
 #include "gimple.h"
 #include "tree-pretty-print.h"
 #include "diagnostic-path.h"
-#include "diagnostic-metadata.h"
 #include "analyzer/analyzer.h"
 #include "diagnostic-event-id.h"
 #include "analyzer/analyzer-logging.h"
@@ -92,11 +91,10 @@  public:
     return 0;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    return warning_at (rich_loc, get_controlling_option (),
-		       "pattern match on %<%E %s %E%>",
-		       m_lhs, op_symbol_code (m_op), m_rhs);
+    return ctxt.warn ("pattern match on %<%E %s %E%>",
+		      m_lhs, op_symbol_code (m_op), m_rhs);
   }
 
 private:
diff --git a/gcc/analyzer/sm-sensitive.cc b/gcc/analyzer/sm-sensitive.cc
index 0597e390b3b..4776d6465bb 100644
--- a/gcc/analyzer/sm-sensitive.cc
+++ b/gcc/analyzer/sm-sensitive.cc
@@ -30,7 +30,6 @@  along with GCC; see the file COPYING3.  If not see
 #include "gimple.h"
 #include "options.h"
 #include "diagnostic-path.h"
-#include "diagnostic-metadata.h"
 #include "analyzer/analyzer.h"
 #include "diagnostic-event-id.h"
 #include "analyzer/analyzer-logging.h"
@@ -95,15 +94,12 @@  public:
     return OPT_Wanalyzer_exposure_through_output_file;
   }
 
-  bool emit (rich_location *rich_loc,
-	     logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
     /* CWE-532: Information Exposure Through Log Files */
-    m.add_cwe (532);
-    return warning_meta (rich_loc, m, get_controlling_option (),
-			 "sensitive value %qE written to output file",
-			 m_arg);
+    ctxt.add_cwe (532);
+    return ctxt.warn ("sensitive value %qE written to output file",
+		      m_arg);
   }
 
   label_text describe_state_change (const evdesc::state_change &change)
diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc
index 9ebcbdbc8e0..6bca395ac5c 100644
--- a/gcc/analyzer/sm-signal.cc
+++ b/gcc/analyzer/sm-signal.cc
@@ -32,7 +32,6 @@  along with GCC; see the file COPYING3.  If not see
 #include "options.h"
 #include "bitmap.h"
 #include "diagnostic-path.h"
-#include "diagnostic-metadata.h"
 #include "analyzer/analyzer.h"
 #include "diagnostic-event-id.h"
 #include "analyzer/analyzer-logging.h"
@@ -114,15 +113,13 @@  public:
     return OPT_Wanalyzer_unsafe_call_within_signal_handler;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
     auto_diagnostic_group d;
-    diagnostic_metadata m;
     /* CWE-479: Signal Handler Use of a Non-reentrant Function.  */
-    m.add_cwe (479);
-    if (warning_meta (rich_loc, m, get_controlling_option (),
-		      "call to %qD from within signal handler",
-		      m_unsafe_fndecl))
+    ctxt.add_cwe (479);
+    if (ctxt.warn ("call to %qD from within signal handler",
+		   m_unsafe_fndecl))
       {
 	/* If we know a possible alternative function, add a note
 	   suggesting the replacement.  */
diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc
index dfd5f7fa5d2..d01e3f03951 100644
--- a/gcc/analyzer/sm-taint.cc
+++ b/gcc/analyzer/sm-taint.cc
@@ -31,7 +31,6 @@  along with GCC; see the file COPYING3.  If not see
 #include "gimple.h"
 #include "options.h"
 #include "diagnostic-path.h"
-#include "diagnostic-metadata.h"
 #include "analyzer/analyzer.h"
 #include "analyzer/analyzer-logging.h"
 #include "gimple-iterator.h"
@@ -211,33 +210,29 @@  public:
     return OPT_Wanalyzer_tainted_array_index;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
     /* CWE-129: "Improper Validation of Array Index".  */
-    m.add_cwe (129);
+    ctxt.add_cwe (129);
     if (m_arg)
       switch (m_has_bounds)
 	{
 	default:
 	  gcc_unreachable ();
 	case BOUNDS_NONE:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value %qE"
-			       " in array lookup without bounds checking",
-			       m_arg);
+	  return ctxt.warn ("use of attacker-controlled value %qE"
+			    " in array lookup without bounds checking",
+			    m_arg);
 	  break;
 	case BOUNDS_UPPER:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value %qE"
-			       " in array lookup without checking for negative",
-			       m_arg);
+	  return ctxt.warn ("use of attacker-controlled value %qE"
+			    " in array lookup without checking for negative",
+			    m_arg);
 	  break;
 	case BOUNDS_LOWER:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value %qE"
-			       " in array lookup without upper-bounds checking",
-			       m_arg);
+	  return ctxt.warn ("use of attacker-controlled value %qE"
+			    " in array lookup without upper-bounds checking",
+			    m_arg);
 	  break;
 	}
     else
@@ -246,21 +241,18 @@  public:
 	default:
 	  gcc_unreachable ();
 	case BOUNDS_NONE:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value"
-			       " in array lookup without bounds checking");
+	  return ctxt.warn ("use of attacker-controlled value"
+			    " in array lookup without bounds checking");
 	  break;
 	case BOUNDS_UPPER:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value"
-			       " in array lookup without checking for"
-			       " negative");
+	  return ctxt.warn ("use of attacker-controlled value"
+			    " in array lookup without checking for"
+			    " negative");
 	  break;
 	case BOUNDS_LOWER:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value"
-			       " in array lookup without upper-bounds"
-			       " checking");
+	  return ctxt.warn ("use of attacker-controlled value"
+			    " in array lookup without upper-bounds"
+			    " checking");
 	  break;
 	}
   }
@@ -327,33 +319,29 @@  public:
     return OPT_Wanalyzer_tainted_offset;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
     /* CWE-823: "Use of Out-of-range Pointer Offset".  */
-    m.add_cwe (823);
+    ctxt.add_cwe (823);
     if (m_arg)
       switch (m_has_bounds)
 	{
 	default:
 	  gcc_unreachable ();
 	case BOUNDS_NONE:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value %qE as offset"
-			       " without bounds checking",
-			       m_arg);
+	  return ctxt.warn ("use of attacker-controlled value %qE as offset"
+			    " without bounds checking",
+			    m_arg);
 	  break;
 	case BOUNDS_UPPER:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value %qE as offset"
-			       " without lower-bounds checking",
-			       m_arg);
+	  return ctxt.warn ("use of attacker-controlled value %qE as offset"
+			    " without lower-bounds checking",
+			    m_arg);
 	  break;
 	case BOUNDS_LOWER:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value %qE as offset"
-			       " without upper-bounds checking",
-			       m_arg);
+	  return ctxt.warn ("use of attacker-controlled value %qE as offset"
+			    " without upper-bounds checking",
+			    m_arg);
 	  break;
 	}
     else
@@ -362,19 +350,16 @@  public:
 	default:
 	  gcc_unreachable ();
 	case BOUNDS_NONE:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value as offset"
-			       " without bounds checking");
+	  return ctxt.warn ("use of attacker-controlled value as offset"
+			    " without bounds checking");
 	  break;
 	case BOUNDS_UPPER:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value as offset"
-			       " without lower-bounds checking");
+	  return ctxt.warn ("use of attacker-controlled value as offset"
+			    " without lower-bounds checking");
 	  break;
 	case BOUNDS_LOWER:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value as offset"
-			       " without upper-bounds checking");
+	  return ctxt.warn ("use of attacker-controlled value as offset"
+			    " without upper-bounds checking");
 	  break;
 	}
   }
@@ -437,33 +422,29 @@  public:
     return OPT_Wanalyzer_tainted_size;
   }
 
-  bool emit (rich_location *rich_loc, logger *) override
+  bool emit (diagnostic_emission_context &ctxt) override
   {
     /* "CWE-129: Improper Validation of Array Index".  */
-    diagnostic_metadata m;
-    m.add_cwe (129);
+    ctxt.add_cwe (129);
     if (m_arg)
       switch (m_has_bounds)
 	{
 	default:
 	  gcc_unreachable ();
 	case BOUNDS_NONE:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value %qE as size"
-			       " without bounds checking",
-			       m_arg);
+	  return ctxt.warn ("use of attacker-controlled value %qE as size"
+			    " without bounds checking",
+			    m_arg);
 	  break;
 	case BOUNDS_UPPER:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value %qE as size"
-			       " without lower-bounds checking",
-			       m_arg);
+	  return ctxt.warn ("use of attacker-controlled value %qE as size"
+			    " without lower-bounds checking",
+			    m_arg);
 	  break;
 	case BOUNDS_LOWER:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value %qE as size"
-			       " without upper-bounds checking",
-			       m_arg);
+	  return ctxt.warn ("use of attacker-controlled value %qE as size"
+			    " without upper-bounds checking",
+			    m_arg);
 	  break;
 	}
     else
@@ -472,19 +453,16 @@  public:
 	default:
 	  gcc_unreachable ();
 	case BOUNDS_NONE:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value as size"
-			       " without bounds checking");
+	  return ctxt.warn ("use of attacker-controlled value as size"
+			    " without bounds checking");
 	  break;
 	case BOUNDS_UPPER:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value as size"
-			       " without lower-bounds checking");
+	  return ctxt.warn ("use of attacker-controlled value as size"
+			    " without lower-bounds checking");
 	  break;
 	case BOUNDS_LOWER:
-	  return warning_meta (rich_loc, m, get_controlling_option (),
-			       "use of attacker-controlled value as size"
-			       " without upper-bounds checking");
+	  return ctxt.warn ("use of attacker-controlled value as size"
+			    " without upper-bounds checking");
 	  break;
 	}
   }
@@ -547,9 +525,9 @@  public:
     return "tainted_access_attrib_size";
   }
 
-  bool emit (rich_location *rich_loc, logger *logger) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    bool warned = tainted_size::emit (rich_loc, logger);
+    bool warned = tainted_size::emit (ctxt);
     if (warned)
       {
 	inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
@@ -583,20 +561,17 @@  public:
     return OPT_Wanalyzer_tainted_divisor;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
     /* CWE-369: "Divide By Zero".  */
-    m.add_cwe (369);
+    ctxt.add_cwe (369);
     if (m_arg)
-      return warning_meta (rich_loc, m, get_controlling_option (),
-			   "use of attacker-controlled value %qE as divisor"
-			   " without checking for zero",
-			   m_arg);
+      return ctxt.warn ("use of attacker-controlled value %qE as divisor"
+			" without checking for zero",
+			m_arg);
     else
-      return warning_meta (rich_loc, m, get_controlling_option (),
-			   "use of attacker-controlled value as divisor"
-			   " without checking for zero");
+      return ctxt.warn ("use of attacker-controlled value as divisor"
+			" without checking for zero");
   }
 
   label_text describe_final_event (const evdesc::final_event &ev) final override
@@ -645,11 +620,10 @@  public:
     return OPT_Wanalyzer_tainted_allocation_size;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
     /* "CWE-789: Memory Allocation with Excessive Size Value".  */
-    m.add_cwe (789);
+    ctxt.add_cwe (789);
 
     bool warned;
     if (m_arg)
@@ -658,24 +632,21 @@  public:
 	default:
 	  gcc_unreachable ();
 	case BOUNDS_NONE:
-	  warned = warning_meta (rich_loc, m, get_controlling_option (),
-				 "use of attacker-controlled value %qE as"
-				 " allocation size without bounds checking",
-				 m_arg);
+	  warned = ctxt.warn ("use of attacker-controlled value %qE as"
+			      " allocation size without bounds checking",
+			      m_arg);
 	  break;
 	case BOUNDS_UPPER:
-	  warned = warning_meta (rich_loc, m, get_controlling_option (),
-				 "use of attacker-controlled value %qE as"
-				 " allocation size without"
-				 " lower-bounds checking",
-				 m_arg);
+	  warned = ctxt.warn ("use of attacker-controlled value %qE as"
+			      " allocation size without"
+			      " lower-bounds checking",
+			      m_arg);
 	  break;
 	case BOUNDS_LOWER:
-	  warned = warning_meta (rich_loc, m, get_controlling_option (),
-				 "use of attacker-controlled value %qE as"
-				 " allocation size without"
-				 " upper-bounds checking",
-				 m_arg);
+	  warned = ctxt.warn ("use of attacker-controlled value %qE as"
+			      " allocation size without"
+			      " upper-bounds checking",
+			      m_arg);
 	  break;
 	}
     else
@@ -684,27 +655,24 @@  public:
 	default:
 	  gcc_unreachable ();
 	case BOUNDS_NONE:
-	  warned = warning_meta (rich_loc, m, get_controlling_option (),
-				 "use of attacker-controlled value as"
-				 " allocation size without bounds"
-				 " checking");
+	  warned = ctxt.warn ("use of attacker-controlled value as"
+			      " allocation size without bounds"
+			      " checking");
 	  break;
 	case BOUNDS_UPPER:
-	  warned = warning_meta (rich_loc, m, get_controlling_option (),
-				 "use of attacker-controlled value as"
-				 " allocation size without"
-				 " lower-bounds checking");
+	  warned = ctxt.warn ("use of attacker-controlled value as"
+			      " allocation size without"
+			      " lower-bounds checking");
 	  break;
 	case BOUNDS_LOWER:
-	  warned = warning_meta (rich_loc, m, get_controlling_option (),
-				 "use of attacker-controlled value as"
-				 " allocation size without"
-				 " upper-bounds checking");
+	  warned = ctxt.warn ("use of attacker-controlled value as"
+			      " allocation size without"
+			      " upper-bounds checking");
 	  break;
 	}
     if (warned)
       {
-	location_t loc = rich_loc->get_loc ();
+	const location_t loc = ctxt.get_location ();
 	switch (m_mem_space)
 	  {
 	  default:
@@ -800,15 +768,13 @@  public:
     return OPT_Wanalyzer_tainted_assertion;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
     /* "CWE-617: Reachable Assertion".  */
-    m.add_cwe (617);
+    ctxt.add_cwe (617);
 
-    return warning_meta (rich_loc, m, get_controlling_option (),
-			 "use of attacked-controlled value in"
-			 " condition for assertion");
+    return ctxt.warn ("use of attacked-controlled value in"
+		      " condition for assertion");
   }
 
   location_t fixup_location (location_t loc,
diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc
index 602508598cc..be1802e1e5a 100644
--- a/gcc/analyzer/store.cc
+++ b/gcc/analyzer/store.cc
@@ -38,7 +38,6 @@  along with GCC; see the file COPYING3.  If not see
 #include "fold-const.h"
 #include "tree-pretty-print.h"
 #include "diagnostic-color.h"
-#include "diagnostic-metadata.h"
 #include "bitmap.h"
 #include "selftest.h"
 #include "analyzer/analyzer.h"
diff --git a/gcc/analyzer/varargs.cc b/gcc/analyzer/varargs.cc
index f79b2a7d7b5..7cdfb203a33 100644
--- a/gcc/analyzer/varargs.cc
+++ b/gcc/analyzer/varargs.cc
@@ -41,7 +41,6 @@  along with GCC; see the file COPYING3.  If not see
 #include "analyzer/supergraph.h"
 #include "analyzer/diagnostic-manager.h"
 #include "analyzer/exploded-graph.h"
-#include "diagnostic-metadata.h"
 #include "analyzer/call-details.h"
 
 #if ENABLE_ANALYZER
@@ -403,11 +402,9 @@  public:
 	    && 0 == strcmp (m_usage_fnname, other.m_usage_fnname));
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    auto_diagnostic_group d;
-    return warning_at (rich_loc, get_controlling_option (),
-		       "%qs after %qs", m_usage_fnname, "va_end");
+    return ctxt.warn ("%qs after %qs", m_usage_fnname, "va_end");
   }
 
   const char *get_kind () const final override
@@ -478,11 +475,9 @@  public:
     return va_list_sm_diagnostic::subclass_equal_p (other);
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    auto_diagnostic_group d;
-    return warning_at (rich_loc, get_controlling_option (),
-		       "missing call to %qs", "va_end");
+    return ctxt.warn ("missing call to %qs", "va_end");
   }
 
   const char *get_kind () const final override { return "va_list_leak"; }
@@ -892,18 +887,15 @@  public:
     return OPT_Wanalyzer_va_arg_type_mismatch;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    auto_diagnostic_group d;
-    diagnostic_metadata m;
     /* "CWE-686: Function Call With Incorrect Argument Type".  */
-    m.add_cwe (686);
+    ctxt.add_cwe (686);
     bool warned
-      = warning_meta (rich_loc, m, get_controlling_option (),
-		      "%<va_arg%> expected %qT but received %qT"
-		      " for variadic argument %i of %qE",
-		      m_expected_type, m_actual_type,
-		      get_variadic_index_for_diagnostic (), m_va_list_tree);
+      = ctxt.warn ("%<va_arg%> expected %qT but received %qT"
+		   " for variadic argument %i of %qE",
+		   m_expected_type, m_actual_type,
+		   get_variadic_index_for_diagnostic (), m_va_list_tree);
     return warned;
   }
 
@@ -942,15 +934,12 @@  public:
     return OPT_Wanalyzer_va_list_exhausted;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    auto_diagnostic_group d;
-    diagnostic_metadata m;
     /* CWE-685: Function Call With Incorrect Number of Arguments.  */
-    m.add_cwe (685);
-    bool warned = warning_meta (rich_loc, m, get_controlling_option (),
-				"%qE has no more arguments (%i consumed)",
-				m_va_list_tree, get_num_consumed ());
+    ctxt.add_cwe (685);
+    bool warned = ctxt.warn ("%qE has no more arguments (%i consumed)",
+			     m_va_list_tree, get_num_consumed ());
     return warned;
   }
 
diff --git a/gcc/diagnostic-core.h b/gcc/diagnostic-core.h
index 04eba3d140e..965c9e986ab 100644
--- a/gcc/diagnostic-core.h
+++ b/gcc/diagnostic-core.h
@@ -123,6 +123,12 @@  extern bool emit_diagnostic (diagnostic_t, rich_location *, int,
 			     const char *, ...) ATTRIBUTE_GCC_DIAG(4,5);
 extern bool emit_diagnostic_valist (diagnostic_t, location_t, int, const char *,
 				    va_list *) ATTRIBUTE_GCC_DIAG (4,0);
+extern bool emit_diagnostic_valist (diagnostic_t,
+				    rich_location *,
+				    const diagnostic_metadata *,
+				    int,
+				    const char *,
+				    va_list *) ATTRIBUTE_GCC_DIAG (5,0);
 extern bool seen_error (void);
 
 #ifdef BUFSIZ
diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc
index 1bb728654e6..67775929f85 100644
--- a/gcc/diagnostic-format-sarif.cc
+++ b/gcc/diagnostic-format-sarif.cc
@@ -569,16 +569,20 @@  sarif_builder::make_result_object (diagnostic_context *context,
       free (rule_id);
     }
 
-  /* "taxa" property (SARIF v2.1.0 section 3.27.8).  */
   if (diagnostic->metadata)
-    if (int cwe_id = diagnostic->metadata->get_cwe ())
-      {
-	json::array *taxa_arr = new json::array ();
-	json::object *cwe_id_obj
-	  = make_reporting_descriptor_reference_object_for_cwe_id (cwe_id);
-	taxa_arr->append (cwe_id_obj);
-	result_obj->set ("taxa", taxa_arr);
-      }
+    {
+      /* "taxa" property (SARIF v2.1.0 section 3.27.8).  */
+      if (int cwe_id = diagnostic->metadata->get_cwe ())
+	{
+	  json::array *taxa_arr = new json::array ();
+	  json::object *cwe_id_obj
+	    = make_reporting_descriptor_reference_object_for_cwe_id (cwe_id);
+	  taxa_arr->append (cwe_id_obj);
+	  result_obj->set ("taxa", taxa_arr);
+	}
+
+      diagnostic->metadata->maybe_add_sarif_properties (*result_obj);
+    }
 
   /* "level" property (SARIF v2.1.0 section 3.27.10).  */
   if (const char *sarif_level = maybe_get_sarif_level (diagnostic->kind))
diff --git a/gcc/diagnostic-metadata.h b/gcc/diagnostic-metadata.h
index 8e06c89d74a..1af80fd9be7 100644
--- a/gcc/diagnostic-metadata.h
+++ b/gcc/diagnostic-metadata.h
@@ -21,6 +21,8 @@  along with GCC; see the file COPYING3.  If not see
 #ifndef GCC_DIAGNOSTIC_METADATA_H
 #define GCC_DIAGNOSTIC_METADATA_H
 
+class sarif_object;
+
 /* A bundle of additional metadata that can be associated with a
    diagnostic.
 
@@ -63,6 +65,14 @@  class diagnostic_metadata
   };
 
   diagnostic_metadata () : m_cwe (0) {}
+  virtual ~diagnostic_metadata () {}
+
+  /* Hook for SARIF output to allow for adding diagnostic-specific
+     properties to  the result object's property bag.  */
+  virtual void
+  maybe_add_sarif_properties (sarif_object &/*result_obj*/) const
+  {
+  }
 
   void add_cwe (int cwe) { m_cwe = cwe; }
   int get_cwe () const { return m_cwe; }
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index b4ebcd29457..70874b76620 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -1838,6 +1838,18 @@  emit_diagnostic_valist (diagnostic_t kind, location_t location, int opt,
   return diagnostic_impl (&richloc, NULL, opt, gmsgid, ap, kind);
 }
 
+/* As above, but with rich_location and metadata.  */
+
+bool
+emit_diagnostic_valist (diagnostic_t kind,
+			rich_location *richloc,
+			const diagnostic_metadata *metadata,
+			int opt,
+			const char *gmsgid, va_list *ap)
+{
+  return diagnostic_impl (richloc, metadata, opt, gmsgid, ap, kind);
+}
+
 /* An informative note at LOCATION.  Use this for additional details on an error
    message.  */
 void
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-accept.c b/gcc/testsuite/gcc.dg/analyzer/fd-accept.c
index cce95555421..d07ab154d0f 100644
--- a/gcc/testsuite/gcc.dg/analyzer/fd-accept.c
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-accept.c
@@ -65,7 +65,7 @@  int test_accept_on_accept (int fd_a)
   if (fd_b == -1)
     return -1;
 
-  int fd_c = accept (fd_b, NULL, 0);  /* { dg-warning "'accept' on file descriptor 'fd_b' in wrong phase \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */
+  int fd_c = accept (fd_b, NULL, 0);  /* { dg-warning "'accept' on file descriptor 'fd_b' in wrong phase \\\[CWE-666\\\] \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */
   /* { dg-message "'accept' expects a listening stream socket file descriptor but 'fd_b' is connected" "final event" { target *-*-* } .-1 } */
 
   return fd_b;
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-bind.c b/gcc/testsuite/gcc.dg/analyzer/fd-bind.c
index 2a5cee58230..2f69841bfd2 100644
--- a/gcc/testsuite/gcc.dg/analyzer/fd-bind.c
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-bind.c
@@ -35,7 +35,7 @@  void test_double_bind (int fd, const char *sockname)
   addr.sun_family = AF_UNIX;
   strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
   bind (fd, (struct sockaddr *)&addr, sizeof (addr));
-  bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on file descriptor 'fd' in wrong phase \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */
+  bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on file descriptor 'fd' in wrong phase \\\[CWE-666\\\] \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */
   /* { dg-message "'bind' expects a new socket file descriptor but 'fd' has already been bound" "final event" { target *-*-* } .-1 } */
 }
 
@@ -71,7 +71,7 @@  void test_bind_after_accept (int fd, const char *sockname)
   memset (&addr, 0, sizeof (addr));
   addr.sun_family = AF_UNIX;
   strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1);
-  bind (afd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on file descriptor 'afd' in wrong phase \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */
+  bind (afd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on file descriptor 'afd' in wrong phase \\\[CWE-666\\\] \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */
   /* { dg-message "'bind' expects a new socket file descriptor but 'afd' is already connected" "final event" { target *-*-* } .-1 } */
 
   close (afd);
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-socket-misuse.c b/gcc/testsuite/gcc.dg/analyzer/fd-socket-misuse.c
index 87e8967ff21..914948644bb 100644
--- a/gcc/testsuite/gcc.dg/analyzer/fd-socket-misuse.c
+++ b/gcc/testsuite/gcc.dg/analyzer/fd-socket-misuse.c
@@ -18,7 +18,7 @@  void test_read_on_new_socket (void *buf)
   int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */
   if (fd == -1)
     return;
-  read (fd, buf, 1); /* { dg-warning "'read' on file descriptor 'fd' in wrong phase \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */
+  read (fd, buf, 1); /* { dg-warning "'read' on file descriptor 'fd' in wrong phase \\\[CWE-666\\\] \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */
   /* { dg-message "'read' expects a stream socket to be connected via 'accept' but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */
   close (fd);
 }
diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c b/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c
index a364c8a678b..b5814dd709e 100644
--- a/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c
+++ b/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c
@@ -310,18 +310,16 @@  public:
   }
 
   bool
-  emit (rich_location *rich_loc, logger *) final override
+  emit (diagnostic_emission_context &ctxt) final override
   {
-    diagnostic_metadata m;
     bool warned;
     // just assuming constants for now
     auto actual_refcnt
 	= m_actual_refcnt->dyn_cast_constant_svalue ()->get_constant ();
     auto ob_refcnt = m_ob_refcnt->dyn_cast_constant_svalue ()->get_constant ();
-    warned = warning_meta (rich_loc, m, get_controlling_option (),
-			   "expected %qE to have "
-			   "reference count: %qE but ob_refcnt field is: %qE",
-			   m_reg_tree, actual_refcnt, ob_refcnt);
+    warned = ctxt.warn ("expected %qE to have "
+			"reference count: %qE but ob_refcnt field is: %qE",
+			m_reg_tree, actual_refcnt, ob_refcnt);
 
     // location_t loc = rich_loc->get_loc ();
     // foo (loc);
diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c b/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c
index e0fc9cd3e47..6ea6c03e1d5 100644
--- a/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c
+++ b/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c
@@ -155,10 +155,9 @@  class double_save_thread : public gil_diagnostic
     return m_call == sub_other.m_call;
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    return warning_at (rich_loc, get_controlling_option (),
-		       "nested usage of %qs", "Py_BEGIN_ALLOW_THREADS");
+    return ctxt.warn ("nested usage of %qs", "Py_BEGIN_ALLOW_THREADS");
   }
 
   label_text describe_final_event (const evdesc::final_event &ev) final override
@@ -194,19 +193,16 @@  class fncall_without_gil : public gil_diagnostic
 	    && m_arg_idx == sub_other.m_arg_idx);
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    auto_diagnostic_group d;
     if (m_callee_fndecl)
-      return warning_at (rich_loc, get_controlling_option (),
-			 "use of PyObject as argument %i of %qE"
-			 " without the GIL",
-			 m_arg_idx + 1, m_callee_fndecl);
+      return ctxt.warn ("use of PyObject as argument %i of %qE"
+			" without the GIL",
+			m_arg_idx + 1, m_callee_fndecl);
     else
-      return warning_at (rich_loc, get_controlling_option (),
-			 "use of PyObject as argument %i of call"
-			 " without the GIL",
-			 m_arg_idx + 1, m_callee_fndecl);
+      return ctxt.warn ("use of PyObject as argument %i of call"
+			" without the GIL",
+			m_arg_idx + 1, m_callee_fndecl);
   }
 
   label_text describe_final_event (const evdesc::final_event &ev) final override
@@ -245,11 +241,9 @@  class pyobject_usage_without_gil : public gil_diagnostic
 			((const pyobject_usage_without_gil&)base_other).m_expr);
   }
 
-  bool emit (rich_location *rich_loc, logger *) final override
+  bool emit (diagnostic_emission_context &ctxt) final override
   {
-    auto_diagnostic_group d;
-    return warning_at (rich_loc, get_controlling_option (),
-		       "use of PyObject %qE without the GIL", m_expr);
+    return ctxt.warn ("use of PyObject %qE without the GIL", m_expr);
   }
 
   label_text describe_final_event (const evdesc::final_event &ev) final override