diagnostics: generalize state graph code to use json::property instances (v2)

In r16-1631-g2334d30cd8feac I added support for capturing state
information from -fanalyzer in the form of embedded XML strings
in SARIF output.

In r16-2211-ga5d9debedd2f46 I rewrote this so the state was captured in
the form of a SARIF directed graph, using various custom types.

I want to add the ability to capture other kinds of graph in our SARIF
output (e.g. inheritance hierarchies, CFGs, etc), so  the following patch
reworks the state graph handling code to minimize the use of custom types.
Instead, the patch introduces various json::property types, and
describes the state graph serialization in terms of instances of these
properties, rather than hardcoding string attribute names in readers and
writers.  The custom SARIF properties live in a new
"gcc/custom-sarif-properties/" directory.

The "experimental-html" scheme keys "show-state-diagrams-dot-src" and
"show-state-diagrams-sarif" become "show-graph-dot-src" and
"show-graph-dot-src" in preparation for new kinds of graph in the output.

This is an updated version of the patch, tested to build with GCC 5
(which the previous version didn't leading to PR bootstrap/122151)

contrib/ChangeLog:
	* gcc.doxy (INPUT): Add gcc/custom-sarif-properties

gcc/ChangeLog:
	* Makefile.in (OBJS-libcommon): Add
	custom-sarif-properties/digraphs.o and
	custom-sarif-properties/state-graphs.o.  Remove
	diagnostics/state-graphs.o.
	* configure: Regenerate.
	* configure.ac: Add custom-sarif-properties to subdir iteration.
	* custom-sarif-properties/digraphs.cc: New file.
	* custom-sarif-properties/digraphs.h: New file.
	* custom-sarif-properties/state-graphs.cc: New file.
	* custom-sarif-properties/state-graphs.h: New file.
	* diagnostics/diagnostics-selftests.cc
	(run_diagnostics_selftests): Drop call of state_graphs_cc_tests.
	* diagnostics/diagnostics-selftests.h (state_graphs_cc_tests):
	Delete decl.
	* diagnostics/digraphs.cc: Include
	"custom-sarif-properties/digraphs.h".  Move include of
	"selftest.h" to within CHECKING_P section.
	(using digraph_object): New.
	(namespace properties): New.
	(diagnostics::digraphs::object::get_attr): Delete.
	(diagnostics::digraphs::object::set_attr): Delete.
	(diagnostics::digraphs::object::set_json_attr): Delete.
	(digraph_object::get_property): New definitions, for various
	property types.
	(digraph_object::set_property): Likewise.
	(digraph_object::maybe_get_property): New.
	(digraph_object::get_property_as_tristate): New.
	(digraph_object::ensure_property_bag): New.
	(digraph::get_graph_kind): New.
	(digraph::set_graph_kind): New.
	Add include of "custom-sarif-properties/state-graphs.h".
	(selftest::test_simple_graph): Rewrite to use json::property
	instances rather than string attribute names.
	(selftest::test_property_objects): New test.
	(selftest::digraphs_cc_tests): Call it.
	* diagnostics/digraphs.h: Include "tristate.h".
	(object::get_attr): Delete.
	(object::set_attr): Delete.
	(object::get_property): New decls.
	(object::set_property): New decls.
	(object::maybe_get_property): New.
	(object::get_property_as_tristate): New.
	(object::set_json_attr): Delete.
	(object::ensure_property_bag): New.
	(graph::get_graph_kind): New.
	(graph::set_graph_kind): New.
	* diagnostics/html-sink.cc
	(html_generation_options::html_generation_options): Update for
	field renamings.
	(html_generation_options::dump): Likewise.
	(html_builder::maybe_make_state_diagram): Likewise.
	(html_builder::add_graph): Show SARIF and .dot src inline, if
	requested.
	* diagnostics/html-sink.h
	(html_generation_options::m_show_state_diagrams_sarif): Rename
	to...
	(html_generation_options::m_show_graph_sarif): ...this.
	(html_generation_options::m_show_state_diagrams_dot_src): Rename
	to...
	(html_generation_options::m_show_graph_dot_src0): ...this.
	* diagnostics/output-spec.cc
	(html_scheme_handler::maybe_handle_kv): Rename keys.
	(html_scheme_handler::get_keys): Likewise.
	* diagnostics/state-graphs-to-dot.cc: : Reimplement throughout to
	use json::property instances found within custom_sarif_properties
	throughout, rather than types in diagnostics::state_graphs.
	* diagnostics/state-graphs.cc: Deleted file.
	* diagnostics/state-graphs.h: Delete almost all, except decl of
	diagnostics::state_graphs::make_dot_graph.
	* doc/invoke.texi: Update for changes to "experimental-html" sink
	keys.
	* json.cc (json::object::set_string): New.
	(json::object::set_integer): New.
	(json::object::set_bool): New.
	(json::object::set_array_of_string): New.
	* json.h: Include "label-text.h".
	(struct json::property): New template.
	(json::string_property): New.
	(json::integer_property): New.
	(json::bool_property): New.
	(json::json_property): New.
	(using json::array_of_string_property): New.
	(struct json::enum_traits): New.
	(enum_json::property): New.
	(json::value::dyn_cast_array): New vfunc.
	(json::value::dyn_cast_integer_number): New vfunc.
	(json::value::set_string): New.
	(json::value::set_integer): New.
	(json::value::set_bool): New.
	(json::value::set_array_of_string): New.
	(json::value::maybe_get_enum): New.
	(json::value::set_enum): New.
	(json::array::dyn_cast_array): New.
	(json::integer_number::dyn_cast_integer_number): New.
	(object::maybe_get_enum): New.
	(object::set_enum): New.

gcc/analyzer/ChangeLog:
	* ana-state-to-diagnostic-state.cc: Reimplement throughout to use
	json::property instances found within custom_sarif_properties
	throughout, rather than types in diagnostics::state_graphs.
	* ana-state-to-diagnostic-state.h: Likewise.
	* checker-event.cc: Likewise.
	* sm-malloc.cc: Likewise.

gcc/testsuite/ChangeLog:
	* gcc.dg/plugin/diagnostic_plugin_test_graphs.cc
	(report_diag_with_graphs): Port from set_attr to set_property.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
This commit is contained in:
David Malcolm 2025-10-16 17:39:03 -04:00
parent 822a139e7d
commit c89bd48e7e
26 changed files with 908 additions and 521 deletions

View File

@ -478,7 +478,7 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories # directories like "/usr/src/myproject". Separate the files or directories
# with spaces. # with spaces.
INPUT = gcc gcc/analyzer gcc/diagnostics gcc/text-art INPUT = gcc gcc/analyzer gcc/custom-sarif-properties gcc/diagnostics gcc/text-art
# This tag can be used to specify the character encoding of the source files that # This tag can be used to specify the character encoding of the source files that
# doxygen parses. Internally doxygen uses the UTF-8 encoding, which is also the default # doxygen parses. Internally doxygen uses the UTF-8 encoding, which is also the default

View File

@ -1861,6 +1861,8 @@ OBJS = \
# Objects in libcommon.a, potentially used by all host binaries and with # Objects in libcommon.a, potentially used by all host binaries and with
# no target dependencies. # no target dependencies.
OBJS-libcommon = \ OBJS-libcommon = \
custom-sarif-properties/digraphs.o \
custom-sarif-properties/state-graphs.o \
diagnostic-global-context.o \ diagnostic-global-context.o \
diagnostics/buffering.o \ diagnostics/buffering.o \
diagnostics/changes.o \ diagnostics/changes.o \
@ -1880,7 +1882,6 @@ OBJS-libcommon = \
diagnostics/paths.o \ diagnostics/paths.o \
diagnostics/paths-output.o \ diagnostics/paths-output.o \
diagnostics/source-printing.o \ diagnostics/source-printing.o \
diagnostics/state-graphs.o \
diagnostics/state-graphs-to-dot.o \ diagnostics/state-graphs-to-dot.o \
diagnostics/selftest-context.o \ diagnostics/selftest-context.o \
diagnostics/selftest-logical-locations.o \ diagnostics/selftest-logical-locations.o \

View File

@ -39,38 +39,55 @@ along with GCC; see the file COPYING3. If not see
namespace ana { namespace ana {
using namespace ::diagnostics::state_graphs; namespace node_properties = custom_sarif_properties::state_graphs::node;
static void static void
set_wi_attr (state_node_ref state_node, set_wi_attr (diagnostics::digraphs::node &state_node,
const char *attr_name, const json::string_property &property,
const wide_int_ref &w, const wide_int_ref &w,
signop sgn) signop sgn)
{ {
pretty_printer pp; pretty_printer pp;
pp_wide_int (&pp, w, sgn); pp_wide_int (&pp, w, sgn);
state_node.set_attr (attr_name, pp_formatted_text (&pp)); state_node.set_property (property, pp_formatted_text (&pp));
} }
static void static void
set_type_attr (state_node_ref state_node, const_tree type) set_type_attr (diagnostics::digraphs::node &state_node,
const_tree type)
{ {
gcc_assert (type); gcc_assert (type);
pretty_printer pp; pretty_printer pp;
pp_format_decoder (&pp) = default_tree_printer; pp_format_decoder (&pp) = default_tree_printer;
pp_printf (&pp, "%T", type); pp_printf (&pp, "%T", type);
state_node.set_type (pp_formatted_text (&pp)); state_node.set_property (node_properties::type,
pp_formatted_text (&pp));
} }
static void static void
set_bits_attr (state_node_ref state_node, set_bits_attr (diagnostics::digraphs::node & state_node,
bit_range bits) bit_range bits)
{ {
pretty_printer pp; pretty_printer pp;
bits.dump_to_pp (&pp); bits.dump_to_pp (&pp);
state_node.set_attr ("bits", pp_formatted_text (&pp)); state_node.set_property (node_properties::bits,
pp_formatted_text (&pp));
} }
static void
set_value_attrs (diagnostics::digraphs::node &state_node,
const svalue &sval)
{
state_node.set_property (node_properties::value,
sval.to_json ());
pretty_printer pp;
pp_format_decoder (&pp) = default_tree_printer;
sval.dump_to_pp (&pp, true);
state_node.set_property (node_properties::value_str,
pp_formatted_text (&pp));
}
// class analyzer_state_graph : public diagnostics::digraphs::digraph // class analyzer_state_graph : public diagnostics::digraphs::digraph
analyzer_state_graph::analyzer_state_graph (const program_state &state, analyzer_state_graph::analyzer_state_graph (const program_state &state,
@ -141,34 +158,34 @@ analyzer_state_graph::analyzer_state_graph (const program_state &state,
/* Ensure we have a node for the dst region. This /* Ensure we have a node for the dst region. This
could lead to additional pending edges. */ could lead to additional pending edges. */
auto dst_node = get_or_create_state_node (item.m_dst_reg); 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); add_edge (nullptr, item.m_src_node, dst_node);
} }
} }
state_node_ref diagnostics::digraphs::node &
analyzer_state_graph::get_or_create_state_node (const region &reg) analyzer_state_graph::get_or_create_state_node (const region &reg)
{ {
auto existing = m_region_to_state_node_map.find (&reg); auto existing = m_region_to_state_node_map.find (&reg);
if (existing != m_region_to_state_node_map.end ()) if (existing != m_region_to_state_node_map.end ())
return *existing->second; return *existing->second;
auto ref = create_and_add_state_node (reg); auto &state_node = create_and_add_state_node (reg);
m_region_to_state_node_map[&reg] = &ref.m_node; m_region_to_state_node_map[&reg] = &state_node;
return ref; return state_node;
} }
state_node_ref diagnostics::digraphs::node &
analyzer_state_graph::create_and_add_state_node (const region &reg) analyzer_state_graph::create_and_add_state_node (const region &reg)
{ {
auto node = create_state_node (reg); auto node = create_state_node (reg);
state_node_ref result = *node; diagnostics::digraphs::node &result = *node;
if (auto parent_reg = reg.get_parent_region ()) if (auto parent_reg = reg.get_parent_region ())
if (parent_reg->get_kind () != RK_ROOT) if (parent_reg->get_kind () != RK_ROOT)
{ {
auto parent_state_node = get_or_create_state_node (*parent_reg); auto &parent_state_node = get_or_create_state_node (*parent_reg);
parent_state_node.m_node.add_child (std::move (node)); parent_state_node.add_child (std::move (node));
return result; return result;
} }
add_node (std::move (node)); add_node (std::move (node));
@ -264,19 +281,18 @@ analyzer_state_graph::make_node_id (const region &reg)
std::unique_ptr<diagnostics::digraphs::node> std::unique_ptr<diagnostics::digraphs::node>
analyzer_state_graph:: analyzer_state_graph::
make_state_node (diagnostics::state_graphs::node_kind kind, make_state_node (enum node_properties::kind_t kind,
std::string id) std::string id)
{ {
auto node = std::make_unique<diagnostics::digraphs::node> (*this, std::move (id)); auto node = std::make_unique<diagnostics::digraphs::node> (*this, std::move (id));
state_node_ref node_ref (*node); node->set_property (node_properties::kind_prop, kind);
node_ref.set_node_kind (kind);
return node; return node;
} }
std::unique_ptr<diagnostics::digraphs::node> std::unique_ptr<diagnostics::digraphs::node>
analyzer_state_graph:: analyzer_state_graph::
make_memspace_state_node (const region &reg, make_memspace_state_node (const region &reg,
diagnostics::state_graphs::node_kind kind) enum node_properties::kind_t kind)
{ {
return make_state_node (kind, make_node_id (reg)); return make_state_node (kind, make_node_id (reg));
} }
@ -296,7 +312,7 @@ analyzer_state_graph::create_state_node (const region &reg)
const frame_region &frame_reg const frame_region &frame_reg
= static_cast<const frame_region &> (reg); = static_cast<const frame_region &> (reg);
node = make_state_node (diagnostics::state_graphs::node_kind::stack_frame, node = make_state_node (node_properties::kind_t::stack_frame,
make_node_id (reg)); make_node_id (reg));
node->set_logical_loc node->set_logical_loc
(m_logical_loc_mgr.key_from_tree (frame_reg.get_fndecl ())); (m_logical_loc_mgr.key_from_tree (frame_reg.get_fndecl ()));
@ -304,58 +320,59 @@ analyzer_state_graph::create_state_node (const region &reg)
pretty_printer pp; pretty_printer pp;
pp_format_decoder (&pp) = default_tree_printer; pp_format_decoder (&pp) = default_tree_printer;
pp_printf (&pp, "%E", frame_reg.get_fndecl ()); pp_printf (&pp, "%E", frame_reg.get_fndecl ());
node->set_attr (STATE_NODE_PREFIX, "function", node->set_property (node_properties::function,
pp_formatted_text (&pp)); pp_formatted_text (&pp));
} }
} }
break; break;
case RK_GLOBALS: case RK_GLOBALS:
node = make_memspace_state_node (reg, node = make_memspace_state_node (reg,
diagnostics::state_graphs::node_kind::globals); node_properties::kind_t::globals);
break; break;
case RK_CODE: case RK_CODE:
node = make_memspace_state_node (reg, node = make_memspace_state_node (reg,
diagnostics::state_graphs::node_kind::code); node_properties::kind_t::code);
break; break;
case RK_FUNCTION: case RK_FUNCTION:
node = make_memspace_state_node (reg, node = make_memspace_state_node (reg,
diagnostics::state_graphs::node_kind::function); node_properties::kind_t::function);
// TODO // TODO
break; break;
case RK_STACK: case RK_STACK:
node = make_memspace_state_node (reg, node = make_memspace_state_node (reg,
diagnostics::state_graphs::node_kind::stack); node_properties::kind_t::stack);
break; break;
case RK_HEAP: case RK_HEAP:
node = make_memspace_state_node (reg, node = make_memspace_state_node (reg,
diagnostics::state_graphs::node_kind::heap_); node_properties::kind_t::heap_);
break; break;
case RK_THREAD_LOCAL: case RK_THREAD_LOCAL:
node = make_memspace_state_node (reg, node = make_memspace_state_node (reg,
diagnostics::state_graphs::node_kind::thread_local_); node_properties::kind_t::thread_local_);
break; break;
case RK_ROOT: case RK_ROOT:
gcc_unreachable (); gcc_unreachable ();
break; break;
case RK_SYMBOLIC: case RK_SYMBOLIC:
node = make_memspace_state_node (reg, node = make_memspace_state_node (reg,
diagnostics::state_graphs::node_kind::other); node_properties::kind_t::other);
break; break;
case RK_DECL: case RK_DECL:
{ {
node = make_state_node (diagnostics::state_graphs::node_kind::variable, node = make_state_node (node_properties::kind_t::variable,
make_node_id (reg)); make_node_id (reg));
const decl_region &decl_reg const decl_region &decl_reg
= static_cast<const decl_region &> (reg); = static_cast<const decl_region &> (reg);
state_node_ref node_ref (*node);
{ {
pretty_printer pp; pretty_printer pp;
pp_format_decoder (&pp) = default_tree_printer; pp_format_decoder (&pp) = default_tree_printer;
pp_printf (&pp, "%E", decl_reg.get_decl ()); pp_printf (&pp, "%E", decl_reg.get_decl ());
node_ref.set_name (pp_formatted_text (&pp)); node->set_property (node_properties::name,
pp_formatted_text (&pp));
} }
set_type_attr (*node, TREE_TYPE (decl_reg.get_decl ())); set_type_attr (*node, TREE_TYPE (decl_reg.get_decl ()));
} }
@ -377,14 +394,15 @@ analyzer_state_graph::create_state_node (const region &reg)
case RK_ERRNO: case RK_ERRNO:
case RK_PRIVATE: case RK_PRIVATE:
case RK_UNKNOWN: case RK_UNKNOWN:
node = make_state_node (diagnostics::state_graphs::node_kind::other, node = make_state_node (node_properties::kind_t::other,
make_node_id (reg)); make_node_id (reg));
break; break;
case RK_HEAP_ALLOCATED: case RK_HEAP_ALLOCATED:
case RK_ALLOCA: case RK_ALLOCA:
node = make_memspace_state_node (reg, node
diagnostics::state_graphs::node_kind::dynalloc_buffer); = make_memspace_state_node (reg,
node_properties::kind_t::dynalloc_buffer);
set_attr_for_dynamic_extents (reg, *node); set_attr_for_dynamic_extents (reg, *node);
break; break;
} }
@ -425,9 +443,9 @@ create_state_nodes_for_binding_cluster (const binding_cluster &cluster,
get_or_create_state_node (*reg); get_or_create_state_node (*reg);
} }
auto ref = get_or_create_state_node (*cluster.get_base_region ()); auto &ref = get_or_create_state_node (*cluster.get_base_region ());
ref.m_node.add_child (create_state_node_for_conc_bindings (conc_bindings)); ref.add_child (create_state_node_for_conc_bindings (conc_bindings));
const region *typed_reg = cluster.get_base_region (); const region *typed_reg = cluster.get_base_region ();
if (!typed_reg->get_type ()) if (!typed_reg->get_type ())
@ -455,23 +473,18 @@ create_state_nodes_for_binding_cluster (const binding_cluster &cluster,
std::unique_ptr<diagnostics::digraphs::node> std::unique_ptr<diagnostics::digraphs::node>
analyzer_state_graph::create_state_node_for_conc_bindings (const concrete_bindings_t &conc_bindings) analyzer_state_graph::create_state_node_for_conc_bindings (const concrete_bindings_t &conc_bindings)
{ {
auto node = make_state_node (diagnostics::state_graphs::node_kind::other, auto node = make_state_node (node_properties::kind_t::other,
make_node_id ("concrete-bindings")); make_node_id ("concrete-bindings"));
for (auto iter : conc_bindings) for (auto iter : conc_bindings)
{ {
const bit_range bits = iter.first; const bit_range bits = iter.first;
const svalue *sval = iter.second; const svalue *sval = iter.second;
auto binding_state_node auto binding_state_node
= make_state_node (diagnostics::state_graphs::node_kind::other, = make_state_node (node_properties::kind_t::other,
make_node_id ("binding")); make_node_id ("binding"));
set_bits_attr (*binding_state_node, bits); set_bits_attr (*binding_state_node, bits);
{ gcc_assert (sval);
pretty_printer pp; set_value_attrs (*binding_state_node, *sval);
pp_format_decoder (&pp) = default_tree_printer;
sval->dump_to_pp (&pp, true);
binding_state_node->set_attr (STATE_NODE_PREFIX, "value",
pp_formatted_text (&pp));
}
node->add_child (std::move (binding_state_node)); node->add_child (std::move (binding_state_node));
} }
return node; return node;
@ -496,27 +509,28 @@ analyzer_state_graph::get_bit_range_within_base_region (const region &reg,
void void
analyzer_state_graph:: analyzer_state_graph::
populate_state_node_for_typed_region (state_node_ref node, populate_state_node_for_typed_region (diagnostics::digraphs::node &state_node,
const region &reg, const region &reg,
const concrete_bindings_t &conc_bindings, const concrete_bindings_t &conc_bindings,
bool create_all) bool create_all)
{ {
const_tree reg_type = reg.get_type (); const_tree reg_type = reg.get_type ();
gcc_assert (reg_type); gcc_assert (reg_type);
set_type_attr (node, reg_type); set_type_attr (state_node, reg_type);
bit_range bits (0, 0); bit_range bits (0, 0);
if (get_bit_range_within_base_region (reg, bits)) if (get_bit_range_within_base_region (reg, bits))
{ {
set_bits_attr (node, bits); set_bits_attr (state_node, bits);
auto search = conc_bindings.find (bits); auto search = conc_bindings.find (bits);
if (search != conc_bindings.end ()) if (search != conc_bindings.end ())
{ {
const svalue *bound_sval = search->second; const svalue *bound_sval = search->second;
node.set_json_attr ("value", bound_sval->to_json ()); gcc_assert (bound_sval);
set_value_attrs (state_node, *bound_sval);
if (const region *dst_reg = bound_sval->maybe_get_region ()) if (const region *dst_reg = bound_sval->maybe_get_region ())
m_pending_edges.push_back ({node, *dst_reg}); m_pending_edges.push_back ({state_node, *dst_reg});
} }
} }
@ -555,9 +569,10 @@ populate_state_node_for_typed_region (state_node_ref node,
{ {
auto child_state_node auto child_state_node
= make_state_node = make_state_node
(diagnostics::state_graphs::node_kind::element, (node_properties::kind_t::element,
make_node_id (*child_reg)); make_node_id (*child_reg));
set_wi_attr (*child_state_node, "index", idx, UNSIGNED); set_wi_attr (*child_state_node,
node_properties::index, idx, UNSIGNED);
// Recurse: // Recurse:
gcc_assert (element_type); gcc_assert (element_type);
@ -565,7 +580,7 @@ populate_state_node_for_typed_region (state_node_ref node,
*child_reg, *child_reg,
conc_bindings, conc_bindings,
create_all); create_all);
node.m_node.add_child (std::move (child_state_node)); state_node.add_child (std::move (child_state_node));
} }
} }
} }
@ -587,11 +602,12 @@ populate_state_node_for_typed_region (state_node_ref node,
{ {
auto child_state_node auto child_state_node
= make_state_node = make_state_node
(diagnostics::state_graphs::node_kind::padding, (node_properties::kind_t::padding,
make_node_id (*child_reg)); make_node_id (*child_reg));
set_wi_attr (*child_state_node, "num_bits", set_wi_attr (*child_state_node,
node_properties::num_bits,
item.m_bit_range.m_size_in_bits, SIGNED); item.m_bit_range.m_size_in_bits, SIGNED);
node.m_node.add_child (std::move (child_state_node)); state_node.add_child (std::move (child_state_node));
} }
} }
else else
@ -600,27 +616,27 @@ populate_state_node_for_typed_region (state_node_ref node,
= m_mgr.get_field_region (&reg, = m_mgr.get_field_region (&reg,
const_cast<tree> (item.m_field)); const_cast<tree> (item.m_field));
if (show_child_state_node_for_child_region_p (*child_reg, if (show_child_state_node_for_child_region_p (*child_reg,
conc_bindings, conc_bindings,
create_all)) create_all))
{ {
auto child_state_node auto child_state_node
= make_state_node = make_state_node
(diagnostics::state_graphs::node_kind::field, (node_properties::kind_t::field,
make_node_id (*child_reg)); make_node_id (*child_reg));
{ {
pretty_printer pp; pretty_printer pp;
pp_format_decoder (&pp) = default_tree_printer; pp_format_decoder (&pp) = default_tree_printer;
pp_printf (&pp, "%D", item.m_field); pp_printf (&pp, "%D", item.m_field);
child_state_node->set_attr (STATE_NODE_PREFIX, "name", child_state_node->set_property (node_properties::name,
pp_formatted_text (&pp)); pp_formatted_text (&pp));
} }
// Recurse: // Recurse:
populate_state_node_for_typed_region (*child_state_node, populate_state_node_for_typed_region (*child_state_node,
*child_reg, *child_reg,
conc_bindings, conc_bindings,
create_all); create_all);
node.m_node.add_child (std::move (child_state_node)); state_node.add_child (std::move (child_state_node));
} }
} }
} }
@ -630,8 +646,9 @@ populate_state_node_for_typed_region (state_node_ref node,
} }
void void
analyzer_state_graph::set_attr_for_dynamic_extents (const region &reg, analyzer_state_graph::
state_node_ref node_ref) set_attr_for_dynamic_extents (const region &reg,
diagnostics::digraphs::node &state_node)
{ {
const svalue *sval = m_state.m_region_model->get_dynamic_extents (&reg); const svalue *sval = m_state.m_region_model->get_dynamic_extents (&reg);
if (sval) if (sval)
@ -642,15 +659,16 @@ analyzer_state_graph::set_attr_for_dynamic_extents (const region &reg,
pp_wide_int (&pp, wi::to_wide (cst), UNSIGNED); pp_wide_int (&pp, wi::to_wide (cst), UNSIGNED);
else else
sval->dump_to_pp (&pp, true); sval->dump_to_pp (&pp, true);
node_ref.set_attr ("dynamic-extents", pp_formatted_text (&pp)); state_node.set_property (state_node_properties::dynamic_extents,
pp_formatted_text (&pp));
} }
} }
bool bool
analyzer_state_graph:: analyzer_state_graph::
show_child_state_node_for_child_region_p (const region &reg, show_child_state_node_for_child_region_p (const region &reg,
const concrete_bindings_t &conc_bindings, const concrete_bindings_t &conc_bindings,
bool create_all) bool create_all)
{ {
if (create_all) if (create_all)
return true; return true;

View File

@ -23,34 +23,38 @@ along with GCC; see the file COPYING3. If not see
#include "diagnostics/state-graphs.h" #include "diagnostics/state-graphs.h"
#include "tree-logical-location.h" #include "tree-logical-location.h"
#include "custom-sarif-properties/state-graphs.h"
namespace ana { namespace ana {
namespace state_node_properties = custom_sarif_properties::state_graphs::node;
class analyzer_state_graph : public diagnostics::digraphs::digraph class analyzer_state_graph : public diagnostics::digraphs::digraph
{ {
public: public:
analyzer_state_graph (const program_state &state, analyzer_state_graph (const program_state &state,
const extrinsic_state &ext_state); const extrinsic_state &ext_state);
diagnostics::state_graphs::state_node_ref diagnostics::digraphs::node &
get_or_create_state_node (const region &reg); get_or_create_state_node (const region &reg);
private: private:
struct pending_edge struct pending_edge
{ {
diagnostics::state_graphs::state_node_ref m_src_node; diagnostics::digraphs::node & m_src_node;
const region &m_dst_reg; const region &m_dst_reg;
}; };
diagnostics::state_graphs::state_node_ref diagnostics::digraphs::node &
create_and_add_state_node (const region &reg); create_and_add_state_node (const region &reg);
std::unique_ptr<diagnostics::digraphs::node> std::unique_ptr<diagnostics::digraphs::node>
make_state_node (diagnostics::state_graphs::node_kind kind, make_state_node (enum state_node_properties::kind_t kind,
std::string id); std::string id);
std::unique_ptr<diagnostics::digraphs::node> std::unique_ptr<diagnostics::digraphs::node>
make_memspace_state_node (const region &reg, make_memspace_state_node (const region &reg,
enum diagnostics::state_graphs::node_kind kind); enum state_node_properties::kind_t kind);
std::unique_ptr<diagnostics::digraphs::node> std::unique_ptr<diagnostics::digraphs::node>
create_state_node (const region &reg); create_state_node (const region &reg);
@ -71,14 +75,14 @@ private:
bit_range &out); bit_range &out);
void void
populate_state_node_for_typed_region (diagnostics::state_graphs::state_node_ref, populate_state_node_for_typed_region (diagnostics::digraphs::node &,
const region &reg, const region &reg,
const concrete_bindings_t &conc_bindings, const concrete_bindings_t &conc_bindings,
bool create_all); bool create_all);
void void
set_attr_for_dynamic_extents (const region &reg, set_attr_for_dynamic_extents (const region &reg,
diagnostics::state_graphs::state_node_ref); diagnostics::digraphs::node &);
bool bool
show_child_state_node_for_child_region_p (const region &reg, show_child_state_node_for_child_region_p (const region &reg,
@ -95,7 +99,8 @@ private:
const program_state &m_state; const program_state &m_state;
const extrinsic_state &m_ext_state; const extrinsic_state &m_ext_state;
region_model_manager &m_mgr; region_model_manager &m_mgr;
std::map<const region *, diagnostics::digraphs::node *> m_region_to_state_node_map; std::map<const region *,
diagnostics::digraphs::node *> m_region_to_state_node_map;
std::map<const region *, tree> m_types_for_untyped_regions; std::map<const region *, tree> m_types_for_untyped_regions;
unsigned m_next_id; unsigned m_next_id;
std::vector<pending_edge> m_pending_edges; std::vector<pending_edge> m_pending_edges;

View File

@ -29,6 +29,7 @@ along with GCC; see the file COPYING3. If not see
#include "tree-logical-location.h" #include "tree-logical-location.h"
#include "diagnostics/sarif-sink.h" #include "diagnostics/sarif-sink.h"
#include "diagnostics/state-graphs.h" #include "diagnostics/state-graphs.h"
#include "custom-sarif-properties/state-graphs.h"
#include "analyzer/analyzer-logging.h" #include "analyzer/analyzer-logging.h"
#include "analyzer/sm.h" #include "analyzer/sm.h"
@ -242,9 +243,11 @@ checker_event::maybe_make_diagnostic_state_graph (bool debug) const
pretty_printer pp; pretty_printer pp;
text_art::theme *theme = global_dc->get_diagram_theme (); text_art::theme *theme = global_dc->get_diagram_theme ();
text_art::dump_to_pp (*state, theme, &pp); text_art::dump_to_pp (*state, theme, &pp);
result->set_attr (STATE_GRAPH_PREFIX, const json::string_property program_state_property
"analyzer/program_state/", (custom_sarif_properties::state_graphs::graph::prefix,
pp_formatted_text (&pp)); "analyzer/program_state/");
result->set_property (program_state_property,
pp_formatted_text (&pp));
} }
return result; return result;

View File

@ -2735,7 +2735,7 @@ malloc_state_machine::transition_ptr_sval_non_null (region_model *model,
smap->set_state (model, new_ptr_sval, m_free.m_nonnull, nullptr, ext_state); smap->set_state (model, new_ptr_sval, m_free.m_nonnull, nullptr, ext_state);
} }
static enum diagnostics::state_graphs::node_dynalloc_state static enum custom_sarif_properties::state_graphs::node::dynalloc_state_t
get_dynalloc_state_for_state (enum resource_state rs) get_dynalloc_state_for_state (enum resource_state rs)
{ {
switch (rs) switch (rs)
@ -2746,17 +2746,17 @@ get_dynalloc_state_for_state (enum resource_state rs)
case RS_NULL: case RS_NULL:
case RS_NON_HEAP: case RS_NON_HEAP:
case RS_STOP: case RS_STOP:
return diagnostics::state_graphs::node_dynalloc_state::unknown; return state_node_properties::dynalloc_state_t::unknown;
case RS_ASSUMED_NON_NULL: case RS_ASSUMED_NON_NULL:
return diagnostics::state_graphs::node_dynalloc_state::nonnull; return state_node_properties::dynalloc_state_t::nonnull;
case RS_UNCHECKED: case RS_UNCHECKED:
return diagnostics::state_graphs::node_dynalloc_state::unchecked; return state_node_properties::dynalloc_state_t::unchecked;
case RS_NONNULL: case RS_NONNULL:
return diagnostics::state_graphs::node_dynalloc_state::nonnull; return state_node_properties::dynalloc_state_t::nonnull;
case RS_FREED: case RS_FREED:
return diagnostics::state_graphs::node_dynalloc_state::freed; return state_node_properties::dynalloc_state_t::freed;
} }
} }
@ -2768,24 +2768,23 @@ add_state_to_state_graph (analyzer_state_graph &out_state_graph,
{ {
if (const region *reg = sval.maybe_get_region ()) if (const region *reg = sval.maybe_get_region ())
{ {
auto reg_node = out_state_graph.get_or_create_state_node (*reg); auto &reg_node = out_state_graph.get_or_create_state_node (*reg);
auto alloc_state = as_a_allocation_state (state); auto alloc_state = as_a_allocation_state (state);
gcc_assert (alloc_state); gcc_assert (alloc_state);
reg_node.set_dynalloc_state reg_node.set_property (state_node_properties::dynalloc_state_prop,
(get_dynalloc_state_for_state (alloc_state->m_rs)); get_dynalloc_state_for_state (alloc_state->m_rs));
if (alloc_state->m_deallocators) if (alloc_state->m_deallocators)
{ {
pretty_printer pp; pretty_printer pp;
alloc_state->m_deallocators->dump_to_pp (&pp); alloc_state->m_deallocators->dump_to_pp (&pp);
reg_node.m_node.set_attr (STATE_NODE_PREFIX, reg_node.set_property (state_node_properties::expected_deallocators,
"expected-deallocators", pp_formatted_text (&pp));
pp_formatted_text (&pp));
} }
if (alloc_state->m_deallocator) if (alloc_state->m_deallocator)
reg_node.m_node.set_attr (STATE_NODE_PREFIX, reg_node.set_property (state_node_properties::deallocator,
"deallocator", alloc_state->m_deallocator->m_name);
alloc_state->m_deallocator->m_name);
} }
} }

2
gcc/configure vendored
View File

@ -36891,7 +36891,7 @@ $as_echo "$as_me: executing $ac_file commands" >&6;}
"depdir":C) $SHELL $ac_aux_dir/mkinstalldirs $DEPDIR ;; "depdir":C) $SHELL $ac_aux_dir/mkinstalldirs $DEPDIR ;;
"gccdepdir":C) "gccdepdir":C)
${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs build/$DEPDIR ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs build/$DEPDIR
for lang in $subdirs c-family common analyzer diagnostics text-art rtl-ssa sym-exec for lang in $subdirs c-family common analyzer custom-sarif-properties diagnostics text-art rtl-ssa sym-exec
do do
${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs $lang/$DEPDIR ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs $lang/$DEPDIR
done ;; done ;;

View File

@ -1368,7 +1368,7 @@ AC_CHECK_HEADERS(ext/hash_map)
ZW_CREATE_DEPDIR ZW_CREATE_DEPDIR
AC_CONFIG_COMMANDS([gccdepdir],[ AC_CONFIG_COMMANDS([gccdepdir],[
${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs build/$DEPDIR ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs build/$DEPDIR
for lang in $subdirs c-family common analyzer diagnostics text-art rtl-ssa sym-exec for lang in $subdirs c-family common analyzer custom-sarif-properties diagnostics text-art rtl-ssa sym-exec
do do
${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs $lang/$DEPDIR ${CONFIG_SHELL-/bin/sh} $ac_aux_dir/mkinstalldirs $lang/$DEPDIR
done], [subdirs="$subdirs" ac_aux_dir=$ac_aux_dir DEPDIR=$DEPDIR]) done], [subdirs="$subdirs" ac_aux_dir=$ac_aux_dir DEPDIR=$DEPDIR])

View File

@ -0,0 +1,28 @@
/* Extra properties for digraphs in SARIF property bags.
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/>. */
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "json.h"
#include "custom-sarif-properties/digraphs.h"
const json::string_property custom_sarif_properties::digraphs::digraph::kind
("gcc/digraphs/graph/kind");

View File

@ -0,0 +1,37 @@
/* Extra properties for digraphs in SARIF property bags.
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_CUSTOM_SARIF_PROPERTIES_DIGRAPHS_H
#define GCC_CUSTOM_SARIF_PROPERTIES_DIGRAPHS_H
/* SARIF property names relating to digraphs. */
namespace custom_sarif_properties {
namespace digraphs {
namespace digraph {
/* A hint about the kind of graph we have,
and thus what kinds of nodes and edges to expect. */
extern const json::string_property kind;
// string; values: "cfg"
}
}
}
#endif /* ! GCC_CUSTOM_SARIF_PROPERTIES_DIGRAPHS_H */

View File

@ -0,0 +1,161 @@
/* Properties for capturing state graphs in SARIF property bags.
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/>. */
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "json.h"
#include "custom-sarif-properties/state-graphs.h"
/* graph. */
namespace graph = custom_sarif_properties::state_graphs::graph;
#define STATE_GRAPH_PREFIX "gcc/diagnostic_state_graph/"
const char *const graph::prefix = STATE_GRAPH_PREFIX;
#undef STATE_GRAPH_PREFIX
/* node. */
namespace node = custom_sarif_properties::state_graphs::node;
#define STATE_NODE_PREFIX "gcc/diagnostic_state_node/"
const json::enum_property<enum node::kind_t>
node::kind_prop (STATE_NODE_PREFIX "kind");
const json::string_property node::function (STATE_NODE_PREFIX "function");
const json::string_property node::dynamic_extents
(STATE_NODE_PREFIX "dynamic-extents");
const json::string_property node::name (STATE_NODE_PREFIX "name");
const json::string_property node::type (STATE_NODE_PREFIX "type");
const json::json_property node::value (STATE_NODE_PREFIX "value");
const json::string_property node::value_str (STATE_NODE_PREFIX "value_str");
const json::string_property node::index (STATE_NODE_PREFIX "index");
const json::string_property node::bits (STATE_NODE_PREFIX "bits");
const json::string_property node::num_bits (STATE_NODE_PREFIX "num_bits");
const json::string_property node::deallocator (STATE_NODE_PREFIX "deallocator");
const json::string_property node::expected_deallocators
(STATE_NODE_PREFIX "expected-deallocators");
const json::enum_property<enum node::dynalloc_state_t>
node::dynalloc_state_prop (STATE_NODE_PREFIX "dynalloc-state");
#undef STATE_NODE_PREFIX
/* edge. */
namespace edge_props = custom_sarif_properties::state_graphs::edge;
#define STATE_EDGE_PREFIX "gcc/diagnostic_state_edge/"
extern const char *const edge_props::prefix = STATE_EDGE_PREFIX;
#undef STATE_EDGE_PREFIX
// Traits for enum node:kind_t
namespace json {
template<>
enum node::kind_t
json::enum_traits<enum node::kind_t>::get_unknown_value ()
{
return node::kind_t::other;
}
static const char * const node_kind_strs[] = {
"globals",
"code",
"function",
"stack",
"stack-frame",
"heap",
"thread-local",
"dynalloc-buffer",
"variable",
"field",
"padding",
"element",
"other",
};
template<>
bool
json::enum_traits<enum node::kind_t>::
maybe_get_value_from_string (const char *str,
enum_t &out)
{
for (size_t i = 0; i < ARRAY_SIZE (node_kind_strs); ++i)
if (!strcmp (node_kind_strs[i], str))
{
out = static_cast<enum_t> (i);
return true;
}
return false;
}
template<>
const char *
json::enum_traits<enum node::kind_t>::get_string_for_value (enum_t value)
{
return node_kind_strs[static_cast<int> (value)];
}
// Traits for enum node:dynalloc_state_t
template<>
enum node::dynalloc_state_t
json::enum_traits<enum node::dynalloc_state_t>::get_unknown_value ()
{
return node::dynalloc_state_t::unknown;
}
static const char * const dynalloc_state_strs[] = {
"unknown",
"nonnull",
"unchecked",
"freed"
};
template<>
bool
json::enum_traits<enum node::dynalloc_state_t>::
maybe_get_value_from_string (const char *str,
enum_t &out)
{
for (size_t i = 0; i < ARRAY_SIZE (dynalloc_state_strs); ++i)
if (!strcmp (dynalloc_state_strs[i], str))
{
out = static_cast<enum_t> (i);
return true;
}
return false;
}
template<>
const char *
json::enum_traits<enum node::dynalloc_state_t>::
get_string_for_value (enum_t value)
{
return dynalloc_state_strs[static_cast <size_t> (value)];
}
} // namespace json

View File

@ -0,0 +1,98 @@
/* Properties for capturing state graphs in SARIF property bags.
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/>. */
#include "json.h"
#ifndef GCC_DIAGNOSTICS_SARIF_PROPERTIES_STATE_GRAPHS_H
#define GCC_DIAGNOSTICS_SARIF_PROPERTIES_STATE_GRAPHS_H
/* SARIF property names relating to GCC's CFGs. */
namespace custom_sarif_properties {
namespace state_graphs {
namespace graph {
extern const char *const prefix;
}
namespace node {
enum class kind_t
{
// 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
};
enum class dynalloc_state_t
{
unknown,
nonnull,
unchecked,
freed
};
extern const json::enum_property<enum kind_t> kind_prop;
extern const json::string_property function;
extern const json::string_property dynamic_extents;
extern const json::string_property name;
extern const json::string_property type;
/* The value of a memory region, expressed as a json::value. */
extern const json::json_property value;
/* The value of a memory region, expressed as a string. */
extern const json::string_property value_str;
/* For element nodes, the index within the array. */
extern const json::string_property index;
/* The range of bits or bytes within the base region. */
extern const json::string_property bits;
/* The size of a padding region. */
extern const json::string_property num_bits;
extern const json::string_property deallocator;
extern const json::string_property expected_deallocators;
extern const json::enum_property<enum dynalloc_state_t>
dynalloc_state_prop;
}
namespace edge {
extern const char *const prefix;
}
}
}
#endif /* ! GCC_DIAGNOSTICS_SARIF_PROPERTIES_STATE_GRAPHS_H */

View File

@ -46,7 +46,6 @@ run_diagnostics_selftests ()
sarif_sink_cc_tests (); sarif_sink_cc_tests ();
digraphs_cc_tests (); digraphs_cc_tests ();
output_spec_cc_tests (); output_spec_cc_tests ();
state_graphs_cc_tests ();
lazy_paths_cc_tests (); lazy_paths_cc_tests ();
paths_output_cc_tests (); paths_output_cc_tests ();
changes_cc_tests (); changes_cc_tests ();

View File

@ -44,7 +44,6 @@ extern void paths_output_cc_tests ();
extern void sarif_sink_cc_tests (); extern void sarif_sink_cc_tests ();
extern void selftest_logical_locations_cc_tests (); extern void selftest_logical_locations_cc_tests ();
extern void source_printing_cc_tests (); extern void source_printing_cc_tests ();
extern void state_graphs_cc_tests ();
} /* end of namespace diagnostics::selftest. */ } /* end of namespace diagnostics::selftest. */

View File

@ -30,13 +30,15 @@ along with GCC; see the file COPYING3. If not see
#include "graphviz.h" #include "graphviz.h"
#include "diagnostics/digraphs.h" #include "diagnostics/digraphs.h"
#include "diagnostics/sarif-sink.h" #include "diagnostics/sarif-sink.h"
#include "custom-sarif-properties/digraphs.h"
#include "selftest.h" using digraph_object = diagnostics::digraphs::object;
using digraph = diagnostics::digraphs::digraph; using digraph = diagnostics::digraphs::digraph;
using digraph_node = diagnostics::digraphs::node; using digraph_node = diagnostics::digraphs::node;
using digraph_edge = diagnostics::digraphs::edge; using digraph_edge = diagnostics::digraphs::edge;
namespace properties = custom_sarif_properties::digraphs;
namespace { namespace {
class conversion_to_dot class conversion_to_dot
@ -171,66 +173,145 @@ conversion_to_dot::has_edges_p (const digraph_node &input_node)
// class object // class object
/* String properties. */
const char * const char *
diagnostics::digraphs::object:: digraph_object::get_property (const json::string_property &property) const
get_attr (const char *key_prefix, const char *key) const
{ {
if (!m_property_bag) if (!m_property_bag)
return nullptr; return nullptr;
std::string prefixed_key = std::string (key_prefix) + key; if (json::value *jv = m_property_bag->get (property.m_key.get ()))
if (json::value *jv = m_property_bag->get (prefixed_key.c_str ()))
if (json::string *jstr = jv->dyn_cast_string ()) if (json::string *jstr = jv->dyn_cast_string ())
return jstr->get_string (); return jstr->get_string ();
return nullptr; return nullptr;
} }
void void
diagnostics::digraphs::object:: digraph_object::set_property (const json::string_property &property,
set_attr (const char *key_prefix, const char *key, const char *value) const char *utf8_value)
{ {
set_json_attr (key_prefix, key, std::make_unique<json::string> (value)); auto &bag = ensure_property_bag ();
bag.set_string (property.m_key.get (), utf8_value);
}
/* Integer properties. */
bool
digraph_object::maybe_get_property (const json::integer_property &property,
long &out_value) const
{
if (!m_property_bag)
return false;
if (json::value *jv = m_property_bag->get (property.m_key.get ()))
if (json::integer_number *jnum = jv->dyn_cast_integer_number ())
{
out_value = jnum->get ();
return true;
}
return false;
} }
void void
diagnostics::digraphs::object:: digraph_object::set_property (const json::integer_property &property, long value)
set_json_attr (const char *key_prefix, const char *key, std::unique_ptr<json::value> value) {
auto &bag = ensure_property_bag ();
bag.set_integer (property.m_key.get (), value);
}
/* Bool properties. */
void
digraph_object::set_property (const json::bool_property &property, bool value)
{
auto &bag = ensure_property_bag ();
bag.set_bool (property.m_key.get (), value);
}
tristate
digraph_object::
get_property_as_tristate (const json::bool_property &property) const
{
if (m_property_bag)
{
if (json::value *jv = m_property_bag->get (property.m_key.get ()))
switch (jv->get_kind ())
{
default:
break;
case json::JSON_TRUE:
return tristate (true);
case json::JSON_FALSE:
return tristate (false);
}
}
return tristate::unknown ();
}
/* Array-of-string properties. */
json::array *
digraph_object::get_property (const json::array_of_string_property &property) const
{
if (m_property_bag)
if (json::value *jv = m_property_bag->get (property.m_key.get ()))
if (json::array *arr = jv->dyn_cast_array ())
return arr;
return nullptr;
}
/* json::value properties. */
const json::value *
digraph_object::get_property (const json::json_property &property) const
{
if (m_property_bag)
return m_property_bag->get (property.m_key.get ());
return nullptr;
}
void
digraph_object::set_property (const json::json_property &property,
std::unique_ptr<json::value> value)
{
auto &bag = ensure_property_bag ();
bag.set (property.m_key.get (), std::move (value));
}
json::object &
digraph_object::ensure_property_bag ()
{ {
std::string prefixed_key = std::string (key_prefix) + key;
if (!m_property_bag) if (!m_property_bag)
m_property_bag = std::make_unique<json::object> (); m_property_bag = std::make_unique<sarif_property_bag> ( );
m_property_bag->set (prefixed_key.c_str (), std::move (value)); return *m_property_bag;
} }
// class digraph // class digraph
DEBUG_FUNCTION void DEBUG_FUNCTION void
diagnostics::digraphs::digraph::dump () const digraph::dump () const
{ {
make_json_sarif_graph ()->dump (); make_json_sarif_graph ()->dump ();
} }
std::unique_ptr<json::object> std::unique_ptr<json::object>
diagnostics::digraphs::digraph::make_json_sarif_graph () const digraph::make_json_sarif_graph () const
{ {
return make_sarif_graph (*this, nullptr, nullptr); return make_sarif_graph (*this, nullptr, nullptr);
} }
std::unique_ptr<dot::graph> std::unique_ptr<dot::graph>
diagnostics::digraphs::digraph::make_dot_graph () const digraph::make_dot_graph () const
{ {
conversion_to_dot to_dot; conversion_to_dot converter;
return to_dot.make_dot_graph_from_diagnostic_graph (*this); return converter.make_dot_graph_from_diagnostic_graph (*this);
} }
std::unique_ptr<diagnostics::digraphs::digraph> std::unique_ptr<digraph>
diagnostics::digraphs::digraph::clone () const digraph::clone () const
{ {
auto result = std::make_unique<diagnostics::digraphs::digraph> (); auto result = std::make_unique<diagnostics::digraphs::digraph> ();
if (get_property_bag ()) if (get_property_bag ())
result->set_property_bag (get_property_bag ()->clone_as_object ()); result->set_property_bag (get_property_bag ()->clone_as_object ());
std::map<diagnostics::digraphs::node *, diagnostics::digraphs::node *> node_mapping; std::map<digraph_node *, digraph_node *> node_mapping;
for (auto &iter : m_nodes) for (auto &iter : m_nodes)
result->add_node (iter->clone (*result, node_mapping)); result->add_node (iter->clone (*result, node_mapping));
@ -241,10 +322,10 @@ diagnostics::digraphs::digraph::clone () const
} }
void void
diagnostics::digraphs::digraph::add_edge (const char *id, digraph::add_edge (const char *id,
node &src_node, node &src_node,
node &dst_node, node &dst_node,
const char *label) const char *label)
{ {
auto e = std::make_unique<digraph_edge> (*this, auto e = std::make_unique<digraph_edge> (*this,
id, id,
@ -263,7 +344,7 @@ diagnostics::digraphs::digraph::add_edge (const char *id,
to edges by id (SARIF 2.1.0's §3.43.2 edgeId property). */ to edges by id (SARIF 2.1.0's §3.43.2 edgeId property). */
std::string std::string
diagnostics::digraphs::digraph::make_edge_id (const char *edge_id) digraph::make_edge_id (const char *edge_id)
{ {
/* If we have an id, use it. */ /* If we have an id, use it. */
if (edge_id) if (edge_id)
@ -284,27 +365,38 @@ diagnostics::digraphs::digraph::make_edge_id (const char *edge_id)
} }
} }
const char *
digraph::get_graph_kind () const
{
return get_property (properties::digraph::kind);
}
void
digraph::set_graph_kind (const char *kind)
{
set_property (properties::digraph::kind, kind);
}
// class node // class node
DEBUG_FUNCTION void DEBUG_FUNCTION void
diagnostics::digraphs::node::dump () const digraph_node::dump () const
{ {
to_json_sarif_node ()->dump (); to_json_sarif_node ()->dump ();
} }
std::unique_ptr<json::object> std::unique_ptr<json::object>
diagnostics::digraphs::node::to_json_sarif_node () const digraph_node::to_json_sarif_node () const
{ {
return make_sarif_node (*this, nullptr, nullptr); return make_sarif_node (*this, nullptr, nullptr);
} }
std::unique_ptr<diagnostics::digraphs::node> std::unique_ptr<digraph_node>
diagnostics::digraphs::node::clone (digraph &new_graph, digraph_node::clone (digraph &new_graph,
std::map<node *, node *> &node_mapping) const std::map<node *, node *> &node_mapping) const
{ {
auto result auto result
= std::make_unique<diagnostics::digraphs::node> (new_graph, = std::make_unique<digraph_node> (new_graph, get_id ());
get_id ());
node_mapping.insert ({const_cast <node *> (this), result.get ()}); node_mapping.insert ({const_cast <node *> (this), result.get ()});
result->set_logical_loc (m_logical_loc); result->set_logical_loc (m_logical_loc);
@ -353,6 +445,9 @@ diagnostics::digraphs::edge::to_json_sarif_edge () const
#if CHECKING_P #if CHECKING_P
#include "selftest.h"
#include "custom-sarif-properties/state-graphs.h"
namespace diagnostics { namespace diagnostics {
namespace selftest { namespace selftest {
@ -391,16 +486,17 @@ test_simple_graph ()
#define KEY_PREFIX "/placeholder/" #define KEY_PREFIX "/placeholder/"
auto g = std::make_unique<digraph> (); auto g = std::make_unique<digraph> ();
g->set_description ("test graph"); g->set_description ("test graph");
g->set_attr (KEY_PREFIX, "date", "1066"); g->set_property (json::string_property (KEY_PREFIX, "date"), "1066");
auto a = std::make_unique<digraph_node> (*g, "a"); auto a = std::make_unique<digraph_node> (*g, "a");
auto b = std::make_unique<digraph_node> (*g, "b"); auto b = std::make_unique<digraph_node> (*g, "b");
b->set_attr (KEY_PREFIX, "color", "red"); b->set_property (json::string_property (KEY_PREFIX, "color"), "red");
auto c = std::make_unique<digraph_node> (*g, "c"); auto c = std::make_unique<digraph_node> (*g, "c");
c->set_label ("I am a node label"); c->set_label ("I am a node label");
auto e = std::make_unique<digraph_edge> (*g, nullptr, *a, *c); auto e = std::make_unique<digraph_edge> (*g, nullptr, *a, *c);
e->set_attr (KEY_PREFIX, "status", "copacetic"); e->set_property (json::string_property (KEY_PREFIX, "status"),
"copacetic");
e->set_label ("I am an edge label"); e->set_label ("I am an edge label");
g->add_edge (std::move (e)); g->add_edge (std::move (e));
@ -449,6 +545,34 @@ test_simple_graph ()
} }
} }
static void
test_property_objects ()
{
namespace state_node_properties = custom_sarif_properties::state_graphs::node;
digraph g;
digraph_node node (g, "a");
ASSERT_EQ (node.get_property (state_node_properties::kind_prop),
state_node_properties::kind_t::other);
node.set_property (state_node_properties::kind_prop,
state_node_properties::kind_t::stack);
ASSERT_EQ (node.get_property (state_node_properties::kind_prop),
state_node_properties::kind_t::stack);
ASSERT_EQ (node.get_property (state_node_properties::dynalloc_state_prop),
state_node_properties::dynalloc_state_t::unknown);
node.set_property (state_node_properties::dynalloc_state_prop,
state_node_properties::dynalloc_state_t::freed);
ASSERT_EQ (node.get_property (state_node_properties::dynalloc_state_prop),
state_node_properties::dynalloc_state_t::freed);
ASSERT_EQ (node.get_property (state_node_properties::type), nullptr);
node.set_property (state_node_properties::type, "const char *");
ASSERT_STREQ (node.get_property (state_node_properties::type),
"const char *");
}
/* Run all of the selftests within this file. */ /* Run all of the selftests within this file. */
void void
@ -456,6 +580,7 @@ digraphs_cc_tests ()
{ {
test_empty_graph (); test_empty_graph ();
test_simple_graph (); test_simple_graph ();
test_property_objects ();
} }
} // namespace diagnostics::selftest } // namespace diagnostics::selftest

View File

@ -22,6 +22,7 @@ along with GCC; see the file COPYING3. If not see
#define GCC_DIAGNOSTICS_DIGRAPHS_H #define GCC_DIAGNOSTICS_DIGRAPHS_H
#include "json.h" #include "json.h"
#include "tristate.h"
#include "diagnostics/logical-locations.h" #include "diagnostics/logical-locations.h"
class graphviz_out; class graphviz_out;
@ -55,23 +56,57 @@ class edge;
class object class object
{ {
public: public:
const char * /* String properties. */
get_attr (const char *key_prefix, const char *get_property (const json::string_property &property) const;
const char *key) const; void set_property (const json::string_property &property,
const char *utf8_value);
void /* Integer properties. */
set_attr (const char *key_prefix, bool maybe_get_property (const json::integer_property &property, long &out) const;
const char *key, void set_property (const json::integer_property &property, long value);
const char *value);
/* Bool properties. */
tristate
get_property_as_tristate (const json::bool_property &property) const;
void set_property (const json::bool_property &property, bool value);
/* Array-of-string properties. */
json::array *
get_property (const json::array_of_string_property &property) const;
/* enum properties. */
template <typename EnumType>
EnumType
get_property (const json::enum_property<EnumType> &property) const
{
if (m_property_bag)
{
EnumType result;
if (m_property_bag->maybe_get_enum<EnumType> (property, result))
return result;
}
return json::enum_traits<EnumType>::get_unknown_value ();
}
template <typename EnumType>
void void
set_json_attr (const char *key_prefix, set_property (const json::enum_property<EnumType> &property,
const char *key, EnumType value)
std::unique_ptr<json::value> value); {
auto &bag = ensure_property_bag ();
bag.set_enum<EnumType> (property, value);
}
/* json::value properties. */
const json::value *get_property (const json::json_property &property) const;
void set_property (const json::json_property &property,
std::unique_ptr<json::value> value);
json::object * json::object *
get_property_bag () const { return m_property_bag.get (); } get_property_bag () const { return m_property_bag.get (); }
json::object &
ensure_property_bag ();
void void
set_property_bag (std::unique_ptr<json::object> property_bag) set_property_bag (std::unique_ptr<json::object> property_bag)
{ {
@ -188,6 +223,9 @@ class digraph : public object
std::unique_ptr<digraph> clone () const; std::unique_ptr<digraph> clone () const;
const char *get_graph_kind () const;
void set_graph_kind (const char *);
private: private:
void void
add_node_id (std::string node_id, node &new_node) add_node_id (std::string node_id, node &new_node)
@ -300,7 +338,7 @@ class node : public object
clone (digraph &new_graph, clone (digraph &new_graph,
std::map<node *, node *> &node_mapping) const; std::map<node *, node *> &node_mapping) const;
private: private:
std::string m_id; std::string m_id;
std::unique_ptr<std::string> m_label; std::unique_ptr<std::string> m_label;
std::vector<std::unique_ptr<node>> m_children; std::vector<std::unique_ptr<node>> m_children;

View File

@ -57,8 +57,8 @@ html_generation_options::html_generation_options ()
: m_css (true), : m_css (true),
m_javascript (true), m_javascript (true),
m_show_state_diagrams (false), m_show_state_diagrams (false),
m_show_state_diagrams_sarif (false), m_show_graph_sarif (false),
m_show_state_diagrams_dot_src (false) m_show_graph_dot_src (false)
{ {
} }
@ -68,8 +68,8 @@ html_generation_options::dump (FILE *outfile, int indent) const
DIAGNOSTICS_DUMPING_EMIT_BOOL_FIELD (m_css); DIAGNOSTICS_DUMPING_EMIT_BOOL_FIELD (m_css);
DIAGNOSTICS_DUMPING_EMIT_BOOL_FIELD (m_javascript); DIAGNOSTICS_DUMPING_EMIT_BOOL_FIELD (m_javascript);
DIAGNOSTICS_DUMPING_EMIT_BOOL_FIELD (m_show_state_diagrams); DIAGNOSTICS_DUMPING_EMIT_BOOL_FIELD (m_show_state_diagrams);
DIAGNOSTICS_DUMPING_EMIT_BOOL_FIELD (m_show_state_diagrams_sarif); DIAGNOSTICS_DUMPING_EMIT_BOOL_FIELD (m_show_graph_sarif);
DIAGNOSTICS_DUMPING_EMIT_BOOL_FIELD (m_show_state_diagrams_dot_src); DIAGNOSTICS_DUMPING_EMIT_BOOL_FIELD (m_show_graph_dot_src);
} }
class html_builder; class html_builder;
@ -640,7 +640,7 @@ html_builder::maybe_make_state_diagram (const paths::event &event)
the debug version. */ the debug version. */
auto state_graph auto state_graph
= event.maybe_make_diagnostic_state_graph = event.maybe_make_diagnostic_state_graph
(m_html_gen_opts.m_show_state_diagrams_sarif); (m_html_gen_opts.m_show_graph_sarif);
if (!state_graph) if (!state_graph)
return nullptr; return nullptr;
@ -652,7 +652,7 @@ html_builder::maybe_make_state_diagram (const paths::event &event)
auto wrapper = std::make_unique<xml::element> ("div", false); auto wrapper = std::make_unique<xml::element> ("div", false);
xml::printer xp (*wrapper); xml::printer xp (*wrapper);
if (m_html_gen_opts.m_show_state_diagrams_sarif) if (m_html_gen_opts.m_show_graph_sarif)
{ {
// For debugging, show the SARIF src inline: // For debugging, show the SARIF src inline:
pretty_printer pp; pretty_printer pp;
@ -660,7 +660,7 @@ html_builder::maybe_make_state_diagram (const paths::event &event)
print_pre_source (xp, pp_formatted_text (&pp)); print_pre_source (xp, pp_formatted_text (&pp));
} }
if (m_html_gen_opts.m_show_state_diagrams_dot_src) if (m_html_gen_opts.m_show_graph_dot_src)
{ {
// For debugging, show the dot src inline: // For debugging, show the dot src inline:
pretty_printer pp; pretty_printer pp;
@ -1278,21 +1278,41 @@ void
html_builder::add_graph (const digraphs::digraph &dg, html_builder::add_graph (const digraphs::digraph &dg,
xml::element &parent_element) xml::element &parent_element)
{ {
auto div = std::make_unique<xml::element> ("div", false);
div->set_attr ("class", "gcc-directed-graph");
xml::printer xp (*div);
if (m_html_gen_opts.m_show_graph_sarif)
{
// For debugging, show the SARIF src inline:
pretty_printer pp;
dg.make_json_sarif_graph ()->print (&pp, true);
print_pre_source (xp, pp_formatted_text (&pp));
}
if (auto dot_graph = dg.make_dot_graph ()) if (auto dot_graph = dg.make_dot_graph ())
if (auto svg_element = dot::make_svg_from_graph (*dot_graph)) {
{ if (m_html_gen_opts.m_show_graph_dot_src)
auto div = std::make_unique<xml::element> ("div", false); {
div->set_attr ("class", "gcc-directed-graph"); // For debugging, show the dot src inline:
xml::printer xp (*div); pretty_printer pp;
if (const char *description = dg.get_description ()) dot::writer w (pp);
{ dot_graph->print (w);
xp.push_tag ("h2", true); print_pre_source (xp, pp_formatted_text (&pp));
xp.add_text (description); }
xp.pop_tag ("h2");
} if (auto svg_element = dot::make_svg_from_graph (*dot_graph))
xp.append (std::move (svg_element)); {
parent_element.add_child (std::move (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 void

View File

@ -40,11 +40,12 @@ struct html_generation_options
// If true, attempt to show state diagrams at events // If true, attempt to show state diagrams at events
bool m_show_state_diagrams; bool m_show_state_diagrams;
// If true, show the SARIF form of the state with such diagrams /* If true, show the SARIF form of the state with such diagrams,
bool m_show_state_diagrams_sarif; and of other graphs. */
bool m_show_graph_sarif;
// If true, show the .dot source used for the diagram // If true, show the .dot source used for such graphs
bool m_show_state_diagrams_dot_src; bool m_show_graph_dot_src;
}; };
extern diagnostics::output_file extern diagnostics::output_file

View File

@ -650,12 +650,12 @@ html_scheme_handler::maybe_handle_kv (const context &ctxt,
if (key == "show-state-diagrams") if (key == "show-state-diagrams")
return parse_bool_value (ctxt, key, value, return parse_bool_value (ctxt, key, value,
m_html_gen_opts.m_show_state_diagrams); m_html_gen_opts.m_show_state_diagrams);
if (key == "show-state-diagrams-dot-src") if (key == "show-graph-dot-src")
return parse_bool_value (ctxt, key, value, return parse_bool_value (ctxt, key, value,
m_html_gen_opts.m_show_state_diagrams_dot_src); m_html_gen_opts.m_show_graph_dot_src);
if (key == "show-state-diagrams-sarif") if (key == "show-graph-sarif")
return parse_bool_value (ctxt, key, value, return parse_bool_value (ctxt, key, value,
m_html_gen_opts.m_show_state_diagrams_sarif); m_html_gen_opts.m_show_graph_sarif);
return result::unrecognized; return result::unrecognized;
} }
@ -666,8 +666,8 @@ html_scheme_handler::get_keys (auto_vec<const char *> &out) const
out.safe_push ("file"); out.safe_push ("file");
out.safe_push ("javascript"); out.safe_push ("javascript");
out.safe_push ("show-state-diagrams"); out.safe_push ("show-state-diagrams");
out.safe_push ("show-state-diagrams-dot-src"); out.safe_push ("show-graph-dot-src");
out.safe_push ("show-state-diagrams-sarif"); out.safe_push ("show-graph-sarif");
} }
} // namespace output_spec } // namespace output_spec

View File

@ -27,6 +27,7 @@ along with GCC; see the file COPYING3. If not see
#include "system.h" #include "system.h"
#include "coretypes.h" #include "coretypes.h"
#include "custom-sarif-properties/state-graphs.h"
#include "diagnostics/state-graphs.h" #include "diagnostics/state-graphs.h"
#include "graphviz.h" #include "graphviz.h"
#include "xml.h" #include "xml.h"
@ -36,6 +37,8 @@ along with GCC; see the file COPYING3. If not see
using namespace diagnostics; using namespace diagnostics;
using namespace diagnostics::state_graphs; using namespace diagnostics::state_graphs;
namespace state_node_properties = custom_sarif_properties::state_graphs::node;
static int static int
get_depth (const digraphs::node &n) get_depth (const digraphs::node &n)
{ {
@ -47,28 +50,28 @@ get_depth (const digraphs::node &n)
} }
static const char * static const char *
get_color_for_dynalloc_state (enum node_dynalloc_state dynalloc_st) get_color_for_dynalloc_state (enum state_node_properties::dynalloc_state_t dynalloc_st)
{ {
switch (dynalloc_st) switch (dynalloc_st)
{ {
default: default:
gcc_unreachable (); gcc_unreachable ();
break; break;
case node_dynalloc_state::unknown: case state_node_properties::dynalloc_state_t::unknown:
case node_dynalloc_state::nonnull: case state_node_properties::dynalloc_state_t::nonnull:
return nullptr; return nullptr;
case node_dynalloc_state::unchecked: case state_node_properties::dynalloc_state_t::unchecked:
return "#ec7a08"; // pf-orange-400 return "#ec7a08"; // pf-orange-400
case node_dynalloc_state::freed: case state_node_properties::dynalloc_state_t::freed:
return "#cc0000"; // pf-red-100 return "#cc0000"; // pf-red-100
} }
} }
static void static void
set_color_for_dynalloc_state (dot::attr_list &attrs, set_color_for_dynalloc_state (dot::attr_list &attrs,
enum node_dynalloc_state state) enum state_node_properties::dynalloc_state_t state)
{ {
if (const char *color = get_color_for_dynalloc_state (state)) if (const char *color = get_color_for_dynalloc_state (state))
attrs.add (dot::id ("color"), dot::id (color)); attrs.add (dot::id ("color"), dot::id (color));
@ -106,7 +109,7 @@ public:
= std::make_unique<dot::subgraph> (dot::id ("cluster_memory_regions")); = std::make_unique<dot::subgraph> (dot::id ("cluster_memory_regions"));
for (size_t i = 0; i < input_state_graph.get_num_nodes (); ++i) for (size_t i = 0; i < input_state_graph.get_num_nodes (); ++i)
on_input_state_node (*root_cluster, on_input_state_node (*root_cluster,
state_node_ref (input_state_graph.get_node (i))); input_state_graph.get_node (i));
add_stmt (std::move (root_cluster)); add_stmt (std::move (root_cluster));
/* Now create dot edges for edges in input_stage_graph. */ /* Now create dot edges for edges in input_stage_graph. */
@ -126,7 +129,8 @@ public:
auto e = std::make_unique<dot::edge_stmt> (src_port_id->second, auto e = std::make_unique<dot::edge_stmt> (src_port_id->second,
dst_port_id->second); dst_port_id->second);
set_color_for_dynalloc_state set_color_for_dynalloc_state
(e->m_attrs, state_node_ref (dst_node).get_dynalloc_state ()); (e->m_attrs,
dst_node.get_property (state_node_properties::dynalloc_state_prop));
add_stmt (std::move (e)); add_stmt (std::move (e));
} }
@ -147,9 +151,9 @@ private:
} }
dot::id dot::id
make_id (state_node_ref state_node, bool cluster) make_id (const diagnostics::digraphs::node &state_node, bool cluster)
{ {
std::string input_node_id = state_node.m_node.get_id (); std::string input_node_id = state_node.get_id ();
if (cluster) if (cluster)
return std::string ("cluster_") + input_node_id; return std::string ("cluster_") + input_node_id;
else else
@ -157,44 +161,44 @@ private:
} }
bool bool
starts_node_p (state_node_ref state_node) starts_node_p (const diagnostics::digraphs::node &state_node)
{ {
switch (state_node.get_node_kind ()) switch (state_node.get_property (state_node_properties::kind_prop))
{ {
default: default:
return false; return false;
case node_kind::stack: case state_node_properties::kind_t::stack:
/* We want all frames in the stack in the same table, /* We want all frames in the stack in the same table,
so they are grouped. */ so they are grouped. */
case node_kind::dynalloc_buffer: case state_node_properties::kind_t::dynalloc_buffer:
case node_kind::variable: case state_node_properties::kind_t::variable:
return true; return true;
} }
} }
const char * const char *
get_label_for_node (state_node_ref state_node) get_label_for_node (const diagnostics::digraphs::node &state_node)
{ {
switch (state_node.get_node_kind ()) switch (state_node.get_property (state_node_properties::kind_prop))
{ {
default: default:
return nullptr; return nullptr;
case node_kind::globals: case state_node_properties::kind_t::globals:
return _("Globals"); return _("Globals");
case node_kind::code: case state_node_properties::kind_t::code:
return _("Code"); return _("Code");
case node_kind::stack: case state_node_properties::kind_t::stack:
return _("Stack"); return _("Stack");
case node_kind::heap_: case state_node_properties::kind_t::heap_:
return _("Heap"); return _("Heap");
} }
} }
void void
on_input_state_node (dot::subgraph &parent_subgraph, on_input_state_node (dot::subgraph &parent_subgraph,
state_node_ref state_node) const diagnostics::digraphs::node &state_node)
{ {
dot::id sg_id = make_id (state_node, true); dot::id sg_id = make_id (state_node, true);
@ -207,7 +211,7 @@ private:
xp.set_attr ("cellborder", "1"); xp.set_attr ("cellborder", "1");
xp.set_attr ("cellspacing", "0"); xp.set_attr ("cellspacing", "0");
const int max_depth = get_depth (state_node.m_node); const int max_depth = get_depth (state_node);
const int num_columns = max_depth + 2; const int num_columns = max_depth + 2;
dot::id id_of_dot_node = make_id (state_node, false); dot::id id_of_dot_node = make_id (state_node, false);
@ -233,9 +237,9 @@ private:
child_subgraph->add_attr (dot::id ("label"), dot::id (label)); child_subgraph->add_attr (dot::id ("label"), dot::id (label));
// recurse: // recurse:
for (size_t i = 0; i < state_node.m_node.get_num_children (); ++i) for (size_t i = 0; i < state_node.get_num_children (); ++i)
on_input_state_node (*child_subgraph, on_input_state_node (*child_subgraph,
state_node.m_node.get_child (i)); state_node.get_child (i));
parent_subgraph.m_stmt_list.add_stmt (std::move (child_subgraph)); parent_subgraph.m_stmt_list.add_stmt (std::move (child_subgraph));
} }
} }
@ -246,10 +250,10 @@ private:
add_title_tr (const dot::id &id_of_dot_node, add_title_tr (const dot::id &id_of_dot_node,
xml::printer &xp, xml::printer &xp,
int num_columns, int num_columns,
state_node_ref state_node, const diagnostics::digraphs::node &state_node,
std::string heading, std::string heading,
enum style styl, enum style styl,
enum node_dynalloc_state dynalloc_state) enum state_node_properties::dynalloc_state_t dynalloc_state)
{ {
xp.push_tag ("tr", true); xp.push_tag ("tr", true);
xp.push_tag ("td", false); xp.push_tag ("td", false);
@ -298,48 +302,51 @@ private:
void void
on_node_in_table (const dot::id &id_of_dot_node, on_node_in_table (const dot::id &id_of_dot_node,
xml::printer &xp, xml::printer &xp,
state_node_ref state_node, const diagnostics::digraphs::node &state_node,
int max_depth, int max_depth,
int depth, int depth,
int num_columns) int num_columns)
{ {
bool recurse = true; bool recurse = true;
auto input_node_kind = state_node.get_node_kind (); auto input_node_kind
= state_node.get_property (state_node_properties::kind_prop);
switch (input_node_kind) switch (input_node_kind)
{ {
case node_kind::padding: case state_node_properties::kind_t::padding:
case node_kind::other: case state_node_properties::kind_t::other:
return; return;
case node_kind::stack: case state_node_properties::kind_t::stack:
add_title_tr (id_of_dot_node, xp, num_columns, state_node, "Stack", add_title_tr (id_of_dot_node, xp, num_columns, state_node, "Stack",
style::h1, style::h1,
node_dynalloc_state::unknown); state_node_properties::dynalloc_state_t::unknown);
break; break;
case node_kind::stack_frame: case state_node_properties::kind_t::stack_frame:
if (auto logical_loc = state_node.get_logical_loc ()) if (auto logical_loc = state_node.get_logical_loc ())
if (const char *function if (const char *function
= m_logical_loc_mgr.get_short_name (logical_loc)) = m_logical_loc_mgr.get_short_name (logical_loc))
add_title_tr (id_of_dot_node, xp, num_columns, state_node, add_title_tr (id_of_dot_node, xp, num_columns, state_node,
std::string ("Frame: ") + function, std::string ("Frame: ") + function,
style::h2, style::h2,
node_dynalloc_state::unknown); state_node_properties::dynalloc_state_t::unknown);
break; break;
case node_kind::dynalloc_buffer: case state_node_properties::kind_t::dynalloc_buffer:
{ {
enum node_dynalloc_state dynalloc_st enum state_node_properties::dynalloc_state_t dynalloc_st
= state_node.get_dynalloc_state (); = state_node.get_property
const char *extents = state_node.get_dynamic_extents (); (state_node_properties::dynalloc_state_prop);
const char *type = state_node.get_type (); const char *extents
= state_node.get_property (state_node_properties::dynamic_extents);
const char *type = state_node.get_property (state_node_properties::type);
pretty_printer pp; pretty_printer pp;
switch (dynalloc_st) switch (dynalloc_st)
{ {
default: default:
gcc_unreachable (); gcc_unreachable ();
case node_dynalloc_state::unknown: case state_node_properties::dynalloc_state_t::unknown:
case node_dynalloc_state::nonnull: case state_node_properties::dynalloc_state_t::nonnull:
if (type) if (type)
{ {
if (extents) if (extents)
@ -356,7 +363,7 @@ private:
} }
break; break;
case node_dynalloc_state::unchecked: case state_node_properties::dynalloc_state_t::unchecked:
if (type) if (type)
{ {
if (extents) if (extents)
@ -371,7 +378,7 @@ private:
} }
break; break;
case node_dynalloc_state::freed: case state_node_properties::dynalloc_state_t::freed:
// TODO: show deallocator // TODO: show deallocator
// TODO: show deallocation event // TODO: show deallocation event
pp_printf (&pp, "Freed buffer"); pp_printf (&pp, "Freed buffer");
@ -404,9 +411,10 @@ private:
{ {
default: default:
break; break;
case node_kind::variable: case state_node_properties::kind_t::variable:
{ {
const char *name = state_node.get_name (); const char *name
= state_node.get_property (state_node_properties::name);
gcc_assert (name); gcc_assert (name);
xp.push_tag ("td", false); xp.push_tag ("td", false);
maybe_add_dst_port (id_of_dot_node, xp, state_node); maybe_add_dst_port (id_of_dot_node, xp, state_node);
@ -416,9 +424,10 @@ private:
xp.pop_tag ("td"); xp.pop_tag ("td");
} }
break; break;
case node_kind::element: case state_node_properties::kind_t::element:
{ {
const char *index = state_node.get_index (); const char *index
= state_node.get_property (state_node_properties::index);
gcc_assert (index); gcc_assert (index);
xp.push_tag ("td", false); xp.push_tag ("td", false);
maybe_add_dst_port (id_of_dot_node, xp, state_node); maybe_add_dst_port (id_of_dot_node, xp, state_node);
@ -430,9 +439,10 @@ private:
xp.pop_tag ("td"); xp.pop_tag ("td");
} }
break; break;
case node_kind::field: case state_node_properties::kind_t::field:
{ {
const char *name = state_node.get_name (); const char *name
= state_node.get_property (state_node_properties::name);
gcc_assert (name); gcc_assert (name);
xp.push_tag ("td", false); xp.push_tag ("td", false);
maybe_add_dst_port (id_of_dot_node, xp, state_node); maybe_add_dst_port (id_of_dot_node, xp, state_node);
@ -445,7 +455,8 @@ private:
break; break;
} }
if (const char *type = state_node.get_type ()) if (const char *type
= state_node.get_property (state_node_properties::type))
{ {
xp.push_tag ("td", false); xp.push_tag ("td", false);
xp.set_attr ("align", "right"); xp.set_attr ("align", "right");
@ -455,7 +466,8 @@ private:
xp.pop_tag ("td"); xp.pop_tag ("td");
} }
if (const char *value = state_node.get_value ()) if (const char *value
= state_node.get_property (state_node_properties::value_str))
{ {
xp.push_tag ("td", false); xp.push_tag ("td", false);
xp.set_attr ("align", "left"); xp.set_attr ("align", "left");
@ -466,15 +478,16 @@ private:
xp.pop_tag ("td"); xp.pop_tag ("td");
recurse = false; recurse = false;
} }
xp.pop_tag ("tr"); xp.pop_tag ("tr");
} }
break; break;
} }
if (recurse) if (recurse)
for (size_t i = 0; i < state_node.m_node.get_num_children (); ++i) for (size_t i = 0; i < state_node.get_num_children (); ++i)
on_node_in_table (id_of_dot_node, xp, on_node_in_table (id_of_dot_node, xp,
state_node.m_node.get_child (i), state_node.get_child (i),
max_depth, depth + 1, num_columns); max_depth, depth + 1, num_columns);
} }
@ -497,9 +510,9 @@ private:
void void
maybe_add_src_port (const dot::id &id_of_dot_node, maybe_add_src_port (const dot::id &id_of_dot_node,
xml::printer &xp, xml::printer &xp,
state_node_ref state_node) const diagnostics::digraphs::node &state_node)
{ {
auto iter = m_src_nodes.find (&state_node.m_node); auto iter = m_src_nodes.find (&state_node);
if (iter == m_src_nodes.end ()) if (iter == m_src_nodes.end ())
return; return;
@ -507,7 +520,7 @@ private:
dot::node_id node_id (id_of_dot_node, dot::node_id node_id (id_of_dot_node,
dot::port (src_id, dot::port (src_id,
dot::compass_pt::e)); dot::compass_pt::e));
m_src_node_to_port_id.insert ({&state_node.m_node, node_id}); m_src_node_to_port_id.insert ({&state_node, node_id});
xp.set_attr ("port", src_id.m_str); xp.set_attr ("port", src_id.m_str);
} }
@ -517,9 +530,9 @@ private:
void void
maybe_add_dst_port (const dot::id &id_of_dot_node, maybe_add_dst_port (const dot::id &id_of_dot_node,
xml::printer &xp, xml::printer &xp,
state_node_ref state_node) const diagnostics::digraphs::node &state_node)
{ {
auto iter = m_dst_nodes.find (&state_node.m_node); auto iter = m_dst_nodes.find (&state_node);
if (iter == m_dst_nodes.end ()) if (iter == m_dst_nodes.end ())
return; return;
@ -527,7 +540,7 @@ private:
dot::node_id node_id (id_of_dot_node, dot::node_id node_id (id_of_dot_node,
dot::port (dst_id/*, dot::port (dst_id/*,
dot::compass_pt::w*/)); dot::compass_pt::w*/));
m_dst_node_to_port_id.insert ({&state_node.m_node, node_id}); m_dst_node_to_port_id.insert ({&state_node, node_id});
xp.set_attr ("port", dst_id.m_str); xp.set_attr ("port", dst_id.m_str);
} }
@ -535,11 +548,11 @@ private:
const logical_locations::manager &m_logical_loc_mgr; const logical_locations::manager &m_logical_loc_mgr;
/* All nodes involved in edges (and thus will need a port). */ /* All nodes involved in edges (and thus will need a port). */
std::set<digraphs::node *> m_src_nodes; std::set<const digraphs::node *> m_src_nodes;
std::set<digraphs::node *> m_dst_nodes; std::set<const digraphs::node *> m_dst_nodes;
std::map<digraphs::node *, dot::node_id> m_src_node_to_port_id; std::map<const digraphs::node *, dot::node_id> m_src_node_to_port_id;
std::map<digraphs::node *, dot::node_id> m_dst_node_to_port_id; std::map<const digraphs::node *, dot::node_id> m_dst_node_to_port_id;
}; };
std::unique_ptr<dot::graph> std::unique_ptr<dot::graph>

View File

@ -1,156 +0,0 @@
/* 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 "diagnostics/state-graphs.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 diagnostics {
namespace selftest {
static void
test_node_attrs ()
{
digraphs::digraph g;
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
state_graphs_cc_tests ()
{
test_node_attrs ();
}
} // namespace diagnostics::selftest
} // namespace diagnostics
#endif /* CHECKING_P */

View File

@ -22,7 +22,6 @@ along with GCC; see the file COPYING3. If not see
#define GCC_DIAGNOSTICS_STATE_GRAPHS_H #define GCC_DIAGNOSTICS_STATE_GRAPHS_H
#include "diagnostics/digraphs.h" #include "diagnostics/digraphs.h"
#include "diagnostics/logical-locations.h"
/* diagnostics::digraphs provides support for directed graphs. /* diagnostics::digraphs provides support for directed graphs.
@ -34,118 +33,11 @@ along with GCC; see the file COPYING3. If not see
in these nodes to stash extra properties (e.g. what kind of memory region in these nodes to stash extra properties (e.g. what kind of memory region
a node is e.g. stack vs heap). */ a node is e.g. stack vs heap). */
class sarif_graph;
namespace dot { class graph; } namespace dot { class graph; }
namespace diagnostics { namespace diagnostics {
namespace state_graphs { 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_locations::key
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> extern std::unique_ptr<dot::graph>
make_dot_graph (const diagnostics::digraphs::digraph &state_graph, make_dot_graph (const diagnostics::digraphs::digraph &state_graph,
const logical_locations::manager &logical_loc_mgr); const logical_locations::manager &logical_loc_mgr);

View File

@ -6296,16 +6296,16 @@ These are visible by pressing ``j'' and ``k'' to single-step forward and
backward through events. Enabling this option will slow down backward through events. Enabling this option will slow down
HTML generation. HTML generation.
@item show-state-diagrams-dot-src=@r{[}yes@r{|}no@r{]} @item show-graph-dot-src=@r{[}yes@r{|}no@r{]}
This is a debugging feature and defaults to @code{no}. This is a debugging feature and defaults to @code{no}.
If @code{show-state-diagrams-dot-src=yes} If @code{show-graph-dot-src=yes}
then if @code{show-state-diagrams=yes}, then if @code{show-state-diagrams=yes},
the generated state diagrams will also show the .dot source input to the generated state diagrams will also show the .dot source input to
GraphViz used for the diagram. GraphViz used for the diagram.
@item show-state-diagrams-sarif=@r{[}yes@r{|}no@r{]} @item show-graph-sarif=@r{[}yes@r{|}no@r{]}
This is a debugging feature and defaults to @code{no}. This is a debugging feature and defaults to @code{no}.
If @code{show-state-diagrams-sarif=yes} If @code{show-graph-sarif=yes}
then if @code{show-state-diagrams=yes}, the generated state diagrams will then if @code{show-state-diagrams=yes}, the generated state diagrams will
also show a SARIF representation of the state. also show a SARIF representation of the state.

View File

@ -394,6 +394,31 @@ object::set_bool (const char *key, bool v)
set (key, new json::literal (v)); set (key, new json::literal (v));
} }
void
object::set_string (const string_property &property, const char *utf8_value)
{
set_string (property.m_key.get (), utf8_value);
}
void
object::set_integer (const integer_property &property, long value)
{
set_integer (property.m_key.get (), value);
}
void
object::set_bool (const bool_property &property, bool value)
{
set_bool (property.m_key.get (), value);
}
void
object::set_array_of_string (const array_of_string_property &property,
std::unique_ptr<json::array> value)
{
set<array> (property.m_key.get (), std::move (value));
}
/* Subroutine of json::compare for comparing a pairs of objects. */ /* Subroutine of json::compare for comparing a pairs of objects. */
int int

View File

@ -21,6 +21,8 @@ along with GCC; see the file COPYING3. If not see
#ifndef GCC_JSON_H #ifndef GCC_JSON_H
#define GCC_JSON_H #define GCC_JSON_H
#include "label-text.h"
/* Implementation of JSON, a lightweight data-interchange format. /* Implementation of JSON, a lightweight data-interchange format.
See http://www.json.org/ See http://www.json.org/
@ -116,6 +118,41 @@ struct token
} // namespace json::pointer } // namespace json::pointer
/* Typesafe way to work with properties in JSON objects. */
template <typename Traits>
struct property
{
explicit property (const char *key)
: m_key (label_text::borrow (key))
{}
explicit property (const char *key_prefix, const char *key)
: m_key (label_text::take (concat (key_prefix, key, nullptr)))
{}
label_text m_key;
};
using string_property = property<string>;
using integer_property = property<integer_number>;
using bool_property = property<literal>;
using json_property = property<value>;
using array_of_string_property = property<array>;
template <typename EnumType>
struct enum_traits
{
typedef EnumType enum_t;
static enum_t get_unknown_value ();
static bool maybe_get_value_from_string (const char *, enum_t &out);
static const char *get_string_for_value (enum_t value);
};
template <typename EnumType>
using enum_property = property<enum_traits<EnumType>>;
/* Base class of JSON value. */ /* Base class of JSON value. */
class value class value
@ -130,6 +167,8 @@ class value
void DEBUG_FUNCTION dump () const; void DEBUG_FUNCTION dump () const;
virtual object *dyn_cast_object () { return nullptr; } virtual object *dyn_cast_object () { return nullptr; }
virtual array *dyn_cast_array () { return nullptr; }
virtual integer_number *dyn_cast_integer_number () { return nullptr; }
virtual string *dyn_cast_string () { 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::value &val_a, const json::value &val_b);
@ -183,6 +222,19 @@ class object : public value
/* Set to literal true/false. */ /* Set to literal true/false. */
void set_bool (const char *key, bool v); void set_bool (const char *key, bool v);
/* Typesafe access to properties by name (such as from a schema). */
void set_string (const string_property &property, const char *utf8_value);
void set_integer (const integer_property &property, long value);
void set_bool (const bool_property &property, bool value);
void set_array_of_string (const array_of_string_property &property,
std::unique_ptr<json::array> value);
template <typename EnumType>
bool maybe_get_enum (const enum_property<EnumType> &property,
EnumType &out) const;
template <typename EnumType>
void set_enum (const enum_property<EnumType> &property,
EnumType value);
static int compare (const json::object &obj_a, const json::object &obj_b); static int compare (const json::object &obj_a, const json::object &obj_b);
size_t get_num_keys () const { return m_keys.length (); } size_t get_num_keys () const { return m_keys.length (); }
@ -210,6 +262,8 @@ class array : public value
void print (pretty_printer *pp, bool formatted) const final override; void print (pretty_printer *pp, bool formatted) const final override;
std::unique_ptr<value> clone () const final override; std::unique_ptr<value> clone () const final override;
array *dyn_cast_array () final override { return this; }
void append (value *v); void append (value *v);
void append_string (const char *utf8_value); void append_string (const char *utf8_value);
@ -269,6 +323,8 @@ class integer_number : public value
void print (pretty_printer *pp, bool formatted) const final override; void print (pretty_printer *pp, bool formatted) const final override;
std::unique_ptr<value> clone () const final override; std::unique_ptr<value> clone () const final override;
integer_number *dyn_cast_integer_number () final override { return this; }
long get () const { return m_value; } long get () const { return m_value; }
private: private:
@ -317,6 +373,32 @@ class literal : public value
enum kind m_kind; enum kind m_kind;
}; };
template <typename EnumType>
inline bool
object::maybe_get_enum (const enum_property<EnumType> &property,
EnumType &out) const
{
if (value *jv = get (property.m_key.get ()))
if (string *jstr = jv->dyn_cast_string ())
{
if (enum_traits<EnumType>::maybe_get_value_from_string
(jstr->get_string (), out))
return true;
}
return false;
}
template <typename EnumType>
inline void
object::set_enum (const enum_property<EnumType> &property,
EnumType value)
{
const char *str
= json::enum_traits<EnumType>::get_string_for_value (value);
set_string (property.m_key.get (), str);
}
} // namespace json } // namespace json
template <> template <>

View File

@ -210,9 +210,8 @@ report_diag_with_graphs (location_t loc)
g->set_description (desc); g->set_description (desc);
auto a = std::make_unique<diagnostic_node> (*g, "a"); auto a = std::make_unique<diagnostic_node> (*g, "a");
auto b = std::make_unique<diagnostic_node> (*g, "b"); auto b = std::make_unique<diagnostic_node> (*g, "b");
#define KEY_PREFIX "/placeholder-prefix/" const json::string_property color ("/placeholder-prefix/color");
b->set_attr (KEY_PREFIX, "color", "red"); b->set_property (color, "red");
#undef KEY_PREFIX
auto c = std::make_unique<diagnostic_node> (*g, "c"); auto c = std::make_unique<diagnostic_node> (*g, "c");
c->set_label ("I am a node label"); c->set_label ("I am a node label");