]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
diagnostics::output_spec: support client-specific keys
authorDavid Malcolm <dmalcolm@redhat.com>
Thu, 2 Oct 2025 14:44:54 +0000 (10:44 -0400)
committerDavid Malcolm <dmalcolm@redhat.com>
Thu, 2 Oct 2025 14:44:54 +0000 (10:44 -0400)
This patch adds the ability for users of diagnostics::output_spec to
add custom key/values pairs to
  -fdiagnostics-{add,set}-output=SCHEME:KEY=VALUE

The intent is for allow e.g. capturing compiler-specific information in
SARIF sinks (CFGs, inheritance hierarchies, etc)

gcc/ChangeLog:
* diagnostics/output-spec.cc: (scheme_handler::parse_bool_value):
Convert to...
(key_handler::parse_bool_value): ...this.
(scheme_handler::parse_enum_value): Convert to...
(key_handler::parse_enum_value): ...this.
(struct text_scheme_handler::decoded_args): Eliminate, moving
fields into class text_scheme_handler.
(text_scheme_handler::text_scheme_handler): Initialize the new
fields.
Add a "dc" param and use it to initialize m_show_color.
(struct sarif_scheme_handler::decoded_args): Eliminate, moving
fields into class sarif_scheme_handler.
(sarif_scheme_handler::sarif_scheme_handler): Initialize the new
fields.
(struct html_scheme_handler::decoded_args): Eliminate, moving
fields into class html_scheme_handler.
(html_scheme_handler::html_scheme_handler): Initialize the new
fields.
(context::report_unknown_key): Get keys from scheme rather than
passing them in.  Support client keys.
(context::parse_and_make_sink): Pass dc to output_factory ctor.
(output_factory::output_factory): Pass dc to text_scheme_handler.
(output_factory::get_scheme_handler): Make return non-const.
(output_factory::make_sink): Move key-handling here, rather than
in individual sinks.
(context::handle_kv): New.
(text_scheme_handler::make_sink): Eliminate key decoding.
(text_scheme_handler::decode_kv): Convert to...
(text_scheme_handler::maybe_handle_kv): ...this...
(text_scheme_handler::get_keys): ...and this.
(sarif_scheme_handler::make_sink): Eliminate key decoding.
(sarif_scheme_handler::decode_kv): Convert to...
(sarif_scheme_handler::maybe_handle_kv): ...this...
(sarif_scheme_handler::get_keys): ...and this.
(html_scheme_handler::make_sink): Eliminate key decoding.
(html_scheme_handler::decode_kv): Convert to...
(html_scheme_handler::maybe_handle_kv): ...this...
(html_scheme_handler::get_keys): ...and this.
(struct selftest::parser_test): Add "client_keys" arg, and update
for new param ordering.
(selftest::parser_test::parse_and_make_sink): New.
(selftest::test_output_arg_parsing): Move auto-fixes to caller.
(class selftest::test_key_handler): New.
(selftest::test_client_arg_parsing): New test.
(selftest::output_spec_cc_tests): Call it.
* diagnostics/output-spec.h (class key_handler): New.
(class scheme_handler): Move here from output-spec.cc.
(context::report_unknown_key): Simplify params.
(context::handle_kv): Update params.
(context::context): Add "client_keys" param.
(context::m_client_keys): New field.
(struct dc_spec_context): Update order of params.  Add
"client_keys" param.
* libgdiagnostics.cc (spec_context::spec_context): Pass nullptr
for client keys.
* opts-diagnostic.cc (opt_spec_context::opt_spec_context):
Likewise.  Update for new param ordering.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
gcc/diagnostics/output-spec.cc
gcc/diagnostics/output-spec.h
gcc/libgdiagnostics.cc
gcc/opts-diagnostic.cc

index 28ea044fcc70c3a9ca3af104691d53be6316b149..dfde7f0bdf3038b981018b16e195f226a7b872d9 100644 (file)
@@ -73,171 +73,160 @@ struct scheme_name_and_params
 class output_factory
 {
 public:
-  output_factory ();
+  output_factory (diagnostics::context &dc);
 
   std::unique_ptr<sink>
   make_sink (const context &ctxt,
             diagnostics::context &dc,
             const scheme_name_and_params &scheme_and_kvs);
 
-  const scheme_handler *get_scheme_handler (const std::string &scheme_name);
+  scheme_handler *get_scheme_handler (const std::string &scheme_name);
 
 private:
   std::vector<std::unique_ptr<scheme_handler>> m_scheme_handlers;
 };
 
-class scheme_handler
+enum key_handler::result
+key_handler::parse_bool_value (const context &ctxt,
+                              const std::string &key,
+                              const std::string &value,
+                              bool &out) const
 {
-public:
-  scheme_handler (std::string scheme_name)
-    : m_scheme_name (std::move (scheme_name))
-  {}
-  virtual ~scheme_handler () {}
-
-  const std::string &get_scheme_name () const { return m_scheme_name; }
-
-  virtual std::unique_ptr<sink>
-  make_sink (const context &ctxt,
-            diagnostics::context &dc,
-            const scheme_name_and_params &scheme_and_kvs) const = 0;
+  if (value == "yes")
+    {
+      out = true;
+      return result::ok;
+    }
+  else if (value == "no")
+    {
+      out = false;
+      return result::ok;
+    }
+  else
+    {
+      ctxt.report_error
+       ("%<%s%s%>:"
+        " unexpected value %qs for key %qs; expected %qs or %qs",
+        ctxt.get_option_name (), ctxt.get_unparsed_spec (),
+        value.c_str (),
+        key.c_str (),
+        "yes", "no");
+      return result::malformed_value;
+    }
+}
 
-protected:
-  bool
-  parse_bool_value (const context &ctxt,
-                   const std::string &key,
-                   const std::string &value,
-                   bool &out) const
-  {
-    if (value == "yes")
-      {
-       out = true;
-       return true;
-      }
-    else if (value == "no")
-      {
-       out = false;
-       return true;
-      }
-    else
+template <typename EnumType, size_t NumValues>
+key_handler::result
+key_handler::parse_enum_value (const context &ctxt,
+                              const std::string &key,
+                              const std::string &value,
+                              const std::array<std::pair<const char *,
+                                                         EnumType>,
+                                               NumValues> &value_names,
+                              EnumType &out) const
+{
+  for (auto &iter : value_names)
+    if (value == iter.first)
       {
-       ctxt.report_error
-         ("%<%s%s%>:"
-          " unexpected value %qs for key %qs; expected %qs or %qs",
-          ctxt.get_option_name (), ctxt.get_unparsed_spec (),
-          value.c_str (),
-          key.c_str (),
-          "yes", "no");
-
-       return false;
+       out = iter.second;
+       return result::ok;
       }
-    }
-  template <typename EnumType, size_t NumValues>
-  bool
-  parse_enum_value (const context &ctxt,
-                   const std::string &key,
-                   const std::string &value,
-                   const std::array<std::pair<const char *, EnumType>, NumValues> &value_names,
-                   EnumType &out) const
-  {
-    for (auto &iter : value_names)
-      if (value == iter.first)
-       {
-         out = iter.second;
-         return true;
-       }
-
-    auto_vec<const char *> known_values;
-    for (auto iter : value_names)
-      known_values.safe_push (iter.first);
-    pp_markup::comma_separated_quoted_strings e (known_values);
-    ctxt.report_error
-      ("%<%s%s%>:"
-       " unexpected value %qs for key %qs; known values: %e",
-       ctxt.get_option_name (), ctxt.get_unparsed_spec (),
-       value.c_str (),
-       key.c_str (),
-       &e);
-    return false;
-  }
 
-private:
-  const std::string m_scheme_name;
-};
+  auto_vec<const char *> known_values;
+  for (auto iter : value_names)
+    known_values.safe_push (iter.first);
+  pp_markup::comma_separated_quoted_strings e (known_values);
+  ctxt.report_error
+    ("%<%s%s%>:"
+     " unexpected value %qs for key %qs; known values: %e",
+     ctxt.get_option_name (), ctxt.get_unparsed_spec (),
+     value.c_str (),
+     key.c_str (),
+     &e);
+  return result::malformed_value;
+}
 
 class text_scheme_handler : public scheme_handler
 {
 public:
-  struct decoded_args
+  text_scheme_handler (diagnostics::context &dc)
+  : scheme_handler ("text"),
+    m_show_color (pp_show_color (dc.get_reference_printer ())),
+    m_show_nesting (true),
+    m_show_locations_in_nesting (true),
+    m_show_levels (false)
   {
-    bool m_show_color;
-    bool m_show_nesting;
-    bool m_show_locations_in_nesting;
-    bool m_show_levels;
-  };
-
-  text_scheme_handler () : scheme_handler ("text") {}
+  }
 
   std::unique_ptr<sink>
   make_sink (const context &ctxt,
-            diagnostics::context &dc,
-            const scheme_name_and_params &scheme_and_kvs) const final override;
+            diagnostics::context &dc) final override;
+
+  enum result
+  maybe_handle_kv (const context &ctxt,
+                  const std::string &key,
+                  const std::string &value) final override;
+
+  void
+  get_keys (auto_vec<const char *> &out) const final override;
 
-  bool
-  decode_kv (const context &ctxt,
-            const std::string &key,
-            const std::string &value,
-            decoded_args &out_opts) const;
+private:
+  bool m_show_color;
+  bool m_show_nesting;
+  bool m_show_locations_in_nesting;
+  bool m_show_levels;
 };
 
 class sarif_scheme_handler : public scheme_handler
 {
 public:
-  struct decoded_args
+  sarif_scheme_handler ()
+  : scheme_handler ("sarif"),
+    m_serialization_kind (sarif_serialization_kind::json)
   {
-    label_text m_filename;
-    enum sarif_serialization_kind m_serialization_kind;
-    sarif_generation_options m_generation_opts;
-  };
-
-  sarif_scheme_handler () : scheme_handler ("sarif") {}
+  }
 
   std::unique_ptr<sink>
   make_sink (const context &ctxt,
-            diagnostics::context &dc,
-            const scheme_name_and_params &scheme_and_kvs) const final override;
+            diagnostics::context &dc) final override;
 
-  bool
-  decode_kv (const context &ctxt,
-            const std::string &key,
-            const std::string &value,
-            decoded_args &out_opts) const;
+  enum result
+  maybe_handle_kv (const context &ctxt,
+                  const std::string &key,
+                  const std::string &value) final override;
+
+  void
+  get_keys (auto_vec<const char *> &out) const final override;
 
 private:
   static std::unique_ptr<sarif_serialization_format>
   make_sarif_serialization_object (enum sarif_serialization_kind);
+
+  label_text m_filename;
+  enum sarif_serialization_kind m_serialization_kind;
+  sarif_generation_options m_generation_opts;
 };
 
 class html_scheme_handler : public scheme_handler
 {
 public:
-  struct decoded_args
-  {
-    label_text m_filename;
-    html_generation_options m_html_gen_opts;
-  };
-
   html_scheme_handler () : scheme_handler ("experimental-html") {}
 
   std::unique_ptr<sink>
   make_sink (const context &ctxt,
-            diagnostics::context &dc,
-            const scheme_name_and_params &scheme_and_kvs) const final override;
+            diagnostics::context &dc) final override;
+
+  enum result
+  maybe_handle_kv (const context &ctxt,
+                  const std::string &key,
+                  const std::string &value) final override;
 
-  bool
-  decode_kv (const context &ctxt,
-            const std::string &key,
-            const std::string &value,
-            decoded_args &opts_out) const;
+  void
+  get_keys (auto_vec<const char *> &out) const final override;
+
+private:
+  label_text m_filename;
+  html_generation_options m_html_gen_opts;
 };
 
 /* struct context.  */
@@ -253,15 +242,38 @@ context::report_error (const char *gmsgid, ...) const
 
 void
 context::report_unknown_key (const std::string &key,
-                            const std::string &scheme_name,
-                            auto_vec<const char *> &known_keys) const
+                            const scheme_handler &scheme) const
 {
-  pp_markup::comma_separated_quoted_strings e (known_keys);
+  auto_vec<const char *> scheme_key_vec;
+  scheme.get_keys (scheme_key_vec);
+
+  pp_markup::comma_separated_quoted_strings e_scheme_keys (scheme_key_vec);
+
+  const char *scheme_name = scheme.get_scheme_name ().c_str ();
+
+  if (m_client_keys)
+    {
+      auto_vec<const char *> client_key_vec;
+      m_client_keys->get_keys (client_key_vec);
+      if (!client_key_vec.is_empty ())
+       {
+         pp_markup::comma_separated_quoted_strings e_client_keys
+           (client_key_vec);
+         report_error
+           ("%<%s%s%>:"
+            " unknown key %qs for output scheme %qs;"
+            " scheme keys: %e; client keys: %e",
+            get_option_name (), get_unparsed_spec (),
+            key.c_str (), scheme_name,
+            &e_scheme_keys, &e_client_keys);
+       }
+    }
+
   report_error
     ("%<%s%s%>:"
-     " unknown key %qs for format %qs; known keys: %e",
+     " unknown key %qs for output scheme %qs; scheme keys: %e",
      get_option_name (), get_unparsed_spec (),
-     key.c_str (), scheme_name.c_str (), &e);
+     key.c_str (), scheme_name, &e_scheme_keys);
 }
 
 void
@@ -348,7 +360,7 @@ context::parse_and_make_sink (diagnostics::context &dc)
   if (!parsed_arg)
     return nullptr;
 
-  output_factory factory;
+  output_factory factory (dc);
   return factory.make_sink (*this, dc, *parsed_arg);
 }
 
@@ -356,14 +368,14 @@ context::parse_and_make_sink (diagnostics::context &dc)
 
 /* class output_factory.  */
 
-output_factory::output_factory ()
+output_factory::output_factory (diagnostics::context &dc)
 {
-  m_scheme_handlers.push_back (std::make_unique<text_scheme_handler> ());
+  m_scheme_handlers.push_back (std::make_unique<text_scheme_handler> (dc));
   m_scheme_handlers.push_back (std::make_unique<sarif_scheme_handler> ());
   m_scheme_handlers.push_back (std::make_unique<html_scheme_handler> ());
 }
 
-const scheme_handler *
+scheme_handler *
 output_factory::get_scheme_handler (const std::string &scheme_name)
 {
   for (auto &iter : m_scheme_handlers)
@@ -391,61 +403,91 @@ output_factory::make_sink (const context &ctxt,
       return nullptr;
     }
 
-  return scheme_handler->make_sink (ctxt, dc, scheme_and_kvs);
-}
-
-/* class text_scheme_handler : public scheme_handler.  */
-
-std::unique_ptr<sink>
-text_scheme_handler::make_sink (const context &ctxt,
-                               diagnostics::context &dc,
-                               const scheme_name_and_params &scheme_and_kvs) const
-{
-  decoded_args opts;
-  opts.m_show_color = pp_show_color (dc.get_reference_printer ());
-  opts.m_show_nesting = true;
-  opts.m_show_locations_in_nesting = true;
-  opts.m_show_levels = false;
+  /* Parse key/value pairs.  */
   for (auto& iter : scheme_and_kvs.m_kvs)
     {
       const std::string &key = iter.first;
       const std::string &value = iter.second;
-      if (!decode_kv (ctxt, key, value, opts))
+      if (!ctxt.handle_kv (key, value, *scheme_handler))
        return nullptr;
     }
 
+  return scheme_handler->make_sink (ctxt, dc);
+}
+
+bool
+context::handle_kv (const std::string &key,
+                   const std::string &value,
+                   scheme_handler &scheme) const
+{
+  auto result = scheme.maybe_handle_kv (*this, key, value);
+  switch (result)
+    {
+    default: gcc_unreachable ();
+    case key_handler::result::ok:
+      return true;
+    case key_handler::result::malformed_value:
+      return false;
+    case key_handler::result::unrecognized:
+      /* Key recognized by the scheme; try the client keys.  */
+      if (m_client_keys)
+       {
+         result = m_client_keys->maybe_handle_kv (*this, key, value);
+         switch (result)
+           {
+           default: gcc_unreachable ();
+           case key_handler::result::ok:
+             return true;
+           case key_handler::result::malformed_value:
+             return false;
+           case key_handler::result::unrecognized:
+             break;
+           }
+       }
+      report_unknown_key (key, scheme);
+      return false;
+    }
+}
+
+/* class text_scheme_handler : public scheme_handler.  */
+
+std::unique_ptr<sink>
+text_scheme_handler::make_sink (const context &,
+                               diagnostics::context &dc)
+{
   auto sink = std::make_unique<diagnostics::text_sink> (dc);
-  sink->set_show_nesting (opts.m_show_nesting);
-  sink->set_show_locations_in_nesting (opts.m_show_locations_in_nesting);
-  sink->set_show_nesting_levels (opts.m_show_levels);
-  pp_show_color (sink->get_printer ()) = opts.m_show_color;
+  sink->set_show_nesting (m_show_nesting);
+  sink->set_show_locations_in_nesting (m_show_locations_in_nesting);
+  sink->set_show_nesting_levels (m_show_levels);
+  pp_show_color (sink->get_printer ()) = m_show_color;
   return sink;
 }
 
-bool
-text_scheme_handler::decode_kv (const context &ctxt,
-                               const std::string &key,
-                               const std::string &value,
-                               decoded_args &opts_out) const
+enum key_handler::result
+text_scheme_handler::maybe_handle_kv (const context &ctxt,
+                                     const std::string &key,
+                                     const std::string &value)
 {
   if (key == "color")
-    return parse_bool_value (ctxt, key, value, opts_out.m_show_color);
+    return parse_bool_value (ctxt, key, value, m_show_color);
   if (key == "show-nesting")
-    return parse_bool_value (ctxt, key, value, opts_out.m_show_nesting);
+    return parse_bool_value (ctxt, key, value, m_show_nesting);
   if (key == "show-nesting-locations")
     return parse_bool_value (ctxt, key, value,
-                            opts_out.m_show_locations_in_nesting);
+                            m_show_locations_in_nesting);
   if (key == "show-nesting-levels")
-    return parse_bool_value (ctxt, key, value, opts_out.m_show_levels);
-
-  /* Key not found.  */
-  auto_vec<const char *> known_keys;
-  known_keys.safe_push ("color");
-  known_keys.safe_push ("show-nesting");
-  known_keys.safe_push ("show-nesting-locations");
-  known_keys.safe_push ("show-nesting-levels");
-  ctxt.report_unknown_key (key, get_scheme_name (), known_keys);
-  return false;
+    return parse_bool_value (ctxt, key, value, m_show_levels);
+
+  return result::unrecognized;
+}
+
+void
+text_scheme_handler::get_keys (auto_vec<const char *> &out) const
+{
+  out.safe_push ("color");
+  out.safe_push ("show-nesting");
+  out.safe_push ("show-nesting-locations");
+  out.safe_push ("show-nesting-levels");
 }
 
 /* class sarif_scheme_handler : public scheme_handler.  */
@@ -453,23 +495,11 @@ text_scheme_handler::decode_kv (const context &ctxt,
 std::unique_ptr<sink>
 sarif_scheme_handler::
 make_sink (const context &ctxt,
-          diagnostics::context &dc,
-          const scheme_name_and_params &scheme_and_kvs) const
+          diagnostics::context &dc)
 {
-  decoded_args opts;
-  opts.m_serialization_kind = sarif_serialization_kind::json;
-
-  for (auto& iter : scheme_and_kvs.m_kvs)
-    {
-      const std::string &key = iter.first;
-      const std::string &value = iter.second;
-      if (!decode_kv (ctxt, key, value, opts))
-       return nullptr;
-    }
-
   output_file output_file_;
-  if (opts.m_filename.get ())
-    output_file_ = ctxt.open_output_file (std::move (opts.m_filename));
+  if (m_filename.get ())
+    output_file_ = ctxt.open_output_file (std::move (m_filename));
   else
     // Default filename
     {
@@ -485,33 +515,32 @@ make_sink (const context &ctxt,
        = open_sarif_output_file (dc,
                                  ctxt.get_affected_location_mgr (),
                                  basename,
-                                 opts.m_serialization_kind);
+                                 m_serialization_kind);
     }
   if (!output_file_)
     return nullptr;
 
   auto serialization_obj
-    = make_sarif_serialization_object (opts.m_serialization_kind);
+    = make_sarif_serialization_object (m_serialization_kind);
 
   auto sink = make_sarif_sink (dc,
                               *ctxt.get_affected_location_mgr (),
                               std::move (serialization_obj),
-                              opts.m_generation_opts,
+                              m_generation_opts,
                               std::move (output_file_));
 
   return sink;
 }
 
-bool
-sarif_scheme_handler::decode_kv (const context &ctxt,
-                                const std::string &key,
-                                const std::string &value,
-                                decoded_args &opts_out) const
+enum key_handler::result
+sarif_scheme_handler::maybe_handle_kv (const context &ctxt,
+                                      const std::string &key,
+                                      const std::string &value)
 {
   if (key == "file")
     {
-      opts_out.m_filename = label_text::take (xstrdup (value.c_str ()));
-      return true;
+      m_filename = label_text::take (xstrdup (value.c_str ()));
+      return result::ok;
     }
   if (key == "serialization")
     {
@@ -522,7 +551,7 @@ sarif_scheme_handler::decode_kv (const context &ctxt,
        (ctxt,
         key, value,
         value_names,
-        opts_out.m_serialization_kind);
+        m_serialization_kind);
     }
   if (key == "version")
     {
@@ -534,20 +563,22 @@ sarif_scheme_handler::decode_kv (const context &ctxt,
        (ctxt,
         key, value,
         value_names,
-        opts_out.m_generation_opts.m_version);
+        m_generation_opts.m_version);
     }
   if (key == "state-graphs")
     return parse_bool_value (ctxt, key, value,
-                            opts_out.m_generation_opts.m_state_graph);
-
-  /* Key not found.  */
-  auto_vec<const char *> known_keys;
-  known_keys.safe_push ("file");
-  known_keys.safe_push ("serialization");
-  known_keys.safe_push ("state-graphs");
-  known_keys.safe_push ("version");
-  ctxt.report_unknown_key (key, get_scheme_name (), known_keys);
-  return false;
+                            m_generation_opts.m_state_graph);
+
+  return result::unrecognized;
+}
+
+void
+sarif_scheme_handler::get_keys (auto_vec<const char *> &out) const
+{
+  out.safe_push ("file");
+  out.safe_push ("serialization");
+  out.safe_push ("state-graphs");
+  out.safe_push ("version");
 }
 
 std::unique_ptr<sarif_serialization_format>
@@ -569,21 +600,11 @@ make_sarif_serialization_object (enum sarif_serialization_kind kind)
 std::unique_ptr<sink>
 html_scheme_handler::
 make_sink (const context &ctxt,
-          diagnostics::context &dc,
-          const scheme_name_and_params &scheme_and_kvs) const
+          diagnostics::context &dc)
 {
-  decoded_args opts;
-  for (auto& iter : scheme_and_kvs.m_kvs)
-    {
-      const std::string &key = iter.first;
-      const std::string &value = iter.second;
-      if (!decode_kv (ctxt, key, value, opts))
-       return nullptr;
-    }
-
   output_file output_file_;
-  if (opts.m_filename.get ())
-    output_file_ = ctxt.open_output_file (std::move (opts.m_filename));
+  if (m_filename.get ())
+    output_file_ = ctxt.open_output_file (std::move (m_filename));
   else
     // Default filename
     {
@@ -606,49 +627,47 @@ make_sink (const context &ctxt,
 
   auto sink = make_html_sink (dc,
                              *ctxt.get_affected_location_mgr (),
-                             opts.m_html_gen_opts,
+                             m_html_gen_opts,
                              std::move (output_file_));
   return sink;
 }
 
-bool
-html_scheme_handler::decode_kv (const context &ctxt,
-                               const std::string &key,
-                               const std::string &value,
-                               decoded_args &opts_out) const
+enum key_handler::result
+html_scheme_handler::maybe_handle_kv (const context &ctxt,
+                                     const std::string &key,
+                                     const std::string &value)
 {
   if (key == "css")
-    return parse_bool_value (ctxt, key, value, opts_out.m_html_gen_opts.m_css);
+    return parse_bool_value (ctxt, key, value, m_html_gen_opts.m_css);
   if (key == "file")
     {
-      opts_out.m_filename = label_text::take (xstrdup (value.c_str ()));
-      return true;
+      m_filename = label_text::take (xstrdup (value.c_str ()));
+      return result::ok;
     }
   if (key == "javascript")
     return parse_bool_value (ctxt, key, value,
-                            opts_out.m_html_gen_opts.m_javascript);
+                            m_html_gen_opts.m_javascript);
   if (key == "show-state-diagrams")
     return parse_bool_value (ctxt, key, value,
-                            opts_out.m_html_gen_opts.m_show_state_diagrams);
+                            m_html_gen_opts.m_show_state_diagrams);
   if (key == "show-state-diagrams-dot-src")
-    return parse_bool_value
-      (ctxt, key, value,
-       opts_out.m_html_gen_opts.m_show_state_diagrams_dot_src);
+    return parse_bool_value (ctxt, key, value,
+                            m_html_gen_opts.m_show_state_diagrams_dot_src);
   if (key == "show-state-diagrams-sarif")
-    return parse_bool_value
-      (ctxt, key, value,
-       opts_out.m_html_gen_opts.m_show_state_diagrams_sarif);
-
-  /* Key not found.  */
-  auto_vec<const char *> known_keys;
-  known_keys.safe_push ("css");
-  known_keys.safe_push ("file");
-  known_keys.safe_push ("javascript");
-  known_keys.safe_push ("show-state-diagrams");
-  known_keys.safe_push ("show-state-diagram-dot-src");
-  known_keys.safe_push ("show-state-diagram-sarif");
-  ctxt.report_unknown_key (key, get_scheme_name (), known_keys);
-  return false;
+    return parse_bool_value (ctxt, key, value,
+                            m_html_gen_opts.m_show_state_diagrams_sarif);
+  return result::unrecognized;
+}
+
+void
+html_scheme_handler::get_keys (auto_vec<const char *> &out) const
+{
+  out.safe_push ("css");
+  out.safe_push ("file");
+  out.safe_push ("javascript");
+  out.safe_push ("show-state-diagrams");
+  out.safe_push ("show-state-diagrams-dot-src");
+  out.safe_push ("show-state-diagrams-sarif");
 }
 
 } // namespace output_spec
@@ -685,17 +704,19 @@ struct parser_test
   class test_spec_context : public diagnostics::output_spec::dc_spec_context
   {
   public:
-    test_spec_context (diagnostics::context &dc,
+    test_spec_context (const char *option_name,
+                      const char *unparsed_spec,
+                      diagnostics::output_spec::key_handler *client_keys,
                       line_maps *location_mgr,
-                      location_t loc,
-                      const char *option_name,
-                      const char *unparsed_arg)
-    : dc_spec_context (dc,
+                      diagnostics::context &dc,
+                      location_t loc)
+    : dc_spec_context (option_name,
+                      unparsed_spec,
+                      client_keys,
                       location_mgr,
+                      dc,
                       location_mgr,
-                      loc,
-                      option_name,
-                      unparsed_arg)
+                      loc)
     {
     }
 
@@ -706,9 +727,15 @@ struct parser_test
     }
   };
 
-  parser_test (const char *unparsed_spec)
+  parser_test (const char *unparsed_spec,
+              diagnostics::output_spec::key_handler *client_keys = nullptr)
   : m_dc (),
-    m_ctxt (m_dc, line_table, UNKNOWN_LOCATION, "-fOPTION=", unparsed_spec),
+    m_ctxt ("-fOPTION=",
+           unparsed_spec,
+           client_keys,
+           line_table,
+           m_dc,
+           UNKNOWN_LOCATION),
     m_fmt (m_dc.get_sink (0))
   {
     pp_buffer (m_fmt.get_printer ())->m_flush_p = false;
@@ -720,6 +747,12 @@ struct parser_test
     return diagnostics::output_spec::parse (m_ctxt);
   }
 
+  std::unique_ptr<diagnostics::sink>
+  parse_and_make_sink ()
+  {
+    return m_ctxt.parse_and_make_sink (m_dc);
+  }
+
   bool execution_failed_p () const
   {
     return m_dc.execution_failed_p ();
@@ -742,9 +775,6 @@ private:
 static void
 test_output_arg_parsing ()
 {
-  auto_fix_quotes fix_quotes;
-  auto_fix_progname fix_progname;
-
   /* Minimal correct example.  */
   {
     parser_test pt ("foo");
@@ -831,12 +861,59 @@ test_output_arg_parsing ()
   }
 }
 
+class test_key_handler : public diagnostics::output_spec::key_handler
+{
+public:
+  test_key_handler ()
+  : m_verbose (false),
+    m_strict (false)
+  {
+  }
+
+  enum result
+  maybe_handle_kv (const diagnostics::output_spec::context &ctxt,
+                  const std::string &key,
+                  const std::string &value) final override
+  {
+    if (key == "verbose")
+      return parse_bool_value (ctxt, key, value, m_verbose);
+    if (key == "strict")
+      return parse_bool_value (ctxt, key, value, m_strict);
+    return result::unrecognized;
+  }
+
+  void
+  get_keys (auto_vec<const char *> &out_known_keys) const final override
+  {
+    out_known_keys.safe_push ("verbose");
+    out_known_keys.safe_push ("strict");
+  }
+
+  bool m_verbose;
+  bool m_strict;
+};
+
+static void
+test_client_arg_parsing ()
+{
+  test_key_handler client_keys;
+  parser_test pt ("text:verbose=yes,strict=no", &client_keys);
+  auto result = pt.parse_and_make_sink ();
+  ASSERT_TRUE (result.get ());
+  ASSERT_TRUE (client_keys.m_verbose);
+  ASSERT_FALSE (client_keys.m_strict);
+}
+
 /* Run all of the selftests within this file.  */
 
 void
 output_spec_cc_tests ()
 {
+  auto_fix_quotes fix_quotes;
+  auto_fix_progname fix_progname;
+
   test_output_arg_parsing ();
+  test_client_arg_parsing ();
 }
 
 } // namespace diagnostics::selftest
index e24002bc8ae4bbde017dddb5c2f9efd97ff3fd03..bfc42c0c2de7ec3e4a553b56cadb9ebfcf7d9e73 100644 (file)
@@ -27,12 +27,71 @@ along with GCC; see the file COPYING3.  If not see
 namespace diagnostics {
 namespace output_spec {
 
+class context;
+
+/* An abstract base class for schemes, and for client-specific keys.  */
+
+class key_handler
+{
+public:
+  enum class result
+  {
+    ok,
+    unrecognized,
+    malformed_value
+  };
+
+  /* Attempt to decode KEY and VALUE, storing the decoded value.  */
+  virtual enum result
+  maybe_handle_kv (const context &ctxt,
+                  const std::string &key,
+                  const std::string &value) = 0;
+
+  virtual void
+  get_keys (auto_vec<const char *> &out) const = 0;
+
+  enum result
+  parse_bool_value (const context &ctxt,
+                   const std::string &key,
+                   const std::string &value,
+                   bool &out) const;
+
+  template <typename EnumType, size_t NumValues>
+  enum result
+  parse_enum_value (const context &ctxt,
+                   const std::string &key,
+                   const std::string &value,
+                   const std::array<std::pair<const char *, EnumType>,
+                                    NumValues> &value_names,
+                   EnumType &out) const;
+};
+
+/* Abstract subclass for handling particular schemes and their keys.  */
+
+class scheme_handler : public key_handler
+{
+public:
+  scheme_handler (std::string scheme_name)
+    : m_scheme_name (std::move (scheme_name))
+  {}
+  virtual ~scheme_handler () {}
+
+  const std::string &get_scheme_name () const { return m_scheme_name; }
+
+  virtual std::unique_ptr<sink>
+  make_sink (const context &ctxt,
+            diagnostics::context &dc) = 0;
+
+private:
+  const std::string m_scheme_name;
+};
+
 /* An abstract base class for handling the DSL of -fdiagnostics-add-output=
    and -fdiagnostics-set-output=.  */
 
 class context
 {
- public:
+public:
   std::unique_ptr<sink>
   parse_and_make_sink (diagnostics::context &dc);
 
@@ -42,8 +101,7 @@ class context
 
   void
   report_unknown_key (const std::string &key,
-                     const std::string &scheme_name,
-                     auto_vec<const char *> &known_keys) const;
+                     const scheme_handler &scheme) const;
 
   void
   report_missing_key (const std::string &key,
@@ -70,12 +128,19 @@ class context
   virtual const char *
   get_base_filename () const = 0;
 
+  bool
+  handle_kv (const std::string &key,
+            const std::string &value,
+            scheme_handler &scheme) const;
+
 protected:
   context (const char *option_name,
           const char *unparsed_spec,
+          key_handler *client_keys,
           line_maps *affected_location_mgr)
   : m_option_name (option_name),
     m_unparsed_spec (unparsed_spec),
+    m_client_keys (client_keys),
     m_affected_location_mgr (affected_location_mgr)
   {
   }
@@ -86,6 +151,9 @@ protected:
   // e.g. "scheme:foo=bar,key=value"
   const char *m_unparsed_spec;
 
+  // Optional borrowed ptr to client-specific keys
+  key_handler *m_client_keys;
+
   line_maps *m_affected_location_mgr;
 };
 
@@ -94,13 +162,17 @@ protected:
 struct dc_spec_context : public output_spec::context
 {
 public:
-  dc_spec_context (diagnostics::context &dc,
+  dc_spec_context (const char *option_name,
+                  const char *unparsed_spec,
+                  key_handler *client_keys,
                   line_maps *affected_location_mgr,
+                  diagnostics::context &dc,
                   line_maps *control_location_mgr,
-                  location_t loc,
-                  const char *option_name,
-                  const char *unparsed_spec)
-  : context (option_name, unparsed_spec, affected_location_mgr),
+                  location_t loc)
+    : context (option_name,
+              unparsed_spec,
+              client_keys,
+              affected_location_mgr),
     m_dc (dc),
     m_control_location_mgr (control_location_mgr),
     m_loc (loc)
index 6b269a1d1fe847b0b99dc3175e0cfbbedf5a60be..46714ff2aa5ed6ad98677a9cc1dfb2fbe413c338 100644 (file)
@@ -2484,7 +2484,8 @@ public:
                const char *unparsed_spec,
                diagnostic_manager &affected_mgr,
                diagnostic_manager &control_mgr)
-  : context (option_name, unparsed_spec, affected_mgr.get_line_table ()),
+  : context (option_name, unparsed_spec, nullptr,
+            affected_mgr.get_line_table ()),
     m_control_mgr (control_mgr)
   {}
 
index 6f459ec4617365b10cbddbf3020a5401cca4b393..a230c2119a9f6290478958ae3fe58bc865d34315 100644 (file)
@@ -49,12 +49,13 @@ public:
                    location_t loc,
                    const char *option_name,
                    const char *option_value)
-  : dc_spec_context (dc,
+  : dc_spec_context (option_name,
+                    option_value,
+                    nullptr,
                     location_mgr,
+                    dc,
                     location_mgr,
-                    loc,
-                    option_name,
-                    option_value),
+                    loc),
     m_opts (opts)
   {}