# Analyzer object files
ANALYZER_OBJS = \
analyzer/access-diagram.o \
+ analyzer/ana-state-to-diagnostic-state.o \
analyzer/analysis-plan.o \
analyzer/analyzer.o \
analyzer/analyzer-language.o \
godump.o \
graph.o \
graphds.o \
- graphviz.o \
graphite.o \
graphite-isl-ast-to-gimple.o \
graphite-dependences.o \
diagnostic-path.o \
diagnostic-path-output.o \
diagnostic-show-locus.o \
+ diagnostic-state-to-dot.o \
edit-context.o \
+ graphviz.o pex.o \
pretty-print.o intl.o \
json.o json-parsing.o \
xml.o \
--- /dev/null
+/* Converting ana::program_state to XML state documents.
+ 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
+#include "analyzer/common.h"
+
+#include "xml.h"
+#include "xml-printer.h"
+
+#include "analyzer/region-model.h"
+#include "analyzer/program-state.h"
+#include "analyzer/record-layout.h"
+#include "analyzer/ana-state-to-diagnostic-state.h"
+
+#if ENABLE_ANALYZER
+
+#if __GNUC__ >= 10
+#pragma GCC diagnostic ignored "-Wformat"
+#endif
+
+namespace ana {
+
+static void
+set_wi_attr (xml::element &e,
+ const char *attr_name,
+ const wide_int_ref &w,
+ signop sgn)
+{
+ pretty_printer pp;
+ pp_wide_int (&pp, w, sgn);
+ e.set_attr (attr_name, pp_formatted_text (&pp));
+}
+
+static void
+set_type_attr (xml::element &e, const_tree type)
+{
+ gcc_assert (type);
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ pp_printf (&pp, "%T", type);
+ e.set_attr ("type", pp_formatted_text (&pp));
+}
+
+static void
+set_bits_attr (xml::element &e,
+ bit_range bits)
+{
+ pretty_printer pp;
+ bits.dump_to_pp (&pp);
+ e.set_attr ("bits", pp_formatted_text (&pp));
+}
+
+static void
+set_region_id_attr (xml::element &e,
+ const region ®)
+{
+ e.set_attr ("region_id", std::to_string (reg.get_id ()));
+}
+
+// class xml_state : public xml::document
+
+xml_state::xml_state (const program_state &state,
+ const extrinsic_state &ext_state)
+: xml::document (),
+ m_state (state),
+ m_ext_state (ext_state),
+ m_mgr (*ext_state.get_engine ()->get_model_manager ()),
+ m_root (nullptr)
+{
+ auto root = std::make_unique<xml::element> ("state-diagram", false);
+ m_root = root.get ();
+ add_child (std::move (root));
+
+ /* Find pointers to heap-allocated regions, and record their types,
+ so that we have a user-friendly way of showing the memory
+ (by field, rather than by byte offset). */
+ for (auto cluster_iter : *state.m_region_model->get_store ())
+ for (auto binding_iter : *cluster_iter.second)
+ {
+ const svalue *svalue = binding_iter.second;
+ if (const region *reg = svalue->maybe_get_region ())
+ if (svalue->get_type () && !reg->get_type ())
+ {
+ tree pointed_to_type = TREE_TYPE (svalue->get_type ());
+ if (!VOID_TYPE_P (pointed_to_type))
+ m_types_for_untyped_regions[reg] = pointed_to_type;
+ }
+ }
+
+ /* TODO: look for vtable pointers at the top of dynamically-allocated
+ regions and use that type as a fallback. */
+
+ /* Find regions of interest.
+ Create elements per region, and build into hierarchy.
+ Add edges for pointers. */
+
+ /* Create stack, from top to bottom. */
+ for (int i = state.m_region_model->get_stack_depth () - 1; i >= 0; --i)
+ {
+ const frame_region *reg = state.m_region_model->get_frame_at_index (i);
+ get_or_create_element (*reg);
+ }
+
+ /* Create bound memory. */
+ for (auto iter : *state.m_region_model->get_store ())
+ {
+ const bool create_all = false; // "true" for verbose, for debugging
+ create_elements_for_binding_cluster (*iter.second, create_all);
+ }
+
+ /* TODO: Constraints. */
+
+ /* Annotate with information from state machines. */
+ {
+ int i;
+ sm_state_map *smap;
+ FOR_EACH_VEC_ELT (state.m_checker_states, i, smap)
+ {
+ auto &sm = ext_state.get_sm (i);
+ for (const auto &iter : *smap)
+ sm.add_state_to_xml (*this, *iter.first, iter.second.m_state);
+ if (auto s = smap->get_global_state ())
+ sm.add_global_state_to_xml (*this, s);
+ }
+ }
+}
+
+xml::element &
+xml_state::get_or_create_element (const region ®)
+{
+ auto existing = m_region_to_element_map.find (®);
+ if (existing != m_region_to_element_map.end ())
+ return *existing->second;
+
+ auto &e = create_and_add_element (reg);
+ m_region_to_element_map[®] = &e;
+ return e;
+}
+
+xml::element&
+xml_state::create_and_add_element (const region ®)
+{
+ auto e = create_element (reg);
+ xml::element &result = *e;
+ if (auto parent_reg = reg.get_parent_region ())
+ {
+ auto parent_element = &get_or_create_element (*parent_reg);
+ parent_element->add_child (std::move (e));
+ }
+ else
+ m_root->add_child (std::move (e));
+ return result;
+}
+
+std::unique_ptr<xml::element>
+xml_state::make_memory_space_element (const char *label)
+{
+ auto e = std::make_unique<xml::element> ("memory-space", false);
+ e->set_attr ("label", label);
+ return e;
+}
+
+std::unique_ptr<xml::element>
+xml_state::create_element (const region ®)
+{
+ std::unique_ptr<xml::element> e;
+ switch (reg.get_kind ())
+ {
+ default:
+ gcc_unreachable ();
+
+ case RK_FRAME:
+ {
+ e = std::make_unique<xml::element> ("stack-frame", false);
+ const frame_region &frame_reg
+ = static_cast<const frame_region &> (reg);
+ {
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ pp_printf (&pp, "%E", frame_reg.get_fndecl ());
+ e->set_attr ("function", pp_formatted_text (&pp));
+ }
+ }
+ break;
+ case RK_GLOBALS:
+ e = make_memory_space_element ("Globals");
+ break;
+ case RK_CODE:
+ e = make_memory_space_element ("Code");
+ break;
+ case RK_FUNCTION:
+ e = std::make_unique<xml::element> ("function", false);
+ // TODO
+ break;
+ case RK_LABEL:
+ e = std::make_unique<xml::element> ("label", false);
+ // TODO
+ break;
+ case RK_STACK:
+ e = std::make_unique<xml::element> ("stack", false);
+ break;
+ case RK_HEAP:
+ e = make_memory_space_element ("Heap");
+ break;
+ case RK_THREAD_LOCAL:
+ e = make_memory_space_element ("Thread-local");
+ break;
+ case RK_ROOT:
+ e = std::make_unique<xml::element> ("memory-regions", false);
+ break;
+ case RK_SYMBOLIC:
+ e = std::make_unique<xml::element> ("symbolic-region", false);
+ // TODO
+ break;
+ case RK_DECL:
+ {
+ e = std::make_unique<xml::element> ("variable", false);
+ const decl_region &decl_reg
+ = static_cast<const decl_region &> (reg);
+ {
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ pp_printf (&pp, "%E", decl_reg.get_decl ());
+ e->set_attr ("name", pp_formatted_text (&pp));
+ }
+ set_type_attr (*e, TREE_TYPE (decl_reg.get_decl ()));
+ }
+ break;
+ case RK_FIELD:
+ e = std::make_unique<xml::element> ("field", false);
+ break;
+ case RK_ELEMENT:
+ e = std::make_unique<xml::element> ("element", false);
+ break;
+ case RK_OFFSET:
+ e = std::make_unique<xml::element> ("offset-region", false);
+ // TODO
+ break;
+ case RK_SIZED:
+ e = std::make_unique<xml::element> ("sized-region", false);
+ // TODO
+ break;
+ case RK_CAST:
+ e = std::make_unique<xml::element> ("cast-region", false);
+ // TODO
+ break;
+ case RK_HEAP_ALLOCATED:
+ e = std::make_unique<xml::element> ("heap-buffer", false);
+ set_attr_for_dynamic_extents (reg, *e);
+ break;
+ case RK_ALLOCA:
+ e = std::make_unique<xml::element> ("alloca-buffer", false);
+ set_attr_for_dynamic_extents (reg, *e);
+ break;
+ case RK_STRING:
+ e = std::make_unique<xml::element> ("string-region", false);
+ // TODO
+ break;
+ case RK_BIT_RANGE:
+ e = std::make_unique<xml::element> ("RK_BIT_RANGE", false); // TODO
+ break;
+ case RK_VAR_ARG:
+ e = std::make_unique<xml::element> ("RK_VAR_ARG", false); // TODO
+ break;
+ case RK_ERRNO:
+ e = std::make_unique<xml::element> ("errno", false);
+ break;
+ case RK_PRIVATE:
+ e = std::make_unique<xml::element> ("RK_PRIVATE", false); // TODO
+ break;
+ case RK_UNKNOWN:
+ e = std::make_unique<xml::element> ("RK_UNKNOWN", false); // TODO
+ break;
+ }
+ gcc_assert (e);
+
+ set_region_id_attr (*e, reg);
+
+ if (reg.get_base_region () == ®)
+ if (!reg.get_type ())
+ {
+ auto search
+ = m_types_for_untyped_regions.find (®);
+ if (search != m_types_for_untyped_regions.end ())
+ {
+ tree type_to_use = search->second;
+ set_type_attr (*e, type_to_use);
+ }
+ }
+
+ return e;
+}
+
+void
+xml_state::create_elements_for_binding_cluster (const binding_cluster &cluster,
+ bool create_all)
+{
+ /* TODO:
+ - symbolic bindings
+ - get current svalue, so as to get "zeros" and "uninitialized". */
+
+ concrete_bindings_t conc_bindings;
+ for (auto iter : cluster)
+ {
+ const binding_key *key = iter.first;
+ const svalue *svalue = iter.second;
+ if (auto conc_key = key->dyn_cast_concrete_binding ())
+ conc_bindings[conc_key->get_bit_range ()] = svalue;
+ if (const region *reg = svalue->maybe_get_region ())
+ get_or_create_element (*reg);
+ }
+
+ auto &e = get_or_create_element (*cluster.get_base_region ());
+
+ e.add_child (create_element_for_conc_bindings (conc_bindings));
+
+ const region *typed_reg = cluster.get_base_region ();
+ if (!typed_reg->get_type ())
+ {
+ auto search
+ = m_types_for_untyped_regions.find (cluster.get_base_region ());
+ if (search != m_types_for_untyped_regions.end ())
+ {
+ tree type_to_use = search->second;
+ typed_reg = m_mgr.get_cast_region (typed_reg, type_to_use);
+ }
+ }
+
+ if (typed_reg->get_type ())
+ populate_element_for_typed_region (e,
+ *typed_reg,
+ conc_bindings,
+ create_all);
+ else
+ {
+ // TODO
+ }
+}
+
+std::unique_ptr<xml::element>
+xml_state::create_element_for_conc_bindings (const concrete_bindings_t &conc_bindings)
+{
+ auto e = std::make_unique<xml::element> ("concrete-bindings", false);
+ for (auto iter : conc_bindings)
+ {
+ const bit_range bits = iter.first;
+ const svalue *sval = iter.second;
+ auto binding_element
+ = std::make_unique<xml::element> ("binding", false);
+ set_bits_attr (*binding_element, bits);
+ {
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ sval->dump_to_pp (&pp, true);
+ binding_element->set_attr ("value", pp_formatted_text (&pp));
+ if (auto svalue_element = create_element_for_svalue (sval))
+ binding_element->add_child (std::move (svalue_element));
+ }
+ e->add_child (std::move (binding_element));
+ }
+ return e;
+}
+
+// Try to get the bit_range of REG within its base region
+bool
+xml_state::get_bit_range_within_base_region (const region ®,
+ bit_range &out)
+{
+ region_offset start_offset = reg.get_offset (&m_mgr);
+ if (!start_offset.concrete_p ())
+ return false;
+ region_offset next_offset = reg.get_next_offset (&m_mgr);
+ if (!next_offset.concrete_p ())
+ return false;
+ out = bit_range (start_offset.get_bit_offset (),
+ next_offset.get_bit_offset ()
+ - start_offset.get_bit_offset ());
+ return true;
+}
+
+void
+xml_state::populate_element_for_typed_region (xml::element &e,
+ const region ®,
+ const concrete_bindings_t &conc_bindings,
+ bool create_all)
+{
+ const_tree reg_type = reg.get_type ();
+ gcc_assert (reg_type);
+ set_type_attr (e, reg_type);
+
+ bit_range bits (0, 0);
+ if (get_bit_range_within_base_region (reg, bits))
+ {
+ set_bits_attr (e, bits);
+
+ auto search = conc_bindings.find (bits);
+ if (search != conc_bindings.end ())
+ {
+ const svalue *bound_sval = search->second;
+ if (auto svalue_element = create_element_for_svalue (bound_sval))
+ {
+ xml::printer xp (e);
+ xp.push_tag ("value-of-region");
+ xp.append (std::move (svalue_element));
+ }
+ }
+ }
+
+ switch (TREE_CODE (reg_type))
+ {
+ default:
+ break;
+
+ case ARRAY_TYPE:
+ {
+ tree domain = TYPE_DOMAIN (reg_type);
+ if (!domain)
+ return;
+ const_tree max_idx = TYPE_MAX_VALUE (domain);
+ if (!max_idx)
+ return;
+ if (TREE_CODE (max_idx) != INTEGER_CST)
+ return;
+ const_tree min_idx = TYPE_MIN_VALUE (domain);
+ if (TREE_CODE (min_idx) != INTEGER_CST)
+ return;
+ for (offset_int idx = wi::to_offset (min_idx);
+ idx <= wi::to_offset (max_idx);
+ ++idx)
+ {
+ const_tree element_type = TREE_TYPE (reg_type);
+ const svalue *sval_index
+ = m_mgr.get_or_create_int_cst (domain, idx);
+ const region *child_reg
+ = m_mgr.get_element_region (®,
+ const_cast<tree> (element_type),
+ sval_index);
+ if (show_child_element_for_child_region_p (*child_reg,
+ conc_bindings,
+ create_all))
+ {
+ // Here "element" is in the xml sense
+ auto child_element
+ = std::make_unique<xml::element> ("element", false);
+ set_wi_attr (*child_element, "index", idx, UNSIGNED);
+ set_region_id_attr (*child_element, *child_reg);
+ // Recurse:
+ gcc_assert (element_type);
+ populate_element_for_typed_region (*child_element,
+ *child_reg,
+ conc_bindings,
+ create_all);
+ e.add_child (std::move (child_element));
+ }
+ }
+ }
+ break;
+
+ case RECORD_TYPE:
+ {
+ const record_layout layout (reg_type);
+ for (auto item : layout)
+ {
+ if (item.m_is_padding)
+ {
+ const bit_range bits (0, item.m_bit_range.m_size_in_bits);
+ const region *child_reg
+ = m_mgr.get_bit_range (®, NULL_TREE, bits);
+ if (show_child_element_for_child_region_p (*child_reg,
+ conc_bindings,
+ create_all))
+ {
+ auto child_element
+ = std::make_unique<xml::element> ("padding", false);
+ set_wi_attr (*child_element, "num_bits",
+ item.m_bit_range.m_size_in_bits, SIGNED);
+ e.add_child (std::move (child_element));
+ }
+ }
+ else
+ {
+ const region *child_reg
+ = m_mgr.get_field_region (®,
+ const_cast<tree> (item.m_field));
+ if (show_child_element_for_child_region_p (*child_reg,
+ conc_bindings,
+ create_all))
+ {
+ auto child_element
+ = std::make_unique<xml::element> ("field", false);
+ {
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ pp_printf (&pp, "%D", item.m_field);
+ child_element->set_attr ("name",
+ pp_formatted_text (&pp));
+ }
+ set_region_id_attr (*child_element, *child_reg);
+ // Recurse:
+ populate_element_for_typed_region (*child_element,
+ *child_reg,
+ conc_bindings,
+ create_all);
+ e.add_child (std::move (child_element));
+ }
+ }
+ }
+ }
+ break;
+ }
+}
+
+void
+xml_state::set_attr_for_dynamic_extents (const region ®, xml::element &e)
+{
+ const svalue *sval = m_state.m_region_model->get_dynamic_extents (®);
+ if (sval)
+ {
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ if (auto cst = sval->maybe_get_constant ())
+ pp_wide_int (&pp, wi::to_wide (cst), UNSIGNED);
+ else
+ sval->dump_to_pp (&pp, true);
+ e.set_attr ("dynamic-extents", pp_formatted_text (&pp));
+ }
+}
+
+bool
+xml_state::
+show_child_element_for_child_region_p (const region ®,
+ const concrete_bindings_t &conc_bindings,
+ bool create_all)
+{
+ if (create_all)
+ return true;
+ bit_range reg_bits (0, 0);
+ if (!get_bit_range_within_base_region (reg, reg_bits))
+ return true;
+
+ /* Is any of "bits" bound?
+ TODO: ideally there would be a more efficient way to do this, using
+ spatial relationships. */
+ for (auto iter : conc_bindings)
+ {
+ const bit_range bound_bits = iter.first;
+ if (bound_bits.intersects_p (reg_bits))
+ return true;
+ }
+ return false;
+}
+
+std::unique_ptr<xml::element>
+xml_state::create_element_for_svalue (const svalue *sval)
+{
+ if (!sval)
+ return nullptr;
+
+ std::unique_ptr<xml::element> result;
+ switch (sval->get_kind ())
+ {
+ default:
+ gcc_unreachable ();
+ case SK_REGION:
+ {
+ const region_svalue *region_sval = (const region_svalue *)sval;
+ result
+ = std::make_unique<xml::element> ("pointer-to-region", false);
+ set_region_id_attr (*result, *region_sval->get_pointee ());
+ }
+ break;
+ case SK_CONSTANT:
+ {
+ const constant_svalue *constant_sval = (const constant_svalue *)sval;
+ result = std::make_unique<xml::element> ("constant", false);
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ pp_printf (&pp, "%E", constant_sval->get_constant ());
+ result->set_attr ("value", pp_formatted_text (&pp));
+ }
+ break;
+ case SK_UNKNOWN:
+ result = std::make_unique<xml::element> ("unknown", false);
+ break;
+ case SK_POISONED:
+ {
+ const poisoned_svalue *poisoned_sval = (const poisoned_svalue *)sval;
+ switch (poisoned_sval->get_poison_kind ())
+ {
+ default:
+ gcc_unreachable ();
+ case poison_kind::uninit:
+ result = std::make_unique<xml::element> ("uninitialized", false);
+ break;
+ case poison_kind::freed:
+ result = std::make_unique<xml::element> ("freed", false);
+ break;
+ case poison_kind::deleted:
+ result = std::make_unique<xml::element> ("deleted", false);
+ break;
+ case poison_kind::popped_stack:
+ result = std::make_unique<xml::element> ("popped-stack", false);
+ break;
+ }
+ }
+ break;
+ case SK_SETJMP:
+ {
+ //const setjmp_svalue *setjmp_sval = (const setjmp_svalue *)sval;
+ result = std::make_unique<xml::element> ("setjmp-buffer", false);
+ // TODO
+ }
+ break;
+ case SK_INITIAL:
+ {
+ const initial_svalue *initial_sval = (const initial_svalue *)sval;
+ result = std::make_unique<xml::element> ("initial-value-of", false);
+ set_region_id_attr (*result, *initial_sval->get_region ());
+ }
+ break;
+ case SK_UNARYOP:
+ {
+ const unaryop_svalue *unaryop_sval = (const unaryop_svalue *)sval;
+ result = std::make_unique<xml::element> ("unary-op", false);
+ result->set_attr ("op", get_tree_code_name (unaryop_sval->get_op ()));
+ result->add_child
+ (create_element_for_svalue (unaryop_sval->get_arg ()));
+ }
+ break;
+ case SK_BINOP:
+ {
+ const binop_svalue *binop_sval = (const binop_svalue *)sval;
+ result = std::make_unique<xml::element> ("binary-op", false);
+ result->set_attr ("op", get_tree_code_name (binop_sval->get_op ()));
+ result->add_child (create_element_for_svalue (binop_sval->get_arg0 ()));
+ result->add_child (create_element_for_svalue (binop_sval->get_arg1 ()));
+ }
+ break;
+ case SK_SUB:
+ {
+ //const sub_svalue *sub_sval = (const sub_svalue *)sval;
+ result = std::make_unique<xml::element> ("subregion-value", false);
+ // TODO
+ }
+ break;
+ case SK_REPEATED:
+ {
+ const repeated_svalue *repeated_sval = (const repeated_svalue *)sval;
+ result = std::make_unique<xml::element> ("repeated-value", false);
+ result->add_child
+ (create_element_for_svalue (repeated_sval->get_outer_size ()));
+ result->add_child
+ (create_element_for_svalue (repeated_sval->get_inner_svalue ()));
+ }
+ break;
+ case SK_BITS_WITHIN:
+ {
+ const bits_within_svalue *bits_within_sval
+ = (const bits_within_svalue *)sval;
+ result = std::make_unique<xml::element> ("bits-within", false);
+ set_bits_attr (*result, bits_within_sval->get_bits ());
+ result->add_child
+ (create_element_for_svalue (bits_within_sval->get_inner_svalue ()));
+ }
+ break;
+ case SK_UNMERGEABLE:
+ {
+ const unmergeable_svalue *unmergeable_sval
+ = (const unmergeable_svalue *)sval;
+ result = std::make_unique<xml::element> ("unmergeable", false);
+ result->add_child
+ (create_element_for_svalue (unmergeable_sval->get_arg ()));
+ }
+ break;
+ case SK_PLACEHOLDER:
+ {
+ const placeholder_svalue *placeholder_sval
+ = (const placeholder_svalue *)sval;
+ result = std::make_unique<xml::element> ("placeholder", false);
+ result->set_attr ("name", placeholder_sval->get_name ());
+ }
+ break;
+ case SK_WIDENING:
+ {
+ //const widening_svalue *widening_sval = (const widening_svalue *)sval;
+ result = std::make_unique<xml::element> ("iterating-value", false);
+ // TODO
+ }
+ break;
+ case SK_COMPOUND:
+ {
+ //const compound_svalue *compound_sval = (const compound_svalue *)sval;
+ result = std::make_unique<xml::element> ("compound-value", false);
+ // TODO
+ }
+ break;
+ case SK_CONJURED:
+ {
+ //const conjured_svalue *conjured_sval = (const conjured_svalue *)sval;
+ result = std::make_unique<xml::element> ("conjured-value", false);
+ // TODO
+ }
+ break;
+ case SK_ASM_OUTPUT:
+ {
+ /* const asm_output_svalue *asm_output_sval
+ = (const asm_output_svalue *)sval; */
+ result = std::make_unique<xml::element> ("asm-output", false);
+ // TODO
+ }
+ break;
+ case SK_CONST_FN_RESULT:
+ {
+ /* const const_fn_result_svalue *const_fn_result_sval
+ = (const const_fn_result_svalue *)sval; */
+ result = std::make_unique<xml::element> ("const-fn-result", false);
+ // TODO
+ }
+ }
+
+ if (result)
+ {
+ if (sval->get_type ())
+ set_type_attr (*result, sval->get_type ());
+
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ sval->dump_to_pp (&pp, true);
+ result->set_attr ("dump-text", pp_formatted_text (&pp));
+ }
+
+ return result;
+}
+
+std::unique_ptr<xml::document>
+program_state::make_xml (const extrinsic_state &ext_state) const
+{
+ return std::make_unique<xml_state> (*this, ext_state);
+}
+
+void
+program_state::dump_xml_to_pp (const extrinsic_state &ext_state,
+ pretty_printer *pp) const
+{
+ auto doc = make_xml (ext_state);
+ doc->write_as_xml (pp, 0, true);
+}
+
+void
+program_state::dump_xml_to_file (const extrinsic_state &ext_state,
+ FILE *outf) const
+{
+ pretty_printer pp;
+ pp.set_output_stream (outf);
+ dump_xml_to_pp (ext_state, &pp);
+ pp_flush (&pp);
+}
+
+void
+program_state::dump_xml (const extrinsic_state &ext_state) const
+{
+ dump_xml_to_file (ext_state, stderr);
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
--- /dev/null
+/* XML documents for dumping state in an easier-to-read form.
+ 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_ANALYZER_ANA_STATE_TO_XML_STATE_H
+#define GCC_ANALYZER_ANA_STATE_TO_XML_STATE_H
+
+#include "xml.h"
+
+namespace ana {
+
+class xml_state : public xml::document
+{
+public:
+ xml_state (const program_state &state,
+ const extrinsic_state &ext_state);
+
+ xml::element &
+ get_or_create_element (const region ®);
+
+private:
+ xml::element&
+ create_and_add_element (const region ®);
+
+ static std::unique_ptr<xml::element>
+ make_memory_space_element (const char *label);
+
+ std::unique_ptr<xml::element>
+ create_element (const region ®);
+
+ /* Spatially sorted concrete bindings. */
+ typedef std::map<bit_range, const svalue *> concrete_bindings_t;
+
+ void
+ create_elements_for_binding_cluster (const binding_cluster &cluster,
+ bool create_all);
+
+ std::unique_ptr<xml::element>
+ create_element_for_conc_bindings (const concrete_bindings_t &conc_bindings);
+
+ // Try to get the bit_range of REG within its base region
+ bool
+ get_bit_range_within_base_region (const region ®,
+ bit_range &out);
+
+ void
+ populate_element_for_typed_region (xml::element &e,
+ const region ®,
+ const concrete_bindings_t &conc_bindings,
+ bool create_all);
+
+ void
+ set_attr_for_dynamic_extents (const region ®, xml::element &e);
+
+ bool
+ show_child_element_for_child_region_p (const region ®,
+ const concrete_bindings_t &conc_bindings,
+ bool create_all);
+
+ std::unique_ptr<xml::element>
+ create_element_for_svalue (const svalue *sval);
+
+ const program_state &m_state;
+ const extrinsic_state &m_ext_state;
+ region_model_manager &m_mgr;
+ xml::element *m_root;
+ std::map<const region *, xml::element *> m_region_to_element_map;
+ std::map<const region *, tree> m_types_for_untyped_regions;
+};
+
+} // namespace ana
+
+#endif /* GCC_ANALYZER_ANA_STATE_TO_XML_STATE_H */
#include "inlining-iterator.h"
#include "tree-logical-location.h"
#include "diagnostic-format-sarif.h"
+#include "xml.h"
#include "analyzer/analyzer-logging.h"
#include "analyzer/sm.h"
checker_event::checker_event (enum event_kind kind,
const event_loc_info &loc_info)
-: m_kind (kind), m_loc (loc_info.m_loc),
+: m_path (nullptr),
+ m_kind (kind), m_loc (loc_info.m_loc),
m_original_fndecl (loc_info.m_fndecl),
m_effective_fndecl (loc_info.m_fndecl),
m_original_depth (loc_info.m_depth),
pertinent data within the sm-state). */
void
-checker_event::prepare_for_emission (checker_path *,
+checker_event::prepare_for_emission (checker_path *path,
pending_diagnostic *pd,
diagnostic_event_id_t emission_id)
{
+ m_path = path;
m_pending_diagnostic = pd;
m_emission_id = emission_id;
print_desc (*pp.get ());
}
+std::unique_ptr<xml::document>
+checker_event::maybe_make_xml_state (bool debug) const
+{
+ const program_state *state = get_program_state ();
+ if (!state)
+ return nullptr;
+
+ gcc_assert (m_path);
+ const extrinsic_state &ext_state = m_path->get_ext_state ();
+
+ auto result = state->make_xml (ext_state);
+
+ if (debug)
+ {
+ pretty_printer pp;
+ text_art::theme *theme = global_dc->get_diagram_theme ();
+ text_art::dump_to_pp (*state, theme, &pp);
+ result->add_comment (pp_formatted_text (&pp));
+ }
+
+ return result;
+}
+
/* class debug_event : public checker_event. */
/* Implementation of diagnostic_event::print_desc vfunc for
/* class function_entry_event : public checker_event. */
-function_entry_event::function_entry_event (const program_point &dst_point)
+function_entry_event::function_entry_event (const program_point &dst_point,
+ const program_state &state)
: checker_event (event_kind::function_entry,
event_loc_info (dst_point.get_supernode
()->get_start_location (),
dst_point.get_fndecl (),
- dst_point.get_stack_depth ()))
+ dst_point.get_stack_depth ())),
+ m_state (state)
{
}
return false;
}
+const program_state *
+superedge_event::get_program_state () const
+{
+ return &m_eedge.m_dest->get_state ();
+}
+
/* superedge_event's ctor. */
superedge_event::superedge_event (enum event_kind kind,
return m_dest_snode->m_fun->decl;
}
+const program_state *
+call_event::get_program_state () const
+{
+ /* Use the state at the source (at the caller),
+ rather than the one at the dest, which has a frame for the callee. */
+ return &m_eedge.m_src->get_state ();
+}
+
/* class return_event : public superedge_event. */
/* return_event's ctor. */
return meaning (VERB_danger, NOUN_unknown);
}
+const program_state *
+warning_event::get_program_state () const
+{
+ if (m_program_state)
+ return m_program_state.get ();
+ else
+ return &m_enode->get_state ();
+}
+
} // namespace ana
#endif /* #if ENABLE_ANALYZER */
virtual bool is_function_entry_p () const { return false; }
virtual bool is_return_p () const { return false; }
+ virtual const program_state *
+ get_program_state () const { return nullptr; }
+
+ std::unique_ptr<xml::document>
+ maybe_make_xml_state (bool debug) const final override;
+
/* For use with %@. */
const diagnostic_event_id_t *get_id_ptr () const
{
const event_loc_info &loc_info);
private:
+ const checker_path *m_path;
const enum event_kind m_kind;
protected:
location_t m_loc;
void print_desc (pretty_printer &) const final override;
+ const program_state *
+ get_program_state () const final override
+ {
+ return &m_dst_state;
+ }
+
const gimple * const m_stmt;
const program_state m_dst_state;
};
class function_entry_event : public checker_event
{
public:
- function_entry_event (const event_loc_info &loc_info)
- : checker_event (event_kind::function_entry, loc_info)
+ function_entry_event (const event_loc_info &loc_info,
+ const program_state &state)
+ : checker_event (event_kind::function_entry, loc_info),
+ m_state (state)
{
}
- function_entry_event (const program_point &dst_point);
+ function_entry_event (const program_point &dst_point,
+ const program_state &state);
void print_desc (pretty_printer &pp) const override;
meaning get_meaning () const override;
bool is_function_entry_p () const final override { return true; }
+
+ const program_state *
+ get_program_state () const final override
+ {
+ return &m_state;
+ }
+
+private:
+ const program_state &m_state;
};
/* Subclass of checker_event describing a state change. */
void print_desc (pretty_printer &pp) const final override;
meaning get_meaning () const override;
+ const program_state *
+ get_program_state () const final override
+ {
+ return &m_dst_state;
+ }
+
const function *get_dest_function () const
{
return m_dst_state.get_current_function ();
bool should_filter_p (int verbosity) const;
+ const program_state *
+ get_program_state () const override;
+
protected:
superedge_event (enum event_kind kind, const exploded_edge &eedge,
const event_loc_info &loc_info);
bool is_call_p () const final override;
+ const program_state *
+ get_program_state () const final override;
+
protected:
tree get_caller_fndecl () const;
tree get_callee_fndecl () const;
warning_event (const event_loc_info &loc_info,
const exploded_node *enode,
const state_machine *sm,
- tree var, state_machine::state_t state)
+ tree var, state_machine::state_t state,
+ const program_state *program_state_ = nullptr)
: checker_event (event_kind::warning, loc_info),
m_enode (enode),
m_sm (sm), m_var (var), m_state (state)
{
+ if (program_state_)
+ m_program_state = std::make_unique<program_state> (*program_state_);
}
void print_desc (pretty_printer &pp) const final override;
meaning get_meaning () const override;
+ const program_state *
+ get_program_state () const final override;
+
const exploded_node *get_exploded_node () const { return m_enode; }
private:
const state_machine *m_sm;
tree m_var;
state_machine::state_t m_state;
+ /* Optional copy of program state, for when this is different from
+ m_enode's state: */
+ std::unique_ptr<program_state> m_program_state;
};
} // namespace ana
{
public:
checker_path (const logical_location_manager &logical_loc_mgr,
+ const extrinsic_state &ext_state,
logger *logger)
: diagnostic_path (logical_loc_mgr),
+ m_ext_state (ext_state),
m_thread ("main"),
m_logger (logger)
{}
return m_thread;
}
+ const extrinsic_state &get_ext_state () const { return m_ext_state; }
+
checker_event *get_checker_event (int idx)
{
return m_events[idx];
private:
DISABLE_COPY_AND_ASSIGN(checker_path);
+ const extrinsic_state &m_ext_state;
+
simple_diagnostic_thread m_thread;
/* The events that have occurred along this path. */
#define GCC_ANALYZER_COMMON_H
#include "config.h"
+#define INCLUDE_MAP
+#define INCLUDE_STRING
#define INCLUDE_VECTOR
#include "system.h"
#include "coretypes.h"
for (unsigned i = 0; i < m_notes.length (); i++)
if (!m_notes[i]->equal_p (*other.m_notes[i]))
return false;
+
+ // Don't deduplicate dump_path_diagnostic instances
+ if (!strcmp (m_d->get_kind (), "dump_path_diagnostic"))
+ return this == &other;
+
return (m_sm == other.m_sm
/* We don't compare m_enode. */
&& m_snode == other.m_snode
/* This is the diagnostic_path subclass that will be built for
the diagnostic. */
checker_path emission_path (get_logical_location_manager (),
+ eg.get_ext_state (),
get_logger ());
/* Populate emission_path with a full description of EPATH. */
}
tree leaked_tree_for_diag = fixup_tree_for_diagnostic (leaked_tree);
- std::unique_ptr<pending_diagnostic> pd = sm.on_leak (leaked_tree_for_diag);
+ std::unique_ptr<pending_diagnostic> pd = sm.on_leak (leaked_tree_for_diag,
+ m_old_state,
+ m_new_state);
if (pd)
{
pending_location ploc (m_enode_for_diag,
state->dump (eg.get_ext_state (), true);
return;
}
+ else if (is_special_named_call_p (call, "__analyzer_dump_xml", 0))
+ {
+ state->dump_xml (eg.get_ext_state ());
+ return;
+ }
+ else if (is_special_named_call_p (call, "__analyzer_dump_dot", 0))
+ {
+ state->dump_dot (eg.get_ext_state ());
+ return;
+ }
else if (is_special_named_call_p (call, "__analyzer_dump_state", 2))
{
state->impl_call_analyzer_dump_state (call, eg.get_ext_state (),
const gimple *get_stmt () const override { return m_stmt; }
const exploded_graph *get_eg () const override { return m_eg; }
+ const program_state *get_state () const override { return m_new_state; }
+
void maybe_did_work () override;
bool checking_for_infinite_loop_p () const override { return false; }
void on_unusable_in_infinite_loop () override {}
{
public:
recursive_function_entry_event (const program_point &dst_point,
+ const program_state &dst_state,
const infinite_recursion_diagnostic &pd,
bool topmost)
- : function_entry_event (dst_point),
+ : function_entry_event (dst_point, dst_state),
m_pd (pd),
m_topmost (topmost)
{
{
gcc_assert (m_prev_entry_event == NULL);
std::unique_ptr<checker_event> prev_entry_event
- = std::make_unique <recursive_function_entry_event> (dst_point,
- *this, false);
+ = std::make_unique <recursive_function_entry_event>
+ (dst_point,
+ dst_node->get_state (),
+ *this, false);
m_prev_entry_event = prev_entry_event.get ();
emission_path->add_event (std::move (prev_entry_event));
}
else if (eedge.m_dest == m_new_entry_enode)
emission_path->add_event
(std::make_unique<recursive_function_entry_event>
- (dst_point, *this, true));
+ (dst_point, dst_node->get_state (), *this, true));
else
pending_diagnostic::add_function_entry_event (eedge, emission_path);
}
#include "analyzer/region-model.h"
#include "analyzer/pending-diagnostic.h"
#include "analyzer/call-details.h"
+#include "analyzer/program-state.h"
#if ENABLE_ANALYZER
: public pending_diagnostic_subclass<dump_path_diagnostic>
{
public:
+ dump_path_diagnostic (const program_state &state)
+ : m_state (state)
+ {
+ }
+
int get_controlling_option () const final override
{
return 0;
{
return true;
}
+
+ const program_state *
+ get_final_state () const final override
+ {
+ return &m_state;
+ }
+
+private:
+ program_state m_state;
};
/* Handle calls to "__analyzer_dump_path" by queuing a diagnostic at this
region_model_context *ctxt = cd.get_ctxt ();
if (!ctxt)
return;
- ctxt->warn (std::make_unique<dump_path_diagnostic> ());
+ if (const program_state *state = ctxt->get_state ())
+ ctxt->warn (std::make_unique<dump_path_diagnostic> (*state));
}
};
{
const exploded_node *dst_node = eedge.m_dest;
const program_point &dst_point = dst_node->get_point ();
- emission_path->add_event (std::make_unique<function_entry_event> (dst_point));
+ const program_state &dst_state = dst_node->get_state ();
+ emission_path->add_event
+ (std::make_unique<function_entry_event> (dst_point,
+ dst_state));
}
/* Base implementation of pending_diagnostic::add_call_event.
(std::make_unique<warning_event>
(loc_info,
enode,
- sm, var, state));
+ sm, var, state,
+ get_final_state ()));
}
} // namespace ana
tree var, state_machine::state_t state,
checker_path *emission_path);
+ virtual const program_state *
+ get_final_state () const
+ {
+ return nullptr;
+ }
+
/* Vfunc for determining that this pending_diagnostic supercedes OTHER,
and that OTHER should therefore not be emitted.
They have already been tested for being at the same stmt. */
#include "cgraph.h"
#include "digraph.h"
#include "diagnostic-event-id.h"
+#include "diagnostic-state.h"
+#include "graphviz.h"
#include "text-art/tree-widget.h"
#include "text-art/dump.h"
#include "analyzer/state-purge.h"
#include "analyzer/call-summary.h"
#include "analyzer/analyzer-selftests.h"
+#include "analyzer/ana-state-to-diagnostic-state.h"
#if ENABLE_ANALYZER
return state_widget;
}
+void
+program_state::dump_dot (const extrinsic_state &ext_state) const
+{
+ auto doc = make_xml (ext_state);
+ auto graph = make_dot_graph_from_xml_state (*doc);
+
+ pretty_printer pp;
+ dot::writer w (pp);
+ graph->print (w);
+ pp_flush (&pp);
+}
+
/* Update this program_state to reflect a top-level call to FUN.
The params will have initial_svalues. */
#define GCC_ANALYZER_PROGRAM_STATE_H
#include "text-art/widget.h"
+#include "text-art/tree-widget.h"
+
+#include "analyzer/store.h"
+
+namespace xml { class document; }
namespace ana {
void dump (const extrinsic_state &ext_state, bool simple) const;
void dump () const;
+ std::unique_ptr<xml::document> make_xml (const extrinsic_state &ext_state) const;
+ void dump_xml_to_pp (const extrinsic_state &ext_state, pretty_printer *pp) const;
+ void dump_xml_to_file (const extrinsic_state &ext_state, FILE *outf) const;
+ void dump_xml (const extrinsic_state &ext_state) const;
+ void dump_dot (const extrinsic_state &ext_state) const;
+
std::unique_ptr<json::object>
to_json (const extrinsic_state &ext_state) const;
/* class record_layout. */
-record_layout::record_layout (tree record_type)
+record_layout::record_layout (const_tree record_type)
{
gcc_assert (TREE_CODE (record_type) == RECORD_TYPE);
{
public:
item (const bit_range &br,
- tree field,
+ const_tree field,
bool is_padding)
: m_bit_range (br),
m_field (field),
}
bit_range m_bit_range;
- tree m_field;
+ const_tree m_field;
bool m_is_padding;
};
- record_layout (tree record_type);
+ record_layout (const_tree record_type);
void dump_to_pp (pretty_printer *pp) const;
DEBUG_FUNCTION void dump () const;
const record_layout::item *get_item_at (bit_offset_t offset) const;
+ auto begin () const { return m_items.begin (); }
+ auto end () const { return m_items.end (); }
+
private:
void maybe_pad_to (bit_offset_t next_offset);
static void
complain_about_fully_uninit_item (const record_layout::item &item)
{
- tree field = item.m_field;
+ const_tree field = item.m_field;
bit_size_t num_bits = item.m_bit_range.m_size_in_bits;
if (item.m_is_padding)
{
static void
complain_about_partially_uninit_item (const record_layout::item &item)
{
- tree field = item.m_field;
+ const_tree field = item.m_field;
if (item.m_is_padding)
inform (DECL_SOURCE_LOCATION (field),
"padding after field %qD is partially uninitialized",
virtual const exploded_graph *get_eg () const = 0;
+ virtual const program_state *get_state () const = 0;
+
/* Hooks for detecting infinite loops. */
virtual void maybe_did_work () = 0;
virtual bool checking_for_infinite_loop_p () const = 0;
const gimple *get_stmt () const override { return NULL; }
const exploded_graph *get_eg () const override { return NULL; }
+ const program_state *get_state () const override { return nullptr; }
+
void maybe_did_work () override {}
bool checking_for_infinite_loop_p () const override { return false; }
void on_unusable_in_infinite_loop () override {}
return nullptr;
}
+ const program_state *get_state () const override
+ {
+ if (m_inner)
+ return m_inner->get_state ();
+ else
+ return nullptr;
+ }
+
void maybe_did_work () override
{
if (m_inner)
const svalue *rhs) const final override;
bool can_purge_p (state_t s) const final override;
- std::unique_ptr<pending_diagnostic> on_leak (tree var) const final override;
+
+ std::unique_ptr<pending_diagnostic>
+ on_leak (tree var,
+ const program_state *old_state,
+ const program_state *new_state) const final override;
bool is_unchecked_fd_p (state_t s) const;
bool is_valid_fd_p (state_t s) const;
class fd_leak : public fd_diagnostic
{
public:
- fd_leak (const fd_state_machine &sm, tree arg) : fd_diagnostic (sm, arg) {}
+ fd_leak (const fd_state_machine &sm, tree arg,
+ const program_state *final_state)
+ : fd_diagnostic (sm, arg),
+ m_final_state ()
+ {
+ if (final_state)
+ m_final_state = std::make_unique<program_state> (*final_state);
+ }
const char *
get_kind () const final override
return true;
}
+ const program_state *
+ get_final_state () const final override
+ {
+ return m_final_state.get ();
+ }
+
private:
diagnostic_event_id_t m_open_event;
+ std::unique_ptr<program_state> m_final_state;
};
class fd_access_mode_mismatch : public fd_param_diagnostic
else
{
sm_ctxt.warn (node, stmt, NULL_TREE,
- std::make_unique<fd_leak> (*this, NULL_TREE));
+ std::make_unique<fd_leak> (*this, NULL_TREE, nullptr));
}
}
sm_ctxt.on_transition (node, stmt, lhs, m_start, m_unchecked_write_only);
else
sm_ctxt.warn (node, stmt, NULL_TREE,
- std::make_unique<fd_leak> (*this, NULL_TREE));
+ std::make_unique<fd_leak> (*this, NULL_TREE, nullptr));
}
void
}
else
sm_ctxt.warn (node, &call, NULL_TREE,
- std::make_unique<fd_leak> (*this, NULL_TREE));
+ std::make_unique<fd_leak> (*this, NULL_TREE, nullptr));
}
else
{
}
else
sm_ctxt.warn (node, &call, NULL_TREE,
- std::make_unique<fd_leak> (*this, NULL_TREE));
+ std::make_unique<fd_leak> (*this, NULL_TREE, nullptr));
}
else
{
}
std::unique_ptr<pending_diagnostic>
-fd_state_machine::on_leak (tree var) const
+fd_state_machine::on_leak (tree var,
+ const program_state *,
+ const program_state *new_state) const
{
- return std::make_unique<fd_leak> (*this, var);
+ return std::make_unique<fd_leak> (*this, var, new_state);
}
} // namespace
#include "analyzer/analyzer-selftests.h"
#include "analyzer/call-string.h"
#include "analyzer/program-point.h"
+#include "analyzer/program-state.h"
#include "analyzer/store.h"
#include "analyzer/region-model.h"
#include "analyzer/call-details.h"
const svalue *rhs) const final override;
bool can_purge_p (state_t s) const final override;
- std::unique_ptr<pending_diagnostic> on_leak (tree var) const final override;
+
+ std::unique_ptr<pending_diagnostic>
+ on_leak (tree var,
+ const program_state *old_state,
+ const program_state *new_state) const final override;
/* State for a FILE * returned from fopen that hasn't been checked for
NULL.
class file_leak : public file_diagnostic
{
public:
- file_leak (const fileptr_state_machine &sm, tree arg)
- : file_diagnostic (sm, arg)
- {}
+ file_leak (const fileptr_state_machine &sm, tree arg,
+ const program_state *final_state)
+ : file_diagnostic (sm, arg),
+ m_final_state ()
+ {
+ if (final_state)
+ m_final_state = std::make_unique<program_state> (*final_state);
+ }
const char *get_kind () const final override { return "file_leak"; }
return true;
}
+ const program_state *
+ get_final_state () const final override
+ {
+ return m_final_state.get ();
+ }
+
private:
diagnostic_event_id_t m_fopen_event;
+ std::unique_ptr<program_state> m_final_state;
};
/* fileptr_state_machine's ctor. */
state 'unchecked' and 'nonnull'. */
std::unique_ptr<pending_diagnostic>
-fileptr_state_machine::on_leak (tree var) const
+fileptr_state_machine::on_leak (tree var,
+ const program_state *,
+ const program_state *new_state) const
{
- return std::make_unique<file_leak> (*this, var);
+ return std::make_unique<file_leak> (*this, var, new_state);
}
} // anonymous namespace
#include "diagnostic-event-id.h"
#include "stringpool.h"
#include "attribs.h"
+#include "xml-printer.h"
#include "analyzer/analyzer-logging.h"
#include "analyzer/sm.h"
#include "analyzer/checker-event.h"
#include "analyzer/exploded-graph.h"
#include "analyzer/inlining-iterator.h"
+#include "analyzer/ana-state-to-diagnostic-state.h"
#if ENABLE_ANALYZER
const frame_region *) const final override;
bool can_purge_p (state_t s) const final override;
- std::unique_ptr<pending_diagnostic> on_leak (tree var) const final override;
+
+ std::unique_ptr<pending_diagnostic>
+ on_leak (tree var,
+ const program_state *old_state,
+ const program_state *new_state) const final override;
bool reset_when_passed_to_unknown_fn_p (state_t s,
bool is_mutable) const final override;
const svalue *new_ptr_sval,
const extrinsic_state &ext_state) const;
+ void
+ add_state_to_xml (xml_state &out_xml,
+ const svalue &sval,
+ state_machine::state_t state) const final override;
+
standard_deallocator_set m_free;
standard_deallocator_set m_scalar_delete;
standard_deallocator_set m_vector_delete;
class malloc_leak : public malloc_diagnostic
{
public:
- malloc_leak (const malloc_state_machine &sm, tree arg)
- : malloc_diagnostic (sm, arg) {}
+ malloc_leak (const malloc_state_machine &sm, tree arg,
+ const program_state *final_state)
+ : malloc_diagnostic (sm, arg),
+ m_final_state ()
+ {
+ if (final_state)
+ m_final_state = std::make_unique<program_state> (*final_state);
+ }
const char *get_kind () const final override { return "malloc_leak"; }
return true;
}
+ const program_state *
+ get_final_state () const final override
+ {
+ return m_final_state.get ();
+ }
+
private:
diagnostic_event_id_t m_alloc_event;
+ std::unique_ptr<program_state> m_final_state;
};
class free_of_non_heap : public malloc_diagnostic
'nonnull'). */
std::unique_ptr<pending_diagnostic>
-malloc_state_machine::on_leak (tree var) const
+malloc_state_machine::on_leak (tree var,
+ const program_state *,
+ const program_state *new_state) const
{
- return std::make_unique<malloc_leak> (*this, var);
+ return std::make_unique<malloc_leak> (*this, var, new_state);
}
/* Implementation of state_machine::reset_when_passed_to_unknown_fn_p vfunc
smap->set_state (model, new_ptr_sval, m_free.m_nonnull, NULL, ext_state);
}
+void
+malloc_state_machine::add_state_to_xml (xml_state &out_xml,
+ const svalue &sval,
+ state_machine::state_t state) const
+{
+ if (const region *reg = sval.maybe_get_region ())
+ {
+ auto ®_element = out_xml.get_or_create_element (*reg);
+ auto alloc_state = as_a_allocation_state (state);
+ gcc_assert (alloc_state);
+
+ reg_element.set_attr ("dynamic-alloc-state", state->get_name ());
+ if (alloc_state->m_deallocators)
+ {
+ pretty_printer pp;
+ alloc_state->m_deallocators->dump_to_pp (&pp);
+ reg_element.set_attr ("expected-deallocators", pp_formatted_text (&pp));
+ }
+ if (alloc_state->m_deallocator)
+ reg_element.set_attr ("deallocator",
+ alloc_state->m_deallocator->m_name);
+ }
+}
+
} // anonymous namespace
/* Internal interface to this file. */
/* Base implementation of state_machine::on_leak. */
std::unique_ptr<pending_diagnostic>
-state_machine::on_leak (tree var ATTRIBUTE_UNUSED) const
+state_machine::on_leak (tree var ATTRIBUTE_UNUSED,
+ const program_state *old_state ATTRIBUTE_UNUSED,
+ const program_state *new_state ATTRIBUTE_UNUSED) const
{
- return NULL;
+ return nullptr;
}
/* Dump a multiline representation of this state machine to PP. */
return sm_obj;
}
+void
+state_machine::add_state_to_xml (xml_state &out_xml,
+ const svalue &sval,
+ state_machine::state_t state) const
+{
+ // no-op
+}
+
+void
+state_machine::add_global_state_to_xml (xml_state &out_xml,
+ state_machine::state_t state) const
+{
+ // no-op
+}
+
/* class sm_context. */
const region_model *
class state_machine;
class sm_context;
class pending_diagnostic;
+class xml_state;
extern bool any_pointer_p (tree expr);
extern bool any_pointer_p (const svalue *sval);
/* Called when VAR leaks (and !can_purge_p). */
virtual std::unique_ptr<pending_diagnostic>
- on_leak (tree var ATTRIBUTE_UNUSED) const;
+ on_leak (tree var ATTRIBUTE_UNUSED,
+ const program_state *old_state,
+ const program_state *new_state) const;
/* Return true if S should be reset to "start" for values passed (or reachable
from) calls to unknown functions. IS_MUTABLE is true for pointers as
state_t get_start_state () const { return m_start; }
+ virtual void
+ add_state_to_xml (xml_state &out_xml,
+ const svalue &sval,
+ state_machine::state_t state) const;
+
+ virtual void
+ add_global_state_to_xml (xml_state &out_xml,
+ state_machine::state_t state) const;
+
protected:
state_t add_state (const char *name);
state_t add_custom_state (state *s)
bool as_byte_range (byte_range *out) const;
+ bool
+ operator< (const bit_range &other) const
+ {
+ if (m_start_bit_offset < other.m_start_bit_offset)
+ return true;
+ if (m_start_bit_offset > other.m_start_bit_offset)
+ return false;
+ return (m_size_in_bits < other.m_size_in_bits);
+ }
+
bit_offset_t m_start_bit_offset;
bit_size_t m_size_in_bits;
};
{
return s != m_started;
}
- std::unique_ptr<pending_diagnostic> on_leak (tree var) const final override;
+
+ std::unique_ptr<pending_diagnostic>
+ on_leak (tree var,
+ const program_state *old_state,
+ const program_state *new_state) const final override;
/* State for a va_list that is the result of a va_start or va_copy. */
state_t m_started;
{
public:
va_list_leak (const va_list_state_machine &sm,
- const svalue *ap_sval, tree ap_tree)
+ const svalue *ap_sval, tree ap_tree,
+ const program_state *final_state)
: va_list_sm_diagnostic (sm, ap_sval, ap_tree),
- m_start_event_fnname (NULL)
+ m_start_event_fnname (NULL),
+ m_final_state ()
{
+ if (final_state)
+ m_final_state = std::make_unique<program_state> (*final_state);
}
int get_controlling_option () const final override
return true;
}
+ const program_state *
+ get_final_state () const final override
+ {
+ return m_final_state.get ();
+ }
+
private:
diagnostic_event_id_t m_start_event;
const char *m_start_event_fnname;
+ std::unique_ptr<program_state> m_final_state;
};
/* Update state machine for a "va_start" call. */
(for complaining about leaks of values in state 'started'). */
std::unique_ptr<pending_diagnostic>
-va_list_state_machine::on_leak (tree var) const
+va_list_state_machine::on_leak (tree var,
+ const program_state *,
+ const program_state *new_state) const
{
- return std::make_unique<va_list_leak> (*this, nullptr, var);
+ return std::make_unique<va_list_leak> (*this, nullptr, var, new_state);
}
} // anonymous namespace
#include "intl.h"
#include "xml.h"
#include "xml-printer.h"
+#include "diagnostic-state.h"
+#include "graphviz.h"
#include "json.h"
#include "selftest-xml.h"
html_generation_options::html_generation_options ()
: m_css (true),
- m_javascript (true)
+ m_javascript (true),
+ m_show_state_diagrams (false),
+ m_show_state_diagram_xml (false),
+ m_show_state_diagram_dot_src (false)
{
}
m_ui_focus_ids.append_string (focus_id.c_str ());
}
+ std::unique_ptr<xml::node>
+ maybe_make_state_diagram (const diagnostic_event &event);
+
private:
void
add_stylesheet (std::string url);
" const element_id = focus_ids[focus_idx];\n"
" return document.getElementById(element_id);\n"
" }\n"
+ " function get_any_state_diagram (focus_idx)\n"
+ " {\n"
+ " const element_id = focus_ids[focus_idx];\n"
+ " return document.getElementById(element_id + \"-state-diagram\");\n"
+ " }\n"
" function unhighlight_current_focus_idx ()\n"
" {\n"
" get_focus_span (current_focus_idx).classList.remove ('selected');\n"
+ " state_diagram = get_any_state_diagram (current_focus_idx);\n"
+ " if (state_diagram) {\n"
+ " state_diagram.style.visibility = \"hidden\";\n"
+ " }\n"
" }\n"
" function highlight_current_focus_idx ()\n"
" {\n"
" const el = get_focus_span (current_focus_idx);\n"
" el.classList.add ('selected');\n"
+ " state_diagram = get_any_state_diagram (current_focus_idx);\n"
+ " if (state_diagram) {\n"
+ " state_diagram.style.visibility = \"visible\";\n"
+ " }\n"
" // Center the element on the screen\n"
" const top_y = el.getBoundingClientRect ().top + window.pageYOffset;\n"
" const middle = top_y - (window.innerHeight / 2);\n"
m_cur_nesting_levels.pop_back ();
}
+static void
+print_pre_source (xml::printer &xp, const char *text)
+{
+ xp.push_tag_with_class ("pre", "source", true);
+ xp.add_text (text);
+ xp.pop_tag ("pre");
+}
+
+std::unique_ptr<xml::node>
+html_builder::maybe_make_state_diagram (const diagnostic_event &event)
+{
+ if (!m_html_gen_opts.m_show_state_diagrams)
+ return nullptr;
+
+ /* Get XML state document; if we're going to print it later, also request
+ the debug version. */
+ auto xml_state
+ = event.maybe_make_xml_state (m_html_gen_opts.m_show_state_diagram_xml);
+ if (!xml_state)
+ return nullptr;
+
+ // Convert it to .dot AST
+ auto graph = make_dot_graph_from_xml_state (*xml_state);
+ gcc_assert (graph);
+
+ auto wrapper = std::make_unique<xml::element> ("div", false);
+ xml::printer xp (*wrapper);
+
+ if (m_html_gen_opts.m_show_state_diagram_xml)
+ {
+ // For debugging, show the XML src inline:
+ pretty_printer pp;
+ xml_state->write_as_xml (&pp, 0, true);
+ print_pre_source (xp, pp_formatted_text (&pp));
+ }
+
+ if (m_html_gen_opts.m_show_state_diagram_dot_src)
+ {
+ // For debugging, show the dot src inline:
+ pretty_printer pp;
+ dot::writer w (pp);
+ graph->print (w);
+ print_pre_source (xp, pp_formatted_text (&pp));
+ }
+
+ // Turn the .dot into SVG and splice into place
+ auto svg = dot::make_svg_from_graph (*graph);
+ xp.append (std::move (svg));
+
+ return wrapper;
+}
+
/* Custom subclass of html_label_writer.
Wrap labels within a <span> element, supplying them with event IDs.
Add the IDs to the list of focus IDs. */
public:
html_path_label_writer (xml::printer &xp,
html_builder &builder,
+ const diagnostic_path &path,
const std::string &event_id_prefix)
: m_xp (xp),
m_html_builder (builder),
+ m_path (path),
m_event_id_prefix (event_id_prefix),
- m_next_event_idx (0)
+ m_next_event_idx (0),
+ m_curr_event_id ()
{
}
void begin_label () final override
{
+ m_curr_event_id = m_next_event_idx++;
m_xp.push_tag_with_class ("span", "event", true);
- pretty_printer pp;
- pp_printf (&pp, "%s%i",
- m_event_id_prefix.c_str (), m_next_event_idx++);
- m_xp.set_attr ("id", pp_formatted_text (&pp));
- m_html_builder.add_focus_id (pp_formatted_text (&pp));
+ m_xp.set_attr ("id", get_element_id ());
+ m_html_builder.add_focus_id (get_element_id ());
}
void end_label () final override
{
+ const diagnostic_event &event
+ = m_path.get_event (m_curr_event_id.zero_based ());
+ if (auto state_doc = m_html_builder.maybe_make_state_diagram (event))
+ {
+ m_xp.push_tag_with_class ("div", "state-diagram", false);
+ m_xp.set_attr ("id", get_element_id () + "-state-diagram");
+ m_xp.set_attr ("style",
+ ("position: absolute;"
+ " z-index: 1;"
+ " visibility: hidden;"));
+ m_xp.append (std::move (state_doc));
+ m_xp.pop_tag ("div");
+ }
+
m_xp.pop_tag ("span"); // from begin_label
}
private:
+ std::string
+ get_element_id () const
+ {
+ gcc_assert (m_curr_event_id.known_p ());
+ return (m_event_id_prefix
+ + std::to_string (m_curr_event_id.zero_based ()));
+ }
+
xml::printer &m_xp;
html_builder &m_html_builder;
+ const diagnostic_path &m_path;
const std::string &m_event_id_prefix;
int m_next_event_idx;
+ diagnostic_event_id_t m_curr_event_id;
};
/* See https://pf3.patternfly.org/v3/pattern-library/widgets/#alerts */
xp.pop_tag ("label");
std::string event_id_prefix (diag_id + "-event-");
- html_path_label_writer event_label_writer (xp, *this,
+ html_path_label_writer event_label_writer (xp, *this, *path,
event_id_prefix);
+
diagnostic_source_print_policy dspp (m_context);
print_path_as_html (xp, *path, m_context, &event_label_writer,
dspp);
bool m_css;
bool m_javascript;
+
+ // Debugging options:
+
+ // If true, attempt to show state diagrams at events
+ bool m_show_state_diagrams;
+
+ // If true, show the XML form of the state with such diagrams
+ bool m_show_state_diagram_xml;
+
+ // If true, show the .dot source used for the diagram
+ bool m_show_state_diagram_dot_src;
};
extern diagnostic_output_file
#include "pretty-print-urlifier.h"
#include "demangle.h"
#include "backtrace.h"
+#include "xml.h"
/* A json::array where the values are "unique" as per
SARIF v2.1.0 section 3.7.3 ("Array properties with unique values"). */
via a property bag. */
ev.maybe_add_sarif_properties (*this, tfl_obj);
+ if (get_opts ().m_xml_state)
+ if (auto xml_state = ev.maybe_make_xml_state (true))
+ {
+ sarif_property_bag &props = tfl_obj.get_or_create_properties ();
+
+ pretty_printer pp;
+ xml_state->write_as_xml (&pp, 0, true);
+
+#define PROPERTY_PREFIX "gcc/diagnostic_event/"
+ props.set_string (PROPERTY_PREFIX "xml_state",
+ pp_formatted_text (&pp));
+#undef PROPERTY_PREFIX
+ }
+
/* "location" property (SARIF v2.1.0 section 3.38.3). */
tfl_obj.set<sarif_location>
("location",
// struct sarif_generation_options
sarif_generation_options::sarif_generation_options ()
-: m_version (sarif_version::v2_1_0)
+: m_version (sarif_version::v2_1_0),
+ m_xml_state (false)
{
}
sarif_generation_options ();
enum sarif_version m_version;
+ bool m_xml_state;
};
extern std::unique_ptr<diagnostic_output_format>
#include "config.h"
#define INCLUDE_ALGORITHM
+#define INCLUDE_MAP
#define INCLUDE_STRING
#define INCLUDE_VECTOR
#include "system.h"
#include "coretypes.h"
#include "diagnostic.h"
#include "diagnostic-path.h"
+#include "xml.h"
/* Disable warnings about missing quoting in GCC diagnostics for the print
calls below. */
return label_text::take (xstrdup (pp_formatted_text (pp.get ())));
}
+// Base implementation of diagnostic_event::maybe_make_xml_state
+
+std::unique_ptr<xml::document>
+diagnostic_event::maybe_make_xml_state (bool) const
+{
+ // Don't attempt to make a state document:
+ return nullptr;
+}
+
/* class diagnostic_path. */
/* Subroutine of diagnostic_path::interprocedural_p.
#include "diagnostic-event-id.h"
#include "logical-location.h"
+namespace xml { class document; }
+
class sarif_builder;
class sarif_object;
{
}
+ /* Hook for capturing state at this event, potentially for visualizing
+ in HTML output. */
+ virtual std::unique_ptr<xml::document>
+ maybe_make_xml_state (bool debug) const;
+
label_text get_desc (pretty_printer &ref_pp) const;
};
--- /dev/null
+/* Creating GraphViz .dot files from XML state documents.
+ 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 "xml.h"
+#include "xml-printer.h"
+#include "graphviz.h"
+
+static int
+get_depth (const xml::element &e)
+{
+ int deepest_child = 0;
+ for (auto &iter : e.m_children)
+ if (xml::element *child_element = iter->dyn_cast_element ())
+ deepest_child = std::max (deepest_child,
+ get_depth (*child_element));
+ return deepest_child + 1;
+}
+
+enum class dynalloc_state
+{
+ unknown,
+ nonnull,
+ unchecked,
+ freed
+};
+
+static const char *
+get_color_for_dynalloc_state (enum dynalloc_state dynalloc_state)
+{
+ switch (dynalloc_state)
+ {
+ default:
+ gcc_unreachable ();
+ break;
+ case dynalloc_state::unknown:
+ case dynalloc_state::nonnull:
+ return nullptr;
+
+ case dynalloc_state::unchecked:
+ return "#ec7a08"; // pf-orange-400
+
+ case dynalloc_state::freed:
+ return "#cc0000"; // pf-red-100
+ }
+}
+
+static void
+set_color_for_dynalloc_state (dot::attr_list &attrs,
+ enum dynalloc_state dynalloc_state)
+{
+ if (const char *color = get_color_for_dynalloc_state (dynalloc_state))
+ attrs.add (dot::id ("color"), dot::id (color));
+}
+
+static enum dynalloc_state
+get_dynalloc_state (const xml::element &input_element)
+{
+ const char *dyn_alloc_state = input_element.get_attr ("dynamic-alloc-state");
+ if (!dyn_alloc_state)
+ return dynalloc_state::unknown;
+
+ if (dyn_alloc_state == std::string ("unchecked"))
+ return dynalloc_state::unchecked;
+
+ if (dyn_alloc_state == std::string ("nonnull"))
+ return dynalloc_state::nonnull;
+
+ if (dyn_alloc_state == std::string ("freed"))
+ return dynalloc_state::freed;
+
+ return dynalloc_state::unknown;
+}
+
+class state_diagram : public dot::graph
+{
+public:
+ state_diagram (const xml::document &input_state_doc)
+ : m_next_id (0),
+ m_show_tags (false)
+ {
+ // "node [shape=plaintext]\n"
+ {
+ auto attr_stmt
+ = std::make_unique<dot::attr_stmt> (dot::attr_stmt::kind::node);
+ attr_stmt->m_attrs.add (dot::id ("shape"), dot::id ("plaintext"));
+ add_stmt (std::move (attr_stmt));
+ }
+
+ /* Recurse down the XML state diagram, creating subgraphs
+ and then eventually creating nodes, and recursively
+ creating XML tables, adding ports for the endpoints of edges,
+ and recording edges we'll want to create (into m_pending_edges). */
+ xml::element *input_elmt_state_diagram
+ = input_state_doc.find_child_element ("state-diagram");
+ gcc_assert (input_elmt_state_diagram);
+ xml::element *input_elmt_mem_regions
+ = input_elmt_state_diagram->find_child_element ("memory-regions");
+ if (!input_elmt_mem_regions)
+ return;
+ auto root_cluster
+ = std::make_unique<dot::subgraph> (dot::id ("cluster_memory_regions"));
+ for (auto &iter : input_elmt_mem_regions->m_children)
+ on_input_xml_node (*root_cluster, *iter);
+ add_stmt (std::move (root_cluster));
+
+ /* We should now have ports for edge endpoints for all region ids.
+ Use them now to create edges. */
+ for (auto &pe : m_pending_edges)
+ {
+ auto search = m_region_id_to_dst_node_id.find (pe.m_dst_region_id);
+ if (search != m_region_id_to_dst_node_id.end ())
+ {
+ auto &dst_node_id = search->second;
+ auto e = std::make_unique<dot::edge_stmt> (pe.m_src_node_id,
+ dst_node_id);
+
+ auto dynalloc_state = m_region_id_to_dynalloc_state.find (pe.m_dst_region_id);
+ if (dynalloc_state != m_region_id_to_dynalloc_state.end ())
+ set_color_for_dynalloc_state (e->m_attrs,
+ dynalloc_state->second);
+
+ add_stmt (std::move (e));
+ }
+ }
+ }
+
+private:
+ struct pending_edge
+ {
+ dot::node_id m_src_node_id;
+ std::string m_dst_region_id;
+ };
+
+ dot::id
+ get_id_for_region (const char *region_id)
+ {
+ gcc_assert (region_id);
+ return std::string ("cluster_region_") + region_id;
+ }
+
+ dot::id
+ make_id (bool cluster = false)
+ {
+ if (cluster)
+ return std::string ("cluster_") + std::to_string (m_next_id++);
+ else
+ return std::string ("id_") + std::to_string (m_next_id++);
+ }
+
+ bool
+ starts_node_p (const xml::element &e)
+ {
+ if (e.m_kind == "stack"
+ || e.m_kind == "heap-buffer"
+ || e.m_kind == "variable") // e.g. within globals
+ return true;
+ return false;
+ }
+
+ void
+ on_input_xml_node (dot::subgraph &parent_subgraph,
+ xml::node &input_node)
+ {
+ xml::element *input_element = input_node.dyn_cast_element ();
+ if (!input_element)
+ return;
+
+ dot::id sg_id = make_id (true);
+
+ if (starts_node_p (*input_element))
+ {
+ // Create node with table
+ xml::element table ("table", false);
+ xml::printer xp (table);
+ xp.set_attr ("border", "0");
+ xp.set_attr ("cellborder", "1");
+ xp.set_attr ("cellspacing", "0");
+
+ const int max_depth = get_depth (*input_element);
+ const int num_columns = max_depth + 2;
+
+ dot::id id_of_node = make_id ();
+ on_xml_node (id_of_node, xp, *input_element,
+ max_depth, 0, num_columns);
+
+ auto node = std::make_unique<dot::node_stmt> (std::move (id_of_node));
+ node->m_attrs.add (dot::id ("shape"),
+ dot::id ("plaintext"));
+
+ // xml must be done by now
+
+ node->m_attrs.add (dot::id ("label"),
+ dot::id (table));
+
+ parent_subgraph.m_stmt_list.add_stmt (std::move (node));
+ }
+ else
+ {
+ auto child_subgraph = std::make_unique<dot::subgraph> (std::move (sg_id));
+
+ if (const char *label = input_element->get_attr ("label"))
+ child_subgraph->add_attr (dot::id ("label"), dot::id (label));
+
+ // recurse:
+ for (auto &iter : input_element->m_children)
+ on_input_xml_node (*child_subgraph, *iter);
+ parent_subgraph.m_stmt_list.add_stmt (std::move (child_subgraph));
+ }
+ }
+
+ enum class style { h1, h2 };
+
+ void
+ add_title_tr (const dot::id &id_of_node,
+ xml::printer &xp,
+ int num_columns,
+ const xml::element &input_element,
+ std::string heading,
+ enum style style,
+ enum dynalloc_state dynalloc_state)
+ {
+ xp.push_tag ("tr", true);
+ xp.push_tag ("td", false);
+ xp.set_attr ("colspan", std::to_string (num_columns));
+ xp.set_attr ("cellpadding", "5");
+
+ const char *bgcolor;
+ const char *color;
+ if (const char *c = get_color_for_dynalloc_state (dynalloc_state))
+ {
+ bgcolor = c;
+ color = "white";
+ }
+ else
+ switch (style)
+ {
+ default:
+ gcc_unreachable ();
+ case style::h1:
+ // from diagnostic-format-html.cc: HTML_STYLE .linenum
+ bgcolor = "#0088ce";
+ color = "white";
+ break;
+ case style::h2:
+ // from diagnostic-format-html.cc: HTML_STYLE .events-hdr
+ bgcolor = "#393f44"; // pf-black-800
+ color = "white";
+ break;
+ }
+
+ xp.set_attr ("bgcolor", bgcolor);
+ xp.push_tag ("font", false);
+ xp.set_attr ("color", color);
+ if (heading == "")
+ heading = " ";
+ xp.add_text (std::move (heading));
+ xp.pop_tag ("font");
+
+ maybe_add_dst_port (id_of_node, xp, input_element);
+
+ xp.pop_tag ("td");
+ xp.pop_tag ("tr");
+ }
+
+ /* Recursively add <TR> to XP for INPUT_NODE and its descendents. */
+ void
+ on_xml_node (const dot::id &id_of_node,
+ xml::printer &xp,
+ xml::node &input_node,
+ int max_depth,
+ int depth,
+ int num_columns)
+ {
+ bool recurse = true;
+
+ xml::element *input_element = input_node.dyn_cast_element ();
+ if (!input_element)
+ return;
+
+ if (input_element->m_kind == "concrete-bindings")
+ return;
+ if (input_element->m_kind == "padding")
+ return;
+
+ if (input_element->m_kind == "stack")
+ {
+ add_title_tr (id_of_node, xp, num_columns, *input_element, "Stack",
+ style::h1, dynalloc_state::unknown);
+ }
+ else if (input_element->m_kind == "stack-frame")
+ {
+ if (const char *function = input_element->get_attr ("function"))
+ add_title_tr (id_of_node, xp, num_columns, *input_element,
+ std::string ("Frame: ") + function,
+ style::h2, dynalloc_state::unknown);
+ }
+ else if (input_element->m_kind == "heap-buffer")
+ {
+ const char *extents = input_element->get_attr ("dynamic-extents");
+ enum dynalloc_state dynalloc_state = get_dynalloc_state (*input_element);
+ if (auto region_id = input_element->get_attr ("region_id"))
+ m_region_id_to_dynalloc_state[region_id] = dynalloc_state;
+ const char *type = input_element->get_attr ("type");
+ pretty_printer pp;
+ switch (dynalloc_state)
+ {
+ default:
+ gcc_unreachable ();
+
+ case dynalloc_state::unknown:
+ case dynalloc_state::nonnull:
+ if (type)
+ {
+ if (extents)
+ pp_printf (&pp, "%s (%s byte allocation)",
+ type, extents);
+ else
+ pp_printf (&pp, "%s", type);
+ }
+ else
+ {
+ if (extents)
+ pp_printf (&pp, "%s byte allocation",
+ extents);
+ }
+ break;
+
+ case dynalloc_state::unchecked:
+ if (type)
+ {
+ if (extents)
+ pp_printf (&pp, "%s (unchecked %s byte allocation)",
+ type, extents);
+ }
+ else
+ {
+ if (extents)
+ pp_printf (&pp, "Unchecked %s byte allocation",
+ extents);
+ }
+ break;
+
+ case dynalloc_state::freed:
+ // TODO: show deallocator
+ // TODO: show deallocation event
+ pp_printf (&pp, "Freed buffer");
+ break;
+ }
+ add_title_tr (id_of_node, xp, num_columns, *input_element,
+ pp_formatted_text (&pp),
+ style::h2,
+ dynalloc_state);
+ }
+ else
+ {
+ xp.push_tag ("tr", true);
+ if (depth > 0)
+ {
+ /* Indent, by create a <td> spanning "depth" columns. */
+ xp.push_tag ("td", false);
+ xp.set_attr ("colspan", std::to_string (depth));
+ xp.add_text (" "); // graphviz doesn't like <td/>
+ xp.pop_tag ("td");
+ }
+ if (m_show_tags)
+ {
+ // Debug: show XML tag
+ xp.push_tag ("td", false);
+ xp.add_text ("<");
+ xp.add_text (input_element->m_kind);
+ xp.add_text (">");
+ xp.pop_tag ("td");
+ }
+ if (input_element->m_kind == "variable")
+ {
+ const char *name = input_element->get_attr ("name");
+ gcc_assert (name);
+ xp.push_tag ("td", false);
+ maybe_add_dst_port (id_of_node, xp, *input_element);
+ push_src_text (xp);
+ xp.add_text (name);
+ pop_src_text (xp);
+ xp.pop_tag ("td");
+ }
+ else if (input_element->m_kind == "element")
+ {
+ const char *index = input_element->get_attr ("index");
+ gcc_assert (index);
+ xp.push_tag ("td", false);
+ maybe_add_dst_port (id_of_node, xp, *input_element);
+ push_src_text (xp);
+ xp.add_text ("[");
+ xp.add_text (index);
+ xp.add_text ("]");
+ pop_src_text (xp);
+ xp.pop_tag ("td");
+ }
+ else if (input_element->m_kind == "field")
+ {
+ const char *name = input_element->get_attr ("name");
+ gcc_assert (name);
+ xp.push_tag ("td", false);
+ maybe_add_dst_port (id_of_node, xp, *input_element);
+ push_src_text (xp);
+ xp.add_text (".");
+ xp.add_text (name);
+ pop_src_text (xp);
+ xp.pop_tag ("td");
+ }
+ if (const char *type = input_element->get_attr ("type"))
+ {
+ xp.push_tag ("td", false);
+ if (max_depth > depth)
+ xp.set_attr ("colspan", std::to_string (max_depth - depth));
+ xp.set_attr ("align", "right");
+ push_src_text (xp);
+ xp.add_text (type);
+ pop_src_text (xp);
+ xp.pop_tag ("td");
+ }
+ if (auto value = input_element->find_child_element ("value-of-region"))
+ {
+ xp.push_tag ("td", false);
+ for (auto &iter : value->m_children)
+ if (auto child_element = iter->dyn_cast_element ())
+ print_value (id_of_node, xp, *child_element);
+ xp.pop_tag ("td");
+ recurse = false;
+ }
+ xp.pop_tag ("tr");
+ }
+
+ if (recurse)
+ for (auto &iter : input_element->m_children)
+ on_xml_node (id_of_node, xp, *iter, max_depth, depth + 1, num_columns);
+ }
+
+ void
+ push_src_text (xml::printer &xp)
+ {
+ xp.push_tag ("font");
+ xp.set_attr ("color", "blue");
+ }
+
+ void
+ pop_src_text (xml::printer &xp)
+ {
+ xp.pop_tag ("font");
+ }
+
+ void
+ print_value (const dot::id &id_of_node,
+ xml::printer &xp,
+ xml::element &input_element)
+ {
+ if (input_element.m_kind == "pointer-to-region")
+ if (const char *dst_region_id = input_element.get_attr ("region_id"))
+ {
+ dot::id src_port_id = make_id ();
+ xp.set_attr ("port", src_port_id.m_str);
+ m_pending_edges.push_back
+ ({dot::node_id (id_of_node,
+ dot::port (src_port_id,
+ dot::compass_pt::e)),
+ dst_region_id});
+ }
+
+ if (input_element.m_kind == "uninitialized")
+ {
+ xp.add_text ("(uninitialized)");
+ return;
+ }
+
+ if (auto dump_text = input_element.get_attr ("dump-text"))
+ xp.add_text (dump_text);
+ }
+
+ /* If INPUT_ELEMENT has a "region_id", add a port to XP for possible
+ incoming edges to use. */
+
+ void
+ maybe_add_dst_port (const dot::id &id_of_node,
+ xml::printer &xp,
+ const xml::element &input_element)
+ {
+ if (const char *region_id = input_element.get_attr ("region_id"))
+ {
+ dot::id dst_id = make_id ();
+ dot::node_id node_id (id_of_node,
+ dot::port (dst_id/*,
+ dot::compass_pt::w*/));
+ xp.set_attr ("port", dst_id.m_str);
+ m_region_id_to_dst_node_id.emplace (std::string (region_id),
+ std::move (node_id));
+ }
+ }
+
+
+private:
+ int m_next_id;
+ std::vector<pending_edge> m_pending_edges;
+ std::map<std::string, dot::node_id> m_region_id_to_dst_node_id;
+ std::map<std::string, enum dynalloc_state> m_region_id_to_dynalloc_state;
+ bool m_show_tags;
+};
+
+std::unique_ptr<dot::graph>
+make_dot_graph_from_xml_state (const xml::document &xml_state)
+{
+ return std::make_unique<state_diagram> (xml_state);
+}
--- /dev/null
+/* Capturing changing state in diagnostic paths.
+ Copyright (C) 2025 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#ifndef GCC_DIAGNOSTIC_STATE_H
+#define GCC_DIAGNOSTIC_STATE_H
+
+/* We want to be able to express changing program states in diagnostic paths,
+ so that we can emit this in HTML and SARIF output, and to keep this
+ separate from implementation details of -fanalyzer.
+
+ For now, we use xml::document as the type in the diagnostic subsystem
+ for (optionally) tracking the state at a diagnostic_event. */
+
+namespace xml { class document; }
+namespace dot { class graph; }
+
+extern std::unique_ptr<dot::graph>
+make_dot_graph_from_xml_state (const xml::document &xml_state);
+
+#endif /* GCC_DIAGNOSTIC_STATE_H */
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
+#define INCLUDE_STRING
+#define INCLUDE_VECTOR
#include "config.h"
#include "system.h"
#include "coretypes.h"
@item @code{-fdump-analyzer-exploded-nodes-2}
which dumps a @file{SRC.eg.txt} file containing the full @code{exploded_graph}.
+@item @code{-fdiagnostics-add-output=experimental-html:show-state-diagrams=yes}
+which writes out the diagnostics in HTML form, and generates SVG state
+diagrams visualizing the state of memory at each event (inspired by the
+"ddd" debugger). These can be seen by pressing 'j' and 'k' to single-step
+forward and backward through events. Note that these SVG diagrams are
+created from an intermediate XML representation generated from
+@code{program_state} objects. The XML representation can be easier to
+read - for example, rather than storing the contents of memory via byte
+offsets, it uses fields for structs and element indexes for arrays,
+recursively. However it is a different representation, and thus bugs could
+be hidden by this transformation. Generating the SVG diagrams requires
+an invocation of "dot" per event, so it noticeably slows down diagnostic
+emission, hence the opt-in command-line flag. The XML and ``dot''
+representations can be seen by @code{__analyzer_dump_xml} and
+@code{__analyzer_dump_dot} below (writing them to stderr), or by adding
+@code{show-state-diagrams-xml=yes} and
+@code{show-state-diagrams-dot-src=yes} to the html sink, which shows
+them within the generated HTML next to the generated SVG.
+
@end itemize
Assuming that you have the
will emit a warning describing the capacity of the base region of
the region pointed to by the 1st argument.
+@item __analyzer_dump_dot
+@smallexample
+__analyzer_dump_dot ();
+@end smallexample
+
+will dump GraphViz .dot source to stderr reaches the call in its
+traversal of the source. This .dot source implements a diagram
+describing the analyzer’s state.
+
@item __analyzer_dump_escaped
@smallexample
extern void __analyzer_dump_escaped (void);
a name matching the 1st argument (which must be a string literal).
This is for use when debugging, and may be of use in DejaGnu tests.
+@item __analyzer_dump_xml
+@smallexample
+__analyzer_dump_xml ();
+@end smallexample
+
+will dump the copious information about the analyzer's state each time it
+reaches the call in its traversal of the source.
+
@item __analyzer_eval
@smallexample
__analyzer_eval (expr);
@end table
+There is also this key intended for use by GCC developers, rather than
+end-users, and subject to change or removal without notice:
+
+@table @gcctabopt
+
+@item xml-state=@r{[}yes@r{|}no@r{]}
+This is a debugging feature and defaults to @code{no}.
+If @code{xml-state=yes}, then attempt to capture detailed state information
+from @option{-fanalyzer} in the generated SARIF.
+
+@end table
+
@item experimental-html
Emit diagnostics to a file in HTML format. This scheme is experimental,
and may go away in future GCC releases. The keys and details of the output
@end table
+There are also these keys intended for use by GCC developers, rather than
+end-users, and subject to change or removal without notice:
+
+@table @gcctabopt
+
+@item show-state-diagrams=@r{[}yes@r{|}no@r{]}
+This is a debugging feature and defaults to @code{no}.
+If @code{show-state-diagrams=yes}, then attempt to use @command{dot} to
+generate SVG diagrams in the generated HTML, visualizing the state at each
+event in a diagnostic path.
+These are visible by pressing ``j'' and ``k'' to single-step forward and
+backward through events. Enabling this option will slow down
+HTML generation.
+
+@item show-state-diagrams-dot-src=@r{[}yes@r{|}no@r{]}
+This is a debugging feature and defaults to @code{no}.
+If @code{show-state-diagrams-dot-src=yes}
+then if @code{show-state-diagrams=yes},
+the generated state diagrams will also show the .dot source input to
+GraphViz used for the diagram.
+
+@item show-state-diagrams-xml=@r{[}yes@r{|}no@r{]}
+This is a debugging feature and defaults to @code{no}.
+If @code{show-state-diagrams-xml=yes}
+then if @code{show-state-diagrams=yes}, the generated state diagrams will
+also show an XML representation of the state.
+
+@end table
+
@end table
For example,
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
+#define INCLUDE_MAP
+#define INCLUDE_STRING
+#define INCLUDE_VECTOR
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "graphviz.h"
+#include "xml.h"
+#include "xml-printer.h"
+#include "pex.h"
+#include "selftest.h"
-/* graphviz_out's ctor, wrapping PP. */
-
-graphviz_out::graphviz_out (pretty_printer *pp)
+dot::writer::writer (pretty_printer &pp)
: m_pp (pp),
m_indent (0)
{
}
+/* Print the current indent to the underlying pp. */
+
+void
+dot::writer::write_indent ()
+{
+ for (int i = 0; i < m_indent * 4; ++i)
+ pp_space (get_pp ());
+}
+
+graphviz_out::graphviz_out (pretty_printer *pp)
+: writer (*pp)
+{
+ gcc_assert (pp);
+}
+
/* Formatted print of FMT. */
void
va_start (ap, fmt);
text_info text (fmt, &ap, errno);
- pp_format (m_pp, &text);
- pp_output_formatted_text (m_pp);
+ pp_format (get_pp (), &text);
+ pp_output_formatted_text (get_pp ());
va_end (ap);
}
va_start (ap, fmt);
text_info text (fmt, &ap, errno);
- pp_format (m_pp, &text);
- pp_output_formatted_text (m_pp);
+ pp_format (get_pp (), &text);
+ pp_output_formatted_text (get_pp ());
va_end (ap);
- pp_newline (m_pp);
-}
-
-/* Print the current indent to the underlying pp. */
-
-void
-graphviz_out::write_indent ()
-{
- for (int i = 0; i < m_indent * 2; ++i)
- pp_space (m_pp);
+ pp_newline (get_pp ());
}
/* Write the start of an HTML-like row via <TR>, writing to the stream
void
graphviz_out::begin_tr ()
{
- pp_string (m_pp, "<TR>");
- pp_write_text_to_stream (m_pp);
+ pp_string (get_pp (), "<TR>");
+ pp_write_text_to_stream (get_pp ());
}
/* Write the end of an HTML-like row via </TR>, writing to the stream
void
graphviz_out::end_tr ()
{
- pp_string (m_pp, "</TR>");
- pp_write_text_to_stream (m_pp);
+ pp_string (get_pp (), "</TR>");
+ pp_write_text_to_stream (get_pp ());
}
/* Write the start of an HTML-like <TD>, writing to the stream
void
graphviz_out::begin_td ()
{
- pp_string (m_pp, "<TD ALIGN=\"LEFT\">");
- pp_write_text_to_stream (m_pp);
+ pp_string (get_pp (), "<TD ALIGN=\"LEFT\">");
+ pp_write_text_to_stream (get_pp ());
}
/* Write the end of an HTML-like </TD>, writing to the stream
void
graphviz_out::end_td ()
{
- pp_string (m_pp, "</TD>");
- pp_write_text_to_stream (m_pp);
+ pp_string (get_pp (), "</TD>");
+ pp_write_text_to_stream (get_pp ());
}
/* Write the start of an HTML-like row via <TR><TD>, writing to the stream
void
graphviz_out::begin_trtd ()
{
- pp_string (m_pp, "<TR><TD ALIGN=\"LEFT\">");
- pp_write_text_to_stream (m_pp);
+ pp_string (get_pp (), "<TR><TD ALIGN=\"LEFT\">");
+ pp_write_text_to_stream (get_pp ());
}
/* Write the end of an HTML-like row via </TD></TR>, writing to the stream
void
graphviz_out::end_tdtr ()
{
- pp_string (m_pp, "</TD></TR>");
- pp_write_text_to_stream (m_pp);
+ pp_string (get_pp (), "</TD></TR>");
+ pp_write_text_to_stream (get_pp ());
+}
+
+namespace dot {
+
+// Definitions
+
+// struct ast_node
+
+void
+ast_node::dump () const
+{
+ pretty_printer pp;
+ pp.set_output_stream (stderr);
+ writer w (pp);
+ print (w);
+ pp_newline (&pp);
+ pp_flush (&pp);
+}
+
+// struct id
+
+id::id (std::string str)
+: m_str (std::move (str)),
+ m_kind (is_identifier_p (m_str.c_str ())
+ ? kind::identifier
+ : kind::quoted)
+{
+}
+
+id::id (const xml::node &n)
+: m_kind (kind::html)
+{
+ pretty_printer pp;
+ n.write_as_xml (&pp, 0, true);
+ m_str = pp_formatted_text (&pp);
+}
+
+void
+id::print (writer &w) const
+{
+ switch (m_kind)
+ {
+ default:
+ gcc_unreachable ();
+
+ case kind::identifier:
+ w.write_string (m_str.c_str ());
+ break;
+
+ case kind::quoted:
+ w.write_character ('"');
+ for (auto ch : m_str)
+ if (ch == '"')
+ w.write_string ("\\\"");
+ else
+ w.write_character (ch);
+ w.write_character ('"');
+ break;
+
+ case kind::html:
+ w.write_character ('<');
+ w.write_string (m_str.c_str ());
+ w.write_character ('>');
+ break;
+
+ }
+}
+
+bool
+id::is_identifier_p (const char *str)
+{
+ const char initial_ch = *str;
+ if (initial_ch != '_' && !ISALPHA (initial_ch))
+ return false;
+ for (const char *iter = str + 1; *iter; ++iter)
+ {
+ const char iter_ch = *iter;
+ if (iter_ch != '_' && !ISALNUM (iter_ch))
+ return false;
+ }
+ return true;
+}
+
+// struct kv_pair
+
+void
+kv_pair::print (writer &w) const
+{
+ m_key.print (w);
+ w.write_character ('=');
+ m_value.print (w);
+}
+
+// struct attr_list
+
+void
+attr_list::print (writer &w) const
+{
+ if (m_kvs.empty ())
+ return;
+ w.write_string (" [");
+ for (auto iter = m_kvs.begin (); iter != m_kvs.end (); ++iter)
+ {
+ if (iter != m_kvs.begin ())
+ w.write_string ("; ");
+ iter->print (w);
+ }
+ w.write_string ("]");
+}
+
+// struct stmt_list
+
+void
+stmt_list::print (writer &w) const
+{
+ for (auto &stmt : m_stmts)
+ {
+ w.write_indent ();
+ stmt->print (w);
+ w.write_string (";");
+ w.write_newline ();
+ }
+}
+
+void
+stmt_list::add_edge (node_id src_id, node_id dst_id)
+{
+ m_stmts.push_back
+ (std::make_unique<dot::edge_stmt>
+ (std::move (src_id), std::move (dst_id)));
+}
+
+void
+stmt_list::add_attr (id key, id value)
+{
+ add_stmt
+ (std::make_unique <kv_stmt> (kv_pair (std::move (key),
+ std::move (value))));
+}
+
+// struct graph
+
+void
+graph::print (writer &w) const
+{
+ w.write_indent ();
+ w.write_string ("digraph ");
+ if (m_id)
+ {
+ m_id->print (w);
+ w.write_character (' ');
+ }
+ w.write_string ("{");
+ w.write_newline ();
+
+ w.indent ();
+ m_stmt_list.print (w);
+ w.outdent ();
+
+ w.write_indent ();
+ w.write_string ("}");
+ w.write_newline ();
+}
+
+// struct stmt_with_attr_list : public stmt
+
+void
+stmt_with_attr_list::set_label (dot::id value)
+{
+ m_attrs.add (dot::id ("label"), std::move (value));
+}
+
+// struct node_stmt : public stmt_with_attr_list
+
+void
+node_stmt::print (writer &w) const
+{
+ m_id.print (w);
+ m_attrs.print (w);
+}
+
+// struct attr_stmt : public stmt_with_attr_list
+
+void
+attr_stmt::print (writer &w) const
+{
+ switch (m_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case kind::graph:
+ w.write_string ("graph");
+ break;
+ case kind::node:
+ w.write_string ("node");
+ break;
+ case kind::edge:
+ w.write_string ("edge");
+ break;
+ }
+ m_attrs.print (w);
+}
+
+// struct kv_stmt : public stmt
+
+void
+kv_stmt::print (writer &w) const
+{
+ m_kv.print (w);
+}
+
+// struct node_id
+
+void
+node_id::print (writer &w) const
+{
+ m_id.print (w);
+ if (m_port)
+ m_port->print (w);
+}
+
+// struct port
+
+void
+port::print (writer &w) const
+{
+ if (m_id)
+ {
+ w.write_character (':');
+ m_id->print (w);
+ }
+ if (m_compass_pt)
+ {
+ w.write_character (':');
+ switch (*m_compass_pt)
+ {
+ default:
+ gcc_unreachable ();
+ case compass_pt::n:
+ w.write_string ("n");
+ break;
+ case compass_pt::ne:
+ w.write_string ("ne");
+ break;
+ case compass_pt::e:
+ w.write_string ("e");
+ break;
+ case compass_pt::se:
+ w.write_string ("se");
+ break;
+ case compass_pt::s:
+ w.write_string ("s");
+ break;
+ case compass_pt::sw:
+ w.write_string ("sw");
+ break;
+ case compass_pt::w:
+ w.write_string ("w");
+ break;
+ case compass_pt::nw:
+ w.write_string ("nw");
+ break;
+ case compass_pt::c:
+ w.write_string ("c");
+ break;
+ }
+ }
+}
+
+// struct edge_stmt : public stmt_with_attr_list
+
+void
+edge_stmt::print (writer &w) const
+{
+ for (auto iter = m_node_ids.begin (); iter != m_node_ids.end (); ++iter)
+ {
+ if (iter != m_node_ids.begin ())
+ w.write_string (" -> ");
+ iter->print (w);
+ }
+ m_attrs.print (w);
+}
+
+// struct subgraph : public stmt
+
+void
+subgraph::print (writer &w) const
+{
+ w.write_newline ();
+ w.write_indent ();
+ w.write_string ("subgraph ");
+ m_id.print (w);
+ w.write_string (" {");
+ w.write_newline ();
+
+ w.indent ();
+ m_stmt_list.print (w);
+ w.outdent ();
+ w.write_newline ();
+
+ w.write_indent ();
+ w.write_string ("}"); // newline and semicolon added by stmt_list
+}
+
+/* Convert G to graphviz source, attempt to invoke "dot -Tsvg" on it
+ as a subprocess, and get the SVG source from stdout, or nullptr
+ if there was a problem. */
+
+static std::unique_ptr<std::string>
+make_svg_document_buffer_from_graph (const graph &g)
+{
+ /* Ideally there would be a way of doing this without
+ invoking dot as a subprocess. */
+
+ std::vector<std::string> args;
+ args.push_back ("dot");
+ args.push_back ("-Tsvg");
+
+ pex p (0, "dot", nullptr);
+
+ {
+ auto pipe_stdin = p.input_file (true, nullptr);
+ gcc_assert (pipe_stdin.m_file);
+ pretty_printer pp;
+ pp.set_output_stream (pipe_stdin.m_file);
+ writer w (pp);
+ g.print (w);
+ pp_flush (&pp);
+ }
+
+ int err = 0;
+ const char * errmsg
+ = p.run (PEX_SEARCH,
+ "dot", args, nullptr, nullptr, &err);
+ auto pipe_stdout = p.read_output ();
+ auto content = pipe_stdout.read_all ();
+
+ if (errmsg)
+ return nullptr;
+ if (err)
+ return nullptr;
+
+ std::string result;
+ result.reserve (content->size () + 1);
+ for (auto &iter : *content)
+ result.push_back (iter);
+ return std::make_unique<std::string> (std::move (result));
+}
+
+/* Convert G to graphviz source, attempt to invoke "dot -Tsvg" on it
+ as a subprocess, and get the SVG source from stdout, and extract
+ the "svg" subtree as an xml::raw node.
+
+ Note that this
+ (a) invokes "dot" as a subprocess
+ (b) assumes that we trust the output from "dot".
+
+ Return nullptr if there was a problem. */
+
+std::unique_ptr<xml::node>
+make_svg_from_graph (const graph &g)
+{
+ auto svg_src = make_svg_document_buffer_from_graph (g);
+ if (!svg_src)
+ return nullptr;
+
+ /* Skip past the XML header to the parts we care about. */
+ auto pos = svg_src->find ("<!-- Generated by graphviz");
+ if (pos == svg_src->npos)
+ return nullptr;
+
+ auto substring = std::string (*svg_src, pos);
+ return std::make_unique<xml::raw> (std::move (substring));
+}
+
+} // namespace dot
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+test_ids ()
+{
+ ASSERT_TRUE (dot::id::is_identifier_p ("foo"));
+ ASSERT_FALSE (dot::id::is_identifier_p ("hello world"));
+ ASSERT_TRUE (dot::id::is_identifier_p ("foo42"));
+ ASSERT_FALSE (dot::id::is_identifier_p ("42"));
+ ASSERT_TRUE (dot::id::is_identifier_p ("_"));
+}
+
+static void
+test_trivial_graph ()
+{
+ dot::graph g;
+ // node "a"
+ {
+ g.add_stmt (std::make_unique<dot::node_stmt> (dot::id ("a")));
+ }
+ // node "b"
+ {
+ auto n = std::make_unique<dot::node_stmt> (dot::id ("b"));
+ n->m_attrs.add (dot::id ("label"), dot::id ("This is node b"));
+ n->m_attrs.add (dot::id ("color"), dot::id ("green"));
+ g.add_stmt (std::move (n));
+ }
+ // an edge between them
+ {
+ auto e = std::make_unique<dot::edge_stmt> (dot::id ("a"),
+ dot::id ("b"));
+ e->m_attrs.add (dot::id ("label"), dot::id ("I'm an edge"));
+ g.add_stmt (std::move (e));
+ }
+ pretty_printer pp;
+ dot::writer w (pp);
+ g.print (w);
+ ASSERT_STREQ
+ (pp_formatted_text (&pp),
+ ("digraph {\n"
+ " a;\n"
+ " b [label=\"This is node b\"; color=green];\n"
+ " a -> b [label=\"I'm an edge\"];\n"
+ "}\n"));
+}
+
+/* Recreating the HTML record example from
+ https://graphviz.org/doc/info/shapes.html#html */
+
+static void
+test_layout_example ()
+{
+ dot::graph g (dot::id ("structs"));
+
+ // "node [shape=plaintext]\n"
+ {
+ auto attr_stmt
+ = std::make_unique<dot::attr_stmt> (dot::attr_stmt::kind::node);
+ attr_stmt->m_attrs.add (dot::id ("shape"), dot::id ("plaintext"));
+ g.add_stmt (std::move (attr_stmt));
+ }
+
+ // struct1
+ {
+ auto n = std::make_unique<dot::node_stmt> (dot::id ("struct1"));
+
+ xml::element table ("TABLE", false);
+ xml::printer xp (table);
+ xp.set_attr ("BORDER", "0");
+ xp.set_attr ("CELLBORDER", "1");
+ xp.set_attr ("CELLSPACING", "0");
+
+ xp.push_tag ("TR", true);
+
+ xp.push_tag ("TD", false);
+ xp.add_text ("left");
+ xp.pop_tag ("TD");
+
+ xp.push_tag ("TD", false);
+ xp.set_attr ("PORT", "f1");
+ xp.add_text ("mid dle");
+ xp.pop_tag ("TD");
+
+ xp.push_tag ("TD", false);
+ xp.set_attr ("PORT", "f2");
+ xp.add_text ("right");
+ xp.pop_tag ("TD");
+
+ n->set_label (table);
+ g.add_stmt (std::move (n));
+ }
+
+ // struct2
+ {
+ auto n = std::make_unique<dot::node_stmt> (dot::id ("struct2"));
+ xml::element table ("TABLE", false);
+ xml::printer xp (table);
+ xp.set_attr ("BORDER", "0");
+ xp.set_attr ("CELLBORDER", "1");
+ xp.set_attr ("CELLSPACING", "0");
+
+ xp.push_tag ("TR", true);
+
+ xp.push_tag ("TD", false);
+ xp.set_attr ("PORT", "f0");
+ xp.add_text ("one");
+ xp.pop_tag ("TD");
+
+ xp.push_tag ("TD", false);
+ xp.add_text ("two");
+ xp.pop_tag ("TD");
+
+ n->set_label (table);
+ g.add_stmt (std::move (n));
+ }
+
+ // struct3
+ {
+ auto n = std::make_unique<dot::node_stmt> (dot::id ("struct3"));
+ xml::element table ("TABLE", false);
+ xml::printer xp (table);
+ xp.set_attr ("BORDER", "0");
+ xp.set_attr ("CELLBORDER", "1");
+ xp.set_attr ("CELLSPACING", "0");
+ xp.set_attr ("CELLPADDING", "4");
+
+ xp.push_tag ("TR", false);
+
+ xp.push_tag ("TD", true);
+ xp.set_attr ("ROWSPAN", "3");
+ xp.add_text ("hello");
+ xp.append (std::make_unique<xml::element> ("BR", false));
+ xp.add_text ("world");
+ xp.pop_tag ("TD");
+
+ xp.push_tag ("TD", true);
+ xp.set_attr ("COLSPAN", "3");
+ xp.add_text ("b");
+ xp.pop_tag ("TD");
+
+ xp.push_tag ("TD", true);
+ xp.set_attr ("ROWSPAN", "3");
+ xp.add_text ("g");
+ xp.pop_tag ("TD");
+
+ xp.push_tag ("TD", true);
+ xp.set_attr ("ROWSPAN", "3");
+ xp.add_text ("h");
+ xp.pop_tag ("TD");
+
+ xp.pop_tag ("TR");
+
+ xp.push_tag ("TR", false);
+
+ xp.push_tag ("TD", true);
+ xp.add_text ("c");
+ xp.pop_tag ("TD");
+
+ xp.push_tag ("TD", true);
+ xp.set_attr ("PORT", "here");
+ xp.add_text ("d");
+ xp.pop_tag ("TD");
+
+ xp.push_tag ("TD", true);
+ xp.add_text ("e");
+ xp.pop_tag ("TD");
+
+ xp.pop_tag ("TR");
+
+ xp.push_tag ("TR", false);
+
+ xp.push_tag ("TD", true);
+ xp.set_attr ("COLSPAN", "3");
+ xp.add_text ("f");
+ xp.pop_tag ("TD");
+
+ n->set_label (table);
+ g.add_stmt (std::move (n));
+ }
+
+ g.m_stmt_list.add_edge
+ (dot::node_id (dot::id ("struct1"),
+ dot::port (dot::id ("f1"))),
+ dot::node_id (dot::id ("struct2"),
+ dot::port (dot::id ("f0"))));
+ g.m_stmt_list.add_edge
+ (dot::node_id (dot::id ("struct1"),
+ dot::port (dot::id ("f2"))),
+ dot::node_id (dot::id ("struct3"),
+ dot::port (dot::id ("here"))));
+
+ pretty_printer pp;
+ dot::writer w (pp);
+ g.print (w);
+
+ /* There are some whitespace differences with the example in the
+ GraphViz docs. */
+ ASSERT_STREQ
+ (pp_formatted_text (&pp),
+ ("digraph structs {\n"
+ " node [shape=plaintext];\n" // added semicolon
+ " struct1 [label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n"
+ " <TR><TD>left</TD><TD PORT=\"f1\">mid dle</TD><TD PORT=\"f2\">right</TD></TR>\n"
+ "</TABLE>\n"
+ ">];\n"
+ " struct2 [label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n"
+ " <TR><TD PORT=\"f0\">one</TD><TD>two</TD></TR>\n"
+ "</TABLE>\n"
+ ">];\n"
+ " struct3 [label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n"
+ " <TR>\n"
+ " <TD ROWSPAN=\"3\">hello<BR/>world</TD>\n"
+ " <TD COLSPAN=\"3\">b</TD>\n"
+ " <TD ROWSPAN=\"3\">g</TD>\n"
+ " <TD ROWSPAN=\"3\">h</TD>\n"
+ " </TR>\n"
+ " <TR>\n"
+ " <TD>c</TD>\n"
+ " <TD PORT=\"here\">d</TD>\n"
+ " <TD>e</TD>\n"
+ " </TR>\n"
+ " <TR>\n"
+ " <TD COLSPAN=\"3\">f</TD>\n"
+ " </TR>\n"
+ "</TABLE>\n"
+ ">];\n"
+ " struct1:f1 -> struct2:f0;\n"
+ " struct1:f2 -> struct3:here;\n"
+ "}\n"));
+}
+
+/* Run all of the selftests within this file. */
+
+void
+graphviz_cc_tests ()
+{
+ test_ids ();
+ test_trivial_graph ();
+ test_layout_example ();
}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
#include "pretty-print.h" /* for ATTRIBUTE_GCC_PPDIAG. */
+namespace xml { class node; }
+
+namespace dot {
+
+/* A class for writing .dot output to a pretty_printer with
+ indentation to show nesting. */
+
+class writer {
+public:
+ writer (pretty_printer &pp);
+
+ void indent () { m_indent++; }
+ void outdent () { m_indent--; }
+
+ void write_indent ();
+
+ void write_character (char ch)
+ {
+ pp_character (&m_pp, ch);
+ }
+ void write_string (const char *str)
+ {
+ pp_string (&m_pp, str);
+ }
+ void write_newline ()
+ {
+ pp_newline (&m_pp);
+ }
+
+ pretty_printer *get_pp () const { return &m_pp; }
+
+ private:
+ pretty_printer &m_pp;
+ int m_indent;
+};
+
+// An AST for the dot language
+// See https://graphviz.org/doc/info/lang.html
+
+// Forward decls
+
+struct id;
+struct node_id;
+struct port;
+struct kv_pair;
+struct graph;
+struct attr_list;
+struct stmt_list;
+struct stmt;
+ struct node_stmt;
+ struct attr_stmt;
+ struct kv_stmt;
+ struct edge_stmt;
+ struct subgraph;
+
+// Decls
+
+struct ast_node
+{
+ virtual ~ast_node () {}
+ virtual void print (writer &w) const = 0;
+
+ void dump () const;
+};
+
+struct id : public ast_node
+{
+ enum class kind
+ {
+ identifier,
+ quoted,
+ html
+ };
+
+ id (std::string str);
+
+ /* For HTML labels: see https://graphviz.org/doc/info/shapes.html#html */
+ id (const xml::node &n);
+
+ void print (writer &w) const final override;
+
+ static bool is_identifier_p (const char *);
+
+ std::string m_str;
+ enum kind m_kind;
+};
+
+/* ID '=' ID */
+
+struct kv_pair : ast_node
+{
+ kv_pair (id key, id value)
+ : m_key (std::move (key)),
+ m_value (std::move (value))
+ {
+ }
+
+ void print (writer &w) const final override;
+
+ id m_key;
+ id m_value;
+};
+
+/* attr_list: '[' [ a_list ] ']' [ attr_list ] */
+
+struct attr_list : public ast_node
+{
+ void print (writer &w) const;
+ void add (id key, id value)
+ {
+ m_kvs.push_back ({std::move (key), std::move (value)});
+ }
+
+ std::vector<kv_pair> m_kvs;
+};
+
+/* stmt_list : [ stmt [ ';' ] stmt_list ] */
+
+struct stmt_list : public ast_node
+{
+ void print (writer &w) const final override;
+ void add_stmt (std::unique_ptr<stmt> s)
+ {
+ m_stmts.push_back (std::move (s));
+ }
+ void add_edge (node_id src_id, node_id dst_id);
+ void add_attr (id key, id value);
+
+ std::vector<std::unique_ptr<stmt>> m_stmts;
+};
+
+/* graph : [ strict ] (graph | digraph) [ ID ] '{' stmt_list '}' */
+
+struct graph : public ast_node
+{
+ graph ()
+ : m_id (nullptr)
+ {
+ }
+
+ graph (id id_)
+ : m_id (std::make_unique<id> (std::move (id_)))
+ {
+ }
+
+ void print (writer &w) const final override;
+
+ void add_stmt (std::unique_ptr<stmt> s)
+ {
+ m_stmt_list.add_stmt (std::move (s));
+ }
+
+ std::unique_ptr<id> m_id; // optional
+ stmt_list m_stmt_list;
+};
+
+/* Abstract base class.
+ stmt : node_stmt
+ | edge_stmt
+ | attr_stmt
+ | ID '=' ID ("kv_stmt")
+ | subgraph */
+
+struct stmt
+{
+ virtual ~stmt () {}
+ virtual void print (writer &w) const = 0;
+};
+
+struct stmt_with_attr_list : public stmt
+{
+ void set_attr (id key, id value)
+ {
+ m_attrs.add (std::move (key), std::move (value));
+ }
+ void set_label (dot::id label);
+
+ attr_list m_attrs;
+};
+
+struct node_stmt : public stmt_with_attr_list
+{
+ node_stmt (id id_)
+ : m_id (id_)
+ {
+ }
+
+ void print (writer &w) const final override;
+
+ id m_id;
+};
+
+struct attr_stmt : public stmt_with_attr_list
+{
+ enum class kind { graph, node, edge };
+
+ attr_stmt (enum kind kind_)
+ : m_kind (kind_)
+ {
+ }
+
+ void print (writer &w) const final override;
+
+ enum kind m_kind;
+};
+
+/* "ID '=' ID" as a stmt. */
+
+struct kv_stmt : public stmt
+{
+ kv_stmt (kv_pair kv)
+ : m_kv (std::move (kv))
+ {}
+
+ void print (writer &w) const final override;
+
+ kv_pair m_kv;
+};
+
+/* node_id : ID [ port ] */
+
+enum class compass_pt
+{
+ n, ne, e, se, s, sw, w, nw, c
+ /* "_" clashes with intl macro */
+};
+
+/* port : ':' ID [ ':' compass_pt ]
+ | ':' compass_pt
+*/
+
+struct port : public ast_node
+{
+ port (id id_)
+ : m_id (std::make_unique<id> (std::move (id_))),
+ m_compass_pt (nullptr)
+ {
+ }
+
+ port (enum compass_pt compass_pt_)
+ : m_id (nullptr),
+ m_compass_pt (std::make_unique<compass_pt> (compass_pt_))
+ {
+ }
+
+ port (id id_,
+ enum compass_pt compass_pt_)
+ : m_id (std::make_unique<id> (std::move (id_))),
+ m_compass_pt (std::make_unique<compass_pt> (compass_pt_))
+ {
+ }
+
+ port (const port &other)
+ : m_id (nullptr),
+ m_compass_pt (nullptr)
+ {
+ if (other.m_id)
+ m_id = std::make_unique<id> (*other.m_id);
+ if (other.m_compass_pt)
+ m_compass_pt = std::make_unique<enum compass_pt> (*other.m_compass_pt);
+ }
+
+ void print (writer &w) const final override;
+
+ std::unique_ptr<id> m_id; // would be std::optional
+ std::unique_ptr<enum compass_pt> m_compass_pt; // would be std::optional
+};
+
+struct node_id : public ast_node
+{
+ node_id (id id_)
+ : m_id (id_),
+ m_port (nullptr)
+ {
+ }
+ node_id (id id_, port port_)
+ : m_id (id_),
+ m_port (std::make_unique<port> (std::move (port_)))
+ {
+ }
+ node_id (const node_id &other)
+ : m_id (other.m_id),
+ m_port (nullptr)
+ {
+ if (other.m_port)
+ m_port = std::make_unique<port> (*other.m_port);
+ }
+
+ void print (writer &w) const final override;
+
+ id m_id;
+ std::unique_ptr<port> m_port; // would be std::optional
+};
+
+/* The full grammar for edge_stmt is:
+ edge_stmt : (node_id | subgraph) edgeRHS [ attr_list ]
+ edgeRHS : edgeop (node_id | subgraph) [ edgeRHS ]
+ This class support the subsets where all are "node_id", rather than
+ "subgraph", and doesn't yet support "port" giving effectively:
+ node_id (edgeop node_id)+ [ attr_list]
+ */
+
+struct edge_stmt : public stmt_with_attr_list
+{
+ edge_stmt (node_id src_id, node_id dst_id)
+ {
+ m_node_ids.push_back (std::move (src_id));
+ m_node_ids.push_back (std::move (dst_id));
+ }
+
+ void print (writer &w) const final override;
+
+ std::vector<node_id> m_node_ids; // should have 2 or more elements
+};
+
+/* [ subgraph [ ID ] ] '{' stmt_list '}' */
+
+struct subgraph : public stmt
+{
+ subgraph (id id_)
+ : m_id (id_)
+ {
+ }
+
+ void print (writer &w) const final override;
+
+ void add_stmt (std::unique_ptr<stmt> s)
+ {
+ m_stmt_list.add_stmt (std::move (s));
+ }
+ void add_attr (id key, id value)
+ {
+ m_stmt_list.add_stmt
+ (std::make_unique <kv_stmt> (kv_pair (std::move (key),
+ std::move (value))));
+ }
+
+ id m_id;
+ stmt_list m_stmt_list;
+};
+
+extern std::unique_ptr<xml::node>
+make_svg_from_graph (const graph &g);
+
+} // namespace dot
+
/* A class for writing .dot output to a pretty_printer with
indentation to show nesting. */
-class graphviz_out {
+class graphviz_out : public dot::writer {
public:
graphviz_out (pretty_printer *pp);
void println (const char *fmt, ...)
ATTRIBUTE_GCC_PPDIAG(2,3);
- void indent () { m_indent++; }
- void outdent () { m_indent--; }
-
- void write_indent ();
-
void begin_tr ();
void end_tr ();
void begin_trtd ();
void end_tdtr ();
-
- pretty_printer *get_pp () const { return m_pp; }
-
- private:
- pretty_printer *m_pp;
- int m_indent;
};
#endif /* GCC_GRAPHVIZ_H */
enum sarif_serialization_kind serialization_kind
= sarif_serialization_kind::json;
enum sarif_version version = sarif_version::v2_1_0;
+ bool xml_state = false;
for (auto& iter : parsed_arg.m_kvs)
{
const std::string &key = iter.first;
return nullptr;
continue;
}
+ if (key == "xml-state")
+ {
+ if (!parse_bool_value (ctxt, unparsed_arg, key, value,
+ xml_state))
+ return nullptr;
+ continue;
+ }
/* Key not found. */
auto_vec<const char *> known_keys;
known_keys.safe_push ("file");
known_keys.safe_push ("serialization");
known_keys.safe_push ("version");
+ known_keys.safe_push ("xml-state");
ctxt.report_unknown_key (unparsed_arg, key, get_scheme_name (),
known_keys);
return nullptr;
sarif_generation_options sarif_gen_opts;
sarif_gen_opts.m_version = version;
+ sarif_gen_opts.m_xml_state = xml_state;
std::unique_ptr<sarif_serialization_format> serialization_obj;
switch (serialization_kind)
bool css = true;
label_text filename;
bool javascript = true;
+ bool show_state_diagrams = false;
+ bool show_state_diagram_xml = false;
+ bool show_state_diagram_dot_src = false;
+
for (auto& iter : parsed_arg.m_kvs)
{
const std::string &key = iter.first;
return nullptr;
continue;
}
+ if (key == "show-state-diagrams")
+ {
+ if (!parse_bool_value (ctxt, unparsed_arg, key, value,
+ show_state_diagrams))
+ return nullptr;
+ continue;
+ }
+ if (key == "show-state-diagram-dot-src")
+ {
+ if (!parse_bool_value (ctxt, unparsed_arg, key, value,
+ show_state_diagram_dot_src))
+ return nullptr;
+ continue;
+ }
+ if (key == "show-state-diagram-xml")
+ {
+ if (!parse_bool_value (ctxt, unparsed_arg, key, value,
+ show_state_diagram_xml))
+ return nullptr;
+ continue;
+ }
/* Key not found. */
auto_vec<const char *> known_keys;
known_keys.safe_push ("css");
known_keys.safe_push ("file");
known_keys.safe_push ("javascript");
+ known_keys.safe_push ("show-state-diagrams");
+ known_keys.safe_push ("show-state-diagram-dot-src");
+ known_keys.safe_push ("show-state-diagram-xml");
ctxt.report_unknown_key (unparsed_arg, key, get_scheme_name (),
known_keys);
return nullptr;
html_generation_options html_gen_opts;
html_gen_opts.m_css = css;
html_gen_opts.m_javascript = javascript;
+ html_gen_opts.m_show_state_diagrams = show_state_diagrams;
+ html_gen_opts.m_show_state_diagram_xml = show_state_diagram_xml;
+ html_gen_opts.m_show_state_diagram_dot_src = show_state_diagram_dot_src;
auto sink = make_html_sink (ctxt.m_dc,
*line_table,
--- /dev/null
+/* C++ wrapper around libiberty's pex API.
+ 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_STRING
+#define INCLUDE_VECTOR
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "pex.h"
+
+/* Read the contents of FILE into memory, or return nullptr
+ if there are any problems. */
+
+static std::unique_ptr<std::vector<char>>
+read_all_of_file (FILE *f_in)
+{
+ /* Read content, allocating a buffer for it. */
+ auto result = std::make_unique<std::vector<char>> ();
+ char buf[4096];
+ size_t iter_sz_in;
+
+ while ( (iter_sz_in = fread (buf, 1, sizeof (buf), f_in)) )
+ {
+ size_t old_total_sz = result->size ();
+ size_t new_total_sz = old_total_sz + iter_sz_in;
+ size_t old_alloc_sz = result->capacity ();
+ if (new_total_sz > old_alloc_sz)
+ {
+ size_t new_alloc_sz = std::max (old_alloc_sz * 2, new_total_sz);
+ result->reserve (new_alloc_sz);
+ }
+ gcc_assert (result->capacity () >= new_total_sz);
+ result->resize (new_total_sz);
+ memcpy (result->data () + old_total_sz, buf, iter_sz_in);
+ }
+
+ if (!feof (f_in))
+ return nullptr;
+
+ return result;
+}
+
+// struct file_wrapper
+
+std::unique_ptr<std::vector<char>>
+file_wrapper::read_all ()
+{
+ return read_all_of_file (m_file);
+}
+
+// struct pex
+
+const char *
+pex::run (int flags, const char *executable, const std::vector<std::string> &args,
+ const char *outname, const char *errname, int *err)
+{
+ std::vector<char *> argv;
+ for (auto &iter : args)
+ argv.push_back (const_cast<char *> (iter.c_str ()));
+ argv.push_back (nullptr);
+ return pex_run (m_obj, flags, executable, argv.data (),
+ outname, errname, err);
+}
--- /dev/null
+/* C++ wrapper around libiberty's pex API.
+ 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_PEX_H
+#define GCC_PEX_H
+
+struct file_wrapper
+{
+ enum class ownership { owned, borrowed };
+
+ file_wrapper (FILE *file, enum ownership ownership)
+ : m_file (file),
+ m_ownership (ownership)
+ {
+ }
+ ~file_wrapper ()
+ {
+ if (m_ownership == ownership::owned)
+ {
+ gcc_assert (m_file);
+ fclose (m_file);
+ }
+ }
+
+ std::unique_ptr<std::vector<char>>
+ read_all ();
+
+ FILE *m_file;
+ enum ownership m_ownership;
+};
+
+// RAII wrapper around pex_obj
+
+struct pex
+{
+ pex (int flags, const char *pname, const char *tempbase)
+ : m_obj (pex_init (flags, pname, tempbase))
+ {
+ }
+
+ ~pex ()
+ {
+ pex_free (m_obj);
+ }
+
+ const char *
+ run (int flags, const char *executable, char * const *argv,
+ const char *outname, const char *errname, int *err)
+ {
+ return pex_run (m_obj, flags, executable, argv, outname, errname, err);
+ }
+
+ const char *
+ run (int flags, const char *executable, const std::vector<std::string> &args,
+ const char *outname, const char *errname, int *err);
+
+ file_wrapper
+ input_file (int flags, const char *in_name)
+ {
+ return file_wrapper (pex_input_file (m_obj, flags, in_name),
+ /* closed on first call to pex_run. */
+ file_wrapper::ownership::borrowed);
+ }
+
+ file_wrapper
+ input_pipe (bool binary = true)
+ {
+ return file_wrapper (pex_input_pipe (m_obj, binary),
+ /* closed on first call to pex_run. */
+ file_wrapper::ownership::borrowed);
+ }
+
+ file_wrapper
+ read_output (bool binary = true)
+ {
+ return file_wrapper (pex_read_output (m_obj, binary),
+ file_wrapper::ownership::borrowed);
+ }
+
+ pex_obj *m_obj;
+};
+
+#endif /* GCC_PEX_H */
ordered_hash_map_tests_cc_tests ();
splay_tree_cc_tests ();
xml_cc_tests ();
+ graphviz_cc_tests ();
/* Mid-level data structures. */
input_cc_tests ();
extern void ggc_tests_cc_tests ();
extern void gimple_cc_tests ();
extern void gimple_range_tests ();
+extern void graphviz_cc_tests ();
extern void hash_map_tests_cc_tests ();
extern void hash_set_tests_cc_tests ();
extern void input_cc_tests ();
--- /dev/null
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct foo
+{
+ virtual ~foo () {}
+
+ foo (int i) : m_i (i) {}
+
+ int m_i;
+};
+
+void test ()
+{
+ foo *f = new foo (42);
+} // { dg-warning "leak of 'f'" }
/* Emit a warning describing the size of the base region of (*ptr). */
extern void __analyzer_dump_capacity (const void *ptr);
+/* When reached, dump GraphViz .dot source to stderr for a diagram
+ describing the analyzer’s state. */
+extern void __analyzer_dump_dot (void);
+
/* Dump information about what decls have escaped at this point on the path. */
extern void __analyzer_dump_escaped (void);
This is for use when debugging, and may be of use in DejaGnu tests. */
extern void __analyzer_dump_state (const char *name, ...);
+/* Dump copious information about the analyzer’s state when reached. */
+extern void __analyzer_dump_xml (void);
+
/* Emit a warning with text "TRUE", FALSE" or "UNKNOWN" based on the
truthfulness of the argument. */
extern void __analyzer_eval (int);
--- /dev/null
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+def test_xml_state(sarif):
+ result = get_result_by_index(sarif, 0)
+
+ assert result['level'] == 'warning'
+ assert result['ruleId'] == '-Wanalyzer-use-after-free'
+
+ # TODO: check code flow
+ events = result["codeFlows"][0]["threadFlows"][0]['locations']
+
+ # Event "(1)": "entry to 'test'" (index == 0)
+ assert events[0]['location']['message']['text'] == "entry to 'test'"
+ state0 = get_xml_state(events, 0)
+ memory_regions = state0.find('memory-regions')
+ assert memory_regions is not None
+ stack = memory_regions.find('stack')
+ assert stack is not None
+ frame = stack.find('stack-frame')
+ assert frame.get('function') == 'test'
+
+ # Final event:
+ assert events[-1]['location']['message']['text'].startswith("use after 'free' of ")
+ state = get_xml_state(events, -1)
+ # TODO
+
--- /dev/null
+/* { dg-additional-options "-fdiagnostics-add-output=sarif:xml-state=yes" } */
+
+#include "analyzer-decls.h"
+
+struct node
+{
+ struct node *m_next;
+ int m_val;
+};
+
+struct node *first;
+
+struct node *
+append_value (int value)
+{
+ struct node *n = __builtin_malloc (sizeof (struct node));
+ if (!n)
+ return 0;
+ n->m_val = value;
+
+ n->m_next = first;
+ first = n;
+
+ return n;
+}
+
+int g;
+
+void
+test ()
+{
+ if (!append_value (42))
+ return;
+ if (!append_value (1066))
+ return;
+ if (!append_value (1776))
+ return;
+
+ __builtin_free (first->m_next->m_next);
+ first->m_next->m_next->m_next->m_next = NULL; /* { dg-warning "-Wanalyzer-use-after-free" } */
+}
+
+/* Verify that some JSON was written to a file with the expected name. */
+/* { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest state-diagram-1.c "state-diagram-1-sarif.py" } } */
--- /dev/null
+#include "analyzer-decls.h"
+#include <stdio.h>
+
+int g;
+int h;
+
+int
+test_pointer_to_global (FILE *f)
+{
+ int *p = &g;
+ int *q = &h;
+
+ fread (&g, sizeof (g), 1, f);
+ fread (&h, sizeof (h), 1, f);
+
+ return *p / *q; /* { dg-warning "-Wanalyzer-tainted-divisor" } */
+}
--- /dev/null
+#include "analyzer-decls.h"
+
+int
+inner (int *p)
+{
+ return *p; /* { dg-warning "-Wanalyzer-use-of-uninitialized-value" } */
+}
+
+int
+middle (int *q)
+{
+ return inner (q);
+}
+
+int
+outer ()
+{
+ int i;
+ return middle (&i);
+}
--- /dev/null
+#include "analyzer-decls.h"
+
+void test ()
+{
+ void *p = __builtin_malloc (1024);
+ __builtin_free (p);
+ __builtin_free (p); /* { dg-warning "-Wanalyzer-double-free" } */
+}
--- /dev/null
+# Smoketest of HTML state diagram output
+
+from htmltest import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def html_tree():
+ return html_tree_from_env()
+
+def test_state_diagram(html_tree):
+ diag = get_diag_by_index(html_tree, 0)
+ assert diag is not None
+
+ path = diag.find('xhtml:div[@id="execution-path"]', ns)
+ assert path is not None
+
+ event_label = path.find('.//xhtml:span[@id="gcc-diag-0-event-0"]', ns)
+ assert event_label is not None
+ assert event_label.get('class') == 'event'
+
+ assert event_label.text == '(1) here'
+
+ state_diagram = event_label.find('xhtml:div[@class="state-diagram"]', ns)
+ assert state_diagram is not None
+ assert state_diagram.get('id') == 'gcc-diag-0-event-0-state-diagram'
+
+ svg = state_diagram.find('.//svg:svg', ns)
+ assert svg is not None
--- /dev/null
+import xml.etree.ElementTree as ET
+
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+def test_nested_types_in_xml_state(sarif):
+ result = get_result_by_index(sarif, 0)
+
+ assert result['level'] == 'note'
+
+ events = result["codeFlows"][0]["threadFlows"][0]['locations']
+
+ assert events[0]['location']['message']['text'] == 'here'
+ state = get_xml_state(events, 0)
+
+ memory_regions = state.find('memory-regions')
+ assert memory_regions is not None
+
+ stack = memory_regions.find('stack')
+ assert stack is not None
+
+ frame = stack.find('stack-frame')
+ assert frame.get('function') == 'test'
+
+ # We have:
+ # baz_arr[1].m_bars[1].m_foos[2].m_ints[1] = 42;
+
+ # Verify that we correctly expand from the analyzer's bit-offset
+ # representation to nested elements and fields.
+
+ # "baz_arr":
+ baz_arr = frame.find("variable[@name='baz_arr']")
+ assert baz_arr.get('type') == 'struct baz[2]'
+
+ # "baz_arr[1]":
+ baz_arr_1 = baz_arr.find("element[@index='1']")
+ assert baz_arr_1.get('type') == 'struct baz'
+
+ assert baz_arr.find("element[@index='0']") is None
+
+ # "baz_arr[1].m_bars":
+ baz_arr_1_m_bars = baz_arr_1.find("field[@name='m_bars']")
+ assert baz_arr_1_m_bars.get('type') == 'struct bar[2]'
+
+ # "baz_arr[1].m_bars[1]"
+ baz_arr_1_m_bars_1 = baz_arr_1_m_bars.find("element[@index='1']")
+ assert baz_arr_1_m_bars_1.get('type') == 'struct bar'
+
+ # "baz_arr[1].m_bars[1].m_foos"
+ baz_arr_1_m_bars_1_m_foos = baz_arr_1_m_bars_1.find("field[@name='m_foos']")
+ assert baz_arr_1_m_bars_1_m_foos.get('type') == 'struct foo[3]'
+
+ # "baz_arr[1].m_bars[1].m_foos[2]"
+ baz_arr_1_m_bars_1_m_foos_2 = baz_arr_1_m_bars_1_m_foos.find("element[@index='2']")
+ assert baz_arr_1_m_bars_1_m_foos_2.get('type') == 'struct foo'
+
+ # "baz_arr[1].m_bars[1].m_foos[2].m_ints"
+ baz_arr_1_m_bars_1_m_foos_2_m_ints = baz_arr_1_m_bars_1_m_foos_2.find('field[@name="m_ints"]')
+ assert baz_arr_1_m_bars_1_m_foos_2_m_ints.get('type') == 'int[4]'
+
+ # "baz_arr[1].m_bars[1].m_foos[2].m_ints[1]"
+ baz_arr_1_m_bars_1_m_foos_2_m_ints_1 = baz_arr_1_m_bars_1_m_foos_2_m_ints.find('element[@index="1"]')
+ assert baz_arr_1_m_bars_1_m_foos_2_m_ints_1.get('type') == 'int'
+
+ value = baz_arr_1_m_bars_1_m_foos_2_m_ints_1.find('value-of-region')
+ constant = value.find('constant')
+ assert constant.get('value') == '42'
+ assert constant.get('type') == 'int'
--- /dev/null
+/* { dg-additional-options "-fdiagnostics-add-output=sarif:xml-state=yes" } */
+/* { dg-additional-options "-fdiagnostics-add-output=experimental-html:javascript=no,show-state-diagrams=yes" } */
+/* { dg-additional-options "-fdiagnostics-show-caret" } */
+
+#include "analyzer-decls.h"
+
+struct foo
+{
+ int m_ints[4];
+};
+
+struct bar
+{
+ struct foo m_foos[3];
+ int m_int;
+ char m_ch;
+};
+
+struct baz
+{
+ struct bar m_bars[2];
+ struct foo m_foos[5];
+};
+
+void test (void)
+{
+ struct baz baz_arr[2];
+ baz_arr[1].m_bars[1].m_foos[2].m_ints[1] = 42;
+ __analyzer_dump_path (); /* { dg-message "path" } */
+}
+
+/* We need -fdiagnostics-show-caret for the HTML output to show the source,
+ and thus to show event labels. */
+
+/* { dg-begin-multiline-output "" }
+ __analyzer_dump_path ();
+ { dg-end-multiline-output "" } */
+
+/* Verify that some JSON was written to a file with the expected name. */
+/* { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest state-diagram-5.c "state-diagram-5-sarif.py" } } */
+
+/* Use a Python script to verify various properties about the generated
+ .html file:
+ { dg-final { run-html-pytest state-diagram-5.c "state-diagram-5-html.py" } } */
/* { dg-options "-g" } */
#define INCLUDE_MEMORY
+#define INCLUDE_STRING
#define INCLUDE_VECTOR
#include "gcc-plugin.h"
#include "config.h"
/* { dg-options "-g" } */
#define INCLUDE_MEMORY
+#define INCLUDE_STRING
#define INCLUDE_VECTOR
#include "gcc-plugin.h"
#include "config.h"
/* { dg-options "-g" } */
#define INCLUDE_MEMORY
+#define INCLUDE_STRING
#define INCLUDE_VECTOR
#include "gcc-plugin.h"
#include "config.h"
/* { dg-options "-g" } */
#define INCLUDE_MEMORY
+#define INCLUDE_STRING
#define INCLUDE_VECTOR
#include "gcc-plugin.h"
#include "config.h"
return ET.parse(html_filename)
XHTML = 'http://www.w3.org/1999/xhtml'
-ns = {'xhtml': XHTML}
+SVG = 'http://www.w3.org/2000/svg'
+ns = {'xhtml': XHTML,
+ 'svg' : SVG}
def make_tag(local_name):
return f'{{{XHTML}}}' + local_name
import json
import os
+import xml.etree.ElementTree as ET
def sarif_from_env():
# return parsed JSON content a SARIF_PATH file
def get_location_relationships(location):
return location['relationships']
+
+def get_result_by_index(sarif, idx):
+ runs = sarif['runs']
+ run = runs[0]
+ results = run['results']
+ return results[idx]
+
+def get_xml_state(events, event_idx):
+ xml_src = events[event_idx]['properties']['gcc/diagnostic_event/xml_state']
+ if 0:
+ print(xml_src)
+ xml = ET.fromstring(xml_src)
+ assert xml.tag == 'state-diagram'
+ return xml
add_text (pp_formatted_text (&pp));
}
+void
+node_with_children::add_comment (std::string str)
+{
+ add_child (std::make_unique <comment> (std::move (str)));
+}
+
+element *
+node_with_children::find_child_element (std::string kind) const
+{
+ for (auto &iter : m_children)
+ if (element *e = iter->dyn_cast_element ())
+ if (e->m_kind == kind)
+ return e;
+ return nullptr;
+}
+
/* struct document : public node_with_children. */
void
m_attributes[name] = std::move (value);
}
+const char *
+element::get_attr (const char *name) const
+{
+ auto iter = m_attributes.find (name);
+ if (iter == m_attributes.end ())
+ return nullptr;
+ return iter->second.c_str ();
+}
+
+// struct comment : public node
+
+void
+comment::write_as_xml (pretty_printer *pp,
+ int depth, bool indent) const
+{
+ if (indent)
+ {
+ for (int i = 0; i < depth; ++i)
+ pp_string (pp, " ");
+ }
+ pp_string (pp, "<!-- ");
+ write_escaped_text (pp, m_text.c_str ());
+ pp_string (pp, " -->");
+ if (indent)
+ pp_newline (pp);
+}
+
// struct raw : public node
void
" </bar>\n"
" </foo>\n"
"</top>\n");
+
+ xml::element *foo = top.find_child_element ("foo");
+ ASSERT_TRUE (foo);
+ ASSERT_EQ (top.find_child_element ("not-foo"), nullptr);
+ xml::element *bar = foo->find_child_element ("bar");
+ ASSERT_TRUE (bar);
+ ASSERT_STREQ (bar->get_attr ("size"), "3");
+ ASSERT_STREQ (bar->get_attr ("color"), "red");
+ ASSERT_EQ (bar->get_attr ("airspeed-velocity"), nullptr);
}
// Verify that element attributes preserve insertion order.
"</top>\n");
}
+static void
+test_comment ()
+{
+ xml::document doc;
+ doc.add_comment ("hello");
+ doc.add_comment ("world");
+ ASSERT_XML_PRINT_EQ
+ (doc,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!-- hello -->\n"
+ "<!-- world -->\n");
+
+}
/* Run all of the selftests within this file. */
void
test_no_dtd ();
test_printer ();
test_attribute_ordering ();
+ test_comment ();
}
} // namespace selftest
struct document;
struct element;
struct doctypedecl;
+ struct comment;
+ struct raw;
struct node
{
int depth, bool indent) const = 0;
virtual text *dyn_cast_text ()
{
- return 0;
+ return nullptr;
+ }
+ virtual element *dyn_cast_element ()
+ {
+ return nullptr;
}
void dump (FILE *out) const;
void DEBUG_FUNCTION dump () const { dump (stderr); }
void add_child (std::unique_ptr<node> node);
void add_text (std::string str);
void add_text_from_pp (pretty_printer &pp);
+ void add_comment (std::string str);
+
+ element *find_child_element (std::string kind) const;
std::vector<std::unique_ptr<node>> m_children;
};
m_preserve_whitespace (preserve_whitespace)
{}
+ element *dyn_cast_element () final override
+ {
+ return this;
+ }
+
void write_as_xml (pretty_printer *pp,
int depth, bool indent) const final override;
void set_attr (const char *name, std::string value);
+ const char *get_attr (const char *name) const;
std::string m_kind;
bool m_preserve_whitespace;
std::vector<std::string> m_key_insertion_order;
};
+/* An XML comment. */
+
+struct comment : public node
+{
+ comment (std::string text)
+ : m_text (std::move (text))
+ {
+ }
+
+ void write_as_xml (pretty_printer *pp,
+ int depth, bool indent) const final override;
+
+ std::string m_text;
+};
+
/* A fragment of raw XML source, to be spliced in directly.
Use sparingly. */