aboutsummaryrefslogtreecommitdiff
path: root/gcc/diagnostic-output-spec.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/diagnostic-output-spec.cc')
-rw-r--r--gcc/diagnostic-output-spec.cc849
1 files changed, 849 insertions, 0 deletions
diff --git a/gcc/diagnostic-output-spec.cc b/gcc/diagnostic-output-spec.cc
new file mode 100644
index 0000000..25ef86f
--- /dev/null
+++ b/gcc/diagnostic-output-spec.cc
@@ -0,0 +1,849 @@
+/* Support for the DSL of -fdiagnostics-add-output= and
+ -fdiagnostics-set-output=.
+ Copyright (C) 2024-2025 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+/* This file implements the domain-specific language for the options
+ -fdiagnostics-add-output= and -fdiagnostics-set-output=, and for
+ the "diagnostic_manager_add_sink_from_spec" entrypoint to
+ libgdiagnostics. */
+
+#include "config.h"
+#define INCLUDE_ARRAY
+#define INCLUDE_STRING
+#define INCLUDE_VECTOR
+#include "system.h"
+#include "coretypes.h"
+#include "version.h"
+#include "intl.h"
+#include "diagnostic.h"
+#include "diagnostic-color.h"
+#include "diagnostic-format.h"
+#include "diagnostic-format-html.h"
+#include "diagnostic-format-text.h"
+#include "diagnostic-format-sarif.h"
+#include "selftest.h"
+#include "selftest-diagnostic.h"
+#include "pretty-print-markup.h"
+#include "diagnostic-output-spec.h"
+
+/* A namespace for handling the DSL of the arguments of
+ -fdiagnostics-add-output= and -fdiagnostics-set-output=. */
+
+namespace diagnostics_output_spec {
+
+/* Decls. */
+
+struct scheme_name_and_params
+{
+ std::string m_scheme_name;
+ std::vector<std::pair<std::string, std::string>> m_kvs;
+};
+
+/* Class for parsing the arguments of -fdiagnostics-add-output= and
+ -fdiagnostics-set-output=, and making diagnostic_output_format
+ instances (or issuing errors). */
+
+class output_factory
+{
+public:
+ class scheme_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<diagnostic_output_format>
+ make_sink (const context &ctxt,
+ diagnostic_context &dc,
+ const char *unparsed_arg,
+ const scheme_name_and_params &parsed_arg) const = 0;
+
+ protected:
+ bool
+ parse_bool_value (const context &ctxt,
+ const char *unparsed_arg,
+ const std::string &key,
+ const std::string &value,
+ bool &out) const
+ {
+ if (value == "yes")
+ {
+ out = true;
+ return true;
+ }
+ else if (value == "no")
+ {
+ out = false;
+ return true;
+ }
+ else
+ {
+ ctxt.report_error
+ ("%<%s%s%>:"
+ " unexpected value %qs for key %qs; expected %qs or %qs",
+ ctxt.get_option_name (), unparsed_arg,
+ value.c_str (),
+ key.c_str (),
+ "yes", "no");
+
+ return false;
+ }
+ }
+ template <typename EnumType, size_t NumValues>
+ bool
+ parse_enum_value (const context &ctxt,
+ const char *unparsed_arg,
+ const std::string &key,
+ const std::string &value,
+ const std::array<std::pair<const char *, EnumType>, NumValues> &value_names,
+ EnumType &out) const
+ {
+ for (auto &iter : value_names)
+ if (value == iter.first)
+ {
+ out = iter.second;
+ return true;
+ }
+
+ auto_vec<const char *> known_values;
+ for (auto iter : value_names)
+ known_values.safe_push (iter.first);
+ pp_markup::comma_separated_quoted_strings e (known_values);
+ ctxt.report_error
+ ("%<%s%s%>:"
+ " unexpected value %qs for key %qs; known values: %e",
+ ctxt.get_option_name (), unparsed_arg,
+ value.c_str (),
+ key.c_str (),
+ &e);
+ return false;
+ }
+
+ private:
+ const std::string m_scheme_name;
+ };
+
+ output_factory ();
+
+ std::unique_ptr<diagnostic_output_format>
+ make_sink (const context &ctxt,
+ diagnostic_context &dc,
+ const char *unparsed_arg,
+ const scheme_name_and_params &parsed_arg);
+
+ const scheme_handler *get_scheme_handler (const std::string &scheme_name);
+
+private:
+ std::vector<std::unique_ptr<scheme_handler>> m_scheme_handlers;
+};
+
+class text_scheme_handler : public output_factory::scheme_handler
+{
+public:
+ text_scheme_handler () : scheme_handler ("text") {}
+
+ std::unique_ptr<diagnostic_output_format>
+ make_sink (const context &ctxt,
+ diagnostic_context &dc,
+ const char *unparsed_arg,
+ const scheme_name_and_params &parsed_arg) const final override;
+};
+
+class sarif_scheme_handler : public output_factory::scheme_handler
+{
+public:
+ sarif_scheme_handler () : scheme_handler ("sarif") {}
+
+ std::unique_ptr<diagnostic_output_format>
+ make_sink (const context &ctxt,
+ diagnostic_context &dc,
+ const char *unparsed_arg,
+ const scheme_name_and_params &parsed_arg) const final override;
+
+private:
+ static sarif_generation_options
+ make_sarif_gen_opts (enum sarif_version version,
+ bool xml_state);
+
+ static std::unique_ptr<sarif_serialization_format>
+ make_sarif_serialization_object (enum sarif_serialization_kind);
+};
+
+class html_scheme_handler : public output_factory::scheme_handler
+{
+public:
+ html_scheme_handler () : scheme_handler ("experimental-html") {}
+
+ std::unique_ptr<diagnostic_output_format>
+ make_sink (const context &ctxt,
+ diagnostic_context &dc,
+ const char *unparsed_arg,
+ const scheme_name_and_params &parsed_arg) const final override;
+};
+
+/* struct context. */
+
+void
+context::report_error (const char *gmsgid, ...) const
+{
+ va_list ap;
+ va_start (ap, gmsgid);
+ report_error_va (gmsgid, &ap);
+ va_end (ap);
+}
+
+void
+context::report_unknown_key (const char *unparsed_arg,
+ const std::string &key,
+ const std::string &scheme_name,
+ auto_vec<const char *> &known_keys) const
+{
+ pp_markup::comma_separated_quoted_strings e (known_keys);
+ report_error
+ ("%<%s%s%>:"
+ " unknown key %qs for format %qs; known keys: %e",
+ get_option_name (), unparsed_arg,
+ key.c_str (), scheme_name.c_str (), &e);
+}
+
+void
+context::report_missing_key (const char *unparsed_arg,
+ const std::string &key,
+ const std::string &scheme_name,
+ const char *metavar) const
+{
+ report_error
+ ("%<%s%s%>:"
+ " missing required key %qs for format %qs;"
+ " try %<%s%s:%s=%s%>",
+ get_option_name (), unparsed_arg,
+ key.c_str (), scheme_name.c_str (),
+ get_option_name (), scheme_name.c_str (), key.c_str (), metavar);
+}
+
+diagnostic_output_file
+context::open_output_file (label_text &&filename) const
+{
+ FILE *outf = fopen (filename.get (), "w");
+ if (!outf)
+ {
+ report_error ("unable to open %qs: %m", filename.get ());
+ return diagnostic_output_file (nullptr, false, std::move (filename));
+ }
+ return diagnostic_output_file (outf, true, std::move (filename));
+}
+
+static std::unique_ptr<scheme_name_and_params>
+parse (const context &ctxt, const char *unparsed_arg)
+{
+ scheme_name_and_params result;
+ if (const char *const colon = strchr (unparsed_arg, ':'))
+ {
+ result.m_scheme_name = std::string (unparsed_arg, colon - unparsed_arg);
+ /* Expect zero of more of KEY=VALUE,KEY=VALUE, etc .*/
+ const char *iter = colon + 1;
+ const char *last_separator = ":";
+ while (iter)
+ {
+ /* Look for a non-empty key string followed by '='. */
+ const char *eq = strchr (iter, '=');
+ if (eq == nullptr || eq == iter)
+ {
+ /* Missing '='. */
+ ctxt.report_error
+ ("%<%s%s%>:"
+ " expected KEY=VALUE-style parameter for format %qs"
+ " after %qs;"
+ " got %qs",
+ ctxt.get_option_name (), unparsed_arg,
+ result.m_scheme_name.c_str (),
+ last_separator,
+ iter);
+ return nullptr;
+ }
+ std::string key = std::string (iter, eq - iter);
+ std::string value;
+ const char *comma = strchr (iter, ',');
+ if (comma)
+ {
+ value = std::string (eq + 1, comma - (eq + 1));
+ iter = comma + 1;
+ last_separator = ",";
+ }
+ else
+ {
+ value = std::string (eq + 1);
+ iter = nullptr;
+ }
+ result.m_kvs.push_back ({std::move (key), std::move (value)});
+ }
+ }
+ else
+ result.m_scheme_name = unparsed_arg;
+ return std::make_unique<scheme_name_and_params> (std::move (result));
+}
+
+std::unique_ptr<diagnostic_output_format>
+context::parse_and_make_sink (const char *unparsed_arg,
+ diagnostic_context &dc)
+{
+ auto parsed_arg = diagnostics_output_spec::parse (*this, unparsed_arg);
+ if (!parsed_arg)
+ return nullptr;
+
+ diagnostics_output_spec::output_factory factory;
+ return factory.make_sink (*this, dc, unparsed_arg, *parsed_arg);
+}
+
+/* class output_factory::scheme_handler. */
+
+/* class output_factory. */
+
+output_factory::output_factory ()
+{
+ m_scheme_handlers.push_back (std::make_unique<text_scheme_handler> ());
+ m_scheme_handlers.push_back (std::make_unique<sarif_scheme_handler> ());
+ m_scheme_handlers.push_back (std::make_unique<html_scheme_handler> ());
+}
+
+const output_factory::scheme_handler *
+output_factory::get_scheme_handler (const std::string &scheme_name)
+{
+ for (auto &iter : m_scheme_handlers)
+ if (iter->get_scheme_name () == scheme_name)
+ return iter.get ();
+ return nullptr;
+}
+
+std::unique_ptr<diagnostic_output_format>
+output_factory::make_sink (const context &ctxt,
+ diagnostic_context &dc,
+ const char *unparsed_arg,
+ const scheme_name_and_params &parsed_arg)
+{
+ auto scheme_handler = get_scheme_handler (parsed_arg.m_scheme_name);
+ if (!scheme_handler)
+ {
+ auto_vec<const char *> strings;
+ for (auto &iter : m_scheme_handlers)
+ strings.safe_push (iter->get_scheme_name ().c_str ());
+ pp_markup::comma_separated_quoted_strings e (strings);
+ ctxt.report_error ("%<%s%s%>:"
+ " unrecognized format %qs; known formats: %e",
+ ctxt.get_option_name (), unparsed_arg,
+ parsed_arg.m_scheme_name.c_str (), &e);
+ return nullptr;
+ }
+
+ return scheme_handler->make_sink (ctxt, dc, unparsed_arg, parsed_arg);
+}
+
+/* class text_scheme_handler : public output_factory::scheme_handler. */
+
+std::unique_ptr<diagnostic_output_format>
+text_scheme_handler::make_sink (const context &ctxt,
+ diagnostic_context &dc,
+ const char *unparsed_arg,
+ const scheme_name_and_params &parsed_arg) const
+{
+ bool show_color = pp_show_color (dc.get_reference_printer ());
+ bool show_nesting = false;
+ bool show_locations_in_nesting = true;
+ bool show_levels = false;
+ for (auto& iter : parsed_arg.m_kvs)
+ {
+ const std::string &key = iter.first;
+ const std::string &value = iter.second;
+ if (key == "color")
+ {
+ if (!parse_bool_value (ctxt, unparsed_arg, key, value, show_color))
+ return nullptr;
+ continue;
+ }
+ if (key == "experimental-nesting")
+ {
+ if (!parse_bool_value (ctxt, unparsed_arg, key, value,
+ show_nesting))
+ return nullptr;
+ continue;
+ }
+ if (key == "experimental-nesting-show-locations")
+ {
+ if (!parse_bool_value (ctxt, unparsed_arg, key, value,
+ show_locations_in_nesting))
+ return nullptr;
+ continue;
+ }
+ if (key == "experimental-nesting-show-levels")
+ {
+ if (!parse_bool_value (ctxt, unparsed_arg, key, value, show_levels))
+ return nullptr;
+ continue;
+ }
+
+ /* Key not found. */
+ auto_vec<const char *> known_keys;
+ known_keys.safe_push ("color");
+ known_keys.safe_push ("experimental-nesting");
+ known_keys.safe_push ("experimental-nesting-show-locations");
+ known_keys.safe_push ("experimental-nesting-show-levels");
+ ctxt.report_unknown_key (unparsed_arg, key, get_scheme_name (),
+ known_keys);
+ return nullptr;
+ }
+
+ auto sink = std::make_unique<diagnostic_text_output_format> (dc);
+ sink->set_show_nesting (show_nesting);
+ sink->set_show_locations_in_nesting (show_locations_in_nesting);
+ sink->set_show_nesting_levels (show_levels);
+ return sink;
+}
+
+/* class sarif_scheme_handler : public output_factory::scheme_handler. */
+
+std::unique_ptr<diagnostic_output_format>
+sarif_scheme_handler::make_sink (const context &ctxt,
+ diagnostic_context &dc,
+ const char *unparsed_arg,
+ const scheme_name_and_params &parsed_arg) const
+{
+ label_text filename;
+ enum sarif_serialization_kind serialization_kind
+ = sarif_serialization_kind::json;
+ enum sarif_version version = sarif_version::v2_1_0;
+ bool xml_state = false;
+ for (auto& iter : parsed_arg.m_kvs)
+ {
+ const std::string &key = iter.first;
+ const std::string &value = iter.second;
+ if (key == "file")
+ {
+ filename = label_text::take (xstrdup (value.c_str ()));
+ continue;
+ }
+ if (key == "serialization")
+ {
+ static const std::array<std::pair<const char *, enum sarif_serialization_kind>,
+ (size_t)sarif_serialization_kind::num_values> value_names
+ {{{"json", sarif_serialization_kind::json}}};
+
+ if (!parse_enum_value<enum sarif_serialization_kind>
+ (ctxt, unparsed_arg,
+ key, value,
+ value_names,
+ serialization_kind))
+ return nullptr;
+ continue;
+ }
+ if (key == "version")
+ {
+ static const std::array<std::pair<const char *, enum sarif_version>,
+ (size_t)sarif_version::num_versions> value_names
+ {{{"2.1", sarif_version::v2_1_0},
+ {"2.2-prerelease", sarif_version::v2_2_prerelease_2024_08_08}}};
+
+ if (!parse_enum_value<enum sarif_version> (ctxt, unparsed_arg,
+ key, value,
+ value_names,
+ version))
+ return nullptr;
+ continue;
+ }
+ if (key == "xml-state")
+ {
+ if (!parse_bool_value (ctxt, unparsed_arg, key, value,
+ xml_state))
+ return nullptr;
+ continue;
+ }
+
+ /* Key not found. */
+ auto_vec<const char *> known_keys;
+ known_keys.safe_push ("file");
+ known_keys.safe_push ("serialization");
+ known_keys.safe_push ("version");
+ known_keys.safe_push ("xml-state");
+ ctxt.report_unknown_key (unparsed_arg, key, get_scheme_name (),
+ known_keys);
+ return nullptr;
+ }
+
+ diagnostic_output_file output_file;
+ if (filename.get ())
+ output_file = ctxt.open_output_file (std::move (filename));
+ else
+ // Default filename
+ {
+ const char *basename = ctxt.get_base_filename ();
+ if (!basename)
+ {
+ ctxt.report_missing_key (unparsed_arg,
+ "file",
+ get_scheme_name (),
+ "FILENAME");
+ return nullptr;
+ }
+ output_file
+ = diagnostic_output_format_open_sarif_file
+ (dc,
+ ctxt.get_affected_location_mgr (),
+ basename,
+ serialization_kind);
+ }
+ if (!output_file)
+ return nullptr;
+
+ auto sarif_gen_opts = make_sarif_gen_opts (version, xml_state);
+
+ auto serialization_obj = make_sarif_serialization_object (serialization_kind);
+
+ auto sink = make_sarif_sink (dc,
+ *ctxt.get_affected_location_mgr (),
+ std::move (serialization_obj),
+ sarif_gen_opts,
+ std::move (output_file));
+ return sink;
+}
+
+sarif_generation_options
+sarif_scheme_handler::make_sarif_gen_opts (enum sarif_version version,
+ bool xml_state)
+{
+ sarif_generation_options sarif_gen_opts;
+ sarif_gen_opts.m_version = version;
+ sarif_gen_opts.m_xml_state = xml_state;
+ return sarif_gen_opts;
+}
+
+std::unique_ptr<sarif_serialization_format>
+sarif_scheme_handler::
+make_sarif_serialization_object (enum sarif_serialization_kind kind)
+{
+ switch (kind)
+ {
+ default:
+ gcc_unreachable ();
+ case sarif_serialization_kind::json:
+ return std::make_unique<sarif_serialization_format_json> (true);
+ break;
+ }
+}
+
+/* class html_scheme_handler : public output_factory::scheme_handler. */
+
+std::unique_ptr<diagnostic_output_format>
+html_scheme_handler::make_sink (const context &ctxt,
+ diagnostic_context &dc,
+ const char *unparsed_arg,
+ const scheme_name_and_params &parsed_arg) const
+{
+ bool css = true;
+ label_text filename;
+ bool javascript = true;
+ bool show_state_diagrams = false;
+ bool show_state_diagram_xml = false;
+ bool show_state_diagram_dot_src = false;
+ for (auto& iter : parsed_arg.m_kvs)
+ {
+ const std::string &key = iter.first;
+ const std::string &value = iter.second;
+ if (key == "css")
+ {
+ if (!parse_bool_value (ctxt, unparsed_arg, key, value,
+ css))
+ return nullptr;
+ continue;
+ }
+ if (key == "file")
+ {
+ filename = label_text::take (xstrdup (value.c_str ()));
+ continue;
+ }
+ if (key == "javascript")
+ {
+ if (!parse_bool_value (ctxt, unparsed_arg, key, value,
+ javascript))
+ return nullptr;
+ continue;
+ }
+ if (key == "show-state-diagrams")
+ {
+ if (!parse_bool_value (ctxt, unparsed_arg, key, value,
+ show_state_diagrams))
+ return nullptr;
+ continue;
+ }
+ if (key == "show-state-diagram-dot-src")
+ {
+ if (!parse_bool_value (ctxt, unparsed_arg, key, value,
+ show_state_diagram_dot_src))
+ return nullptr;
+ continue;
+ }
+ if (key == "show-state-diagram-xml")
+ {
+ if (!parse_bool_value (ctxt, unparsed_arg, key, value,
+ show_state_diagram_xml))
+ return nullptr;
+ continue;
+ }
+
+ /* 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-xml");
+ ctxt.report_unknown_key (unparsed_arg, key, get_scheme_name (),
+ known_keys);
+ return nullptr;
+ }
+
+ diagnostic_output_file output_file;
+ if (filename.get ())
+ output_file = ctxt.open_output_file (std::move (filename));
+ else
+ // Default filename
+ {
+ const char *basename = ctxt.get_base_filename ();
+ if (!basename)
+ {
+ ctxt.report_missing_key (unparsed_arg,
+ "file",
+ get_scheme_name (),
+ "FILENAME");
+ return nullptr;
+ }
+ output_file
+ = diagnostic_output_format_open_html_file
+ (dc,
+ ctxt.get_affected_location_mgr (),
+ basename);
+ }
+ if (!output_file)
+ return nullptr;
+
+ html_generation_options html_gen_opts;
+ html_gen_opts.m_css = css;
+ html_gen_opts.m_javascript = javascript;
+ html_gen_opts.m_show_state_diagrams = show_state_diagrams;
+ html_gen_opts.m_show_state_diagram_xml = show_state_diagram_xml;
+ html_gen_opts.m_show_state_diagram_dot_src = show_state_diagram_dot_src;
+
+ auto sink = make_html_sink (dc,
+ *ctxt.get_affected_location_mgr (),
+ html_gen_opts,
+ std::move (output_file));
+ return sink;
+}
+
+} // namespace diagnostics_output_spec
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* RAII class to temporarily override "progname" to the
+ string "PROGNAME". */
+
+class auto_fix_progname
+{
+public:
+ auto_fix_progname ()
+ {
+ m_old_progname = progname;
+ progname = "PROGNAME";
+ }
+
+ ~auto_fix_progname ()
+ {
+ progname = m_old_progname;
+ }
+
+private:
+ const char *m_old_progname;
+};
+
+struct parser_test
+{
+ class test_spec_context : public diagnostics_output_spec::gcc_spec_context
+ {
+ public:
+ test_spec_context (diagnostic_context &dc,
+ line_maps *location_mgr,
+ location_t loc,
+ const char *option_name)
+ : gcc_spec_context (dc,
+ location_mgr,
+ location_mgr,
+ loc,
+ option_name)
+ {
+ }
+
+ const char *
+ get_base_filename () const final override
+ {
+ return "BASE_FILENAME";
+ }
+ };
+
+ parser_test ()
+ : m_dc (),
+ m_ctxt (m_dc, line_table, UNKNOWN_LOCATION, "-fOPTION="),
+ m_fmt (m_dc.get_output_format (0))
+ {
+ pp_buffer (m_fmt.get_printer ())->m_flush_p = false;
+ }
+
+ std::unique_ptr<diagnostics_output_spec::scheme_name_and_params>
+ parse (const char *unparsed_arg)
+ {
+ return diagnostics_output_spec::parse (m_ctxt, unparsed_arg);
+ }
+
+ bool execution_failed_p () const
+ {
+ return m_dc.execution_failed_p ();
+ }
+
+ const char *
+ get_diagnostic_text () const
+ {
+ return pp_formatted_text (m_fmt.get_printer ());
+ }
+
+private:
+ test_diagnostic_context m_dc;
+ test_spec_context m_ctxt;
+ diagnostic_output_format &m_fmt;
+};
+
+/* Selftests. */
+
+static void
+test_output_arg_parsing ()
+{
+ auto_fix_quotes fix_quotes;
+ auto_fix_progname fix_progname;
+
+ /* Minimal correct example. */
+ {
+ parser_test pt;
+ auto result = pt.parse ("foo");
+ ASSERT_EQ (result->m_scheme_name, "foo");
+ ASSERT_EQ (result->m_kvs.size (), 0);
+ ASSERT_FALSE (pt.execution_failed_p ());
+ }
+
+ /* Stray trailing colon with no key/value pairs. */
+ {
+ parser_test pt;
+ auto result = pt.parse ("foo:");
+ ASSERT_EQ (result, nullptr);
+ ASSERT_TRUE (pt.execution_failed_p ());
+ ASSERT_STREQ (pt.get_diagnostic_text (),
+ "PROGNAME: error: `-fOPTION=foo:':"
+ " expected KEY=VALUE-style parameter for format `foo'"
+ " after `:';"
+ " got `'\n");
+ }
+
+ /* No key before '='. */
+ {
+ parser_test pt;
+ auto result = pt.parse ("foo:=");
+ ASSERT_EQ (result, nullptr);
+ ASSERT_TRUE (pt.execution_failed_p ());
+ ASSERT_STREQ (pt.get_diagnostic_text (),
+ "PROGNAME: error: `-fOPTION=foo:=':"
+ " expected KEY=VALUE-style parameter for format `foo'"
+ " after `:';"
+ " got `='\n");
+ }
+
+ /* No value for key. */
+ {
+ parser_test pt;
+ auto result = pt.parse ("foo:key,");
+ ASSERT_EQ (result, nullptr);
+ ASSERT_TRUE (pt.execution_failed_p ());
+ ASSERT_STREQ (pt.get_diagnostic_text (),
+ "PROGNAME: error: `-fOPTION=foo:key,':"
+ " expected KEY=VALUE-style parameter for format `foo'"
+ " after `:';"
+ " got `key,'\n");
+ }
+
+ /* Correct example, with one key/value pair. */
+ {
+ parser_test pt;
+ auto result = pt.parse ("foo:key=value");
+ ASSERT_EQ (result->m_scheme_name, "foo");
+ ASSERT_EQ (result->m_kvs.size (), 1);
+ ASSERT_EQ (result->m_kvs[0].first, "key");
+ ASSERT_EQ (result->m_kvs[0].second, "value");
+ ASSERT_FALSE (pt.execution_failed_p ());
+ }
+
+ /* Stray trailing comma. */
+ {
+ parser_test pt;
+ auto result = pt.parse ("foo:key=value,");
+ ASSERT_EQ (result, nullptr);
+ ASSERT_TRUE (pt.execution_failed_p ());
+ ASSERT_STREQ (pt.get_diagnostic_text (),
+ "PROGNAME: error: `-fOPTION=foo:key=value,':"
+ " expected KEY=VALUE-style parameter for format `foo'"
+ " after `,';"
+ " got `'\n");
+ }
+
+ /* Correct example, with two key/value pairs. */
+ {
+ parser_test pt;
+ auto result = pt.parse ("foo:color=red,shape=circle");
+ ASSERT_EQ (result->m_scheme_name, "foo");
+ ASSERT_EQ (result->m_kvs.size (), 2);
+ ASSERT_EQ (result->m_kvs[0].first, "color");
+ ASSERT_EQ (result->m_kvs[0].second, "red");
+ ASSERT_EQ (result->m_kvs[1].first, "shape");
+ ASSERT_EQ (result->m_kvs[1].second, "circle");
+ ASSERT_FALSE (pt.execution_failed_p ());
+ }
+}
+
+/* Run all of the selftests within this file. */
+
+void
+diagnostic_output_spec_cc_tests ()
+{
+ test_output_arg_parsing ();
+}
+
+} // namespace selftest
+
+
+#endif /* #if CHECKING_P */