diff --git a/gcc/diagnostic-client-data-hooks.h b/gcc/diagnostic-client-data-hooks.h
index ba78546abeb..03464202fe1 100644
--- a/gcc/diagnostic-client-data-hooks.h
+++ b/gcc/diagnostic-client-data-hooks.h
@@ -84,22 +84,4 @@ public:
   virtual void for_each_plugin (plugin_visitor &v) const = 0;
 };
 
-/* Abstract base class for a diagnostic_context to get at
-   information about a specific plugin within a client.  */
-
-class diagnostic_client_plugin_info
-{
-public:
-  /* For use e.g. by SARIF "name" property (SARIF v2.1.0 section 3.19.8).  */
-  virtual const char *get_short_name () const = 0;
-
-  /* For use e.g. by SARIF "fullName" property
-     (SARIF v2.1.0 section 3.19.9).  */
-  virtual const char *get_full_name () const = 0;
-
-  /* For use e.g. by SARIF "version" property
-     (SARIF v2.1.0 section 3.19.13).  */
-  virtual const char *get_version () const = 0;
-};
-
 #endif /* ! GCC_DIAGNOSTIC_CLIENT_DATA_HOOKS_H */
diff --git a/gcc/diagnostic-client-plugin.h b/gcc/diagnostic-client-plugin.h
new file mode 100644
index 00000000000..b0e266fac1b
--- /dev/null
+++ b/gcc/diagnostic-client-plugin.h
@@ -0,0 +1,43 @@
+/* Metadata about plugins within a diagnostic client.
+   Copyright (C) 2022 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_DIAGNOSTIC_CLIENT_PLUGIN_H
+#define GCC_DIAGNOSTIC_CLIENT_PLUGIN_H
+
+/* Abstract base class for a diagnostic_context to get at
+   information about a specific plugin within a client,
+   and for associating plugins with diagnostic metadata.  */
+
+class diagnostic_client_plugin_info
+{
+public:
+  /* For use e.g. by SARIF "name" property (SARIF v2.1.0 section 3.19.8).  */
+  virtual const char *get_short_name () const = 0;
+
+  /* For use e.g. by SARIF "fullName" property
+     (SARIF v2.1.0 section 3.19.9).  */
+  virtual const char *get_full_name () const = 0;
+
+  /* For use e.g. by SARIF "version" property
+     (SARIF v2.1.0 section 3.19.13).  */
+  virtual const char *get_version () const = 0;
+};
+
+#endif /* ! GCC_DIAGNOSTIC_CLIENT_PLUGIN_H */
diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc
index a7bb9fb639d..a409abf648b 100644
--- a/gcc/diagnostic-format-sarif.cc
+++ b/gcc/diagnostic-format-sarif.cc
@@ -29,9 +29,26 @@ along with GCC; see the file COPYING3.  If not see
 #include "cpplib.h"
 #include "logical-location.h"
 #include "diagnostic-client-data-hooks.h"
+#include "diagnostic-client-plugin.h"
 
 class sarif_builder;
 
+/* Subclass of json::object for SARIF toolComponent objects
+   (SARIF v2.1.0 section section 3.19), used for the driver
+   and for extensions.  */
+
+class sarif_tool_component : public json::object
+{
+public:
+  sarif_tool_component ();
+
+  void lazily_add_rule (char *id, json::object *reporting_desc_obj);
+
+private:
+  hash_set <free_string_hash> m_rule_id_set;
+  json::array *m_rules_arr;
+};
+
 /* Subclass of json::object for SARIF result objects
    (SARIF v2.1.0 section 3.27.  */
 
@@ -110,6 +127,13 @@ public:
   json::object *make_message_object (const char *msg) const;
 
 private:
+  sarif_tool_component *
+  get_plugin_tool_component_object
+    (const diagnostic_client_plugin_info *plugin);
+  void set_result_rule_id (diagnostic_context *context,
+			   diagnostic_info *diagnostic,
+			   diagnostic_t orig_diag_kind,
+			   sarif_result *result_obj);
   sarif_result *make_result_object (diagnostic_context *context,
 				    diagnostic_info *diagnostic,
 				    diagnostic_t orig_diag_kind);
@@ -133,12 +157,16 @@ private:
   json::object *make_multiformat_message_string (const char *msg) const;
   json::object *make_top_level_object (json::array *results);
   json::object *make_run_object (json::array *results);
-  json::object *make_tool_object () const;
-  json::object *make_driver_tool_component_object () const;
+  json::object *make_tool_object ();
+  sarif_tool_component *make_driver_tool_component_object () const;
   json::array *maybe_make_taxonomies_array () const;
   json::object *maybe_make_cwe_taxonomy_object () const;
   json::object *make_tool_component_reference_object_for_cwe () const;
   json::object *
+  make_reporting_descriptor_object_for_rule
+    (const diagnostic_metadata::rule &rule,
+     const char *desc);
+  json::object *
   make_reporting_descriptor_object_for_warning (diagnostic_context *context,
 						diagnostic_info *diagnostic,
 						diagnostic_t orig_diag_kind,
@@ -168,8 +196,11 @@ private:
 
   hash_set <const char *> m_filenames;
   bool m_seen_any_relative_paths;
-  hash_set <free_string_hash> m_rule_id_set;
-  json::array *m_rules_arr;
+
+  sarif_tool_component *m_driver_obj;
+  json::array *m_extensions_arr;
+  hash_map <const diagnostic_client_plugin_info *,
+	    sarif_tool_component *> m_plugin_objs;
 
   /* The set of all CWE IDs we've seen, if any.  */
   hash_set <int_hash <int, 0, 1> > m_cwe_id_set;
@@ -179,6 +210,39 @@ private:
 
 static sarif_builder *the_builder;
 
+/* class sarif_tool_component : public json::object.  */
+
+sarif_tool_component::sarif_tool_component ()
+: m_rule_id_set (),
+  m_rules_arr (new json::array ())
+{
+  /* "rules" property (SARIF v2.1.0 section 3.19.23).  */
+  set ("rules", m_rules_arr);
+}
+
+/* Take ownership of ID and REPORTING_DESC_OBJ.
+   Ensure that ID is represented within the rules array,
+   using REPORTING_DESC_OBJ if it isn't.   */
+
+void
+sarif_tool_component::lazily_add_rule (char *id,
+				       json::object *reporting_desc_obj)
+{
+  if (m_rule_id_set.contains (id))
+    {
+      /* Already seen; clean up redundant entries.  */
+      free (id);
+      delete reporting_desc_obj;
+    }
+  else
+    {
+      /* This is the first time we've seen this ruleId.  */
+      /* Add to set, taking ownership.  */
+      m_rule_id_set.add (id);
+      m_rules_arr->append (reporting_desc_obj);
+    }
+}
+
 /* class sarif_result : public json::object.  */
 
 /* Handle secondary diagnostics that occur within a diagnostic group.
@@ -221,10 +285,12 @@ sarif_builder::sarif_builder (diagnostic_context *context)
   m_results_array (new json::array ()),
   m_cur_group_result (NULL),
   m_seen_any_relative_paths (false),
-  m_rule_id_set (),
-  m_rules_arr (new json::array ()),
+  m_driver_obj (NULL),
+  m_extensions_arr (NULL),
   m_tabstop (context->tabstop)
 {
+  m_driver_obj = make_driver_tool_component_object ();
+  m_extensions_arr = new json::array ();
 }
 
 /* Implementation of "end_diagnostic" for SARIF output.  */
@@ -320,51 +386,120 @@ make_rule_id_for_diagnostic_kind (diagnostic_t diag_kind)
   return rstrip;
 }
 
-/* Make a result object (SARIF v2.1.0 section 3.27) for DIAGNOSTIC.  */
+/* Get or create a toolComponent object (SARIF v2.1.0 section 3.19)
+   for PLUGIN, adding to m_extensions_arr and using m_plugin_objs to
+   reuse any existing object for PLUGIN.  */
 
-sarif_result *
-sarif_builder::make_result_object (diagnostic_context *context,
+sarif_tool_component *
+sarif_builder::
+get_plugin_tool_component_object (const diagnostic_client_plugin_info *plugin)
+{
+  if (sarif_tool_component **slot = m_plugin_objs.get (plugin))
+    return *slot;
+
+  sarif_tool_component *plugin_obj = new sarif_tool_component ();
+  m_plugin_objs.put (plugin, plugin_obj);
+  m_extensions_arr->append (plugin_obj);
+
+  /* "name" property (SARIF v2.1.0 section 3.19.8).  */
+  if (const char *short_name = plugin->get_short_name ())
+    plugin_obj->set ("name", new json::string (short_name));
+
+  /* "fullName" property (SARIF v2.1.0 section 3.19.9).  */
+  if (const char *full_name = plugin->get_full_name ())
+    plugin_obj->set ("fullName", new json::string (full_name));
+
+  /* "version" property (SARIF v2.1.0 section 3.19.13).  */
+  if (const char *version = plugin->get_version ())
+    plugin_obj->set ("version", new json::string (version));
+
+  return plugin_obj;
+}
+
+/* If DIAGNOSTIC has any associated rules, get the first one.  */
+
+static const diagnostic_metadata::rule *
+maybe_get_first_rule (diagnostic_info *diagnostic)
+{
+  if (!diagnostic->metadata)
+    return NULL;
+  if (diagnostic->metadata->get_num_rules () == 0)
+    return NULL;
+  return &diagnostic->metadata->get_rule (0);
+}
+
+/* Set "ruleId" property of RESULT_OBJ (SARIF v2.1.0 section 3.27.5).
+   Ensure that there is such a rule within the component, and that such
+   a component exists (either for the driver, or any plugin).  */
+
+void
+sarif_builder::set_result_rule_id (diagnostic_context *context,
 				   diagnostic_info *diagnostic,
-				   diagnostic_t orig_diag_kind)
+				   diagnostic_t orig_diag_kind,
+				   sarif_result *result_obj)
 {
-  sarif_result *result_obj = new sarif_result ();
+  /* Determine which component we should search for within/add the
+     rule to.  */
+  sarif_tool_component *component_obj = m_driver_obj;
+  if (diagnostic->metadata)
+    if (const diagnostic_client_plugin_info *plugin
+	  = diagnostic->metadata->get_plugin ())
+      component_obj = get_plugin_tool_component_object (plugin);
 
   /* "ruleId" property (SARIF v2.1.0 section 3.27.5).  */
-  /* Ideally we'd have an option_name for these.  */
+  if (const diagnostic_metadata::rule *rule
+	= maybe_get_first_rule (diagnostic))
+    if (char *desc = rule->make_description ())
+      {
+	/* Lazily create reportingDescriptor objects for the rule
+	   and add to the component.
+	   Set ruleId referencing them.  */
+	result_obj->set ("ruleId", new json::string (desc));
+	component_obj->lazily_add_rule
+	  (desc,
+	   make_reporting_descriptor_object_for_rule (*rule, desc));
+	return;
+      }
+
+  /* Otherwise, try to use the option_name for these.  */
   if (char *option_text
 	= context->option_name (context, diagnostic->option_index,
 				orig_diag_kind, diagnostic->kind))
     {
-      /* Lazily create reportingDescriptor objects for and add to m_rules_arr.
+      /* Lazily create reportingDescriptor objects for the warning
+	 and add to the component.
 	 Set ruleId referencing them.  */
       result_obj->set ("ruleId", new json::string (option_text));
-      if (m_rule_id_set.contains (option_text))
-	free (option_text);
-      else
-	{
-	  /* This is the first time we've seen this ruleId.  */
-	  /* Add to set, taking ownership.  */
-	  m_rule_id_set.add (option_text);
-
-	  json::object *reporting_desc_obj
-	    = make_reporting_descriptor_object_for_warning (context,
-							    diagnostic,
-							    orig_diag_kind,
-							    option_text);
-	  m_rules_arr->append (reporting_desc_obj);
-	}
-    }
-  else
-    {
-      /* Otherwise, we have an "error" or a stray "note"; use the
-	 diagnostic kind as the ruleId, so that the result object at least
-	 has a ruleId.
-	 We don't bother creating reportingDescriptor objects for these.  */
-      char *rule_id = make_rule_id_for_diagnostic_kind (orig_diag_kind);
-      result_obj->set ("ruleId", new json::string (rule_id));
-      free (rule_id);
+      component_obj->lazily_add_rule
+	(option_text,
+	 make_reporting_descriptor_object_for_warning (context,
+						       diagnostic,
+						       orig_diag_kind,
+						       option_text));
+      return;
     }
 
+  /* Otherwise, we have a "warning" without an option, an "error" or a stray
+     "note"; use the diagnostic kind as the ruleId, so that the result object
+     at least has a ruleId.
+     We don't bother creating reportingDescriptor objects for these.  */
+  char *rule_id = make_rule_id_for_diagnostic_kind (orig_diag_kind);
+  result_obj->set ("ruleId", new json::string (rule_id));
+  free (rule_id);
+}
+
+/* Make a result object (SARIF v2.1.0 section 3.27) for DIAGNOSTIC.  */
+
+sarif_result *
+sarif_builder::make_result_object (diagnostic_context *context,
+				   diagnostic_info *diagnostic,
+				   diagnostic_t orig_diag_kind)
+{
+  sarif_result *result_obj = new sarif_result ();
+
+  /* "ruleId" property (SARIF v2.1.0 section 3.27.5).  */
+  set_result_rule_id (context, diagnostic, orig_diag_kind, result_obj);
+
   /* "taxa" property (SARIF v2.1.0 section 3.27.8).  */
   if (diagnostic->metadata)
     if (int cwe_id = diagnostic->metadata->get_cwe ())
@@ -424,6 +559,32 @@ sarif_builder::make_result_object (diagnostic_context *context,
   return result_obj;
 }
 
+/* Make a reportingDescriptor object (SARIF v2.1.0 section 3.49)
+   for RULE, with DESC.  */
+
+json::object *
+sarif_builder::
+make_reporting_descriptor_object_for_rule (const diagnostic_metadata::rule &rule,
+					   const char *desc)
+{
+  json::object *reporting_desc = new json::object ();
+
+  /* "id" property (SARIF v2.1.0 section 3.49.3).  */
+  reporting_desc->set ("id", new json::string (desc));
+
+  /* We don't implement "name" property (SARIF v2.1.0 section 3.49.7), since
+     it seems redundant compared to "id".  */
+
+  /* "helpUri" property (SARIF v2.1.0 section 3.49.12).  */
+  if (char *url = rule.make_url ())
+    {
+      reporting_desc->set ("helpUri", new json::string (url));
+      free (url);
+    }
+
+  return reporting_desc;
+}
+
 /* Make a reportingDescriptor object (SARIF v2.1.0 section 3.49)
    for a GCC warning.  */
 
@@ -1089,16 +1250,16 @@ sarif_builder::make_run_object (json::array *results)
 /* Make a tool object (SARIF v2.1.0 section 3.18).  */
 
 json::object *
-sarif_builder::make_tool_object () const
+sarif_builder::make_tool_object ()
 {
   json::object *tool_obj = new json::object ();
 
   /* "driver" property (SARIF v2.1.0 section 3.18.2).  */
-  json::object *driver_obj = make_driver_tool_component_object ();
-  tool_obj->set ("driver", driver_obj);
+  tool_obj->set ("driver", m_driver_obj);
 
   /* Report plugins via the "extensions" property
      (SARIF v2.1.0 section 3.18.3).  */
+  tool_obj->set ("extensions", m_extensions_arr);
   if (m_context->m_client_data_hooks)
     if (const client_version_info *vinfo
 	  = m_context->m_client_data_hooks->get_any_version_info ())
@@ -1106,36 +1267,19 @@ sarif_builder::make_tool_object () const
 	class my_plugin_visitor : public client_version_info :: plugin_visitor
 	{
 	public:
+	  my_plugin_visitor (sarif_builder *builder)
+	  : m_builder (builder)
+	  {}
+
 	  void on_plugin (const diagnostic_client_plugin_info &p) final override
 	  {
-	    /* Create a toolComponent object (SARIF v2.1.0 section 3.19)
-	       for the plugin.  */
-	    json::object *plugin_obj = new json::object ();
-	    m_plugin_objs.safe_push (plugin_obj);
-
-	    /* "name" property (SARIF v2.1.0 section 3.19.8).  */
-	    if (const char *short_name = p.get_short_name ())
-	      plugin_obj->set ("name", new json::string (short_name));
-
-	    /* "fullName" property (SARIF v2.1.0 section 3.19.9).  */
-	    if (const char *full_name = p.get_full_name ())
-	      plugin_obj->set ("fullName", new json::string (full_name));
-
-	    /* "version" property (SARIF v2.1.0 section 3.19.13).  */
-	    if (const char *version = p.get_version ())
-	      plugin_obj->set ("version", new json::string (version));
+	    m_builder->get_plugin_tool_component_object (&p);
 	  }
-	  auto_vec <json::object *> m_plugin_objs;
+	private:
+	  sarif_builder *m_builder;
 	};
-	my_plugin_visitor v;
+	my_plugin_visitor v (this);
 	vinfo->for_each_plugin (v);
-	if (v.m_plugin_objs.length () > 0)
-	  {
-	    json::array *extensions_arr = new json::array ();
-	    tool_obj->set ("extensions", extensions_arr);
-	    for (auto iter : v.m_plugin_objs)
-	      extensions_arr->append (iter);
-	  }
       }
 
   /* Perhaps we could also show GMP, MPFR, MPC, isl versions as other
@@ -1147,10 +1291,10 @@ sarif_builder::make_tool_object () const
 /* Make a toolComponent object (SARIF v2.1.0 section 3.19) for what SARIF
    calls the "driver" (see SARIF v2.1.0 section 3.18.1).  */
 
-json::object *
+sarif_tool_component *
 sarif_builder::make_driver_tool_component_object () const
 {
-  json::object *driver_obj = new json::object ();
+  sarif_tool_component *driver_obj = new sarif_tool_component ();
 
   if (m_context->m_client_data_hooks)
     if (const client_version_info *vinfo
@@ -1179,9 +1323,6 @@ sarif_builder::make_driver_tool_component_object () const
 	  }
       }
 
-  /* "rules" property (SARIF v2.1.0 section 3.19.23).  */
-  driver_obj->set ("rules", m_rules_arr);
-
   return driver_obj;
 }
 
diff --git a/gcc/diagnostic-metadata.h b/gcc/diagnostic-metadata.h
index 80017d35fa9..dce6763a1d9 100644
--- a/gcc/diagnostic-metadata.h
+++ b/gcc/diagnostic-metadata.h
@@ -21,11 +21,16 @@ along with GCC; see the file COPYING3.  If not see
 #ifndef GCC_DIAGNOSTIC_METADATA_H
 #define GCC_DIAGNOSTIC_METADATA_H
 
+class diagnostic_client_plugin_info;
+
 /* A bundle of additional metadata that can be associated with a
    diagnostic.
 
-   This supports an optional CWE identifier, and zero or more
-   "rules".  */
+   This supports:
+   - an optional plugin associated with this diagnostic
+   - an optional CWE identifier
+   - zero or more "rules" (such as rules within a coding standard,
+     or within a specification).  */
 
 class diagnostic_metadata
 {
@@ -62,7 +67,15 @@ class diagnostic_metadata
     const char *m_url;
   };
 
-  diagnostic_metadata () : m_cwe (0) {}
+  /* Ctors.  */
+  diagnostic_metadata ()
+  : m_plugin (NULL),
+    m_cwe (0)
+  {}
+  diagnostic_metadata (const diagnostic_client_plugin_info *plugin)
+  : m_plugin (plugin),
+    m_cwe (0)
+  {}
 
   void add_cwe (int cwe) { m_cwe = cwe; }
   int get_cwe () const { return m_cwe; }
@@ -74,10 +87,13 @@ class diagnostic_metadata
     m_rules.safe_push (&r);
   }
 
+  const diagnostic_client_plugin_info *get_plugin () const { return m_plugin; }
+
   unsigned get_num_rules () const { return m_rules.length (); }
   const rule &get_rule (unsigned idx) const { return *(m_rules[idx]); }
 
  private:
+  const diagnostic_client_plugin_info *m_plugin;
   int m_cwe;
   auto_vec<const rule *> m_rules;
 };
diff --git a/gcc/doc/plugins.texi b/gcc/doc/plugins.texi
index 6d1a5fa7607..a1713901314 100644
--- a/gcc/doc/plugins.texi
+++ b/gcc/doc/plugins.texi
@@ -102,11 +102,20 @@ the parser.  The arguments to @code{plugin_init} are:
 @item @code{version}: GCC version.
 @end itemize
 
-The @code{plugin_info} struct is defined as follows:
+The @code{plugin_info} class is defined as follows:
 
 @smallexample
-struct plugin_name_args
+class plugin_name_args : public diagnostic_client_plugin_info
 @{
+ public:
+  plugin_name_args (char *base_name_,
+		    const char *full_name_);
+
+  /* Implementation of diagnostic_client_plugin_info.  */
+  const char *get_short_name () const final override;
+  const char *get_full_name () const final override;
+  const char *get_version () const final override;
+
   char *base_name;              /* Short name of the plugin
                                    (filename without .so suffix). */
   const char *full_name;        /* Path to the plugin as specified with
@@ -149,7 +158,7 @@ recommended version check to perform looks like
 ...
 
 int
-plugin_init (struct plugin_name_args *plugin_info,
+plugin_init (plugin_name_args *plugin_info,
              struct plugin_gcc_version *version)
 @{
   if (!plugin_default_version_check (version, &gcc_version))
@@ -295,7 +304,7 @@ struct register_pass_info
 
 /* Sample plugin code that registers a new pass.  */
 int
-plugin_init (struct plugin_name_args *plugin_info,
+plugin_init (plugin_name_args *plugin_info,
              struct plugin_gcc_version *version)
 @{
   struct register_pass_info pass_info;
diff --git a/gcc/plugin.cc b/gcc/plugin.cc
index 6c42e057cbc..19cc74867d3 100644
--- a/gcc/plugin.cc
+++ b/gcc/plugin.cc
@@ -26,6 +26,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "options.h"
 #include "tree-pass.h"
 #include "diagnostic-core.h"
+#include "diagnostic-client-plugin.h"
 #include "flags.h"
 #include "intl.h"
 #include "plugin.h"
@@ -124,6 +125,39 @@ static const char *str_plugin_init_func_name = "plugin_init";
 static const char *str_license = "plugin_is_GPL_compatible";
 #endif
 
+/* class plugin_name_args : public diagnostic_client_plugin_info.  */
+
+/* plugin_name_args's ctor.  */
+
+plugin_name_args::plugin_name_args (char *base_name_,
+				    const char *full_name_)
+: base_name (base_name_),
+  full_name (full_name_),
+  argc (0),
+  argv (NULL),
+  version (NULL),
+  help (NULL)
+{
+}
+
+const char *
+plugin_name_args::get_short_name () const
+{
+  return base_name;
+}
+
+const char *
+plugin_name_args::get_full_name () const
+{
+  return full_name;
+}
+
+const char *
+plugin_name_args::get_version () const
+{
+  return version;
+}
+
 /* Helper function for hashing the base_name of the plugin_name_args
    structure to be inserted into the hash table.  */
 
@@ -236,7 +270,7 @@ add_new_plugin (const char* plugin_name)
       return;
     }
 
-  plugin = XCNEW (struct plugin_name_args);
+  plugin = new plugin_name_args (base_name, plugin_name);
   plugin->base_name = base_name;
   plugin->full_name = plugin_name;
 
diff --git a/gcc/plugin.h b/gcc/plugin.h
index e7e8b51d15a..f5d22a8f53b 100644
--- a/gcc/plugin.h
+++ b/gcc/plugin.h
@@ -21,6 +21,7 @@ along with GCC; see the file COPYING3.  If not see
 #define PLUGIN_H
 
 #include "highlev-plugin-common.h"
+#include "diagnostic-client-plugin.h"
 
 /* Event names.  */
 enum plugin_event
@@ -65,8 +66,17 @@ struct plugin_gcc_version
 };
 
 /* Object that keeps track of the plugin name and its arguments. */
-struct plugin_name_args
+class plugin_name_args : public diagnostic_client_plugin_info
 {
+ public:
+  plugin_name_args (char *base_name_,
+		    const char *full_name_);
+
+  /* Implementation of diagnostic_client_plugin_info.  */
+  const char *get_short_name () const final override;
+  const char *get_full_name () const final override;
+  const char *get_version () const final override;
+
   char *base_name;              /* Short name of the plugin (filename without
                                    .so suffix). */
   const char *full_name;        /* Path to the plugin as specified with
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-sarif.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-sarif.c
new file mode 100644
index 00000000000..ac8f4ba2d83
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-sarif.c
@@ -0,0 +1,39 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-format=sarif-file" } */
+
+extern char *gets (char *s);
+
+void test_cwe (void)
+{
+  char buf[1024];
+  gets (buf);
+}
+
+/* Verify that some JSON was written to a file with the expected name.  */
+
+/* We expect various properties.
+   The indentation here reflects the expected hierarchy, though these tests
+   don't check for that, merely the string fragments we expect.
+   { dg-final { scan-sarif-file "\"version\": \"2.1.0\"" } }
+   { dg-final { scan-sarif-file "\"runs\": \\\[" } }
+     { dg-final { scan-sarif-file "\"tool\": " } }
+       { dg-final { scan-sarif-file "\"driver\": " } }
+         { dg-final { scan-sarif-file "\"name\": \"GNU C" } }
+
+         { dg-final { scan-sarif-file "\"rules\": \\\[" } }
+           { dg-final { scan-sarif-file "\"id\": \"STR34-C\"" } }
+           { dg-final { scan-sarif-file "\"helpUri\": \"https://example.com/\"" } }
+
+	   Ideally we would verify that the above rule is within the extension,
+	   rather than the driver.  Unfortunately we don't have a way to
+	   do this at present.
+
+       { dg-final { scan-sarif-file "\"extensions\": \\\[" } }
+         { dg-final { scan-sarif-file "\"name\": \"diagnostic_plugin_test_metadata\"" } }
+     { dg-final { scan-sarif-file "\"results\": \\\[" } }
+       { dg-final { scan-sarif-file "\"level\": \"warning\"" } }
+       { dg-final { scan-sarif-file "\"message\": " } }
+         { dg-final { scan-sarif-file "\"text\": \"never use 'gets'\"" } }
+       { dg-final { scan-sarif-file "\"taxa\": \\\[" } }
+         { dg-final { scan-sarif-file "\"id\": \"242\"" } }
+*/
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_metadata.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_metadata.c
index b86a8b3650e..b2a86e1bc68 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_metadata.c
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_metadata.c
@@ -32,6 +32,8 @@
 
 int plugin_is_GPL_compatible;
 
+static diagnostic_client_plugin_info *diag_plugin_info;
+
 const pass_data pass_data_test_metadata =
 {
   GIMPLE_PASS, /* type */
@@ -106,7 +108,7 @@ pass_test_metadata::execute (function *fun)
 	if (gcall *call = check_for_named_call (stmt, "gets", 1))
 	  {
 	    gcc_rich_location richloc (gimple_location (call));
-	    diagnostic_metadata m;
+	    diagnostic_metadata m (diag_plugin_info);
 
 	    /* CWE-242: Use of Inherently Dangerous Function.  */
 	    m.add_cwe (242);
@@ -136,6 +138,8 @@ plugin_init (struct plugin_name_args *plugin_info,
   if (!plugin_default_version_check (version, &gcc_version))
     return 1;
 
+  diag_plugin_info = plugin_info;
+
   pass_info.pass = new pass_test_metadata (g);
   pass_info.reference_pass_name = "ssa";
   pass_info.ref_pass_instance_number = 1;
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index 63b117d3cde..2244f52211d 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -96,7 +96,9 @@ set plugin_test_list [list \
 	  diagnostic-test-inlining-2.c \
 	  diagnostic-test-inlining-3.c \
 	  diagnostic-test-inlining-4.c } \
-    { diagnostic_plugin_test_metadata.c diagnostic-test-metadata.c } \
+    { diagnostic_plugin_test_metadata.c \
+	  diagnostic-test-metadata.c \
+	  diagnostic-test-metadata-sarif.c } \
     { diagnostic_plugin_test_paths.c \
 	  diagnostic-test-paths-1.c \
 	  diagnostic-test-paths-2.c \
diff --git a/gcc/tree-diagnostic-client-data-hooks.cc b/gcc/tree-diagnostic-client-data-hooks.cc
index f8ff271d2f5..c58df1fe70c 100644
--- a/gcc/tree-diagnostic-client-data-hooks.cc
+++ b/gcc/tree-diagnostic-client-data-hooks.cc
@@ -27,41 +27,10 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic.h"
 #include "tree-logical-location.h"
 #include "diagnostic-client-data-hooks.h"
+#include "diagnostic-client-plugin.h"
 #include "langhooks.h"
 #include "plugin.h"
 
-/* Concrete class for supplying a diagnostic_context with information
-   about a specific plugin within the client, when the client is the
-   compiler (i.e. a GCC plugin).  */
-
-class compiler_diagnostic_client_plugin_info
-  : public diagnostic_client_plugin_info
-{
-public:
-  compiler_diagnostic_client_plugin_info (const plugin_name_args *args)
-  : m_args (args)
-  {
-  }
-
-  const char *get_short_name () const final override
-  {
-    return m_args->base_name;
-  }
-
-  const char *get_full_name () const final override
-  {
-    return m_args->full_name;
-  }
-
-  const char *get_version () const final override
-  {
-    return m_args->version;
-  }
-
-private:
-  const plugin_name_args *m_args;
-};
-
 /* Concrete subclass of client_version_info for use by compilers proper,
    (i.e. using lang_hooks, and with knowledge of GCC plugins).  */
 
@@ -103,10 +72,9 @@ private:
   on_plugin_cb (const plugin_name_args *args,
 		void *user_data)
   {
-    compiler_diagnostic_client_plugin_info cpi (args);
     client_version_info::plugin_visitor *visitor
       = (client_version_info::plugin_visitor *)user_data;
-    visitor->on_plugin (cpi);
+    visitor->on_plugin (*args);
   }
 };
 
