diff options
author | David Malcolm <dmalcolm@redhat.com> | 2025-06-23 11:06:43 -0400 |
---|---|---|
committer | David Malcolm <dmalcolm@redhat.com> | 2025-06-23 11:19:24 -0400 |
commit | 2334d30cd8feac28831ffef857cd09753a3ca3d3 (patch) | |
tree | 6ed24ac0d8f118e10469282b4c75d55b13150a8e /gcc | |
parent | 5a64c96cfe7a4d5783319c2fb8472bc75d702e9a (diff) | |
download | gcc-2334d30cd8feac28831ffef857cd09753a3ca3d3.zip gcc-2334d30cd8feac28831ffef857cd09753a3ca3d3.tar.gz gcc-2334d30cd8feac28831ffef857cd09753a3ca3d3.tar.bz2 |
diagnostics: add state diagrams to analyzer experimental-html output [PR116792]
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>
Diffstat (limited to 'gcc')
63 files changed, 3722 insertions, 102 deletions
diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 56fa5ac..f106e83 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -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 index 0000000..b85a2f1 --- /dev/null +++ b/gcc/analyzer/ana-state-to-diagnostic-state.cc @@ -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 ®) +{ + e.set_attr ("region_id", std::to_string (reg.get_id ())); +} + +// class xml_state : public xml::document + +xml_state::xml_state (const program_state &state, + const extrinsic_state &ext_state) +: xml::document (), + m_state (state), + m_ext_state (ext_state), + m_mgr (*ext_state.get_engine ()->get_model_manager ()), + m_root (nullptr) +{ + auto root = std::make_unique<xml::element> ("state-diagram", false); + m_root = root.get (); + add_child (std::move (root)); + + /* Find pointers to heap-allocated regions, and record their types, + so that we have a user-friendly way of showing the memory + (by field, rather than by byte offset). */ + for (auto cluster_iter : *state.m_region_model->get_store ()) + for (auto binding_iter : *cluster_iter.second) + { + const svalue *svalue = binding_iter.second; + if (const region *reg = svalue->maybe_get_region ()) + if (svalue->get_type () && !reg->get_type ()) + { + tree pointed_to_type = TREE_TYPE (svalue->get_type ()); + if (!VOID_TYPE_P (pointed_to_type)) + m_types_for_untyped_regions[reg] = pointed_to_type; + } + } + + /* TODO: look for vtable pointers at the top of dynamically-allocated + regions and use that type as a fallback. */ + + /* Find regions of interest. + Create elements per region, and build into hierarchy. + Add edges for pointers. */ + + /* Create stack, from top to bottom. */ + for (int i = state.m_region_model->get_stack_depth () - 1; i >= 0; --i) + { + const frame_region *reg = state.m_region_model->get_frame_at_index (i); + get_or_create_element (*reg); + } + + /* Create bound memory. */ + for (auto iter : *state.m_region_model->get_store ()) + { + const bool create_all = false; // "true" for verbose, for debugging + create_elements_for_binding_cluster (*iter.second, create_all); + } + + /* TODO: Constraints. */ + + /* Annotate with information from state machines. */ + { + int i; + sm_state_map *smap; + FOR_EACH_VEC_ELT (state.m_checker_states, i, smap) + { + auto &sm = ext_state.get_sm (i); + for (const auto &iter : *smap) + sm.add_state_to_xml (*this, *iter.first, iter.second.m_state); + if (auto s = smap->get_global_state ()) + sm.add_global_state_to_xml (*this, s); + } + } +} + +xml::element & +xml_state::get_or_create_element (const region ®) +{ + auto existing = m_region_to_element_map.find (®); + if (existing != m_region_to_element_map.end ()) + return *existing->second; + + auto &e = create_and_add_element (reg); + m_region_to_element_map[®] = &e; + return e; +} + +xml::element& +xml_state::create_and_add_element (const region ®) +{ + auto e = create_element (reg); + xml::element &result = *e; + if (auto parent_reg = reg.get_parent_region ()) + { + auto parent_element = &get_or_create_element (*parent_reg); + parent_element->add_child (std::move (e)); + } + else + m_root->add_child (std::move (e)); + return result; +} + +std::unique_ptr<xml::element> +xml_state::make_memory_space_element (const char *label) +{ + auto e = std::make_unique<xml::element> ("memory-space", false); + e->set_attr ("label", label); + return e; +} + +std::unique_ptr<xml::element> +xml_state::create_element (const region ®) +{ + std::unique_ptr<xml::element> e; + switch (reg.get_kind ()) + { + default: + gcc_unreachable (); + + case RK_FRAME: + { + e = std::make_unique<xml::element> ("stack-frame", false); + const frame_region &frame_reg + = static_cast<const frame_region &> (reg); + { + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + pp_printf (&pp, "%E", frame_reg.get_fndecl ()); + e->set_attr ("function", pp_formatted_text (&pp)); + } + } + break; + case RK_GLOBALS: + e = make_memory_space_element ("Globals"); + break; + case RK_CODE: + e = make_memory_space_element ("Code"); + break; + case RK_FUNCTION: + e = std::make_unique<xml::element> ("function", false); + // TODO + break; + case RK_LABEL: + e = std::make_unique<xml::element> ("label", false); + // TODO + break; + case RK_STACK: + e = std::make_unique<xml::element> ("stack", false); + break; + case RK_HEAP: + e = make_memory_space_element ("Heap"); + break; + case RK_THREAD_LOCAL: + e = make_memory_space_element ("Thread-local"); + break; + case RK_ROOT: + e = std::make_unique<xml::element> ("memory-regions", false); + break; + case RK_SYMBOLIC: + e = std::make_unique<xml::element> ("symbolic-region", false); + // TODO + break; + case RK_DECL: + { + e = std::make_unique<xml::element> ("variable", false); + const decl_region &decl_reg + = static_cast<const decl_region &> (reg); + { + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + pp_printf (&pp, "%E", decl_reg.get_decl ()); + e->set_attr ("name", pp_formatted_text (&pp)); + } + set_type_attr (*e, TREE_TYPE (decl_reg.get_decl ())); + } + break; + case RK_FIELD: + e = std::make_unique<xml::element> ("field", false); + break; + case RK_ELEMENT: + e = std::make_unique<xml::element> ("element", false); + break; + case RK_OFFSET: + e = std::make_unique<xml::element> ("offset-region", false); + // TODO + break; + case RK_SIZED: + e = std::make_unique<xml::element> ("sized-region", false); + // TODO + break; + case RK_CAST: + e = std::make_unique<xml::element> ("cast-region", false); + // TODO + break; + case RK_HEAP_ALLOCATED: + e = std::make_unique<xml::element> ("heap-buffer", false); + set_attr_for_dynamic_extents (reg, *e); + break; + case RK_ALLOCA: + e = std::make_unique<xml::element> ("alloca-buffer", false); + set_attr_for_dynamic_extents (reg, *e); + break; + case RK_STRING: + e = std::make_unique<xml::element> ("string-region", false); + // TODO + break; + case RK_BIT_RANGE: + e = std::make_unique<xml::element> ("RK_BIT_RANGE", false); // TODO + break; + case RK_VAR_ARG: + e = std::make_unique<xml::element> ("RK_VAR_ARG", false); // TODO + break; + case RK_ERRNO: + e = std::make_unique<xml::element> ("errno", false); + break; + case RK_PRIVATE: + e = std::make_unique<xml::element> ("RK_PRIVATE", false); // TODO + break; + case RK_UNKNOWN: + e = std::make_unique<xml::element> ("RK_UNKNOWN", false); // TODO + break; + } + gcc_assert (e); + + set_region_id_attr (*e, reg); + + if (reg.get_base_region () == ®) + if (!reg.get_type ()) + { + auto search + = m_types_for_untyped_regions.find (®); + if (search != m_types_for_untyped_regions.end ()) + { + tree type_to_use = search->second; + set_type_attr (*e, type_to_use); + } + } + + return e; +} + +void +xml_state::create_elements_for_binding_cluster (const binding_cluster &cluster, + bool create_all) +{ + /* TODO: + - symbolic bindings + - get current svalue, so as to get "zeros" and "uninitialized". */ + + concrete_bindings_t conc_bindings; + for (auto iter : cluster) + { + const binding_key *key = iter.first; + const svalue *svalue = iter.second; + if (auto conc_key = key->dyn_cast_concrete_binding ()) + conc_bindings[conc_key->get_bit_range ()] = svalue; + if (const region *reg = svalue->maybe_get_region ()) + get_or_create_element (*reg); + } + + auto &e = get_or_create_element (*cluster.get_base_region ()); + + e.add_child (create_element_for_conc_bindings (conc_bindings)); + + const region *typed_reg = cluster.get_base_region (); + if (!typed_reg->get_type ()) + { + auto search + = m_types_for_untyped_regions.find (cluster.get_base_region ()); + if (search != m_types_for_untyped_regions.end ()) + { + tree type_to_use = search->second; + typed_reg = m_mgr.get_cast_region (typed_reg, type_to_use); + } + } + + if (typed_reg->get_type ()) + populate_element_for_typed_region (e, + *typed_reg, + conc_bindings, + create_all); + else + { + // TODO + } +} + +std::unique_ptr<xml::element> +xml_state::create_element_for_conc_bindings (const concrete_bindings_t &conc_bindings) +{ + auto e = std::make_unique<xml::element> ("concrete-bindings", false); + for (auto iter : conc_bindings) + { + const bit_range bits = iter.first; + const svalue *sval = iter.second; + auto binding_element + = std::make_unique<xml::element> ("binding", false); + set_bits_attr (*binding_element, bits); + { + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + sval->dump_to_pp (&pp, true); + binding_element->set_attr ("value", pp_formatted_text (&pp)); + if (auto svalue_element = create_element_for_svalue (sval)) + binding_element->add_child (std::move (svalue_element)); + } + e->add_child (std::move (binding_element)); + } + return e; +} + +// Try to get the bit_range of REG within its base region +bool +xml_state::get_bit_range_within_base_region (const region ®, + bit_range &out) +{ + region_offset start_offset = reg.get_offset (&m_mgr); + if (!start_offset.concrete_p ()) + return false; + region_offset next_offset = reg.get_next_offset (&m_mgr); + if (!next_offset.concrete_p ()) + return false; + out = bit_range (start_offset.get_bit_offset (), + next_offset.get_bit_offset () + - start_offset.get_bit_offset ()); + return true; +} + +void +xml_state::populate_element_for_typed_region (xml::element &e, + const region ®, + const concrete_bindings_t &conc_bindings, + bool create_all) +{ + const_tree reg_type = reg.get_type (); + gcc_assert (reg_type); + set_type_attr (e, reg_type); + + bit_range bits (0, 0); + if (get_bit_range_within_base_region (reg, bits)) + { + set_bits_attr (e, bits); + + auto search = conc_bindings.find (bits); + if (search != conc_bindings.end ()) + { + const svalue *bound_sval = search->second; + if (auto svalue_element = create_element_for_svalue (bound_sval)) + { + xml::printer xp (e); + xp.push_tag ("value-of-region"); + xp.append (std::move (svalue_element)); + } + } + } + + switch (TREE_CODE (reg_type)) + { + default: + break; + + case ARRAY_TYPE: + { + tree domain = TYPE_DOMAIN (reg_type); + if (!domain) + return; + const_tree max_idx = TYPE_MAX_VALUE (domain); + if (!max_idx) + return; + if (TREE_CODE (max_idx) != INTEGER_CST) + return; + const_tree min_idx = TYPE_MIN_VALUE (domain); + if (TREE_CODE (min_idx) != INTEGER_CST) + return; + for (offset_int idx = wi::to_offset (min_idx); + idx <= wi::to_offset (max_idx); + ++idx) + { + const_tree element_type = TREE_TYPE (reg_type); + const svalue *sval_index + = m_mgr.get_or_create_int_cst (domain, idx); + const region *child_reg + = m_mgr.get_element_region (®, + const_cast<tree> (element_type), + sval_index); + if (show_child_element_for_child_region_p (*child_reg, + conc_bindings, + create_all)) + { + // Here "element" is in the xml sense + auto child_element + = std::make_unique<xml::element> ("element", false); + set_wi_attr (*child_element, "index", idx, UNSIGNED); + set_region_id_attr (*child_element, *child_reg); + // Recurse: + gcc_assert (element_type); + populate_element_for_typed_region (*child_element, + *child_reg, + conc_bindings, + create_all); + e.add_child (std::move (child_element)); + } + } + } + break; + + case RECORD_TYPE: + { + const record_layout layout (reg_type); + for (auto item : layout) + { + if (item.m_is_padding) + { + const bit_range bits (0, item.m_bit_range.m_size_in_bits); + const region *child_reg + = m_mgr.get_bit_range (®, NULL_TREE, bits); + if (show_child_element_for_child_region_p (*child_reg, + conc_bindings, + create_all)) + { + auto child_element + = std::make_unique<xml::element> ("padding", false); + set_wi_attr (*child_element, "num_bits", + item.m_bit_range.m_size_in_bits, SIGNED); + e.add_child (std::move (child_element)); + } + } + else + { + const region *child_reg + = m_mgr.get_field_region (®, + const_cast<tree> (item.m_field)); + if (show_child_element_for_child_region_p (*child_reg, + conc_bindings, + create_all)) + { + auto child_element + = std::make_unique<xml::element> ("field", false); + { + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + pp_printf (&pp, "%D", item.m_field); + child_element->set_attr ("name", + pp_formatted_text (&pp)); + } + set_region_id_attr (*child_element, *child_reg); + // Recurse: + populate_element_for_typed_region (*child_element, + *child_reg, + conc_bindings, + create_all); + e.add_child (std::move (child_element)); + } + } + } + } + break; + } +} + +void +xml_state::set_attr_for_dynamic_extents (const region ®, xml::element &e) +{ + const svalue *sval = m_state.m_region_model->get_dynamic_extents (®); + if (sval) + { + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + if (auto cst = sval->maybe_get_constant ()) + pp_wide_int (&pp, wi::to_wide (cst), UNSIGNED); + else + sval->dump_to_pp (&pp, true); + e.set_attr ("dynamic-extents", pp_formatted_text (&pp)); + } +} + +bool +xml_state:: +show_child_element_for_child_region_p (const region ®, + const concrete_bindings_t &conc_bindings, + bool create_all) +{ + if (create_all) + return true; + bit_range reg_bits (0, 0); + if (!get_bit_range_within_base_region (reg, reg_bits)) + return true; + + /* Is any of "bits" bound? + TODO: ideally there would be a more efficient way to do this, using + spatial relationships. */ + for (auto iter : conc_bindings) + { + const bit_range bound_bits = iter.first; + if (bound_bits.intersects_p (reg_bits)) + return true; + } + return false; +} + +std::unique_ptr<xml::element> +xml_state::create_element_for_svalue (const svalue *sval) +{ + if (!sval) + return nullptr; + + std::unique_ptr<xml::element> result; + switch (sval->get_kind ()) + { + default: + gcc_unreachable (); + case SK_REGION: + { + const region_svalue *region_sval = (const region_svalue *)sval; + result + = std::make_unique<xml::element> ("pointer-to-region", false); + set_region_id_attr (*result, *region_sval->get_pointee ()); + } + break; + case SK_CONSTANT: + { + const constant_svalue *constant_sval = (const constant_svalue *)sval; + result = std::make_unique<xml::element> ("constant", false); + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + pp_printf (&pp, "%E", constant_sval->get_constant ()); + result->set_attr ("value", pp_formatted_text (&pp)); + } + break; + case SK_UNKNOWN: + result = std::make_unique<xml::element> ("unknown", false); + break; + case SK_POISONED: + { + const poisoned_svalue *poisoned_sval = (const poisoned_svalue *)sval; + switch (poisoned_sval->get_poison_kind ()) + { + default: + gcc_unreachable (); + case poison_kind::uninit: + result = std::make_unique<xml::element> ("uninitialized", false); + break; + case poison_kind::freed: + result = std::make_unique<xml::element> ("freed", false); + break; + case poison_kind::deleted: + result = std::make_unique<xml::element> ("deleted", false); + break; + case poison_kind::popped_stack: + result = std::make_unique<xml::element> ("popped-stack", false); + break; + } + } + break; + case SK_SETJMP: + { + //const setjmp_svalue *setjmp_sval = (const setjmp_svalue *)sval; + result = std::make_unique<xml::element> ("setjmp-buffer", false); + // TODO + } + break; + case SK_INITIAL: + { + const initial_svalue *initial_sval = (const initial_svalue *)sval; + result = std::make_unique<xml::element> ("initial-value-of", false); + set_region_id_attr (*result, *initial_sval->get_region ()); + } + break; + case SK_UNARYOP: + { + const unaryop_svalue *unaryop_sval = (const unaryop_svalue *)sval; + result = std::make_unique<xml::element> ("unary-op", false); + result->set_attr ("op", get_tree_code_name (unaryop_sval->get_op ())); + result->add_child + (create_element_for_svalue (unaryop_sval->get_arg ())); + } + break; + case SK_BINOP: + { + const binop_svalue *binop_sval = (const binop_svalue *)sval; + result = std::make_unique<xml::element> ("binary-op", false); + result->set_attr ("op", get_tree_code_name (binop_sval->get_op ())); + result->add_child (create_element_for_svalue (binop_sval->get_arg0 ())); + result->add_child (create_element_for_svalue (binop_sval->get_arg1 ())); + } + break; + case SK_SUB: + { + //const sub_svalue *sub_sval = (const sub_svalue *)sval; + result = std::make_unique<xml::element> ("subregion-value", false); + // TODO + } + break; + case SK_REPEATED: + { + const repeated_svalue *repeated_sval = (const repeated_svalue *)sval; + result = std::make_unique<xml::element> ("repeated-value", false); + result->add_child + (create_element_for_svalue (repeated_sval->get_outer_size ())); + result->add_child + (create_element_for_svalue (repeated_sval->get_inner_svalue ())); + } + break; + case SK_BITS_WITHIN: + { + const bits_within_svalue *bits_within_sval + = (const bits_within_svalue *)sval; + result = std::make_unique<xml::element> ("bits-within", false); + set_bits_attr (*result, bits_within_sval->get_bits ()); + result->add_child + (create_element_for_svalue (bits_within_sval->get_inner_svalue ())); + } + break; + case SK_UNMERGEABLE: + { + const unmergeable_svalue *unmergeable_sval + = (const unmergeable_svalue *)sval; + result = std::make_unique<xml::element> ("unmergeable", false); + result->add_child + (create_element_for_svalue (unmergeable_sval->get_arg ())); + } + break; + case SK_PLACEHOLDER: + { + const placeholder_svalue *placeholder_sval + = (const placeholder_svalue *)sval; + result = std::make_unique<xml::element> ("placeholder", false); + result->set_attr ("name", placeholder_sval->get_name ()); + } + break; + case SK_WIDENING: + { + //const widening_svalue *widening_sval = (const widening_svalue *)sval; + result = std::make_unique<xml::element> ("iterating-value", false); + // TODO + } + break; + case SK_COMPOUND: + { + //const compound_svalue *compound_sval = (const compound_svalue *)sval; + result = std::make_unique<xml::element> ("compound-value", false); + // TODO + } + break; + case SK_CONJURED: + { + //const conjured_svalue *conjured_sval = (const conjured_svalue *)sval; + result = std::make_unique<xml::element> ("conjured-value", false); + // TODO + } + break; + case SK_ASM_OUTPUT: + { + /* const asm_output_svalue *asm_output_sval + = (const asm_output_svalue *)sval; */ + result = std::make_unique<xml::element> ("asm-output", false); + // TODO + } + break; + case SK_CONST_FN_RESULT: + { + /* const const_fn_result_svalue *const_fn_result_sval + = (const const_fn_result_svalue *)sval; */ + result = std::make_unique<xml::element> ("const-fn-result", false); + // TODO + } + } + + if (result) + { + if (sval->get_type ()) + set_type_attr (*result, sval->get_type ()); + + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + sval->dump_to_pp (&pp, true); + result->set_attr ("dump-text", pp_formatted_text (&pp)); + } + + return result; +} + +std::unique_ptr<xml::document> +program_state::make_xml (const extrinsic_state &ext_state) const +{ + return std::make_unique<xml_state> (*this, ext_state); +} + +void +program_state::dump_xml_to_pp (const extrinsic_state &ext_state, + pretty_printer *pp) const +{ + auto doc = make_xml (ext_state); + doc->write_as_xml (pp, 0, true); +} + +void +program_state::dump_xml_to_file (const extrinsic_state &ext_state, + FILE *outf) const +{ + pretty_printer pp; + pp.set_output_stream (outf); + dump_xml_to_pp (ext_state, &pp); + pp_flush (&pp); +} + +void +program_state::dump_xml (const extrinsic_state &ext_state) const +{ + dump_xml_to_file (ext_state, stderr); +} + +} // namespace ana + +#endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/ana-state-to-diagnostic-state.h b/gcc/analyzer/ana-state-to-diagnostic-state.h new file mode 100644 index 0000000..bd6aa46 --- /dev/null +++ b/gcc/analyzer/ana-state-to-diagnostic-state.h @@ -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 ®); + +private: + xml::element& + create_and_add_element (const region ®); + + static std::unique_ptr<xml::element> + make_memory_space_element (const char *label); + + std::unique_ptr<xml::element> + create_element (const region ®); + + /* Spatially sorted concrete bindings. */ + typedef std::map<bit_range, const svalue *> concrete_bindings_t; + + void + create_elements_for_binding_cluster (const binding_cluster &cluster, + bool create_all); + + std::unique_ptr<xml::element> + create_element_for_conc_bindings (const concrete_bindings_t &conc_bindings); + + // Try to get the bit_range of REG within its base region + bool + get_bit_range_within_base_region (const region ®, + bit_range &out); + + void + populate_element_for_typed_region (xml::element &e, + const region ®, + const concrete_bindings_t &conc_bindings, + bool create_all); + + void + set_attr_for_dynamic_extents (const region ®, xml::element &e); + + bool + show_child_element_for_child_region_p (const region ®, + const concrete_bindings_t &conc_bindings, + bool create_all); + + std::unique_ptr<xml::element> + create_element_for_svalue (const svalue *sval); + + const program_state &m_state; + const extrinsic_state &m_ext_state; + region_model_manager &m_mgr; + xml::element *m_root; + std::map<const region *, xml::element *> m_region_to_element_map; + std::map<const region *, tree> m_types_for_untyped_regions; +}; + +} // namespace ana + +#endif /* GCC_ANALYZER_ANA_STATE_TO_XML_STATE_H */ diff --git a/gcc/analyzer/checker-event.cc b/gcc/analyzer/checker-event.cc index e041778..04b66bf 100644 --- a/gcc/analyzer/checker-event.cc +++ b/gcc/analyzer/checker-event.cc @@ -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 */ diff --git a/gcc/analyzer/checker-event.h b/gcc/analyzer/checker-event.h index e29173a..7c44f1e 100644 --- a/gcc/analyzer/checker-event.h +++ b/gcc/analyzer/checker-event.h @@ -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 diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h index 80c975c..3c174bf 100644 --- a/gcc/analyzer/checker-path.h +++ b/gcc/analyzer/checker-path.h @@ -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. */ diff --git a/gcc/analyzer/common.h b/gcc/analyzer/common.h index cb03004..c35cdb2 100644 --- a/gcc/analyzer/common.h +++ b/gcc/analyzer/common.h @@ -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" diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index 6867dd2..d3cf993 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -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. */ diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index c3e4800..5eb9048 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -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 (), diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h index 23e344d..ca0b787 100644 --- a/gcc/analyzer/exploded-graph.h +++ b/gcc/analyzer/exploded-graph.h @@ -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 {} diff --git a/gcc/analyzer/infinite-recursion.cc b/gcc/analyzer/infinite-recursion.cc index 0641117..bf5c194 100644 --- a/gcc/analyzer/infinite-recursion.cc +++ b/gcc/analyzer/infinite-recursion.cc @@ -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); } diff --git a/gcc/analyzer/kf-analyzer.cc b/gcc/analyzer/kf-analyzer.cc index 3e671e5..13476de 100644 --- a/gcc/analyzer/kf-analyzer.cc +++ b/gcc/analyzer/kf-analyzer.cc @@ -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)); } }; diff --git a/gcc/analyzer/pending-diagnostic.cc b/gcc/analyzer/pending-diagnostic.cc index 70dc815..b727392 100644 --- a/gcc/analyzer/pending-diagnostic.cc +++ b/gcc/analyzer/pending-diagnostic.cc @@ -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 diff --git a/gcc/analyzer/pending-diagnostic.h b/gcc/analyzer/pending-diagnostic.h index 6a0289e..469513c 100644 --- a/gcc/analyzer/pending-diagnostic.h +++ b/gcc/analyzer/pending-diagnostic.h @@ -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. */ diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index 21f78e5..0d9199f 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -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. */ diff --git a/gcc/analyzer/program-state.h b/gcc/analyzer/program-state.h index 269ffde..d8bd920 100644 --- a/gcc/analyzer/program-state.h +++ b/gcc/analyzer/program-state.h @@ -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; diff --git a/gcc/analyzer/record-layout.cc b/gcc/analyzer/record-layout.cc index aaf8ccd..7edc81c 100644 --- a/gcc/analyzer/record-layout.cc +++ b/gcc/analyzer/record-layout.cc @@ -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); diff --git a/gcc/analyzer/record-layout.h b/gcc/analyzer/record-layout.h index c1c4189..8649d1d 100644 --- a/gcc/analyzer/record-layout.h +++ b/gcc/analyzer/record-layout.h @@ -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); diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 1ee882c..d1c7e8c 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -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", diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 2c7f737..781a585 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -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) diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc index cee8d2d..09c3115 100644 --- a/gcc/analyzer/sm-fd.cc +++ b/gcc/analyzer/sm-fd.cc @@ -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 diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc index d7dbe2f..61e2424 100644 --- a/gcc/analyzer/sm-file.cc +++ b/gcc/analyzer/sm-file.cc @@ -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 diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc index 333dfea..ee60b96 100644 --- a/gcc/analyzer/sm-malloc.cc +++ b/gcc/analyzer/sm-malloc.cc @@ -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 ®_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. */ diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc index 0abbdd6..54bd92c 100644 --- a/gcc/analyzer/sm.cc +++ b/gcc/analyzer/sm.cc @@ -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 * diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h index a932765..f366be1 100644 --- a/gcc/analyzer/sm.h +++ b/gcc/analyzer/sm.h @@ -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) diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h index 171324c..c855394 100644 --- a/gcc/analyzer/store.h +++ b/gcc/analyzer/store.h @@ -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; }; diff --git a/gcc/analyzer/varargs.cc b/gcc/analyzer/varargs.cc index 6ea0d29..3b8f863 100644 --- a/gcc/analyzer/varargs.cc +++ b/gcc/analyzer/varargs.cc @@ -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 diff --git a/gcc/diagnostic-format-html.cc b/gcc/diagnostic-format-html.cc index 5668b50..b1b0895 100644 --- a/gcc/diagnostic-format-html.cc +++ b/gcc/diagnostic-format-html.cc @@ -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); diff --git a/gcc/diagnostic-format-html.h b/gcc/diagnostic-format-html.h index 9ca43f2..09a97e0 100644 --- a/gcc/diagnostic-format-html.h +++ b/gcc/diagnostic-format-html.h @@ -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 diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc index 853a833..d4ebe5a 100644 --- a/gcc/diagnostic-format-sarif.cc +++ b/gcc/diagnostic-format-sarif.cc @@ -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) { } diff --git a/gcc/diagnostic-format-sarif.h b/gcc/diagnostic-format-sarif.h index 763dc25..c3ae330 100644 --- a/gcc/diagnostic-format-sarif.h +++ b/gcc/diagnostic-format-sarif.h @@ -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> diff --git a/gcc/diagnostic-path.cc b/gcc/diagnostic-path.cc index 7a9c051..0c617ed 100644 --- a/gcc/diagnostic-path.cc +++ b/gcc/diagnostic-path.cc @@ -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. diff --git a/gcc/diagnostic-path.h b/gcc/diagnostic-path.h index 4419139..a3987a0 100644 --- a/gcc/diagnostic-path.h +++ b/gcc/diagnostic-path.h @@ -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 index 0000000..b6d7ec5 --- /dev/null +++ b/gcc/diagnostic-state-to-dot.cc @@ -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 index 0000000..68c3e00 --- /dev/null +++ b/gcc/diagnostic-state.h @@ -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 */ diff --git a/gcc/digraph.cc b/gcc/digraph.cc index 10e7705..196b4b1 100644 --- a/gcc/digraph.cc +++ b/gcc/digraph.cc @@ -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" diff --git a/gcc/doc/analyzer.texi b/gcc/doc/analyzer.texi index 755db7c..4dd14c8 100644 --- a/gcc/doc/analyzer.texi +++ b/gcc/doc/analyzer.texi @@ -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); diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 91b0a20..693bd57 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -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, diff --git a/gcc/graphviz.cc b/gcc/graphviz.cc index 65bb6cb..f8cedc0 100644 --- a/gcc/graphviz.cc +++ b/gcc/graphviz.cc @@ -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 */ diff --git a/gcc/graphviz.h b/gcc/graphviz.h index 9a1ca04..5943589 100644 --- a/gcc/graphviz.h +++ b/gcc/graphviz.h @@ -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 */ diff --git a/gcc/opts-diagnostic.cc b/gcc/opts-diagnostic.cc index 3629f17..1a7d83c 100644 --- a/gcc/opts-diagnostic.cc +++ b/gcc/opts-diagnostic.cc @@ -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 index 0000000..44a3ff1 --- /dev/null +++ b/gcc/pex.cc @@ -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 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 */ diff --git a/gcc/selftest-run-tests.cc b/gcc/selftest-run-tests.cc index df49a67..2d8573c 100644 --- a/gcc/selftest-run-tests.cc +++ b/gcc/selftest-run-tests.cc @@ -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 (); diff --git a/gcc/selftest.h b/gcc/selftest.h index 94acf62..a6c9602 100644 --- a/gcc/selftest.h +++ b/gcc/selftest.h @@ -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 index 0000000..dd9be6a --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/state-diagram.C @@ -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'" } diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h index 372a136..db0140b 100644 --- a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h +++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h @@ -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 index 0000000..d2967d4 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/state-diagram-1-sarif.py @@ -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 index 0000000..3d853d2 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/state-diagram-1.c @@ -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 index 0000000..bde2bc4 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/state-diagram-2.c @@ -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 index 0000000..a28553d --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/state-diagram-3.c @@ -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 index 0000000..37c654f --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/state-diagram-4.c @@ -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 index 0000000..79374b2 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/state-diagram-5-html.py @@ -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 index 0000000..484da96 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/state-diagram-5-sarif.py @@ -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 index 0000000..8e00cac --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/state-diagram-5.c @@ -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" } } */ diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.cc b/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.cc index 0f1f864..1fe5b5c 100644 --- a/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.cc +++ b/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.cc @@ -2,6 +2,7 @@ /* { dg-options "-g" } */ #define INCLUDE_MEMORY +#define INCLUDE_STRING #define INCLUDE_VECTOR #include "gcc-plugin.h" #include "config.h" diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.cc b/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.cc index 3cac3f8f..6f4cbc2 100644 --- a/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.cc +++ b/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.cc @@ -5,6 +5,7 @@ /* { dg-options "-g" } */ #define INCLUDE_MEMORY +#define INCLUDE_STRING #define INCLUDE_VECTOR #include "gcc-plugin.h" #include "config.h" diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.cc b/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.cc index 771ff75..18e054b 100644 --- a/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.cc +++ b/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.cc @@ -2,6 +2,7 @@ /* { dg-options "-g" } */ #define INCLUDE_MEMORY +#define INCLUDE_STRING #define INCLUDE_VECTOR #include "gcc-plugin.h" #include "config.h" diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_known_fns_plugin.cc b/gcc/testsuite/gcc.dg/plugin/analyzer_known_fns_plugin.cc index c7087f0..5a6e075 100644 --- a/gcc/testsuite/gcc.dg/plugin/analyzer_known_fns_plugin.cc +++ b/gcc/testsuite/gcc.dg/plugin/analyzer_known_fns_plugin.cc @@ -2,6 +2,7 @@ /* { dg-options "-g" } */ #define INCLUDE_MEMORY +#define INCLUDE_STRING #define INCLUDE_VECTOR #include "gcc-plugin.h" #include "config.h" diff --git a/gcc/testsuite/lib/htmltest.py b/gcc/testsuite/lib/htmltest.py index 49ed4b0..35f524c 100644 --- a/gcc/testsuite/lib/htmltest.py +++ b/gcc/testsuite/lib/htmltest.py @@ -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 diff --git a/gcc/testsuite/lib/sarif.py b/gcc/testsuite/lib/sarif.py index 7daf35b..384de2f 100644 --- a/gcc/testsuite/lib/sarif.py +++ b/gcc/testsuite/lib/sarif.py @@ -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 @@ -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 @@ -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. */ |