This patch refactors the support for -fdiagnostics-add-output=SCHEME from GCC's options parsing so that it is also available to sarif-replay and to other clients of libgdiagnostics.
With this users of sarif-replay and other such tools can generate HTML or SARIF as well as text output, using the same -fdiagnostics-add-output=SCHEME as GCC. As a test, the patch adds support for this option to the dg-lint script below "contrib". For example dg-lint can now generate text, html, and sarif output via: LD_LIBRARY_PATH=../build/gcc/ \ ./contrib/dg-lint/dg-lint \ contrib/dg-lint/test-*.c \ -fdiagnostics-add-output=experimental-html:file=dg-lint-tests.html \ -fdiagnostics-add-output=sarif:file=dg-lint-tests.sarif where the HTML output from dg-lint can be seen here: https://dmalcolm.fedorapeople.org/gcc/2025-06-20/dg-lint-tests.html the sarif output here: https://dmalcolm.fedorapeople.org/gcc/2025-06-23/dg-lint-tests.sarif and a screenshot of VS Code viewing the sarif output is here: https://dmalcolm.fedorapeople.org/gcc/2025-06-23/vscode-viewing-dg-lint-sarif-output.png As well as allowing sarif-replay to generate HTML, this patch allows sarif-replay to also generate SARIF. Ideally this would faithfully round-trip all the data, but it's not perfect (which I'm tracking as PR sarif-replay/120792). Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu. Pushed to trunk as r16-1636-gd0142e147486e6. contrib/ChangeLog: PR other/116792 PR testsuite/116163 PR sarif-replay/120792 * dg-lint/dg-lint: Add -fdiagnostics-add-output. * dg-lint/libgdiagnostics.py: Add diagnostic_manager_add_sink_from_spec. (Manager.add_sink_from_spec): New. gcc/ChangeLog: PR other/116792 PR testsuite/116163 PR sarif-replay/120792 * Makefile.in (OBJS-libcommon): Add diagnostic-output-spec.o. * diagnostic-format-html.cc (html_builder::html_builder): Ensure title is non-empty. * diagnostic-output-spec.cc: New file, taken from material in opts-diagnostic.cc. * diagnostic-output-spec.h: New file. * diagnostic.cc (diagnostic_context::set_main_input_filename): New. * diagnostic.h (diagnostic_context::set_main_input_filename): New decl. * doc/libgdiagnostics/topics/compatibility.rst (LIBGDIAGNOSTICS_ABI_2): New. * doc/libgdiagnostics/topics/diagnostic-manager.rst (diagnostic_manager_add_sink_from_spec): New. (diagnostic_manager_set_analysis_target): New. * libgdiagnostics++.h (manager::add_sink_from_spec): New. (manager::set_analysis_target): New. * libgdiagnostics.cc: Include "diagnostic-output-spec.h". (struct spec_context): New. (diagnostic_manager_add_sink_from_spec): New. (diagnostic_manager_set_analysis_target): New. * libgdiagnostics.h (LIBDIAGNOSTICS_HAVE_diagnostic_manager_add_sink_from_spec): New define. (diagnostic_manager_add_sink_from_spec): New decl. (LIBDIAGNOSTICS_HAVE_diagnostic_manager_set_analysis_target): New define. (diagnostic_manager_set_analysis_target): New decl. * libgdiagnostics.map (LIBGDIAGNOSTICS_ABI_2): New. * libsarifreplay.cc (sarif_replayer::handle_artifact_obj): Looks for "analysisTarget" in roles and call set_analysis_target using the artifact if found. * opts-diagnostic.cc: Refactor, moving material to diagnostic-output-spec.cc. (struct opt_spec_context): New. (handle_OPT_fdiagnostics_add_output_): Use opt_spec_context. (handle_OPT_fdiagnostics_set_output_): Likewise. * sarif-replay.cc: Define INCLUDE_STRING. (struct options): Add m_extra_output_specs. (usage_msg): Add -fdiagnostics-add-output=SCHEME. (str_starts_with): New. (parse_options): Add -fdiagnostics-add-output=SCHEME. (main): Likewise. * selftest-run-tests.cc (selftest::run_tests): Call diagnostic_output_spec_cc_tests rather than opts_diagnostic_cc_tests. * selftest.h (selftest::diagnostic_output_spec_cc_tests): Replace... (selftest::opts_diagnostic_cc_tests): ...this. gcc/testsuite/ChangeLog: PR other/116792 PR testsuite/116163 PR sarif-replay/120792 * sarif-replay.dg/2.1.0-valid/signal-1-check-html.py: New test script. * sarif-replay.dg/2.1.0-valid/signal-1.c.sarif: Add html and sarif generation to options. Invoke the new script to verify that HTML and SARIF is generated. --- contrib/dg-lint/dg-lint | 8 + contrib/dg-lint/libgdiagnostics.py | 17 + gcc/Makefile.in | 1 + gcc/diagnostic-format-html.cc | 1 + gcc/diagnostic-output-spec.cc | 828 ++++++++++++++++++ gcc/diagnostic-output-spec.h | 116 +++ gcc/diagnostic.cc | 7 + gcc/diagnostic.h | 2 + .../libgdiagnostics/topics/compatibility.rst | 9 + .../topics/diagnostic-manager.rst | 42 + gcc/libgdiagnostics++.h | 19 + gcc/libgdiagnostics.cc | 65 ++ gcc/libgdiagnostics.h | 31 + gcc/libgdiagnostics.map | 7 + gcc/libsarifreplay.cc | 11 + gcc/opts-diagnostic.cc | 820 +---------------- gcc/sarif-replay.cc | 32 + gcc/selftest-run-tests.cc | 2 +- gcc/selftest.h | 2 +- .../2.1.0-valid/signal-1-check-html.py | 26 + .../signal-1-check-sarif-roundtrip.py | 41 + .../2.1.0-valid/signal-1.c.sarif | 10 + 22 files changed, 1304 insertions(+), 793 deletions(-) create mode 100644 gcc/diagnostic-output-spec.cc create mode 100644 gcc/diagnostic-output-spec.h create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1-check-html.py create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1-check-sarif-roundtrip.py diff --git a/contrib/dg-lint/dg-lint b/contrib/dg-lint/dg-lint index 01d58d7a3e9..4ae0686b975 100755 --- a/contrib/dg-lint/dg-lint +++ b/contrib/dg-lint/dg-lint @@ -380,9 +380,17 @@ def skip_file(filename): 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): diff --git a/contrib/dg-lint/libgdiagnostics.py b/contrib/dg-lint/libgdiagnostics.py index 03a6440a3e3..8c8cc4887cd 100644 --- a/contrib/dg-lint/libgdiagnostics.py +++ b/contrib/dg-lint/libgdiagnostics.py @@ -124,6 +124,13 @@ cdll.diagnostic_add_fix_it_hint_replace.argtypes \ 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): @@ -156,6 +163,16 @@ class Manager: 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 diff --git a/gcc/Makefile.in b/gcc/Makefile.in index f106e833425..9535804f7fb 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1858,6 +1858,7 @@ OBJS-libcommon = diagnostic-spec.o diagnostic.o diagnostic-color.o \ 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 \ diff --git a/gcc/diagnostic-format-html.cc b/gcc/diagnostic-format-html.cc index b1b0895d031..c397c9f088d 100644 --- a/gcc/diagnostic-format-html.cc +++ b/gcc/diagnostic-format-html.cc @@ -426,6 +426,7 @@ html_builder::html_builder (diagnostic_context &context, { 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) diff --git a/gcc/diagnostic-output-spec.cc b/gcc/diagnostic-output-spec.cc new file mode 100644 index 00000000000..e58f0c40fc0 --- /dev/null +++ b/gcc/diagnostic-output-spec.cc @@ -0,0 +1,828 @@ +/* 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 */ diff --git a/gcc/diagnostic-output-spec.h b/gcc/diagnostic-output-spec.h new file mode 100644 index 00000000000..e02cdfe1705 --- /dev/null +++ b/gcc/diagnostic-output-spec.h @@ -0,0 +1,116 @@ +/* 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 diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc index ab52e34e2fb..8547d4882d3 100644 --- a/gcc/diagnostic.cc +++ b/gcc/diagnostic.cc @@ -524,6 +524,13 @@ diagnostic_context::supports_fnotice_on_stderr_p () const 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) diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h index b6cab0d52ed..f9c8253395b 100644 --- a/gcc/diagnostic.h +++ b/gcc/diagnostic.h @@ -840,6 +840,8 @@ public: return m_option_classifier.m_classification_history; } + void set_main_input_filename (const char *filename); + private: void error_recursion () ATTRIBUTE_NORETURN; diff --git a/gcc/doc/libgdiagnostics/topics/compatibility.rst b/gcc/doc/libgdiagnostics/topics/compatibility.rst index 4df685001e6..10adcc516ce 100644 --- a/gcc/doc/libgdiagnostics/topics/compatibility.rst +++ b/gcc/doc/libgdiagnostics/topics/compatibility.rst @@ -177,3 +177,12 @@ acccessing values within a :type:`diagnostic_logical_location`: * :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` diff --git a/gcc/doc/libgdiagnostics/topics/diagnostic-manager.rst b/gcc/doc/libgdiagnostics/topics/diagnostic-manager.rst index d594b260709..0390704963b 100644 --- a/gcc/doc/libgdiagnostics/topics/diagnostic-manager.rst +++ b/gcc/doc/libgdiagnostics/topics/diagnostic-manager.rst @@ -56,3 +56,45 @@ Responsibilities include: 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 diff --git a/gcc/libgdiagnostics++.h b/gcc/libgdiagnostics++.h index 18a88a2472c..93b8f90aefb 100644 --- a/gcc/libgdiagnostics++.h +++ b/gcc/libgdiagnostics++.h @@ -329,6 +329,17 @@ public: 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) { @@ -381,6 +392,8 @@ public: diagnostic begin_diagnostic (enum diagnostic_level level); + void + set_analysis_target (file f); diagnostic_manager *m_inner; bool m_owned; @@ -683,6 +696,12 @@ manager::begin_diagnostic (enum diagnostic_level level) 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 diff --git a/gcc/libgdiagnostics.cc b/gcc/libgdiagnostics.cc index 29f63eb720c..74814c7ef28 100644 --- a/gcc/libgdiagnostics.cc +++ b/gcc/libgdiagnostics.cc @@ -31,6 +31,7 @@ along with GCC; see the file COPYING3. If not see #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" @@ -1983,3 +1984,67 @@ diagnostic_logical_location_get_decorated_name (const diagnostic_logical_locatio 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 ()); +} diff --git a/gcc/libgdiagnostics.h b/gcc/libgdiagnostics.h index f957779604b..9af2747cb82 100644 --- a/gcc/libgdiagnostics.h +++ b/gcc/libgdiagnostics.h @@ -734,6 +734,37 @@ extern diagnostic_file * 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 diff --git a/gcc/libgdiagnostics.map b/gcc/libgdiagnostics.map index 4233cf86c76..49cabcad0b7 100644 --- a/gcc/libgdiagnostics.map +++ b/gcc/libgdiagnostics.map @@ -83,3 +83,10 @@ LIBGDIAGNOSTICS_ABI_1 { 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; diff --git a/gcc/libsarifreplay.cc b/gcc/libsarifreplay.cc index 6c797624f82..2d6c394e697 100644 --- a/gcc/libsarifreplay.cc +++ b/gcc/libsarifreplay.cc @@ -966,6 +966,17 @@ sarif_replayer::handle_artifact_obj (const json::object &artifact_obj) 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"); diff --git a/gcc/opts-diagnostic.cc b/gcc/opts-diagnostic.cc index 1a7d83c0476..d2c3a932e2a 100644 --- a/gcc/opts-diagnostic.cc +++ b/gcc/opts-diagnostic.cc @@ -19,7 +19,8 @@ along with GCC; see the file COPYING3. If not see /* 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 @@ -30,627 +31,42 @@ along with GCC; see the file COPYING3. If not see #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, @@ -662,14 +78,8 @@ 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; @@ -687,183 +97,11 @@ handle_OPT_fdiagnostics_set_output_ (const gcc_options &opts, 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 */ diff --git a/gcc/sarif-replay.cc b/gcc/sarif-replay.cc index 1523d875e90..a96c97bd92b 100644 --- a/gcc/sarif-replay.cc +++ b/gcc/sarif-replay.cc @@ -19,6 +19,7 @@ along with GCC; see the file COPYING3. If not see <http://www.gnu.org/licenses/>. */ #include "config.h" +#define INCLUDE_STRING #define INCLUDE_VECTOR #include "system.h" #include "coretypes.h" @@ -48,6 +49,7 @@ struct options replay_options m_replay_opts; std::vector<const char *> m_sarif_filenames; + std::vector<std::string> m_extra_output_specs; }; static void @@ -70,6 +72,10 @@ static const char *const usage_msg = ( "\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" @@ -95,6 +101,19 @@ print_usage () 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, @@ -128,6 +147,13 @@ parse_options (int argc, char **argv, 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; @@ -221,6 +247,12 @@ main (int argc, char **argv) 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, diff --git a/gcc/selftest-run-tests.cc b/gcc/selftest-run-tests.cc index 2d8573ce28f..139c7b381b6 100644 --- a/gcc/selftest-run-tests.cc +++ b/gcc/selftest-run-tests.cc @@ -103,6 +103,7 @@ selftest::run_tests () 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 (); @@ -112,7 +113,6 @@ selftest::run_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. */ diff --git a/gcc/selftest.h b/gcc/selftest.h index a6c96027ce0..4af647cca9e 100644 --- a/gcc/selftest.h +++ b/gcc/selftest.h @@ -225,6 +225,7 @@ extern void diagnostic_color_cc_tests (); 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 (); @@ -250,7 +251,6 @@ extern void lazy_diagnostic_path_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 (); diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1-check-html.py b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1-check-html.py new file mode 100644 index 00000000000..a0978da048b --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1-check-html.py @@ -0,0 +1,26 @@ +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 " diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1-check-sarif-roundtrip.py b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1-check-sarif-roundtrip.py new file mode 100644 index 00000000000..ea93b039e8f --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1-check-sarif-roundtrip.py @@ -0,0 +1,41 @@ +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") diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1.c.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1.c.sarif index 54fa0b83f1d..3b88af95f93 100644 --- a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1.c.sarif +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/signal-1.c.sarif @@ -2,6 +2,8 @@ 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", @@ -211,3 +213,11 @@ In function 'custom_logger': | | (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" } } */ -- 2.49.0