def main(argv):
parser = argparse.ArgumentParser()#usage=__doc__)
parser.add_argument('paths', nargs='+', type=pathlib.Path)
+ parser.add_argument('-fdiagnostics-add-output', action='append')
opts = parser.parse_args(argv[1:])
ctxt = Context()
+ control_mgr = libgdiagnostics.Manager()
+ control_mgr.add_text_sink()
+ for scheme in opts.fdiagnostics_add_output:
+ ctxt.mgr.add_sink_from_spec("-fdiagnostics-add-output=",
+ scheme,
+ control_mgr)
+
for path in opts.paths:
if path.is_dir():
for dirpath, dirnames, filenames in os.walk(path):
ctypes.c_char_p]
cdll.diagnostic_add_fix_it_hint_replace.restype = None
+cdll.diagnostic_manager_add_sink_from_spec.argtypes \
+ = [c_diagnostic_manager_ptr,
+ ctypes.c_char_p,
+ ctypes.c_char_p,
+ c_diagnostic_manager_ptr]
+cdll.diagnostic_manager_add_sink_from_spec.restype = ctypes.c_int
+
# Helper functions
def _to_utf8(s: str):
c_stderr,
DIAGNOSTIC_COLORIZE_IF_TTY)
+ def add_sink_from_spec(self, option_name: str, scheme: str, control_mgr):
+ assert self.c_mgr
+ assert control_mgr.c_mgr
+ res = cdll.diagnostic_manager_add_sink_from_spec (self.c_mgr,
+ _to_utf8(option_name),
+ _to_utf8(scheme),
+ control_mgr.c_mgr)
+ if res:
+ raise RuntimeError()
+
def get_file(self, path: str, sarif_lang: str = None):
assert self.c_mgr
assert path
diagnostic-format-text.o \
diagnostic-global-context.o \
diagnostic-macro-unwinding.o \
+ diagnostic-output-spec.o \
diagnostic-path.o \
diagnostic-path-output.o \
diagnostic-show-locus.o \
{
xml::auto_print_element title (xp, "title", true);
m_title_element = xp.get_insertion_point ();
+ m_title_element->add_text (" ");
}
if (m_html_gen_opts.m_css)
--- /dev/null
+/* 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;
+};
+
+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;
+
+ sarif_generation_options sarif_gen_opts;
+ sarif_gen_opts.m_version = version;
+ sarif_gen_opts.m_xml_state = xml_state;
+
+ std::unique_ptr<sarif_serialization_format> serialization_obj;
+ switch (serialization_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case sarif_serialization_kind::json:
+ serialization_obj
+ = std::make_unique<sarif_serialization_format_json> (true);
+ break;
+ }
+
+ auto sink = make_sarif_sink (dc,
+ *ctxt.get_affected_location_mgr (),
+ std::move (serialization_obj),
+ sarif_gen_opts,
+ std::move (output_file));
+ return sink;
+}
+
+/* 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 */
--- /dev/null
+/* 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/>. */
+
+#ifndef GCC_DIAGNOSTIC_OUTPUT_SPEC_H
+#define GCC_DIAGNOSTIC_OUTPUT_SPEC_H
+
+#include "diagnostic-format.h"
+#include "diagnostic-output-file.h"
+
+namespace diagnostics_output_spec {
+
+/* An abstract base class for handling the DSL of -fdiagnostics-add-output=
+ and -fdiagnostics-set-output=. */
+
+class context
+{
+ public:
+ std::unique_ptr<diagnostic_output_format>
+ parse_and_make_sink (const char *,
+ diagnostic_context &dc);
+
+ void
+ report_error (const char *gmsgid, ...) const
+ ATTRIBUTE_GCC_DIAG(2,3);
+
+ void
+ report_unknown_key (const char *unparsed_arg,
+ const std::string &key,
+ const std::string &scheme_name,
+ auto_vec<const char *> &known_keys) const;
+
+ void
+ report_missing_key (const char *unparsed_arg,
+ const std::string &key,
+ const std::string &scheme_name,
+ const char *metavar) const;
+
+ diagnostic_output_file
+ open_output_file (label_text &&filename) const;
+
+ const char *
+ get_option_name () const { return m_option_name; }
+
+ line_maps *
+ get_affected_location_mgr () const { return m_affected_location_mgr; }
+
+ virtual ~context () {}
+
+ virtual void
+ report_error_va (const char *gmsgid, va_list *ap) const = 0;
+
+ virtual const char *
+ get_base_filename () const = 0;
+
+protected:
+ context (const char *option_name,
+ line_maps *affected_location_mgr)
+ : m_option_name (option_name),
+ m_affected_location_mgr (affected_location_mgr)
+ {
+ }
+
+ const char *m_option_name;
+ line_maps *m_affected_location_mgr;
+};
+
+/* A subclass that implements reporting errors via a diagnostic_context. */
+
+struct gcc_spec_context : public diagnostics_output_spec::context
+{
+public:
+ gcc_spec_context (diagnostic_context &dc,
+ line_maps *affected_location_mgr,
+ line_maps *control_location_mgr,
+ location_t loc,
+ const char *option_name)
+ : context (option_name, affected_location_mgr),
+ m_dc (dc),
+ m_control_location_mgr (control_location_mgr),
+ m_loc (loc)
+ {}
+
+ void report_error_va (const char *gmsgid, va_list *ap) const final override
+ ATTRIBUTE_GCC_DIAG(2, 0)
+ {
+ m_dc.begin_group ();
+ rich_location richloc (m_control_location_mgr, m_loc);
+ m_dc.diagnostic_impl (&richloc, nullptr, -1, gmsgid, ap, DK_ERROR);
+ m_dc.end_group ();
+ }
+
+ diagnostic_context &m_dc;
+ line_maps *m_control_location_mgr;
+ location_t m_loc;
+};
+
+} // namespace diagnostics_output_spec
+
+#endif
return true;
}
+void
+diagnostic_context::set_main_input_filename (const char *filename)
+{
+ for (auto sink : m_output_sinks)
+ sink->set_main_input_filename (filename);
+}
+
void
diagnostic_context::
set_client_data_hooks (std::unique_ptr<diagnostic_client_data_hooks> hooks)
return m_option_classifier.m_classification_history;
}
+ void set_main_input_filename (const char *filename);
+
private:
void error_recursion () ATTRIBUTE_NORETURN;
* :func:`diagnostic_logical_location_get_fully_qualified_name`
* :func:`diagnostic_logical_location_get_decorated_name`
+
+``LIBGDIAGNOSTICS_ABI_2``
+-------------------------
+``LIBGDIAGNOSTICS_ABI_2`` covers the addition of these functions for
+supporting command-line options and SARIF playback:
+
+ * :func:`diagnostic_manager_add_sink_from_spec`
+
+ * :func:`diagnostic_manager_set_analysis_target`
This will flush output to all of the output sinks, and clean up.
The parameter must be non-NULL.
+
+.. function:: int diagnostic_manager_add_sink_from_spec (diagnostic_manager *affected_mgr, \
+ const char *option_name, \
+ const char *spec, \
+ diagnostic_manager *control_mgr)
+
+ This function can be used to support option processing similar to GCC's
+ :option:`-fdiagnostics-add-output=`. This allows command-line tools to
+ support the same domain-specific language for specifying output sink
+ as GCC does.
+
+ The function will attempt to parse :param:`spec` as if it were
+ an argument to GCC's :option:`-fdiagnostics-add-output=OUTPUT-SPEC`.
+ If successful, it will add an output sink to :param:`affected_mgr` and return zero.
+ Otherwise, it will emit an error diagnostic to :param:`control_mgr` and
+ return non-zero.
+
+ :param:`affected_mgr` and :param:`control_mgr` can be the same manager,
+ or be different managers.
+
+ This function was added in :ref:`LIBGDIAGNOSTICS_ABI_2`; you can
+ test for its presence using
+
+ .. code-block:: c
+
+ #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_manager_add_sink_from_spec
+
+
+.. function:: void diagnostic_manager_set_analysis_target (diagnostic_manager *mgr, \
+ const diagnostic_file *file)
+
+ This function sets the "main input file" of :param:`mgr` to be
+ :param:`file`.
+ This affects the :code:`<title>` of generated HTML and
+ the :code:`role` of the artifact in SARIF output (SARIF v2.1.0 section 3.24.6).
+
+ This function was added in :ref:`LIBGDIAGNOSTICS_ABI_2`; you can
+ test for its presence using
+
+ .. code-block:: c
+
+ #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_manager_set_analysis_target
version);
}
+ bool
+ add_sink_from_spec (const char *option_name,
+ const char *spec,
+ manager control_mgr)
+ {
+ return diagnostic_manager_add_sink_from_spec (m_inner,
+ option_name,
+ spec,
+ control_mgr.m_inner);
+ }
+
void
write_patch (FILE *dst_stream)
{
diagnostic
begin_diagnostic (enum diagnostic_level level);
+ void
+ set_analysis_target (file f);
diagnostic_manager *m_inner;
bool m_owned;
return diagnostic (diagnostic_begin (m_inner, level));
}
+inline void
+manager::set_analysis_target (file f)
+{
+ diagnostic_manager_set_analysis_target (m_inner, f.m_inner);
+}
+
} // namespace libgdiagnostics
#endif // #ifndef LIBGDIAGNOSTICSPP_H
#include "diagnostic-client-data-hooks.h"
#include "diagnostic-format-sarif.h"
#include "diagnostic-format-text.h"
+#include "diagnostic-output-spec.h"
#include "logical-location.h"
#include "edit-context.h"
#include "libgdiagnostics.h"
return loc->m_decorated_name.get_str ();
}
+
+namespace {
+
+struct spec_context : public diagnostics_output_spec::context
+{
+public:
+ spec_context (const char *option_name,
+ diagnostic_manager &affected_mgr,
+ diagnostic_manager &control_mgr)
+ : context (option_name, affected_mgr.get_line_table ()),
+ m_control_mgr (control_mgr)
+ {}
+
+ void report_error_va (const char *gmsgid, va_list *ap) const final override
+ {
+ diagnostic *diag
+ = diagnostic_begin (&m_control_mgr, DIAGNOSTIC_LEVEL_ERROR);
+ diagnostic_finish_va (diag, gmsgid, ap);
+ }
+
+ const char *
+ get_base_filename () const final override
+ {
+ return nullptr;
+ }
+
+private:
+ diagnostic_manager &m_control_mgr;
+};
+
+} // anon namespace
+
+/* Public entrypoint. */
+
+int
+diagnostic_manager_add_sink_from_spec (diagnostic_manager *affected_mgr,
+ const char *option_name,
+ const char *spec,
+ diagnostic_manager *control_mgr)
+{
+ FAIL_IF_NULL (affected_mgr);
+ FAIL_IF_NULL (option_name);
+ FAIL_IF_NULL (spec);
+ FAIL_IF_NULL (control_mgr);
+
+ spec_context ctxt (option_name, *affected_mgr, *control_mgr);
+ auto inner_sink = ctxt.parse_and_make_sink (spec, affected_mgr->get_dc ());
+ if (!inner_sink)
+ return -1;
+ affected_mgr->get_dc ().add_sink (std::move (inner_sink));
+ return 0;
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_manager_set_analysis_target (diagnostic_manager *mgr,
+ const diagnostic_file *file)
+{
+ FAIL_IF_NULL (mgr);
+ FAIL_IF_NULL (file);
+
+ mgr->get_dc ().set_main_input_filename (file->get_name ());
+}
diagnostic_physical_location_get_file (const diagnostic_physical_location *physical_loc)
LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL(0);
+/* Attempt to parse SPEC as if an argument to GCC's
+ -fdiagnostics-add-output=OUTPUT-SPEC.
+ If successful, add an output sink to AFFECTED_MGR and return zero.
+ Otherwise, emit a diagnostic to CONTROL_MGR and return non-zero.
+ Added in LIBGDIAGNOSTICS_ABI_2. */
+#define LIBDIAGNOSTICS_HAVE_diagnostic_manager_add_sink_from_spec
+
+extern int
+diagnostic_manager_add_sink_from_spec (diagnostic_manager *affected_mgr,
+ const char *option_name,
+ const char *spec,
+ diagnostic_manager *control_mgr)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (4);
+
+
+/* Set the main input file of MGR to be FILE.
+ This affects the <title> of generated HTML and
+ the "role" of the artifact in SARIF output (SARIF v2.1.0
+ section 3.24.6).
+ Added in LIBGDIAGNOSTICS_ABI_2. */
+#define LIBDIAGNOSTICS_HAVE_diagnostic_manager_set_analysis_target
+
+extern void
+diagnostic_manager_set_analysis_target (diagnostic_manager *mgr,
+ const diagnostic_file *file)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
/* DEFERRED:
- thread-safety
- plural forms
diagnostic_logical_location_get_fully_qualified_name;
diagnostic_logical_location_get_decorated_name;
} LIBGDIAGNOSTICS_ABI_0;
+
+# Add hooks needed for HTML output from sarif-replay
+LIBGDIAGNOSTICS_ABI_2 {
+ global:
+ diagnostic_manager_add_sink_from_spec;
+ diagnostic_manager_set_analysis_target;
+} LIBGDIAGNOSTICS_ABI_1;
auto file = m_output_mgr.new_file (artifact_loc_uri->get_string (),
sarif_source_language);
+ // 3.24.6 "roles" property
+ const property_spec_ref prop_roles
+ ("artifact", "roles", "3.24.6");
+ if (auto roles_obj
+ = get_optional_property<json::array> (artifact_obj,
+ prop_roles))
+ for (auto iter : *roles_obj)
+ if (auto str = require_string (*iter, prop_roles))
+ if (!strcmp (str->get_string (), "analysisTarget"))
+ m_output_mgr.set_analysis_target (file);
+
// Set contents, if available
const property_spec_ref prop_contents
("artifact", "contents", "3.24.8");
/* This file implements the options -fdiagnostics-add-output=,
- -fdiagnostics-set-output=, and their domain-specific language. */
+ -fdiagnostics-set-output=. Most of the work is done
+ by diagnostic-output-spec.cc so it can be shared by libgdiagnostics. */
#include "config.h"
#define INCLUDE_ARRAY
#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"
#include "opts.h"
#include "options.h"
-/* A namespace for handling the DSL of the arguments of
- -fdiagnostics-add-output= and -fdiagnostics-set-output=. */
-
-namespace gcc {
-namespace diagnostics_output_spec {
-
/* Decls. */
-struct context
+namespace {
+
+struct opt_spec_context : public diagnostics_output_spec::gcc_spec_context
{
public:
- context (const gcc_options &opts,
- diagnostic_context &dc,
- line_maps *location_mgr,
- location_t loc,
- const char *option_name)
- : m_opts (opts), m_dc (dc), m_location_mgr (location_mgr), m_loc (loc),
- m_option_name (option_name)
+ opt_spec_context (const gcc_options &opts,
+ 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),
+ m_opts (opts)
{}
- void
- report_error (const char *gmsgid, ...) const
- ATTRIBUTE_GCC_DIAG(2,3);
-
- void
- report_unknown_key (const char *unparsed_arg,
- const std::string &key,
- const std::string &scheme_name,
- auto_vec<const char *> &known_keys) const;
-
- void
- report_missing_key (const char *unparsed_arg,
- const std::string &key,
- const std::string &scheme_name,
- const char *metavar) const;
-
- diagnostic_output_file
- open_output_file (label_text &&filename) const;
-
- const gcc_options &m_opts;
- diagnostic_context &m_dc;
- line_maps *m_location_mgr;
- location_t m_loc;
- const char *m_option_name;
-};
-
-struct scheme_name_and_params
-{
- std::string m_scheme_name;
- std::vector<std::pair<std::string, std::string>> m_kvs;
-};
-
-static std::unique_ptr<scheme_name_and_params>
-parse (const context &ctxt, const char *unparsed_arg);
-
-/* Class for parsing the arguments of -fdiagnostics-add-output= and
- -fdiagnostics-set-output=, and making diagnostic_output_format
- instances (or issuing errors). */
-
-class output_factory
-{
-public:
- class scheme_handler
+ const char *
+ get_base_filename () const final override
{
- 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,
- 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.m_option_name, unparsed_arg,
- value.c_str (),
- key.c_str (),
- "yes", "no");
-
- return false;
- }
- }
- template <typename EnumType, size_t NumValues>
- bool
- parse_enum_value (const context &ctxt,
- const char *unparsed_arg,
- const std::string &key,
- const std::string &value,
- const std::array<std::pair<const char *, EnumType>, NumValues> &value_names,
- EnumType &out) const
- {
- for (auto &iter : value_names)
- if (value == iter.first)
- {
- out = iter.second;
- return true;
- }
-
- auto_vec<const char *> known_values;
- for (auto iter : value_names)
- known_values.safe_push (iter.first);
- pp_markup::comma_separated_quoted_strings e (known_values);
- ctxt.report_error
- ("%<%s%s%>:"
- " unexpected value %qs for key %qs; known values: %e",
- ctxt.m_option_name, unparsed_arg,
- value.c_str (),
- key.c_str (),
- &e);
- return false;
- }
-
- private:
- const std::string m_scheme_name;
- };
-
- output_factory ();
-
- std::unique_ptr<diagnostic_output_format>
- make_sink (const context &ctxt,
- 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,
- 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,
- const char *unparsed_arg,
- const scheme_name_and_params &parsed_arg) const final override;
-};
-
-class html_scheme_handler : public output_factory::scheme_handler
-{
-public:
- html_scheme_handler () : scheme_handler ("experimental-html") {}
+ return (m_opts.x_dump_base_name
+ ? m_opts.x_dump_base_name
+ : m_opts.x_main_input_basename);
+ }
- std::unique_ptr<diagnostic_output_format>
- make_sink (const context &ctxt,
- const char *unparsed_arg,
- const scheme_name_and_params &parsed_arg) const final override;
+ const gcc_options &m_opts;
};
-/* struct context. */
-
-void
-context::report_error (const char *gmsgid, ...) const
-{
- m_dc.begin_group ();
- va_list ap;
- va_start (ap, gmsgid);
- rich_location richloc (m_location_mgr, m_loc);
- m_dc.diagnostic_impl (&richloc, nullptr, -1, gmsgid, &ap, DK_ERROR);
- va_end (ap);
- m_dc.end_group ();
-}
-
-void
-context::report_unknown_key (const char *unparsed_arg,
- const std::string &key,
- const std::string &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",
- m_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%>",
- m_option_name, unparsed_arg,
- key.c_str (), scheme_name.c_str (),
- m_option_name, scheme_name.c_str (), key.c_str (), metavar);
-}
-
-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.m_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));
-}
-
-/* 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,
- 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.m_option_name, unparsed_arg,
- parsed_arg.m_scheme_name.c_str (), &e);
- return nullptr;
- }
-
- return scheme_handler->make_sink (ctxt, 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,
- const char *unparsed_arg,
- const scheme_name_and_params &parsed_arg) const
-{
- bool show_color = pp_show_color (ctxt.m_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> (ctxt.m_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;
-}
-
-diagnostic_output_file
-context::open_output_file (label_text &&filename) const
-{
- FILE *outf = fopen (filename.get (), "w");
- if (!outf)
- {
- rich_location richloc (m_location_mgr, m_loc);
- m_dc.emit_diagnostic_with_group
- (DK_ERROR, richloc, nullptr, 0,
- "unable to open %qs: %m", filename.get ());
- return diagnostic_output_file (nullptr, false, std::move (filename));
- }
- return diagnostic_output_file (outf, true, std::move (filename));
-}
-
-/* class sarif_scheme_handler : public output_factory::scheme_handler. */
-
-std::unique_ptr<diagnostic_output_format>
-sarif_scheme_handler::make_sink (const context &ctxt,
- 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.m_opts.x_dump_base_name
- ? ctxt.m_opts.x_dump_base_name
- : ctxt.m_opts.x_main_input_basename);
- output_file = diagnostic_output_format_open_sarif_file (ctxt.m_dc,
- line_table,
- basename,
- serialization_kind);
- }
- if (!output_file)
- return nullptr;
-
- sarif_generation_options sarif_gen_opts;
- sarif_gen_opts.m_version = version;
- sarif_gen_opts.m_xml_state = xml_state;
-
- std::unique_ptr<sarif_serialization_format> serialization_obj;
- switch (serialization_kind)
- {
- default:
- gcc_unreachable ();
- case sarif_serialization_kind::json:
- serialization_obj
- = std::make_unique<sarif_serialization_format_json> (true);
- break;
- }
-
- auto sink = make_sarif_sink (ctxt.m_dc,
- *line_table,
- std::move (serialization_obj),
- sarif_gen_opts,
- std::move (output_file));
- return sink;
-}
-
-/* class html_scheme_handler : public output_factory::scheme_handler. */
-
-std::unique_ptr<diagnostic_output_format>
-html_scheme_handler::make_sink (const context &ctxt,
- 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.m_opts.x_dump_base_name
- ? ctxt.m_opts.x_dump_base_name
- : ctxt.m_opts.x_main_input_basename);
- output_file = diagnostic_output_format_open_html_file (ctxt.m_dc,
- line_table,
- 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 (ctxt.m_dc,
- *line_table,
- html_gen_opts,
- std::move (output_file));
- return sink;
-}
-
-} // namespace diagnostics_output_spec
-} // namespace gcc
+} // anon namespace
void
handle_OPT_fdiagnostics_add_output_ (const gcc_options &opts,
gcc_assert (line_table);
const char *const option_name = "-fdiagnostics-add-output=";
- gcc::diagnostics_output_spec::context ctxt (opts, dc, line_table, loc,
- option_name);
- auto result = gcc::diagnostics_output_spec::parse (ctxt, arg);
- if (!result)
- return;
-
- gcc::diagnostics_output_spec::output_factory factory;
- auto sink = factory.make_sink (ctxt, arg, *result);
+ opt_spec_context ctxt (opts, dc, line_table, loc, option_name);
+ auto sink = ctxt.parse_and_make_sink (arg, dc);
if (!sink)
return;
gcc_assert (line_table);
const char *const option_name = "-fdiagnostics-set-output=";
- gcc::diagnostics_output_spec::context ctxt (opts, dc, line_table, loc,
- option_name);
- auto result = gcc::diagnostics_output_spec::parse (ctxt, arg);
- if (!result)
- return;
-
- gcc::diagnostics_output_spec::output_factory factory;
- auto sink = factory.make_sink (ctxt, arg, *result);
+ opt_spec_context ctxt (opts, dc, line_table, loc, option_name);
+ auto sink = ctxt.parse_and_make_sink (arg, dc);
if (!sink)
return;
sink->set_main_input_filename (opts.x_main_input_filename);
dc.set_output_format (std::move (sink));
}
-
-#if CHECKING_P
-
-namespace selftest {
-
-/* RAII class to temporarily override "progname" to the
- string "PROGNAME". */
-
-class auto_fix_progname
-{
-public:
- auto_fix_progname ()
- {
- m_old_progname = progname;
- progname = "PROGNAME";
- }
-
- ~auto_fix_progname ()
- {
- progname = m_old_progname;
- }
-
-private:
- const char *m_old_progname;
-};
-
-struct parser_test
-{
- parser_test ()
- : m_opts (),
- m_dc (),
- m_ctxt (m_opts, m_dc, line_table, UNKNOWN_LOCATION, "-fOPTION="),
- m_fmt (m_dc.get_output_format (0))
- {
- pp_buffer (m_fmt.get_printer ())->m_flush_p = false;
- }
-
- std::unique_ptr<gcc::diagnostics_output_spec::scheme_name_and_params>
- parse (const char *unparsed_arg)
- {
- return gcc::diagnostics_output_spec::parse (m_ctxt, unparsed_arg);
- }
-
- bool execution_failed_p () const
- {
- return m_dc.execution_failed_p ();
- }
-
- const char *
- get_diagnostic_text () const
- {
- return pp_formatted_text (m_fmt.get_printer ());
- }
-
-private:
- const gcc_options m_opts;
- test_diagnostic_context m_dc;
- gcc::diagnostics_output_spec::context m_ctxt;
- diagnostic_output_format &m_fmt;
-};
-
-/* Selftests. */
-
-static void
-test_output_arg_parsing ()
-{
- auto_fix_quotes fix_quotes;
- auto_fix_progname fix_progname;
-
- /* Minimal correct example. */
- {
- parser_test pt;
- auto result = pt.parse ("foo");
- ASSERT_EQ (result->m_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
-opts_diagnostic_cc_tests ()
-{
- test_output_arg_parsing ();
-}
-
-} // namespace selftest
-
-#endif /* #if CHECKING_P */
<http://www.gnu.org/licenses/>. */
#include "config.h"
+#define INCLUDE_STRING
#define INCLUDE_VECTOR
#include "system.h"
#include "coretypes.h"
replay_options m_replay_opts;
std::vector<const char *> m_sarif_filenames;
+ std::vector<std::string> m_extra_output_specs;
};
static void
"\n"
"Options:\n"
"\n"
+" -fdiagnostics-add-output=SCHEME\n"
+" Add an additional output sink when replaying diagnostics, as\n"
+" per the gcc option\n"
+"\n"
" -fdiagnostics-color={never|always|auto}\n"
" Control colorization of diagnostics. Default: auto.\n"
"\n"
fprintf (stderr, usage_msg);
}
+/* If STR starts with PREFIX, return the rest of STR.
+ Otherwise return nullptr. */
+
+static const char *
+str_starts_with (const char *str, const char *prefix)
+{
+ size_t prefix_len = strlen (prefix);
+ if (0 == strncmp (str, prefix, prefix_len))
+ return str + prefix_len;
+ else
+ return nullptr;
+}
+
static bool
parse_options (int argc, char **argv,
options &opts,
opts.m_replay_opts.m_echo_file = true;
handled = true;
}
+#define ADD_OUTPUT_OPTION "-fdiagnostics-add-output="
+ else if (const char *arg
+ = str_starts_with (option, ADD_OUTPUT_OPTION))
+ {
+ opts.m_extra_output_specs.push_back (std::string (arg));
+ handled = true;
+ }
else if (strcmp (option, "-fdiagnostics-color=never") == 0)
{
opts.m_replay_opts.m_diagnostics_colorize = DIAGNOSTIC_COLORIZE_NO;
libgdiagnostics::manager playback_mgr;
playback_mgr.add_text_sink (stderr,
opts.m_replay_opts.m_diagnostics_colorize);
+ for (auto spec : opts.m_extra_output_specs)
+ if (playback_mgr.add_sink_from_spec
+ (ADD_OUTPUT_OPTION,
+ spec.c_str (),
+ libgdiagnostics::manager (control_mgr.m_inner, false)))
+ return -1;
int result = sarif_replay_path (filename,
playback_mgr.m_inner,
diagnostic_format_html_cc_tests ();
diagnostic_format_json_cc_tests ();
diagnostic_format_sarif_cc_tests ();
+ diagnostic_output_spec_cc_tests ();
edit_context_cc_tests ();
fold_const_cc_tests ();
spellcheck_cc_tests ();
simple_diagnostic_path_cc_tests ();
lazy_diagnostic_path_cc_tests ();
attribs_cc_tests ();
- opts_diagnostic_cc_tests ();
path_coverage_cc_tests ();
/* This one relies on most of the above. */
extern void diagnostic_format_html_cc_tests ();
extern void diagnostic_format_json_cc_tests ();
extern void diagnostic_format_sarif_cc_tests ();
+extern void diagnostic_output_spec_cc_tests ();
extern void diagnostic_path_output_cc_tests ();
extern void diagnostic_show_locus_cc_tests ();
extern void digraph_cc_tests ();
extern void opt_suggestions_cc_tests ();
extern void optinfo_emit_json_cc_tests ();
extern void opts_cc_tests ();
-extern void opts_diagnostic_cc_tests ();
extern void ordered_hash_map_tests_cc_tests ();
extern void path_coverage_cc_tests ();
extern void predict_cc_tests ();
--- /dev/null
+from htmltest import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def html_tree():
+ return html_tree_from_env()
+
+def test_generated_html(html_tree):
+ root = html_tree.getroot ()
+ assert root.tag == make_tag('html')
+
+ head = root.find('xhtml:head', ns)
+ assert head is not None
+
+ title = head.find('xhtml:title', ns)
+ assert title.text == '../../src/gcc/testsuite/gcc.dg/analyzer/signal-1.c'
+
+ diag = get_diag_by_index(html_tree, 0)
+
+ msg = get_message_within_diag(diag)
+ assert msg is not None
+
+ assert_tag(msg[0], 'strong')
+ assert msg[0].text == 'warning: '
+ assert msg[0].tail == " call to ‘fprintf’ from within signal handler "
--- /dev/null
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+def test_basics(sarif):
+ schema = sarif['$schema']
+ assert schema == "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json"
+
+ version = sarif['version']
+ assert version == "2.1.0"
+
+def test_execution_successful(sarif):
+ runs = sarif['runs']
+ run = runs[0]
+
+ invocations = run['invocations']
+ assert len(invocations) == 1
+ invocation = invocations[0]
+
+ assert invocation['executionSuccessful'] == True
+
+def test_warning(sarif):
+ result = get_result_by_index(sarif, 0)
+
+ assert result['level'] == 'warning'
+
+ # TODO: this should be "-Wanalyzer-unsafe-call-within-signal-handler" and have a URL
+ assert result['ruleId'] == 'warning'
+
+ # TODO: check code flow
+ events = result["codeFlows"][0]["threadFlows"][0]['locations']
+
+ # Event "(1)": "entry to 'main'" (index == 0)
+ assert events[0]['location']['message']['text'] == "entry to ‘main’"
+
+ # Final event:
+ assert events[-1]['location']['message']['text'].startswith("call to ‘fprintf’ from within signal handler")
The dg directives were stripped out from the generated .sarif
to avoid confusing DejaGnu for this test. */
+/* { dg-additional-options "-fdiagnostics-add-output=experimental-html:file=signal-1.c.sarif.html,javascript=no" } */
+/* { dg-additional-options "-fdiagnostics-add-output=sarif:file=signal-1.c.roundtrip.sarif" } */
{"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
| | (7) call to ‘fprintf’ from within signal handler
|
{ dg-end-multiline-output "" } */
+
+/* Use a Python script to verify various properties about the generated
+ .html file:
+ { dg-final { run-html-pytest signal-1.c.sarif "2.1.0-valid/signal-1-check-html.py" } } */
+
+/* Use a Python script to verify various properties about the *generated*
+ .sarif file:
+ { dg-final { run-sarif-pytest signal-1.c.roundtrip "2.1.0-valid/signal-1-check-sarif-roundtrip.py" } } */