| /* Directed graphs associated with a diagnostic. |
| Copyright (C) 2025-2026 Free Software Foundation, Inc. |
| Contributed by David Malcolm <dmalcolm@redhat.com>. |
| |
| 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/>. */ |
| |
| #define INCLUDE_ALGORITHM |
| #define INCLUDE_MAP |
| #define INCLUDE_SET |
| #define INCLUDE_STRING |
| #define INCLUDE_VECTOR |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| |
| #include "graphviz.h" |
| #include "diagnostics/digraphs.h" |
| #include "diagnostics/digraphs-to-dot.h" |
| #include "diagnostics/sarif-sink.h" |
| #include "custom-sarif-properties/digraphs.h" |
| |
| using digraph_object = diagnostics::digraphs::object; |
| using digraph = diagnostics::digraphs::digraph; |
| using digraph_node = diagnostics::digraphs::node; |
| using digraph_edge = diagnostics::digraphs::edge; |
| |
| namespace properties = custom_sarif_properties::digraphs; |
| |
| // class object |
| |
| /* String properties. */ |
| |
| const char * |
| digraph_object::get_property (const json::string_property &property) const |
| { |
| if (!m_property_bag) |
| return nullptr; |
| if (json::value *jv = m_property_bag->get (property.m_key.get ())) |
| if (json::string *jstr = jv->dyn_cast_string ()) |
| return jstr->get_string (); |
| return nullptr; |
| } |
| |
| void |
| digraph_object::set_property (const json::string_property &property, |
| const char *utf8_value) |
| { |
| auto &bag = ensure_property_bag (); |
| bag.set_string (property.m_key.get (), utf8_value); |
| } |
| |
| /* Integer properties. */ |
| |
| bool |
| digraph_object::maybe_get_property (const json::integer_property &property, |
| long &out_value) const |
| { |
| if (!m_property_bag) |
| return false; |
| if (json::value *jv = m_property_bag->get (property.m_key.get ())) |
| if (json::integer_number *jnum = jv->dyn_cast_integer_number ()) |
| { |
| out_value = jnum->get (); |
| return true; |
| } |
| return false; |
| } |
| |
| void |
| digraph_object::set_property (const json::integer_property &property, long value) |
| { |
| auto &bag = ensure_property_bag (); |
| bag.set_integer (property.m_key.get (), value); |
| } |
| |
| /* Bool properties. */ |
| void |
| digraph_object::set_property (const json::bool_property &property, bool value) |
| { |
| auto &bag = ensure_property_bag (); |
| bag.set_bool (property.m_key.get (), value); |
| } |
| |
| tristate |
| digraph_object:: |
| get_property_as_tristate (const json::bool_property &property) const |
| { |
| if (m_property_bag) |
| { |
| if (json::value *jv = m_property_bag->get (property.m_key.get ())) |
| switch (jv->get_kind ()) |
| { |
| default: |
| break; |
| case json::JSON_TRUE: |
| return tristate (true); |
| case json::JSON_FALSE: |
| return tristate (false); |
| } |
| } |
| return tristate::unknown (); |
| } |
| |
| /* Array-of-string properties. */ |
| json::array * |
| digraph_object::get_property (const json::array_of_string_property &property) const |
| { |
| if (m_property_bag) |
| if (json::value *jv = m_property_bag->get (property.m_key.get ())) |
| if (json::array *arr = jv->dyn_cast_array ()) |
| return arr; |
| return nullptr; |
| } |
| |
| /* json::value properties. */ |
| const json::value * |
| digraph_object::get_property (const json::json_property &property) const |
| { |
| if (m_property_bag) |
| return m_property_bag->get (property.m_key.get ()); |
| return nullptr; |
| } |
| |
| void |
| digraph_object::set_property (const json::json_property &property, |
| std::unique_ptr<json::value> value) |
| { |
| auto &bag = ensure_property_bag (); |
| bag.set (property.m_key.get (), std::move (value)); |
| } |
| |
| json::object & |
| digraph_object::ensure_property_bag () |
| { |
| if (!m_property_bag) |
| m_property_bag = std::make_unique<sarif_property_bag> ( ); |
| return *m_property_bag; |
| } |
| |
| // class digraph |
| |
| DEBUG_FUNCTION void |
| digraph::dump () const |
| { |
| make_json_sarif_graph ()->dump (); |
| } |
| |
| std::unique_ptr<json::object> |
| digraph::make_json_sarif_graph () const |
| { |
| return make_sarif_graph (*this, nullptr, nullptr); |
| } |
| |
| std::unique_ptr<dot::graph> |
| digraph::make_dot_graph () const |
| { |
| auto converter = to_dot::converter::make (*this); |
| return converter->make_dot_graph_from_diagnostic_graph (*this); |
| } |
| |
| std::unique_ptr<digraph> |
| digraph::clone () const |
| { |
| auto result = std::make_unique<diagnostics::digraphs::digraph> (); |
| |
| if (get_property_bag ()) |
| result->set_property_bag (get_property_bag ()->clone_as_object ()); |
| |
| std::map<digraph_node *, digraph_node *> node_mapping; |
| |
| for (auto &iter : m_nodes) |
| result->add_node (iter->clone (*result, node_mapping)); |
| for (auto &iter : m_edges) |
| result->add_edge (iter->clone (*result, node_mapping)); |
| |
| return result; |
| } |
| |
| void |
| digraph::add_edge (const char *id, |
| node &src_node, |
| node &dst_node, |
| const char *label) |
| { |
| auto e = std::make_unique<digraph_edge> (*this, |
| id, |
| src_node, |
| dst_node); |
| if (label) |
| e->set_label (label); |
| add_edge (std::move (e)); |
| } |
| |
| /* Utility function for edge ids: either use EDGE_ID, or |
| generate a unique one for when we don't care about the name. |
| |
| Edges in SARIF "SHALL" have an id that's unique within the graph |
| (SARIF 2.1.0 §3.41.2). This is so that graph traversals can refer |
| to edges by id (SARIF 2.1.0's §3.43.2 edgeId property). */ |
| |
| std::string |
| digraph::make_edge_id (const char *edge_id) |
| { |
| /* If we have an id, use it. */ |
| if (edge_id) |
| return edge_id; |
| |
| /* Otherwise, generate a unique one of the form "edgeN". */ |
| while (true) |
| { |
| auto candidate (std::string ("edge") |
| + std::to_string (m_next_edge_id_index++)); |
| auto iter = m_id_to_edge_map.find (candidate); |
| if (iter != m_id_to_edge_map.end ()) |
| { |
| // Try again with the next index... |
| continue; |
| } |
| return candidate; |
| } |
| } |
| |
| const char * |
| digraph::get_graph_kind () const |
| { |
| return get_property (properties::digraph::kind); |
| } |
| |
| void |
| digraph::set_graph_kind (const char *kind) |
| { |
| set_property (properties::digraph::kind, kind); |
| } |
| |
| // class node |
| |
| DEBUG_FUNCTION void |
| digraph_node::dump () const |
| { |
| to_json_sarif_node ()->dump (); |
| } |
| |
| std::unique_ptr<json::object> |
| digraph_node::to_json_sarif_node () const |
| { |
| return make_sarif_node (*this, nullptr, nullptr); |
| } |
| |
| std::unique_ptr<digraph_node> |
| digraph_node::clone (digraph &new_graph, |
| std::map<node *, node *> &node_mapping) const |
| { |
| auto result |
| = std::make_unique<digraph_node> (new_graph, get_id ()); |
| node_mapping.insert ({const_cast <node *> (this), result.get ()}); |
| |
| result->set_logical_loc (m_logical_loc); |
| |
| if (get_property_bag ()) |
| result->set_property_bag (get_property_bag ()->clone_as_object ()); |
| |
| for (auto &iter : m_children) |
| result->add_child (iter->clone (new_graph, node_mapping)); |
| |
| return result; |
| } |
| |
| // class edge |
| |
| std::unique_ptr<digraph_edge> |
| digraph_edge::clone (digraph &new_graph, |
| const std::map<node *, node *> &node_mapping) const |
| { |
| auto iter_new_src = node_mapping.find (&m_src_node); |
| gcc_assert (iter_new_src != node_mapping.end ()); |
| auto iter_new_dst = node_mapping.find (&m_dst_node); |
| gcc_assert (iter_new_dst != node_mapping.end ()); |
| auto result |
| = std::make_unique<digraph_edge> (new_graph, |
| m_id.c_str (), |
| *iter_new_src->second, |
| *iter_new_dst->second); |
| if (get_property_bag ()) |
| result->set_property_bag (get_property_bag ()->clone_as_object ()); |
| |
| return result; |
| } |
| |
| DEBUG_FUNCTION void |
| diagnostics::digraphs::edge::dump () const |
| { |
| to_json_sarif_edge ()->dump (); |
| } |
| |
| std::unique_ptr<json::object> |
| diagnostics::digraphs::edge::to_json_sarif_edge () const |
| { |
| return make_sarif_edge (*this, nullptr); |
| } |
| |
| #if CHECKING_P |
| |
| #include "selftest.h" |
| #include "custom-sarif-properties/state-graphs.h" |
| |
| namespace diagnostics { |
| namespace selftest { |
| |
| static void |
| test_empty_graph () |
| { |
| digraph g; |
| |
| { |
| auto sarif = g.make_json_sarif_graph (); |
| |
| pretty_printer pp; |
| sarif->print (&pp, true); |
| ASSERT_STREQ |
| (pp_formatted_text (&pp), |
| ("{\"nodes\": [],\n" |
| " \"edges\": []}")); |
| } |
| |
| { |
| auto dg = g.make_dot_graph (); |
| |
| pretty_printer pp; |
| dot::writer w (pp); |
| dg->print (w); |
| ASSERT_STREQ |
| (pp_formatted_text (&pp), |
| ("digraph {\n" |
| "}\n")); |
| } |
| } |
| |
| static void |
| test_simple_graph () |
| { |
| #define KEY_PREFIX "/placeholder/" |
| auto g = std::make_unique<digraph> (); |
| g->set_description ("test graph"); |
| g->set_property (json::string_property (KEY_PREFIX, "date"), "1066"); |
| |
| auto a = std::make_unique<digraph_node> (*g, "a"); |
| auto b = std::make_unique<digraph_node> (*g, "b"); |
| b->set_property (json::string_property (KEY_PREFIX, "color"), "red"); |
| auto c = std::make_unique<digraph_node> (*g, "c"); |
| c->set_label ("I am a node label"); |
| |
| auto e = std::make_unique<digraph_edge> (*g, nullptr, *a, *c); |
| e->set_property (json::string_property (KEY_PREFIX, "status"), |
| "copacetic"); |
| e->set_label ("I am an edge label"); |
| g->add_edge (std::move (e)); |
| |
| g->add_node (std::move (a)); |
| |
| b->add_child (std::move (c)); |
| g->add_node (std::move (b)); |
| #undef KEY_PREFIX |
| |
| { |
| auto sarif = g->make_json_sarif_graph (); |
| |
| pretty_printer pp; |
| sarif->print (&pp, true); |
| ASSERT_STREQ |
| (pp_formatted_text (&pp), |
| ("{\"properties\": {\"/placeholder/date\": \"1066\"},\n" |
| " \"nodes\": [{\"id\": \"a\"},\n" |
| " {\"id\": \"b\",\n" |
| " \"properties\": {\"/placeholder/color\": \"red\"},\n" |
| " \"children\": [{\"id\": \"c\"}]}],\n" |
| " \"edges\": [{\"id\": \"edge0\",\n" |
| " \"properties\": {\"/placeholder/status\": \"copacetic\"},\n" |
| " \"sourceNodeId\": \"a\",\n" |
| " \"targetNodeId\": \"c\"}]}")); |
| } |
| |
| { |
| auto dg = g->make_dot_graph (); |
| |
| pretty_printer pp; |
| dot::writer w (pp); |
| dg->print (w); |
| ASSERT_STREQ |
| (pp_formatted_text (&pp), |
| ("digraph {\n" |
| " label=\"test graph\";\n" |
| " a;\n" |
| " \n" |
| " subgraph cluster_b {\n" |
| " c [label=\"I am a node label\"];\n" |
| "\n" |
| " };\n" |
| " a -> c [label=\"I am an edge label\"];\n" |
| "}\n")); |
| } |
| } |
| |
| static void |
| test_property_objects () |
| { |
| namespace state_node_properties = custom_sarif_properties::state_graphs::node; |
| |
| digraph g; |
| digraph_node node (g, "a"); |
| |
| ASSERT_EQ (node.get_property (state_node_properties::kind_prop), |
| state_node_properties::kind_t::other); |
| node.set_property (state_node_properties::kind_prop, |
| state_node_properties::kind_t::stack); |
| ASSERT_EQ (node.get_property (state_node_properties::kind_prop), |
| state_node_properties::kind_t::stack); |
| |
| ASSERT_EQ (node.get_property (state_node_properties::dynalloc_state_prop), |
| state_node_properties::dynalloc_state_t::unknown); |
| node.set_property (state_node_properties::dynalloc_state_prop, |
| state_node_properties::dynalloc_state_t::freed); |
| ASSERT_EQ (node.get_property (state_node_properties::dynalloc_state_prop), |
| state_node_properties::dynalloc_state_t::freed); |
| |
| ASSERT_EQ (node.get_property (state_node_properties::type), nullptr); |
| node.set_property (state_node_properties::type, "const char *"); |
| ASSERT_STREQ (node.get_property (state_node_properties::type), |
| "const char *"); |
| } |
| |
| /* Run all of the selftests within this file. */ |
| |
| void |
| digraphs_cc_tests () |
| { |
| test_empty_graph (); |
| test_simple_graph (); |
| test_property_objects (); |
| } |
| |
| } // namespace diagnostics::selftest |
| } // namespace diagnostics |
| |
| #endif /* CHECKING_P */ |