]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
diagnostics: add state diagrams to analyzer experimental-html output [PR116792]
authorDavid Malcolm <dmalcolm@redhat.com>
Mon, 23 Jun 2025 15:06:43 +0000 (11:06 -0400)
committerDavid Malcolm <dmalcolm@redhat.com>
Mon, 23 Jun 2025 15:19:24 +0000 (11:19 -0400)
This patch adds various support for debugging diagnostic paths and
events, intended initially for myself to help with debugging -fanalyzer.

It adds the optional ability for a diagnostic_event to supply a
description of the predicted state of the program at that point along
the diagnostic_path.  To isolate the diagnostic subsystem from the
analyzer, this representation is currently an xml::document with custom
elements.  The XML representation is similar to the analyzer's internal
state but 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.

These states are handled by the HTML and SARIF diagnostic sinks.

The SARIF sink simply embeds the XML as a string in a property bag of the
threadFlowLocation object (SARIF v2.1.0 section 3.38).

For HTML output, the "experimental-html" sink gains a new
"show-state-diagrams=yes" option i.e.:
  -fdiagnostics-add-output=experimental-html:show-state-diagrams=yes
which converts the state XML into SVG 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, making it *much* easier to debug -fanalyzer.

An example of output can be seen here:
  https://dmalcolm.fedorapeople.org/gcc/2025-06-23/state-diagram-1.c.html
showing an issue in a singly-linked list; there are various other
examples in the parent directory.

Generating the SVG diagrams requires an invocation of "dot" per event,
so it noticeable slows down diagnostic emission, hence the opt-in
command-line flag.  However, I'm already finding bugs in -fanalyzer with
this that I hadn't seen before.

Given that the UI is rather clunky and there is lots of room for
improvement to the visualizations, for now this feature is marked
as being for GCC developers, not end-users.

The patch also adds a dot::ast_node class hierarachy to make it easy to
create GraphViz dot files with the correct escaping, and adds a C++
wrapper around pex adding some syntactic sugar for invoking
subprocesses.

gcc/ChangeLog:
PR other/116792
* Makefile.in (ANALYZER_OBJS): Add
analyzer/ana-state-to-diagnostic-state.o.
(OBJS): Move graphviz.o to...
(OBJS-libcommon): ...here.  Add diagnostic-state-to-dot.o and pex.o.
* diagnostic-format-html.cc: Include "diagnostic-state.h" and
"graphviz.h".
(html_generation_options::html_generation_options): Initialize the
new flags.
(HTML_SCRIPT): Add function "get_any_state_diagram".  Use it
when changing current focus id to update the visibility of the
pertinent diagram, if any.
(print_pre_source): New.
(html_builder::maybe_make_state_diagram): New.
(html_path_label_writer::html_path_label_writer): Add "path" param.
Initialize m_path and m_curr_event_id.
(html_path_label_writer::begin_label): Store current event id.
(html_path_label_writer::end_label): Attempt to make a state
diagram and add it if successful.
(html_path_label_writer::get_element_id): New.
(html_path_label_writer::m_path): New field.
(html_path_label_writer::m_curr_event_id): New field.
(html_builder::make_element_for_diagnostic): Pass path to label
writer.
* diagnostic-format-html.h
(html_generation_options::m_show_state_diagrams): New field.
(html_generation_options::m_show_state_diagram_xml): New field.
(html_generation_options::m_show_state_diagram_dot_src): New field.
* diagnostic-format-sarif.cc: Include "xml.h".
(populate_thread_flow_location_object): If requested, attempt to
generate xml state and add it to the proeprty bag as
"gcc/diagnostic_event/xml_state" in xml source form.
(sarif_generation_options::sarif_generation_options): Initialize
m_xml_state.
* diagnostic-format-sarif.h
(sarif_generation_options::m_xml_state): New field.
* diagnostic-path.cc: Define INCLUDE_MAP.  Include "xml.h".
(diagnostic_event::maybe_make_xml_state): New.
* diagnostic-path.h (class xml::document): New forward decl.
(diagnostic_event::maybe_make_xml_state): New vfunc decl.
* diagnostic-state-to-dot.cc: New file.
* diagnostic-state.h: New file.
* digraph.cc: Define INCLUDE_STRING and INCLUDE_VECTOR.
* doc/analyzer.texi: Document state diagrams in html output.
(__analyzer_dump_dot): New.
(__analyzer_dump_xml): New.
* doc/invoke.texi (sarif): Add "xml-state" key.
(experimental-html): Add keys "show-state-diagrams",
"show-state-diagrams-dot-src" and "show-state-diagrams-xml".
* graphviz.cc: Define INCLUDE_MAP, INCLUDE_STRING, and
INCLUDE_VECTOR.  Include "xml.h", "xml-printer.h", "pex.h" and
"selftest.h".
(graphviz_out::graphviz_out): Extract...
(dot::writer::writer): ...this.
(graphviz_out::write_indent): Convert to...
(dot::writer::write_indent): ...this.
(graphviz_out::print): Use get_pp.
(graphviz_out::println): Likewise.
(graphviz_out::begin_tr): Likewise.
(graphviz_out::end_tr): Likewise.
(graphviz_out::begin_td): Likewise.
(graphviz_out::end_td): Likewise.
(graphviz_out::begin_trtd): Likewise.
(graphviz_out::end_tdtr): Likewise.
(dot::ast_node::dump): New.
(dot::id::id): New.
(dot::id::print): New.
(dot::id::is_identifier_p): New.
(dot::kv_pair::print): New.
(dot::attr_list::print): New.
(dot::stmt_list::print): New.
(dot::stmt_list::add_edge): New.
(dot::stmt_list::add_attr): New.
(dot::graph::print): New.
(dot::stmt_with_attr_list::set_label): New.
(dot::node_stmt::print): New.
(dot::attr_stmt::print): New.
(dot::kv_stmt::print): New.
(dot::node_id::print): New.
(dot::port::print): New.
(dot::edge_stmt::print): New.
(dot::subgraph::print): New.
(dot::make_svg_document_buffer_from_graph): New.
(dot::make_svg_from_graph): New.
(selftest:test_ids): New.
(selftest:test_trivial_graph): New.
(selftest:test_layout_example): New.
(selftest:graphviz_cc_tests): New.
* graphviz.h (xml::node): New forward decl.
(class graphviz_out): Split out into...
(class dot::writer): ...this new class
(struct dot::ast_node): New.
(struct dot::id): New.
(struct dot::kv_pair): New.
(struct dot::attr_list): New.
(struct dot::stmt_list): New.
(struct dot::graph): New.
(struct dot::stmt): New.
(struct dot::stmt_with_attr_list): New.
(struct dot::node_stmt): New.
(struct dot::attr_stmt): New.
(struct dot::kv_stmt): New.
(enum class dot::compass_pt): New.
(struct dot::port): New.
(struct dot::node_id): New.
(struct dot::edge_stmt): New.
(struct dot::subgraph): New.
(dot::make_svg_from_graph): New.
* opts-diagnostic.cc (sarif_scheme_handler::make_sink): Add
"xml-state" flag.
(html_scheme_handler::make_sink): Add flags "show-state-diagrams",
"show-state-diagram-dot-src", and "show-state-diagram-xml".
* pex.cc: New file.
* pex.h: New file.
* selftest-run-tests.cc (selftest::run_tests): Call
graphviz_cc_tests.
* selftest.h (selftest::graphviz_cc_tests): New decl.
* xml.cc (xml::node_with_children::add_comment): New.
(xml::node_with_children::find_child_element): New.
(xml::element::get_attr): New.
(xml::comment::write_as_xml): New.
(selftest::test_printer): Add coverage of find_child_element and
get_attr.
(selftest::test_comment): New.
(selftest::xml_cc_tests): Call test_comment.
* xml.h: New forward decls.
(xml::node::dyn_cast_text): Use nullptr.
(xml::node::dyn_cast_element): New vfunc.
(xml::node_with_children::add_comment): New decl.
(xml::node_with_children::find_child_element): New decl.
(xml::element::dyn_cast_element): New vfunc impl.
(xml::element::get_attr): New decl.
(struct xml::comment): New xml::node subclass.

gcc/analyzer/ChangeLog:
PR other/116792
* ana-state-to-diagnostic-state.cc: New file.
* ana-state-to-diagnostic-state.h: New file.
* checker-event.cc: Include "xml.h".
(checker_event::checker_event): Initialize m_path.
(checker_event::prepare_for_emission): Store the path pointer into
m_path.
(checker_event::maybe_make_xml_state): New.
(function_entry_event::function_entry_event): Add "state" param
and use it to initialize m_state.
(superedge_event::get_program_state): New.
(call_event::get_program_state): New.
(warning_event::get_program_state): New.
* checker-event.h (checker_event::get_program_state): New vfunc.
(checker_event::maybe_make_xml_state): New decl.
(checker_event::m_path): New field.
(statement_event::get_program_state): New vfunc impl.
(function_entry_event::function_entry_event): Add "state" param.
(function_entry_event::get_program_state): New vfunc impl.
(function_entry_event::m_state): New field.
(state_change_event::get_program_state): New vfunc impl.
(superedge_event::get_program_state): New vfunc decl.
(warning_event::warning_event): Add "program_state_" param and
copy it.
(warning_event::get_program_state): New vfunc decl.
(warning_event::m_program_state): New field.
* checker-path.h (checker_path::checker_path): Add ext_state param.
(checker_path::get_ext_state): New accessor.
(checker_path::m_ext_state): New field.
* common.h: Define INCLUDE_MAP and INCLUDE_STRING.
* diagnostic-manager.cc (saved_diagnostic::operator==): Don't
deduplicate dump_path_diagnostic instances.
(diagnostic_manager::emit_saved_diagnostic): Pass ext_state to
checker_path ctor.
* engine.cc:
(impl_region_model_context::on_state_leak): Pass old and new state
to state_machine::on_leak.
(exploded_node::on_stmt_pre): Implement __analyzer_dump_xml and
__analyzer_dump_dot.
* exploded-graph.h (impl_region_model_context::get_state): New.
* infinite-recursion.cc
(recursive_function_entry_event::recursive_function_entry_event):
Add "dst_state" param and pass to function_entry_event ctor.
(infinite_recursion_diagnostic::add_function_entry_event): Pass state
to event ctor.
* kf-analyzer.cc: Include "analyzer/program-state.h"
(dump_path_diagnostic::dump_path_diagnostic): Add "state" param.
(dump_path_diagnostic::get_final_state): New.
(dump_path_diagnostic::m_state): New field.
(kf_analyzer_dump_path::impl_call_pre): Pass state to warning.
* pending-diagnostic.cc
(pending_diagnostic::add_function_entry_event): Pass state to
function_entry_event.
(pending_diagnostic::add_final_event): Likewise to warning_event.
* pending-diagnostic.h (pending_diagnostic::get_final_state): New
vfunc decl.
* program-state.cc: Include "diagnostic-state.h", "graphviz.h" and
"analyzer/ana-state-to-diagnostic-state.h".
(program_state::dump_dot): New.
* program-state.h: Include "text-art/tree-widget.h" and
"analyzer/store.h".
(class xml::document): New forward decl.
(make_xml): New.
(dump_xml_to_pp): New.
(dump_xml_to_file): New.
(dump_xml): New.
(dump_dot): New.
* record-layout.cc (record_layout::record_layout): Make param
const_tree.
* record-layout.h (item::item): Likewise.
(item::m_field): Likewise.
(record_layout::record_layout): Likewise.
(record_layout::begin): New.
(record_layout::end): New.
* region-model.cc
(exposure_through_uninit_copy::complain_about_fully_uninit_item):
Use const_tree.
(exposure_through_uninit_copy::complain_about_partially_uninit_item):
Likewise.
* region-model.h (region_model_context::get_state): New vfunc.
(noop_region_model_context::get_state): New.
(region_model_context_decorator::get_state): New.
* sm-fd.cc (fd_leak::fd_leak): Add "final_state" param and capture
it if present.
(fd_leak::get_final_state): New.
(fd_leak::m_final_state): New.
(fd_state_machine::on_open): Pass nullptr for new "final_state"
param.
(fd_state_machine::on_creat): Likewise.
(fd_state_machine::on_socket): Likewise.
(fd_state_machine::on_accept): Likewise.
(fd_state_machine::on_leak): Add state params and pass new state
as final state to fd_leak ctor.
* sm-file.cc: Include "analyzer/program-state.h".
(file_leak::file_leak): Add "final_state" param and capture it if
present.
(file_leak::get_final_state): New.
(file_leak::m_final_state): New.
(fileptr_state_machine::on_leak): Add state params and pass new
state as final state to fd_leak ctor.
* sm-malloc.cc: Include
"analyzer/ana-state-to-diagnostic-state.h".
(malloc_leak::malloc_leak): Add "final_state" param and use it.
(malloc_leak::get_final_state): New vfunc impl.
(malloc_leak::m_final_state): New field.
(malloc_state_machine::on_leak): Add state params; capture final
state.
(malloc_state_machine::add_state_to_xml): New.
* sm.cc (state_machine::on_leak): Add "old_state" and "new_state"
params.  Use nullptr.
(state_machine::add_state_to_xml): New.
(state_machine::add_global_state_to_xml): New.
* sm.h (class xml_state): New forward decl.
(state_machine::on_leak): Add state params.
(state_machine::add_state_to_xml): New vfunc decl.
(state_machine::add_global_state_to_xml): New vfunc decl.
* store.h (bit_range::operator<): New.
* varargs.cc (va_list_leak::va_list_leak): Add final_state param
and capture it if non-null.
(va_list_leak::get_final_state): New.
(va_list_leak::m_final_state): New.
(va_list_state_machine::on_leak): Add state params and pass final
state to va_list_leak ctor.

gcc/testsuite/ChangeLog:
PR other/116792
* g++.dg/analyzer/state-diagram.C: New test.
* gcc.dg/analyzer/analyzer-decls.h (__analyzer_dump_dot): New
decl.
(__analyzer_dump_xml): New decl.
* gcc.dg/analyzer/state-diagram-1-sarif.py: New test script.
* gcc.dg/analyzer/state-diagram-1.c: New test.
* gcc.dg/analyzer/state-diagram-2.c: New test.
* gcc.dg/analyzer/state-diagram-3.c: New test.
* gcc.dg/analyzer/state-diagram-4.c: New test.
* gcc.dg/analyzer/state-diagram-5-html.py: New test script.
* gcc.dg/analyzer/state-diagram-5-sarif.py: New test script.
* gcc.dg/analyzer/state-diagram-5.c: New test.
* gcc.dg/plugin/analyzer_cpython_plugin.cc: Define INCLUDE_STRING.
* gcc.dg/plugin/analyzer_gil_plugin.cc: Likewise.
* gcc.dg/plugin/analyzer_kernel_plugin.cc: Likewise.
* gcc.dg/plugin/analyzer_known_fns_plugin.cc: Likewise.
* lib/htmltest.py (ns): Add SVG namespace.
* lib/sarif.py (get_result_by_index): New.
(get_xml_state): New.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
63 files changed:
gcc/Makefile.in
gcc/analyzer/ana-state-to-diagnostic-state.cc [new file with mode: 0644]
gcc/analyzer/ana-state-to-diagnostic-state.h [new file with mode: 0644]
gcc/analyzer/checker-event.cc
gcc/analyzer/checker-event.h
gcc/analyzer/checker-path.h
gcc/analyzer/common.h
gcc/analyzer/diagnostic-manager.cc
gcc/analyzer/engine.cc
gcc/analyzer/exploded-graph.h
gcc/analyzer/infinite-recursion.cc
gcc/analyzer/kf-analyzer.cc
gcc/analyzer/pending-diagnostic.cc
gcc/analyzer/pending-diagnostic.h
gcc/analyzer/program-state.cc
gcc/analyzer/program-state.h
gcc/analyzer/record-layout.cc
gcc/analyzer/record-layout.h
gcc/analyzer/region-model.cc
gcc/analyzer/region-model.h
gcc/analyzer/sm-fd.cc
gcc/analyzer/sm-file.cc
gcc/analyzer/sm-malloc.cc
gcc/analyzer/sm.cc
gcc/analyzer/sm.h
gcc/analyzer/store.h
gcc/analyzer/varargs.cc
gcc/diagnostic-format-html.cc
gcc/diagnostic-format-html.h
gcc/diagnostic-format-sarif.cc
gcc/diagnostic-format-sarif.h
gcc/diagnostic-path.cc
gcc/diagnostic-path.h
gcc/diagnostic-state-to-dot.cc [new file with mode: 0644]
gcc/diagnostic-state.h [new file with mode: 0644]
gcc/digraph.cc
gcc/doc/analyzer.texi
gcc/doc/invoke.texi
gcc/graphviz.cc
gcc/graphviz.h
gcc/opts-diagnostic.cc
gcc/pex.cc [new file with mode: 0644]
gcc/pex.h [new file with mode: 0644]
gcc/selftest-run-tests.cc
gcc/selftest.h
gcc/testsuite/g++.dg/analyzer/state-diagram.C [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h
gcc/testsuite/gcc.dg/analyzer/state-diagram-1-sarif.py [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/state-diagram-1.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/state-diagram-2.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/state-diagram-3.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/state-diagram-4.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/state-diagram-5-html.py [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/state-diagram-5-sarif.py [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/state-diagram-5.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.cc
gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.cc
gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.cc
gcc/testsuite/gcc.dg/plugin/analyzer_known_fns_plugin.cc
gcc/testsuite/lib/htmltest.py
gcc/testsuite/lib/sarif.py
gcc/xml.cc
gcc/xml.h

index 56fa5ac25ff9c7284180dbe0abafaf94cbafd540..f106e833425946175e32dec6d581dd4417402bed 100644 (file)
@@ -1325,6 +1325,7 @@ C_COMMON_OBJS = c-family/c-common.o c-family/c-cppbuiltin.o c-family/c-dump.o \
 # 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 \
@@ -1537,7 +1538,6 @@ OBJS = \
        godump.o \
        graph.o \
        graphds.o \
-       graphviz.o \
        graphite.o \
        graphite-isl-ast-to-gimple.o \
        graphite-dependences.o \
@@ -1861,7 +1861,9 @@ OBJS-libcommon = diagnostic-spec.o diagnostic.o diagnostic-color.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 \
diff --git a/gcc/analyzer/ana-state-to-diagnostic-state.cc b/gcc/analyzer/ana-state-to-diagnostic-state.cc
new file mode 100644 (file)
index 0000000..b85a2f1
--- /dev/null
@@ -0,0 +1,786 @@
+/* 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 &reg)
+{
+  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 &reg)
+{
+  auto existing = m_region_to_element_map.find (&reg);
+  if (existing != m_region_to_element_map.end ())
+    return *existing->second;
+
+  auto &e = create_and_add_element (reg);
+  m_region_to_element_map[&reg] = &e;
+  return e;
+}
+
+xml::element&
+xml_state::create_and_add_element (const region &reg)
+{
+  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 &reg)
+{
+  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 () == &reg)
+    if (!reg.get_type ())
+      {
+       auto search
+         = m_types_for_untyped_regions.find (&reg);
+       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 &reg,
+                                            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 &reg,
+                                             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 (&reg,
+                                         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 (&reg, 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 (&reg,
+                                           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 &reg, xml::element &e)
+{
+  const svalue *sval = m_state.m_region_model->get_dynamic_extents (&reg);
+  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 &reg,
+                                      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 */
diff --git a/gcc/analyzer/ana-state-to-diagnostic-state.h b/gcc/analyzer/ana-state-to-diagnostic-state.h
new file mode 100644 (file)
index 0000000..bd6aa46
--- /dev/null
@@ -0,0 +1,89 @@
+/* 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 &reg);
+
+private:
+  xml::element&
+  create_and_add_element (const region &reg);
+
+  static std::unique_ptr<xml::element>
+  make_memory_space_element (const char *label);
+
+  std::unique_ptr<xml::element>
+  create_element (const region &reg);
+
+  /* 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 &reg,
+                                   bit_range &out);
+
+  void
+  populate_element_for_typed_region (xml::element &e,
+                                    const region &reg,
+                                    const concrete_bindings_t &conc_bindings,
+                                    bool create_all);
+
+  void
+  set_attr_for_dynamic_extents (const region &reg, xml::element &e);
+
+  bool
+  show_child_element_for_child_region_p (const region &reg,
+                                        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 */
index e041778e8d1984aa558a700ae77bd25393661dbe..04b66bf85d6354b27e96a81b71e6cdacf471ea21 100644 (file)
@@ -28,6 +28,7 @@ along with GCC; see the file COPYING3.  If not see
 #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"
@@ -106,7 +107,8 @@ event_kind_to_string (enum event_kind ek)
 
 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),
@@ -211,10 +213,11 @@ checker_event::debug () const
    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;
 
@@ -222,6 +225,29 @@ checker_event::prepare_for_emission (checker_path *,
   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
@@ -344,12 +370,14 @@ region_creation_event_debug::print_desc (pretty_printer &pp) const
 
 /* 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)
 {
 }
 
@@ -557,6 +585,12 @@ superedge_event::should_filter_p (int verbosity) const
   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,
@@ -869,6 +903,14 @@ call_event::get_callee_fndecl () const
   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.  */
@@ -1213,6 +1255,15 @@ warning_event::get_meaning () const
   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 */
index e29173a439d188fc505cef88b0fcd463bc84282c..7c44f1e83c8edd9c3b195f74457b5601df9b2f26 100644 (file)
@@ -128,6 +128,12 @@ public:
   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
   {
@@ -144,6 +150,7 @@ protected:
                 const event_loc_info &loc_info);
 
  private:
+  const checker_path *m_path;
   const enum event_kind m_kind;
  protected:
   location_t m_loc;
@@ -225,6 +232,12 @@ public:
 
   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;
 };
@@ -335,17 +348,29 @@ private:
 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.  */
@@ -366,6 +391,12 @@ public:
   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 ();
@@ -408,6 +439,9 @@ public:
 
   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);
@@ -518,6 +552,9 @@ public:
 
   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;
@@ -792,16 +829,22 @@ public:
   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:
@@ -809,6 +852,9 @@ 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
index 80c975c6a745a773c069eac1a7e3f38e24ead55d..3c174bf8dd18ded8869e03a8f0e619db9ba099a3 100644 (file)
@@ -32,8 +32,10 @@ class checker_path : public diagnostic_path
 {
 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)
   {}
@@ -59,6 +61,8 @@ public:
     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];
@@ -140,6 +144,8 @@ public:
 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.  */
index cb030043d8ad6800d8761ac917bfa3f1aff98333..c35cdb25d305625d486c3efa8fb3f9b97e13ad0c 100644 (file)
@@ -22,6 +22,8 @@ along with GCC; see the file COPYING3.  If not see
 #define GCC_ANALYZER_COMMON_H
 
 #include "config.h"
+#define INCLUDE_MAP
+#define INCLUDE_STRING
 #define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
index 6867dd267e6af2bd900eb6bb67fa10440e793f83..d3cf993fa42e495bf47a1fed75442cdc48b94b91 100644 (file)
@@ -687,6 +687,11 @@ saved_diagnostic::operator== (const saved_diagnostic &other) const
   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
@@ -1581,6 +1586,7 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
   /* 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.  */
index c3e4800f70adc45f635a5147b1048e47ff8b1428..5eb9048d5edd9ef5f57610e4d8514677e42282b7 100644 (file)
@@ -925,7 +925,9 @@ impl_region_model_context::on_state_leak (const state_machine &sm,
     }
 
   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,
@@ -1578,6 +1580,16 @@ exploded_node::on_stmt_pre (exploded_graph &eg,
          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 (),
index 23e344d87e4aa3a823d6edae892eed69795ed19f..ca0b78785950b487c340e4b6003a403a24469868 100644 (file)
@@ -112,6 +112,8 @@ class impl_region_model_context : public region_model_context
   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 {}
index 064111769b0f0ec74f754c57181577c18e93c97d..bf5c19490efdbb944096e9aca2ff0909904e2039 100644 (file)
@@ -108,9 +108,10 @@ public:
     {
     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)
       {
@@ -148,15 +149,17 @@ public:
       {
        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);
   }
index 3e671e5edadb22f2e1219e89624baf145ef80fb6..13476defcafb652a556d0501aef038331bda6b29 100644 (file)
@@ -28,6 +28,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/region-model.h"
 #include "analyzer/pending-diagnostic.h"
 #include "analyzer/call-details.h"
+#include "analyzer/program-state.h"
 
 #if ENABLE_ANALYZER
 
@@ -260,6 +261,11 @@ class dump_path_diagnostic
   : 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;
@@ -280,6 +286,15 @@ public:
   {
     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
@@ -297,7 +312,8 @@ public:
     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));
   }
 };
 
index 70dc8154d62f0c7c54760c75a369f3473e9ae526..b727392bb960693aa79daf3c47540f0e1dc9cabb 100644 (file)
@@ -185,7 +185,10 @@ pending_diagnostic::add_function_entry_event (const exploded_edge &eedge,
 {
   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.
@@ -241,7 +244,8 @@ pending_diagnostic::add_final_event (const state_machine *sm,
     (std::make_unique<warning_event>
      (loc_info,
       enode,
-      sm, var, state));
+      sm, var, state,
+      get_final_state ()));
 }
 
 } // namespace ana
index 6a0289e705ba2c9b891fcecf60049ba27f23bc07..469513c726bd4cf77ab098d68b9cec63e40188ac 100644 (file)
@@ -364,6 +364,12 @@ class pending_diagnostic
                                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.  */
index 21f78e5b5c3c7ebacfa557e46be2f01d0edb29ee..0d9199f61f22a2ee1c545a7248abcfe419b5cb21 100644 (file)
@@ -28,6 +28,8 @@ along with GCC; see the file COPYING3.  If not see
 #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"
@@ -48,6 +50,7 @@ along with GCC; see the file COPYING3.  If not see
 #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
 
@@ -1224,6 +1227,18 @@ program_state::make_dump_widget (const text_art::dump_widget_info &dwi) const
   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.  */
 
index 269ffde9ee6c7054e331478b60a06aa590c6f2d6..d8bd9206937940dd02ef5fc28d3ea9b68a2ae9ca 100644 (file)
@@ -22,6 +22,11 @@ along with GCC; see the file COPYING3.  If not see
 #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 {
 
@@ -243,6 +248,12 @@ public:
   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;
 
index aaf8ccd6f96997870f73c3c34ed94fb8aaafb6af..7edc81cede091c8d6079b1445ed41f8c153c53ac 100644 (file)
@@ -30,7 +30,7 @@ namespace ana {
 
 /* 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);
 
index c1c41890dcd19ae89890b7199faf27bda8967b5e..8649d1d601cdb06a3dfe6c74334d151cbae66e5e 100644 (file)
@@ -36,7 +36,7 @@ public:
   {
   public:
     item (const bit_range &br,
-         tree field,
+         const_tree field,
          bool is_padding)
     : m_bit_range (br),
       m_field (field),
@@ -69,17 +69,20 @@ public:
     }
 
     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);
 
index 1ee882c98ea5899ac22aa61bb7a9f0e71b19977f..d1c7e8cd53ead0b55dcd6b4c9bc0dae5232ae2d2 100644 (file)
@@ -7798,7 +7798,7 @@ private:
   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)
       {
@@ -7859,7 +7859,7 @@ private:
   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",
index 2c7f73795e242708c86be6f76d9f02f494bd9f95..781a585f7bc4d687702c21ad57e9f3c5442228c1 100644 (file)
@@ -928,6 +928,8 @@ class region_model_context
 
   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;
@@ -989,6 +991,8 @@ public:
 
   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 {}
@@ -1167,6 +1171,14 @@ class region_model_context_decorator : public region_model_context
        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)
index cee8d2d7670ca7df06606c38c9cd63b2bc1c2c29..09c311594bab6bebefd6c8965773fb7fd73943be 100644 (file)
@@ -116,7 +116,11 @@ public:
                     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;
@@ -477,7 +481,14 @@ protected:
 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
@@ -543,8 +554,15 @@ public:
     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
@@ -1528,7 +1546,7 @@ fd_state_machine::on_open (sm_context &sm_ctxt, const supernode *node,
   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));
     }
 }
 
@@ -1541,7 +1559,7 @@ fd_state_machine::on_creat (sm_context &sm_ctxt, const supernode *node,
     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
@@ -1792,7 +1810,7 @@ fd_state_machine::on_socket (const call_details &cd,
        }
       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
     {
@@ -2185,7 +2203,7 @@ fd_state_machine::on_accept (const call_details &cd,
        }
       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
     {
@@ -2321,9 +2339,11 @@ fd_state_machine::can_purge_p (state_t s) const
 }
 
 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
 
index d7dbe2fe7b6e7b402f48da1f45e684f27bba61d9..61e24248c0806313b5df77da4bca35fb7855344a 100644 (file)
@@ -30,6 +30,7 @@ along with GCC; see the file COPYING3.  If not see
 #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"
@@ -72,7 +73,11 @@ public:
                     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.
@@ -226,9 +231,14 @@ private:
 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"; }
 
@@ -286,8 +296,15 @@ public:
     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.  */
@@ -492,9 +509,11 @@ fileptr_state_machine::can_purge_p (state_t s) const
    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
index 333dfea47d3400b7638ea60a9317edff0f4a4942..ee60b963b2b7ff881aabef36320ea14dbcccad38 100644 (file)
@@ -23,6 +23,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-event-id.h"
 #include "stringpool.h"
 #include "attribs.h"
+#include "xml-printer.h"
 
 #include "analyzer/analyzer-logging.h"
 #include "analyzer/sm.h"
@@ -37,6 +38,7 @@ along with GCC; see the file COPYING3.  If not see
 #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
 
@@ -403,7 +405,11 @@ public:
                     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;
@@ -429,6 +435,11 @@ public:
       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;
@@ -1414,8 +1425,14 @@ private:
 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"; }
 
@@ -1475,8 +1492,15 @@ public:
     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
@@ -2589,9 +2613,11 @@ malloc_state_machine::can_purge_p (state_t s) const
    '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
@@ -2700,6 +2726,30 @@ malloc_state_machine::transition_ptr_sval_non_null (region_model *model,
   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 &reg_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. */
index 0abbdd69adf1aa9cf008e8d053de61e30a1e8fe1..54bd92c31abab5c8980e03ec70243507927e9a97 100644 (file)
@@ -116,9 +116,11 @@ state_machine::get_state_by_name (const char *name) const
 /* 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.  */
@@ -158,6 +160,21 @@ state_machine::to_json () const
   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 *
index a932765131b67883e6e428006bb79d31a4ffd3f1..f366be132171b0a7be212b3651129722ec5e1806 100644 (file)
@@ -28,6 +28,7 @@ namespace ana {
 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);
@@ -136,7 +137,9 @@ public:
 
   /* 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
@@ -184,6 +187,15 @@ public:
 
   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)
index 171324c13d3440c50d4f271949aa9da853ff4c43..c85539435f2aad02c854b9a307ca5df621f073da 100644 (file)
@@ -299,6 +299,16 @@ struct bit_range
 
   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;
 };
index 6ea0d29d1314237dc65a0478afbb671db2577f33..3b8f86364be44c1fc110e95153f4a960cf6d023b 100644 (file)
@@ -205,7 +205,11 @@ public:
   {
     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;
@@ -460,10 +464,14 @@ class va_list_leak : public va_list_sm_diagnostic
 {
 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
@@ -524,9 +532,16 @@ public:
     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.  */
@@ -633,9 +648,11 @@ va_list_state_machine::on_va_end (sm_context &sm_ctxt,
    (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
index 5668b50a91aeb3f60d25377526faf16b3b6a7ec7..b1b0895d031866e9ee0b38d347e0fc2c65322dd3 100644 (file)
@@ -41,6 +41,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "xml.h"
 #include "xml-printer.h"
+#include "diagnostic-state.h"
+#include "graphviz.h"
 #include "json.h"
 #include "selftest-xml.h"
 
@@ -48,7 +50,10 @@ along with GCC; see the file COPYING3.  If not see
 
 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)
 {
 }
 
@@ -141,6 +146,9 @@ public:
     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);
@@ -310,14 +318,27 @@ const char * const HTML_SCRIPT
      "      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"
@@ -563,6 +584,58 @@ html_builder::pop_nesting_level ()
   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.  */
@@ -572,34 +645,59 @@ class html_path_label_writer : public html_label_writer
 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 */
@@ -1013,8 +1111,9 @@ html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic,
       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);
index 9ca43f253f03d576e8f73f85795c82250487c1ea..09a97e0ccac767d7a19f385f54e00a104f952eed 100644 (file)
@@ -30,6 +30,17 @@ struct html_generation_options
 
   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
index 853a8333b3ccfba8bc12dacbffe684f4d55272ff..d4ebe5a31edcbe8d2e02dfb43773d6749194bae4 100644 (file)
@@ -50,6 +50,7 @@ along with GCC; see the file COPYING3.  If not see
 #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").  */
@@ -2959,6 +2960,20 @@ populate_thread_flow_location_object (sarif_result &result,
      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",
@@ -4101,7 +4116,8 @@ make_sarif_sink (diagnostic_context &context,
 // 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)
 {
 }
 
index 763dc25f39e8ba714ed2bc6e91ef656b69524a44..c3ae330d3e4f9aa7f2226e3631896b49c37f4a76 100644 (file)
@@ -101,6 +101,7 @@ struct sarif_generation_options
   sarif_generation_options ();
 
   enum sarif_version m_version;
+  bool m_xml_state;
 };
 
 extern std::unique_ptr<diagnostic_output_format>
index 7a9c051c534cc62ca89201c08e40b06afcc0ddea..0c617edeaa5174fbccd0a49802466ee5f1e3b796 100644 (file)
@@ -20,12 +20,14 @@ along with GCC; see the file COPYING3.  If not see
 
 #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.  */
@@ -154,6 +156,15 @@ diagnostic_event::get_desc (pretty_printer &ref_pp) const
   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.
index 4419139413bdc4b498d2883f84101dc489748223..a3987a015a3e2da254fe0349baa3493e170d1265 100644 (file)
@@ -25,6 +25,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-event-id.h"
 #include "logical-location.h"
 
+namespace xml { class document; }
+
 class sarif_builder;
 class sarif_object;
 
@@ -170,6 +172,11 @@ class diagnostic_event
   {
   }
 
+  /* 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;
 };
 
diff --git a/gcc/diagnostic-state-to-dot.cc b/gcc/diagnostic-state-to-dot.cc
new file mode 100644 (file)
index 0000000..b6d7ec5
--- /dev/null
@@ -0,0 +1,537 @@
+/* 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);
+}
diff --git a/gcc/diagnostic-state.h b/gcc/diagnostic-state.h
new file mode 100644 (file)
index 0000000..68c3e00
--- /dev/null
@@ -0,0 +1,37 @@
+/* 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 */
index 10e77056dee0ef88155110d835a1341a7b06d3c2..196b4b1438e2ca1c1fcb1d34bfa79b77eed05b62 100644 (file)
@@ -18,6 +18,8 @@ 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"
index 755db7cde53871412ef31e857b390d8bcbde31cd..4dd14c8895cfda7096a9ffc3f466c33dfc4e371c 100644 (file)
@@ -619,6 +619,25 @@ python-xdot)
 @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
@@ -688,6 +707,15 @@ extern void __analyzer_dump_capacity (const void *ptr);
 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);
@@ -764,6 +792,14 @@ will emit a warning describing the state of the 2nd argument
 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);
index 91b0a201e1b6497d1edcfa26016f240ff60650d7..693bd57691e2fe8c84d735002d2224e27280ce5e 100644 (file)
@@ -6131,6 +6131,18 @@ in this release.
 
 @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
@@ -6154,6 +6166,35 @@ for viewing results.  Defaults to yes.
 
 @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,
index 65bb6cbcd768b070f89072ec21c39c5c5469af7b..f8cedc023b86e08ca36127c4a8f1c845247d00b0 100644 (file)
@@ -18,19 +18,39 @@ 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_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
@@ -40,8 +60,8 @@ graphviz_out::print (const char *fmt, ...)
 
   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);
 }
 
@@ -57,20 +77,11 @@ graphviz_out::println (const char *fmt, ...)
 
   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
@@ -79,8 +90,8 @@ graphviz_out::write_indent ()
 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
@@ -89,8 +100,8 @@ graphviz_out::begin_tr ()
 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
@@ -99,8 +110,8 @@ graphviz_out::end_tr ()
 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
@@ -109,8 +120,8 @@ graphviz_out::begin_td ()
 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
@@ -119,8 +130,8 @@ graphviz_out::end_td ()
 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
@@ -129,6 +140,628 @@ graphviz_out::begin_trtd ()
 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 */
index 9a1ca0416769fba7c55777ba3d0d79c676b1efa4..5943589f947f7f355baf63c9aec94e7e14ca7bb1 100644 (file)
@@ -23,10 +23,356 @@ along with GCC; see the file COPYING3.  If not see
 
 #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);
 
@@ -35,11 +381,6 @@ class graphviz_out {
   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 ();
 
@@ -48,12 +389,6 @@ class graphviz_out {
 
   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 */
index 3629f17f0ceedf60024b0890b84ca60106ed3e50..1a7d83c0476ee980452b0feedfd1f05a97a0c118 100644 (file)
@@ -450,6 +450,7 @@ sarif_scheme_handler::make_sink (const context &ctxt,
   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;
@@ -487,12 +488,20 @@ sarif_scheme_handler::make_sink (const context &ctxt,
            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;
@@ -517,6 +526,7 @@ sarif_scheme_handler::make_sink (const context &ctxt,
 
   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)
@@ -547,6 +557,10 @@ html_scheme_handler::make_sink (const context &ctxt,
   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;
@@ -570,12 +584,36 @@ html_scheme_handler::make_sink (const context &ctxt,
            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;
@@ -600,6 +638,9 @@ html_scheme_handler::make_sink (const context &ctxt,
   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,
diff --git a/gcc/pex.cc b/gcc/pex.cc
new file mode 100644 (file)
index 0000000..44a3ff1
--- /dev/null
@@ -0,0 +1,80 @@
+/* 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);
+}
diff --git a/gcc/pex.h b/gcc/pex.h
new file mode 100644 (file)
index 0000000..9c6816a
--- /dev/null
+++ b/gcc/pex.h
@@ -0,0 +1,100 @@
+/* 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 */
index df49a67c9c91a654475da393b722492452a0397a..2d8573ce28fca691ccfd2cf08e5c93ea194843a2 100644 (file)
@@ -81,6 +81,7 @@ selftest::run_tests ()
   ordered_hash_map_tests_cc_tests ();
   splay_tree_cc_tests ();
   xml_cc_tests ();
+  graphviz_cc_tests ();
 
   /* Mid-level data structures.  */
   input_cc_tests ();
index 94acf62b434ed004c26c0e757d6a891b765ae97a..a6c96027ce048ef89794c4698a8da2f3fe6df44e 100644 (file)
@@ -239,6 +239,7 @@ extern void gcc_urlifier_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 ();
diff --git a/gcc/testsuite/g++.dg/analyzer/state-diagram.C b/gcc/testsuite/g++.dg/analyzer/state-diagram.C
new file mode 100644 (file)
index 0000000..dd9be6a
--- /dev/null
@@ -0,0 +1,15 @@
+#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'" }
index 372a13608d0961bebe7a4e6f6ac77acb09574f62..db0140bee81f847138a45138ec169ec92f9562b8 100644 (file)
@@ -30,6 +30,10 @@ extern void __analyzer_dump (void);
 /* 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);
 
@@ -58,6 +62,9 @@ extern void __analyzer_dump_region_model (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);
diff --git a/gcc/testsuite/gcc.dg/analyzer/state-diagram-1-sarif.py b/gcc/testsuite/gcc.dg/analyzer/state-diagram-1-sarif.py
new file mode 100644 (file)
index 0000000..d2967d4
--- /dev/null
@@ -0,0 +1,32 @@
+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
+
diff --git a/gcc/testsuite/gcc.dg/analyzer/state-diagram-1.c b/gcc/testsuite/gcc.dg/analyzer/state-diagram-1.c
new file mode 100644 (file)
index 0000000..3d853d2
--- /dev/null
@@ -0,0 +1,48 @@
+/* { 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" } } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/state-diagram-2.c b/gcc/testsuite/gcc.dg/analyzer/state-diagram-2.c
new file mode 100644 (file)
index 0000000..bde2bc4
--- /dev/null
@@ -0,0 +1,17 @@
+#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" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/state-diagram-3.c b/gcc/testsuite/gcc.dg/analyzer/state-diagram-3.c
new file mode 100644 (file)
index 0000000..a28553d
--- /dev/null
@@ -0,0 +1,20 @@
+#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);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/state-diagram-4.c b/gcc/testsuite/gcc.dg/analyzer/state-diagram-4.c
new file mode 100644 (file)
index 0000000..37c654f
--- /dev/null
@@ -0,0 +1,8 @@
+#include "analyzer-decls.h"
+
+void test ()
+{
+  void *p = __builtin_malloc (1024);
+  __builtin_free (p);
+  __builtin_free (p); /* { dg-warning "-Wanalyzer-double-free" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/state-diagram-5-html.py b/gcc/testsuite/gcc.dg/analyzer/state-diagram-5-html.py
new file mode 100644 (file)
index 0000000..79374b2
--- /dev/null
@@ -0,0 +1,29 @@
+# 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
diff --git a/gcc/testsuite/gcc.dg/analyzer/state-diagram-5-sarif.py b/gcc/testsuite/gcc.dg/analyzer/state-diagram-5-sarif.py
new file mode 100644 (file)
index 0000000..484da96
--- /dev/null
@@ -0,0 +1,73 @@
+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'
diff --git a/gcc/testsuite/gcc.dg/analyzer/state-diagram-5.c b/gcc/testsuite/gcc.dg/analyzer/state-diagram-5.c
new file mode 100644 (file)
index 0000000..8e00cac
--- /dev/null
@@ -0,0 +1,48 @@
+/* { 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" } } */
index 0f1f8640d65cb93fa7bf0ef8ad831fcd81f3c687..1fe5b5c7a69ee4c93da65c1666dc84bf47b2df46 100644 (file)
@@ -2,6 +2,7 @@
 /* { dg-options "-g" } */
 
 #define INCLUDE_MEMORY
+#define INCLUDE_STRING
 #define INCLUDE_VECTOR
 #include "gcc-plugin.h"
 #include "config.h"
index 3cac3f8f923dc8e3bbf28d863f849317f055cabb..6f4cbc23c7b39dab4e024f5543f6f5c8be1ce0b5 100644 (file)
@@ -5,6 +5,7 @@
 /* { dg-options "-g" } */
 
 #define INCLUDE_MEMORY
+#define INCLUDE_STRING
 #define INCLUDE_VECTOR
 #include "gcc-plugin.h"
 #include "config.h"
index 771ff75c91156cd137db963ffd5a42c40e8d5e66..18e054b5dd65b594cc37d76fd851af99f04d5017 100644 (file)
@@ -2,6 +2,7 @@
 /* { dg-options "-g" } */
 
 #define INCLUDE_MEMORY
+#define INCLUDE_STRING
 #define INCLUDE_VECTOR
 #include "gcc-plugin.h"
 #include "config.h"
index c7087f0dfd9b5b7e7473245d55a392c5c25ef3a4..5a6e0759b4fe2a91b52449a5435c13abf6b52dad 100644 (file)
@@ -2,6 +2,7 @@
 /* { dg-options "-g" } */
 
 #define INCLUDE_MEMORY
+#define INCLUDE_STRING
 #define INCLUDE_VECTOR
 #include "gcc-plugin.h"
 #include "config.h"
index 49ed4b0358204d265b25a89147980408cd9ca55d..35f524c8d55c6c1a09ed9dcb699d259948a99cda 100644 (file)
@@ -9,7 +9,9 @@ def html_tree_from_env():
     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
index 7daf35b581907408a5e99527249c3ba5617c089d..384de2fb0154aae6c2b1b133a07a5c99e5ba8b97 100644 (file)
@@ -1,5 +1,6 @@
 import json
 import os
+import xml.etree.ElementTree as ET
 
 def sarif_from_env():
     # return parsed JSON content a SARIF_PATH file
@@ -21,3 +22,17 @@ def get_location_snippet_text(location):
 
 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    
index a23298bdd84bdf2fffb2b3b8f036577141c9af3f..6bb269a2a19e06f4ae3013876fc0165b42ccdd81 100644 (file)
@@ -128,6 +128,22 @@ node_with_children::add_text_from_pp (pretty_printer &pp)
   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
@@ -193,6 +209,33 @@ element::set_attr (const char *name, std::string value)
   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
@@ -363,6 +406,15 @@ test_printer ()
      "    </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.
@@ -393,6 +445,19 @@ test_attribute_ordering ()
      "</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
@@ -401,6 +466,7 @@ xml_cc_tests ()
   test_no_dtd ();
   test_printer ();
   test_attribute_ordering ();
+  test_comment ();
 }
 
 } // namespace selftest
index 952cfa4376b081c0481ffd625abbcecfd38e6de5..15591290c7d71f301f134b3167a8176443ed56a8 100644 (file)
--- a/gcc/xml.h
+++ b/gcc/xml.h
@@ -30,6 +30,8 @@ struct node;
     struct document;
     struct element;
   struct doctypedecl;
+  struct comment;
+  struct raw;
 
 struct node
 {
@@ -38,7 +40,11 @@ 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); }
@@ -66,6 +72,9 @@ struct node_with_children : public node
   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;
 };
@@ -90,10 +99,16 @@ struct element : public node_with_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;
@@ -101,6 +116,21 @@ struct element : public node_with_children
   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.  */