https://gcc.gnu.org/g:4a6c15e64ea5993eee4573fbae2659b2af8395bb
commit r17-5669-g4a6c15e64ea5993eee4573fbae2659b2af8395bb Author: David Malcolm <[email protected]> Date: Mon May 11 14:27:02 2026 -0400 analyzer: add analyzer-graphs=yes|no option to SARIF output This patch adds a new "analyzer-graphs=yes|no" suboption to -fdiagnostics-add-output=sarif that enables capturing detailed information about the internal state of the analyzer into the SARIF sink. Specifically, it adds the analyzer's supergraph and exploded_graph to the SARIF file's theRun.graphs array. I've found this extremely helpful when using AI agents to debug non-trivial analyzer bugs. That said, this is a debugging-only feature and is subject to change or removal without notice. gcc/ChangeLog: * Makefile.in (ANALYZER_OBJS): Add analyzer/analyzer-diagnostic-extensions.o. * diagnostics/sarif-sink.cc (make_sarif_node): Update for json::object::set_string overload accepting a std::string. (make_sarif_edge): Likewise. (make_sarif_edge): Likewise. (set_string_property_escaping_braces): Likewise. * doc/invoke.texi: Add analyzer-graphs=yes|no to the SARIF sink keys for GCC developers. * json.cc: Define INCLUDE_STRING. (object::set_string): New overload. * json.h (object::set_string): New overload decl. * opts-diagnostic.cc (gcc_extra_keys::gcc_extra_keys): Initialize m_analyzer_graphs. (gcc_extra_keys::maybe_handle_kv): Handle key "analyzer-graphs". (gcc_extra_keys::get_keys): Add "analyzer-graphs". (gcc_extra_keys::m_analyzer_graphs): New field. (handle_gcc_specific_keys): Handle m_analyzer_graphs. * opts-diagnostic.h (gcc_extension_factory::make_analyzer_graphs_extension): New vfunc decl. * tree-diagnostic-sink-extensions.h (compiler_extension_factory::make_analyzer_graphs_extension): New decl. gcc/analyzer/ChangeLog: * analyzer-diagnostic-extensions.cc: New file. * common.h (gcc::topics::analyzer_events::on_ana_done): New struct. (gcc::topics::analyzer_events::subscriber): Add on_message vfunc for the above. * diagnostic-manager.cc (saved_diagnostic::to_json): Capture m_problem and m_duplicates. * engine.cc (class exploded_graph_diagnostic_digraph): New. (feasibility_problem::to_json): New. (exploded_graph_annotator::print_enode_port): Update for change to prefix of supernode IDs. (impl_run_checkers): Publish the "on_ana_done" message. * exploded-graph.h (exploded_graph::make_diagnostics_digraph): New decl. (exploded_graph::get_worklist): New decl. (feasibility_problem::to_json): New decl. * known-function-manager.cc (get_keys_as_json): New. (known_function_manager::to_json): New. * known-function-manager.h (known_function_manager::to_json): New decl. * ops.cc (rejected_eh_dispatch::to_json): New. * pending-diagnostic.cc: Include "diagnostics/sarif-sink.h". (pending_diagnostic::to_json): New. * pending-diagnostic.h (pending_diagnostic::to_json): New decl. * region-model.cc (rejected_constraint::make_base_json): New. (rejected_op_constraint::to_json): New. (rejected_default_case::to_json): New. (rejected_ranges_constraint::to_json): New. * region-model.h (rejected_constraint::to_json): New decl. (rejected_constraint::make_base_json): New decl. (rejected_op_constraint::to_json): New decl. (rejected_default_case::to_json): New decl. (rejected_ranges_constraint::to_json): New decl. * supergraph.cc: Include "tree-logical-location.h" and "diagnostics/digraphs.h". (class supergraph_diagnostic_digraph): New. (supernode::dump_dot_id): Use "supernode_" as the prefix, rather than just "node_". * supergraph.h (make_diagnostics_digraph): New decl. gcc/testsuite/ChangeLog: * gcc.dg/analyzer/sarif-analyzer-graphs.c: New test. * gcc.dg/analyzer/sarif-analyzer-graphs.py: New test script. Signed-off-by: David Malcolm <[email protected]> Diff: --- gcc/Makefile.in | 1 + gcc/analyzer/analyzer-diagnostic-extensions.cc | 144 +++++++++++++++ gcc/analyzer/common.h | 10 ++ gcc/analyzer/diagnostic-manager.cc | 13 +- gcc/analyzer/engine.cc | 200 ++++++++++++++++++++- gcc/analyzer/exploded-graph.h | 9 + gcc/analyzer/known-function-manager.cc | 44 +++++ gcc/analyzer/known-function-manager.h | 2 + gcc/analyzer/ops.cc | 4 + gcc/analyzer/pending-diagnostic.cc | 11 ++ gcc/analyzer/pending-diagnostic.h | 2 + gcc/analyzer/region-model.cc | 37 ++++ gcc/analyzer/region-model.h | 7 + gcc/analyzer/supergraph.cc | 189 ++++++++++++++++++- gcc/analyzer/supergraph.h | 3 + gcc/diagnostics/sarif-sink.cc | 10 +- gcc/doc/invoke.texi | 6 + gcc/json.cc | 11 ++ gcc/json.h | 1 + gcc/opts-diagnostic.cc | 9 +- gcc/opts-diagnostic.h | 3 + .../gcc.dg/analyzer/sarif-analyzer-graphs.c | 18 ++ .../gcc.dg/analyzer/sarif-analyzer-graphs.py | 53 ++++++ gcc/tree-diagnostic-sink-extensions.h | 3 + 24 files changed, 780 insertions(+), 10 deletions(-) diff --git a/gcc/Makefile.in b/gcc/Makefile.in index f2a015d729df..19e35f6149b8 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1347,6 +1347,7 @@ ANALYZER_OBJS = \ analyzer/ana-state-to-diagnostic-state.o \ analyzer/analysis-plan.o \ analyzer/analyzer.o \ + analyzer/analyzer-diagnostic-extensions.o \ analyzer/analyzer-language.o \ analyzer/analyzer-logging.o \ analyzer/analyzer-pass.o \ diff --git a/gcc/analyzer/analyzer-diagnostic-extensions.cc b/gcc/analyzer/analyzer-diagnostic-extensions.cc new file mode 100644 index 000000000000..ae09ddd8a807 --- /dev/null +++ b/gcc/analyzer/analyzer-diagnostic-extensions.cc @@ -0,0 +1,144 @@ +/* Analyzer-specific extensions to diagnostic-handling. + Copyright (C) 2026 Free Software Foundation, Inc. + Contributed by David Malcolm <[email protected]>. + +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/>. */ + +#include "analyzer/common.h" + +#include "diagnostics/digraphs.h" +#include "diagnostics/dumping.h" +#include "context.h" +#include "channels.h" +#include "tree-diagnostic-sink-extensions.h" + +#include "analyzer/exploded-graph.h" + +namespace { namespace analyzer_events = gcc::topics::analyzer_events; } + +#if ENABLE_ANALYZER + +namespace ana { + +class lazy_supergraph_diagnostic_digraph + : public lazily_created<diagnostics::digraphs::digraph> +{ +public: + lazy_supergraph_diagnostic_digraph (const supergraph &sg) + : m_sg (sg) + { + } + + std::unique_ptr<diagnostics::digraphs::digraph> + create_object () const final override + { + auto g = m_sg.make_diagnostics_digraph (); + g->set_description ("\"supergraph\" used by '-fanalyzer'" + " internally to represent the user's code"); + return g; + } + +private: + const supergraph &m_sg; +}; + +class lazy_exploded_graph_diagnostic_digraph + : public lazily_created<diagnostics::digraphs::digraph> +{ +public: + lazy_exploded_graph_diagnostic_digraph (const exploded_graph &eg) + : m_eg (eg) + { + } + + std::unique_ptr<diagnostics::digraphs::digraph> + create_object () const final override + { + auto g = m_eg.make_diagnostics_digraph (); + g->set_description ("\"exploded graph\" used by '-fanalyzer'" + " internally to represent interprocedural control" + " flow in the user's code along with possible states" + " at each program point"); + return g; + } + +private: + const exploded_graph &m_eg; +}; + +static void +report_analyzer_info (diagnostics::sink &sink, + const analyzer_events::on_ana_done &m) +{ + sink.report_global_digraph + (lazy_supergraph_diagnostic_digraph (m.m_eg.get_supergraph ())); + + sink.report_global_digraph + (lazy_exploded_graph_diagnostic_digraph (m.m_eg)); + + // TODO: capture other information??? +} + +} // namespace ana + +#endif /* #if ENABLE_ANALYZER */ + +/* A diagnostics::sink::extension which subscribes to the analyzer + being done and responds to it by adding internal details about the + analysis to the diagnostic sink. */ + +class analyzer_sink_extension : public diagnostics::sink::extension +{ +public: + analyzer_sink_extension (diagnostics::sink &sink) + : extension (sink), + m_event_subscriber (sink) + { + auto &channel = g->get_channels ().analyzer_events_channel; + channel.add_subscriber (m_event_subscriber); + } + + void + dump (FILE *out, int indent) const + { + diagnostics::dumping::emit_heading (out, indent, "analyzer_sink_extension"); + } + +private: + class event_subscriber : public analyzer_events::subscriber + { + public: + event_subscriber (diagnostics::sink &sink) : m_sink (sink) {} + + void + on_message (const analyzer_events::on_ana_done &m) final override + { +#if ENABLE_ANALYZER + report_analyzer_info (m_sink, m); +#endif + } + + private: + diagnostics::sink &m_sink; + } m_event_subscriber; +}; + +std::unique_ptr<diagnostics::sink::extension> +compiler_extension_factory::make_analyzer_graphs_extension (diagnostics::sink &sink) const +{ + return std::make_unique<analyzer_sink_extension> (sink); +} diff --git a/gcc/analyzer/common.h b/gcc/analyzer/common.h index 05452ef02ede..637f8a432206 100644 --- a/gcc/analyzer/common.h +++ b/gcc/analyzer/common.h @@ -712,6 +712,15 @@ struct on_frame_popped ana::region_model_context *m_ctxt; }; +/* A message published by the analyzer when it has finished and is about + to clean up. */ + +struct on_ana_done +{ + const ana::engine &m_engine; + const ana::exploded_graph &m_eg; +}; + struct subscriber { virtual ~subscriber () = default; @@ -719,6 +728,7 @@ struct subscriber { virtual void on_message (const on_tu_finished &) {} virtual void on_message (const on_ana_init &) {} virtual void on_message (const on_frame_popped &) {} + virtual void on_message (const on_ana_done &) {} }; } // namespace gcc::topics::analyzer_events diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index 41b4c2a75a9f..2e7acba09109 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -767,8 +767,19 @@ saved_diagnostic::to_json () const sd_obj->set ("state", m_state->to_json ()); if (m_best_epath) sd_obj->set_integer ("path_length", get_epath_length ()); - sd_obj->set_string ("pending_diagnostic", m_d->get_kind ()); + sd_obj->set<json::object> ("pending_diagnostic", m_d->to_json ()); sd_obj->set_integer ("idx", m_idx); + if (m_problem) + sd_obj->set<json::object> ("feasibility_problem", m_problem->to_json ()); + if (!m_duplicates.is_empty ()) + { + auto arr = std::make_unique<json::array> (); + + for (auto &dup : m_duplicates) + arr->append (std::make_unique <json::integer_number> (dup->m_idx)); + + sd_obj->set<json::array> ("duplicates", std::move (arr)); + } return sd_obj; } diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index c8a7dcef730e..a42350531b4b 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -1915,6 +1915,181 @@ exploded_edge::maybe_get_op () const return m_sedge->get_op (); } +/* A way to export the exploded_graph as a diagnostics::digraphs::digraph, + and thus to export it to SARIF diagnostic sinks. */ + +class exploded_graph_diagnostic_digraph : public diagnostics::digraphs::digraph +{ +public: + exploded_graph_diagnostic_digraph (const exploded_graph &eg) + : m_eg (eg) + { + set_graph_kind ("exploded_graph"); + + int i; + exploded_node *n; + FOR_EACH_VEC_ELT (m_eg.m_nodes, i, n) + add_node_for_enode (*n); + + exploded_edge *e; + FOR_EACH_VEC_ELT (m_eg.m_edges, i, e) + add_edge_for_eedge (*e); + + const json::json_property worklist_prop + ("gcc/analyzer/exploded_graph/worklist"); + set_property (worklist_prop, + m_eg.get_worklist ().to_json ()); + + /* The following are global, rather than part of the + exploded graph; we add them as part of the exploded graph's + diagnostic digraph for convenience. */ + + // TODO: analysis_plan + + const json::json_property diagnostic_manager_prop + ("gcc/analyzer/diagnostic_manager"); + set_property (diagnostic_manager_prop, + m_eg.get_diagnostic_manager ().to_json ()); + + auto model_mgr = m_eg.get_ext_state ().get_model_manager (); + + const json::json_property known_functions_prop + ("gcc/analyzer/known_functions"); + set_property (known_functions_prop, + model_mgr->get_known_function_manager ()->to_json ()); + + const json::json_property ext_state_prop + ("gcc/analyzer/extrinsic_state"); + set_property (ext_state_prop, + m_eg.get_ext_state ().to_json ()); + } + +private: + void + add_node_for_enode (const exploded_node &enode) + { + pretty_printer pp; + enode.dump_dot_id (&pp); + auto node_for_enode + = std::make_unique<diagnostics::digraphs::node> (*this, + pp_formatted_text (&pp)); + + if (auto fun = enode.get_function ()) + node_for_enode->set_logical_loc + (tree_logical_location_manager::key_from_tree (fun->decl)); + node_for_enode->set_physical_loc (enode.get_location ()); + + auto &point = enode.get_point (); + if (auto snode = point.get_supernode ()) + { + const json::string_property enode_prop_supernode + ("gcc/analyzer/exploded_node/supernode"); + pretty_printer pp; + snode->dump_dot_id (&pp); + node_for_enode->set_property + (enode_prop_supernode, + pp_formatted_text (&pp)); + } + + auto &cs = point.get_call_string (); + auto cs_arr = std::make_unique<json::array> (); + for (int i = 0; i < cs.length (); ++i) + { + auto e = cs[i]; + auto obj = std::make_unique<json::object> (); + + { + pretty_printer pp; + e.m_call_sedge->m_src->dump_dot_id (&pp); + obj->set_string ("source supernode", pp_formatted_text (&pp)); + } + { + pretty_printer pp; + e.m_call_sedge->m_dest->dump_dot_id (&pp); + obj->set_string ("destination supernode", pp_formatted_text (&pp)); + } + { + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + e.m_call_op->print_as_edge_label (&pp, false); + obj->set_string ("call operation", pp_formatted_text (&pp)); + } + cs_arr->append (std::move (obj)); + } + const json::json_property enode_prop_call_string + ("gcc/analyzer/exploded_node/call_string"); + node_for_enode->set_property (enode_prop_call_string, + std::move (cs_arr)); + + const json::json_property enode_prop_program_state + ("gcc/analyzer/exploded_node/program_state"); + node_for_enode->set_property + (enode_prop_program_state, + enode.get_state ().to_json (m_eg.get_ext_state ())); + + const json::string_property enode_prop_status + ("gcc/analyzer/exploded_node/status"); + node_for_enode->set_property + (enode_prop_status, + exploded_node::status_to_str (enode.get_status ())); + + m_node_for_enode.insert ({&enode, node_for_enode.get ()}); + add_node (std::move (node_for_enode)); + } + + void + add_edge_for_eedge (const exploded_edge &eedge) + { + diagnostics::digraphs::node *src_node + = get_node_for_enode (eedge.m_src); + diagnostics::digraphs::node *dst_node + = get_node_for_enode (eedge.m_dest); + auto e = std::make_unique<diagnostics::digraphs::edge> (*this, + nullptr, + *src_node, + *dst_node); + if (auto *sedge = eedge.m_sedge) + { + const json::string_property eedge_prop_superedge + ("gcc/analyzer/exploded_edge/superedge"); + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + sedge->dump_label_to_pp (&pp, false); + e->set_property (eedge_prop_superedge, + pp_formatted_text (&pp)); + } + if (auto *custom_info = eedge.m_custom_info.get ()) + { + const json::string_property eedge_prop_custom_edge_info + ("gcc/analyzer/exploded_edge/custom_edge_info"); + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + custom_info->print (&pp); + e->set_property (eedge_prop_custom_edge_info, + pp_formatted_text (&pp)); + } + add_edge (std::move (e)); + } + + diagnostics::digraphs::node * + get_node_for_enode (const exploded_node *enode) + { + auto iter = m_node_for_enode.find (enode); + gcc_assert (iter != m_node_for_enode.end ()); + return iter->second; + } + + const exploded_graph &m_eg; + std::map<const exploded_node *, + diagnostics::digraphs::node *> m_node_for_enode; +}; + +std::unique_ptr<diagnostics::digraphs::digraph> +exploded_graph::make_diagnostics_digraph () const +{ + return std::make_unique<exploded_graph_diagnostic_digraph> (*this); +} + /* struct stats. */ /* stats' ctor. */ @@ -3781,6 +3956,20 @@ feasibility_problem::dump_to_pp (pretty_printer *pp) const } } +std::unique_ptr<json::object> +feasibility_problem::to_json () const +{ + auto obj = std::make_unique<json::object> (); + + obj->set_integer ("eedge_idx", m_eedge_idx); + obj->set_integer ("eedge src enode", m_eedge.m_src->m_index); + obj->set_integer ("eedge dst enode", m_eedge.m_dest->m_index); + if (m_rc) + obj->set <json::object> ("rejected_constraint", m_rc->to_json ()); + + return obj; +} + /* class feasibility_state. */ /* Ctor for feasibility_state, at the beginning of a path. */ @@ -4569,8 +4758,11 @@ private: const char *compass_pt) const { if (const supernode *snode = enode.get_supernode ()) - pp_printf (pp, "node_%i:en_%i:%s", - snode->m_id, enode.m_index, compass_pt); + { + snode->dump_dot_id (pp); + pp_printf (pp, ":en_%i:%s", + enode.m_index, compass_pt); + } else pp_printf (pp, "en_%i:%s", enode.m_index, compass_pt); @@ -4886,6 +5078,10 @@ impl_run_checkers (logger *logger) if (flag_dump_analyzer_untracked) eng.get_model_manager ()->dump_untracked_regions (); + if (auto channel + = g->get_channels ().analyzer_events_channel.get_if_active ()) + channel->publish (gcc::topics::analyzer_events::on_ana_done {eng, eg}); + delete purge_map; /* Free up any dominance info that we may have created. */ diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h index c33bd476c9d0..43166599067d 100644 --- a/gcc/analyzer/exploded-graph.h +++ b/gcc/analyzer/exploded-graph.h @@ -899,6 +899,12 @@ public: exploded_node *find_previous_entry_to (function *top_of_stack_fun, exploded_node *enode) const; + std::unique_ptr<diagnostics::digraphs::digraph> + make_diagnostics_digraph () const; + + const worklist & + get_worklist () const { return m_worklist; } + private: void print_bar_charts (pretty_printer *pp) const; @@ -963,6 +969,9 @@ public: void dump_to_pp (pretty_printer *pp) const; + std::unique_ptr<json::object> + to_json () const; + unsigned m_eedge_idx; const exploded_edge &m_eedge; std::unique_ptr<rejected_constraint> m_rc; diff --git a/gcc/analyzer/known-function-manager.cc b/gcc/analyzer/known-function-manager.cc index 679fe1a70158..db190eab6aa7 100644 --- a/gcc/analyzer/known-function-manager.cc +++ b/gcc/analyzer/known-function-manager.cc @@ -192,6 +192,50 @@ known_function_manager::get_by_identifier_in_std_ns (tree identifier) const return nullptr; } +static std::unique_ptr<json::array> +get_keys_as_json (const hash_map<tree, known_function *> &map) +{ + std::vector<std::string> vec_keys; + for (const auto &iter : map) + vec_keys.push_back (IDENTIFIER_POINTER (iter.first)); + + std::sort (vec_keys.begin (), vec_keys.end ()); + + auto arr = std::make_unique<json::array> (); + for (auto iter : vec_keys) + arr->append_string (iter.c_str ()); + return arr; +} + +std::unique_ptr<json::object> +known_function_manager::to_json () const +{ + auto obj = std::make_unique<json::object> (); + + obj->set ("root_ns", get_keys_as_json (m_map_id_to_kf)); + obj->set ("std_ns", get_keys_as_json (m_std_ns_map_id_to_kf)); + + // Built-in functions + // TODO: needs bulletproofing of combined_fn_name + if (0) + { + std::vector<std::string> vec_keys; + for (int i = 0; i < CFN_LAST; ++i) + if (m_combined_fns_arr[i]) + vec_keys.push_back + (combined_fn_name (static_cast<enum combined_fn> (i))); + + std::sort (vec_keys.begin (), vec_keys.end ()); + + auto arr = std::make_unique<json::array> (); + for (auto iter : vec_keys) + arr->append_string (iter.c_str ()); + obj->set ("builtin_fns", std::move (arr)); + } + + return obj; +} + } // namespace ana diff --git a/gcc/analyzer/known-function-manager.h b/gcc/analyzer/known-function-manager.h index 6abcfab2b2e6..5cc84196a797 100644 --- a/gcc/analyzer/known-function-manager.h +++ b/gcc/analyzer/known-function-manager.h @@ -51,6 +51,8 @@ public: const known_function *get_match (tree fndecl, const call_details &cd) const; const known_function *get_internal_fn (enum internal_fn) const; + std::unique_ptr<json::object> to_json () const; + private: DISABLE_COPY_AND_ASSIGN (known_function_manager); diff --git a/gcc/analyzer/ops.cc b/gcc/analyzer/ops.cc index 382edf7144ef..6d7b23b7e98f 100644 --- a/gcc/analyzer/ops.cc +++ b/gcc/analyzer/ops.cc @@ -1948,6 +1948,10 @@ public: { pp_printf (pp, "rejected_eh_dispatch"); } + std::unique_ptr<json::object> to_json () const final override + { + return rejected_constraint::make_base_json ("rejected_eh_dispatch"); + } }; static bool diff --git a/gcc/analyzer/pending-diagnostic.cc b/gcc/analyzer/pending-diagnostic.cc index 778b5baec7a8..87b6060771b4 100644 --- a/gcc/analyzer/pending-diagnostic.cc +++ b/gcc/analyzer/pending-diagnostic.cc @@ -22,6 +22,7 @@ along with GCC; see the file COPYING3. If not see #include "diagnostics/event-id.h" #include "diagnostics/logging.h" +#include "diagnostics/sarif-sink.h" #include "cpplib.h" #include "digraph.h" #include "ordered-hash-map.h" @@ -152,6 +153,16 @@ diagnostic_emission_context::inform (const char *gmsgid, ...) va_end (ap); } +std::unique_ptr<json::object> +pending_diagnostic::to_json () const +{ + /* maybe_add_sarif_properties assumes a sarif_object, so use one here. */ + auto obj = std::make_unique<diagnostics::sarif_object> (); + obj->set_string ("kind", get_kind ()); + maybe_add_sarif_properties (*obj); + return obj; +} + /* Return true if T1 and T2 are "the same" for the purposes of diagnostic deduplication. */ diff --git a/gcc/analyzer/pending-diagnostic.h b/gcc/analyzer/pending-diagnostic.h index 900a229c19dc..f97e995339c8 100644 --- a/gcc/analyzer/pending-diagnostic.h +++ b/gcc/analyzer/pending-diagnostic.h @@ -276,6 +276,8 @@ class pending_diagnostic /* Hand-coded RTTI: get an ID for the subclass. */ virtual const char *get_kind () const = 0; + virtual std::unique_ptr<json::object> to_json () const; + /* A vfunc for identifying "use of uninitialized value". */ virtual bool use_of_uninit_p () const { return false; } diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 09c3eb5aead8..361b06073474 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -7495,6 +7495,17 @@ debug (const region_model &rmodel) rmodel.dump (false); } +// class rejected_constraint + +std::unique_ptr<json::object> +rejected_constraint::make_base_json (const char *kind) const +{ + auto obj = std::make_unique<json::object> (); + obj->set_string ("kind", kind); + obj->set<json::object> ("region_model", m_model.to_json ()); + return obj; +} + /* class rejected_op_constraint : public rejected_constraint. */ void @@ -7506,6 +7517,16 @@ rejected_op_constraint::dump_to_pp (pretty_printer *pp) const m_rhs->dump_to_pp (pp, true); } +std::unique_ptr<json::object> +rejected_op_constraint::to_json () const +{ + auto obj = rejected_constraint::make_base_json ("rejected_op_constraint"); + obj->set<json::value> ("lhs", m_lhs->to_json ()); + obj->set_string ("op", op_symbol_code (m_op)); + obj->set<json::value> ("rhs", m_rhs->to_json ()); + return obj; +} + /* class rejected_default_case : public rejected_constraint. */ void @@ -7514,6 +7535,12 @@ rejected_default_case::dump_to_pp (pretty_printer *pp) const pp_string (pp, "implicit default for enum"); } +std::unique_ptr<json::object> +rejected_default_case::to_json () const +{ + return rejected_constraint::make_base_json ("rejected_default_case"); +} + /* class rejected_ranges_constraint : public rejected_constraint. */ void @@ -7526,6 +7553,16 @@ rejected_ranges_constraint::dump_to_pp (pretty_printer *pp) const m_ranges->dump_to_pp (pp, true); } +std::unique_ptr<json::object> +rejected_ranges_constraint::to_json () const +{ + auto obj = rejected_constraint::make_base_json ("rejected_ranges_constraint"); + if (m_expr) + obj->set ("expr", tree_to_json (m_expr)); + obj->set<json::value> ("ranges", m_ranges->to_json ()); + return obj; +} + /* class engine. */ /* engine's ctor. */ diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 22a57a847357..90aed12d0769 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -1261,6 +1261,7 @@ class rejected_constraint public: virtual ~rejected_constraint () {} virtual void dump_to_pp (pretty_printer *pp) const = 0; + virtual std::unique_ptr<json::object> to_json () const = 0; const region_model &get_model () const { return m_model; } @@ -1269,6 +1270,9 @@ protected: : m_model (model) {} + std::unique_ptr<json::object> + make_base_json (const char *kind) const; + region_model m_model; }; @@ -1282,6 +1286,7 @@ public: {} void dump_to_pp (pretty_printer *pp) const final override; + std::unique_ptr<json::object> to_json () const final override; const svalue *m_lhs; enum tree_code m_op; @@ -1296,6 +1301,7 @@ public: {} void dump_to_pp (pretty_printer *pp) const final override; + std::unique_ptr<json::object> to_json () const final override; }; class rejected_ranges_constraint : public rejected_constraint @@ -1308,6 +1314,7 @@ public: {} void dump_to_pp (pretty_printer *pp) const final override; + std::unique_ptr<json::object> to_json () const final override; private: tree m_expr; diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc index 81b337fd7ff1..b9b5e70795a1 100644 --- a/gcc/analyzer/supergraph.cc +++ b/gcc/analyzer/supergraph.cc @@ -31,8 +31,10 @@ along with GCC; see the file COPYING3. If not see #include "tree-cfg.h" #include "cfganal.h" #include "except.h" +#include "tree-logical-location.h" #include "diagnostics/file-cache.h" +#include "diagnostics/digraphs.h" #include "analyzer/supergraph.h" #include "analyzer/analyzer-logging.h" @@ -554,6 +556,191 @@ supergraph::dump_dot (const char *path, const dump_args_t &dump_args) const fclose (fp); } +/* A way to export the supergraph as a diagnostics::digraphs::digraph, and thus + to export it to SARIF diagnostic sinks. */ + +class supergraph_diagnostic_digraph : public diagnostics::digraphs::digraph +{ +public: + supergraph_diagnostic_digraph (const supergraph &sg) + : m_sg (sg) + { + set_graph_kind ("supergraph"); + + /* TODO: maybe (optionally) sub-subdivide by TU, for LTO; see also: + https://gcc-python-plugin.readthedocs.io/en/latest/_images/sample-supergraph.png + */ + + /* Break out the supernodes into clusters by function. */ + { + cgraph_node *node; + FOR_EACH_FUNCTION_WITH_GIMPLE_BODY (node) + { + function *fun = node->get_fun (); + gcc_assert (fun); + auto_cfun sentinel (fun); + const char *funcname = function_name (fun); + pretty_printer pp; + pp_printf (&pp, "cluster for %s", funcname); + std::string id = pp_formatted_text (&pp); + + auto diag_node_for_func + = std::make_unique<diagnostics::digraphs::node> (*this, id); + diag_node_for_func->set_logical_loc + (tree_logical_location_manager::key_from_tree (fun->decl)); + + if (loops_for_fn (fun)) + add_nodes_for_loop (diag_node_for_func.get (), + get_loop (fun, 0), + fun); + else + { + basic_block bb; + FOR_ALL_BB_FN (bb, fun) + add_nodes_for_bb (*diag_node_for_func, bb, fun); + } + + add_node (std::move (diag_node_for_func)); + } + } + + /* Superedges. */ + int i; + superedge *e; + FOR_EACH_VEC_ELT (m_sg.m_edges, i, e) + add_edge_for_superedge (*e); + } + +private: + /* Recursively add nodes into PARENT for all the snodes within LOOP + and the loops within it. */ + void + add_nodes_for_loop (diagnostics::digraphs::node *parent, + class loop *loop, + function *fun) + { + gcc_assert (parent); + gcc_assert (loop); + gcc_assert (fun); + + basic_block *body; + unsigned int i; + + if (loop->header != NULL + && loop->latch != EXIT_BLOCK_PTR_FOR_FN (cfun)) + { + pretty_printer pp; + pp_printf (&pp, "cluster for loop %d of %d", + loop->num, fun->funcdef_no); + + auto loop_node + = std::make_unique<diagnostics::digraphs::node> (*this, + pp_formatted_text (&pp)); + loop_node->set_logical_loc + (tree_logical_location_manager::key_from_tree (fun->decl)); + + auto new_parent = loop_node.get (); + parent->add_child (std::move (loop_node)); + parent = new_parent; + } + + // Recurse + for (class loop *inner = loop->inner; inner; inner = inner->next) + add_nodes_for_loop (parent, inner, fun); + + if (loop->header == nullptr) + return; + + if (loop->latch == EXIT_BLOCK_PTR_FOR_FN (cfun)) + body = get_loop_body (loop); + else + body = get_loop_body_in_bfs_order (loop); + + for (i = 0; i < loop->num_nodes; i++) + { + basic_block bb = body[i]; + if (bb->loop_father == loop) + add_nodes_for_bb (*parent, bb, fun); + } + + free (body); + } + + /* Add nodes for all the snodes from BB. */ + void + add_nodes_for_bb (diagnostics::digraphs::node &parent, + basic_block bb, + function *fun) + { + pretty_printer pp; + const char *funcname = function_name (fun); + pp_printf (&pp, "%s_bb_%i", funcname, bb->index); + auto node_for_bb + = std::make_unique<diagnostics::digraphs::node> (*this, + pp_formatted_text (&pp)); + node_for_bb->set_logical_loc + (tree_logical_location_manager::key_from_tree (fun->decl)); + + // TODO: maybe keep an index per-function/per-bb to speed this up??? + int i; + supernode *snode; + FOR_EACH_VEC_ELT (m_sg.m_nodes, i, snode) + if (snode->m_fun == fun && snode->m_bb == bb) + add_node_for_supernode (*node_for_bb, *snode); + parent.add_child (std::move (node_for_bb)); + } + + void + add_node_for_supernode (diagnostics::digraphs::node &bb_node, + const supernode &snode) + { + pretty_printer pp; + snode.dump_dot_id (&pp); + auto node_for_snode + = std::make_unique<diagnostics::digraphs::node> (*this, + pp_formatted_text (&pp)); + m_node_for_snode.insert ({&snode, node_for_snode.get ()}); + + node_for_snode->set_logical_loc + (tree_logical_location_manager::key_from_tree (snode.m_fun->decl)); + /* TODO: if we have an m_label, it would be nice to use this as the + logical location, within the function. However SARIF doesn't support + this yet (https://github.com/oasis-tcs/sarif-spec/issues/802). */ + + node_for_snode->set_physical_loc (snode.m_loc); + + bb_node.add_child (std::move (node_for_snode)); + } + + void + add_edge_for_superedge (const superedge &sedge) + { + diagnostics::digraphs::node *src_node + = get_node_for_snode (sedge.m_src); + diagnostics::digraphs::node *dst_node + = get_node_for_snode (sedge.m_dest); + label_text desc = sedge.get_description (false); + add_edge (nullptr, *src_node, *dst_node, desc.get ()); + } + + diagnostics::digraphs::node * + get_node_for_snode (const supernode *snode) + { + auto iter = m_node_for_snode.find (snode); + gcc_assert (iter != m_node_for_snode.end ()); + return iter->second; + } + + const supergraph &m_sg; + std::map<const supernode *, diagnostics::digraphs::node *> m_node_for_snode; +}; + +std::unique_ptr<diagnostics::digraphs::digraph> +supergraph::make_diagnostics_digraph () const +{ + return std::make_unique<supergraph_diagnostic_digraph> (*this); +} + /* Return a new json::object of the form {"nodes" : [objs for snodes], "edges" : [objs for sedges]}. */ @@ -898,7 +1085,7 @@ supernode::dump_dot (graphviz_out *gv, const dump_args_t &args) const void supernode::dump_dot_id (pretty_printer *pp) const { - pp_printf (pp, "node_%i", m_id); + pp_printf (pp, "supernode_%i", m_id); } /* Return a new json::object of the form diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h index c90dedbaff50..7936b66c166e 100644 --- a/gcc/analyzer/supergraph.h +++ b/gcc/analyzer/supergraph.h @@ -173,6 +173,9 @@ public: supernode *add_node (function *fun, basic_block bb, logger *logger); + std::unique_ptr<diagnostics::digraphs::digraph> + make_diagnostics_digraph () const; + private: gimple * populate_for_basic_block (basic_block bb, diff --git a/gcc/diagnostics/sarif-sink.cc b/gcc/diagnostics/sarif-sink.cc index 834b6aec3adf..fad4e5cde8c7 100644 --- a/gcc/diagnostics/sarif-sink.cc +++ b/gcc/diagnostics/sarif-sink.cc @@ -2971,7 +2971,7 @@ make_sarif_node (const digraphs::node &n, auto result = std::make_unique<sarif_node> (); // 3.40.2 id property - result->set_string ("id", n.get_id ().c_str ()); + result->set_string ("id", n.get_id ()); copy_any_property_bag (n, *result); @@ -3019,7 +3019,7 @@ make_sarif_edge (const digraphs::edge &e, auto result = std::make_unique<sarif_edge> (); // 3.41.2 id property - result->set_string ("id", e.get_id ().c_str ()); + result->set_string ("id", e.get_id ()); copy_any_property_bag (e, *result); @@ -3030,10 +3030,10 @@ make_sarif_edge (const digraphs::edge &e, builder->make_message_object (label)); // 3.41.4 sourceNodeId property - result->set_string ("sourceNodeId", e.get_src_node ().get_id ().c_str ()); + result->set_string ("sourceNodeId", e.get_src_node ().get_id ()); // 3.41.5 targetNodeId property - result->set_string ("targetNodeId", e.get_dst_node ().get_id ().c_str ()); + result->set_string ("targetNodeId", e.get_dst_node ().get_id ()); return result; } @@ -3316,7 +3316,7 @@ set_string_property_escaping_braces (json::object &obj, const char *value) { std::string escaped (escape_braces (value)); - obj.set_string (property_name, escaped.c_str ()); + obj.set_string (property_name, escaped); } /* Make a "message" object (SARIF v2.1.0 section 3.11) for MSG. */ diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 41be82cb8a5d..60d7163eee35 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -6522,6 +6522,12 @@ This is a debugging feature and defaults to @code{no}. If @code{state-graphs=yes}, then attempt to capture detailed state information from @option{-fanalyzer} in the generated SARIF. +@item analyzer-graphs=@r{[}yes@r{|}no@r{]} +This is a debugging feature and defaults to @code{no}. +If @code{analyzer-graphs=yes}, then attempt to capture detailed +information about the analysis by @option{-fanalyzer} in the +generated SARIF. + @end table @opindex fdiagnostics-add-output=experimental-html diff --git a/gcc/json.cc b/gcc/json.cc index bff9afffb892..c776d02d3351 100644 --- a/gcc/json.cc +++ b/gcc/json.cc @@ -18,6 +18,7 @@ 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/>. */ +#define INCLUDE_STRING #include "config.h" #include "system.h" #include "coretypes.h" @@ -429,6 +430,16 @@ object::set_string (const char *key, const char *utf8_value) return str; } +/* As above, but taking a std::string. */ + +const json::string * +object::set_string (const char *key, std::string utf8_value) +{ + json::string *str = new json::string (utf8_value.c_str ()); + set (key, str); + return str; +} + /* Set value of KEY within this object to a JSON integer value based on V. Return a borrowed ptr to the new json::integer_number. */ diff --git a/gcc/json.h b/gcc/json.h index 3d79e2a023cb..e32cd99d08a3 100644 --- a/gcc/json.h +++ b/gcc/json.h @@ -223,6 +223,7 @@ class object : public value const map_t &get_map () const { return m_map; } const json::string *set_string (const char *key, const char *utf8_value); + const json::string *set_string (const char *key, std::string utf8_value); const json::integer_number *set_integer (const char *key, long v); void set_float (const char *key, double v); diff --git a/gcc/opts-diagnostic.cc b/gcc/opts-diagnostic.cc index 594f735a8c67..b1ca4c610fc9 100644 --- a/gcc/opts-diagnostic.cc +++ b/gcc/opts-diagnostic.cc @@ -49,7 +49,8 @@ class gcc_extra_keys : public diagnostics::output_spec::key_handler { public: gcc_extra_keys () - : m_cfgs (false) + : m_analyzer_graphs (false), + m_cfgs (false) { } enum result @@ -57,6 +58,8 @@ public: const std::string &key, const std::string &value) final override { + if (key == "analyzer-graphs") + return parse_bool_value (ctxt, key, value, m_analyzer_graphs); if (key == "cfgs") return parse_bool_value (ctxt, key, value, m_cfgs); return result::unrecognized; @@ -65,9 +68,11 @@ public: void get_keys (auto_vec<const char *> &out_known_keys) const final override { + out_known_keys.safe_push ("analyzer-graphs"); out_known_keys.safe_push ("cfgs"); } + bool m_analyzer_graphs; bool m_cfgs; }; @@ -107,6 +112,8 @@ handle_gcc_specific_keys (const gcc_extra_keys &gcc_keys, { if (gcc_keys.m_cfgs) sink.add_extension (ext_factory.make_cfg_extension (sink)); + if (gcc_keys.m_analyzer_graphs) + sink.add_extension (ext_factory.make_analyzer_graphs_extension (sink)); } static std::unique_ptr<diagnostics::sink> diff --git a/gcc/opts-diagnostic.h b/gcc/opts-diagnostic.h index 6c2b25d63428..224caa65e595 100644 --- a/gcc/opts-diagnostic.h +++ b/gcc/opts-diagnostic.h @@ -73,6 +73,9 @@ public: virtual std::unique_ptr<diagnostics::sink::extension> make_cfg_extension (diagnostics::sink &sink) const = 0; + virtual std::unique_ptr<diagnostics::sink::extension> + make_analyzer_graphs_extension (diagnostics::sink &sink) const = 0; + static const gcc_extension_factory *singleton; }; diff --git a/gcc/testsuite/gcc.dg/analyzer/sarif-analyzer-graphs.c b/gcc/testsuite/gcc.dg/analyzer/sarif-analyzer-graphs.c new file mode 100644 index 000000000000..5beeb88aeb4b --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/sarif-analyzer-graphs.c @@ -0,0 +1,18 @@ +/* Smoketest for the "analyzer-graphs=yes" option to the sarif + diagnostic sink. */ + +/* { dg-additional-options "-fdiagnostics-add-output=sarif:analyzer-graphs=yes" } */ + +void double_free (int flag, void *p) +{ + if (flag) + __builtin_free (p); + __builtin_free (p); /* { dg-warning "double-'free' of 'p'" } */ +} + +/* Verify that some JSON was written to a file with the expected name. */ +/* { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest sarif-analyzer-graphs.c "sarif-analyzer-graphs.py" } } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/sarif-analyzer-graphs.py b/gcc/testsuite/gcc.dg/analyzer/sarif-analyzer-graphs.py new file mode 100644 index 000000000000..3be5fb910657 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/sarif-analyzer-graphs.py @@ -0,0 +1,53 @@ +""" +Smoketest for the "analyzer-graphs=yes" option to the sarif +diagnostic sink. +""" + +from sarif import * + +import pytest + [email protected](scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_supergraph(sarif): + runs = sarif['runs'] + graphs = runs[0]['graphs'] + + sg = graphs[0] + assert sg['properties']['gcc/digraphs/graph/kind'] == 'supergraph' + assert sg['description']['text'] == ('"supergraph" used by \'-fanalyzer\' internally to ' + "represent the user's code") + assert len(sg['nodes']) > 0 + assert len(sg['edges']) > 0 + +def test_exploded_graph(sarif): + runs = sarif['runs'] + graphs = runs[0]['graphs'] + + eg = graphs[1] + assert eg['description']['text'] == ('"exploded graph" used by \'-fanalyzer\' internally ' + 'to represent interprocedural control flow in the ' + "user's code along with possible states at each " + 'program point') + assert eg['properties']['gcc/digraphs/graph/kind'] == 'exploded_graph' + assert len(eg['nodes']) > 0 + assert len(eg['edges']) > 0 + + dm = eg['properties']['gcc/analyzer/diagnostic_manager'] + assert dm is not None + assert len(dm['diagnostics']) == 1 + diag = dm['diagnostics'][0] + assert diag['pending_diagnostic']['kind'] == 'double_free' + + assert eg['properties']['gcc/analyzer/exploded_graph/worklist']['scc'] + + ext_state = eg['properties']['gcc/analyzer/extrinsic_state'] + assert ext_state is not None + assert 'checkers' in ext_state + + kfm = eg['properties']['gcc/analyzer/known_functions'] + assert kfm is not None + assert 'realloc' in kfm['root_ns'] + assert 'strlen' in kfm['std_ns'] diff --git a/gcc/tree-diagnostic-sink-extensions.h b/gcc/tree-diagnostic-sink-extensions.h index b921eaa6c7f5..8877b08f1b7a 100644 --- a/gcc/tree-diagnostic-sink-extensions.h +++ b/gcc/tree-diagnostic-sink-extensions.h @@ -27,6 +27,9 @@ class compiler_extension_factory : public gcc_extension_factory public: std::unique_ptr<diagnostics::sink::extension> make_cfg_extension (diagnostics::sink &sink) const final override; + + std::unique_ptr<diagnostics::sink::extension> + make_analyzer_graphs_extension (diagnostics::sink &sink) const final override; }; #endif /* TREE_DIAGNOSTIC_SINK_EXTENSIONS_H */
