diagnostic-format-sarif.o \
diagnostic-format-text.o \
diagnostic-global-context.o \
+ diagnostic-digraphs.o \
diagnostic-macro-unwinding.o \
diagnostic-output-spec.o \
diagnostic-path.o \
diagnostic-path-output.o \
diagnostic-show-locus.o \
+ diagnostic-state-graphs.o \
diagnostic-state-to-dot.o \
edit-context.o \
graphviz.o pex.o \
-/* Converting ana::program_state to XML state documents.
+/* Creating diagnostic state graphs from ana::program_state.
Copyright (C) 2025 Free Software Foundation, Inc.
Contributed by David Malcolm <dmalcolm@redhat.com>.
#define INCLUDE_SET
#include "analyzer/common.h"
-#include "xml.h"
-#include "xml-printer.h"
+#include "diagnostic-state-graphs.h"
+#include "diagnostic-format-sarif.h"
#include "analyzer/region-model.h"
#include "analyzer/program-state.h"
namespace ana {
+using namespace ::diagnostics::state_graphs;
+
static void
-set_wi_attr (xml::element &e,
+set_wi_attr (state_node_ref state_node,
const char *attr_name,
const wide_int_ref &w,
signop sgn)
{
pretty_printer pp;
pp_wide_int (&pp, w, sgn);
- e.set_attr (attr_name, pp_formatted_text (&pp));
+ state_node.set_attr (attr_name, pp_formatted_text (&pp));
}
static void
-set_type_attr (xml::element &e, const_tree type)
+set_type_attr (state_node_ref state_node, const_tree type)
{
gcc_assert (type);
pretty_printer pp;
pp_format_decoder (&pp) = default_tree_printer;
pp_printf (&pp, "%T", type);
- e.set_attr ("type", pp_formatted_text (&pp));
+ state_node.set_type (pp_formatted_text (&pp));
}
static void
-set_bits_attr (xml::element &e,
+set_bits_attr (state_node_ref state_node,
bit_range bits)
{
pretty_printer pp;
bits.dump_to_pp (&pp);
- e.set_attr ("bits", pp_formatted_text (&pp));
+ state_node.set_attr ("bits", pp_formatted_text (&pp));
}
-static void
-set_region_id_attr (xml::element &e,
- const region ®)
-{
- e.set_attr ("region_id", std::to_string (reg.get_id ()));
-}
+// class analyzer_state_graph : public diagnostics::digraphs::digraph
-// class xml_state : public xml::document
-
-xml_state::xml_state (const program_state &state,
- const extrinsic_state &ext_state)
-: xml::document (),
- m_state (state),
+analyzer_state_graph::analyzer_state_graph (const program_state &state,
+ const extrinsic_state &ext_state)
+: m_state (state),
m_ext_state (ext_state),
m_mgr (*ext_state.get_engine ()->get_model_manager ()),
- m_root (nullptr)
+ m_next_id (0)
{
- auto root = std::make_unique<xml::element> ("state-diagram", false);
- m_root = root.get ();
- add_child (std::move (root));
-
/* Find pointers to heap-allocated regions, and record their types,
so that we have a user-friendly way of showing the memory
(by field, rather than by byte offset). */
for (int i = state.m_region_model->get_stack_depth () - 1; i >= 0; --i)
{
const frame_region *reg = state.m_region_model->get_frame_at_index (i);
- get_or_create_element (*reg);
+ get_or_create_state_node (*reg);
}
/* Create bound memory. */
for (auto iter : *state.m_region_model->get_store ())
{
const bool create_all = false; // "true" for verbose, for debugging
- create_elements_for_binding_cluster (*iter.second, create_all);
+ create_state_nodes_for_binding_cluster (*iter.second, create_all);
}
/* TODO: Constraints. */
{
auto &sm = ext_state.get_sm (i);
for (const auto &iter : *smap)
- sm.add_state_to_xml (*this, *iter.first, iter.second.m_state);
+ sm.add_state_to_state_graph (*this, *iter.first, iter.second.m_state);
if (auto s = smap->get_global_state ())
- sm.add_global_state_to_xml (*this, s);
+ sm.add_global_state_to_state_graph (*this, s);
}
}
+
+ /* Process pending edges. */
+ while (m_pending_edges.size () > 0)
+ {
+ pending_edge item = m_pending_edges.back ();
+ m_pending_edges.pop_back ();
+
+ /* Ensure we have a node for the dst region. This
+ could lead to additional pending edges. */
+ auto dst_node = get_or_create_state_node (item.m_dst_reg);
+ add_edge (nullptr, item.m_src_node.m_node, dst_node.m_node);
+ }
}
-xml::element &
-xml_state::get_or_create_element (const region ®)
+state_node_ref
+analyzer_state_graph::get_or_create_state_node (const region ®)
{
- auto existing = m_region_to_element_map.find (®);
- if (existing != m_region_to_element_map.end ())
+ auto existing = m_region_to_state_node_map.find (®);
+ if (existing != m_region_to_state_node_map.end ())
return *existing->second;
- auto &e = create_and_add_element (reg);
- m_region_to_element_map[®] = &e;
- return e;
+ auto ref = create_and_add_state_node (reg);
+ m_region_to_state_node_map[®] = &ref.m_node;
+ return ref;
}
-xml::element&
-xml_state::create_and_add_element (const region ®)
+state_node_ref
+analyzer_state_graph::create_and_add_state_node (const region ®)
{
- auto e = create_element (reg);
- xml::element &result = *e;
+ auto node = create_state_node (reg);
+
+ state_node_ref result = *node;
if (auto parent_reg = reg.get_parent_region ())
+ if (parent_reg->get_kind () != RK_ROOT)
+ {
+ auto parent_state_node = get_or_create_state_node (*parent_reg);
+ parent_state_node.m_node.add_child (std::move (node));
+ return result;
+ }
+ add_node (std::move (node));
+ return result;
+}
+
+std::string
+analyzer_state_graph::make_node_id (const char *prefix)
+{
+ return std::string (prefix) + "-" + std::to_string (m_next_id++);
+}
+
+std::string
+analyzer_state_graph::make_node_id (const region ®)
+{
+ const char *prefix = nullptr;
+ switch (reg.get_kind ())
{
- auto parent_element = &get_or_create_element (*parent_reg);
- parent_element->add_child (std::move (e));
+ case RK_ROOT:
+ default:
+ gcc_unreachable ();
+ break;
+
+ case RK_GLOBALS:
+ return "globals";
+ case RK_CODE:
+ return "code";
+ case RK_STACK:
+ return "stack";
+ case RK_HEAP:
+ return "heap";
+
+ case RK_FRAME:
+ prefix = "frame-region";
+ break;
+ case RK_FUNCTION:
+ prefix = "function-region";
+ break;
+ case RK_LABEL:
+ prefix = "label-region";
+ break;
+ case RK_THREAD_LOCAL:
+ prefix = "thread-local-region";
+ break;
+ case RK_SYMBOLIC:
+ prefix = "symbolic-region";
+ break;
+ case RK_DECL:
+ prefix = "decl-region";
+ break;
+ case RK_FIELD:
+ prefix = "field-region";
+ break;
+ case RK_ELEMENT:
+ prefix = "element-region";
+ break;
+ case RK_OFFSET:
+ prefix = "offset-region";
+ break;
+ case RK_SIZED:
+ prefix = "sized-region";
+ break;
+ case RK_CAST:
+ prefix = "cast-region";
+ break;
+ case RK_HEAP_ALLOCATED:
+ prefix = "heap-allocated-region";
+ break;
+ case RK_ALLOCA:
+ prefix = "alloca-region";
+ break;
+ case RK_STRING:
+ prefix = "string-region";
+ break;
+ case RK_BIT_RANGE:
+ prefix = "bit-range-region";
+ break;
+ case RK_VAR_ARG:
+ prefix = "var-arg-region";
+ break;
+ case RK_ERRNO:
+ prefix = "errno-region";
+ break;
+ case RK_PRIVATE:
+ prefix = "private-region";
+ break;
+ case RK_UNKNOWN:
+ prefix = "unknown-region";
+ break;
}
- else
- m_root->add_child (std::move (e));
- return result;
+ return std::string (prefix) + "-" + std::to_string (reg.get_id ());
+}
+
+std::unique_ptr<diagnostics::digraphs::node>
+analyzer_state_graph::
+make_state_node (diagnostics::state_graphs::node_kind kind,
+ std::string id)
+{
+ auto node = std::make_unique<diagnostics::digraphs::node> (*this, std::move (id));
+ state_node_ref node_ref (*node);
+ node_ref.set_node_kind (kind);
+ return node;
}
-std::unique_ptr<xml::element>
-xml_state::make_memory_space_element (const char *label)
+std::unique_ptr<diagnostics::digraphs::node>
+analyzer_state_graph::
+make_memspace_state_node (const region ®,
+ diagnostics::state_graphs::node_kind kind)
{
- auto e = std::make_unique<xml::element> ("memory-space", false);
- e->set_attr ("label", label);
- return e;
+ return make_state_node (kind, make_node_id (reg));
}
-std::unique_ptr<xml::element>
-xml_state::create_element (const region ®)
+std::unique_ptr<diagnostics::digraphs::node>
+analyzer_state_graph::create_state_node (const region ®)
{
- std::unique_ptr<xml::element> e;
+ std::unique_ptr<diagnostics::digraphs::node> node;
+
switch (reg.get_kind ())
{
default:
case RK_FRAME:
{
- e = std::make_unique<xml::element> ("stack-frame", false);
const frame_region &frame_reg
= static_cast<const frame_region &> (reg);
+
+ node = make_state_node (diagnostics::state_graphs::node_kind::stack_frame,
+ make_node_id (reg));
+ node->set_logical_loc
+ (m_logical_loc_mgr.key_from_tree (frame_reg.get_fndecl ()));
{
pretty_printer pp;
pp_format_decoder (&pp) = default_tree_printer;
pp_printf (&pp, "%E", frame_reg.get_fndecl ());
- e->set_attr ("function", pp_formatted_text (&pp));
+ node->set_attr (STATE_NODE_PREFIX, "function",
+ pp_formatted_text (&pp));
}
}
break;
+
case RK_GLOBALS:
- e = make_memory_space_element ("Globals");
+ node = make_memspace_state_node (reg,
+ diagnostics::state_graphs::node_kind::globals);
break;
case RK_CODE:
- e = make_memory_space_element ("Code");
+ node = make_memspace_state_node (reg,
+ diagnostics::state_graphs::node_kind::code);
break;
case RK_FUNCTION:
- e = std::make_unique<xml::element> ("function", false);
- // TODO
- break;
- case RK_LABEL:
- e = std::make_unique<xml::element> ("label", false);
+ node = make_memspace_state_node (reg,
+ diagnostics::state_graphs::node_kind::function);
// TODO
break;
+
case RK_STACK:
- e = std::make_unique<xml::element> ("stack", false);
+ node = make_memspace_state_node (reg,
+ diagnostics::state_graphs::node_kind::stack);
break;
case RK_HEAP:
- e = make_memory_space_element ("Heap");
+ node = make_memspace_state_node (reg,
+ diagnostics::state_graphs::node_kind::heap_);
break;
case RK_THREAD_LOCAL:
- e = make_memory_space_element ("Thread-local");
+ node = make_memspace_state_node (reg,
+ diagnostics::state_graphs::node_kind::thread_local_);
break;
case RK_ROOT:
- e = std::make_unique<xml::element> ("memory-regions", false);
+ gcc_unreachable ();
break;
case RK_SYMBOLIC:
- e = std::make_unique<xml::element> ("symbolic-region", false);
- // TODO
+ node = make_memspace_state_node (reg,
+ diagnostics::state_graphs::node_kind::other);
break;
+
case RK_DECL:
{
- e = std::make_unique<xml::element> ("variable", false);
+ node = make_state_node (diagnostics::state_graphs::node_kind::variable,
+ make_node_id (reg));
const decl_region &decl_reg
= static_cast<const decl_region &> (reg);
+ state_node_ref node_ref (*node);
{
pretty_printer pp;
pp_format_decoder (&pp) = default_tree_printer;
pp_printf (&pp, "%E", decl_reg.get_decl ());
- e->set_attr ("name", pp_formatted_text (&pp));
+ node_ref.set_name (pp_formatted_text (&pp));
}
- set_type_attr (*e, TREE_TYPE (decl_reg.get_decl ()));
+ set_type_attr (*node, TREE_TYPE (decl_reg.get_decl ()));
}
break;
+
case RK_FIELD:
- e = std::make_unique<xml::element> ("field", false);
- break;
case RK_ELEMENT:
- e = std::make_unique<xml::element> ("element", false);
+ /* These should be handled in populate_state_node_for_typed_region. */
+ gcc_unreachable ();
break;
+
+ case RK_LABEL:
case RK_OFFSET:
- e = std::make_unique<xml::element> ("offset-region", false);
- // TODO
- break;
case RK_SIZED:
- e = std::make_unique<xml::element> ("sized-region", false);
- // TODO
- break;
case RK_CAST:
- e = std::make_unique<xml::element> ("cast-region", false);
- // TODO
- break;
- case RK_HEAP_ALLOCATED:
- e = std::make_unique<xml::element> ("heap-buffer", false);
- set_attr_for_dynamic_extents (reg, *e);
- break;
- case RK_ALLOCA:
- e = std::make_unique<xml::element> ("alloca-buffer", false);
- set_attr_for_dynamic_extents (reg, *e);
- break;
case RK_STRING:
- e = std::make_unique<xml::element> ("string-region", false);
- // TODO
- break;
case RK_BIT_RANGE:
- e = std::make_unique<xml::element> ("RK_BIT_RANGE", false); // TODO
- break;
case RK_VAR_ARG:
- e = std::make_unique<xml::element> ("RK_VAR_ARG", false); // TODO
- break;
case RK_ERRNO:
- e = std::make_unique<xml::element> ("errno", false);
- break;
case RK_PRIVATE:
- e = std::make_unique<xml::element> ("RK_PRIVATE", false); // TODO
- break;
case RK_UNKNOWN:
- e = std::make_unique<xml::element> ("RK_UNKNOWN", false); // TODO
+ node = make_state_node (diagnostics::state_graphs::node_kind::other,
+ make_node_id (reg));
break;
- }
- gcc_assert (e);
- set_region_id_attr (*e, reg);
+ case RK_HEAP_ALLOCATED:
+ case RK_ALLOCA:
+ node = make_memspace_state_node (reg,
+ diagnostics::state_graphs::node_kind::dynalloc_buffer);
+ set_attr_for_dynamic_extents (reg, *node);
+ break;
+ }
+ gcc_assert (node);
if (reg.get_base_region () == ®)
if (!reg.get_type ())
if (search != m_types_for_untyped_regions.end ())
{
tree type_to_use = search->second;
- set_type_attr (*e, type_to_use);
+ set_type_attr (*node, type_to_use);
}
}
- return e;
+ return node;
}
void
-xml_state::create_elements_for_binding_cluster (const binding_cluster &cluster,
- bool create_all)
+analyzer_state_graph::
+create_state_nodes_for_binding_cluster (const binding_cluster &cluster,
+ bool create_all)
{
/* TODO:
- symbolic bindings
if (auto conc_key = key->dyn_cast_concrete_binding ())
conc_bindings[conc_key->get_bit_range ()] = svalue;
if (const region *reg = svalue->maybe_get_region ())
- get_or_create_element (*reg);
+ get_or_create_state_node (*reg);
}
- auto &e = get_or_create_element (*cluster.get_base_region ());
+ auto ref = get_or_create_state_node (*cluster.get_base_region ());
- e.add_child (create_element_for_conc_bindings (conc_bindings));
+ ref.m_node.add_child (create_state_node_for_conc_bindings (conc_bindings));
const region *typed_reg = cluster.get_base_region ();
if (!typed_reg->get_type ())
}
if (typed_reg->get_type ())
- populate_element_for_typed_region (e,
- *typed_reg,
- conc_bindings,
- create_all);
+ populate_state_node_for_typed_region (ref,
+ *typed_reg,
+ conc_bindings,
+ create_all);
else
{
// TODO
}
}
-std::unique_ptr<xml::element>
-xml_state::create_element_for_conc_bindings (const concrete_bindings_t &conc_bindings)
+std::unique_ptr<diagnostics::digraphs::node>
+analyzer_state_graph::create_state_node_for_conc_bindings (const concrete_bindings_t &conc_bindings)
{
- auto e = std::make_unique<xml::element> ("concrete-bindings", false);
+ auto node = make_state_node (diagnostics::state_graphs::node_kind::other,
+ make_node_id ("concrete-bindings"));
for (auto iter : conc_bindings)
{
const bit_range bits = iter.first;
const svalue *sval = iter.second;
- auto binding_element
- = std::make_unique<xml::element> ("binding", false);
- set_bits_attr (*binding_element, bits);
+ auto binding_state_node
+ = make_state_node (diagnostics::state_graphs::node_kind::other,
+ make_node_id ("binding"));
+ set_bits_attr (*binding_state_node, bits);
{
pretty_printer pp;
pp_format_decoder (&pp) = default_tree_printer;
sval->dump_to_pp (&pp, true);
- binding_element->set_attr ("value", pp_formatted_text (&pp));
- if (auto svalue_element = create_element_for_svalue (sval))
- binding_element->add_child (std::move (svalue_element));
+ binding_state_node->set_attr (STATE_NODE_PREFIX, "value",
+ pp_formatted_text (&pp));
}
- e->add_child (std::move (binding_element));
+ node->add_child (std::move (binding_state_node));
}
- return e;
+ return node;
}
// Try to get the bit_range of REG within its base region
bool
-xml_state::get_bit_range_within_base_region (const region ®,
- bit_range &out)
+analyzer_state_graph::get_bit_range_within_base_region (const region ®,
+ bit_range &out)
{
region_offset start_offset = reg.get_offset (&m_mgr);
if (!start_offset.concrete_p ())
}
void
-xml_state::populate_element_for_typed_region (xml::element &e,
- const region ®,
- const concrete_bindings_t &conc_bindings,
- bool create_all)
+analyzer_state_graph::
+populate_state_node_for_typed_region (state_node_ref node,
+ const region ®,
+ const concrete_bindings_t &conc_bindings,
+ bool create_all)
{
const_tree reg_type = reg.get_type ();
gcc_assert (reg_type);
- set_type_attr (e, reg_type);
+ set_type_attr (node, reg_type);
bit_range bits (0, 0);
if (get_bit_range_within_base_region (reg, bits))
{
- set_bits_attr (e, bits);
+ set_bits_attr (node, bits);
auto search = conc_bindings.find (bits);
if (search != conc_bindings.end ())
{
const svalue *bound_sval = search->second;
- if (auto svalue_element = create_element_for_svalue (bound_sval))
- {
- xml::printer xp (e);
- xp.push_tag ("value-of-region");
- xp.append (std::move (svalue_element));
- }
+ node.set_json_attr ("value", bound_sval->to_json ());
+ if (const region *dst_reg = bound_sval->maybe_get_region ())
+ m_pending_edges.push_back ({node, *dst_reg});
}
}
= m_mgr.get_element_region (®,
const_cast<tree> (element_type),
sval_index);
- if (show_child_element_for_child_region_p (*child_reg,
+ if (show_child_state_node_for_child_region_p (*child_reg,
conc_bindings,
create_all))
{
- // Here "element" is in the xml sense
- auto child_element
- = std::make_unique<xml::element> ("element", false);
- set_wi_attr (*child_element, "index", idx, UNSIGNED);
- set_region_id_attr (*child_element, *child_reg);
+ auto child_state_node
+ = make_state_node
+ (diagnostics::state_graphs::node_kind::element,
+ make_node_id (*child_reg));
+ set_wi_attr (*child_state_node, "index", idx, UNSIGNED);
+
// Recurse:
gcc_assert (element_type);
- populate_element_for_typed_region (*child_element,
- *child_reg,
- conc_bindings,
- create_all);
- e.add_child (std::move (child_element));
+ populate_state_node_for_typed_region (*child_state_node,
+ *child_reg,
+ conc_bindings,
+ create_all);
+ node.m_node.add_child (std::move (child_state_node));
}
}
}
const bit_range bits (0, item.m_bit_range.m_size_in_bits);
const region *child_reg
= m_mgr.get_bit_range (®, NULL_TREE, bits);
- if (show_child_element_for_child_region_p (*child_reg,
- conc_bindings,
- create_all))
+ if (show_child_state_node_for_child_region_p (*child_reg,
+ conc_bindings,
+ create_all))
{
- auto child_element
- = std::make_unique<xml::element> ("padding", false);
- set_wi_attr (*child_element, "num_bits",
+ auto child_state_node
+ = make_state_node
+ (diagnostics::state_graphs::node_kind::padding,
+ make_node_id (*child_reg));
+ set_wi_attr (*child_state_node, "num_bits",
item.m_bit_range.m_size_in_bits, SIGNED);
- e.add_child (std::move (child_element));
+ node.m_node.add_child (std::move (child_state_node));
}
}
else
const region *child_reg
= m_mgr.get_field_region (®,
const_cast<tree> (item.m_field));
- if (show_child_element_for_child_region_p (*child_reg,
+ if (show_child_state_node_for_child_region_p (*child_reg,
conc_bindings,
create_all))
{
- auto child_element
- = std::make_unique<xml::element> ("field", false);
+ auto child_state_node
+ = make_state_node
+ (diagnostics::state_graphs::node_kind::field,
+ make_node_id (*child_reg));
{
pretty_printer pp;
pp_format_decoder (&pp) = default_tree_printer;
pp_printf (&pp, "%D", item.m_field);
- child_element->set_attr ("name",
- pp_formatted_text (&pp));
+ child_state_node->set_attr (STATE_NODE_PREFIX, "name",
+ pp_formatted_text (&pp));
}
- set_region_id_attr (*child_element, *child_reg);
+
// Recurse:
- populate_element_for_typed_region (*child_element,
+ populate_state_node_for_typed_region (*child_state_node,
*child_reg,
conc_bindings,
create_all);
- e.add_child (std::move (child_element));
+ node.m_node.add_child (std::move (child_state_node));
}
}
}
}
void
-xml_state::set_attr_for_dynamic_extents (const region ®, xml::element &e)
+analyzer_state_graph::set_attr_for_dynamic_extents (const region ®,
+ state_node_ref node_ref)
{
const svalue *sval = m_state.m_region_model->get_dynamic_extents (®);
if (sval)
pp_wide_int (&pp, wi::to_wide (cst), UNSIGNED);
else
sval->dump_to_pp (&pp, true);
- e.set_attr ("dynamic-extents", pp_formatted_text (&pp));
+ node_ref.set_attr ("dynamic-extents", pp_formatted_text (&pp));
}
}
bool
-xml_state::
-show_child_element_for_child_region_p (const region ®,
+analyzer_state_graph::
+show_child_state_node_for_child_region_p (const region ®,
const concrete_bindings_t &conc_bindings,
bool create_all)
{
return false;
}
-std::unique_ptr<xml::element>
-xml_state::create_element_for_svalue (const svalue *sval)
+std::unique_ptr<diagnostics::digraphs::digraph>
+program_state::
+make_diagnostic_state_graph (const extrinsic_state &ext_state) const
{
- if (!sval)
- return nullptr;
-
- std::unique_ptr<xml::element> result;
- switch (sval->get_kind ())
- {
- default:
- gcc_unreachable ();
- case SK_REGION:
- {
- const region_svalue *region_sval = (const region_svalue *)sval;
- result
- = std::make_unique<xml::element> ("pointer-to-region", false);
- set_region_id_attr (*result, *region_sval->get_pointee ());
- }
- break;
- case SK_CONSTANT:
- {
- const constant_svalue *constant_sval = (const constant_svalue *)sval;
- result = std::make_unique<xml::element> ("constant", false);
- pretty_printer pp;
- pp_format_decoder (&pp) = default_tree_printer;
- pp_printf (&pp, "%E", constant_sval->get_constant ());
- result->set_attr ("value", pp_formatted_text (&pp));
- }
- break;
- case SK_UNKNOWN:
- result = std::make_unique<xml::element> ("unknown", false);
- break;
- case SK_POISONED:
- {
- const poisoned_svalue *poisoned_sval = (const poisoned_svalue *)sval;
- switch (poisoned_sval->get_poison_kind ())
- {
- default:
- gcc_unreachable ();
- case poison_kind::uninit:
- result = std::make_unique<xml::element> ("uninitialized", false);
- break;
- case poison_kind::freed:
- result = std::make_unique<xml::element> ("freed", false);
- break;
- case poison_kind::deleted:
- result = std::make_unique<xml::element> ("deleted", false);
- break;
- case poison_kind::popped_stack:
- result = std::make_unique<xml::element> ("popped-stack", false);
- break;
- }
- }
- break;
- case SK_SETJMP:
- {
- //const setjmp_svalue *setjmp_sval = (const setjmp_svalue *)sval;
- result = std::make_unique<xml::element> ("setjmp-buffer", false);
- // TODO
- }
- break;
- case SK_INITIAL:
- {
- const initial_svalue *initial_sval = (const initial_svalue *)sval;
- result = std::make_unique<xml::element> ("initial-value-of", false);
- set_region_id_attr (*result, *initial_sval->get_region ());
- }
- break;
- case SK_UNARYOP:
- {
- const unaryop_svalue *unaryop_sval = (const unaryop_svalue *)sval;
- result = std::make_unique<xml::element> ("unary-op", false);
- result->set_attr ("op", get_tree_code_name (unaryop_sval->get_op ()));
- result->add_child
- (create_element_for_svalue (unaryop_sval->get_arg ()));
- }
- break;
- case SK_BINOP:
- {
- const binop_svalue *binop_sval = (const binop_svalue *)sval;
- result = std::make_unique<xml::element> ("binary-op", false);
- result->set_attr ("op", get_tree_code_name (binop_sval->get_op ()));
- result->add_child (create_element_for_svalue (binop_sval->get_arg0 ()));
- result->add_child (create_element_for_svalue (binop_sval->get_arg1 ()));
- }
- break;
- case SK_SUB:
- {
- //const sub_svalue *sub_sval = (const sub_svalue *)sval;
- result = std::make_unique<xml::element> ("subregion-value", false);
- // TODO
- }
- break;
- case SK_REPEATED:
- {
- const repeated_svalue *repeated_sval = (const repeated_svalue *)sval;
- result = std::make_unique<xml::element> ("repeated-value", false);
- result->add_child
- (create_element_for_svalue (repeated_sval->get_outer_size ()));
- result->add_child
- (create_element_for_svalue (repeated_sval->get_inner_svalue ()));
- }
- break;
- case SK_BITS_WITHIN:
- {
- const bits_within_svalue *bits_within_sval
- = (const bits_within_svalue *)sval;
- result = std::make_unique<xml::element> ("bits-within", false);
- set_bits_attr (*result, bits_within_sval->get_bits ());
- result->add_child
- (create_element_for_svalue (bits_within_sval->get_inner_svalue ()));
- }
- break;
- case SK_UNMERGEABLE:
- {
- const unmergeable_svalue *unmergeable_sval
- = (const unmergeable_svalue *)sval;
- result = std::make_unique<xml::element> ("unmergeable", false);
- result->add_child
- (create_element_for_svalue (unmergeable_sval->get_arg ()));
- }
- break;
- case SK_PLACEHOLDER:
- {
- const placeholder_svalue *placeholder_sval
- = (const placeholder_svalue *)sval;
- result = std::make_unique<xml::element> ("placeholder", false);
- result->set_attr ("name", placeholder_sval->get_name ());
- }
- break;
- case SK_WIDENING:
- {
- //const widening_svalue *widening_sval = (const widening_svalue *)sval;
- result = std::make_unique<xml::element> ("iterating-value", false);
- // TODO
- }
- break;
- case SK_COMPOUND:
- {
- //const compound_svalue *compound_sval = (const compound_svalue *)sval;
- result = std::make_unique<xml::element> ("compound-value", false);
- // TODO
- }
- break;
- case SK_CONJURED:
- {
- //const conjured_svalue *conjured_sval = (const conjured_svalue *)sval;
- result = std::make_unique<xml::element> ("conjured-value", false);
- // TODO
- }
- break;
- case SK_ASM_OUTPUT:
- {
- /* const asm_output_svalue *asm_output_sval
- = (const asm_output_svalue *)sval; */
- result = std::make_unique<xml::element> ("asm-output", false);
- // TODO
- }
- break;
- case SK_CONST_FN_RESULT:
- {
- /* const const_fn_result_svalue *const_fn_result_sval
- = (const const_fn_result_svalue *)sval; */
- result = std::make_unique<xml::element> ("const-fn-result", false);
- // TODO
- }
- }
-
- if (result)
- {
- if (sval->get_type ())
- set_type_attr (*result, sval->get_type ());
-
- pretty_printer pp;
- pp_format_decoder (&pp) = default_tree_printer;
- sval->dump_to_pp (&pp, true);
- result->set_attr ("dump-text", pp_formatted_text (&pp));
- }
-
- return result;
-}
-
-std::unique_ptr<xml::document>
-program_state::make_xml (const extrinsic_state &ext_state) const
-{
- return std::make_unique<xml_state> (*this, ext_state);
-}
-
-void
-program_state::dump_xml_to_pp (const extrinsic_state &ext_state,
- pretty_printer *pp) const
-{
- auto doc = make_xml (ext_state);
- doc->write_as_xml (pp, 0, true);
-}
-
-void
-program_state::dump_xml_to_file (const extrinsic_state &ext_state,
- FILE *outf) const
-{
- pretty_printer pp;
- pp.set_output_stream (outf);
- dump_xml_to_pp (ext_state, &pp);
- pp_flush (&pp);
+ return std::make_unique<analyzer_state_graph> (*this, ext_state);
}
void
-program_state::dump_xml (const extrinsic_state &ext_state) const
+program_state::dump_sarif (const extrinsic_state &ext_state) const
{
- dump_xml_to_file (ext_state, stderr);
+ auto g = make_diagnostic_state_graph (ext_state);
+ g->dump ();
}
} // namespace ana
-/* XML documents for dumping state in an easier-to-read form.
+/* Creating diagnostic state graphs from ana::program_state.
Copyright (C) 2025 Free Software Foundation, Inc.
Contributed by David Malcolm <dmalcolm@redhat.com>.
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
-#ifndef GCC_ANALYZER_ANA_STATE_TO_XML_STATE_H
-#define GCC_ANALYZER_ANA_STATE_TO_XML_STATE_H
+#ifndef GCC_ANALYZER_ANA_STATE_TO_DIAGNOSTIC_STATE_H
+#define GCC_ANALYZER_ANA_STATE_TO_DIAGNOSTIC_STATE_H
-#include "xml.h"
+#include "diagnostic-state-graphs.h"
+#include "tree-logical-location.h"
namespace ana {
-class xml_state : public xml::document
+class analyzer_state_graph : public diagnostics::digraphs::digraph
{
public:
- xml_state (const program_state &state,
- const extrinsic_state &ext_state);
-
- xml::element &
- get_or_create_element (const region ®);
+ analyzer_state_graph (const program_state &state,
+ const extrinsic_state &ext_state);
+ diagnostics::state_graphs::state_node_ref
+ get_or_create_state_node (const region ®);
private:
- xml::element&
- create_and_add_element (const region ®);
-
- static std::unique_ptr<xml::element>
- make_memory_space_element (const char *label);
-
- std::unique_ptr<xml::element>
- create_element (const region ®);
+ struct pending_edge
+ {
+ diagnostics::state_graphs::state_node_ref m_src_node;
+ const region &m_dst_reg;
+ };
+
+ diagnostics::state_graphs::state_node_ref
+ create_and_add_state_node (const region ®);
+
+ std::unique_ptr<diagnostics::digraphs::node>
+ make_state_node (diagnostics::state_graphs::node_kind kind,
+ std::string id);
+
+ std::unique_ptr<diagnostics::digraphs::node>
+ make_memspace_state_node (const region ®,
+ enum diagnostics::state_graphs::node_kind kind);
+
+ std::unique_ptr<diagnostics::digraphs::node>
+ create_state_node (const region ®);
/* Spatially sorted concrete bindings. */
typedef std::map<bit_range, const svalue *> concrete_bindings_t;
void
- create_elements_for_binding_cluster (const binding_cluster &cluster,
- bool create_all);
+ create_state_nodes_for_binding_cluster (const binding_cluster &cluster,
+ bool create_all);
- std::unique_ptr<xml::element>
- create_element_for_conc_bindings (const concrete_bindings_t &conc_bindings);
+ std::unique_ptr<diagnostics::digraphs::node>
+ create_state_node_for_conc_bindings (const concrete_bindings_t &conc_bindings);
// Try to get the bit_range of REG within its base region
bool
bit_range &out);
void
- populate_element_for_typed_region (xml::element &e,
- const region ®,
- const concrete_bindings_t &conc_bindings,
- bool create_all);
+ populate_state_node_for_typed_region (diagnostics::state_graphs::state_node_ref,
+ const region ®,
+ const concrete_bindings_t &conc_bindings,
+ bool create_all);
void
- set_attr_for_dynamic_extents (const region ®, xml::element &e);
+ set_attr_for_dynamic_extents (const region ®,
+ diagnostics::state_graphs::state_node_ref);
bool
- show_child_element_for_child_region_p (const region ®,
- const concrete_bindings_t &conc_bindings,
- bool create_all);
+ show_child_state_node_for_child_region_p (const region ®,
+ const concrete_bindings_t &conc_bindings,
+ bool create_all);
+
+ std::unique_ptr<diagnostics::digraphs::node>
+ create_state_node_for_svalue (const svalue *sval);
- std::unique_ptr<xml::element>
- create_element_for_svalue (const svalue *sval);
+ std::string make_node_id (const region ®);
+ std::string make_node_id (const char *prefix);
+ tree_logical_location_manager m_logical_loc_mgr;
const program_state &m_state;
const extrinsic_state &m_ext_state;
region_model_manager &m_mgr;
- xml::element *m_root;
- std::map<const region *, xml::element *> m_region_to_element_map;
+ std::map<const region *, diagnostics::digraphs::node *> m_region_to_state_node_map;
std::map<const region *, tree> m_types_for_untyped_regions;
+ unsigned m_next_id;
+ std::vector<pending_edge> m_pending_edges;
};
} // namespace ana
-#endif /* GCC_ANALYZER_ANA_STATE_TO_XML_STATE_H */
+#endif /* GCC_ANALYZER_ANA_STATE_TO_DIAGNOSTIC_STATE_H */
#include "inlining-iterator.h"
#include "tree-logical-location.h"
#include "diagnostic-format-sarif.h"
-#include "xml.h"
+#include "diagnostic-state-graphs.h"
#include "analyzer/analyzer-logging.h"
#include "analyzer/sm.h"
print_desc (*pp.get ());
}
-std::unique_ptr<xml::document>
-checker_event::maybe_make_xml_state (bool debug) const
+std::unique_ptr<diagnostics::digraphs::digraph>
+checker_event::maybe_make_diagnostic_state_graph (bool debug) const
{
const program_state *state = get_program_state ();
if (!state)
gcc_assert (m_path);
const extrinsic_state &ext_state = m_path->get_ext_state ();
- auto result = state->make_xml (ext_state);
+ auto result = state->make_diagnostic_state_graph (ext_state);
if (debug)
{
pretty_printer pp;
text_art::theme *theme = global_dc->get_diagram_theme ();
text_art::dump_to_pp (*state, theme, &pp);
- result->add_comment (pp_formatted_text (&pp));
+ result->set_attr (STATE_GRAPH_PREFIX,
+ "analyzer/program_state/",
+ pp_formatted_text (&pp));
}
return result;
#include "tree-logical-location.h"
#include "analyzer/program-state.h"
#include "analyzer/event-loc-info.h"
+#include "diagnostic-digraphs.h"
namespace ana {
virtual bool is_function_entry_p () const { return false; }
virtual bool is_return_p () const { return false; }
+ std::unique_ptr<diagnostics::digraphs::digraph>
+ maybe_make_diagnostic_state_graph (bool debug) const final override;
+
virtual const program_state *
get_program_state () const { return nullptr; }
- std::unique_ptr<xml::document>
- maybe_make_xml_state (bool debug) const final override;
-
/* For use with %@. */
const diagnostic_event_id_t *get_id_ptr () const
{
state->dump (eg.get_ext_state (), true);
return;
}
- else if (is_special_named_call_p (call, "__analyzer_dump_xml", 0))
+ else if (is_special_named_call_p (call, "__analyzer_dump_sarif", 0))
{
- state->dump_xml (eg.get_ext_state ());
+ state->dump_sarif (eg.get_ext_state ());
return;
}
else if (is_special_named_call_p (call, "__analyzer_dump_dot", 0))
#include "cgraph.h"
#include "digraph.h"
#include "diagnostic-event-id.h"
-#include "diagnostic-state.h"
+#include "diagnostic-state-graphs.h"
#include "graphviz.h"
#include "text-art/tree-widget.h"
void
program_state::dump_dot (const extrinsic_state &ext_state) const
{
- auto doc = make_xml (ext_state);
- auto graph = make_dot_graph_from_xml_state (*doc);
+ auto state_graph = make_diagnostic_state_graph (ext_state);
+
+ gcc_assert (global_dc);
+ auto logical_loc_mgr = global_dc->get_logical_location_manager ();
+ gcc_assert (logical_loc_mgr);
+
+ auto graph = diagnostics::state_graphs::make_dot_graph (*state_graph,
+ *logical_loc_mgr);
pretty_printer pp;
dot::writer w (pp);
#include "analyzer/store.h"
-namespace xml { class document; }
-
namespace ana {
/* Data shared by all program_state instances. */
void dump (const extrinsic_state &ext_state, bool simple) const;
void dump () const;
- std::unique_ptr<xml::document> make_xml (const extrinsic_state &ext_state) const;
- void dump_xml_to_pp (const extrinsic_state &ext_state, pretty_printer *pp) const;
- void dump_xml_to_file (const extrinsic_state &ext_state, FILE *outf) const;
- void dump_xml (const extrinsic_state &ext_state) const;
- void dump_dot (const extrinsic_state &ext_state) const;
+ std::unique_ptr<diagnostics::digraphs::digraph>
+ make_diagnostic_state_graph (const extrinsic_state &ext_state) const;
+
+ void
+ dump_sarif (const extrinsic_state &ext_state) const;
+
+ void
+ dump_dot (const extrinsic_state &ext_state) const;
std::unique_ptr<json::object>
to_json (const extrinsic_state &ext_state) const;
const extrinsic_state &ext_state) const;
void
- add_state_to_xml (xml_state &out_xml,
- const svalue &sval,
- state_machine::state_t state) const final override;
+ add_state_to_state_graph (analyzer_state_graph &out_state_graph,
+ const svalue &sval,
+ state_machine::state_t state) const final override;
standard_deallocator_set m_free;
standard_deallocator_set m_scalar_delete;
smap->set_state (model, new_ptr_sval, m_free.m_nonnull, nullptr, ext_state);
}
+static enum diagnostics::state_graphs::node_dynalloc_state
+get_dynalloc_state_for_state (enum resource_state rs)
+{
+ switch (rs)
+ {
+ default:
+ gcc_unreachable ();
+ case RS_START:
+ case RS_NULL:
+ case RS_NON_HEAP:
+ case RS_STOP:
+ return diagnostics::state_graphs::node_dynalloc_state::unknown;
+
+ case RS_ASSUMED_NON_NULL:
+ return diagnostics::state_graphs::node_dynalloc_state::nonnull;
+
+ case RS_UNCHECKED:
+ return diagnostics::state_graphs::node_dynalloc_state::unchecked;
+ case RS_NONNULL:
+ return diagnostics::state_graphs::node_dynalloc_state::nonnull;
+ case RS_FREED:
+ return diagnostics::state_graphs::node_dynalloc_state::freed;
+ }
+}
+
void
-malloc_state_machine::add_state_to_xml (xml_state &out_xml,
- const svalue &sval,
- state_machine::state_t state) const
+malloc_state_machine::
+add_state_to_state_graph (analyzer_state_graph &out_state_graph,
+ const svalue &sval,
+ state_machine::state_t state) const
{
if (const region *reg = sval.maybe_get_region ())
{
- auto ®_element = out_xml.get_or_create_element (*reg);
+ auto reg_node = out_state_graph.get_or_create_state_node (*reg);
auto alloc_state = as_a_allocation_state (state);
gcc_assert (alloc_state);
- reg_element.set_attr ("dynamic-alloc-state", state->get_name ());
+ reg_node.set_dynalloc_state
+ (get_dynalloc_state_for_state (alloc_state->m_rs));
if (alloc_state->m_deallocators)
{
pretty_printer pp;
alloc_state->m_deallocators->dump_to_pp (&pp);
- reg_element.set_attr ("expected-deallocators", pp_formatted_text (&pp));
+ reg_node.m_node.set_attr (STATE_NODE_PREFIX,
+ "expected-deallocators",
+ pp_formatted_text (&pp));
}
if (alloc_state->m_deallocator)
- reg_element.set_attr ("deallocator",
- alloc_state->m_deallocator->m_name);
+ reg_node.m_node.set_attr (STATE_NODE_PREFIX,
+ "deallocator",
+ alloc_state->m_deallocator->m_name);
}
}
}
void
-state_machine::add_state_to_xml (xml_state &out_xml,
- const svalue &sval,
- state_machine::state_t state) const
+state_machine::add_state_to_state_graph (analyzer_state_graph &out_state_graph,
+ const svalue &sval,
+ state_machine::state_t state) const
{
// no-op
}
void
-state_machine::add_global_state_to_xml (xml_state &out_xml,
- state_machine::state_t state) const
+state_machine::add_global_state_to_state_graph (analyzer_state_graph &out_state_graph,
+ state_machine::state_t state) const
{
// no-op
}
class state_machine;
class sm_context;
class pending_diagnostic;
-class xml_state;
+class analyzer_state_graph;
extern bool any_pointer_p (tree expr);
extern bool any_pointer_p (const svalue *sval);
state_t get_start_state () const { return m_start; }
virtual void
- add_state_to_xml (xml_state &out_xml,
- const svalue &sval,
- state_machine::state_t state) const;
+ add_state_to_state_graph (analyzer_state_graph &out_state_graph,
+ const svalue &sval,
+ state_machine::state_t state) const;
virtual void
- add_global_state_to_xml (xml_state &out_xml,
- state_machine::state_t state) const;
+ add_global_state_to_state_graph (analyzer_state_graph &out_state_graph,
+ state_machine::state_t state) const;
protected:
state_t add_state (const char *name);
--- /dev/null
+/* Directed graphs associated with a diagnostic.
+ Copyright (C) 2025 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 "diagnostic-digraphs.h"
+#include "diagnostic-format-sarif.h"
+
+#include "selftest.h"
+
+using digraph = diagnostics::digraphs::digraph;
+using digraph_node = diagnostics::digraphs::node;
+using digraph_edge = diagnostics::digraphs::edge;
+
+namespace {
+
+class conversion_to_dot
+{
+public:
+ std::unique_ptr<dot::graph>
+ make_dot_graph_from_diagnostic_graph (const digraph &);
+
+ std::unique_ptr<dot::stmt>
+ make_dot_node_from_digraph_node (const digraph_node &);
+
+ std::unique_ptr<dot::edge_stmt>
+ make_dot_edge_from_digraph_edge (const digraph_edge &);
+
+ dot::id
+ get_dot_id_for_node (const digraph_node &);
+
+ bool
+ has_edges_p (const digraph_node &);
+
+private:
+ std::set<const digraph_node *> m_nodes_with_edges;
+ std::map<const digraph_node *, dot::stmt *> m_node_map;
+};
+
+} // anonymous namespace
+
+// class conversion_to_dot
+
+std::unique_ptr<dot::graph>
+conversion_to_dot::
+make_dot_graph_from_diagnostic_graph (const diagnostics::digraphs::digraph &input_graph)
+{
+ auto output_graph = std::make_unique<dot::graph> ();
+
+ if (const char *description = input_graph.get_description ())
+ output_graph->m_stmt_list.add_attr (dot::id ("label"),
+ dot::id (description));
+
+ const int num_nodes = input_graph.get_num_nodes ();
+ const int num_edges = input_graph.get_num_edges ();
+
+ /* Determine which nodes have in-edges and out-edges. */
+ for (int i = 0; i < num_edges; ++i)
+ {
+ const digraph_edge &input_edge = input_graph.get_edge (i);
+ m_nodes_with_edges.insert (&input_edge.get_src_node ());
+ m_nodes_with_edges.insert (&input_edge.get_dst_node ());
+ }
+
+ for (int i = 0; i < num_nodes; ++i)
+ {
+ const digraph_node &input_node = input_graph.get_node (i);
+ auto dot_node_stmt = make_dot_node_from_digraph_node (input_node);
+ output_graph->m_stmt_list.add_stmt (std::move (dot_node_stmt));
+ }
+
+ for (int i = 0; i < num_edges; ++i)
+ {
+ const digraph_edge &input_edge = input_graph.get_edge (i);
+ auto dot_edge_stmt = make_dot_edge_from_digraph_edge (input_edge);
+ output_graph->m_stmt_list.add_stmt (std::move (dot_edge_stmt));
+ }
+
+ return output_graph;
+}
+
+std::unique_ptr<dot::stmt>
+conversion_to_dot::
+make_dot_node_from_digraph_node (const diagnostics::digraphs::node &input_node)
+{
+ dot::id dot_id (get_dot_id_for_node (input_node));
+
+ /* For now, we can only do either edges or children, not both
+ ...but see https://graphviz.org/docs/attrs/compound/ */
+
+ if (has_edges_p (input_node))
+ {
+ auto output_node
+ = std::make_unique<dot::node_stmt> (std::move (dot_id));
+ m_node_map[&input_node] = output_node.get ();
+ if (const char *label = input_node.get_label ())
+ output_node->set_label (dot::id (label));
+ return output_node;
+ }
+ else
+ {
+ auto output_node = std::make_unique<dot::subgraph> (std::move (dot_id));
+ m_node_map[&input_node] = output_node.get ();
+ if (const char *label = input_node.get_label ())
+ output_node->add_attr (dot::id ("label"), dot::id (label));
+ const int num_children = input_node.get_num_children ();
+ for (int i = 0; i < num_children; ++i)
+ {
+ const digraph_node &input_child = input_node.get_child (i);
+ auto dot_child_stmt = make_dot_node_from_digraph_node (input_child);
+ output_node->m_stmt_list.add_stmt (std::move (dot_child_stmt));
+ }
+ return output_node;
+ }
+}
+
+std::unique_ptr<dot::edge_stmt>
+conversion_to_dot::
+make_dot_edge_from_digraph_edge (const digraph_edge &input_edge)
+{
+ const digraph_node &src_dnode = input_edge.get_src_node ();
+ const digraph_node &dst_dnode = input_edge.get_dst_node ();
+ auto output_edge
+ = std::make_unique<dot::edge_stmt>
+ (get_dot_id_for_node (src_dnode),
+ get_dot_id_for_node (dst_dnode));
+ if (const char *label = input_edge.get_label ())
+ output_edge->set_label (dot::id (label));
+ return output_edge;
+}
+
+dot::id
+conversion_to_dot::get_dot_id_for_node (const digraph_node &input_node)
+{
+ if (has_edges_p (input_node))
+ return input_node.get_id ();
+ else
+ return std::string ("cluster_") + input_node.get_id ();
+}
+
+bool
+conversion_to_dot::has_edges_p (const digraph_node &input_node)
+{
+ return m_nodes_with_edges.find (&input_node) != m_nodes_with_edges.end ();
+}
+
+// class object
+
+const char *
+diagnostics::digraphs::object::
+get_attr (const char *key_prefix, const char *key) const
+{
+ if (!m_property_bag)
+ return nullptr;
+ std::string prefixed_key = std::string (key_prefix) + key;
+ if (json::value *jv = m_property_bag->get (prefixed_key.c_str ()))
+ if (json::string *jstr = jv->dyn_cast_string ())
+ return jstr->get_string ();
+ return nullptr;
+}
+
+void
+diagnostics::digraphs::object::
+set_attr (const char *key_prefix, const char *key, const char *value)
+{
+ set_json_attr (key_prefix, key, std::make_unique<json::string> (value));
+}
+
+void
+diagnostics::digraphs::object::
+set_json_attr (const char *key_prefix, const char *key, std::unique_ptr<json::value> value)
+{
+ std::string prefixed_key = std::string (key_prefix) + key;
+ if (!m_property_bag)
+ m_property_bag = std::make_unique<json::object> ();
+ m_property_bag->set (prefixed_key.c_str (), std::move (value));
+}
+
+// class digraph
+
+DEBUG_FUNCTION void
+diagnostics::digraphs::digraph::dump () const
+{
+ make_json_sarif_graph ()->dump ();
+}
+
+std::unique_ptr<json::object>
+diagnostics::digraphs::digraph::make_json_sarif_graph () const
+{
+ return make_sarif_graph (*this, nullptr, nullptr);
+}
+
+std::unique_ptr<dot::graph>
+diagnostics::digraphs::digraph::make_dot_graph () const
+{
+ conversion_to_dot to_dot;
+ return to_dot.make_dot_graph_from_diagnostic_graph (*this);
+}
+
+std::unique_ptr<diagnostics::digraphs::digraph>
+diagnostics::digraphs::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<diagnostics::digraphs::node *, diagnostics::digraphs::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
+diagnostics::digraphs::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
+diagnostics::digraphs::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;
+ }
+}
+
+// class node
+
+DEBUG_FUNCTION void
+diagnostics::digraphs::node::dump () const
+{
+ to_json_sarif_node ()->dump ();
+}
+
+std::unique_ptr<json::object>
+diagnostics::digraphs::node::to_json_sarif_node () const
+{
+ return make_sarif_node (*this, nullptr, nullptr);
+}
+
+std::unique_ptr<diagnostics::digraphs::node>
+diagnostics::digraphs::node::clone (digraph &new_graph,
+ std::map<node *, node *> &node_mapping) const
+{
+ auto result
+ = std::make_unique<diagnostics::digraphs::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);
+}
+
+// class lazy_digraph
+
+const diagnostics::digraphs::digraph &
+diagnostics::digraphs::lazy_digraph::get_or_create_digraph () const
+{
+ if (!m_digraph)
+ m_digraph = create_digraph ();
+ gcc_assert (m_digraph);
+ return *m_digraph;
+}
+
+// class lazy_digraphs
+
+const std::vector<std::unique_ptr<diagnostics::digraphs::digraph>> &
+diagnostics::digraphs::lazy_digraphs::get_or_create_digraphs () const
+{
+ if (!m_digraphs)
+ m_digraphs = create_digraphs ();
+ gcc_assert (m_digraphs);
+ return *m_digraphs;
+}
+
+#if CHECKING_P
+
+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_attr (KEY_PREFIX, "date", "1066");
+
+ auto a = std::make_unique<digraph_node> (*g, "a");
+ auto b = std::make_unique<digraph_node> (*g, "b");
+ b->set_attr (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_attr (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"));
+ }
+}
+
+/* Run all of the selftests within this file. */
+
+void
+diagnostic_digraphs_cc_tests ()
+{
+ test_empty_graph ();
+ test_simple_graph ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
--- /dev/null
+/* Directed graphs associated with a diagnostic.
+ Copyright (C) 2025 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/>. */
+
+#ifndef GCC_DIAGNOSTIC_DIGRAPHS_H
+#define GCC_DIAGNOSTIC_DIGRAPHS_H
+
+#include "json.h"
+#include "logical-location.h"
+
+class graphviz_out;
+
+class sarif_graph;
+class sarif_node;
+class sarif_edge;
+
+namespace dot { class graph; }
+
+namespace diagnostics {
+namespace digraphs {
+
+/* A family of classes: digraph, node, and edge, closely related to
+ SARIF's graph, node, and edge types (SARIF v2.1.0 sections 3.39-3.41).
+
+ Nodes can have child nodes, allowing for arbitrarily deep nesting.
+ Edges can be between any pair of nodes (potentially at different
+ nesting levels).
+
+ Digraphs, nodes, and edges also optionally have a JSON property bag,
+ allowing round-tripping of arbitrary key/value pairs through SARIF. */
+
+class digraph;
+class node;
+class edge;
+
+/* A base class for digraph, node, and edge to allow them to have
+ an optional JSON property bag. */
+
+class object
+{
+public:
+ const char *
+ get_attr (const char *key_prefix,
+ const char *key) const;
+
+ void
+ set_attr (const char *key_prefix,
+ const char *key,
+ const char *value);
+
+ void
+ set_json_attr (const char *key_prefix,
+ const char *key,
+ std::unique_ptr<json::value> value);
+
+ json::object *
+ get_property_bag () const { return m_property_bag.get (); }
+
+ void
+ set_property_bag (std::unique_ptr<json::object> property_bag)
+ {
+ m_property_bag = std::move (property_bag);
+ }
+
+private:
+ std::unique_ptr<json::object> m_property_bag;
+};
+
+// A directed graph, corresponding to SARIF v2.1.0 section 3.39.
+
+class digraph : public object
+{
+ public:
+ friend class node;
+ friend class edge;
+
+ digraph () : m_next_edge_id_index (0) {}
+ virtual ~digraph () {}
+
+ const char *
+ get_description () const
+ {
+ if (!m_description)
+ return nullptr;
+ return m_description->c_str ();
+ }
+
+ void
+ set_description (const char *desc)
+ {
+ if (desc)
+ m_description = std::make_unique<std::string> (desc);
+ else
+ m_description = nullptr;
+ }
+
+ node *
+ get_node_by_id (const char *id) const
+ {
+ auto iter = m_id_to_node_map.find (id);
+ if (iter == m_id_to_node_map.end ())
+ return nullptr;
+ return iter->second;
+ }
+
+ edge *
+ get_edge_by_id (const char *id) const
+ {
+ auto iter = m_id_to_edge_map.find (id);
+ if (iter == m_id_to_edge_map.end ())
+ return nullptr;
+ return iter->second;
+ }
+
+ size_t
+ get_num_nodes () const
+ {
+ return m_nodes.size ();
+ }
+
+ node &
+ get_node (size_t idx) const
+ {
+ return *m_nodes[idx].get ();
+ }
+
+ size_t
+ get_num_edges () const
+ {
+ return m_edges.size ();
+ }
+
+ edge &
+ get_edge (size_t idx) const
+ {
+ return *m_edges[idx].get ();
+ }
+
+ void
+ dump () const;
+
+ std::unique_ptr<json::object>
+ make_json_sarif_graph () const;
+
+ std::unique_ptr<dot::graph>
+ make_dot_graph () const;
+
+ void
+ add_node (std::unique_ptr<node> n)
+ {
+ gcc_assert (n);
+ m_nodes.push_back (std::move (n));
+ }
+
+ void
+ add_edge (std::unique_ptr<edge> e)
+ {
+ gcc_assert (e);
+ m_edges.push_back (std::move (e));
+ }
+
+ void
+ add_edge (const char *id,
+ node &src_node,
+ node &dst_node,
+ const char *label = nullptr);
+
+ std::unique_ptr<digraph> clone () const;
+
+ private:
+ void
+ add_node_id (std::string node_id, node &new_node)
+ {
+ m_id_to_node_map.insert ({std::move (node_id), &new_node});
+ }
+ void
+ add_edge_id (std::string edge_id, edge &new_edge)
+ {
+ m_id_to_edge_map.insert ({std::move (edge_id), &new_edge});
+ }
+
+ std::string
+ make_edge_id (const char *edge_id);
+
+ std::unique_ptr<std::string> m_description;
+ std::map<std::string, node *> m_id_to_node_map;
+ std::map<std::string, edge *> m_id_to_edge_map;
+ std::vector<std::unique_ptr<node>> m_nodes;
+ std::vector<std::unique_ptr<edge>> m_edges;
+ size_t m_next_edge_id_index;
+};
+
+// A node in a directed graph, corresponding to SARIF v2.1.0 section 3.40.
+
+class node : public object
+{
+ public:
+ virtual ~node () {}
+
+ node (digraph &g, std::string id)
+ : m_id (id),
+ m_physical_loc (UNKNOWN_LOCATION)
+ {
+ g.add_node_id (std::move (id), *this);
+ }
+ node (const node &) = delete;
+
+ std::string
+ get_id () const { return m_id; }
+
+ const char *
+ get_label () const
+ {
+ if (!m_label)
+ return nullptr;
+ return m_label->c_str ();
+ }
+
+ void
+ set_label (const char *label)
+ {
+ if (label)
+ m_label = std::make_unique<std::string> (label);
+ else
+ m_label = nullptr;
+ }
+
+ size_t
+ get_num_children () const { return m_children.size (); }
+
+ node &
+ get_child (size_t idx) const { return *m_children[idx].get (); }
+
+ void
+ add_child (std::unique_ptr<node> child)
+ {
+ gcc_assert (child);
+ m_children.push_back (std::move (child));
+ }
+
+ location_t
+ get_physical_loc () const
+ {
+ return m_physical_loc;
+ }
+
+ void
+ set_physical_loc (location_t physical_loc)
+ {
+ m_physical_loc = physical_loc;
+ }
+
+ logical_location
+ get_logical_loc () const
+ {
+ return m_logical_loc;
+ }
+
+ void
+ set_logical_loc (logical_location logical_loc)
+ {
+ m_logical_loc = logical_loc;
+ }
+
+ void print (graphviz_out &gv) const;
+
+ void
+ dump () const;
+
+ std::unique_ptr<json::object>
+ to_json_sarif_node () const;
+
+ std::unique_ptr<node>
+ clone (digraph &new_graph,
+ std::map<node *, node *> &node_mapping) const;
+
+ private:
+ std::string m_id;
+ std::unique_ptr<std::string> m_label;
+ std::vector<std::unique_ptr<node>> m_children;
+ location_t m_physical_loc;
+ logical_location m_logical_loc;
+};
+
+// An edge in a directed graph, corresponding to SARIF v2.1.0 section 3.41.
+
+class edge : public object
+{
+ public:
+ virtual ~edge () {}
+
+ /* SARIF requires us to provide unique edge IDs within a graph,
+ but otherwise we don't need them.
+ Pass in nullptr for the id to get the graph to generate a unique
+ edge id for us. */
+ edge (digraph &g,
+ const char *id,
+ node &src_node,
+ node &dst_node)
+ : m_id (g.make_edge_id (id)),
+ m_src_node (src_node),
+ m_dst_node (dst_node)
+ {
+ g.add_edge_id (m_id, *this);
+ }
+
+ std::string
+ get_id () const { return m_id; }
+
+ const char *
+ get_label () const
+ {
+ if (!m_label)
+ return nullptr;
+ return m_label->c_str ();
+ }
+
+ void
+ set_label (const char *label)
+ {
+ if (label)
+ m_label = std::make_unique<std::string> (label);
+ else
+ m_label = nullptr;
+ }
+
+ node &
+ get_src_node () const { return m_src_node; }
+
+ node &
+ get_dst_node () const { return m_dst_node; }
+
+ void
+ dump () const;
+
+ std::unique_ptr<json::object>
+ to_json_sarif_edge () const;
+
+ std::unique_ptr<edge>
+ clone (digraph &new_graph,
+ const std::map<diagnostics::digraphs::node *, diagnostics::digraphs::node *> &node_mapping) const;
+
+private:
+ std::string m_id;
+ std::unique_ptr<std::string> m_label;
+ node &m_src_node;
+ node &m_dst_node;
+};
+
+/* Abstract base class for lazily creating
+ a digraph on demand.
+
+ This allows us to avoid the work of creating the digraph for
+ the common case where we just have a text sink. */
+
+class lazy_digraph
+{
+public:
+ virtual ~lazy_digraph () {}
+
+ const digraph &
+ get_or_create_digraph () const;
+
+private:
+ virtual std::unique_ptr<digraph>
+ create_digraph () const = 0;
+
+ mutable std::unique_ptr<digraph> m_digraph;
+};
+
+/* Abstract base class for lazily creating a collection of
+ digraphs on demand.
+
+ This allows us to avoid the work of creating the digraphs for
+ the common case where we just have a text sink. */
+
+class lazy_digraphs
+{
+public:
+ virtual ~lazy_digraphs () {}
+
+ const std::vector<std::unique_ptr<digraph>> &
+ get_or_create_digraphs () const;
+
+private:
+ virtual std::unique_ptr<std::vector<std::unique_ptr<digraph>>>
+ create_digraphs () const = 0;
+
+ mutable std::unique_ptr<std::vector<std::unique_ptr<digraph>>> m_digraphs;
+};
+
+} // namespace digraphs
+} // namespace diagnostics
+
+#endif /* ! GCC_DIAGNOSTIC_DIGRAPHS_H */
#include "diagnostic-format.h"
#include "diagnostic-format-html.h"
#include "diagnostic-format-text.h"
+#include "diagnostic-format-sarif.h"
#include "diagnostic-output-file.h"
#include "diagnostic-buffer.h"
#include "diagnostic-path.h"
#include "intl.h"
#include "xml.h"
#include "xml-printer.h"
-#include "diagnostic-state.h"
+#include "diagnostic-digraphs.h"
+#include "diagnostic-state-graphs.h"
#include "graphviz.h"
#include "json.h"
#include "selftest-xml.h"
: m_css (true),
m_javascript (true),
m_show_state_diagrams (false),
- m_show_state_diagram_xml (false),
- m_show_state_diagram_dot_src (false)
+ m_show_state_diagrams_sarif (false),
+ m_show_state_diagrams_dot_src (false)
{
}
diagnostic_t orig_diag_kind,
diagnostic_html_format_buffer *buffer);
void emit_diagram (const diagnostic_diagram &diagram);
+ void emit_global_graph (const diagnostics::digraphs::lazy_digraph &);
+
void end_group ();
std::unique_ptr<xml::element> take_current_diagnostic ()
void
pop_nesting_level ();
+ void
+ add_graph (const diagnostics::digraphs::digraph &dg,
+ xml::element &parent_element);
+
diagnostic_context &m_context;
pretty_printer *m_printer;
const line_maps *m_line_maps;
std::unique_ptr<xml::document> m_document;
xml::element *m_head_element;
xml::element *m_title_element;
+ xml::element *m_body_element;
xml::element *m_diagnostics_element;
std::unique_ptr<xml::element> m_cur_diagnostic_element;
std::vector<xml::element *> m_cur_nesting_levels;
m_logical_loc_mgr (nullptr),
m_head_element (nullptr),
m_title_element (nullptr),
+ m_body_element (nullptr),
m_diagnostics_element (nullptr),
m_next_diag_id (0),
m_last_location (UNKNOWN_LOCATION),
{
xml::auto_print_element body (xp, "body");
+ m_body_element = xp.get_insertion_point ();
{
auto diagnostics_element = make_div ("gcc-diagnostic-list");
m_diagnostics_element = diagnostics_element.get ();
if (!m_html_gen_opts.m_show_state_diagrams)
return nullptr;
- /* Get XML state document; if we're going to print it later, also request
+ if (!m_logical_loc_mgr)
+ return nullptr;
+
+ /* Get state graph; if we're going to print it later, also request
the debug version. */
- auto xml_state
- = event.maybe_make_xml_state (m_html_gen_opts.m_show_state_diagram_xml);
- if (!xml_state)
+ auto state_graph
+ = event.maybe_make_diagnostic_state_graph
+ (m_html_gen_opts.m_show_state_diagrams_sarif);
+ if (!state_graph)
return nullptr;
// Convert it to .dot AST
- auto graph = make_dot_graph_from_xml_state (*xml_state);
- gcc_assert (graph);
+ auto dot_graph
+ = diagnostics::state_graphs::make_dot_graph (*state_graph,
+ *m_logical_loc_mgr);
+ gcc_assert (dot_graph);
auto wrapper = std::make_unique<xml::element> ("div", false);
xml::printer xp (*wrapper);
- if (m_html_gen_opts.m_show_state_diagram_xml)
+ if (m_html_gen_opts.m_show_state_diagrams_sarif)
{
- // For debugging, show the XML src inline:
+ // For debugging, show the SARIF src inline:
pretty_printer pp;
- xml_state->write_as_xml (&pp, 0, true);
+ state_graph->make_json_sarif_graph ()->print (&pp, true);
print_pre_source (xp, pp_formatted_text (&pp));
}
- if (m_html_gen_opts.m_show_state_diagram_dot_src)
+ if (m_html_gen_opts.m_show_state_diagrams_dot_src)
{
// For debugging, show the dot src inline:
pretty_printer pp;
dot::writer w (pp);
- graph->print (w);
+ dot_graph->print (w);
print_pre_source (xp, pp_formatted_text (&pp));
}
// Turn the .dot into SVG and splice into place
- auto svg = dot::make_svg_from_graph (*graph);
+ auto svg = dot::make_svg_from_graph (*dot_graph);
if (svg)
xp.append (std::move (svg));
gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
+ // Try to display any per-diagnostic graphs
+ if (diagnostic.metadata)
+ if (auto ldg = diagnostic.metadata->get_lazy_digraphs ())
+ {
+ auto &digraphs = ldg->get_or_create_digraphs ();
+ for (auto &dg : digraphs)
+ add_graph (*dg, *xp.get_insertion_point ());
+ }
+
if (auto patch_element = make_element_for_patch (diagnostic))
{
xp.push_tag ("div");
// TODO: currently a no-op
}
+void
+html_builder::add_graph (const diagnostics::digraphs::digraph &dg,
+ xml::element &parent_element)
+{
+ if (auto dot_graph = dg.make_dot_graph ())
+ if (auto svg_element = dot::make_svg_from_graph (*dot_graph))
+ {
+ auto div = std::make_unique<xml::element> ("div", false);
+ div->set_attr ("class", "gcc-directed-graph");
+ xml::printer xp (*div);
+ if (const char *description = dg.get_description ())
+ {
+ xp.push_tag ("h2", true);
+ xp.add_text (description);
+ xp.pop_tag ("h2");
+ }
+ xp.append (std::move (svg_element));
+ parent_element.add_child (std::move (div));
+ }
+}
+
+void
+html_builder::emit_global_graph (const diagnostics::digraphs::lazy_digraph &ldg)
+{
+ auto &dg = ldg.get_or_create_digraph ();
+ gcc_assert (m_body_element);
+ add_graph (dg, *m_body_element);
+}
+
/* Implementation of "end_group_cb" for HTML output. */
void
m_builder.set_printer (*get_printer ());
}
+ void
+ report_global_digraph (const diagnostics::digraphs::lazy_digraph &ldg) final override
+ {
+ m_builder.emit_global_graph (ldg);
+ }
+
const xml::document &get_document () const
{
return m_builder.get_document ();
// If true, attempt to show state diagrams at events
bool m_show_state_diagrams;
- // If true, show the XML form of the state with such diagrams
- bool m_show_state_diagram_xml;
+ // If true, show the SARIF form of the state with such diagrams
+ bool m_show_state_diagrams_sarif;
// If true, show the .dot source used for the diagram
- bool m_show_state_diagram_dot_src;
+ bool m_show_state_diagrams_dot_src;
};
extern diagnostic_output_file
#include "coretypes.h"
#include "diagnostic.h"
#include "diagnostic-metadata.h"
+#include "diagnostic-digraphs.h"
+#include "diagnostic-state-graphs.h"
#include "diagnostic-path.h"
#include "diagnostic-format.h"
#include "diagnostic-buffer.h"
void emit_diagram (const diagnostic_diagram &diagram);
void end_group ();
+ void
+ report_global_digraph (const diagnostics::digraphs::lazy_digraph &);
+
std::unique_ptr<sarif_result> take_current_result ()
{
return std::move (m_cur_group_result);
const diagnostic_info &diagnostic,
enum diagnostic_artifact_role role);
std::unique_ptr<sarif_location>
- make_location_object (sarif_location_manager &loc_mgr,
+ make_location_object (sarif_location_manager *loc_mgr,
const rich_location &rich_loc,
logical_location logical_loc,
enum diagnostic_artifact_role role);
std::unique_ptr<sarif_array_of_unique<sarif_logical_location>> m_cached_logical_locs;
+ std::unique_ptr<sarif_array_of_unique<sarif_graph>> m_run_graphs;
+
int m_tabstop;
std::unique_ptr<sarif_serialization_format> m_serialization_format;
sometimes these will related to current_function_decl, but
often they won't. */
auto location_obj
- = builder.make_location_object (*this, *diagnostic.richloc,
+ = builder.make_location_object (this, *diagnostic.richloc,
logical_location (),
diagnostic_artifact_role::result_file);
auto message_obj
m_rules_arr (new json::array ()),
m_cached_logical_locs
(std::make_unique<sarif_array_of_unique<sarif_logical_location>> ()),
+ m_run_graphs
+ (std::make_unique<sarif_array_of_unique<sarif_graph>> ()),
m_tabstop (context.m_tabstop),
m_serialization_format (std::move (serialization_format)),
m_sarif_gen_opts (sarif_gen_opts),
}
}
+void
+sarif_builder::
+report_global_digraph (const diagnostics::digraphs::lazy_digraph &ldg)
+{
+ auto &dg = ldg.get_or_create_digraph ();
+
+ /* Presumably the location manager must be nullptr; see
+ https://github.com/oasis-tcs/sarif-spec/issues/712 */
+ m_run_graphs->append (make_sarif_graph (dg, this, nullptr));
+}
+
/* Create a top-level object, and add it to all the results
(and other entities) we've seen so far, moving ownership
to the object. */
result_obj->set<json::array> ("codeFlows", std::move (code_flows_arr));
}
+ // "graphs" property (SARIF v2.1.0 section 3.27.19). */
+ if (diagnostic.metadata)
+ if (auto ldg = diagnostic.metadata->get_lazy_digraphs ())
+ {
+ auto &digraphs = ldg->get_or_create_digraphs ();
+ auto graphs_arr = std::make_unique<json::array> ();
+ for (auto &iter : digraphs)
+ graphs_arr->append (make_sarif_graph (*iter, this,
+ result_obj.get ()));
+ if (graphs_arr->size () > 0)
+ result_obj->set<json::array> ("graphs", std::move (graphs_arr));
+ }
+
/* The "relatedLocations" property (SARIF v2.1.0 section 3.27.22) is
set up later, if any nested diagnostics occur within this diagnostic
group. */
logical_loc = client_data_hooks->get_current_logical_location ();
auto location_obj
- = make_location_object (loc_mgr, *diagnostic.richloc, logical_loc, role);
+ = make_location_object (&loc_mgr, *diagnostic.richloc, logical_loc, role);
/* Don't add entirely empty location objects to the array. */
if (!location_obj->is_empty ())
locations_arr->append<sarif_location> (std::move (location_obj));
/* Make a "location" object (SARIF v2.1.0 section 3.28) for RICH_LOC
and LOGICAL_LOC.
Use LOC_MGR for any locations that need "id" values, and for
- any worklist items. */
+ any worklist items.
+ Note that we might not always have a LOC_MGR; see
+ https://github.com/oasis-tcs/sarif-spec/issues/712 */
std::unique_ptr<sarif_location>
-sarif_builder::make_location_object (sarif_location_manager &loc_mgr,
+sarif_builder::make_location_object (sarif_location_manager *loc_mgr,
const rich_location &rich_loc,
logical_location logical_loc,
enum diagnostic_artifact_role role)
/* Add related locations for any secondary locations in RICH_LOC
that don't have labels (and thus aren't added to "annotations"). */
- if (i > 0 && !handled)
- loc_mgr.add_relationship_to_worklist
+ if (loc_mgr && i > 0 && !handled)
+ loc_mgr->add_relationship_to_worklist
(*location_obj.get (),
sarif_location_manager::worklist_item::kind::unlabelled_secondary_location,
range->m_loc);
std::move (annotations_arr));
}
- add_any_include_chain (loc_mgr, *location_obj.get (), loc);
+ if (loc_mgr)
+ add_any_include_chain (*loc_mgr, *location_obj.get (), loc);
/* A flag for hinting that the diagnostic involves issues at the
level of character encodings (such as homoglyphs, or misleading
builder.make_minimal_sarif_logical_location (logical_loc));
}
+static void
+copy_any_property_bag (const diagnostics::digraphs::object &input_obj,
+ sarif_object &output_obj)
+{
+ if (input_obj.get_property_bag ())
+ {
+ const json::object &old_bag = *input_obj.get_property_bag ();
+ sarif_property_bag &new_bag = output_obj.get_or_create_properties ();
+ for (size_t i = 0; i < old_bag.get_num_keys (); ++i)
+ {
+ const char *key = old_bag.get_key (i);
+ json::value *val = old_bag.get (key);
+ new_bag.set (key, val->clone ());
+ }
+ }
+}
+
+std::unique_ptr<sarif_graph>
+make_sarif_graph (const diagnostics::digraphs::digraph &g,
+ sarif_builder *builder,
+ sarif_location_manager *sarif_location_mgr)
+{
+ auto result = std::make_unique<sarif_graph> ();
+
+ // 3.39.2 description property
+ if (const char *desc = g.get_description ())
+ if (builder)
+ result->set<sarif_message> ("description",
+ builder->make_message_object (desc));
+
+ copy_any_property_bag (g, *result);
+
+ // 3.39.3 nodes property
+ auto nodes_arr = std::make_unique<json::array> ();
+ const int num_nodes = g.get_num_nodes ();
+ for (int i = 0; i < num_nodes; ++i)
+ nodes_arr->append (make_sarif_node (g.get_node (i),
+ builder,
+ sarif_location_mgr));
+ result->set ("nodes", std::move (nodes_arr));
+
+ // 3.39.4 edges property
+ auto edges_arr = std::make_unique<json::array> ();
+ const int num_edges = g.get_num_edges ();
+ for (int i = 0; i < num_edges; ++i)
+ edges_arr->append (make_sarif_edge (g.get_edge (i), builder));
+ result->set ("edges", std::move (edges_arr));
+
+ return result;
+}
+
+std::unique_ptr<sarif_node>
+make_sarif_node (const diagnostics::digraphs::node &n,
+ sarif_builder *builder,
+ sarif_location_manager *sarif_location_mgr)
+{
+ auto result = std::make_unique<sarif_node> ();
+
+ // 3.40.2 id property
+ result->set_string ("id", n.get_id ().c_str ());
+
+ copy_any_property_bag (n, *result);
+
+ // 3.40.3 label property
+ if (const char *label = n.get_label ())
+ if (builder)
+ result->set<sarif_message> ("label",
+ builder->make_message_object (label));
+
+ // 3.40.4 location property
+ if (n.get_logical_loc ()
+ || n.get_physical_loc () != UNKNOWN_LOCATION)
+ if (builder)
+ {
+ rich_location rich_loc
+ (line_table, n.get_physical_loc ());
+ auto loc_obj
+ = builder->make_location_object
+ (sarif_location_mgr,
+ rich_loc,
+ n.get_logical_loc (),
+ diagnostic_artifact_role::scanned_file);
+ result->set<sarif_location> ("location",
+ std::move (loc_obj));
+ }
+
+ // 3.40.5 children property
+ if (const int num_children = n.get_num_children ())
+ {
+ auto children_arr = std::make_unique<json::array> ();
+ for (int i = 0; i < num_children; ++i)
+ children_arr->append (make_sarif_node (n.get_child (i),
+ builder,
+ sarif_location_mgr));
+ result->set ("children", std::move (children_arr));
+ }
+
+ return result;
+}
+
+std::unique_ptr<sarif_edge>
+make_sarif_edge (const diagnostics::digraphs::edge &e,
+ sarif_builder *builder)
+{
+ auto result = std::make_unique<sarif_edge> ();
+
+ // 3.41.2 id property
+ result->set_string ("id", e.get_id ().c_str ());
+
+ copy_any_property_bag (e, *result);
+
+ // 3.41.3 label property
+ if (const char *label = e.get_label ())
+ if (builder)
+ result->set<sarif_message> ("label",
+ builder->make_message_object (label));
+
+ // 3.41.4 sourceNodeId property
+ result->set_string ("sourceNodeId", e.get_src_node ().get_id ().c_str ());
+
+ // 3.41.5 targetNodeId property
+ result->set_string ("targetNodeId", e.get_dst_node ().get_id ().c_str ());
+
+ return result;
+}
+
+void
+sarif_property_bag::set_graph (const char *property_name,
+ sarif_builder &builder,
+ sarif_location_manager *sarif_location_mgr,
+ const diagnostics::digraphs::digraph &g)
+{
+ set<sarif_graph> (property_name,
+ make_sarif_graph (g, &builder, sarif_location_mgr));
+}
+
/* Ensure that m_cached_logical_locs has a "logicalLocation" object
(SARIF v2.1.0 section 3.33) for K, and return its index within the
array. */
via a property bag. */
ev.maybe_add_sarif_properties (*this, tfl_obj);
- if (get_opts ().m_xml_state)
- if (auto xml_state = ev.maybe_make_xml_state (true))
+ if (get_opts ().m_state_graph)
+ if (auto state_graph = ev.maybe_make_diagnostic_state_graph (true))
{
sarif_property_bag &props = tfl_obj.get_or_create_properties ();
- pretty_printer pp;
- xml_state->write_as_xml (&pp, 0, true);
-
#define PROPERTY_PREFIX "gcc/diagnostic_event/"
- props.set_string (PROPERTY_PREFIX "xml_state",
- pp_formatted_text (&pp));
+ props.set_graph (PROPERTY_PREFIX "state_graph",
+ *this,
+ /* Use RESULT for any related locations in the graph's
+ nodes.
+ It's not clear if this is correct; see:
+ https://github.com/oasis-tcs/sarif-spec/issues/712
+ */
+ &result,
+ *state_graph);
#undef PROPERTY_PREFIX
}
std::move (m_cached_logical_locs));
}
+ // "graphs" property (SARIF v2.1.0 3.14.20)
+ if (m_run_graphs->size () > 0)
+ run_obj->set<json::array> ("graphs",
+ std::move (m_run_graphs));
+
return run_obj;
}
/* No-op. */
}
+ void
+ report_global_digraph (const diagnostics::digraphs::lazy_digraph &lazy_digraph) final override
+ {
+ m_builder.report_global_digraph (lazy_digraph);
+ }
+
sarif_builder &get_builder () { return m_builder; }
size_t num_results () const { return m_builder.num_results (); }
sarif_generation_options::sarif_generation_options ()
: m_version (sarif_version::v2_1_0),
- m_xml_state (false)
+ m_state_graph (false)
{
}
std::unique_ptr<sarif_location> location_obj
= builder.make_location_object
- (result, richloc, logical_location (),
+ (&result, richloc, logical_location (),
diagnostic_artifact_role::analysis_target);
ASSERT_NE (location_obj, nullptr);
sarif_generation_options ();
enum sarif_version m_version;
- bool m_xml_state;
+ bool m_state_graph;
};
extern std::unique_ptr<diagnostic_output_format>
diagnostic_output_file output_file);
class sarif_builder;
+class sarif_location_manager;
+
+namespace diagnostics {
+namespace digraphs {
+ class digraph;
+ class node;
+ class edge;
+}}
/* Concrete subclass of json::object for SARIF property bags
(SARIF v2.1.0 section 3.8). */
void set_logical_location (const char *property_name,
sarif_builder &,
logical_location logical_loc);
+ void set_graph (const char *property_name,
+ sarif_builder &,
+ sarif_location_manager *sarif_location_mgr,
+ const diagnostics::digraphs::digraph &g);
};
/* Concrete subclass of json::object for SARIF objects that can
sarif_property_bag &get_or_create_properties ();
};
+/* Subclass of sarif_object for SARIF "graph" objects
+ (SARIF v2.1.0 section 3.39). */
+
+class sarif_graph : public sarif_object
+{
+};
+
+/* Subclass of sarif_object for SARIF "node" objects
+ (SARIF v2.1.0 section 3.40). */
+
+class sarif_node : public sarif_object
+{
+};
+
+/* Subclass of sarif_object for SARIF "edge" objects
+ (SARIF v2.1.0 section 3.41). */
+
+class sarif_edge : public sarif_object
+{
+};
+
+extern std::unique_ptr<sarif_graph>
+make_sarif_graph (const diagnostics::digraphs::digraph &g,
+ sarif_builder *builder,
+ sarif_location_manager *sarif_location_mgr);
+
+extern std::unique_ptr<sarif_node>
+make_sarif_node (const diagnostics::digraphs::node &n,
+ sarif_builder *builder,
+ sarif_location_manager *sarif_location_mgr);
+
+extern std::unique_ptr<sarif_edge>
+make_sarif_edge (const diagnostics::digraphs::edge &e,
+ sarif_builder *builder);
+
#endif /* ! GCC_DIAGNOSTIC_FORMAT_SARIF_H */
void update_printer () override;
+ void
+ report_global_digraph (const diagnostics::digraphs::lazy_digraph &) final override
+ {
+ // no-op for text
+ }
+
/* Helpers for writing lang-specific starters/finalizers for text output. */
char *build_prefix (const diagnostic_info &) const;
void report_current_module (location_t where);
Subclasses should update their m_printer accordingly. */
virtual void update_printer () = 0;
+ virtual void
+ report_global_digraph (const diagnostics::digraphs::lazy_digraph &) = 0;
+
diagnostic_context &get_context () const { return m_context; }
pretty_printer *get_printer () const { return m_printer.get (); }
class sarif_object;
+namespace diagnostics {
+ namespace digraphs {
+ class lazy_digraphs;
+ } // namespace digraphs
+} // namespace diagnostics
+
/* A bundle of additional metadata that can be associated with a
diagnostic.
This supports an optional CWE identifier, and zero or more
- "rules". */
+ "rules".
+
+ Additionally, this provides a place to associate a diagnostic
+ with zero or more directed graphs. */
class diagnostic_metadata
{
const char *m_url;
};
- diagnostic_metadata () : m_cwe (0) {}
+ diagnostic_metadata () : m_cwe (0), m_lazy_digraphs (nullptr) {}
virtual ~diagnostic_metadata () {}
/* Hook for SARIF output to allow for adding diagnostic-specific
unsigned get_num_rules () const { return m_rules.length (); }
const rule &get_rule (unsigned idx) const { return *(m_rules[idx]); }
+ void
+ set_lazy_digraphs (const diagnostics::digraphs::lazy_digraphs *lazy_digraphs)
+ {
+ m_lazy_digraphs = lazy_digraphs;
+ }
+
+ const diagnostics::digraphs::lazy_digraphs *
+ get_lazy_digraphs () const
+ {
+ return m_lazy_digraphs;
+ }
+
private:
int m_cwe;
auto_vec<const rule *> m_rules;
+
+ /* An optional way to create directed graphs associated with the
+ diagnostic, for the sinks that support this (e.g. SARIF). */
+ const diagnostics::digraphs::lazy_digraphs *m_lazy_digraphs;
};
#endif /* ! GCC_DIAGNOSTIC_METADATA_H */
private:
static sarif_generation_options
make_sarif_gen_opts (enum sarif_version version,
- bool xml_state);
+ bool state_graph);
static std::unique_ptr<sarif_serialization_format>
make_sarif_serialization_object (enum sarif_serialization_kind);
enum sarif_serialization_kind serialization_kind
= sarif_serialization_kind::json;
enum sarif_version version = sarif_version::v2_1_0;
- bool xml_state = false;
+ bool state_graph = false;
for (auto& iter : parsed_arg.m_kvs)
{
const std::string &key = iter.first;
return nullptr;
continue;
}
- if (key == "xml-state")
+ if (key == "state-graphs")
{
if (!parse_bool_value (ctxt, unparsed_arg, key, value,
- xml_state))
+ state_graph))
return nullptr;
continue;
}
auto_vec<const char *> known_keys;
known_keys.safe_push ("file");
known_keys.safe_push ("serialization");
+ known_keys.safe_push ("state-graphs");
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;
if (!output_file)
return nullptr;
- auto sarif_gen_opts = make_sarif_gen_opts (version, xml_state);
+ auto sarif_gen_opts = make_sarif_gen_opts (version, state_graph);
auto serialization_obj = make_sarif_serialization_object (serialization_kind);
sarif_generation_options
sarif_scheme_handler::make_sarif_gen_opts (enum sarif_version version,
- bool xml_state)
+ bool state_graph)
{
sarif_generation_options sarif_gen_opts;
sarif_gen_opts.m_version = version;
- sarif_gen_opts.m_xml_state = xml_state;
+ sarif_gen_opts.m_state_graph = state_graph;
return sarif_gen_opts;
}
label_text filename;
bool javascript = true;
bool show_state_diagrams = false;
- bool show_state_diagram_xml = false;
- bool show_state_diagram_dot_src = false;
+ bool show_state_diagrams_sarif = false;
+ bool show_state_diagrams_dot_src = false;
for (auto& iter : parsed_arg.m_kvs)
{
const std::string &key = iter.first;
return nullptr;
continue;
}
- if (key == "show-state-diagram-dot-src")
+ if (key == "show-state-diagrams-dot-src")
{
if (!parse_bool_value (ctxt, unparsed_arg, key, value,
- show_state_diagram_dot_src))
+ show_state_diagrams_dot_src))
return nullptr;
continue;
}
- if (key == "show-state-diagram-xml")
+ if (key == "show-state-diagrams-sarif")
{
if (!parse_bool_value (ctxt, unparsed_arg, key, value,
- show_state_diagram_xml))
+ show_state_diagrams_sarif))
return nullptr;
continue;
}
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");
+ known_keys.safe_push ("show-state-diagram-sarif");
ctxt.report_unknown_key (unparsed_arg, key, get_scheme_name (),
known_keys);
return nullptr;
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;
+ html_gen_opts.m_show_state_diagrams_sarif = show_state_diagrams_sarif;
+ html_gen_opts.m_show_state_diagrams_dot_src = show_state_diagrams_dot_src;
auto sink = make_html_sink (dc,
*ctxt.get_affected_location_mgr (),
#include "coretypes.h"
#include "diagnostic.h"
#include "diagnostic-path.h"
-#include "xml.h"
+#include "diagnostic-state-graphs.h"
/* Disable warnings about missing quoting in GCC diagnostics for the print
calls below. */
return label_text::take (xstrdup (pp_formatted_text (pp.get ())));
}
-// Base implementation of diagnostic_event::maybe_make_xml_state
+// Base implementation of diagnostic_event::maybe_make_diagnostic_state_graph
-std::unique_ptr<xml::document>
-diagnostic_event::maybe_make_xml_state (bool) const
+std::unique_ptr<diagnostics::digraphs::digraph>
+diagnostic_event::maybe_make_diagnostic_state_graph (bool) const
{
- // Don't attempt to make a state document:
+ // Don't attempt to make a state graph:
return nullptr;
}
class sarif_builder;
class sarif_object;
+namespace diagnostics {
+namespace digraphs {
+ class digraph;
+} // namespace digraphs
+} //namespace diagnostics
+
/* A diagnostic_path is an optional additional piece of metadata associated
with a diagnostic (via its rich_location).
}
/* Hook for capturing state at this event, potentially for visualizing
- in HTML output. */
- virtual std::unique_ptr<xml::document>
- maybe_make_xml_state (bool debug) const;
+ in HTML output, or for adding to SARIF. */
+ virtual std::unique_ptr<diagnostics::digraphs::digraph>
+ maybe_make_diagnostic_state_graph (bool debug) const;
label_text get_desc (pretty_printer &ref_pp) const;
};
--- /dev/null
+/* Extensions to diagnostics::digraphs to support state graphs.
+ Copyright (C) 2025 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 "diagnostic-state-graphs.h"
+#include "graphviz.h"
+#include "xml.h"
+#include "xml-printer.h"
+#include "intl.h"
+#include "selftest.h"
+
+using namespace diagnostics::state_graphs;
+
+const char * const node_kind_strs[] = {
+ "globals",
+ "code",
+ "function",
+ "stack",
+ "stack-frame",
+ "heap",
+ "thread-local",
+ "dynalloc-buffer",
+ "variable",
+ "field",
+ "padding",
+ "element",
+ "other",
+};
+
+const char *
+diagnostics::state_graphs::node_kind_to_str (enum node_kind k)
+{
+ return node_kind_strs[static_cast<int> (k)];
+}
+
+// struct state_node_ref
+
+enum node_kind
+state_node_ref::get_node_kind () const
+{
+ const char *value = get_attr ("kind");
+ if (!value)
+ return node_kind::other;
+
+ for (size_t i = 0; i < ARRAY_SIZE (node_kind_strs); ++i)
+ if (!strcmp (node_kind_strs[i], value))
+ return static_cast<enum node_kind> (i);
+
+ return node_kind::other;
+}
+
+void
+state_node_ref::set_node_kind (enum node_kind k)
+{
+ set_attr ("kind", node_kind_to_str (k));
+}
+
+const char * const dynalloc_state_strs[] = {
+ "unknown",
+ "nonnull",
+ "unchecked",
+ "freed"
+};
+
+enum node_dynalloc_state
+state_node_ref::get_dynalloc_state () const
+{
+ const char *value = get_attr ("dynalloc-state");
+ if (!value)
+ return node_dynalloc_state::unknown;
+
+ for (size_t i = 0; i < ARRAY_SIZE (dynalloc_state_strs); ++i)
+ if (!strcmp (dynalloc_state_strs[i], value))
+ return static_cast<enum node_dynalloc_state> (i);
+
+ return node_dynalloc_state::unknown;
+}
+
+void
+state_node_ref::set_dynalloc_state (enum node_dynalloc_state s) const
+{
+ set_attr ("dynalloc-state",
+ dynalloc_state_strs[static_cast <size_t> (s)]);
+}
+
+const char *
+state_node_ref::get_dynamic_extents () const
+{
+ return m_node.get_attr (STATE_NODE_PREFIX, "dynamic-extents");
+}
+
+void
+state_node_ref::set_json_attr (const char *key,
+ std::unique_ptr<json::value> value) const
+{
+ m_node.set_json_attr (STATE_NODE_PREFIX, key, std::move (value));
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+test_node_attrs ()
+{
+ diagnostics::digraphs::digraph g;
+ diagnostics::digraphs::node n (g, "a");
+ state_node_ref node_ref (n);
+
+ ASSERT_EQ (node_ref.get_node_kind (), node_kind::other);
+ node_ref.set_node_kind (node_kind::stack);
+ ASSERT_EQ (node_ref.get_node_kind (), node_kind::stack);
+
+ ASSERT_EQ (node_ref.get_dynalloc_state (), node_dynalloc_state::unknown);
+ node_ref.set_dynalloc_state (node_dynalloc_state::freed);
+ ASSERT_EQ (node_ref.get_dynalloc_state (), node_dynalloc_state::freed);
+
+ ASSERT_EQ (node_ref.get_type (), nullptr);
+ node_ref.set_type ("const char *");
+ ASSERT_STREQ (node_ref.get_type (), "const char *");
+}
+
+/* Run all of the selftests within this file. */
+
+void
+diagnostic_state_graphs_cc_tests ()
+{
+ test_node_attrs ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
--- /dev/null
+/* Extensions to diagnostics::digraphs to support state graphs.
+ Copyright (C) 2025 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/>. */
+
+#ifndef GCC_DIAGNOSTIC_STATE_GRAPHS_H
+#define GCC_DIAGNOSTIC_STATE_GRAPHS_H
+
+#include "diagnostic-digraphs.h"
+#include "logical-location.h"
+
+/* diagnostics::digraphs provides support for directed graphs.
+
+ diagnostics::state_graphs provides a way to extend these graphs
+ for representing "state graphs" i.e. a representation of the state
+ of memory inside a program, for use e.g. by -fanalyzer.
+
+ Specifically, nodes represent memory regions, and we use property bags
+ in these nodes to stash extra properties (e.g. what kind of memory region
+ a node is e.g. stack vs heap). */
+
+class sarif_graph;
+namespace dot { class graph; }
+
+namespace diagnostics {
+namespace state_graphs {
+
+enum class node_kind
+{
+ // Memory regions
+ globals,
+ code,
+ function, // code within a particular function
+ stack,
+ stack_frame,
+ heap_,
+ thread_local_,
+
+ /* Dynamically-allocated buffer,
+ on heap or stack (depending on parent). */
+ dynalloc_buffer,
+
+ variable,
+
+ field, // field within a struct or union
+ padding, // padding bits in a struct or union
+ element, // element within an array
+
+ other // anything else
+};
+
+extern const char *
+node_kind_to_str (enum node_kind);
+
+enum class node_dynalloc_state
+{
+ unknown,
+ nonnull,
+ unchecked,
+ freed
+};
+
+/* Prefixes to use in SARIF property bags. */
+#define STATE_GRAPH_PREFIX "gcc/diagnostic_state_graph/"
+#define STATE_NODE_PREFIX "gcc/diagnostic_state_node/"
+#define STATE_EDGE_PREFIX "gcc/diagnostic_state_edge/"
+
+/* A wrapper around a node that gets/sets attributes, using
+ the node's property bag for storage, so that the data roundtrips
+ through SARIF. */
+
+struct state_node_ref
+{
+ state_node_ref (diagnostics::digraphs::node &node)
+ : m_node (node)
+ {}
+
+ enum node_kind
+ get_node_kind () const;
+ void
+ set_node_kind (enum node_kind);
+
+ // For node_kind::stack_frame, this will be the function
+ logical_location
+ get_logical_loc () const
+ {
+ return m_node.get_logical_loc ();
+ }
+
+ // For node_kind::dynalloc_buffer
+ enum node_dynalloc_state
+ get_dynalloc_state () const;
+
+ void
+ set_dynalloc_state (enum node_dynalloc_state) const;
+
+ const char *
+ get_dynamic_extents () const;
+
+ const char *
+ get_name () const { return get_attr ("name"); }
+ void
+ set_name (const char *name) const { set_attr ("name", name); }
+
+ const char *
+ get_type () const { return get_attr ("type"); }
+ void
+ set_type (const char *type) const { set_attr ("type", type); }
+
+ const char *
+ get_value () const { return get_attr ("value"); }
+
+ const char *
+ get_index () const { return get_attr ("index"); }
+
+ const char *
+ get_attr (const char *key) const
+ {
+ return m_node.get_attr (STATE_NODE_PREFIX, key);
+ }
+
+ void
+ set_attr (const char *key, const char *value) const
+ {
+ return m_node.set_attr (STATE_NODE_PREFIX, key, value);
+ }
+
+ void
+ set_json_attr (const char *key, std::unique_ptr<json::value> value) const;
+
+ diagnostics::digraphs::node &m_node;
+};
+
+extern std::unique_ptr<dot::graph>
+make_dot_graph (const diagnostics::digraphs::digraph &state_graph,
+ const logical_location_manager &logical_loc_mgr);
+
+} // namespace state_graphs
+} // namespace diagnostics
+
+#endif /* ! GCC_DIAGNOSTIC_STATE_GRAPHS_H */
-/* Creating GraphViz .dot files from XML state documents.
+/* Creating GraphViz .dot files from diagnostic state graphs.
Copyright (C) 2025 Free Software Foundation, Inc.
Contributed by David Malcolm <dmalcolm@redhat.com>.
#include "system.h"
#include "coretypes.h"
+#include "diagnostic-state-graphs.h"
+#include "graphviz.h"
#include "xml.h"
#include "xml-printer.h"
-#include "graphviz.h"
+#include "intl.h"
+
+using namespace diagnostics::state_graphs;
static int
-get_depth (const xml::element &e)
+get_depth (const diagnostics::digraphs::node &n)
{
int deepest_child = 0;
- for (auto &iter : e.m_children)
- if (xml::element *child_element = iter->dyn_cast_element ())
+ for (size_t i = 0; i < n.get_num_children (); ++i)
deepest_child = std::max (deepest_child,
- get_depth (*child_element));
+ get_depth (n.get_child (i)));
return deepest_child + 1;
}
-enum class dynalloc_state
-{
- unknown,
- nonnull,
- unchecked,
- freed
-};
-
static const char *
-get_color_for_dynalloc_state (enum dynalloc_state dynalloc_st)
+get_color_for_dynalloc_state (enum node_dynalloc_state dynalloc_st)
{
switch (dynalloc_st)
{
default:
gcc_unreachable ();
break;
- case dynalloc_state::unknown:
- case dynalloc_state::nonnull:
+ case node_dynalloc_state::unknown:
+ case node_dynalloc_state::nonnull:
return nullptr;
- case dynalloc_state::unchecked:
+ case node_dynalloc_state::unchecked:
return "#ec7a08"; // pf-orange-400
- case dynalloc_state::freed:
+ case node_dynalloc_state::freed:
return "#cc0000"; // pf-red-100
}
}
static void
set_color_for_dynalloc_state (dot::attr_list &attrs,
- enum dynalloc_state dynalloc_state)
+ enum node_dynalloc_state state)
{
- if (const char *color = get_color_for_dynalloc_state (dynalloc_state))
+ if (const char *color = get_color_for_dynalloc_state (state))
attrs.add (dot::id ("color"), dot::id (color));
}
-static enum dynalloc_state
-get_dynalloc_state (const xml::element &input_element)
-{
- const char *dyn_alloc_state = input_element.get_attr ("dynamic-alloc-state");
- if (!dyn_alloc_state)
- return dynalloc_state::unknown;
-
- if (dyn_alloc_state == std::string ("unchecked"))
- return dynalloc_state::unchecked;
-
- if (dyn_alloc_state == std::string ("nonnull"))
- return dynalloc_state::nonnull;
-
- if (dyn_alloc_state == std::string ("freed"))
- return dynalloc_state::freed;
-
- return dynalloc_state::unknown;
-}
-
class state_diagram : public dot::graph
{
public:
- state_diagram (const xml::document &input_state_doc)
- : m_next_id (0),
+ state_diagram (const diagnostics::digraphs::digraph &input_state_graph,
+ const logical_location_manager &logical_loc_mgr)
+ : m_logical_loc_mgr (logical_loc_mgr),
+ // m_next_id (0),
m_show_tags (false)
{
// "node [shape=plaintext]\n"
add_stmt (std::move (attr_stmt));
}
- /* Recurse down the XML state diagram, creating subgraphs
+ /* Determine which nodes are involved in edges. */
+ for (size_t i = 0; i < input_state_graph.get_num_edges (); ++i)
+ {
+ auto &edge = input_state_graph.get_edge (i);
+ m_src_nodes.insert (&edge.get_src_node ());
+ m_dst_nodes.insert (&edge.get_dst_node ());
+ }
+
+ /* Recurse down the nodes in the state graph, creating subgraphs
and then eventually creating nodes, and recursively
- creating XML tables, adding ports for the endpoints of edges,
- and recording edges we'll want to create (into m_pending_edges). */
- xml::element *input_elmt_state_diagram
- = input_state_doc.find_child_element ("state-diagram");
- gcc_assert (input_elmt_state_diagram);
- xml::element *input_elmt_mem_regions
- = input_elmt_state_diagram->find_child_element ("memory-regions");
- if (!input_elmt_mem_regions)
- return;
+ creating XML tables, and adding ports for the endpoints of edges
+ where needed. */
+
auto root_cluster
= std::make_unique<dot::subgraph> (dot::id ("cluster_memory_regions"));
- for (auto &iter : input_elmt_mem_regions->m_children)
- on_input_xml_node (*root_cluster, *iter);
+ for (size_t i = 0; i < input_state_graph.get_num_nodes (); ++i)
+ on_input_state_node (*root_cluster,
+ state_node_ref (input_state_graph.get_node (i)));
add_stmt (std::move (root_cluster));
- /* We should now have ports for edge endpoints for all region ids.
- Use them now to create edges. */
- for (auto &pe : m_pending_edges)
+ /* Now create dot edges for edges in input_stage_graph. */
+ for (size_t i = 0; i < input_state_graph.get_num_edges (); ++i)
{
- auto search = m_region_id_to_dst_node_id.find (pe.m_dst_region_id);
- if (search != m_region_id_to_dst_node_id.end ())
- {
- auto &dst_node_id = search->second;
- auto e = std::make_unique<dot::edge_stmt> (pe.m_src_node_id,
- dst_node_id);
-
- auto dynalloc_state = m_region_id_to_dynalloc_state.find (pe.m_dst_region_id);
- if (dynalloc_state != m_region_id_to_dynalloc_state.end ())
- set_color_for_dynalloc_state (e->m_attrs,
- dynalloc_state->second);
-
- add_stmt (std::move (e));
- }
+ auto &edge = input_state_graph.get_edge (i);
+ auto &src_node = edge.get_src_node ();
+ auto &dst_node = edge.get_dst_node ();
+
+ auto src_port_id = m_src_node_to_port_id.find (&src_node);
+ if (src_port_id == m_src_node_to_port_id.end ())
+ continue;
+ auto dst_port_id = m_dst_node_to_port_id.find (&dst_node);
+ if (dst_port_id == m_dst_node_to_port_id.end ())
+ continue;
+
+ auto e = std::make_unique<dot::edge_stmt> (src_port_id->second,
+ dst_port_id->second);
+ set_color_for_dynalloc_state
+ (e->m_attrs, state_node_ref (dst_node).get_dynalloc_state ());
+
+ add_stmt (std::move (e));
}
}
}
dot::id
- make_id (bool cluster = false)
+ make_id (state_node_ref state_node, bool cluster)
{
+ std::string input_node_id = state_node.m_node.get_id ();
if (cluster)
- return std::string ("cluster_") + std::to_string (m_next_id++);
+ return std::string ("cluster_") + input_node_id;
else
- return std::string ("id_") + std::to_string (m_next_id++);
+ return input_node_id;
}
bool
- starts_node_p (const xml::element &e)
+ starts_node_p (state_node_ref state_node)
{
- if (e.m_kind == "stack"
- || e.m_kind == "heap-buffer"
- || e.m_kind == "variable") // e.g. within globals
- return true;
- return false;
+ switch (state_node.get_node_kind ())
+ {
+ default:
+ return false;
+
+ case node_kind::stack:
+ /* We want all frames in the stack in the same table,
+ so they are grouped. */
+ case node_kind::dynalloc_buffer:
+ case node_kind::variable:
+ return true;
+ }
}
- void
- on_input_xml_node (dot::subgraph &parent_subgraph,
- xml::node &input_node)
+ const char *
+ get_label_for_node (state_node_ref state_node)
{
- xml::element *input_element = input_node.dyn_cast_element ();
- if (!input_element)
- return;
+ switch (state_node.get_node_kind ())
+ {
+ default:
+ return nullptr;
+
+ case node_kind::globals:
+ return _("Globals");
+ case node_kind::code:
+ return _("Code");
+ case node_kind::stack:
+ return _("Stack");
+ case node_kind::heap_:
+ return _("Heap");
+ }
+ }
- dot::id sg_id = make_id (true);
+ void
+ on_input_state_node (dot::subgraph &parent_subgraph,
+ state_node_ref state_node)
+ {
+ dot::id sg_id = make_id (state_node, true);
- if (starts_node_p (*input_element))
+ if (starts_node_p (state_node))
{
// Create node with table
xml::element table ("table", false);
xp.set_attr ("cellborder", "1");
xp.set_attr ("cellspacing", "0");
- const int max_depth = get_depth (*input_element);
+ const int max_depth = get_depth (state_node.m_node);
const int num_columns = max_depth + 2;
- dot::id id_of_node = make_id ();
- on_xml_node (id_of_node, xp, *input_element,
- max_depth, 0, num_columns);
+ dot::id id_of_dot_node = make_id (state_node, false);
+ on_node_in_table (id_of_dot_node, xp, state_node,
+ max_depth, 0, num_columns);
- auto node = std::make_unique<dot::node_stmt> (std::move (id_of_node));
+ auto node = std::make_unique<dot::node_stmt> (std::move (id_of_dot_node));
node->m_attrs.add (dot::id ("shape"),
dot::id ("plaintext"));
{
auto child_subgraph = std::make_unique<dot::subgraph> (std::move (sg_id));
- if (const char *label = input_element->get_attr ("label"))
+ if (const char *label = get_label_for_node (state_node))
child_subgraph->add_attr (dot::id ("label"), dot::id (label));
// recurse:
- for (auto &iter : input_element->m_children)
- on_input_xml_node (*child_subgraph, *iter);
+ for (size_t i = 0; i < state_node.m_node.get_num_children (); ++i)
+ on_input_state_node (*child_subgraph,
+ state_node.m_node.get_child (i));
parent_subgraph.m_stmt_list.add_stmt (std::move (child_subgraph));
}
}
enum class style { h1, h2 };
void
- add_title_tr (const dot::id &id_of_node,
+ add_title_tr (const dot::id &id_of_dot_node,
xml::printer &xp,
int num_columns,
- const xml::element &input_element,
+ state_node_ref state_node,
std::string heading,
enum style styl,
- enum dynalloc_state dynalloc_state)
+ enum node_dynalloc_state dynalloc_state)
{
xp.push_tag ("tr", true);
xp.push_tag ("td", false);
xp.add_text (std::move (heading));
xp.pop_tag ("font");
- maybe_add_dst_port (id_of_node, xp, input_element);
+ maybe_add_dst_port (id_of_dot_node, xp, state_node);
xp.pop_tag ("td");
xp.pop_tag ("tr");
}
- /* Recursively add <TR> to XP for INPUT_NODE and its descendents. */
+ /* Recursively add <TR> to XP for STATE_NODE and its descendents. */
void
- on_xml_node (const dot::id &id_of_node,
- xml::printer &xp,
- xml::node &input_node,
- int max_depth,
- int depth,
- int num_columns)
+ on_node_in_table (const dot::id &id_of_dot_node,
+ xml::printer &xp,
+ state_node_ref state_node,
+ int max_depth,
+ int depth,
+ int num_columns)
{
bool recurse = true;
+ auto input_node_kind = state_node.get_node_kind ();
- xml::element *input_element = input_node.dyn_cast_element ();
- if (!input_element)
- return;
-
- if (input_element->m_kind == "concrete-bindings")
- return;
- if (input_element->m_kind == "padding")
- return;
-
- if (input_element->m_kind == "stack")
- {
- add_title_tr (id_of_node, xp, num_columns, *input_element, "Stack",
- style::h1, dynalloc_state::unknown);
- }
- else if (input_element->m_kind == "stack-frame")
- {
- if (const char *function = input_element->get_attr ("function"))
- add_title_tr (id_of_node, xp, num_columns, *input_element,
- std::string ("Frame: ") + function,
- style::h2, dynalloc_state::unknown);
- }
- else if (input_element->m_kind == "heap-buffer")
+ switch (input_node_kind)
{
- const char *extents = input_element->get_attr ("dynamic-extents");
- enum dynalloc_state dynalloc_st = get_dynalloc_state (*input_element);
- if (auto region_id = input_element->get_attr ("region_id"))
- m_region_id_to_dynalloc_state[region_id] = dynalloc_st;
- const char *type = input_element->get_attr ("type");
- pretty_printer pp;
- switch (dynalloc_st)
- {
- default:
- gcc_unreachable ();
-
- case dynalloc_state::unknown:
- case dynalloc_state::nonnull:
- if (type)
- {
+ case node_kind::padding:
+ case node_kind::other:
+ return;
+
+ case node_kind::stack:
+ add_title_tr (id_of_dot_node, xp, num_columns, state_node, "Stack",
+ style::h1,
+ node_dynalloc_state::unknown);
+ break;
+ case node_kind::stack_frame:
+ if (auto logical_loc = state_node.get_logical_loc ())
+ if (const char *function
+ = m_logical_loc_mgr.get_short_name (logical_loc))
+ add_title_tr (id_of_dot_node, xp, num_columns, state_node,
+ std::string ("Frame: ") + function,
+ style::h2,
+ node_dynalloc_state::unknown);
+ break;
+ case node_kind::dynalloc_buffer:
+ {
+ enum node_dynalloc_state dynalloc_st
+ = state_node.get_dynalloc_state ();
+ const char *extents = state_node.get_dynamic_extents ();
+ const char *type = state_node.get_type ();
+ pretty_printer pp;
+ switch (dynalloc_st)
+ {
+ default:
+ gcc_unreachable ();
+
+ case node_dynalloc_state::unknown:
+ case node_dynalloc_state::nonnull:
+ if (type)
+ {
if (extents)
pp_printf (&pp, "%s (%s byte allocation)",
type, extents);
else
pp_printf (&pp, "%s", type);
- }
- else
+ }
+ else
+ {
+ if (extents)
+ pp_printf (&pp, "%s byte allocation",
+ extents);
+ }
+ break;
+
+ case node_dynalloc_state::unchecked:
+ if (type)
+ {
+ if (extents)
+ pp_printf (&pp, "%s (unchecked %s byte allocation)",
+ type, extents);
+ }
+ else
+ {
+ if (extents)
+ pp_printf (&pp, "Unchecked %s byte allocation",
+ extents);
+ }
+ break;
+
+ case node_dynalloc_state::freed:
+ // TODO: show deallocator
+ // TODO: show deallocation event
+ pp_printf (&pp, "Freed buffer");
+ break;
+ }
+ maybe_add_dst_port (id_of_dot_node, xp, state_node);
+ add_title_tr (id_of_dot_node, xp, num_columns, state_node,
+ pp_formatted_text (&pp),
+ style::h2,
+ dynalloc_st);
+ }
+ break;
+
+ default:
+ {
+ xp.push_tag ("tr", true);
+
+ maybe_add_dst_port (id_of_dot_node, xp, state_node);
+
+ if (depth > 0)
+ {
+ /* Indent, by create a <td> spanning "depth" columns. */
+ xp.push_tag ("td", false);
+ xp.set_attr ("colspan", std::to_string (depth));
+ xp.add_text (" "); // graphviz doesn't like <td/>
+ xp.pop_tag ("td");
+ }
+
+ switch (input_node_kind)
+ {
+ default:
+ break;
+ case node_kind::variable:
{
- if (extents)
- pp_printf (&pp, "%s byte allocation",
- extents);
+ const char *name = state_node.get_name ();
+ gcc_assert (name);
+ xp.push_tag ("td", false);
+ maybe_add_dst_port (id_of_dot_node, xp, state_node);
+ push_src_text (xp);
+ xp.add_text (name);
+ pop_src_text (xp);
+ xp.pop_tag ("td");
}
- break;
-
- case dynalloc_state::unchecked:
- if (type)
+ break;
+ case node_kind::element:
{
- if (extents)
- pp_printf (&pp, "%s (unchecked %s byte allocation)",
- type, extents);
+ const char *index = state_node.get_index ();
+ gcc_assert (index);
+ xp.push_tag ("td", false);
+ maybe_add_dst_port (id_of_dot_node, xp, state_node);
+ push_src_text (xp);
+ xp.add_text ("[");
+ xp.add_text (index);
+ xp.add_text ("]");
+ pop_src_text (xp);
+ xp.pop_tag ("td");
}
- else
+ break;
+ case node_kind::field:
{
- if (extents)
- pp_printf (&pp, "Unchecked %s byte allocation",
- extents);
+ const char *name = state_node.get_name ();
+ gcc_assert (name);
+ xp.push_tag ("td", false);
+ maybe_add_dst_port (id_of_dot_node, xp, state_node);
+ push_src_text (xp);
+ xp.add_text (".");
+ xp.add_text (name);
+ pop_src_text (xp);
+ xp.pop_tag ("td");
}
- break;
-
- case dynalloc_state::freed:
- // TODO: show deallocator
- // TODO: show deallocation event
- pp_printf (&pp, "Freed buffer");
- break;
- }
- add_title_tr (id_of_node, xp, num_columns, *input_element,
- pp_formatted_text (&pp),
- style::h2,
- dynalloc_st);
- }
- else
- {
- xp.push_tag ("tr", true);
- if (depth > 0)
- {
- /* Indent, by create a <td> spanning "depth" columns. */
- xp.push_tag ("td", false);
- xp.set_attr ("colspan", std::to_string (depth));
- xp.add_text (" "); // graphviz doesn't like <td/>
- xp.pop_tag ("td");
- }
- if (m_show_tags)
- {
- // Debug: show XML tag
- xp.push_tag ("td", false);
- xp.add_text ("<");
- xp.add_text (input_element->m_kind);
- xp.add_text (">");
- xp.pop_tag ("td");
- }
- if (input_element->m_kind == "variable")
- {
- const char *name = input_element->get_attr ("name");
- gcc_assert (name);
- xp.push_tag ("td", false);
- maybe_add_dst_port (id_of_node, xp, *input_element);
- push_src_text (xp);
- xp.add_text (name);
- pop_src_text (xp);
- xp.pop_tag ("td");
- }
- else if (input_element->m_kind == "element")
- {
- const char *index = input_element->get_attr ("index");
- gcc_assert (index);
- xp.push_tag ("td", false);
- maybe_add_dst_port (id_of_node, xp, *input_element);
- push_src_text (xp);
- xp.add_text ("[");
- xp.add_text (index);
- xp.add_text ("]");
- pop_src_text (xp);
- xp.pop_tag ("td");
- }
- else if (input_element->m_kind == "field")
- {
- const char *name = input_element->get_attr ("name");
- gcc_assert (name);
- xp.push_tag ("td", false);
- maybe_add_dst_port (id_of_node, xp, *input_element);
- push_src_text (xp);
- xp.add_text (".");
- xp.add_text (name);
- pop_src_text (xp);
- xp.pop_tag ("td");
- }
- if (const char *type = input_element->get_attr ("type"))
- {
- xp.push_tag ("td", false);
- if (max_depth > depth)
- xp.set_attr ("colspan", std::to_string (max_depth - depth));
- xp.set_attr ("align", "right");
- push_src_text (xp);
- xp.add_text (type);
- pop_src_text (xp);
- xp.pop_tag ("td");
- }
- if (auto value = input_element->find_child_element ("value-of-region"))
- {
- xp.push_tag ("td", false);
- for (auto &iter : value->m_children)
- if (auto child_element = iter->dyn_cast_element ())
- print_value (id_of_node, xp, *child_element);
- xp.pop_tag ("td");
- recurse = false;
- }
- xp.pop_tag ("tr");
+ break;
+ }
+
+ if (const char *type = state_node.get_type ())
+ {
+ xp.push_tag ("td", false);
+ xp.set_attr ("align", "right");
+ push_src_text (xp);
+ xp.add_text (type);
+ pop_src_text (xp);
+ xp.pop_tag ("td");
+ }
+
+ if (const char *value = state_node.get_value ())
+ {
+ xp.push_tag ("td", false);
+ xp.set_attr ("align", "left");
+ maybe_add_src_port (id_of_dot_node, xp, state_node);
+ push_src_text (xp);
+ xp.add_text (value);
+ pop_src_text (xp);
+ xp.pop_tag ("td");
+ recurse = false;
+ }
+ xp.pop_tag ("tr");
+ }
+ break;
}
if (recurse)
- for (auto &iter : input_element->m_children)
- on_xml_node (id_of_node, xp, *iter, max_depth, depth + 1, num_columns);
+ for (size_t i = 0; i < state_node.m_node.get_num_children (); ++i)
+ on_node_in_table (id_of_dot_node, xp,
+ state_node.m_node.get_child (i),
+ max_depth, depth + 1, num_columns);
}
void
xp.pop_tag ("font");
}
+ /* If STATE_NODE is in m_src_nodes, add a port to XP for possible
+ incoming edges to use. */
+
void
- print_value (const dot::id &id_of_node,
- xml::printer &xp,
- xml::element &input_element)
+ maybe_add_src_port (const dot::id &id_of_dot_node,
+ xml::printer &xp,
+ state_node_ref state_node)
{
- if (input_element.m_kind == "pointer-to-region")
- if (const char *dst_region_id = input_element.get_attr ("region_id"))
- {
- dot::id src_port_id = make_id ();
- xp.set_attr ("port", src_port_id.m_str);
- m_pending_edges.push_back
- ({dot::node_id (id_of_node,
- dot::port (src_port_id,
- dot::compass_pt::e)),
- dst_region_id});
- }
-
- if (input_element.m_kind == "uninitialized")
- {
- xp.add_text ("(uninitialized)");
- return;
- }
+ auto iter = m_src_nodes.find (&state_node.m_node);
+ if (iter == m_src_nodes.end ())
+ return;
- if (auto dump_text = input_element.get_attr ("dump-text"))
- xp.add_text (dump_text);
+ dot::id src_id = make_id (state_node, false);
+ dot::node_id node_id (id_of_dot_node,
+ dot::port (src_id,
+ dot::compass_pt::e));
+ m_src_node_to_port_id.insert ({&state_node.m_node, node_id});
+ xp.set_attr ("port", src_id.m_str);
}
- /* If INPUT_ELEMENT has a "region_id", add a port to XP for possible
+ /* If STATE_NODE is in m_dst_nodes, add a port to XP for possible
incoming edges to use. */
void
- maybe_add_dst_port (const dot::id &id_of_node,
+ maybe_add_dst_port (const dot::id &id_of_dot_node,
xml::printer &xp,
- const xml::element &input_element)
+ state_node_ref state_node)
{
- if (const char *region_id = input_element.get_attr ("region_id"))
- {
- dot::id dst_id = make_id ();
- dot::node_id node_id (id_of_node,
- dot::port (dst_id/*,
- dot::compass_pt::w*/));
- xp.set_attr ("port", dst_id.m_str);
- m_region_id_to_dst_node_id.emplace (std::string (region_id),
- std::move (node_id));
- }
- }
+ auto iter = m_dst_nodes.find (&state_node.m_node);
+ if (iter == m_dst_nodes.end ())
+ return;
+ dot::id dst_id = make_id (state_node, false);
+ dot::node_id node_id (id_of_dot_node,
+ dot::port (dst_id/*,
+ dot::compass_pt::w*/));
+ m_dst_node_to_port_id.insert ({&state_node.m_node, node_id});
+ xp.set_attr ("port", dst_id.m_str);
+ }
private:
- int m_next_id;
- std::vector<pending_edge> m_pending_edges;
- std::map<std::string, dot::node_id> m_region_id_to_dst_node_id;
- std::map<std::string, enum dynalloc_state> m_region_id_to_dynalloc_state;
+ const logical_location_manager &m_logical_loc_mgr;
+
+ /* All nodes involved in edges (and thus will need a port). */
+ std::set<diagnostics::digraphs::node *> m_src_nodes;
+ std::set<diagnostics::digraphs::node *> m_dst_nodes;
+
+ std::map<diagnostics::digraphs::node *, dot::node_id> m_src_node_to_port_id;
+ std::map<diagnostics::digraphs::node *, dot::node_id> m_dst_node_to_port_id;
+
bool m_show_tags;
};
std::unique_ptr<dot::graph>
-make_dot_graph_from_xml_state (const xml::document &xml_state)
+diagnostics::state_graphs::
+make_dot_graph (const diagnostics::digraphs::digraph &state_graph,
+ const logical_location_manager &logical_loc_mgr)
{
- return std::make_unique<state_diagram> (xml_state);
+ return std::make_unique<state_diagram> (state_graph, logical_loc_mgr);
}
+++ /dev/null
-/* Capturing changing state in diagnostic paths.
- Copyright (C) 2025 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/>. */
-
-#ifndef GCC_DIAGNOSTIC_STATE_H
-#define GCC_DIAGNOSTIC_STATE_H
-
-/* We want to be able to express changing program states in diagnostic paths,
- so that we can emit this in HTML and SARIF output, and to keep this
- separate from implementation details of -fanalyzer.
-
- For now, we use xml::document as the type in the diagnostic subsystem
- for (optionally) tracking the state at a diagnostic_event. */
-
-namespace xml { class document; }
-namespace dot { class graph; }
-
-extern std::unique_ptr<dot::graph>
-make_dot_graph_from_xml_state (const xml::document &xml_state);
-
-#endif /* GCC_DIAGNOSTIC_STATE_H */
}
}
+void
+diagnostic_context::
+report_global_digraph (const diagnostics::digraphs::lazy_digraph &ldg)
+{
+ for (auto sink : m_output_sinks)
+ sink->report_global_digraph (ldg);
+}
+
/* Get the number of digits in the decimal representation of VALUE. */
int
#include "pretty-print.h"
#include "diagnostic-core.h"
+namespace diagnostics {
+ namespace digraphs {
+ class lazy_digraph;
+ } // namespace digraphs
+} // namespace diagnostics
+
namespace text_art
{
class theme;
bool report_diagnostic (diagnostic_info *);
void report_verbatim (text_info &);
+ /* Report a directed graph associated with the run as a whole
+ to any sinks that support directed graphs. */
+ void
+ report_global_digraph (const diagnostics::digraphs::lazy_digraph &);
+
diagnostic_t
classify_diagnostic (diagnostic_option_id option_id,
diagnostic_t new_kind,
diagrams visualizing the state of memory at each event (inspired by the
"ddd" debugger). These can be seen by pressing 'j' and 'k' to single-step
forward and backward through events. Note that these SVG diagrams are
-created from an intermediate XML representation generated from
-@code{program_state} objects. The XML representation can be easier to
+created from an intermediate SARIF directed graph representation generated from
+@code{program_state} objects. The SARIF representation can be easier to
read - for example, rather than storing the contents of memory via byte
offsets, it uses fields for structs and element indexes for arrays,
recursively. However it is a different representation, and thus bugs could
be hidden by this transformation. Generating the SVG diagrams requires
an invocation of "dot" per event, so it noticeably slows down diagnostic
-emission, hence the opt-in command-line flag. The XML and ``dot''
+emission, hence the opt-in command-line flag. The SARIF and ``dot''
representations can be seen by @code{__analyzer_dump_xml} and
@code{__analyzer_dump_dot} below (writing them to stderr), or by adding
-@code{show-state-diagrams-xml=yes} and
+@code{show-state-diagrams-sarif=yes} and
@code{show-state-diagrams-dot-src=yes} to the html sink, which shows
them within the generated HTML next to the generated SVG.
a name matching the 1st argument (which must be a string literal).
This is for use when debugging, and may be of use in DejaGnu tests.
-@item __analyzer_dump_xml
+@item __analyzer_dump_sarif
@smallexample
-__analyzer_dump_xml ();
+__analyzer_dump_sarif ();
@end smallexample
will dump the copious information about the analyzer's state each time it
@table @gcctabopt
-@item xml-state=@r{[}yes@r{|}no@r{]}
+@item state-graphs=@r{[}yes@r{|}no@r{]}
This is a debugging feature and defaults to @code{no}.
-If @code{xml-state=yes}, then attempt to capture detailed state information
-from @option{-fanalyzer} in the generated SARIF.
+If @code{state-graphs=yes}, then attempt to capture detailed state
+information from @option{-fanalyzer} in the generated SARIF.
@end table
the generated state diagrams will also show the .dot source input to
GraphViz used for the diagram.
-@item show-state-diagrams-xml=@r{[}yes@r{|}no@r{]}
+@item show-state-diagrams-sarif=@r{[}yes@r{|}no@r{]}
This is a debugging feature and defaults to @code{no}.
-If @code{show-state-diagrams-xml=yes}
+If @code{show-state-diagrams-sarif=yes}
then if @code{show-state-diagrams=yes}, the generated state diagrams will
-also show an XML representation of the state.
+also show a SARIF representation of the state.
@end table
* :func:`diagnostic_manager_add_sink_from_spec`
* :func:`diagnostic_manager_set_analysis_target`
+
+.. _LIBGDIAGNOSTICS_ABI_3:
+
+``LIBGDIAGNOSTICS_ABI_3``
+-------------------------
+``LIBGDIAGNOSTICS_ABI_3`` covers the addition of these functions for
+working with directed graphs:
+
+ * :func:`diagnostic_manager_new_graph`
+
+ * :func:`diagnostic_manager_take_global_graph`
+
+ * :func:`diagnostic_take_graph`
+
+ * :func:`diagnostic_graph_release`
+
+ * :func:`diagnostic_graph_set_description`
+
+ * :func:`diagnostic_graph_add_node`
+
+ * :func:`diagnostic_graph_add_edge`
+
+ * :func:`diagnostic_graph_get_node_by_id`
+
+ * :func:`diagnostic_graph_get_edge_by_id`
+
+ * :func:`diagnostic_node_set_label`
+
+ * :func:`diagnostic_node_set_location`
+
+ * :func:`diagnostic_node_set_logical_location`
--- /dev/null
+.. Copyright (C) 2025 Free Software Foundation, Inc.
+ Originally contributed by David Malcolm <dmalcolm@redhat.com>
+
+ This 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 of the License, or
+ (at your option) any later version.
+
+ This program 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 this program. If not, see
+ <https://www.gnu.org/licenses/>.
+
+.. default-domain:: c
+
+Graphs
+======
+
+.. type:: diagnostic_graph
+
+SARIF has support for capturing directed graphs (such as callgraphs
+and control flow graphs), both at the level of the run as a whole,
+and at the level of individual results.
+
+libgdiagnostics supports this with the following entrypoints, allowing
+directed graphs to be
+
+* created (with :func:`diagnostic_manager_new_graph`)
+
+* reported "globally" (with :func:`diagnostic_manager_take_global_graph`)
+
+* reported as part of a :type:`diagnostic` (with :func:`diagnostic_take_graph`), or
+
+* discarded (with :func:`diagnostic_graph_release`).
+
+.. function:: diagnostic_graph * diagnostic_manager_new_graph (diagnostic_manager *manager)
+
+ Create a new directed graph.
+
+ The resulting graph is owned by the caller and must have one of
+ :func:`diagnostic_manager_take_global_graph`,
+ :func:`diagnostic_take_graph`,
+ or :func:`diagnostic_graph_release` called on it to avoid leaks.
+
+ The parameter ``manager`` must be non-null.
+
+ This function was added in :ref:`LIBGDIAGNOSTICS_ABI_3`.
+
+.. function:: void diagnostic_manager_take_global_graph (diagnostic_manager *manager, \
+ diagnostic_graph *graph)
+
+ Report this graph "globally", taking ownership of it.
+ This won't appear in text sinks, but in SARIF sinks the graph will be
+ added to theRun.graphs (SARIF v2.1.0 3.14.20).
+
+ Parameters ``manager`` and ``graph`` must both be non-null.
+
+ This function was added in :ref:`LIBGDIAGNOSTICS_ABI_3`.
+
+.. function:: void diagnostic_take_graph (diagnostic *diag, \
+ diagnostic_graph *graph)
+
+ Add this graph to ``diag``, transferring ownership of it to ``diag``.
+ This won't appear in text sinks, but in SARIF sinks the graph will be
+ added to theResult.graphs (SARIF v2.1.0 3.27.19).
+
+ Parameters ``diag`` and ``graph`` must both be non-null.
+
+ This function was added in :ref:`LIBGDIAGNOSTICS_ABI_3`.
+
+.. function:: void diagnostic_graph_release (diagnostic_graph *graph)
+
+ Release ``graph`` which must still be owned by the caller
+ i.e. it must *not* have had
+ :func:`diagnostic_manager_take_global_graph` or
+ :func:`diagnostic_take_graph` called on it.
+
+ Parameters ``graph`` can be null.
+
+ This function was added in :ref:`LIBGDIAGNOSTICS_ABI_3`.
+
+
+.. function:: void diagnostic_graph_set_description (diagnostic_graph *graph, \
+ const char *description)
+
+ Set the description of ``graph`` for use in the value of the
+ SARIF ``description`` property (SARIF v2.1.0 section 3.39.2).
+
+ The parameter ``graph`` must be non-null.
+ The parameter ``description`` can be null, for clearing any existing
+ description.
+
+ This function was added in :ref:`LIBGDIAGNOSTICS_ABI_3`.
+
+.. function:: diagnostic_node * diagnostic_graph_add_node (diagnostic_graph *graph, \
+ const char *node_id, \
+ diagnostic_node *parent_node)
+
+ Create and add a new node within ``graph`` with the given `id``.
+ The id must be unique within nodes in ``graph``.
+
+ The parameters ``graph`` and ``id`` must be non-null.
+
+ ``parent_node`` can be NULL (for a top-level node in the graph),
+ or non-null for a child node, allowing for arbitrary nesting of
+ nodes.
+
+ The new node is owned by ``graph``.
+
+ This function was added in :ref:`LIBGDIAGNOSTICS_ABI_3`.
+
+.. function:: diagnostic_edge * diagnostic_graph_add_edge (diagnostic_graph *graph, \
+ const char *edge_id, \
+ diagnostic_node *src_node, \
+ diagnostic_node *dst_node, \
+ const char *label)
+
+ Create and add a new edge within ``graph``.
+
+ The parameters ``graph``, ``src_node`` and ``dest_node``
+ must be non-null.
+
+ If non-null, then ``edge_id`` must be unique within ``graph``;
+ if ``edge_id`` is null then a unique id of the form "edge0", "edge1",
+ etc will be used automatically.
+
+ If non-null, then ``label`` will be used for the value of the
+ SARIF ``label`` property (SARIF v2.1.0 section 3.41.3).
+
+ The new edge is owned by ``graph``.
+
+ This function was added in :ref:`LIBGDIAGNOSTICS_ABI_3`.
+
+.. function:: diagnostic_node *diagnostic_graph_get_node_by_id (diagnostic_graph *graph, \
+ const char *node_id)
+
+ Get the node in ``graph`` with the given id, or null.
+
+ The parameters ``graph`` and ``node_id`` must be non-null.
+
+ This function was added in :ref:`LIBGDIAGNOSTICS_ABI_3`.
+
+.. function:: diagnostic_edge *diagnostic_graph_get_edge_by_id (diagnostic_graph *graph, \
+ const char *edge_id)
+
+ Get the edge in ``graph`` with the given id, or null.
+
+ The parameters ``graph`` and ``edge_id`` must be non-null.
+
+ This function was added in :ref:`LIBGDIAGNOSTICS_ABI_3`.
+
+
+.. type:: diagnostic_node
+
+.. function:: void diagnostic_node_set_label (diagnostic_node *node, \
+ const char *label)
+
+ Set the label of ``node`` for use in the value of the
+ SARIF ``label`` property (SARIF v2.1.0 section 3.40.3).
+
+ The parameter ``node`` must be non-null.
+ The parameter ``label`` can be null, for clearing any existing
+ label.
+
+ This function was added in :ref:`LIBGDIAGNOSTICS_ABI_3`.
+
+.. function:: void diagnostic_node_set_location (diagnostic_node *node, \
+ const diagnostic_physical_location *loc)
+
+ Set the physical location of ``node``, if any.
+
+ The parameter ``node`` must be non-null.
+ The parameter ``loc`` can be null, for clearing any existing
+ location.
+
+ If set, the value will be used by SARIF sinks within the
+ ``location`` property (SARIF v2.1.0 section 3.40.4).
+
+ This function was added in :ref:`LIBGDIAGNOSTICS_ABI_3`.
+
+.. function:: void diagnostic_node_set_logical_location (diagnostic_node *node, \
+ const diagnostic_logical_location *logical_loc)
+
+ Set the logical location of ``node``, if any.
+
+ The parameter ``node`` must be non-null.
+ The parameter ``logical_loc`` _can be null, for clearing any existing
+ location.
+
+ If set, the value will be used by SARIF sinks within the
+ ``location`` property (SARIF v2.1.0 section 3.40.4).
+
+ This function was added in :ref:`LIBGDIAGNOSTICS_ABI_3`.
execution-paths.rst
text-output.rst
sarif.rst
+ graphs.rst
ux.rst
compatibility.rst
m_port = std::make_unique<port> (*other.m_port);
}
+ node_id &operator= (const node_id &other)
+ {
+ m_id = other.m_id;
+ if (other.m_port)
+ m_port = std::make_unique<port> (*other.m_port);
+ else
+ m_port = nullptr;
+ return *this;
+ }
+
void print (writer &w) const final override;
id m_id;
void DEBUG_FUNCTION dump () const;
virtual object *dyn_cast_object () { return nullptr; }
+ virtual string *dyn_cast_string () { return nullptr; }
static int compare (const json::value &val_a, const json::value &val_b);
static int compare (const json::object &obj_a, const json::object &obj_b);
+ size_t get_num_keys () const { return m_keys.length (); }
+ const char *get_key (size_t i) const { return m_keys[i]; }
+
std::unique_ptr<object> clone_as_object () const;
private:
enum kind get_kind () const final override { return JSON_STRING; }
void print (pretty_printer *pp, bool formatted) const final override;
std::unique_ptr<value> clone () const final override;
+ string *dyn_cast_string () final override { return this; }
const char *get_string () const { return m_utf8; }
size_t get_length () const { return m_len; }
class group;
class manager;
class diagnostic;
+class graph;
+class node;
+class edge;
/* Wrapper around a borrowed diagnostic_text_sink *. */
void
take_execution_path (execution_path path);
+ void
+ take_graph (graph g);
+
void
finish (const char *fmt, ...)
LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
void
set_analysis_target (file f);
+ void
+ take_global_graph (graph g);
+
diagnostic_manager *m_inner;
bool m_owned;
};
+class graph
+{
+public:
+ graph () : m_inner (nullptr), m_owned (false) {}
+
+ graph (diagnostic_graph *graph)
+ : m_inner (graph), m_owned (true)
+ {}
+
+ graph (const diagnostic_graph *graph)
+ : m_inner (const_cast<diagnostic_graph *> (graph)),
+ m_owned (false)
+ {}
+
+ graph (const graph &other) = delete;
+ graph &operator= (const graph &other) = delete;
+
+ graph (graph &&other)
+ : m_inner (other.m_inner),
+ m_owned (other.m_owned)
+ {
+ other.m_inner = nullptr;
+ other.m_owned = false;
+ }
+
+ graph &operator= (graph &&other)
+ {
+ m_inner = other.m_inner;
+ m_owned = other.m_owned;
+ other.m_inner = nullptr;
+ other.m_owned = false;
+ return *this;
+ }
+
+ ~graph ()
+ {
+ if (m_owned)
+ diagnostic_graph_release (m_inner);
+ }
+
+ void
+ set_description (const char *);
+
+ node
+ get_node_by_id (const char *id) const;
+
+ edge
+ get_edge_by_id (const char *id) const;
+
+ edge
+ add_edge (const char *id, node src_node, node dst_node, const char *label);
+
+ diagnostic_graph *m_inner;
+ bool m_owned;
+};
+
+// Borrowed pointer to a diagnostic_node.
+
+class node
+{
+public:
+ node () : m_inner (nullptr) {}
+ node (diagnostic_node *node_) : m_inner (node_) {}
+
+ void
+ set_label (const char *);
+
+ void
+ set_location (physical_location loc);
+
+ void
+ set_logical_location (logical_location loc);
+
+ diagnostic_node *m_inner;
+};
+
+// Borrowed edge to a diagnostic_edge.
+
+class edge
+{
+public:
+ edge (diagnostic_edge *edge_) : m_inner (edge_) {}
+
+ diagnostic_edge *m_inner;
+};
+
// Implementation
// class file
path.m_owned = false;
}
+inline void
+diagnostic::take_graph (graph g)
+{
+ diagnostic_take_graph (m_inner,
+ g.m_inner);
+ g.m_owned = false;
+}
+
inline void
diagnostic::finish (const char *fmt, ...)
{
diagnostic_manager_set_analysis_target (m_inner, f.m_inner);
}
+inline void
+manager::take_global_graph (graph g)
+{
+ diagnostic_manager_take_global_graph (m_inner,
+ g.m_inner);
+ g.m_owned = false;
+}
+
+// class graph
+
+inline void
+graph::set_description (const char *desc)
+{
+ diagnostic_graph_set_description (m_inner, desc);
+}
+
+inline node
+graph::get_node_by_id (const char *id) const
+{
+ return node (diagnostic_graph_get_node_by_id (m_inner, id));
+}
+
+inline edge
+graph::get_edge_by_id (const char *id) const
+{
+ return edge (diagnostic_graph_get_edge_by_id (m_inner, id));
+}
+
+inline edge
+graph::add_edge (const char *id,
+ node src_node, node dst_node,
+ const char *label)
+{
+ return edge (diagnostic_graph_add_edge (m_inner,
+ id,
+ src_node.m_inner,
+ dst_node.m_inner,
+ label));
+}
+
+// class node
+
+inline void
+node::set_label (const char *label)
+{
+ diagnostic_node_set_label (m_inner, label);
+}
+
+inline void
+node::set_location (physical_location loc)
+{
+ diagnostic_node_set_location (m_inner, loc.m_inner);
+}
+
+inline void
+node::set_logical_location (logical_location loc)
+{
+ diagnostic_node_set_logical_location (m_inner, loc.m_inner);
+}
+
} // namespace libgdiagnostics
#endif // #ifndef LIBGDIAGNOSTICSPP_H
--- /dev/null
+/* Private API entrypoints to libgdiagnostics purely for use by sarif-replay.
+ Copyright (C) 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 LIBGDIAGNOSTICS_PRIVATE_H
+#define LIBGDIAGNOSTICS_PRIVATE_H
+
+#include "libgdiagnostics.h"
+
+namespace json { class object; }
+
+extern "C" {
+
+/* Private entrypoints, for use only by sarif-replay.
+ These are subject to removal without notice. */
+
+/* Entrypoints added in LIBGDIAGNOSTICS_ABI_3. */
+
+extern diagnostic_event_id
+private_diagnostic_execution_path_add_event_2 (diagnostic_execution_path *path,
+ const diagnostic_physical_location *physical_loc,
+ const diagnostic_logical_location *logical_loc,
+ unsigned stack_depth,
+ diagnostic_graph *state_graph,
+ const char *fmt, ...)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+ LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (3)
+ LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (5)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (6)
+ LIBGDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (6, 7);
+
+extern void
+private_diagnostic_graph_set_property_bag (diagnostic_graph &graph,
+ std::unique_ptr<json::object> properties);
+
+extern void
+private_diagnostic_node_set_property_bag (diagnostic_node &node,
+ std::unique_ptr<json::object> properties);
+
+extern void
+private_diagnostic_edge_set_property_bag (diagnostic_edge &edge,
+ std::unique_ptr<json::object> properties);
+
+} // extern "C"
+
+#endif /* LIBGDIAGNOSTICS_PRIVATE_H */
#include "config.h"
#define INCLUDE_MAP
+#define INCLUDE_STRING
#define INCLUDE_VECTOR
#include "system.h"
#include "coretypes.h"
#include "diagnostic-format-sarif.h"
#include "diagnostic-format-text.h"
#include "diagnostic-output-spec.h"
+#include "diagnostic-digraphs.h"
+#include "diagnostic-state-graphs.h"
#include "logical-location.h"
#include "edit-context.h"
#include "libgdiagnostics.h"
+#include "libgdiagnostics-private.h"
class owned_nullable_string
{
return m_prev_diag_logical_loc;
}
+ void
+ take_global_graph (std::unique_ptr<diagnostic_graph> graph);
+
private:
void
ensure_linemap_for_file_and_line (const diagnostic_file *file,
owned_nullable_string m_url;
};
+struct diagnostic_graph : public diagnostics::digraphs::digraph
+{
+ diagnostic_graph (diagnostic_manager &) {}
+
+ diagnostic_node *
+ add_node_with_id (std::string id,
+ diagnostic_node *parent_node);
+ diagnostic_edge *
+ add_edge_with_label (const char *id,
+ diagnostic_node &src_node,
+ diagnostic_node &dst_node,
+ const char *label);
+};
+
+struct diagnostic_node : public diagnostics::digraphs::node
+{
+ diagnostic_node (diagnostic_graph &g,
+ std::string id)
+ : node (g, std::move (id))
+ {
+ }
+};
+
+struct diagnostic_edge : public diagnostics::digraphs::edge
+{
+ diagnostic_edge (diagnostic_graph &g,
+ const char *id,
+ diagnostic_node &src_node,
+ diagnostic_node &dst_node)
+ : edge (g, id, src_node, dst_node)
+ {
+ }
+};
+
class libgdiagnostics_path_event : public diagnostic_event
{
public:
libgdiagnostics_path_event (const diagnostic_physical_location *physical_loc,
const diagnostic_logical_location *logical_loc,
unsigned stack_depth,
+ std::unique_ptr<diagnostic_graph> state_graph,
const char *gmsgid,
va_list *args)
: m_physical_loc (physical_loc),
m_logical_loc (logical_loc),
- m_stack_depth (stack_depth)
+ m_stack_depth (stack_depth),
+ m_state_graph (std::move (state_graph))
{
m_desc_uncolored = make_desc (gmsgid, args, false);
m_desc_colored = make_desc (gmsgid, args, true);
return 0;
}
+ std::unique_ptr<diagnostics::digraphs::digraph>
+ maybe_make_diagnostic_state_graph (bool) const final override
+ {
+ if (!m_state_graph)
+ return nullptr;
+
+ return m_state_graph->clone ();
+ }
+
private:
static label_text make_desc (const char *gmsgid,
va_list *args,
const diagnostic_physical_location *m_physical_loc;
const diagnostic_logical_location *m_logical_loc;
unsigned m_stack_depth;
+ std::unique_ptr<diagnostic_graph> m_state_graph;
label_text m_desc_uncolored;
label_text m_desc_colored;
};
add_event_va (const diagnostic_physical_location *physical_loc,
const diagnostic_logical_location *logical_loc,
unsigned stack_depth,
+ std::unique_ptr<diagnostic_graph> state_graph,
const char *gmsgid,
va_list *args)
{
(std::make_unique<libgdiagnostics_path_event> (physical_loc,
logical_loc,
stack_depth,
+ std::move (state_graph),
gmsgid,
args));
return m_events.size () - 1;
std::vector<std::unique_ptr<libgdiagnostics_path_event>> m_events;
};
+class prebuilt_digraphs : public diagnostics::digraphs::lazy_digraphs
+{
+public:
+ using digraph = diagnostics::digraphs::digraph;
+
+ std::unique_ptr<std::vector<std::unique_ptr<digraph>>>
+ create_digraphs () const final override
+ {
+ return std::make_unique<std::vector<std::unique_ptr<digraph>>> (std::move (m_digraphs));
+ }
+
+ void
+ take_graph (std::unique_ptr<diagnostic_graph> graph)
+ {
+ m_digraphs.push_back (std::move (graph));
+ }
+
+private:
+ mutable std::vector<std::unique_ptr<digraph>> m_digraphs;
+};
+
/* This has to be a "struct" as it is exposed in the C API. */
struct diagnostic
m_rich_loc (diag_mgr.get_line_table ()),
m_logical_loc (nullptr),
m_path (nullptr)
- {}
+ {
+ m_metadata.set_lazy_digraphs (&m_graphs);
+ }
diagnostic_manager &get_manager () const
{
m_rich_loc.set_path (path);
}
+ void
+ take_graph (std::unique_ptr<diagnostic_graph> graph)
+ {
+ m_graphs.take_graph (std::move (graph));
+ }
+
+ const prebuilt_digraphs &
+ get_graphs () const
+ {
+ return m_graphs;
+ }
+
private:
diagnostic_manager &m_diag_mgr;
enum diagnostic_level m_level;
impl_rich_location m_rich_loc;
const diagnostic_logical_location *m_logical_loc;
diagnostic_metadata m_metadata;
+ prebuilt_digraphs m_graphs;
std::vector<std::unique_ptr<range_label>> m_labels;
std::vector<std::unique_ptr<impl_rule>> m_rules;
std::unique_ptr<diagnostic_execution_path> m_path;
return new diagnostic_execution_path (*mgr);
}
+void
+diagnostic_manager::take_global_graph (std::unique_ptr<diagnostic_graph> graph)
+{
+ class prebuilt_lazy_digraph : public diagnostics::digraphs::lazy_digraph
+ {
+ public:
+ prebuilt_lazy_digraph (std::unique_ptr<diagnostic_graph> graph)
+ : m_graph (std::move (graph))
+ {
+ }
+
+ std::unique_ptr<diagnostics::digraphs::digraph>
+ create_digraph () const final override
+ {
+ return std::move (m_graph);
+ }
+
+ private:
+ mutable std::unique_ptr<diagnostic_graph> m_graph;
+ };
+
+ m_dc.report_global_digraph (prebuilt_lazy_digraph (std::move (graph)));
+}
/* Error-checking at the API boundary. */
#define FAIL_IF_NULL(PTR_ARG) \
diagnostic_event_id_t result = path->add_event_va (physical_loc,
logical_loc,
stack_depth,
+ nullptr,
gmsgid, &args);
va_end (args);
diagnostic_event_id_t result = path->add_event_va (physical_loc,
logical_loc,
stack_depth,
+ nullptr,
gmsgid, args);
return as_diagnostic_event_id (result);
}
mgr->get_dc ().set_main_input_filename (file->get_name ());
}
+
+// struct diagnostic_graph : public diagnostics::digraphs::graph<foo_traits>
+
+diagnostic_node *
+diagnostic_graph::add_node_with_id (std::string id,
+ diagnostic_node *parent_node)
+{
+ auto node_up = std::make_unique<diagnostic_node> (*this, std::move (id));
+ diagnostic_node *new_node = node_up.get ();
+ if (parent_node)
+ parent_node->add_child (std::move (node_up));
+ else
+ add_node (std::move (node_up));
+ return new_node;
+}
+
+diagnostic_edge *
+diagnostic_graph::add_edge_with_label (const char *id,
+ diagnostic_node &src_node,
+ diagnostic_node &dst_node,
+ const char *label)
+{
+ auto edge_up
+ = std::make_unique<diagnostic_edge> (*this, id,
+ src_node, dst_node);
+ diagnostic_edge *new_edge = edge_up.get ();
+ if (label)
+ new_edge->set_label (label);
+ add_edge (std::move (edge_up));
+ return new_edge;
+}
+
+/* Public entrypoint. */
+
+diagnostic_graph *
+diagnostic_manager_new_graph (diagnostic_manager *manager)
+{
+ FAIL_IF_NULL (manager);
+
+ return new diagnostic_graph (*manager);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_manager_take_global_graph (diagnostic_manager *manager,
+ diagnostic_graph *graph)
+{
+ FAIL_IF_NULL (manager);
+ FAIL_IF_NULL (graph);
+
+ manager->take_global_graph (std::unique_ptr<diagnostic_graph> (graph));
+}
+
+void
+diagnostic_take_graph (diagnostic *diag,
+ diagnostic_graph *graph)
+{
+ FAIL_IF_NULL (diag);
+ FAIL_IF_NULL (graph);
+
+ diag->take_graph (std::unique_ptr<diagnostic_graph> (graph));
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_graph_release (diagnostic_graph *graph)
+{
+ delete graph;
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_graph_set_description (diagnostic_graph *graph,
+ const char *desc)
+{
+ FAIL_IF_NULL (graph);
+
+ graph->set_description (desc);
+}
+
+diagnostic_node *
+diagnostic_graph_add_node (diagnostic_graph *graph,
+ const char *id,
+ diagnostic_node *parent_node)
+{
+ FAIL_IF_NULL (graph);
+ FAIL_IF_NULL (id);
+
+ return graph->add_node_with_id (id, parent_node);
+}
+
+/* Public entrypoint. */
+
+diagnostic_edge *
+diagnostic_graph_add_edge (diagnostic_graph *graph,
+ const char *id,
+ diagnostic_node *src_node,
+ diagnostic_node *dst_node,
+ const char *label)
+{
+ FAIL_IF_NULL (graph);
+ FAIL_IF_NULL (src_node);
+ FAIL_IF_NULL (dst_node);
+
+ return graph->add_edge_with_label (id, *src_node, *dst_node, label);
+}
+
+/* Public entrypoint. */
+
+diagnostic_node *
+diagnostic_graph_get_node_by_id (diagnostic_graph *graph,
+ const char *id)
+{
+ FAIL_IF_NULL (graph);
+ FAIL_IF_NULL (id);
+
+ return static_cast<diagnostic_node *> (graph->get_node_by_id (id));
+}
+
+/* Public entrypoint. */
+
+diagnostic_edge *
+diagnostic_graph_get_edge_by_id (diagnostic_graph *graph,
+ const char *id)
+{
+ FAIL_IF_NULL (graph);
+ FAIL_IF_NULL (id);
+
+ return static_cast<diagnostic_edge *> (graph->get_edge_by_id (id));
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_node_set_location (diagnostic_node *node,
+ const diagnostic_physical_location *loc)
+{
+ FAIL_IF_NULL (node);
+
+ node->set_physical_loc (loc ? loc->m_inner : UNKNOWN_LOCATION);
+}
+
+/* Public entrypoint. */
+
+void
+diagnostic_node_set_label (diagnostic_node *node,
+ const char *label)
+{
+ FAIL_IF_NULL (node);
+
+ node->set_label (label);
+}
+
+void
+diagnostic_node_set_logical_location (diagnostic_node *node,
+ const diagnostic_logical_location *logical_loc)
+{
+ FAIL_IF_NULL (node);
+
+ node->set_logical_loc
+ (impl_logical_location_manager::key_from_ptr (logical_loc));
+}
+
+/* Private entrypoint. */
+
+diagnostic_event_id
+private_diagnostic_execution_path_add_event_2 (diagnostic_execution_path *path,
+ const diagnostic_physical_location *physical_loc,
+ const diagnostic_logical_location *logical_loc,
+ unsigned stack_depth,
+ diagnostic_graph *state_graph,
+ const char *gmsgid, ...)
+
+{
+ FAIL_IF_NULL (path);
+ FAIL_IF_NULL (gmsgid);
+
+ va_list args;
+ va_start (args, gmsgid);
+ diagnostic_event_id_t result
+ = path->add_event_va (physical_loc,
+ logical_loc,
+ stack_depth,
+ std::unique_ptr <diagnostic_graph> (state_graph),
+ gmsgid, &args);
+ va_end (args);
+
+ return as_diagnostic_event_id (result);
+
+}
+
+/* Private entrypoint. */
+
+void
+private_diagnostic_graph_set_property_bag (diagnostic_graph &graph,
+ std::unique_ptr<json::object> properties)
+{
+ graph.set_property_bag (std::move (properties));
+}
+
+/* Private entrypoint. */
+
+void
+private_diagnostic_node_set_property_bag (diagnostic_node &node,
+ std::unique_ptr<json::object> properties)
+{
+ node.set_property_bag (std::move (properties));
+}
+
+/* Private entrypoint. */
+
+void
+private_diagnostic_edge_set_property_bag (diagnostic_edge &edge,
+ std::unique_ptr<json::object> properties)
+{
+ edge.set_property_bag (std::move (properties));
+}
LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+/* Directed graphs. */
+
+typedef struct diagnostic_graph diagnostic_graph;
+typedef struct diagnostic_node diagnostic_node;
+typedef struct diagnostic_edge diagnostic_edge;
+
+/* Create a new graph.
+ This is owned by the caller and must have one of
+ diagnostic_manager_take_global_graph, diagnostic_take_graph,
+ or diagnostic_graph_release called on it.
+ Added in LIBGDIAGNOSTICS_ABI_3. */
+
+extern diagnostic_graph *
+diagnostic_manager_new_graph (diagnostic_manager *manager)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Report this graph "globally", taking ownership of it.
+ Added in LIBGDIAGNOSTICS_ABI_3. */
+
+extern void
+diagnostic_manager_take_global_graph (diagnostic_manager *manager,
+ diagnostic_graph *graph)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Add this graph to DIAG, transferring ownership to it.
+ Added in LIBGDIAGNOSTICS_ABI_3. */
+
+extern void
+diagnostic_take_graph (diagnostic *diag,
+ diagnostic_graph *graph)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Release this graph. Added in LIBGDIAGNOSTICS_ABI_3. */
+
+extern void
+diagnostic_graph_release (diagnostic_graph *graph)
+ LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (1);
+
+/* Set the description of GRAPH for use
+ in the value of the SARIF "description" property
+ (SARIF v2.1.0 section 3.39.2).
+ Added in LIBGDIAGNOSTICS_ABI_3. */
+
+extern void
+diagnostic_graph_set_description (diagnostic_graph *graph,
+ const char *description)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (2);
+
+/* Create and add a new node within GRAPH.
+ NODE_ID must be unique within nodes in GRAPH.
+ The new node is owned by GRAPH.
+ PARENT_NODE can be NULL (for a top-level node in the graph),
+ or non-null for a child node.
+ Added in LIBGDIAGNOSTICS_ABI_3. */
+
+extern diagnostic_node *
+diagnostic_graph_add_node (diagnostic_graph *graph,
+ const char *node_id,
+ diagnostic_node *parent_node)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
+ LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (3);
+
+/* Create and add a new edge within GRAPH.
+
+ If non-null, then EDGE_ID must be unique within edges in GRAPH;
+ if EDGE_ID is null then a unique id of the form "edge0", "edge1", etc
+ will be used automatically.
+
+ The new edge is owned by GRAPH.
+ Added in LIBGDIAGNOSTICS_ABI_3. */
+
+extern diagnostic_edge *
+diagnostic_graph_add_edge (diagnostic_graph *graph,
+ const char *edge_id,
+ diagnostic_node *src_node,
+ diagnostic_node *dst_node,
+ const char *label)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (4)
+ LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (5);
+
+/* Get the node in GRAPH with the given id, or null.
+ Added in LIBGDIAGNOSTICS_ABI_3. */
+
+extern diagnostic_node *
+diagnostic_graph_get_node_by_id (diagnostic_graph *graph,
+ const char *node_id)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Get the edge in GRAPH with the given id, or null.
+ Added in LIBGDIAGNOSTICS_ABI_3. */
+
+extern diagnostic_edge *
+diagnostic_graph_get_edge_by_id (diagnostic_graph *graph,
+ const char *edge_id)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Set the label of NODE for use
+ in the value of the SARIF "label" property
+ (SARIF v2.1.0 section 3.40.3).
+ Added in LIBGDIAGNOSTICS_ABI_3. */
+
+extern void
+diagnostic_node_set_label (diagnostic_node *node,
+ const char *label)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (2);
+
+/* Set the physical location of NODE.
+ Added in LIBGDIAGNOSTICS_ABI_3. */
+
+extern void
+diagnostic_node_set_location (diagnostic_node *node,
+ const diagnostic_physical_location *loc)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (2);
+
+/* Set the logical location of NODE.
+ Added in LIBGDIAGNOSTICS_ABI_3. */
+
+extern void
+diagnostic_node_set_logical_location (diagnostic_node *node,
+ const diagnostic_logical_location *logical_loc)
+ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+ LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (2);
+
/* DEFERRED:
- thread-safety
- plural forms
diagnostic_manager_add_sink_from_spec;
diagnostic_manager_set_analysis_target;
} LIBGDIAGNOSTICS_ABI_1;
+
+# Add hooks needed for diagnostic_graph support.
+LIBGDIAGNOSTICS_ABI_3 {
+ global:
+ diagnostic_manager_new_graph;
+ diagnostic_manager_take_global_graph;
+ diagnostic_take_graph;
+ diagnostic_graph_release;
+ diagnostic_graph_set_description;
+ diagnostic_graph_add_node;
+ diagnostic_graph_add_edge;
+ diagnostic_graph_get_node_by_id;
+ diagnostic_graph_get_edge_by_id;
+ diagnostic_node_set_label;
+ diagnostic_node_set_location;
+ diagnostic_node_set_logical_location;
+
+ # Private hooks used by sarif-replay
+ private_diagnostic_execution_path_add_event_2;
+ private_diagnostic_graph_set_property_bag;
+ private_diagnostic_node_set_property_bag;
+ private_diagnostic_edge_set_property_bag;
+} LIBGDIAGNOSTICS_ABI_2;
#include "system.h"
#include "coretypes.h"
#include "libgdiagnostics++.h"
+#include "libgdiagnostics-private.h"
#include "json-parsing.h"
#include "intl.h"
#include "sarif-spec-urls.def"
label_text m_label;
};
+using id_map = std::map<std::string, const json::string *>;
+
class sarif_replayer
{
public:
const json::object &run_obj,
libgdiagnostics::execution_path &out);
+ // "graph" object (§3.39)
+ enum status
+ handle_graph_object (const json::object &graph_obj,
+ const json::object &run_obj,
+ libgdiagnostics::graph &out);
+ // "node" object (§3.40)
+ libgdiagnostics::node
+ handle_node_object (const json::object &node_obj,
+ const json::object &run_obj,
+ libgdiagnostics::graph &graph,
+ libgdiagnostics::node parent_node,
+ id_map &node_id_map);
+
+ // "edge" object (§3.41)
+ libgdiagnostics::edge
+ handle_edge_object (const json::object &edge_obj,
+ libgdiagnostics::graph &graph,
+ id_map &edge_id_map);
+
+ libgdiagnostics::node
+ get_graph_node_by_id_property (const json::object &edge_json_object,
+ const property_spec_ref &id_prop,
+ libgdiagnostics::graph &graph);
+
// reportingDescriptor lookup (§3.52.3)
const json::object *
lookup_rule_by_id_in_tool (const char *rule_id,
{
va_list ap;
va_start (ap, gmsgid);
- report_problem (jv, ref, gmsgid, &ap, DIAGNOSTIC_LEVEL_ERROR);
+ report_problem (jv, &ref, gmsgid, &ap, DIAGNOSTIC_LEVEL_ERROR);
va_end (ap);
return status::err_invalid_sarif;
}
{
va_list ap;
va_start (ap, gmsgid);
- report_problem (jv, ref, gmsgid, &ap, DIAGNOSTIC_LEVEL_SORRY);
+ report_problem (jv, &ref, gmsgid, &ap, DIAGNOSTIC_LEVEL_SORRY);
va_end (ap);
return status::err_unhandled_sarif;
}
+ void
+ report_note (const json::value &jv,
+ const char *gmsgid, ...)
+ LIBGDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (3, 4)
+ {
+ va_list ap;
+ va_start (ap, gmsgid);
+ report_problem (jv, nullptr, gmsgid, &ap, DIAGNOSTIC_LEVEL_NOTE);
+ va_end (ap);
+ }
+
void
report_problem (const json::value &jv,
- const spec_ref &ref,
+ const spec_ref *ref,
const char *gmsgid,
va_list *args,
enum diagnostic_level level)
There doesn't seem to be a systematic mapping from spec sections to
HTML anchors, so we can't provide URLs
(filed as https://github.com/oasis-tcs/sarif-spec/issues/533 ). */
- char *ref_desc = ref.make_description ();
- char *ref_url = ref.make_url ();
- diag.add_rule (ref_desc, ref_url);
- free (ref_desc);
- free (ref_url);
+ if (ref)
+ {
+ char *ref_desc = ref->make_description ();
+ char *ref_url = ref->make_url ();
+ diag.add_rule (ref_desc, ref_url);
+ free (ref_desc);
+ free (ref_url);
+ }
auto loc_range
= make_physical_location (m_control_mgr,
const string_property_value<ValueType> *value_arr,
size_t num_values);
+ const json::object *
+ maybe_get_property_bag (const json::object &obj)
+ {
+ const property_spec_ref properties
+ ("object", "properties", "3.8.1");
+ return get_optional_property<json::object> (obj, properties);
+ }
+
+ /* Look for a property bag within OBJ.
+ If found, look for a property within it named PROPERTY_NAME
+ of the given type.
+ If successful, return the property's value.
+ Otherwise, return nullptr without complaining (unless the property bag
+ is itself not an object). */
+ template <typename JsonType>
+ const JsonType *
+ maybe_get_property_bag_value (const json::object &obj,
+ const char *property_name)
+ {
+ auto property_bag_obj = maybe_get_property_bag (obj);
+ if (!property_bag_obj)
+ return nullptr;
+ const json::value *property_val = property_bag_obj->get (property_name);
+ if (!property_val)
+ return nullptr;
+ const JsonType *sub = dyn_cast<const JsonType *> (property_val);
+ if (!sub)
+ /* Property is wrong kind of value. Don't treat this as an error. */
+ return nullptr;
+ return sub;
+ }
+
/* The manager to replay the SARIF files to. */
libgdiagnostics::manager m_output_mgr;
break;
}
+ // §3.14.20 "graphs"
+ const property_spec_ref prop_graphs ("run", "graphs", "3.14.20");
+ if (const json::array *graphs
+ = get_optional_property<json::array> (run_obj,
+ prop_graphs))
+ {
+ for (auto element : *graphs)
+ {
+ if (const json::object *graph_json_obj
+ = require_object_for_element (*element, prop_graphs))
+ {
+ libgdiagnostics::graph graph;
+ enum status s = handle_graph_object (*graph_json_obj,
+ run_obj,
+ graph);
+ if (s != status::ok)
+ return s;
+
+ m_output_mgr.take_global_graph (std::move (graph));
+ }
+ else
+ return status::err_invalid_sarif;
+ }
+ }
+
return status::ok;
}
if (path.m_inner)
err.take_execution_path (std::move (path));
+ // §3.27.19 "graphs" property
+ const property_spec_ref prop_graphs ("result", "graphs", "3.27.19");
+ if (const json::array *graphs
+ = get_optional_property<json::array> (result_obj,
+ prop_graphs))
+ {
+ for (auto element : *graphs)
+ {
+ if (const json::object *graph_json_obj
+ = require_object_for_element (*element, prop_graphs))
+ {
+ libgdiagnostics::graph graph;
+ enum status s = handle_graph_object (*graph_json_obj,
+ run_obj,
+ graph);
+ if (s != status::ok)
+ return s;
+
+ err.take_graph (std::move (graph));
+ }
+ else
+ return status::err_invalid_sarif;
+ }
+ }
+
// §3.27.22 relatedLocations property
std::vector<std::pair<libgdiagnostics::diagnostic, label_text>> notes;
const property_spec_ref prop_related_locations
}
}
+ libgdiagnostics::graph state_graph;
+ if (auto sarif_state_graph
+ = maybe_get_property_bag_value<json::object> (tflow_loc_obj,
+ "gcc/diagnostic_event/state_graph"))
+ {
+ enum status s
+ = handle_graph_object (*sarif_state_graph, run_obj, state_graph);
+ if (s != status::ok)
+ return s;
+ }
+
if (message.get ())
- path.add_event (physical_loc,
- logical_loc,
- stack_depth,
- "%s", message.get ());
+ private_diagnostic_execution_path_add_event_2 (path.m_inner,
+ physical_loc.m_inner,
+ logical_loc.m_inner,
+ stack_depth,
+ state_graph.m_inner,
+ "%s", message.get ());
else
- path.add_event (physical_loc,
- logical_loc,
- stack_depth,
- "");
+ private_diagnostic_execution_path_add_event_2 (path.m_inner,
+ physical_loc.m_inner,
+ logical_loc.m_inner,
+ stack_depth,
+ state_graph.m_inner,
+ "");
+ state_graph.m_owned = false;
return status::ok;
}
return status::ok;
}
+// "graph" object (§3.39)
+
+enum status
+sarif_replayer::handle_graph_object (const json::object &graph_json_obj,
+ const json::object &run_obj,
+ libgdiagnostics::graph &out_graph)
+{
+ out_graph = libgdiagnostics::graph
+ (diagnostic_manager_new_graph (m_output_mgr.m_inner));
+
+ id_map node_id_map;
+ id_map edge_id_map;
+
+ if (auto properties = maybe_get_property_bag (graph_json_obj))
+ private_diagnostic_graph_set_property_bag (*out_graph.m_inner,
+ properties->clone_as_object ());
+
+ // §3.39.2: MAY contain a "description" property
+ const property_spec_ref description_prop
+ ("graph", "description", "3.39.2");
+ if (auto description_obj
+ = get_optional_property<json::object> (graph_json_obj, description_prop))
+ {
+ label_text text
+ = make_plain_text_within_result_message (&run_obj,
+ *description_obj,
+ nullptr);
+ if (!text.get ())
+ return status::err_invalid_sarif;
+ out_graph.set_description (text.get ());
+ }
+
+ // §3.39.3: MAY contain a "nodes" property
+ const property_spec_ref nodes_prop
+ ("graph", "nodes", "3.39.3");
+ if (auto nodes_arr
+ = get_optional_property<json::array> (graph_json_obj, nodes_prop))
+ {
+ for (auto element : *nodes_arr)
+ {
+ const json::object *node_json_obj
+ = require_object_for_element (*element, nodes_prop);
+ if (!node_json_obj)
+ return status::err_invalid_sarif;
+ libgdiagnostics::node node
+ = handle_node_object (*node_json_obj, run_obj, out_graph,
+ nullptr, node_id_map);
+ if (node.m_inner == nullptr)
+ return status::err_invalid_sarif;
+ }
+ }
+ else
+ // If we have no nodes, we can't handle the edges.
+ return status::ok;
+
+ // §3.39.4 edges property
+ const property_spec_ref edges_prop
+ ("graph", "edges", "3.39.4");
+ if (auto edges_arr
+ = get_optional_property<json::array> (graph_json_obj, edges_prop))
+ {
+ for (auto element : *edges_arr)
+ {
+ const json::object *edge_json_obj
+ = require_object_for_element (*element, edges_prop);
+ if (!edge_json_obj)
+ return status::err_invalid_sarif;
+ libgdiagnostics::edge edge
+ = handle_edge_object (*edge_json_obj, out_graph, edge_id_map);
+ if (edge.m_inner == nullptr)
+ return status::err_invalid_sarif;
+ }
+ }
+
+ return status::ok;
+}
+
+// "node" object (§3.40)
+
+libgdiagnostics::node
+sarif_replayer::handle_node_object (const json::object &node_json_obj,
+ const json::object &run_obj,
+ libgdiagnostics::graph &graph,
+ libgdiagnostics::node parent_node,
+ id_map &node_id_map)
+{
+ // §3.40.2 "id" property
+ const property_spec_ref id_prop ("node", "id", "3.40.2");
+ auto id_str = get_required_property<json::string> (node_json_obj, id_prop);
+ if (!id_str)
+ return nullptr;
+ const char *id = id_str->get_string ();
+ if (diagnostic_graph_get_node_by_id (graph.m_inner, id))
+ {
+ // Duplicate id; fail:
+ libgdiagnostics::group g (m_control_mgr);
+ report_invalid_sarif (*id_str,
+ id_prop,
+ "duplicate node id %qs within graph",
+ id);
+ gcc_assert (node_id_map[id]);
+ report_note (*node_id_map[id],
+ "%qs already used as node id within graph here",
+ id);
+ return nullptr;
+ }
+ node_id_map[id] = id_str;
+
+ libgdiagnostics::node new_node
+ = libgdiagnostics::node (diagnostic_graph_add_node (graph.m_inner,
+ id,
+ parent_node.m_inner));
+ if (auto properties = maybe_get_property_bag (node_json_obj))
+ private_diagnostic_node_set_property_bag (*new_node.m_inner,
+ properties->clone_as_object ());
+
+ // §3.40.3 "label" property
+ const property_spec_ref label_prop
+ ("node", "label", "3.40.3");
+ if (auto label_obj
+ = get_optional_property<json::object> (node_json_obj, label_prop))
+ {
+ label_text text
+ = make_plain_text_within_result_message (&run_obj,
+ *label_obj,
+ nullptr);
+ if (!text.get ())
+ return nullptr;
+ new_node.set_label (text.get ());
+ }
+
+ // §3.40.4 "location" property
+ const property_spec_ref location_prop ("node", "location", "3.40.4");
+ if (auto location_json_obj
+ = get_optional_property<json::object> (node_json_obj, location_prop))
+ {
+ libgdiagnostics::physical_location physical_loc;
+ libgdiagnostics::logical_location logical_loc;
+ enum status s = handle_location_object (*location_json_obj,
+ run_obj,
+ physical_loc,
+ logical_loc,
+ nullptr); // annotations
+ if (s != status::ok)
+ return nullptr;
+
+ new_node.set_location (physical_loc);
+ new_node.set_logical_location (logical_loc);
+ }
+
+ // §3.40.5: MAY contain a "children" property
+ const property_spec_ref children_prop
+ ("graph", "children", "3.40.5");
+ if (auto children_json_arr
+ = get_optional_property<json::array> (node_json_obj, children_prop))
+ {
+ for (auto json_element : *children_json_arr)
+ {
+ const json::object *child_json_obj
+ = require_object_for_element (*json_element, children_prop);
+ if (!child_json_obj)
+ return nullptr;
+ libgdiagnostics::node child_node
+ = handle_node_object (*child_json_obj, run_obj, graph,
+ new_node, node_id_map);
+ if (child_node.m_inner == nullptr)
+ return nullptr;
+ }
+ }
+
+ return new_node;
+}
+
+// "edge" object (§3.41)
+
+libgdiagnostics::edge
+sarif_replayer::handle_edge_object (const json::object &edge_json_obj,
+ libgdiagnostics::graph &graph,
+ id_map &edge_id_map)
+{
+ // §3.41.2 "id" property
+ const property_spec_ref id_prop ("edge", "id", "3.41.2");
+ auto id_str = get_required_property<json::string> (edge_json_obj, id_prop);
+ if (!id_str)
+ return nullptr;
+ const char *id = id_str->get_string ();
+ if (diagnostic_graph_get_edge_by_id (graph.m_inner, id))
+ {
+ // Duplicate id; fail:
+ libgdiagnostics::group g (m_control_mgr);
+ report_invalid_sarif (*id_str,
+ id_prop,
+ "duplicate edge id %qs within graph",
+ id);
+ gcc_assert (edge_id_map[id]);
+ report_note (*edge_id_map[id],
+ "%qs already used as edge id within graph here",
+ id);
+ return nullptr;
+ }
+ edge_id_map[id] = id_str;
+
+ // §3.41.3 "label" property
+ label_text label;
+ const property_spec_ref label_prop
+ ("edge", "label", "3.41.3");
+ if (auto label_obj
+ = get_optional_property<json::object> (edge_json_obj, label_prop))
+ {
+ label = make_plain_text_within_result_message (nullptr,
+ *label_obj,
+ nullptr);
+ if (!label.get ())
+ return nullptr;
+ }
+
+ // §3.41.4 "sourceNodeId" property
+ const property_spec_ref src_id_prop ("edge", "sourceNodeId", "3.41.4");
+ auto src_node = get_graph_node_by_id_property (edge_json_obj,
+ src_id_prop,
+ graph);
+ if (!src_node.m_inner)
+ return nullptr;
+
+ // §3.41.5 "targetNodeId" property
+ const property_spec_ref dst_id_prop ("edge", "targetNodeId", "3.41.5");
+ auto dst_node = get_graph_node_by_id_property (edge_json_obj,
+ dst_id_prop,
+ graph);
+ if (!dst_node.m_inner)
+ return nullptr;
+
+ auto result = graph.add_edge (id, src_node, dst_node, label.get ());
+
+ if (auto properties = maybe_get_property_bag (edge_json_obj))
+ private_diagnostic_edge_set_property_bag (*result.m_inner,
+ properties->clone_as_object ());
+
+ return result;
+}
+
+libgdiagnostics::node
+sarif_replayer::
+get_graph_node_by_id_property (const json::object &edge_json_obj,
+ const property_spec_ref &id_prop,
+ libgdiagnostics::graph &graph)
+{
+ auto id_str = get_required_property<json::string> (edge_json_obj, id_prop);
+ if (!id_str)
+ return nullptr;
+ const char *id = id_str->get_string ();
+ auto node = graph.get_node_by_id (id);
+ if (!node.m_inner)
+ {
+ // id not found; complain:
+ report_invalid_sarif (*id_str,
+ id_prop,
+ "no node with id %qs in graph",
+ id);
+ return nullptr;
+ }
+ return node;
+}
+
// 3.52.3 reportingDescriptor lookup
// "For an example of the interaction between ruleId and rule.id, see §3.52.4."
diagnostic_show_locus_cc_tests ();
diagnostic_format_html_cc_tests ();
diagnostic_format_sarif_cc_tests ();
+ diagnostic_digraphs_cc_tests ();
diagnostic_output_spec_cc_tests ();
+ diagnostic_state_graphs_cc_tests ();
edit_context_cc_tests ();
fold_const_cc_tests ();
spellcheck_cc_tests ();
extern void convert_cc_tests ();
extern void dbgcnt_cc_tests ();
extern void diagnostic_color_cc_tests ();
+extern void diagnostic_digraphs_cc_tests ();
extern void diagnostic_format_html_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 diagnostic_state_graphs_cc_tests ();
extern void digraph_cc_tests ();
extern void dumpfile_cc_tests ();
extern void edit_context_cc_tests ();
def sarif():
return sarif_from_env()
-def test_xml_state(sarif):
+def test_state_graph(sarif):
result = get_result_by_index(sarif, 0)
assert result['level'] == 'warning'
# Event "(1)": "entry to 'test'" (index == 0)
assert events[0]['location']['message']['text'] == "entry to 'test'"
- state0 = get_xml_state(events, 0)
- memory_regions = state0.find('memory-regions')
- assert memory_regions is not None
- stack = memory_regions.find('stack')
- assert stack is not None
- frame = stack.find('stack-frame')
- assert frame.get('function') == 'test'
+ state0 = get_state_graph(events, 0)
+
+ stack = state0['nodes'][0]
+ assert stack['id'] == 'stack'
+ assert get_state_node_kind(stack) == 'stack'
+
+ frame = stack['children'][0]
+ assert frame['id'].startswith('frame-region-')
+ assert get_state_node_kind(frame) == 'stack-frame'
+ assert get_state_node_attr(frame, 'function') == 'test'
+ assert frame['location']['logicalLocations'][0]['fullyQualifiedName'] == 'test'
# Final event:
assert events[-1]['location']['message']['text'].startswith("use after 'free' of ")
- state = get_xml_state(events, -1)
- # TODO
+ state = get_state_graph(events, -1)
+
+ stack = state['nodes'][0]
+ assert stack['id'] == 'stack'
+ assert get_state_node_kind(stack) == 'stack'
+
+ frame = stack['children'][0]
+ assert frame['id'].startswith('frame-region-')
+ assert get_state_node_kind(frame) == 'stack-frame'
+ assert get_state_node_attr(frame, 'function') == 'test'
+ assert frame['location']['logicalLocations'][0]['fullyQualifiedName'] == 'test'
+
+ heap = state['nodes'][1]
+ assert heap['id'] == 'heap'
+ assert get_state_node_kind(heap) == 'heap'
+
+ assert len(heap['children']) == 3
+ heap_buffer0 = heap['children'][0]
+ assert heap_buffer0['id'].startswith('heap-allocated-region-')
+ assert get_state_node_kind(heap_buffer0) == 'dynalloc-buffer'
+
+ globals_ = state['nodes'][2]
+ assert globals_['id'] == 'globals'
+ assert get_state_node_kind(globals_) == 'globals'
+ first = globals_['children'][0]
+ assert first['id'].startswith('decl-region-')
+ assert get_state_node_kind(first) == 'variable'
+ assert get_state_node_name(first) == 'first'
+ assert get_state_node_type(first) == 'struct node *'
+
+ assert len(state['edges']) == 3
-/* { dg-additional-options "-fdiagnostics-add-output=sarif:xml-state=yes" } */
+/* { dg-additional-options "-fdiagnostics-add-output=sarif:state-graphs=yes" } */
#include "analyzer-decls.h"
-/* { dg-additional-options "-fdiagnostics-add-output=sarif:xml-state=yes" } */
+/* { dg-additional-options "-fdiagnostics-add-output=sarif:state-graphs=yes" } */
#include "analyzer-decls.h"
-import xml.etree.ElementTree as ET
-
from sarif import *
import pytest
def sarif():
return sarif_from_env()
-def test_nested_types_in_xml_state(sarif):
+def test_nested_types_in_state_graph(sarif):
result = get_result_by_index(sarif, 0)
assert result['level'] == 'note'
events = result["codeFlows"][0]["threadFlows"][0]['locations']
assert events[0]['location']['message']['text'] == 'here'
- state = get_xml_state(events, 0)
-
- memory_regions = state.find('memory-regions')
- assert memory_regions is not None
+ state = get_state_graph(events, 0)
- stack = memory_regions.find('stack')
- assert stack is not None
+ stack = state['nodes'][0]
+ assert stack['id'] == 'stack'
+ assert get_state_node_kind(stack) == 'stack'
- frame = stack.find('stack-frame')
- assert frame.get('function') == 'test'
+ frame = stack['children'][0]
+ assert frame['id'].startswith('frame-region-')
+ assert get_state_node_kind(frame) == 'stack-frame'
+ assert get_state_node_attr(frame, 'function') == 'test'
+ assert frame['location']['logicalLocations'][0]['fullyQualifiedName'] == 'test'
# We have:
# baz_arr[1].m_bars[1].m_foos[2].m_ints[1] = 42;
# representation to nested elements and fields.
# "baz_arr":
- baz_arr = frame.find("variable[@name='baz_arr']")
- assert baz_arr.get('type') == 'struct baz[2]'
+ baz_arr = frame['children'][0]
+ assert get_state_node_kind(baz_arr) == 'variable'
+ assert get_state_node_type(baz_arr) == 'struct baz[2]'
+
+ assert len(baz_arr['children']) == 2
+
+ bindings = baz_arr['children'][0]
+ assert bindings['id'].startswith('concrete-bindings-')
+ assert get_state_node_kind(bindings) == 'other'
+ assert get_state_node_value(bindings['children'][0]) == '(int)42'
# "baz_arr[1]":
- baz_arr_1 = baz_arr.find("element[@index='1']")
- assert baz_arr_1.get('type') == 'struct baz'
+ baz_arr_1 = baz_arr['children'][1]
+ assert get_state_node_type(baz_arr_1) == 'struct baz'
+ assert get_state_node_kind(baz_arr_1) == 'element'
+ assert get_state_node_attr(baz_arr_1, 'index') == '1'
- assert baz_arr.find("element[@index='0']") is None
+ assert len(baz_arr_1['children']) == 1
# "baz_arr[1].m_bars":
- baz_arr_1_m_bars = baz_arr_1.find("field[@name='m_bars']")
- assert baz_arr_1_m_bars.get('type') == 'struct bar[2]'
+ baz_arr_1_m_bars = baz_arr_1['children'][0]
+ assert get_state_node_name(baz_arr_1_m_bars) == 'm_bars'
+ assert get_state_node_type(baz_arr_1_m_bars) == 'struct bar[2]'
# "baz_arr[1].m_bars[1]"
- baz_arr_1_m_bars_1 = baz_arr_1_m_bars.find("element[@index='1']")
- assert baz_arr_1_m_bars_1.get('type') == 'struct bar'
+ baz_arr_1_m_bars_1 = baz_arr_1_m_bars['children'][0]
+ assert get_state_node_type(baz_arr_1_m_bars_1) == 'struct bar'
+ assert get_state_node_kind(baz_arr_1_m_bars_1) == 'element'
+ assert get_state_node_attr(baz_arr_1_m_bars_1, 'index') == '1'
# "baz_arr[1].m_bars[1].m_foos"
- baz_arr_1_m_bars_1_m_foos = baz_arr_1_m_bars_1.find("field[@name='m_foos']")
- assert baz_arr_1_m_bars_1_m_foos.get('type') == 'struct foo[3]'
+ baz_arr_1_m_bars_1_m_foos = baz_arr_1_m_bars_1['children'][0]
+ assert get_state_node_kind(baz_arr_1_m_bars_1_m_foos) == 'field'
+ assert get_state_node_name(baz_arr_1_m_bars_1_m_foos) == 'm_foos'
+ assert get_state_node_type(baz_arr_1_m_bars_1_m_foos) == 'struct foo[3]'
# "baz_arr[1].m_bars[1].m_foos[2]"
- baz_arr_1_m_bars_1_m_foos_2 = baz_arr_1_m_bars_1_m_foos.find("element[@index='2']")
- assert baz_arr_1_m_bars_1_m_foos_2.get('type') == 'struct foo'
-
+ baz_arr_1_m_bars_1_m_foos_2 = baz_arr_1_m_bars_1_m_foos['children'][0]
+ assert get_state_node_type(baz_arr_1_m_bars_1_m_foos_2) == 'struct foo'
+ assert get_state_node_kind(baz_arr_1_m_bars_1_m_foos_2) == 'element'
+ assert get_state_node_attr(baz_arr_1_m_bars_1_m_foos_2, 'index') == '2'
+
# "baz_arr[1].m_bars[1].m_foos[2].m_ints"
- baz_arr_1_m_bars_1_m_foos_2_m_ints = baz_arr_1_m_bars_1_m_foos_2.find('field[@name="m_ints"]')
- assert baz_arr_1_m_bars_1_m_foos_2_m_ints.get('type') == 'int[4]'
-
- # "baz_arr[1].m_bars[1].m_foos[2].m_ints[1]"
- baz_arr_1_m_bars_1_m_foos_2_m_ints_1 = baz_arr_1_m_bars_1_m_foos_2_m_ints.find('element[@index="1"]')
- assert baz_arr_1_m_bars_1_m_foos_2_m_ints_1.get('type') == 'int'
+ baz_arr_1_m_bars_1_m_foos_2_m_ints = baz_arr_1_m_bars_1_m_foos_2['children'][0]
+ assert get_state_node_kind(baz_arr_1_m_bars_1_m_foos_2_m_ints) == 'field'
+ assert get_state_node_name(baz_arr_1_m_bars_1_m_foos_2_m_ints) == 'm_ints'
+ assert get_state_node_type(baz_arr_1_m_bars_1_m_foos_2_m_ints) == 'int[4]'
- value = baz_arr_1_m_bars_1_m_foos_2_m_ints_1.find('value-of-region')
- constant = value.find('constant')
- assert constant.get('value') == '42'
- assert constant.get('type') == 'int'
+ # "baz_arr[1].m_bars[1].m_foos[2].m_ints[1]"
+ baz_arr_1_m_bars_1_m_foos_2_m_ints_1 = baz_arr_1_m_bars_1_m_foos_2_m_ints['children'][0]
+ assert get_state_node_type(baz_arr_1_m_bars_1_m_foos_2_m_ints_1) == 'int'
+ assert get_state_node_kind(baz_arr_1_m_bars_1_m_foos_2_m_ints_1) == 'element'
+ assert get_state_node_attr(baz_arr_1_m_bars_1_m_foos_2_m_ints_1, 'index') == '1'
+ assert get_state_node_value(baz_arr_1_m_bars_1_m_foos_2_m_ints_1) == '(int)42'
--- /dev/null
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-add-output=experimental-html:javascript=no" } */
+
+extern void here (void);
+
+void test_graphs (void)
+{
+ here (); /* { dg-error "this is a placeholder error, with graphs" } */
+}
+
+/* Use a Python script to verify various properties about the generated
+ HTML file:
+ { dg-final { run-html-pytest diagnostic-test-graphs-html.c "diagnostic-test-graphs-html.py" } } */
--- /dev/null
+# Verify that metadata works in HTML output.
+
+from htmltest import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def html_tree():
+ return html_tree_from_env()
+
+def test_result_graph(html_tree):
+ root = html_tree.getroot ()
+ assert root.tag == make_tag('html')
+
+ body = root.find('xhtml:body', ns)
+ assert body is not None
+
+ diag_list = body.find('xhtml:div', ns)
+ assert diag_list is not None
+ assert diag_list.attrib['class'] == 'gcc-diagnostic-list'
+
+ diag = diag_list.find('xhtml:div', ns)
+ assert diag is not None
+
+ message = diag.find("./xhtml:div[@class='gcc-message']", ns)
+ assert message.attrib['id'] == 'gcc-diag-0-message'
+
+ assert message[0].tag == make_tag('strong')
+ assert message[0].tail == ' this is a placeholder error, with graphs '
+
+ graph = diag.find("./xhtml:div[@class='gcc-directed-graph']", ns)
+ assert graph is not None
+
+ header = graph.find("./xhtml:h2", ns)
+ assert header.text == 'foo'
+
+def test_run_graph(html_tree):
+ root = html_tree.getroot ()
+ assert root.tag == make_tag('html')
+
+ body = root.find('xhtml:body', ns)
+ assert body is not None
+
+ graph = body.find("./xhtml:div[@class='gcc-directed-graph']", ns)
+ assert graph is not None
+
+ header = graph.find("./xhtml:h2", ns)
+ assert header.text == 'Optimization Passes'
--- /dev/null
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-add-output=sarif" } */
+
+extern void here (void);
+
+void test_graphs (void)
+{
+ here (); /* { dg-error "this is a placeholder error, with graphs" } */
+}
+
+/* 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 diagnostic-test-graphs-sarif.c "diagnostic-test-graphs-sarif.py" } } */
--- /dev/null
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+def test_basics(sarif):
+ schema = sarif['$schema']
+ assert schema == "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json"
+
+ version = sarif['version']
+ assert version == "2.1.0"
+
+def test_result_graph(sarif):
+ runs = sarif['runs']
+ run = runs[0]
+ results = run['results']
+
+ assert len(results) == 1
+
+ result = results[0]
+ assert result['level'] == 'error'
+ assert result['message']['text'] == "this is a placeholder error, with graphs"
+
+ assert len(result['graphs']) == 2
+
+ assert result['graphs'][0]['description']['text'] == 'foo'
+
+ assert len(result['graphs'][0]['nodes']) == 2
+ assert result['graphs'][0]['nodes'][0]['id'] == 'a'
+ assert result['graphs'][0]['nodes'][1]['id'] == 'b'
+ assert result['graphs'][0]['nodes'][1]['properties']['/placeholder-prefix/color'] == 'red'
+ assert len(result['graphs'][0]['nodes'][1]['children']) == 1
+ assert result['graphs'][0]['nodes'][1]['children'][0]['id'] == 'c'
+ assert result['graphs'][0]['nodes'][1]['children'][0]['label']['text'] == 'I am a node label'
+
+ assert len(result['graphs'][0]['edges']) == 1
+ result['graphs'][0]['edges'][0]['id'] == 'my-edge'
+ assert result['graphs'][0]['edges'][0]['label']['text'] == 'I am an edge label'
+ assert result['graphs'][0]['edges'][0]['sourceNodeId'] == 'a'
+ assert result['graphs'][0]['edges'][0]['targetNodeId'] == 'c'
+
+ assert result['graphs'][1]['description']['text'] == 'bar'
+
+def test_run_graph(sarif):
+ runs = sarif['runs']
+ run = runs[0]
+
+ assert len(run['graphs']) == 1
+
+ assert run['graphs'][0]['description']['text'] == 'Optimization Passes'
+ assert run['graphs'][0]['nodes'][0]['id'] == 'all_lowering_passes'
+ assert run['graphs'][0]['edges'][0]['id'] == 'edge0'
--- /dev/null
+/* { dg-do compile } */
+
+extern void here (void);
+
+void test_graphs (void)
+{
+ here (); /* { dg-error "this is a placeholder error, with graphs" } */
+}
--- /dev/null
+/* This plugin exercises diagnostic graphs.
+ We emit an error with a pair of digraphs associated with it,
+ and a global digraph showing the optimization passes. */
+
+#define INCLUDE_MAP
+#define INCLUDE_STRING
+#define INCLUDE_VECTOR
+#include "gcc-plugin.h"
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "tree.h"
+#include "stringpool.h"
+#include "toplev.h"
+#include "basic-block.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "tree.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "plugin-version.h"
+#include "diagnostic.h"
+#include "context.h"
+#include "gcc-rich-location.h"
+#include "diagnostic-metadata.h"
+#include "diagnostic-digraphs.h"
+#include "pass_manager.h"
+
+
+int plugin_is_GPL_compatible;
+
+const pass_data pass_data_test_graph_emission =
+{
+ GIMPLE_PASS, /* type */
+ "test_graph_emission", /* name */
+ OPTGROUP_NONE, /* optinfo_flags */
+ TV_NONE, /* tv_id */
+ PROP_ssa, /* properties_required */
+ 0, /* properties_provided */
+ 0, /* properties_destroyed */
+ 0, /* todo_flags_start */
+ 0, /* todo_flags_finish */
+};
+
+class pass_test_graph_emission : public gimple_opt_pass
+{
+public:
+ pass_test_graph_emission(gcc::context *ctxt)
+ : gimple_opt_pass(pass_data_test_graph_emission, ctxt)
+ {}
+
+ /* opt_pass methods: */
+ bool gate (function *) { return true; }
+ virtual unsigned int execute (function *);
+
+}; // class pass_test_graph_emission
+
+/* Determine if STMT is a call with NUM_ARGS arguments to a function
+ named FUNCNAME.
+ If so, return STMT as a gcall *. Otherwise return NULL. */
+
+static gcall *
+check_for_named_call (gimple *stmt,
+ const char *funcname, unsigned int num_args)
+{
+ gcc_assert (funcname);
+
+ gcall *call = dyn_cast <gcall *> (stmt);
+ if (!call)
+ return NULL;
+
+ tree fndecl = gimple_call_fndecl (call);
+ if (!fndecl)
+ return NULL;
+
+ if (strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), funcname))
+ return NULL;
+
+ if (gimple_call_num_args (call) != num_args)
+ {
+ error_at (stmt->location, "expected number of args: %i (got %i)",
+ num_args, gimple_call_num_args (call));
+ return NULL;
+ }
+
+ return call;
+}
+
+class lazy_passes_graph : public diagnostics::digraphs::lazy_digraph
+{
+public:
+ lazy_passes_graph (const ::gcc::pass_manager &pass_manager_)
+ : m_pass_manager (pass_manager_)
+ {
+ }
+
+ std::unique_ptr<diagnostics::digraphs::digraph>
+ create_digraph () const final override
+ {
+ auto g = std::make_unique<diagnostics::digraphs::digraph> ();
+ g->set_description ("Optimization Passes");
+
+#define DEF_PASS_LIST(NAME) \
+ add_top_level_pass_list (*g, #NAME, m_pass_manager.NAME);
+
+ GCC_PASS_LISTS
+
+#undef DEF_PASS_LIST
+
+ return g;
+ }
+
+ void
+ add_top_level_pass_list (diagnostics::digraphs::digraph &g,
+ const char *pass_list_name,
+ const opt_pass *p) const
+ {
+ gcc_assert (p);
+ auto n = std::make_unique<diagnostics::digraphs::node> (g, pass_list_name);
+ n->set_label (pass_list_name);
+ add_child_pass (g, *n, *p);
+ g.add_node (std::move (n));
+ }
+
+ diagnostics::digraphs::node &
+ add_child_pass (diagnostics::digraphs::digraph &g,
+ diagnostics::digraphs::node &parent_node,
+ const opt_pass &p) const
+ {
+ std::string node_label;
+ std::string node_id;
+ if (p.static_pass_number > 0 )
+ {
+ node_label = std::to_string (p.static_pass_number) + "_" + p.name;
+ node_id = node_label;
+ }
+ else
+ {
+ node_label = std::string (p.name);
+ pretty_printer pp;
+ pp_printf (&pp, "%s_%p", p.name, &p);
+ node_id = pp_formatted_text (&pp);
+ }
+ auto n
+ = std::make_unique<diagnostics::digraphs::node> (g,
+ std::move (node_id));
+ n->set_label (node_label.c_str ());
+ diagnostics::digraphs::node &result = *n;
+ parent_node.add_child (std::move (n));
+
+ // TODO: add attrs for things like type, properties_*, etc
+
+ if (p.sub)
+ {
+ auto &other_node = add_child_pass (g, parent_node, *p.sub);
+ g.add_edge (nullptr, result, other_node, "sub");
+ }
+
+ if (p.next)
+ {
+ auto &other_node = add_child_pass (g, parent_node, *p.next);
+ g.add_edge (nullptr, result, other_node, "next");
+ }
+
+ return result;
+ }
+
+private:
+ const ::gcc::pass_manager &m_pass_manager;
+};
+
+static void
+report_diag_with_graphs (location_t loc)
+{
+ class my_lazy_digraphs : public diagnostics::digraphs::lazy_digraphs
+ {
+ public:
+ using diagnostic_graph = diagnostics::digraphs::digraph;
+ using diagnostic_node = diagnostics::digraphs::node;
+ using diagnostic_edge = diagnostics::digraphs::edge;
+
+ std::unique_ptr<std::vector<std::unique_ptr<diagnostic_graph>>>
+ create_digraphs () const final override
+ {
+ auto graphs
+ = std::make_unique<std::vector<std::unique_ptr<diagnostic_graph>>> ();
+
+ graphs->push_back (make_test_graph ("foo"));
+ graphs->push_back (make_test_graph ("bar"));
+
+ return graphs;
+ }
+
+ private:
+ std::unique_ptr<diagnostic_graph>
+ make_test_graph (const char *desc) const
+ {
+ auto g = std::make_unique<diagnostic_graph> ();
+ g->set_description (desc);
+ auto a = std::make_unique<diagnostic_node> (*g, "a");
+ auto b = std::make_unique<diagnostic_node> (*g, "b");
+#define KEY_PREFIX "/placeholder-prefix/"
+ b->set_attr (KEY_PREFIX, "color", "red");
+#undef KEY_PREFIX
+ auto c = std::make_unique<diagnostic_node> (*g, "c");
+ c->set_label ("I am a node label");
+
+ auto e = std::make_unique<diagnostic_edge> (*g, "my-edge", *a, *c);
+ 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));
+
+ return g;
+ }
+ };
+
+ gcc_rich_location rich_loc (loc);
+ diagnostic_metadata meta;
+ my_lazy_digraphs ldg;
+ meta.set_lazy_digraphs (&ldg);
+ error_meta (&rich_loc, meta,
+ "this is a placeholder error, with graphs");
+}
+
+/* Exercise diagnostic graph emission. */
+
+unsigned int
+pass_test_graph_emission::execute (function *fun)
+{
+ gimple_stmt_iterator gsi;
+ basic_block bb;
+
+ FOR_EACH_BB_FN (bb, fun)
+ for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+ {
+ gimple *stmt = gsi_stmt (gsi);
+
+ if (gcall *call = check_for_named_call (stmt, "here", 0))
+ report_diag_with_graphs (gimple_location (call));
+ }
+
+ return 0;
+}
+
+int
+plugin_init (struct plugin_name_args *plugin_info,
+ struct plugin_gcc_version *version)
+{
+ struct register_pass_info pass_info;
+ const char *plugin_name = plugin_info->base_name;
+ int argc = plugin_info->argc;
+ struct plugin_argument *argv = plugin_info->argv;
+
+ if (!plugin_default_version_check (version, &gcc_version))
+ return 1;
+
+ pass_info.pass = new pass_test_graph_emission (g);
+ pass_info.reference_pass_name = "ssa";
+ pass_info.ref_pass_instance_number = 1;
+ pass_info.pos_op = PASS_POS_INSERT_AFTER;
+ register_callback (plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL,
+ &pass_info);
+
+ gcc_assert (::g->get_passes ());
+ global_dc->report_global_digraph (lazy_passes_graph (*::g->get_passes ()));
+
+ return 0;
+}
diagnostic-test-metadata.c \
diagnostic-test-metadata-html.c \
diagnostic-test-metadata-sarif.c } \
+ { diagnostic_plugin_test_graphs.cc
+ diagnostic-test-graphs.c \
+ diagnostic-test-graphs-html.c \
+ diagnostic-test-graphs-sarif.c } \
{ diagnostic_plugin_test_nesting.cc \
diagnostic-test-nesting-text-plain.c \
diagnostic-test-nesting-text-indented.c \
results = run['results']
return results[idx]
-def get_xml_state(events, event_idx):
- xml_src = events[event_idx]['properties']['gcc/diagnostic_event/xml_state']
+def get_state_graph(events, event_idx):
+ graph = events[event_idx]['properties']['gcc/diagnostic_event/state_graph']
if 0:
- print(xml_src)
- xml = ET.fromstring(xml_src)
- assert xml.tag == 'state-diagram'
- return xml
+ print(graph)
+ assert graph is not None
+ return graph
+
+def get_state_node_attr(obj, attr_name):
+ return obj['properties']['gcc/diagnostic_state_node/%s' % attr_name]
+
+def get_state_node_kind(obj):
+ return get_state_node_attr(obj, 'kind')
+
+def get_state_node_name(obj):
+ return get_state_node_attr(obj, 'name')
+
+def get_state_node_type(obj):
+ return get_state_node_attr(obj, 'type')
+
+def get_state_node_value(obj):
+ return get_state_node_attr(obj, 'value')
--- /dev/null
+{"$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json",
+ "version": "2.1.0",
+ "runs": [{"tool": { "driver": { "name": "example" } },
+ "results": [],
+ "graphs": [{"nodes": [{"id": "a", /* { dg-message "'a' already used as node id within graph here" } */
+ "children": [{"id": "a"}]}], /* { dg-error "duplicate node id 'a' within graph \\\[SARIF v2.1.0 §3.40.2\\\]" } */
+ "edges": []}]}]}
+
+/* { dg-begin-multiline-output "" }
+In JSON property '/runs/0/graphs/0/nodes/0/children/0/id':
+ { dg-end-multiline-output "" } */
+/* { dg-begin-multiline-output "" }
+ 6 | "children": [{"id": "a"}]}],
+ | ^~~
+ { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+In JSON property '/runs/0/graphs/0/nodes/0/id':
+ { dg-end-multiline-output "" } */
+/* { dg-begin-multiline-output "" }
+ 5 | "graphs": [{"nodes": [{"id": "a",
+ | ^~~
+ { dg-end-multiline-output "" } */
--- /dev/null
+{"$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json",
+ "version": "2.1.0",
+ "runs": [{"tool": { "driver": { "name": "example" } },
+ "results": [],
+ "graphs": [{"nodes": [],
+ "edges": [{"id": "edge0",
+ "sourceNodeId": "this-does-not-exist", /* { dg-error "no node with id 'this-does-not-exist' in graph \\\[SARIF v2.1.0 §3.41.4\\\]" } */
+ "targetNodeId": "neither-does-this"}]}]}]}
+
+/* { dg-begin-multiline-output "" }
+In JSON property '/runs/0/graphs/0/edges/0/sourceNodeId':
+ { dg-end-multiline-output "" } */
+/* { dg-begin-multiline-output "" }
+ 7 | "sourceNodeId": "this-does-not-exist",
+ | ^~~~~~~~~~~~~~~~~~~~~
+ { dg-end-multiline-output "" } */
--- /dev/null
+from htmltest import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def html_tree():
+ return html_tree_from_env()
+
+def test_result_graph(html_tree):
+ root = html_tree.getroot ()
+ assert root.tag == make_tag('html')
+
+ body = root.find('xhtml:body', ns)
+ assert body is not None
+
+ diag_list = body.find('xhtml:div', ns)
+ assert diag_list is not None
+ assert diag_list.attrib['class'] == 'gcc-diagnostic-list'
+
+ diag = diag_list.find('xhtml:div', ns)
+ assert diag is not None
+
+ message = diag.find("./xhtml:div[@class='gcc-message']", ns)
+ assert message.attrib['id'] == 'gcc-diag-0-message'
+
+ assert message[0].tag == make_tag('strong')
+ assert message[0].tail == ' this is a placeholder error, with graphs '
+
+ graph = diag.find("./xhtml:div[@class='gcc-directed-graph']", ns)
+ assert graph is not None
+
+ header = graph.find("./xhtml:h2", ns)
+ assert header.text == 'foo'
+
+def test_run_graph(html_tree):
+ root = html_tree.getroot ()
+ assert root.tag == make_tag('html')
+
+ body = root.find('xhtml:body', ns)
+ assert body is not None
+
+ graph = body.find("./xhtml:div[@class='gcc-directed-graph']", ns)
+ assert graph is not None
+
+ header = graph.find("./xhtml:h2", ns)
+ assert header.text == 'Optimization Passes'
--- /dev/null
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+def test_basics(sarif):
+ schema = sarif['$schema']
+ assert schema == "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json"
+
+ version = sarif['version']
+ assert version == "2.1.0"
+
+def test_result_graph(sarif):
+ runs = sarif['runs']
+ run = runs[0]
+ results = run['results']
+
+ assert len(results) == 1
+
+ result = results[0]
+ assert result['level'] == 'error'
+ assert result['message']['text'] == "this is a placeholder error, with graphs"
+
+ assert len(result['graphs']) == 2
+
+ assert result['graphs'][0]['description']['text'] == 'foo'
+
+ assert len(result['graphs'][0]['nodes']) == 2
+ assert result['graphs'][0]['nodes'][0]['id'] == 'a'
+ assert result['graphs'][0]['nodes'][1]['id'] == 'b'
+ assert result['graphs'][0]['nodes'][1]['properties']['/placeholder-prefix/color'] == 'red'
+ assert len(result['graphs'][0]['nodes'][1]['children']) == 1
+ assert result['graphs'][0]['nodes'][1]['children'][0]['id'] == 'c'
+ assert result['graphs'][0]['nodes'][1]['children'][0]['label']['text'] == 'I am a node label'
+
+ assert len(result['graphs'][0]['edges']) == 1
+ result['graphs'][0]['edges'][0]['id'] == 'my-edge'
+ assert result['graphs'][0]['edges'][0]['label']['text'] == 'I am an edge label'
+ assert result['graphs'][0]['edges'][0]['sourceNodeId'] == 'a'
+ assert result['graphs'][0]['edges'][0]['targetNodeId'] == 'c'
+
+ assert result['graphs'][1]['description']['text'] == 'bar'
+
+def test_run_graph(sarif):
+ runs = sarif['runs']
+ run = runs[0]
+
+ assert len(run['graphs']) == 1
+
+ assert run['graphs'][0]['description']['text'] == 'Optimization Passes'
+ assert run['graphs'][0]['nodes'][0]['id'] == 'all_lowering_passes'
+ assert run['graphs'][0]['edges'][0]['id'] == 'edge0'
--- /dev/null
+/* Test a replay of a .sarif file generated from GCC testsuite.
+
+ 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=graphs.sarif.html,javascript=no" } */
+/* { dg-additional-options "-fdiagnostics-add-output=sarif:file=graphs.roundtrip.sarif" } */
+
+{"$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json",
+ "version": "2.1.0",
+ "runs": [{"tool": {"driver": {"name": "GNU C23",
+ "fullName": "GNU C23 (GCC) version 16.0.0 20250702 (experimental) (x86_64-pc-linux-gnu)",
+ "version": "16.0.0 20250702 (experimental)",
+ "informationUri": "https://gcc.gnu.org/gcc-16/",
+ "rules": []},
+ "extensions": [{"name": "diagnostic_plugin_test_graphs",
+ "fullName": "./diagnostic_plugin_test_graphs.so"}]},
+ "invocations": [{"arguments": ["/home/david/gcc-newgit-gcc16/build/gcc/cc1",
+ "-quiet",
+ "-iprefix",
+ "/usr/local/lib/gcc/x86_64-pc-linux-gnu/16.0.0/",
+ "-isystem",
+ "/home/david/gcc-newgit-gcc16/build/gcc/include",
+ "-isystem",
+ "/home/david/gcc-newgit-gcc16/build/gcc/include-fixed",
+ "-iplugindir=/home/david/gcc-newgit-gcc16/build/gcc/plugin",
+ "/home/david/gcc-newgit-gcc16/src/gcc/testsuite/gcc.dg/plugin/diagnostic-test-graphs-sarif.c",
+ "-iplugindir=/home/david/gcc-newgit-gcc16/build/gcc/plugin",
+ "-quiet",
+ "-dumpbase",
+ "diagnostic-test-graphs-sarif.c",
+ "-dumpbase-ext",
+ ".c",
+ "-mtune=generic",
+ "-march=x86-64",
+ "-fdiagnostics-color=never",
+ "-fdiagnostics-urls=never",
+ "-fno-diagnostics-show-caret",
+ "-fno-diagnostics-show-line-numbers",
+ "-fdiagnostics-path-format=separate-events",
+ "-fdiagnostics-text-art-charset=none",
+ "-fno-diagnostics-show-event-links",
+ "-fplugin=./diagnostic_plugin_test_graphs.so",
+ "-fdiagnostics-add-output=sarif",
+ "-o",
+ "diagnostic-test-graphs-sarif.s"],
+ "workingDirectory": {"uri": "/home/david/gcc-newgit-gcc16/build/gcc/testsuite/gcc"},
+ "startTimeUtc": "2025-07-09T22:43:31Z",
+ "executionSuccessful": false,
+ "toolExecutionNotifications": [],
+ "endTimeUtc": "2025-07-09T22:43:31Z"}],
+ "artifacts": [{"location": {"uri": "/home/david/gcc-newgit-gcc16/src/gcc/testsuite/gcc.dg/plugin/diagnostic-test-graphs-sarif.c"},
+ "sourceLanguage": "c",
+ "roles": ["analysisTarget"]}],
+ "results": [{"ruleId": "error",
+ "level": "error",
+ "message": {"text": "this is a placeholder error, with graphs"},
+ "locations": [{"physicalLocation": {"artifactLocation": {"uri": "/home/david/gcc-newgit-gcc16/src/gcc/testsuite/gcc.dg/plugin/diagnostic-test-graphs-sarif.c"},
+ "region": {"startLine": 8,
+ "startColumn": 3,
+ "endColumn": 10},
+ "contextRegion": {"startLine": 8,
+ "snippet": {"text": " here ();"}}},
+ "logicalLocations": [{"index": 0,
+ "fullyQualifiedName": "test_graphs"}]}],
+ "graphs": [{"description": {"text": "foo"},
+ "nodes": [{"id": "a"},
+ {"id": "b",
+ "properties": {"/placeholder-prefix/color": "red"},
+ "children": [{"id": "c",
+ "label": {"text": "I am a node label"}}]}],
+ "edges": [{"id": "my-edge",
+ "label": {"text": "I am an edge label"},
+ "sourceNodeId": "a",
+ "targetNodeId": "c"}]},
+ {"description": {"text": "bar"},
+ "nodes": [{"id": "a"},
+ {"id": "b",
+ "properties": {"/placeholder-prefix/color": "red"},
+ "children": [{"id": "c",
+ "label": {"text": "I am a node label"}}]}],
+ "edges": [{"id": "my-edge",
+ "label": {"text": "I am an edge label"},
+ "sourceNodeId": "a",
+ "targetNodeId": "c"}]}]}],
+ "logicalLocations": [{"name": "test_graphs",
+ "fullyQualifiedName": "test_graphs",
+ "decoratedName": "test_graphs",
+ "kind": "function",
+ "index": 0}],
+ "graphs": [{"description": {"text": "Optimization Passes"},
+ "nodes": [{"id": "all_lowering_passes",
+ "label": {"text": "all_lowering_passes"},
+ "children": [{"id": "*warn_unused_result_0x101ef3d0",
+ "label": {"text": "*warn_unused_result"}},
+ {"id": "*diagnose_omp_blocks_0x101ef430",
+ "label": {"text": "*diagnose_omp_blocks"}},
+ {"id": "*diagnose_tm_blocks_0x101ef490",
+ "label": {"text": "*diagnose_tm_blocks"}},
+ {"id": "13_omp_oacc_kernels_decompose",
+ "label": {"text": "13_omp_oacc_kernels_decompose"}},
+ {"id": "14_omplower",
+ "label": {"text": "14_omplower"}},
+ {"id": "15_lower",
+ "label": {"text": "15_lower"}},
+ {"id": "16_tmlower",
+ "label": {"text": "16_tmlower"}},
+ {"id": "17_ehopt",
+ "label": {"text": "17_ehopt"}},
+ {"id": "18_eh",
+ "label": {"text": "18_eh"}},
+ {"id": "19_coro-lower-builtins",
+ "label": {"text": "19_coro-lower-builtins"}},
+ {"id": "20_cfg",
+ "label": {"text": "20_cfg"}},
+ {"id": "*warn_function_return_0x101ef7f0",
+ "label": {"text": "*warn_function_return"}},
+ {"id": "21_coro-early-expand-ifns",
+ "label": {"text": "21_coro-early-expand-ifns"}},
+ {"id": "22_ompexp",
+ "label": {"text": "22_ompexp"}},
+ {"id": "*build_cgraph_edges_0x101ef910",
+ "label": {"text": "*build_cgraph_edges"}}]},
+ {"id": "all_small_ipa_passes",
+ "label": {"text": "all_small_ipa_passes"},
+ "children": [{"id": "23_afdo_offline",
+ "label": {"text": "23_afdo_offline"}},
+ {"id": "*free_lang_data_0x101ef9d0",
+ "label": {"text": "*free_lang_data"}},
+ {"id": "24_visibility",
+ "label": {"text": "24_visibility"}},
+ {"id": "25_strubm",
+ "label": {"text": "25_strubm"}},
+ {"id": "26_build_ssa_passes",
+ "label": {"text": "26_build_ssa_passes"}},
+ {"id": "27_fixup_cfg",
+ "label": {"text": "27_fixup_cfg"}},
+ {"id": "28_ssa",
+ "label": {"text": "28_ssa"}},
+ {"id": "376_test_graph_emission",
+ "label": {"text": "376_test_graph_emission"}},
+ {"id": "29_walloca",
+ "label": {"text": "29_walloca"}},
+ {"id": "30_warn-printf",
+ "label": {"text": "30_warn-printf"}},
+ {"id": "*nonnullcmp_0x101efce0",
+ "label": {"text": "*nonnullcmp"}},
+ {"id": "31_early_uninit",
+ "label": {"text": "31_early_uninit"}},
+ {"id": "32_waccess",
+ "label": {"text": "32_waccess"}},
+ {"id": "33_ubsan",
+ "label": {"text": "33_ubsan"}},
+ {"id": "34_nothrow",
+ "label": {"text": "34_nothrow"}},
+ {"id": "*rebuild_cgraph_edges_0x101f0020",
+ "label": {"text": "*rebuild_cgraph_edges"}},
+ {"id": "35_opt_local_passes",
+ "label": {"text": "35_opt_local_passes"}},
+ {"id": "36_fixup_cfg",
+ "label": {"text": "36_fixup_cfg"}},
+ {"id": "*rebuild_cgraph_edges_0x101f0140",
+ "label": {"text": "*rebuild_cgraph_edges"}},
+ {"id": "37_local-fnsummary",
+ "label": {"text": "37_local-fnsummary"}},
+ {"id": "38_einline",
+ "label": {"text": "38_einline"}},
+ {"id": "*infinite-recursion_0x101f0260",
+ "label": {"text": "*infinite-recursion"}},
+ {"id": "39_early_optimizations",
+ "label": {"text": "39_early_optimizations"}},
+ {"id": "*remove_cgraph_callee_edges_0x101f0340",
+ "label": {"text": "*remove_cgraph_callee_edges"}},
+ {"id": "40_early_objsz",
+ "label": {"text": "40_early_objsz"}},
+ {"id": "41_ccp",
+ "label": {"text": "41_ccp"}},
+ {"id": "42_forwprop",
+ "label": {"text": "42_forwprop"}},
+ {"id": "43_ethread",
+ "label": {"text": "43_ethread"}},
+ {"id": "44_esra",
+ "label": {"text": "44_esra"}},
+ {"id": "45_ealias",
+ "label": {"text": "45_ealias"}},
+ {"id": "46_phiprop",
+ "label": {"text": "46_phiprop"}},
+ {"id": "47_fre",
+ "label": {"text": "47_fre"}},
+ {"id": "48_evrp",
+ "label": {"text": "48_evrp"}},
+ {"id": "49_mergephi",
+ "label": {"text": "49_mergephi"}},
+ {"id": "50_dse",
+ "label": {"text": "50_dse"}},
+ {"id": "51_cddce",
+ "label": {"text": "51_cddce"}},
+ {"id": "52_phiopt",
+ "label": {"text": "52_phiopt"}},
+ {"id": "53_tailr",
+ "label": {"text": "53_tailr"}},
+ {"id": "54_iftoswitch",
+ "label": {"text": "54_iftoswitch"}},
+ {"id": "55_switchconv",
+ "label": {"text": "55_switchconv"}},
+ {"id": "56_ehcleanup",
+ "label": {"text": "56_ehcleanup"}},
+ {"id": "57_sccopy",
+ "label": {"text": "57_sccopy"}},
+ {"id": "58_profile_estimate",
+ "label": {"text": "58_profile_estimate"}},
+ {"id": "59_local-pure-const",
+ "label": {"text": "59_local-pure-const"}},
+ {"id": "60_modref",
+ "label": {"text": "60_modref"}},
+ {"id": "61_fnsplit",
+ "label": {"text": "61_fnsplit"}},
+ {"id": "*strip_predict_hints_0x101f0c60",
+ "label": {"text": "*strip_predict_hints"}},
+ {"id": "62_release_ssa",
+ "label": {"text": "62_release_ssa"}},
+ {"id": "*rebuild_cgraph_edges_0x101f0d30",
+ "label": {"text": "*rebuild_cgraph_edges"}},
+ {"id": "63_local-fnsummary",
+ "label": {"text": "63_local-fnsummary"}},
+ {"id": "64_remove_symbols",
+ "label": {"text": "64_remove_symbols"}},
+ {"id": "65_strub",
+ "label": {"text": "65_strub"}},
+ {"id": "66_ipa_oacc",
+ "label": {"text": "66_ipa_oacc"}},
+ {"id": "67_pta",
+ "label": {"text": "67_pta"}},
+ {"id": "68_ipa_oacc_kernels",
+ "label": {"text": "68_ipa_oacc_kernels"}},
+ {"id": "69_oacc_kernels",
+ "label": {"text": "69_oacc_kernels"}},
+ {"id": "70_ch",
+ "label": {"text": "70_ch"}},
+ {"id": "71_fre",
+ "label": {"text": "71_fre"}},
+ {"id": "72_lim",
+ "label": {"text": "72_lim"}},
+ {"id": "73_dom",
+ "label": {"text": "73_dom"}},
+ {"id": "74_dce",
+ "label": {"text": "74_dce"}},
+ {"id": "75_parloops",
+ "label": {"text": "75_parloops"}},
+ {"id": "76_ompexpssa",
+ "label": {"text": "76_ompexpssa"}},
+ {"id": "*rebuild_cgraph_edges_0x101f1310",
+ "label": {"text": "*rebuild_cgraph_edges"}},
+ {"id": "77_targetclone",
+ "label": {"text": "77_targetclone"}},
+ {"id": "78_afdo",
+ "label": {"text": "78_afdo"}},
+ {"id": "79_feedback_fnsplit",
+ "label": {"text": "79_feedback_fnsplit"}},
+ {"id": "80_profile",
+ "label": {"text": "80_profile"}},
+ {"id": "81_feedback_fnsplit",
+ "label": {"text": "81_feedback_fnsplit"}},
+ {"id": "82_free-fnsummary",
+ "label": {"text": "82_free-fnsummary"}},
+ {"id": "83_increase_alignment",
+ "label": {"text": "83_increase_alignment"}},
+ {"id": "84_tmipa",
+ "label": {"text": "84_tmipa"}},
+ {"id": "85_emutls",
+ "label": {"text": "85_emutls"}}]},
+ {"id": "all_regular_ipa_passes",
+ "label": {"text": "all_regular_ipa_passes"},
+ "children": [{"id": "86_analyzer",
+ "label": {"text": "86_analyzer"}},
+ {"id": "87_odr",
+ "label": {"text": "87_odr"}},
+ {"id": "88_whole-program",
+ "label": {"text": "88_whole-program"}},
+ {"id": "89_profile_estimate",
+ "label": {"text": "89_profile_estimate"}},
+ {"id": "90_icf",
+ "label": {"text": "90_icf"}},
+ {"id": "91_devirt",
+ "label": {"text": "91_devirt"}},
+ {"id": "92_cdtor",
+ "label": {"text": "92_cdtor"}},
+ {"id": "93_cp",
+ "label": {"text": "93_cp"}},
+ {"id": "94_sra",
+ "label": {"text": "94_sra"}},
+ {"id": "95_fnsummary",
+ "label": {"text": "95_fnsummary"}},
+ {"id": "96_inline",
+ "label": {"text": "96_inline"}},
+ {"id": "97_locality-clone",
+ "label": {"text": "97_locality-clone"}},
+ {"id": "98_pure-const",
+ "label": {"text": "98_pure-const"}},
+ {"id": "99_modref",
+ "label": {"text": "99_modref"}},
+ {"id": "100_free-fnsummary",
+ "label": {"text": "100_free-fnsummary"}},
+ {"id": "101_static-var",
+ "label": {"text": "101_static-var"}},
+ {"id": "102_single-use",
+ "label": {"text": "102_single-use"}},
+ {"id": "103_comdats",
+ "label": {"text": "103_comdats"}}]},
+ {"id": "all_late_ipa_passes",
+ "label": {"text": "all_late_ipa_passes"},
+ "children": [{"id": "104_pta",
+ "label": {"text": "104_pta"}},
+ {"id": "105_simdclone",
+ "label": {"text": "105_simdclone"}}]},
+ {"id": "all_passes",
+ "label": {"text": "all_passes"},
+ "children": [{"id": "106_fixup_cfg",
+ "label": {"text": "106_fixup_cfg"}},
+ {"id": "107_ehdisp",
+ "label": {"text": "107_ehdisp"}},
+ {"id": "108_oaccloops",
+ "label": {"text": "108_oaccloops"}},
+ {"id": "109_omp_oacc_neuter_broadcast",
+ "label": {"text": "109_omp_oacc_neuter_broadcast"}},
+ {"id": "110_oaccdevlow",
+ "label": {"text": "110_oaccdevlow"}},
+ {"id": "111_ompdevlow",
+ "label": {"text": "111_ompdevlow"}},
+ {"id": "112_omptargetlink",
+ "label": {"text": "112_omptargetlink"}},
+ {"id": "113_adjust_alignment",
+ "label": {"text": "113_adjust_alignment"}},
+ {"id": "114_hardcfr",
+ "label": {"text": "114_hardcfr"}},
+ {"id": "*all_optimizations_0x101f2720",
+ "label": {"text": "*all_optimizations"}},
+ {"id": "*remove_cgraph_callee_edges_0x101f2780",
+ "label": {"text": "*remove_cgraph_callee_edges"}},
+ {"id": "*strip_predict_hints_0x101f27e0",
+ "label": {"text": "*strip_predict_hints"}},
+ {"id": "115_ccp",
+ "label": {"text": "115_ccp"}},
+ {"id": "116_objsz",
+ "label": {"text": "116_objsz"}},
+ {"id": "117_post_ipa_warn",
+ "label": {"text": "117_post_ipa_warn"}},
+ {"id": "118_waccess",
+ "label": {"text": "118_waccess"}},
+ {"id": "119_rebuild_frequencies",
+ "label": {"text": "119_rebuild_frequencies"}},
+ {"id": "120_cunrolli",
+ "label": {"text": "120_cunrolli"}},
+ {"id": "121_backprop",
+ "label": {"text": "121_backprop"}},
+ {"id": "122_phiprop",
+ "label": {"text": "122_phiprop"}},
+ {"id": "123_forwprop",
+ "label": {"text": "123_forwprop"}},
+ {"id": "124_alias",
+ "label": {"text": "124_alias"}},
+ {"id": "125_retslot",
+ "label": {"text": "125_retslot"}},
+ {"id": "126_fre",
+ "label": {"text": "126_fre"}},
+ {"id": "127_mergephi",
+ "label": {"text": "127_mergephi"}},
+ {"id": "128_threadfull",
+ "label": {"text": "128_threadfull"}},
+ {"id": "129_vrp",
+ "label": {"text": "129_vrp"}},
+ {"id": "130_bounds",
+ "label": {"text": "130_bounds"}},
+ {"id": "131_dse",
+ "label": {"text": "131_dse"}},
+ {"id": "132_dce",
+ "label": {"text": "132_dce"}},
+ {"id": "133_stdarg",
+ "label": {"text": "133_stdarg"}},
+ {"id": "134_cdce",
+ "label": {"text": "134_cdce"}},
+ {"id": "135_cselim",
+ "label": {"text": "135_cselim"}},
+ {"id": "136_copyprop",
+ "label": {"text": "136_copyprop"}},
+ {"id": "137_ifcombine",
+ "label": {"text": "137_ifcombine"}},
+ {"id": "138_mergephi",
+ "label": {"text": "138_mergephi"}},
+ {"id": "139_phiopt",
+ "label": {"text": "139_phiopt"}},
+ {"id": "140_tailr",
+ "label": {"text": "140_tailr"}},
+ {"id": "141_ch",
+ "label": {"text": "141_ch"}},
+ {"id": "142_cplxlower",
+ "label": {"text": "142_cplxlower"}},
+ {"id": "143_bitintlower",
+ "label": {"text": "143_bitintlower"}},
+ {"id": "144_sra",
+ "label": {"text": "144_sra"}},
+ {"id": "145_thread",
+ "label": {"text": "145_thread"}},
+ {"id": "146_dom",
+ "label": {"text": "146_dom"}},
+ {"id": "147_copyprop",
+ "label": {"text": "147_copyprop"}},
+ {"id": "148_isolate-paths",
+ "label": {"text": "148_isolate-paths"}},
+ {"id": "149_reassoc",
+ "label": {"text": "149_reassoc"}},
+ {"id": "150_dce",
+ "label": {"text": "150_dce"}},
+ {"id": "151_forwprop",
+ "label": {"text": "151_forwprop"}},
+ {"id": "152_phiopt",
+ "label": {"text": "152_phiopt"}},
+ {"id": "153_ccp",
+ "label": {"text": "153_ccp"}},
+ {"id": "154_pow",
+ "label": {"text": "154_pow"}},
+ {"id": "155_bswap",
+ "label": {"text": "155_bswap"}},
+ {"id": "156_laddress",
+ "label": {"text": "156_laddress"}},
+ {"id": "157_lim",
+ "label": {"text": "157_lim"}},
+ {"id": "158_walloca",
+ "label": {"text": "158_walloca"}},
+ {"id": "159_pre",
+ "label": {"text": "159_pre"}},
+ {"id": "160_sink",
+ "label": {"text": "160_sink"}},
+ {"id": "161_sancov",
+ "label": {"text": "161_sancov"}},
+ {"id": "162_asan",
+ "label": {"text": "162_asan"}},
+ {"id": "163_tsan",
+ "label": {"text": "163_tsan"}},
+ {"id": "164_dse",
+ "label": {"text": "164_dse"}},
+ {"id": "165_dce",
+ "label": {"text": "165_dce"}},
+ {"id": "166_fix_loops",
+ "label": {"text": "166_fix_loops"}},
+ {"id": "167_loop",
+ "label": {"text": "167_loop"}},
+ {"id": "168_loopinit",
+ "label": {"text": "168_loopinit"}},
+ {"id": "169_unswitch",
+ "label": {"text": "169_unswitch"}},
+ {"id": "170_lsplit",
+ "label": {"text": "170_lsplit"}},
+ {"id": "171_sccp",
+ "label": {"text": "171_sccp"}},
+ {"id": "172_lversion",
+ "label": {"text": "172_lversion"}},
+ {"id": "173_unrolljam",
+ "label": {"text": "173_unrolljam"}},
+ {"id": "174_cddce",
+ "label": {"text": "174_cddce"}},
+ {"id": "175_ivcanon",
+ "label": {"text": "175_ivcanon"}},
+ {"id": "176_ldist",
+ "label": {"text": "176_ldist"}},
+ {"id": "177_crc",
+ "label": {"text": "177_crc"}},
+ {"id": "178_linterchange",
+ "label": {"text": "178_linterchange"}},
+ {"id": "179_copyprop",
+ "label": {"text": "179_copyprop"}},
+ {"id": "180_graphite0",
+ "label": {"text": "180_graphite0"}},
+ {"id": "181_graphite",
+ "label": {"text": "181_graphite"}},
+ {"id": "182_lim",
+ "label": {"text": "182_lim"}},
+ {"id": "183_copyprop",
+ "label": {"text": "183_copyprop"}},
+ {"id": "184_dce",
+ "label": {"text": "184_dce"}},
+ {"id": "185_parloops",
+ "label": {"text": "185_parloops"}},
+ {"id": "186_ompexpssa",
+ "label": {"text": "186_ompexpssa"}},
+ {"id": "187_ch_vect",
+ "label": {"text": "187_ch_vect"}},
+ {"id": "188_ifcvt",
+ "label": {"text": "188_ifcvt"}},
+ {"id": "189_vect",
+ "label": {"text": "189_vect"}},
+ {"id": "190_dce",
+ "label": {"text": "190_dce"}},
+ {"id": "191_pcom",
+ "label": {"text": "191_pcom"}},
+ {"id": "192_cunroll",
+ "label": {"text": "192_cunroll"}},
+ {"id": "*pre_slp_scalar_cleanup_0x101f4880",
+ "label": {"text": "*pre_slp_scalar_cleanup"}},
+ {"id": "193_fre",
+ "label": {"text": "193_fre"}},
+ {"id": "194_dse",
+ "label": {"text": "194_dse"}},
+ {"id": "195_slp",
+ "label": {"text": "195_slp"}},
+ {"id": "196_aprefetch",
+ "label": {"text": "196_aprefetch"}},
+ {"id": "197_ivopts",
+ "label": {"text": "197_ivopts"}},
+ {"id": "198_lim",
+ "label": {"text": "198_lim"}},
+ {"id": "199_loopdone",
+ "label": {"text": "199_loopdone"}},
+ {"id": "200_no_loop",
+ "label": {"text": "200_no_loop"}},
+ {"id": "201_slp",
+ "label": {"text": "201_slp"}},
+ {"id": "202_simduid",
+ "label": {"text": "202_simduid"}},
+ {"id": "203_veclower2",
+ "label": {"text": "203_veclower2"}},
+ {"id": "204_switchlower",
+ "label": {"text": "204_switchlower"}},
+ {"id": "205_sincos",
+ "label": {"text": "205_sincos"}},
+ {"id": "206_recip",
+ "label": {"text": "206_recip"}},
+ {"id": "207_reassoc",
+ "label": {"text": "207_reassoc"}},
+ {"id": "208_slsr",
+ "label": {"text": "208_slsr"}},
+ {"id": "209_split-paths",
+ "label": {"text": "209_split-paths"}},
+ {"id": "210_tracer",
+ "label": {"text": "210_tracer"}},
+ {"id": "211_fre",
+ "label": {"text": "211_fre"}},
+ {"id": "212_thread",
+ "label": {"text": "212_thread"}},
+ {"id": "213_dom",
+ "label": {"text": "213_dom"}},
+ {"id": "214_strlen",
+ "label": {"text": "214_strlen"}},
+ {"id": "215_threadfull",
+ "label": {"text": "215_threadfull"}},
+ {"id": "216_vrp",
+ "label": {"text": "216_vrp"}},
+ {"id": "217_ccp",
+ "label": {"text": "217_ccp"}},
+ {"id": "218_wrestrict",
+ "label": {"text": "218_wrestrict"}},
+ {"id": "219_dse",
+ "label": {"text": "219_dse"}},
+ {"id": "220_dce",
+ "label": {"text": "220_dce"}},
+ {"id": "221_forwprop",
+ "label": {"text": "221_forwprop"}},
+ {"id": "222_sink",
+ "label": {"text": "222_sink"}},
+ {"id": "223_phiopt",
+ "label": {"text": "223_phiopt"}},
+ {"id": "224_fab",
+ "label": {"text": "224_fab"}},
+ {"id": "225_widening_mul",
+ "label": {"text": "225_widening_mul"}},
+ {"id": "226_store-merging",
+ "label": {"text": "226_store-merging"}},
+ {"id": "227_cddce",
+ "label": {"text": "227_cddce"}},
+ {"id": "228_sccopy",
+ "label": {"text": "228_sccopy"}},
+ {"id": "229_tailc",
+ "label": {"text": "229_tailc"}},
+ {"id": "230_crited",
+ "label": {"text": "230_crited"}},
+ {"id": "231_uninit",
+ "label": {"text": "231_uninit"}},
+ {"id": "232_local-pure-const",
+ "label": {"text": "232_local-pure-const"}},
+ {"id": "233_modref",
+ "label": {"text": "233_modref"}},
+ {"id": "234_uncprop",
+ "label": {"text": "234_uncprop"}},
+ {"id": "*all_optimizations_g_0x101f5af0",
+ "label": {"text": "*all_optimizations_g"}},
+ {"id": "*remove_cgraph_callee_edges_0x101f5b50",
+ "label": {"text": "*remove_cgraph_callee_edges"}},
+ {"id": "*strip_predict_hints_0x101f5bb0",
+ "label": {"text": "*strip_predict_hints"}},
+ {"id": "235_cplxlower",
+ "label": {"text": "235_cplxlower"}},
+ {"id": "236_bitintlower",
+ "label": {"text": "236_bitintlower"}},
+ {"id": "237_veclower2",
+ "label": {"text": "237_veclower2"}},
+ {"id": "238_switchlower",
+ "label": {"text": "238_switchlower"}},
+ {"id": "239_ccp",
+ "label": {"text": "239_ccp"}},
+ {"id": "240_post_ipa_warn",
+ "label": {"text": "240_post_ipa_warn"}},
+ {"id": "241_objsz",
+ "label": {"text": "241_objsz"}},
+ {"id": "242_fab",
+ "label": {"text": "242_fab"}},
+ {"id": "243_strlen",
+ "label": {"text": "243_strlen"}},
+ {"id": "244_copyprop",
+ "label": {"text": "244_copyprop"}},
+ {"id": "245_dce",
+ "label": {"text": "245_dce"}},
+ {"id": "246_rebuild_frequencies",
+ "label": {"text": "246_rebuild_frequencies"}},
+ {"id": "247_sancov",
+ "label": {"text": "247_sancov"}},
+ {"id": "248_asan",
+ "label": {"text": "248_asan"}},
+ {"id": "249_tsan",
+ "label": {"text": "249_tsan"}},
+ {"id": "250_crited",
+ "label": {"text": "250_crited"}},
+ {"id": "251_uninit",
+ "label": {"text": "251_uninit"}},
+ {"id": "252_uncprop",
+ "label": {"text": "252_uncprop"}},
+ {"id": "253_assumptions",
+ "label": {"text": "253_assumptions"}},
+ {"id": "*tminit_0x101f6370",
+ "label": {"text": "*tminit"}},
+ {"id": "254_tmmark",
+ "label": {"text": "254_tmmark"}},
+ {"id": "255_tmmemopt",
+ "label": {"text": "255_tmmemopt"}},
+ {"id": "256_tmedge",
+ "label": {"text": "256_tmedge"}},
+ {"id": "257_simduid",
+ "label": {"text": "257_simduid"}},
+ {"id": "258_vtable-verify",
+ "label": {"text": "258_vtable-verify"}},
+ {"id": "259_lower_vaarg",
+ "label": {"text": "259_lower_vaarg"}},
+ {"id": "260_veclower",
+ "label": {"text": "260_veclower"}},
+ {"id": "261_cplxlower0",
+ "label": {"text": "261_cplxlower0"}},
+ {"id": "262_bitintlower0",
+ "label": {"text": "262_bitintlower0"}},
+ {"id": "263_sancov_O0",
+ "label": {"text": "263_sancov_O0"}},
+ {"id": "264_switchlower_O0",
+ "label": {"text": "264_switchlower_O0"}},
+ {"id": "265_asan0",
+ "label": {"text": "265_asan0"}},
+ {"id": "266_tsan0",
+ "label": {"text": "266_tsan0"}},
+ {"id": "267_musttail",
+ "label": {"text": "267_musttail"}},
+ {"id": "268_sanopt",
+ "label": {"text": "268_sanopt"}},
+ {"id": "269_ehcleanup",
+ "label": {"text": "269_ehcleanup"}},
+ {"id": "270_resx",
+ "label": {"text": "270_resx"}},
+ {"id": "271_nrv",
+ "label": {"text": "271_nrv"}},
+ {"id": "272_isel",
+ "label": {"text": "272_isel"}},
+ {"id": "273_hardcbr",
+ "label": {"text": "273_hardcbr"}},
+ {"id": "274_hardcmp",
+ "label": {"text": "274_hardcmp"}},
+ {"id": "275_waccess",
+ "label": {"text": "275_waccess"}},
+ {"id": "276_optimized",
+ "label": {"text": "276_optimized"}},
+ {"id": "*warn_function_noreturn_0x101f6dd0",
+ "label": {"text": "*warn_function_noreturn"}},
+ {"id": "277_expand",
+ "label": {"text": "277_expand"}},
+ {"id": "*rest_of_compilation_0x101f6e90",
+ "label": {"text": "*rest_of_compilation"}},
+ {"id": "278_vregs",
+ "label": {"text": "278_vregs"}},
+ {"id": "279_into_cfglayout",
+ "label": {"text": "279_into_cfglayout"}},
+ {"id": "280_jump",
+ "label": {"text": "280_jump"}},
+ {"id": "281_subreg1",
+ "label": {"text": "281_subreg1"}},
+ {"id": "282_dfinit",
+ "label": {"text": "282_dfinit"}},
+ {"id": "283_cse1",
+ "label": {"text": "283_cse1"}},
+ {"id": "284_fwprop1",
+ "label": {"text": "284_fwprop1"}},
+ {"id": "285_cprop",
+ "label": {"text": "285_cprop"}},
+ {"id": "286_rtl pre",
+ "label": {"text": "286_rtl pre"}},
+ {"id": "287_hoist",
+ "label": {"text": "287_hoist"}},
+ {"id": "288_hardreg_pre",
+ "label": {"text": "288_hardreg_pre"}},
+ {"id": "289_cprop",
+ "label": {"text": "289_cprop"}},
+ {"id": "290_store_motion",
+ "label": {"text": "290_store_motion"}},
+ {"id": "291_cse_local",
+ "label": {"text": "291_cse_local"}},
+ {"id": "292_ce1",
+ "label": {"text": "292_ce1"}},
+ {"id": "293_apx_nfcvt",
+ "label": {"text": "293_apx_nfcvt"}},
+ {"id": "294_reginfo",
+ "label": {"text": "294_reginfo"}},
+ {"id": "295_loop2",
+ "label": {"text": "295_loop2"}},
+ {"id": "296_loop2_init",
+ "label": {"text": "296_loop2_init"}},
+ {"id": "297_loop2_invariant",
+ "label": {"text": "297_loop2_invariant"}},
+ {"id": "298_loop2_unroll",
+ "label": {"text": "298_loop2_unroll"}},
+ {"id": "299_loop2_doloop",
+ "label": {"text": "299_loop2_doloop"}},
+ {"id": "300_loop2_done",
+ "label": {"text": "300_loop2_done"}},
+ {"id": "301_subreg2",
+ "label": {"text": "301_subreg2"}},
+ {"id": "302_web",
+ "label": {"text": "302_web"}},
+ {"id": "303_cprop",
+ "label": {"text": "303_cprop"}},
+ {"id": "304_stv",
+ "label": {"text": "304_stv"}},
+ {"id": "305_cse2",
+ "label": {"text": "305_cse2"}},
+ {"id": "306_dse1",
+ "label": {"text": "306_dse1"}},
+ {"id": "307_fwprop2",
+ "label": {"text": "307_fwprop2"}},
+ {"id": "308_auto_inc_dec",
+ "label": {"text": "308_auto_inc_dec"}},
+ {"id": "309_init-regs",
+ "label": {"text": "309_init-regs"}},
+ {"id": "310_ud_dce",
+ "label": {"text": "310_ud_dce"}},
+ {"id": "311_ext_dce",
+ "label": {"text": "311_ext_dce"}},
+ {"id": "312_combine",
+ "label": {"text": "312_combine"}},
+ {"id": "313_late_combine",
+ "label": {"text": "313_late_combine"}},
+ {"id": "314_rpad",
+ "label": {"text": "314_rpad"}},
+ {"id": "315_rrvl",
+ "label": {"text": "315_rrvl"}},
+ {"id": "316_stv",
+ "label": {"text": "316_stv"}},
+ {"id": "317_ce2",
+ "label": {"text": "317_ce2"}},
+ {"id": "318_jump_after_combine",
+ "label": {"text": "318_jump_after_combine"}},
+ {"id": "319_bbpart",
+ "label": {"text": "319_bbpart"}},
+ {"id": "320_outof_cfglayout",
+ "label": {"text": "320_outof_cfglayout"}},
+ {"id": "321_split1",
+ "label": {"text": "321_split1"}},
+ {"id": "322_subreg3",
+ "label": {"text": "322_subreg3"}},
+ {"id": "323_no-opt dfinit",
+ "label": {"text": "323_no-opt dfinit"}},
+ {"id": "*stack_ptr_mod_0x101f8050",
+ "label": {"text": "*stack_ptr_mod"}},
+ {"id": "324_mode_sw",
+ "label": {"text": "324_mode_sw"}},
+ {"id": "325_asmcons",
+ "label": {"text": "325_asmcons"}},
+ {"id": "326_sms",
+ "label": {"text": "326_sms"}},
+ {"id": "327_lr_shrinkage",
+ "label": {"text": "327_lr_shrinkage"}},
+ {"id": "328_sched1",
+ "label": {"text": "328_sched1"}},
+ {"id": "329_avoid_store_forwarding",
+ "label": {"text": "329_avoid_store_forwarding"}},
+ {"id": "330_early_remat",
+ "label": {"text": "330_early_remat"}},
+ {"id": "331_ira",
+ "label": {"text": "331_ira"}},
+ {"id": "332_reload",
+ "label": {"text": "332_reload"}},
+ {"id": "*all-postreload_0x101f8410",
+ "label": {"text": "*all-postreload"}},
+ {"id": "333_postreload",
+ "label": {"text": "333_postreload"}},
+ {"id": "334_vzeroupper",
+ "label": {"text": "334_vzeroupper"}},
+ {"id": "335_late_combine",
+ "label": {"text": "335_late_combine"}},
+ {"id": "336_gcse2",
+ "label": {"text": "336_gcse2"}},
+ {"id": "337_split2",
+ "label": {"text": "337_split2"}},
+ {"id": "338_ree",
+ "label": {"text": "338_ree"}},
+ {"id": "339_cmpelim",
+ "label": {"text": "339_cmpelim"}},
+ {"id": "340_pro_and_epilogue",
+ "label": {"text": "340_pro_and_epilogue"}},
+ {"id": "341_dse2",
+ "label": {"text": "341_dse2"}},
+ {"id": "342_csa",
+ "label": {"text": "342_csa"}},
+ {"id": "343_jump2",
+ "label": {"text": "343_jump2"}},
+ {"id": "344_compgotos",
+ "label": {"text": "344_compgotos"}},
+ {"id": "345_sched_fusion",
+ "label": {"text": "345_sched_fusion"}},
+ {"id": "346_peephole2",
+ "label": {"text": "346_peephole2"}},
+ {"id": "347_ce3",
+ "label": {"text": "347_ce3"}},
+ {"id": "348_rnreg",
+ "label": {"text": "348_rnreg"}},
+ {"id": "349_fold_mem_offsets",
+ "label": {"text": "349_fold_mem_offsets"}},
+ {"id": "350_cprop_hardreg",
+ "label": {"text": "350_cprop_hardreg"}},
+ {"id": "351_rtl_dce",
+ "label": {"text": "351_rtl_dce"}},
+ {"id": "352_bbro",
+ "label": {"text": "352_bbro"}},
+ {"id": "*leaf_regs_0x101f8bf0",
+ "label": {"text": "*leaf_regs"}},
+ {"id": "353_split3",
+ "label": {"text": "353_split3"}},
+ {"id": "354_sched2",
+ "label": {"text": "354_sched2"}},
+ {"id": "*stack_regs_0x101f8d10",
+ "label": {"text": "*stack_regs"}},
+ {"id": "355_split4",
+ "label": {"text": "355_split4"}},
+ {"id": "356_stack",
+ "label": {"text": "356_stack"}},
+ {"id": "357_late_pro_and_epilogue",
+ "label": {"text": "357_late_pro_and_epilogue"}},
+ {"id": "*all-late_compilation_0x101f8e90",
+ "label": {"text": "*all-late_compilation"}},
+ {"id": "358_zero_call_used_regs",
+ "label": {"text": "358_zero_call_used_regs"}},
+ {"id": "359_alignments",
+ "label": {"text": "359_alignments"}},
+ {"id": "360_vartrack",
+ "label": {"text": "360_vartrack"}},
+ {"id": "*free_cfg_0x101f9010",
+ "label": {"text": "*free_cfg"}},
+ {"id": "361_mach",
+ "label": {"text": "361_mach"}},
+ {"id": "362_barriers",
+ "label": {"text": "362_barriers"}},
+ {"id": "363_dbr",
+ "label": {"text": "363_dbr"}},
+ {"id": "364_split5",
+ "label": {"text": "364_split5"}},
+ {"id": "365_eh_ranges",
+ "label": {"text": "365_eh_ranges"}},
+ {"id": "366_endbr_and_patchable_area",
+ "label": {"text": "366_endbr_and_patchable_area"}},
+ {"id": "367_align_tight_loops",
+ "label": {"text": "367_align_tight_loops"}},
+ {"id": "368_shorten",
+ "label": {"text": "368_shorten"}},
+ {"id": "369_nothrow",
+ "label": {"text": "369_nothrow"}},
+ {"id": "370_dwarf2",
+ "label": {"text": "370_dwarf2"}},
+ {"id": "371_final",
+ "label": {"text": "371_final"}},
+ {"id": "372_dfinish",
+ "label": {"text": "372_dfinish"}},
+ {"id": "*clean_state_0x101f9500",
+ "label": {"text": "*clean_state"}}]}],
+ "edges": [{"id": "edge0",
+ "label": {"text": "next"},
+ "sourceNodeId": "22_ompexp",
+ "targetNodeId": "*build_cgraph_edges_0x101ef910"},
+ {"id": "edge1",
+ "label": {"text": "next"},
+ "sourceNodeId": "21_coro-early-expand-ifns",
+ "targetNodeId": "22_ompexp"},
+ {"id": "edge2",
+ "label": {"text": "next"},
+ "sourceNodeId": "*warn_function_return_0x101ef7f0",
+ "targetNodeId": "21_coro-early-expand-ifns"},
+ {"id": "edge3",
+ "label": {"text": "next"},
+ "sourceNodeId": "20_cfg",
+ "targetNodeId": "*warn_function_return_0x101ef7f0"},
+ {"id": "edge4",
+ "label": {"text": "next"},
+ "sourceNodeId": "19_coro-lower-builtins",
+ "targetNodeId": "20_cfg"},
+ {"id": "edge5",
+ "label": {"text": "next"},
+ "sourceNodeId": "18_eh",
+ "targetNodeId": "19_coro-lower-builtins"},
+ {"id": "edge6",
+ "label": {"text": "next"},
+ "sourceNodeId": "17_ehopt",
+ "targetNodeId": "18_eh"},
+ {"id": "edge7",
+ "label": {"text": "next"},
+ "sourceNodeId": "16_tmlower",
+ "targetNodeId": "17_ehopt"},
+ {"id": "edge8",
+ "label": {"text": "next"},
+ "sourceNodeId": "15_lower",
+ "targetNodeId": "16_tmlower"},
+ {"id": "edge9",
+ "label": {"text": "next"},
+ "sourceNodeId": "14_omplower",
+ "targetNodeId": "15_lower"},
+ {"id": "edge10",
+ "label": {"text": "next"},
+ "sourceNodeId": "13_omp_oacc_kernels_decompose",
+ "targetNodeId": "14_omplower"},
+ {"id": "edge11",
+ "label": {"text": "next"},
+ "sourceNodeId": "*diagnose_tm_blocks_0x101ef490",
+ "targetNodeId": "13_omp_oacc_kernels_decompose"},
+ {"id": "edge12",
+ "label": {"text": "next"},
+ "sourceNodeId": "*diagnose_omp_blocks_0x101ef430",
+ "targetNodeId": "*diagnose_tm_blocks_0x101ef490"},
+ {"id": "edge13",
+ "label": {"text": "next"},
+ "sourceNodeId": "*warn_unused_result_0x101ef3d0",
+ "targetNodeId": "*diagnose_omp_blocks_0x101ef430"},
+ {"id": "edge14",
+ "label": {"text": "next"},
+ "sourceNodeId": "34_nothrow",
+ "targetNodeId": "*rebuild_cgraph_edges_0x101f0020"},
+ {"id": "edge15",
+ "label": {"text": "next"},
+ "sourceNodeId": "33_ubsan",
+ "targetNodeId": "34_nothrow"},
+ {"id": "edge16",
+ "label": {"text": "next"},
+ "sourceNodeId": "32_waccess",
+ "targetNodeId": "33_ubsan"},
+ {"id": "edge17",
+ "label": {"text": "next"},
+ "sourceNodeId": "31_early_uninit",
+ "targetNodeId": "32_waccess"},
+ {"id": "edge18",
+ "label": {"text": "next"},
+ "sourceNodeId": "*nonnullcmp_0x101efce0",
+ "targetNodeId": "31_early_uninit"},
+ {"id": "edge19",
+ "label": {"text": "next"},
+ "sourceNodeId": "30_warn-printf",
+ "targetNodeId": "*nonnullcmp_0x101efce0"},
+ {"id": "edge20",
+ "label": {"text": "next"},
+ "sourceNodeId": "29_walloca",
+ "targetNodeId": "30_warn-printf"},
+ {"id": "edge21",
+ "label": {"text": "next"},
+ "sourceNodeId": "376_test_graph_emission",
+ "targetNodeId": "29_walloca"},
+ {"id": "edge22",
+ "label": {"text": "next"},
+ "sourceNodeId": "28_ssa",
+ "targetNodeId": "376_test_graph_emission"},
+ {"id": "edge23",
+ "label": {"text": "next"},
+ "sourceNodeId": "27_fixup_cfg",
+ "targetNodeId": "28_ssa"},
+ {"id": "edge24",
+ "label": {"text": "sub"},
+ "sourceNodeId": "26_build_ssa_passes",
+ "targetNodeId": "27_fixup_cfg"},
+ {"id": "edge25",
+ "label": {"text": "next"},
+ "sourceNodeId": "61_fnsplit",
+ "targetNodeId": "*strip_predict_hints_0x101f0c60"},
+ {"id": "edge26",
+ "label": {"text": "next"},
+ "sourceNodeId": "60_modref",
+ "targetNodeId": "61_fnsplit"},
+ {"id": "edge27",
+ "label": {"text": "next"},
+ "sourceNodeId": "59_local-pure-const",
+ "targetNodeId": "60_modref"},
+ {"id": "edge28",
+ "label": {"text": "next"},
+ "sourceNodeId": "58_profile_estimate",
+ "targetNodeId": "59_local-pure-const"},
+ {"id": "edge29",
+ "label": {"text": "next"},
+ "sourceNodeId": "57_sccopy",
+ "targetNodeId": "58_profile_estimate"},
+ {"id": "edge30",
+ "label": {"text": "next"},
+ "sourceNodeId": "56_ehcleanup",
+ "targetNodeId": "57_sccopy"},
+ {"id": "edge31",
+ "label": {"text": "next"},
+ "sourceNodeId": "55_switchconv",
+ "targetNodeId": "56_ehcleanup"},
+ {"id": "edge32",
+ "label": {"text": "next"},
+ "sourceNodeId": "54_iftoswitch",
+ "targetNodeId": "55_switchconv"},
+ {"id": "edge33",
+ "label": {"text": "next"},
+ "sourceNodeId": "53_tailr",
+ "targetNodeId": "54_iftoswitch"},
+ {"id": "edge34",
+ "label": {"text": "next"},
+ "sourceNodeId": "52_phiopt",
+ "targetNodeId": "53_tailr"},
+ {"id": "edge35",
+ "label": {"text": "next"},
+ "sourceNodeId": "51_cddce",
+ "targetNodeId": "52_phiopt"},
+ {"id": "edge36",
+ "label": {"text": "next"},
+ "sourceNodeId": "50_dse",
+ "targetNodeId": "51_cddce"},
+ {"id": "edge37",
+ "label": {"text": "next"},
+ "sourceNodeId": "49_mergephi",
+ "targetNodeId": "50_dse"},
+ {"id": "edge38",
+ "label": {"text": "next"},
+ "sourceNodeId": "48_evrp",
+ "targetNodeId": "49_mergephi"},
+ {"id": "edge39",
+ "label": {"text": "next"},
+ "sourceNodeId": "47_fre",
+ "targetNodeId": "48_evrp"},
+ {"id": "edge40",
+ "label": {"text": "next"},
+ "sourceNodeId": "46_phiprop",
+ "targetNodeId": "47_fre"},
+ {"id": "edge41",
+ "label": {"text": "next"},
+ "sourceNodeId": "45_ealias",
+ "targetNodeId": "46_phiprop"},
+ {"id": "edge42",
+ "label": {"text": "next"},
+ "sourceNodeId": "44_esra",
+ "targetNodeId": "45_ealias"},
+ {"id": "edge43",
+ "label": {"text": "next"},
+ "sourceNodeId": "43_ethread",
+ "targetNodeId": "44_esra"},
+ {"id": "edge44",
+ "label": {"text": "next"},
+ "sourceNodeId": "42_forwprop",
+ "targetNodeId": "43_ethread"},
+ {"id": "edge45",
+ "label": {"text": "next"},
+ "sourceNodeId": "41_ccp",
+ "targetNodeId": "42_forwprop"},
+ {"id": "edge46",
+ "label": {"text": "next"},
+ "sourceNodeId": "40_early_objsz",
+ "targetNodeId": "41_ccp"},
+ {"id": "edge47",
+ "label": {"text": "next"},
+ "sourceNodeId": "*remove_cgraph_callee_edges_0x101f0340",
+ "targetNodeId": "40_early_objsz"},
+ {"id": "edge48",
+ "label": {"text": "sub"},
+ "sourceNodeId": "39_early_optimizations",
+ "targetNodeId": "*remove_cgraph_callee_edges_0x101f0340"},
+ {"id": "edge49",
+ "label": {"text": "next"},
+ "sourceNodeId": "*rebuild_cgraph_edges_0x101f0d30",
+ "targetNodeId": "63_local-fnsummary"},
+ {"id": "edge50",
+ "label": {"text": "next"},
+ "sourceNodeId": "62_release_ssa",
+ "targetNodeId": "*rebuild_cgraph_edges_0x101f0d30"},
+ {"id": "edge51",
+ "label": {"text": "next"},
+ "sourceNodeId": "39_early_optimizations",
+ "targetNodeId": "62_release_ssa"},
+ {"id": "edge52",
+ "label": {"text": "next"},
+ "sourceNodeId": "*infinite-recursion_0x101f0260",
+ "targetNodeId": "39_early_optimizations"},
+ {"id": "edge53",
+ "label": {"text": "next"},
+ "sourceNodeId": "38_einline",
+ "targetNodeId": "*infinite-recursion_0x101f0260"},
+ {"id": "edge54",
+ "label": {"text": "next"},
+ "sourceNodeId": "37_local-fnsummary",
+ "targetNodeId": "38_einline"},
+ {"id": "edge55",
+ "label": {"text": "next"},
+ "sourceNodeId": "*rebuild_cgraph_edges_0x101f0140",
+ "targetNodeId": "37_local-fnsummary"},
+ {"id": "edge56",
+ "label": {"text": "next"},
+ "sourceNodeId": "36_fixup_cfg",
+ "targetNodeId": "*rebuild_cgraph_edges_0x101f0140"},
+ {"id": "edge57",
+ "label": {"text": "sub"},
+ "sourceNodeId": "35_opt_local_passes",
+ "targetNodeId": "36_fixup_cfg"},
+ {"id": "edge58",
+ "label": {"text": "next"},
+ "sourceNodeId": "76_ompexpssa",
+ "targetNodeId": "*rebuild_cgraph_edges_0x101f1310"},
+ {"id": "edge59",
+ "label": {"text": "next"},
+ "sourceNodeId": "75_parloops",
+ "targetNodeId": "76_ompexpssa"},
+ {"id": "edge60",
+ "label": {"text": "next"},
+ "sourceNodeId": "74_dce",
+ "targetNodeId": "75_parloops"},
+ {"id": "edge61",
+ "label": {"text": "next"},
+ "sourceNodeId": "73_dom",
+ "targetNodeId": "74_dce"},
+ {"id": "edge62",
+ "label": {"text": "next"},
+ "sourceNodeId": "72_lim",
+ "targetNodeId": "73_dom"},
+ {"id": "edge63",
+ "label": {"text": "next"},
+ "sourceNodeId": "71_fre",
+ "targetNodeId": "72_lim"},
+ {"id": "edge64",
+ "label": {"text": "next"},
+ "sourceNodeId": "70_ch",
+ "targetNodeId": "71_fre"},
+ {"id": "edge65",
+ "label": {"text": "sub"},
+ "sourceNodeId": "69_oacc_kernels",
+ "targetNodeId": "70_ch"},
+ {"id": "edge66",
+ "label": {"text": "sub"},
+ "sourceNodeId": "68_ipa_oacc_kernels",
+ "targetNodeId": "69_oacc_kernels"},
+ {"id": "edge67",
+ "label": {"text": "next"},
+ "sourceNodeId": "67_pta",
+ "targetNodeId": "68_ipa_oacc_kernels"},
+ {"id": "edge68",
+ "label": {"text": "sub"},
+ "sourceNodeId": "66_ipa_oacc",
+ "targetNodeId": "67_pta"},
+ {"id": "edge69",
+ "label": {"text": "sub"},
+ "sourceNodeId": "78_afdo",
+ "targetNodeId": "79_feedback_fnsplit"},
+ {"id": "edge70",
+ "label": {"text": "sub"},
+ "sourceNodeId": "80_profile",
+ "targetNodeId": "81_feedback_fnsplit"},
+ {"id": "edge71",
+ "label": {"text": "next"},
+ "sourceNodeId": "84_tmipa",
+ "targetNodeId": "85_emutls"},
+ {"id": "edge72",
+ "label": {"text": "next"},
+ "sourceNodeId": "83_increase_alignment",
+ "targetNodeId": "84_tmipa"},
+ {"id": "edge73",
+ "label": {"text": "next"},
+ "sourceNodeId": "82_free-fnsummary",
+ "targetNodeId": "83_increase_alignment"},
+ {"id": "edge74",
+ "label": {"text": "next"},
+ "sourceNodeId": "80_profile",
+ "targetNodeId": "82_free-fnsummary"},
+ {"id": "edge75",
+ "label": {"text": "next"},
+ "sourceNodeId": "78_afdo",
+ "targetNodeId": "80_profile"},
+ {"id": "edge76",
+ "label": {"text": "next"},
+ "sourceNodeId": "77_targetclone",
+ "targetNodeId": "78_afdo"},
+ {"id": "edge77",
+ "label": {"text": "next"},
+ "sourceNodeId": "66_ipa_oacc",
+ "targetNodeId": "77_targetclone"},
+ {"id": "edge78",
+ "label": {"text": "next"},
+ "sourceNodeId": "65_strub",
+ "targetNodeId": "66_ipa_oacc"},
+ {"id": "edge79",
+ "label": {"text": "next"},
+ "sourceNodeId": "64_remove_symbols",
+ "targetNodeId": "65_strub"},
+ {"id": "edge80",
+ "label": {"text": "next"},
+ "sourceNodeId": "35_opt_local_passes",
+ "targetNodeId": "64_remove_symbols"},
+ {"id": "edge81",
+ "label": {"text": "next"},
+ "sourceNodeId": "26_build_ssa_passes",
+ "targetNodeId": "35_opt_local_passes"},
+ {"id": "edge82",
+ "label": {"text": "next"},
+ "sourceNodeId": "25_strubm",
+ "targetNodeId": "26_build_ssa_passes"},
+ {"id": "edge83",
+ "label": {"text": "next"},
+ "sourceNodeId": "24_visibility",
+ "targetNodeId": "25_strubm"},
+ {"id": "edge84",
+ "label": {"text": "next"},
+ "sourceNodeId": "*free_lang_data_0x101ef9d0",
+ "targetNodeId": "24_visibility"},
+ {"id": "edge85",
+ "label": {"text": "next"},
+ "sourceNodeId": "23_afdo_offline",
+ "targetNodeId": "*free_lang_data_0x101ef9d0"},
+ {"id": "edge86",
+ "label": {"text": "next"},
+ "sourceNodeId": "102_single-use",
+ "targetNodeId": "103_comdats"},
+ {"id": "edge87",
+ "label": {"text": "next"},
+ "sourceNodeId": "101_static-var",
+ "targetNodeId": "102_single-use"},
+ {"id": "edge88",
+ "label": {"text": "next"},
+ "sourceNodeId": "100_free-fnsummary",
+ "targetNodeId": "101_static-var"},
+ {"id": "edge89",
+ "label": {"text": "next"},
+ "sourceNodeId": "99_modref",
+ "targetNodeId": "100_free-fnsummary"},
+ {"id": "edge90",
+ "label": {"text": "next"},
+ "sourceNodeId": "98_pure-const",
+ "targetNodeId": "99_modref"},
+ {"id": "edge91",
+ "label": {"text": "next"},
+ "sourceNodeId": "97_locality-clone",
+ "targetNodeId": "98_pure-const"},
+ {"id": "edge92",
+ "label": {"text": "next"},
+ "sourceNodeId": "96_inline",
+ "targetNodeId": "97_locality-clone"},
+ {"id": "edge93",
+ "label": {"text": "next"},
+ "sourceNodeId": "95_fnsummary",
+ "targetNodeId": "96_inline"},
+ {"id": "edge94",
+ "label": {"text": "next"},
+ "sourceNodeId": "94_sra",
+ "targetNodeId": "95_fnsummary"},
+ {"id": "edge95",
+ "label": {"text": "next"},
+ "sourceNodeId": "93_cp",
+ "targetNodeId": "94_sra"},
+ {"id": "edge96",
+ "label": {"text": "next"},
+ "sourceNodeId": "92_cdtor",
+ "targetNodeId": "93_cp"},
+ {"id": "edge97",
+ "label": {"text": "next"},
+ "sourceNodeId": "91_devirt",
+ "targetNodeId": "92_cdtor"},
+ {"id": "edge98",
+ "label": {"text": "next"},
+ "sourceNodeId": "90_icf",
+ "targetNodeId": "91_devirt"},
+ {"id": "edge99",
+ "label": {"text": "next"},
+ "sourceNodeId": "89_profile_estimate",
+ "targetNodeId": "90_icf"},
+ {"id": "edge100",
+ "label": {"text": "next"},
+ "sourceNodeId": "88_whole-program",
+ "targetNodeId": "89_profile_estimate"},
+ {"id": "edge101",
+ "label": {"text": "next"},
+ "sourceNodeId": "87_odr",
+ "targetNodeId": "88_whole-program"},
+ {"id": "edge102",
+ "label": {"text": "next"},
+ "sourceNodeId": "86_analyzer",
+ "targetNodeId": "87_odr"},
+ {"id": "edge103",
+ "label": {"text": "next"},
+ "sourceNodeId": "104_pta",
+ "targetNodeId": "105_simdclone"},
+ {"id": "edge104",
+ "label": {"text": "next"},
+ "sourceNodeId": "183_copyprop",
+ "targetNodeId": "184_dce"},
+ {"id": "edge105",
+ "label": {"text": "next"},
+ "sourceNodeId": "182_lim",
+ "targetNodeId": "183_copyprop"},
+ {"id": "edge106",
+ "label": {"text": "next"},
+ "sourceNodeId": "181_graphite",
+ "targetNodeId": "182_lim"},
+ {"id": "edge107",
+ "label": {"text": "sub"},
+ "sourceNodeId": "180_graphite0",
+ "targetNodeId": "181_graphite"},
+ {"id": "edge108",
+ "label": {"text": "sub"},
+ "sourceNodeId": "189_vect",
+ "targetNodeId": "190_dce"},
+ {"id": "edge109",
+ "label": {"text": "next"},
+ "sourceNodeId": "193_fre",
+ "targetNodeId": "194_dse"},
+ {"id": "edge110",
+ "label": {"text": "sub"},
+ "sourceNodeId": "*pre_slp_scalar_cleanup_0x101f4880",
+ "targetNodeId": "193_fre"},
+ {"id": "edge111",
+ "label": {"text": "next"},
+ "sourceNodeId": "198_lim",
+ "targetNodeId": "199_loopdone"},
+ {"id": "edge112",
+ "label": {"text": "next"},
+ "sourceNodeId": "197_ivopts",
+ "targetNodeId": "198_lim"},
+ {"id": "edge113",
+ "label": {"text": "next"},
+ "sourceNodeId": "196_aprefetch",
+ "targetNodeId": "197_ivopts"},
+ {"id": "edge114",
+ "label": {"text": "next"},
+ "sourceNodeId": "195_slp",
+ "targetNodeId": "196_aprefetch"},
+ {"id": "edge115",
+ "label": {"text": "next"},
+ "sourceNodeId": "*pre_slp_scalar_cleanup_0x101f4880",
+ "targetNodeId": "195_slp"},
+ {"id": "edge116",
+ "label": {"text": "next"},
+ "sourceNodeId": "192_cunroll",
+ "targetNodeId": "*pre_slp_scalar_cleanup_0x101f4880"},
+ {"id": "edge117",
+ "label": {"text": "next"},
+ "sourceNodeId": "191_pcom",
+ "targetNodeId": "192_cunroll"},
+ {"id": "edge118",
+ "label": {"text": "next"},
+ "sourceNodeId": "189_vect",
+ "targetNodeId": "191_pcom"},
+ {"id": "edge119",
+ "label": {"text": "next"},
+ "sourceNodeId": "188_ifcvt",
+ "targetNodeId": "189_vect"},
+ {"id": "edge120",
+ "label": {"text": "next"},
+ "sourceNodeId": "187_ch_vect",
+ "targetNodeId": "188_ifcvt"},
+ {"id": "edge121",
+ "label": {"text": "next"},
+ "sourceNodeId": "186_ompexpssa",
+ "targetNodeId": "187_ch_vect"},
+ {"id": "edge122",
+ "label": {"text": "next"},
+ "sourceNodeId": "185_parloops",
+ "targetNodeId": "186_ompexpssa"},
+ {"id": "edge123",
+ "label": {"text": "next"},
+ "sourceNodeId": "180_graphite0",
+ "targetNodeId": "185_parloops"},
+ {"id": "edge124",
+ "label": {"text": "next"},
+ "sourceNodeId": "179_copyprop",
+ "targetNodeId": "180_graphite0"},
+ {"id": "edge125",
+ "label": {"text": "next"},
+ "sourceNodeId": "178_linterchange",
+ "targetNodeId": "179_copyprop"},
+ {"id": "edge126",
+ "label": {"text": "next"},
+ "sourceNodeId": "177_crc",
+ "targetNodeId": "178_linterchange"},
+ {"id": "edge127",
+ "label": {"text": "next"},
+ "sourceNodeId": "176_ldist",
+ "targetNodeId": "177_crc"},
+ {"id": "edge128",
+ "label": {"text": "next"},
+ "sourceNodeId": "175_ivcanon",
+ "targetNodeId": "176_ldist"},
+ {"id": "edge129",
+ "label": {"text": "next"},
+ "sourceNodeId": "174_cddce",
+ "targetNodeId": "175_ivcanon"},
+ {"id": "edge130",
+ "label": {"text": "next"},
+ "sourceNodeId": "173_unrolljam",
+ "targetNodeId": "174_cddce"},
+ {"id": "edge131",
+ "label": {"text": "next"},
+ "sourceNodeId": "172_lversion",
+ "targetNodeId": "173_unrolljam"},
+ {"id": "edge132",
+ "label": {"text": "next"},
+ "sourceNodeId": "171_sccp",
+ "targetNodeId": "172_lversion"},
+ {"id": "edge133",
+ "label": {"text": "next"},
+ "sourceNodeId": "170_lsplit",
+ "targetNodeId": "171_sccp"},
+ {"id": "edge134",
+ "label": {"text": "next"},
+ "sourceNodeId": "169_unswitch",
+ "targetNodeId": "170_lsplit"},
+ {"id": "edge135",
+ "label": {"text": "next"},
+ "sourceNodeId": "168_loopinit",
+ "targetNodeId": "169_unswitch"},
+ {"id": "edge136",
+ "label": {"text": "sub"},
+ "sourceNodeId": "167_loop",
+ "targetNodeId": "168_loopinit"},
+ {"id": "edge137",
+ "label": {"text": "sub"},
+ "sourceNodeId": "200_no_loop",
+ "targetNodeId": "201_slp"},
+ {"id": "edge138",
+ "label": {"text": "next"},
+ "sourceNodeId": "233_modref",
+ "targetNodeId": "234_uncprop"},
+ {"id": "edge139",
+ "label": {"text": "next"},
+ "sourceNodeId": "232_local-pure-const",
+ "targetNodeId": "233_modref"},
+ {"id": "edge140",
+ "label": {"text": "next"},
+ "sourceNodeId": "231_uninit",
+ "targetNodeId": "232_local-pure-const"},
+ {"id": "edge141",
+ "label": {"text": "next"},
+ "sourceNodeId": "230_crited",
+ "targetNodeId": "231_uninit"},
+ {"id": "edge142",
+ "label": {"text": "next"},
+ "sourceNodeId": "229_tailc",
+ "targetNodeId": "230_crited"},
+ {"id": "edge143",
+ "label": {"text": "next"},
+ "sourceNodeId": "228_sccopy",
+ "targetNodeId": "229_tailc"},
+ {"id": "edge144",
+ "label": {"text": "next"},
+ "sourceNodeId": "227_cddce",
+ "targetNodeId": "228_sccopy"},
+ {"id": "edge145",
+ "label": {"text": "next"},
+ "sourceNodeId": "226_store-merging",
+ "targetNodeId": "227_cddce"},
+ {"id": "edge146",
+ "label": {"text": "next"},
+ "sourceNodeId": "225_widening_mul",
+ "targetNodeId": "226_store-merging"},
+ {"id": "edge147",
+ "label": {"text": "next"},
+ "sourceNodeId": "224_fab",
+ "targetNodeId": "225_widening_mul"},
+ {"id": "edge148",
+ "label": {"text": "next"},
+ "sourceNodeId": "223_phiopt",
+ "targetNodeId": "224_fab"},
+ {"id": "edge149",
+ "label": {"text": "next"},
+ "sourceNodeId": "222_sink",
+ "targetNodeId": "223_phiopt"},
+ {"id": "edge150",
+ "label": {"text": "next"},
+ "sourceNodeId": "221_forwprop",
+ "targetNodeId": "222_sink"},
+ {"id": "edge151",
+ "label": {"text": "next"},
+ "sourceNodeId": "220_dce",
+ "targetNodeId": "221_forwprop"},
+ {"id": "edge152",
+ "label": {"text": "next"},
+ "sourceNodeId": "219_dse",
+ "targetNodeId": "220_dce"},
+ {"id": "edge153",
+ "label": {"text": "next"},
+ "sourceNodeId": "218_wrestrict",
+ "targetNodeId": "219_dse"},
+ {"id": "edge154",
+ "label": {"text": "next"},
+ "sourceNodeId": "217_ccp",
+ "targetNodeId": "218_wrestrict"},
+ {"id": "edge155",
+ "label": {"text": "next"},
+ "sourceNodeId": "216_vrp",
+ "targetNodeId": "217_ccp"},
+ {"id": "edge156",
+ "label": {"text": "next"},
+ "sourceNodeId": "215_threadfull",
+ "targetNodeId": "216_vrp"},
+ {"id": "edge157",
+ "label": {"text": "next"},
+ "sourceNodeId": "214_strlen",
+ "targetNodeId": "215_threadfull"},
+ {"id": "edge158",
+ "label": {"text": "next"},
+ "sourceNodeId": "213_dom",
+ "targetNodeId": "214_strlen"},
+ {"id": "edge159",
+ "label": {"text": "next"},
+ "sourceNodeId": "212_thread",
+ "targetNodeId": "213_dom"},
+ {"id": "edge160",
+ "label": {"text": "next"},
+ "sourceNodeId": "211_fre",
+ "targetNodeId": "212_thread"},
+ {"id": "edge161",
+ "label": {"text": "next"},
+ "sourceNodeId": "210_tracer",
+ "targetNodeId": "211_fre"},
+ {"id": "edge162",
+ "label": {"text": "next"},
+ "sourceNodeId": "209_split-paths",
+ "targetNodeId": "210_tracer"},
+ {"id": "edge163",
+ "label": {"text": "next"},
+ "sourceNodeId": "208_slsr",
+ "targetNodeId": "209_split-paths"},
+ {"id": "edge164",
+ "label": {"text": "next"},
+ "sourceNodeId": "207_reassoc",
+ "targetNodeId": "208_slsr"},
+ {"id": "edge165",
+ "label": {"text": "next"},
+ "sourceNodeId": "206_recip",
+ "targetNodeId": "207_reassoc"},
+ {"id": "edge166",
+ "label": {"text": "next"},
+ "sourceNodeId": "205_sincos",
+ "targetNodeId": "206_recip"},
+ {"id": "edge167",
+ "label": {"text": "next"},
+ "sourceNodeId": "204_switchlower",
+ "targetNodeId": "205_sincos"},
+ {"id": "edge168",
+ "label": {"text": "next"},
+ "sourceNodeId": "203_veclower2",
+ "targetNodeId": "204_switchlower"},
+ {"id": "edge169",
+ "label": {"text": "next"},
+ "sourceNodeId": "202_simduid",
+ "targetNodeId": "203_veclower2"},
+ {"id": "edge170",
+ "label": {"text": "next"},
+ "sourceNodeId": "200_no_loop",
+ "targetNodeId": "202_simduid"},
+ {"id": "edge171",
+ "label": {"text": "next"},
+ "sourceNodeId": "167_loop",
+ "targetNodeId": "200_no_loop"},
+ {"id": "edge172",
+ "label": {"text": "next"},
+ "sourceNodeId": "166_fix_loops",
+ "targetNodeId": "167_loop"},
+ {"id": "edge173",
+ "label": {"text": "next"},
+ "sourceNodeId": "165_dce",
+ "targetNodeId": "166_fix_loops"},
+ {"id": "edge174",
+ "label": {"text": "next"},
+ "sourceNodeId": "164_dse",
+ "targetNodeId": "165_dce"},
+ {"id": "edge175",
+ "label": {"text": "next"},
+ "sourceNodeId": "163_tsan",
+ "targetNodeId": "164_dse"},
+ {"id": "edge176",
+ "label": {"text": "next"},
+ "sourceNodeId": "162_asan",
+ "targetNodeId": "163_tsan"},
+ {"id": "edge177",
+ "label": {"text": "next"},
+ "sourceNodeId": "161_sancov",
+ "targetNodeId": "162_asan"},
+ {"id": "edge178",
+ "label": {"text": "next"},
+ "sourceNodeId": "160_sink",
+ "targetNodeId": "161_sancov"},
+ {"id": "edge179",
+ "label": {"text": "next"},
+ "sourceNodeId": "159_pre",
+ "targetNodeId": "160_sink"},
+ {"id": "edge180",
+ "label": {"text": "next"},
+ "sourceNodeId": "158_walloca",
+ "targetNodeId": "159_pre"},
+ {"id": "edge181",
+ "label": {"text": "next"},
+ "sourceNodeId": "157_lim",
+ "targetNodeId": "158_walloca"},
+ {"id": "edge182",
+ "label": {"text": "next"},
+ "sourceNodeId": "156_laddress",
+ "targetNodeId": "157_lim"},
+ {"id": "edge183",
+ "label": {"text": "next"},
+ "sourceNodeId": "155_bswap",
+ "targetNodeId": "156_laddress"},
+ {"id": "edge184",
+ "label": {"text": "next"},
+ "sourceNodeId": "154_pow",
+ "targetNodeId": "155_bswap"},
+ {"id": "edge185",
+ "label": {"text": "next"},
+ "sourceNodeId": "153_ccp",
+ "targetNodeId": "154_pow"},
+ {"id": "edge186",
+ "label": {"text": "next"},
+ "sourceNodeId": "152_phiopt",
+ "targetNodeId": "153_ccp"},
+ {"id": "edge187",
+ "label": {"text": "next"},
+ "sourceNodeId": "151_forwprop",
+ "targetNodeId": "152_phiopt"},
+ {"id": "edge188",
+ "label": {"text": "next"},
+ "sourceNodeId": "150_dce",
+ "targetNodeId": "151_forwprop"},
+ {"id": "edge189",
+ "label": {"text": "next"},
+ "sourceNodeId": "149_reassoc",
+ "targetNodeId": "150_dce"},
+ {"id": "edge190",
+ "label": {"text": "next"},
+ "sourceNodeId": "148_isolate-paths",
+ "targetNodeId": "149_reassoc"},
+ {"id": "edge191",
+ "label": {"text": "next"},
+ "sourceNodeId": "147_copyprop",
+ "targetNodeId": "148_isolate-paths"},
+ {"id": "edge192",
+ "label": {"text": "next"},
+ "sourceNodeId": "146_dom",
+ "targetNodeId": "147_copyprop"},
+ {"id": "edge193",
+ "label": {"text": "next"},
+ "sourceNodeId": "145_thread",
+ "targetNodeId": "146_dom"},
+ {"id": "edge194",
+ "label": {"text": "next"},
+ "sourceNodeId": "144_sra",
+ "targetNodeId": "145_thread"},
+ {"id": "edge195",
+ "label": {"text": "next"},
+ "sourceNodeId": "143_bitintlower",
+ "targetNodeId": "144_sra"},
+ {"id": "edge196",
+ "label": {"text": "next"},
+ "sourceNodeId": "142_cplxlower",
+ "targetNodeId": "143_bitintlower"},
+ {"id": "edge197",
+ "label": {"text": "next"},
+ "sourceNodeId": "141_ch",
+ "targetNodeId": "142_cplxlower"},
+ {"id": "edge198",
+ "label": {"text": "next"},
+ "sourceNodeId": "140_tailr",
+ "targetNodeId": "141_ch"},
+ {"id": "edge199",
+ "label": {"text": "next"},
+ "sourceNodeId": "139_phiopt",
+ "targetNodeId": "140_tailr"},
+ {"id": "edge200",
+ "label": {"text": "next"},
+ "sourceNodeId": "138_mergephi",
+ "targetNodeId": "139_phiopt"},
+ {"id": "edge201",
+ "label": {"text": "next"},
+ "sourceNodeId": "137_ifcombine",
+ "targetNodeId": "138_mergephi"},
+ {"id": "edge202",
+ "label": {"text": "next"},
+ "sourceNodeId": "136_copyprop",
+ "targetNodeId": "137_ifcombine"},
+ {"id": "edge203",
+ "label": {"text": "next"},
+ "sourceNodeId": "135_cselim",
+ "targetNodeId": "136_copyprop"},
+ {"id": "edge204",
+ "label": {"text": "next"},
+ "sourceNodeId": "134_cdce",
+ "targetNodeId": "135_cselim"},
+ {"id": "edge205",
+ "label": {"text": "next"},
+ "sourceNodeId": "133_stdarg",
+ "targetNodeId": "134_cdce"},
+ {"id": "edge206",
+ "label": {"text": "next"},
+ "sourceNodeId": "132_dce",
+ "targetNodeId": "133_stdarg"},
+ {"id": "edge207",
+ "label": {"text": "next"},
+ "sourceNodeId": "131_dse",
+ "targetNodeId": "132_dce"},
+ {"id": "edge208",
+ "label": {"text": "next"},
+ "sourceNodeId": "130_bounds",
+ "targetNodeId": "131_dse"},
+ {"id": "edge209",
+ "label": {"text": "next"},
+ "sourceNodeId": "129_vrp",
+ "targetNodeId": "130_bounds"},
+ {"id": "edge210",
+ "label": {"text": "next"},
+ "sourceNodeId": "128_threadfull",
+ "targetNodeId": "129_vrp"},
+ {"id": "edge211",
+ "label": {"text": "next"},
+ "sourceNodeId": "127_mergephi",
+ "targetNodeId": "128_threadfull"},
+ {"id": "edge212",
+ "label": {"text": "next"},
+ "sourceNodeId": "126_fre",
+ "targetNodeId": "127_mergephi"},
+ {"id": "edge213",
+ "label": {"text": "next"},
+ "sourceNodeId": "125_retslot",
+ "targetNodeId": "126_fre"},
+ {"id": "edge214",
+ "label": {"text": "next"},
+ "sourceNodeId": "124_alias",
+ "targetNodeId": "125_retslot"},
+ {"id": "edge215",
+ "label": {"text": "next"},
+ "sourceNodeId": "123_forwprop",
+ "targetNodeId": "124_alias"},
+ {"id": "edge216",
+ "label": {"text": "next"},
+ "sourceNodeId": "122_phiprop",
+ "targetNodeId": "123_forwprop"},
+ {"id": "edge217",
+ "label": {"text": "next"},
+ "sourceNodeId": "121_backprop",
+ "targetNodeId": "122_phiprop"},
+ {"id": "edge218",
+ "label": {"text": "next"},
+ "sourceNodeId": "120_cunrolli",
+ "targetNodeId": "121_backprop"},
+ {"id": "edge219",
+ "label": {"text": "next"},
+ "sourceNodeId": "119_rebuild_frequencies",
+ "targetNodeId": "120_cunrolli"},
+ {"id": "edge220",
+ "label": {"text": "next"},
+ "sourceNodeId": "118_waccess",
+ "targetNodeId": "119_rebuild_frequencies"},
+ {"id": "edge221",
+ "label": {"text": "next"},
+ "sourceNodeId": "117_post_ipa_warn",
+ "targetNodeId": "118_waccess"},
+ {"id": "edge222",
+ "label": {"text": "next"},
+ "sourceNodeId": "116_objsz",
+ "targetNodeId": "117_post_ipa_warn"},
+ {"id": "edge223",
+ "label": {"text": "next"},
+ "sourceNodeId": "115_ccp",
+ "targetNodeId": "116_objsz"},
+ {"id": "edge224",
+ "label": {"text": "next"},
+ "sourceNodeId": "*strip_predict_hints_0x101f27e0",
+ "targetNodeId": "115_ccp"},
+ {"id": "edge225",
+ "label": {"text": "next"},
+ "sourceNodeId": "*remove_cgraph_callee_edges_0x101f2780",
+ "targetNodeId": "*strip_predict_hints_0x101f27e0"},
+ {"id": "edge226",
+ "label": {"text": "sub"},
+ "sourceNodeId": "*all_optimizations_0x101f2720",
+ "targetNodeId": "*remove_cgraph_callee_edges_0x101f2780"},
+ {"id": "edge227",
+ "label": {"text": "next"},
+ "sourceNodeId": "251_uninit",
+ "targetNodeId": "252_uncprop"},
+ {"id": "edge228",
+ "label": {"text": "next"},
+ "sourceNodeId": "250_crited",
+ "targetNodeId": "251_uninit"},
+ {"id": "edge229",
+ "label": {"text": "next"},
+ "sourceNodeId": "249_tsan",
+ "targetNodeId": "250_crited"},
+ {"id": "edge230",
+ "label": {"text": "next"},
+ "sourceNodeId": "248_asan",
+ "targetNodeId": "249_tsan"},
+ {"id": "edge231",
+ "label": {"text": "next"},
+ "sourceNodeId": "247_sancov",
+ "targetNodeId": "248_asan"},
+ {"id": "edge232",
+ "label": {"text": "next"},
+ "sourceNodeId": "246_rebuild_frequencies",
+ "targetNodeId": "247_sancov"},
+ {"id": "edge233",
+ "label": {"text": "next"},
+ "sourceNodeId": "245_dce",
+ "targetNodeId": "246_rebuild_frequencies"},
+ {"id": "edge234",
+ "label": {"text": "next"},
+ "sourceNodeId": "244_copyprop",
+ "targetNodeId": "245_dce"},
+ {"id": "edge235",
+ "label": {"text": "next"},
+ "sourceNodeId": "243_strlen",
+ "targetNodeId": "244_copyprop"},
+ {"id": "edge236",
+ "label": {"text": "next"},
+ "sourceNodeId": "242_fab",
+ "targetNodeId": "243_strlen"},
+ {"id": "edge237",
+ "label": {"text": "next"},
+ "sourceNodeId": "241_objsz",
+ "targetNodeId": "242_fab"},
+ {"id": "edge238",
+ "label": {"text": "next"},
+ "sourceNodeId": "240_post_ipa_warn",
+ "targetNodeId": "241_objsz"},
+ {"id": "edge239",
+ "label": {"text": "next"},
+ "sourceNodeId": "239_ccp",
+ "targetNodeId": "240_post_ipa_warn"},
+ {"id": "edge240",
+ "label": {"text": "next"},
+ "sourceNodeId": "238_switchlower",
+ "targetNodeId": "239_ccp"},
+ {"id": "edge241",
+ "label": {"text": "next"},
+ "sourceNodeId": "237_veclower2",
+ "targetNodeId": "238_switchlower"},
+ {"id": "edge242",
+ "label": {"text": "next"},
+ "sourceNodeId": "236_bitintlower",
+ "targetNodeId": "237_veclower2"},
+ {"id": "edge243",
+ "label": {"text": "next"},
+ "sourceNodeId": "235_cplxlower",
+ "targetNodeId": "236_bitintlower"},
+ {"id": "edge244",
+ "label": {"text": "next"},
+ "sourceNodeId": "*strip_predict_hints_0x101f5bb0",
+ "targetNodeId": "235_cplxlower"},
+ {"id": "edge245",
+ "label": {"text": "next"},
+ "sourceNodeId": "*remove_cgraph_callee_edges_0x101f5b50",
+ "targetNodeId": "*strip_predict_hints_0x101f5bb0"},
+ {"id": "edge246",
+ "label": {"text": "sub"},
+ "sourceNodeId": "*all_optimizations_g_0x101f5af0",
+ "targetNodeId": "*remove_cgraph_callee_edges_0x101f5b50"},
+ {"id": "edge247",
+ "label": {"text": "next"},
+ "sourceNodeId": "255_tmmemopt",
+ "targetNodeId": "256_tmedge"},
+ {"id": "edge248",
+ "label": {"text": "next"},
+ "sourceNodeId": "254_tmmark",
+ "targetNodeId": "255_tmmemopt"},
+ {"id": "edge249",
+ "label": {"text": "sub"},
+ "sourceNodeId": "*tminit_0x101f6370",
+ "targetNodeId": "254_tmmark"},
+ {"id": "edge250",
+ "label": {"text": "next"},
+ "sourceNodeId": "299_loop2_doloop",
+ "targetNodeId": "300_loop2_done"},
+ {"id": "edge251",
+ "label": {"text": "next"},
+ "sourceNodeId": "298_loop2_unroll",
+ "targetNodeId": "299_loop2_doloop"},
+ {"id": "edge252",
+ "label": {"text": "next"},
+ "sourceNodeId": "297_loop2_invariant",
+ "targetNodeId": "298_loop2_unroll"},
+ {"id": "edge253",
+ "label": {"text": "next"},
+ "sourceNodeId": "296_loop2_init",
+ "targetNodeId": "297_loop2_invariant"},
+ {"id": "edge254",
+ "label": {"text": "sub"},
+ "sourceNodeId": "295_loop2",
+ "targetNodeId": "296_loop2_init"},
+ {"id": "edge255",
+ "label": {"text": "next"},
+ "sourceNodeId": "355_split4",
+ "targetNodeId": "356_stack"},
+ {"id": "edge256",
+ "label": {"text": "sub"},
+ "sourceNodeId": "*stack_regs_0x101f8d10",
+ "targetNodeId": "355_split4"},
+ {"id": "edge257",
+ "label": {"text": "next"},
+ "sourceNodeId": "354_sched2",
+ "targetNodeId": "*stack_regs_0x101f8d10"},
+ {"id": "edge258",
+ "label": {"text": "next"},
+ "sourceNodeId": "353_split3",
+ "targetNodeId": "354_sched2"},
+ {"id": "edge259",
+ "label": {"text": "next"},
+ "sourceNodeId": "*leaf_regs_0x101f8bf0",
+ "targetNodeId": "353_split3"},
+ {"id": "edge260",
+ "label": {"text": "next"},
+ "sourceNodeId": "352_bbro",
+ "targetNodeId": "*leaf_regs_0x101f8bf0"},
+ {"id": "edge261",
+ "label": {"text": "next"},
+ "sourceNodeId": "351_rtl_dce",
+ "targetNodeId": "352_bbro"},
+ {"id": "edge262",
+ "label": {"text": "next"},
+ "sourceNodeId": "350_cprop_hardreg",
+ "targetNodeId": "351_rtl_dce"},
+ {"id": "edge263",
+ "label": {"text": "next"},
+ "sourceNodeId": "349_fold_mem_offsets",
+ "targetNodeId": "350_cprop_hardreg"},
+ {"id": "edge264",
+ "label": {"text": "next"},
+ "sourceNodeId": "348_rnreg",
+ "targetNodeId": "349_fold_mem_offsets"},
+ {"id": "edge265",
+ "label": {"text": "next"},
+ "sourceNodeId": "347_ce3",
+ "targetNodeId": "348_rnreg"},
+ {"id": "edge266",
+ "label": {"text": "next"},
+ "sourceNodeId": "346_peephole2",
+ "targetNodeId": "347_ce3"},
+ {"id": "edge267",
+ "label": {"text": "next"},
+ "sourceNodeId": "345_sched_fusion",
+ "targetNodeId": "346_peephole2"},
+ {"id": "edge268",
+ "label": {"text": "next"},
+ "sourceNodeId": "344_compgotos",
+ "targetNodeId": "345_sched_fusion"},
+ {"id": "edge269",
+ "label": {"text": "next"},
+ "sourceNodeId": "343_jump2",
+ "targetNodeId": "344_compgotos"},
+ {"id": "edge270",
+ "label": {"text": "next"},
+ "sourceNodeId": "342_csa",
+ "targetNodeId": "343_jump2"},
+ {"id": "edge271",
+ "label": {"text": "next"},
+ "sourceNodeId": "341_dse2",
+ "targetNodeId": "342_csa"},
+ {"id": "edge272",
+ "label": {"text": "next"},
+ "sourceNodeId": "340_pro_and_epilogue",
+ "targetNodeId": "341_dse2"},
+ {"id": "edge273",
+ "label": {"text": "next"},
+ "sourceNodeId": "339_cmpelim",
+ "targetNodeId": "340_pro_and_epilogue"},
+ {"id": "edge274",
+ "label": {"text": "next"},
+ "sourceNodeId": "338_ree",
+ "targetNodeId": "339_cmpelim"},
+ {"id": "edge275",
+ "label": {"text": "next"},
+ "sourceNodeId": "337_split2",
+ "targetNodeId": "338_ree"},
+ {"id": "edge276",
+ "label": {"text": "next"},
+ "sourceNodeId": "336_gcse2",
+ "targetNodeId": "337_split2"},
+ {"id": "edge277",
+ "label": {"text": "next"},
+ "sourceNodeId": "335_late_combine",
+ "targetNodeId": "336_gcse2"},
+ {"id": "edge278",
+ "label": {"text": "next"},
+ "sourceNodeId": "334_vzeroupper",
+ "targetNodeId": "335_late_combine"},
+ {"id": "edge279",
+ "label": {"text": "next"},
+ "sourceNodeId": "333_postreload",
+ "targetNodeId": "334_vzeroupper"},
+ {"id": "edge280",
+ "label": {"text": "sub"},
+ "sourceNodeId": "*all-postreload_0x101f8410",
+ "targetNodeId": "333_postreload"},
+ {"id": "edge281",
+ "label": {"text": "next"},
+ "sourceNodeId": "370_dwarf2",
+ "targetNodeId": "371_final"},
+ {"id": "edge282",
+ "label": {"text": "next"},
+ "sourceNodeId": "369_nothrow",
+ "targetNodeId": "370_dwarf2"},
+ {"id": "edge283",
+ "label": {"text": "next"},
+ "sourceNodeId": "368_shorten",
+ "targetNodeId": "369_nothrow"},
+ {"id": "edge284",
+ "label": {"text": "next"},
+ "sourceNodeId": "367_align_tight_loops",
+ "targetNodeId": "368_shorten"},
+ {"id": "edge285",
+ "label": {"text": "next"},
+ "sourceNodeId": "366_endbr_and_patchable_area",
+ "targetNodeId": "367_align_tight_loops"},
+ {"id": "edge286",
+ "label": {"text": "next"},
+ "sourceNodeId": "365_eh_ranges",
+ "targetNodeId": "366_endbr_and_patchable_area"},
+ {"id": "edge287",
+ "label": {"text": "next"},
+ "sourceNodeId": "364_split5",
+ "targetNodeId": "365_eh_ranges"},
+ {"id": "edge288",
+ "label": {"text": "next"},
+ "sourceNodeId": "363_dbr",
+ "targetNodeId": "364_split5"},
+ {"id": "edge289",
+ "label": {"text": "next"},
+ "sourceNodeId": "362_barriers",
+ "targetNodeId": "363_dbr"},
+ {"id": "edge290",
+ "label": {"text": "next"},
+ "sourceNodeId": "361_mach",
+ "targetNodeId": "362_barriers"},
+ {"id": "edge291",
+ "label": {"text": "next"},
+ "sourceNodeId": "*free_cfg_0x101f9010",
+ "targetNodeId": "361_mach"},
+ {"id": "edge292",
+ "label": {"text": "next"},
+ "sourceNodeId": "360_vartrack",
+ "targetNodeId": "*free_cfg_0x101f9010"},
+ {"id": "edge293",
+ "label": {"text": "next"},
+ "sourceNodeId": "359_alignments",
+ "targetNodeId": "360_vartrack"},
+ {"id": "edge294",
+ "label": {"text": "next"},
+ "sourceNodeId": "358_zero_call_used_regs",
+ "targetNodeId": "359_alignments"},
+ {"id": "edge295",
+ "label": {"text": "sub"},
+ "sourceNodeId": "*all-late_compilation_0x101f8e90",
+ "targetNodeId": "358_zero_call_used_regs"},
+ {"id": "edge296",
+ "label": {"text": "next"},
+ "sourceNodeId": "*all-late_compilation_0x101f8e90",
+ "targetNodeId": "372_dfinish"},
+ {"id": "edge297",
+ "label": {"text": "next"},
+ "sourceNodeId": "357_late_pro_and_epilogue",
+ "targetNodeId": "*all-late_compilation_0x101f8e90"},
+ {"id": "edge298",
+ "label": {"text": "next"},
+ "sourceNodeId": "*all-postreload_0x101f8410",
+ "targetNodeId": "357_late_pro_and_epilogue"},
+ {"id": "edge299",
+ "label": {"text": "next"},
+ "sourceNodeId": "332_reload",
+ "targetNodeId": "*all-postreload_0x101f8410"},
+ {"id": "edge300",
+ "label": {"text": "next"},
+ "sourceNodeId": "331_ira",
+ "targetNodeId": "332_reload"},
+ {"id": "edge301",
+ "label": {"text": "next"},
+ "sourceNodeId": "330_early_remat",
+ "targetNodeId": "331_ira"},
+ {"id": "edge302",
+ "label": {"text": "next"},
+ "sourceNodeId": "329_avoid_store_forwarding",
+ "targetNodeId": "330_early_remat"},
+ {"id": "edge303",
+ "label": {"text": "next"},
+ "sourceNodeId": "328_sched1",
+ "targetNodeId": "329_avoid_store_forwarding"},
+ {"id": "edge304",
+ "label": {"text": "next"},
+ "sourceNodeId": "327_lr_shrinkage",
+ "targetNodeId": "328_sched1"},
+ {"id": "edge305",
+ "label": {"text": "next"},
+ "sourceNodeId": "326_sms",
+ "targetNodeId": "327_lr_shrinkage"},
+ {"id": "edge306",
+ "label": {"text": "next"},
+ "sourceNodeId": "325_asmcons",
+ "targetNodeId": "326_sms"},
+ {"id": "edge307",
+ "label": {"text": "next"},
+ "sourceNodeId": "324_mode_sw",
+ "targetNodeId": "325_asmcons"},
+ {"id": "edge308",
+ "label": {"text": "next"},
+ "sourceNodeId": "*stack_ptr_mod_0x101f8050",
+ "targetNodeId": "324_mode_sw"},
+ {"id": "edge309",
+ "label": {"text": "next"},
+ "sourceNodeId": "323_no-opt dfinit",
+ "targetNodeId": "*stack_ptr_mod_0x101f8050"},
+ {"id": "edge310",
+ "label": {"text": "next"},
+ "sourceNodeId": "322_subreg3",
+ "targetNodeId": "323_no-opt dfinit"},
+ {"id": "edge311",
+ "label": {"text": "next"},
+ "sourceNodeId": "321_split1",
+ "targetNodeId": "322_subreg3"},
+ {"id": "edge312",
+ "label": {"text": "next"},
+ "sourceNodeId": "320_outof_cfglayout",
+ "targetNodeId": "321_split1"},
+ {"id": "edge313",
+ "label": {"text": "next"},
+ "sourceNodeId": "319_bbpart",
+ "targetNodeId": "320_outof_cfglayout"},
+ {"id": "edge314",
+ "label": {"text": "next"},
+ "sourceNodeId": "318_jump_after_combine",
+ "targetNodeId": "319_bbpart"},
+ {"id": "edge315",
+ "label": {"text": "next"},
+ "sourceNodeId": "317_ce2",
+ "targetNodeId": "318_jump_after_combine"},
+ {"id": "edge316",
+ "label": {"text": "next"},
+ "sourceNodeId": "316_stv",
+ "targetNodeId": "317_ce2"},
+ {"id": "edge317",
+ "label": {"text": "next"},
+ "sourceNodeId": "315_rrvl",
+ "targetNodeId": "316_stv"},
+ {"id": "edge318",
+ "label": {"text": "next"},
+ "sourceNodeId": "314_rpad",
+ "targetNodeId": "315_rrvl"},
+ {"id": "edge319",
+ "label": {"text": "next"},
+ "sourceNodeId": "313_late_combine",
+ "targetNodeId": "314_rpad"},
+ {"id": "edge320",
+ "label": {"text": "next"},
+ "sourceNodeId": "312_combine",
+ "targetNodeId": "313_late_combine"},
+ {"id": "edge321",
+ "label": {"text": "next"},
+ "sourceNodeId": "311_ext_dce",
+ "targetNodeId": "312_combine"},
+ {"id": "edge322",
+ "label": {"text": "next"},
+ "sourceNodeId": "310_ud_dce",
+ "targetNodeId": "311_ext_dce"},
+ {"id": "edge323",
+ "label": {"text": "next"},
+ "sourceNodeId": "309_init-regs",
+ "targetNodeId": "310_ud_dce"},
+ {"id": "edge324",
+ "label": {"text": "next"},
+ "sourceNodeId": "308_auto_inc_dec",
+ "targetNodeId": "309_init-regs"},
+ {"id": "edge325",
+ "label": {"text": "next"},
+ "sourceNodeId": "307_fwprop2",
+ "targetNodeId": "308_auto_inc_dec"},
+ {"id": "edge326",
+ "label": {"text": "next"},
+ "sourceNodeId": "306_dse1",
+ "targetNodeId": "307_fwprop2"},
+ {"id": "edge327",
+ "label": {"text": "next"},
+ "sourceNodeId": "305_cse2",
+ "targetNodeId": "306_dse1"},
+ {"id": "edge328",
+ "label": {"text": "next"},
+ "sourceNodeId": "304_stv",
+ "targetNodeId": "305_cse2"},
+ {"id": "edge329",
+ "label": {"text": "next"},
+ "sourceNodeId": "303_cprop",
+ "targetNodeId": "304_stv"},
+ {"id": "edge330",
+ "label": {"text": "next"},
+ "sourceNodeId": "302_web",
+ "targetNodeId": "303_cprop"},
+ {"id": "edge331",
+ "label": {"text": "next"},
+ "sourceNodeId": "301_subreg2",
+ "targetNodeId": "302_web"},
+ {"id": "edge332",
+ "label": {"text": "next"},
+ "sourceNodeId": "295_loop2",
+ "targetNodeId": "301_subreg2"},
+ {"id": "edge333",
+ "label": {"text": "next"},
+ "sourceNodeId": "294_reginfo",
+ "targetNodeId": "295_loop2"},
+ {"id": "edge334",
+ "label": {"text": "next"},
+ "sourceNodeId": "293_apx_nfcvt",
+ "targetNodeId": "294_reginfo"},
+ {"id": "edge335",
+ "label": {"text": "next"},
+ "sourceNodeId": "292_ce1",
+ "targetNodeId": "293_apx_nfcvt"},
+ {"id": "edge336",
+ "label": {"text": "next"},
+ "sourceNodeId": "291_cse_local",
+ "targetNodeId": "292_ce1"},
+ {"id": "edge337",
+ "label": {"text": "next"},
+ "sourceNodeId": "290_store_motion",
+ "targetNodeId": "291_cse_local"},
+ {"id": "edge338",
+ "label": {"text": "next"},
+ "sourceNodeId": "289_cprop",
+ "targetNodeId": "290_store_motion"},
+ {"id": "edge339",
+ "label": {"text": "next"},
+ "sourceNodeId": "288_hardreg_pre",
+ "targetNodeId": "289_cprop"},
+ {"id": "edge340",
+ "label": {"text": "next"},
+ "sourceNodeId": "287_hoist",
+ "targetNodeId": "288_hardreg_pre"},
+ {"id": "edge341",
+ "label": {"text": "next"},
+ "sourceNodeId": "286_rtl pre",
+ "targetNodeId": "287_hoist"},
+ {"id": "edge342",
+ "label": {"text": "next"},
+ "sourceNodeId": "285_cprop",
+ "targetNodeId": "286_rtl pre"},
+ {"id": "edge343",
+ "label": {"text": "next"},
+ "sourceNodeId": "284_fwprop1",
+ "targetNodeId": "285_cprop"},
+ {"id": "edge344",
+ "label": {"text": "next"},
+ "sourceNodeId": "283_cse1",
+ "targetNodeId": "284_fwprop1"},
+ {"id": "edge345",
+ "label": {"text": "next"},
+ "sourceNodeId": "282_dfinit",
+ "targetNodeId": "283_cse1"},
+ {"id": "edge346",
+ "label": {"text": "next"},
+ "sourceNodeId": "281_subreg1",
+ "targetNodeId": "282_dfinit"},
+ {"id": "edge347",
+ "label": {"text": "next"},
+ "sourceNodeId": "280_jump",
+ "targetNodeId": "281_subreg1"},
+ {"id": "edge348",
+ "label": {"text": "next"},
+ "sourceNodeId": "279_into_cfglayout",
+ "targetNodeId": "280_jump"},
+ {"id": "edge349",
+ "label": {"text": "next"},
+ "sourceNodeId": "278_vregs",
+ "targetNodeId": "279_into_cfglayout"},
+ {"id": "edge350",
+ "label": {"text": "sub"},
+ "sourceNodeId": "*rest_of_compilation_0x101f6e90",
+ "targetNodeId": "278_vregs"},
+ {"id": "edge351",
+ "label": {"text": "next"},
+ "sourceNodeId": "*rest_of_compilation_0x101f6e90",
+ "targetNodeId": "*clean_state_0x101f9500"},
+ {"id": "edge352",
+ "label": {"text": "next"},
+ "sourceNodeId": "277_expand",
+ "targetNodeId": "*rest_of_compilation_0x101f6e90"},
+ {"id": "edge353",
+ "label": {"text": "next"},
+ "sourceNodeId": "*warn_function_noreturn_0x101f6dd0",
+ "targetNodeId": "277_expand"},
+ {"id": "edge354",
+ "label": {"text": "next"},
+ "sourceNodeId": "276_optimized",
+ "targetNodeId": "*warn_function_noreturn_0x101f6dd0"},
+ {"id": "edge355",
+ "label": {"text": "next"},
+ "sourceNodeId": "275_waccess",
+ "targetNodeId": "276_optimized"},
+ {"id": "edge356",
+ "label": {"text": "next"},
+ "sourceNodeId": "274_hardcmp",
+ "targetNodeId": "275_waccess"},
+ {"id": "edge357",
+ "label": {"text": "next"},
+ "sourceNodeId": "273_hardcbr",
+ "targetNodeId": "274_hardcmp"},
+ {"id": "edge358",
+ "label": {"text": "next"},
+ "sourceNodeId": "272_isel",
+ "targetNodeId": "273_hardcbr"},
+ {"id": "edge359",
+ "label": {"text": "next"},
+ "sourceNodeId": "271_nrv",
+ "targetNodeId": "272_isel"},
+ {"id": "edge360",
+ "label": {"text": "next"},
+ "sourceNodeId": "270_resx",
+ "targetNodeId": "271_nrv"},
+ {"id": "edge361",
+ "label": {"text": "next"},
+ "sourceNodeId": "269_ehcleanup",
+ "targetNodeId": "270_resx"},
+ {"id": "edge362",
+ "label": {"text": "next"},
+ "sourceNodeId": "268_sanopt",
+ "targetNodeId": "269_ehcleanup"},
+ {"id": "edge363",
+ "label": {"text": "next"},
+ "sourceNodeId": "267_musttail",
+ "targetNodeId": "268_sanopt"},
+ {"id": "edge364",
+ "label": {"text": "next"},
+ "sourceNodeId": "266_tsan0",
+ "targetNodeId": "267_musttail"},
+ {"id": "edge365",
+ "label": {"text": "next"},
+ "sourceNodeId": "265_asan0",
+ "targetNodeId": "266_tsan0"},
+ {"id": "edge366",
+ "label": {"text": "next"},
+ "sourceNodeId": "264_switchlower_O0",
+ "targetNodeId": "265_asan0"},
+ {"id": "edge367",
+ "label": {"text": "next"},
+ "sourceNodeId": "263_sancov_O0",
+ "targetNodeId": "264_switchlower_O0"},
+ {"id": "edge368",
+ "label": {"text": "next"},
+ "sourceNodeId": "262_bitintlower0",
+ "targetNodeId": "263_sancov_O0"},
+ {"id": "edge369",
+ "label": {"text": "next"},
+ "sourceNodeId": "261_cplxlower0",
+ "targetNodeId": "262_bitintlower0"},
+ {"id": "edge370",
+ "label": {"text": "next"},
+ "sourceNodeId": "260_veclower",
+ "targetNodeId": "261_cplxlower0"},
+ {"id": "edge371",
+ "label": {"text": "next"},
+ "sourceNodeId": "259_lower_vaarg",
+ "targetNodeId": "260_veclower"},
+ {"id": "edge372",
+ "label": {"text": "next"},
+ "sourceNodeId": "258_vtable-verify",
+ "targetNodeId": "259_lower_vaarg"},
+ {"id": "edge373",
+ "label": {"text": "next"},
+ "sourceNodeId": "257_simduid",
+ "targetNodeId": "258_vtable-verify"},
+ {"id": "edge374",
+ "label": {"text": "next"},
+ "sourceNodeId": "*tminit_0x101f6370",
+ "targetNodeId": "257_simduid"},
+ {"id": "edge375",
+ "label": {"text": "next"},
+ "sourceNodeId": "253_assumptions",
+ "targetNodeId": "*tminit_0x101f6370"},
+ {"id": "edge376",
+ "label": {"text": "next"},
+ "sourceNodeId": "*all_optimizations_g_0x101f5af0",
+ "targetNodeId": "253_assumptions"},
+ {"id": "edge377",
+ "label": {"text": "next"},
+ "sourceNodeId": "*all_optimizations_0x101f2720",
+ "targetNodeId": "*all_optimizations_g_0x101f5af0"},
+ {"id": "edge378",
+ "label": {"text": "next"},
+ "sourceNodeId": "114_hardcfr",
+ "targetNodeId": "*all_optimizations_0x101f2720"},
+ {"id": "edge379",
+ "label": {"text": "next"},
+ "sourceNodeId": "113_adjust_alignment",
+ "targetNodeId": "114_hardcfr"},
+ {"id": "edge380",
+ "label": {"text": "next"},
+ "sourceNodeId": "112_omptargetlink",
+ "targetNodeId": "113_adjust_alignment"},
+ {"id": "edge381",
+ "label": {"text": "next"},
+ "sourceNodeId": "111_ompdevlow",
+ "targetNodeId": "112_omptargetlink"},
+ {"id": "edge382",
+ "label": {"text": "next"},
+ "sourceNodeId": "110_oaccdevlow",
+ "targetNodeId": "111_ompdevlow"},
+ {"id": "edge383",
+ "label": {"text": "next"},
+ "sourceNodeId": "109_omp_oacc_neuter_broadcast",
+ "targetNodeId": "110_oaccdevlow"},
+ {"id": "edge384",
+ "label": {"text": "next"},
+ "sourceNodeId": "108_oaccloops",
+ "targetNodeId": "109_omp_oacc_neuter_broadcast"},
+ {"id": "edge385",
+ "label": {"text": "next"},
+ "sourceNodeId": "107_ehdisp",
+ "targetNodeId": "108_oaccloops"},
+ {"id": "edge386",
+ "label": {"text": "next"},
+ "sourceNodeId": "106_fixup_cfg",
+ "targetNodeId": "107_ehdisp"}]}]}]}
+
+/* { dg-begin-multiline-output "" }
+In function 'test_graphs':
+/home/david/gcc-newgit-gcc16/src/gcc/testsuite/gcc.dg/plugin/diagnostic-test-graphs-sarif.c:8:3: error: this is a placeholder error, with graphs
+ { dg-end-multiline-output "" } */
+
+/* Use a Python script to verify various properties about the generated
+ .html file:
+ { dg-final { run-html-pytest graphs.sarif "2.1.0-valid/graphs-check-html.py" } } */
+
+/* Use a Python script to verify various properties about the *generated*
+ .sarif file:
+ { dg-final { run-sarif-pytest graphs.roundtrip "2.1.0-valid/graphs-check-sarif-roundtrip.py" } } */