https://gcc.gnu.org/g:97238e4217d5cdc62686d86e784421bd0f76aa4a

commit r15-925-g97238e4217d5cdc62686d86e784421bd0f76aa4a
Author: David Malcolm <dmalc...@redhat.com>
Date:   Thu May 30 08:42:00 2024 -0400

    Add new text_art::tree_widget and use it in analyzer
    
    This patch adds a new text_art::tree_widget, which makes it easy
    to generate hierarchical visualizations using either ASCII:
    
      +- Child 0
      |  +- Grandchild 0 0
      |  +- Grandchild 0 1
      |  `- Grandchild 0 2
      +- Child 1
      |  +- Grandchild 1 0
      |  +- Grandchild 1 1
      |  `- Grandchild 1 2
      `- Child 2
         +- Grandchild 2 0
         +- Grandchild 2 1
         `- Grandchild 2 2
    
    or Unicode:
    
      Root
      ├─ Child 0
      │  ├─ Grandchild 0 0
      │  ├─ Grandchild 0 1
      │  ╰─ Grandchild 0 2
      ├─ Child 1
      │  ├─ Grandchild 1 0
      │  ├─ Grandchild 1 1
      │  ╰─ Grandchild 1 2
      ╰─ Child 2
         ├─ Grandchild 2 0
         ├─ Grandchild 2 1
         ╰─ Grandchild 2 2
    
    potentially with colorization of the connecting lines.
    
    It adds a new template for typename T:
    
      void text_art::dump<T> (const T&);
    
    for using this to dump any object to stderr that supports a
    make_dump_widget method, with similar templates for dumping to
    a pretty_printer * and a FILE *.
    
    It uses this within the analyzer to add two new families of dumping
    methods: one for program states, e.g.:
    
    (gdb) call state->dump()
    State
    ├─ Region Model
    │  ├─ Current Frame: frame: ‘calls_malloc’@2
    │  ├─ Store
    │  │  ├─ m_called_unknown_fn: false
    │  │  ├─ frame: ‘test’@1
    │  │  │  ╰─ _1: (INIT_VAL(n_2(D))*(size_t)4)
    │  │  ╰─ frame: ‘calls_malloc’@2
    │  │     ├─ result_4: &HEAP_ALLOCATED_REGION(27)
    │  │     ╰─ _5: &HEAP_ALLOCATED_REGION(27)
    │  ╰─ Dynamic Extents
    │     ╰─ HEAP_ALLOCATED_REGION(27): (INIT_VAL(n_2(D))*(size_t)4)
    ╰─ ‘malloc’ state machine
       ╰─ 0x468cb40: &HEAP_ALLOCATED_REGION(27): unchecked ({free}) (‘result_4’)
    
    and the other for showing the detail of the recursive makeup of svalues
    and regions, e.g. the (INIT_VAL(n_2(D))*(size_t)4) from above:
    
    (gdb) call size_in_bytes->dump()
    (17): ‘long unsigned int’: binop_svalue(mult_expr: ‘*’)
    ├─ (15): ‘size_t’: initial_svalue
    │  ╰─ m_reg: (12): ‘size_t’: decl_region(‘n_2(D)’)
    │     ╰─ parent: (9): frame_region(‘test’, index: 0, depth: 1)
    │        ╰─ parent: (1): stack region
    │           ╰─ parent: (0): root region
    ╰─ (16): ‘size_t’: constant_svalue (‘4’)
    
    I've already found both of these useful when debugging analyzer issues.
    
    The patch uses the former to update the output of
    -fdump-analyzer-exploded-nodes-2 and
    -fdump-analyzer-exploded-nodes-3.
    
    The older dumping functions within the analyzer are retained in case
    they turn out to still be useful for debugging.
    
    gcc/ChangeLog:
            * Makefile.in (OBJS-libcommon): Add text-art/tree-widget.o.
            * doc/analyzer.texi: Rewrite discussion of dumping state to
            cover the text_art::tree_widget-based dumps, with a more
            interesting example.
            * text-art/dump-widget-info.h: New file.
            * text-art/dump.h: New file.
            * text-art/selftests.cc (selftest::text_art_tests): Call
            text_art_tree_widget_cc_tests.
            * text-art/selftests.h (selftest::text_art_tree_widget_cc_tests):
            New decl.
            * text-art/theme.cc (ascii_theme::get_cppchar): Handle the various
            cell_kind::TREE_*.
            (unicode_theme::get_cppchar): Likewise.
            * text-art/theme.h (enum class theme::cell_kind): Add
            TREE_CHILD_NON_FINAL, TREE_CHILD_FINAL, TREE_X_CONNECTOR, and
            TREE_Y_CONNECTOR.
            * text-art/tree-widget.cc: New file.
    
    gcc/analyzer/ChangeLog:
            * call-details.cc: Define INCLUDE_VECTOR.
            * call-info.cc: Likewise.
            * call-summary.cc: Likewise.
            * checker-event.cc: Likewise.
            * checker-path.cc: Likewise.
            * complexity.cc: Likewise.
            * constraint-manager.cc: Likewise.
            (bounded_range::make_dump_widget): New.
            (bounded_ranges::add_to_dump_widget): New.
            (equiv_class::make_dump_widget): New.
            (constraint::make_dump_widget): New.
            (bounded_ranges_constraint::make_dump_widget): New.
            (constraint_manager::make_dump_widget): New.
            * constraint-manager.h (bounded_range::make_dump_widget): New
            decl.
            (bounded_ranges::add_to_dump_widget): New decl.
            (equiv_class::make_dump_widget): New decl.
            (constraint::make_dump_widget): New decl.
            (bounded_ranges_constraint::make_dump_widget): New decl.
            (constraint_manager::make_dump_widget): New decl.
            * diagnostic-manager.cc: Define INCLUDE_VECTOR.
            * engine.cc: Likewise.  Include "text-art/dump.h".
            (setjmp_svalue::print_dump_widget_label): New.
            (setjmp_svalue::add_dump_widget_children): New.
            (exploded_graph::dump_exploded_nodes): Use text_art::dump_to_file
            for -fdump-analyzer-exploded-nodes-2 and
            -fdump-analyzer-exploded-nodes-3.  Fix overlong line.
            * feasible-graph.cc: Define INCLUDE_VECTOR.
            * infinite-recursion.cc: Likewise.
            * kf-analyzer.cc: Likewise.
            * kf-lang-cp.cc: Likewise.
            * kf.cc: Likewise.
            * known-function-manager.cc: Likewise.
            * pending-diagnostic.cc: Likewise.
            * program-point.cc: Likewise.
            * program-state.cc: Likewise.  Include "text-art/tree-widget" and
            "text-art/dump.h".
            (sm_state_map::make_dump_widget): New.
            (program_state::dump): New.
            (program_state::make_dump_widget): New.
            * program-state.h: Include "text-art/widget.h".
            (sm_state_map::make_dump_widget): New decl.
            (program_state::dump): New decl.
            (program_state::make_dump_widget): New decl.
            * ranges.cc: Define INCLUDE_VECTOR.
            * record-layout.cc: Likewise.
            * region-model-asm.cc: Likewise.
            * region-model-manager.cc: Likewise.
            * region-model-reachability.cc: Likewise.
            * region-model.cc: Likewise.  Include "text-art/tree-widget.h".
            (region_to_value_map::make_dump_widget): New.
            (region_model::dump): New.
            (region_model::make_dump_widget): New.
            (selftest::test_dump): Add test of dump_to_pp<region_model>.
            * region-model.h: Include "text-art/widget.h" and
            "text-art/dump.h".
            (region_to_value_map::make_dump_widget): New decl.
            (region_model::dump): New decl.
            (region_model::make_dump_widget): New decl.
            * region.cc: Define INCLUDE_VECTOR and include "text-art/dump.h".
            (region::dump): New.
            (region::make_dump_widget): New.
            (region::add_dump_widget_children): New.
            (frame_region::print_dump_widget_label): New.
            (globals_region::print_dump_widget_label): New.
            (code_region::print_dump_widget_label): New.
            (function_region::print_dump_widget_label): New.
            (label_region::print_dump_widget_label): New.
            (stack_region::print_dump_widget_label): New.
            (heap_region::print_dump_widget_label): New.
            (root_region::print_dump_widget_label): New.
            (thread_local_region::print_dump_widget_label): New.
            (symbolic_region::print_dump_widget_label): New.
            (symbolic_region::add_dump_widget_children): New.
            (decl_region::print_dump_widget_label): New.
            (field_region::print_dump_widget_label): New.
            (element_region::print_dump_widget_label): New.
            (element_region::add_dump_widget_children): New.
            (offset_region::print_dump_widget_label): New.
            (offset_region::add_dump_widget_children): New.
            (sized_region::print_dump_widget_label): New.
            (sized_region::add_dump_widget_children): New.
            (cast_region::print_dump_widget_label): New.
            (cast_region::add_dump_widget_children): New.
            (heap_allocated_region::print_dump_widget_label): New.
            (alloca_region::print_dump_widget_label): New.
            (string_region::print_dump_widget_label): New.
            (bit_range_region::print_dump_widget_label): New.
            (var_arg_region::print_dump_widget_label): New.
            (errno_region::print_dump_widget_label): New.
            (private_region::print_dump_widget_label): New.
            (unknown_region::print_dump_widget_label): New.
            * region.h: Include "text-art/widget.h".
            (region::dump): New decl.
            (region::make_dump_widget): New decl.
            (region::add_dump_widget_children): New decl.
            (frame_region::print_dump_widget_label): New decl.
            (globals_region::print_dump_widget_label): New decl.
            (code_region::print_dump_widget_label): New decl.
            (function_region::print_dump_widget_label): New decl.
            (label_region::print_dump_widget_label): New decl.
            (stack_region::print_dump_widget_label): New decl.
            (heap_region::print_dump_widget_label): New decl.
            (root_region::print_dump_widget_label): New decl.
            (thread_local_region::print_dump_widget_label): New decl.
            (symbolic_region::print_dump_widget_label): New decl.
            (symbolic_region::add_dump_widget_children): New decl.
            (decl_region::print_dump_widget_label): New decl.
            (field_region::print_dump_widget_label): New decl.
            (element_region::print_dump_widget_label): New decl.
            (element_region::add_dump_widget_children): New decl.
            (offset_region::print_dump_widget_label): New decl.
            (offset_region::add_dump_widget_children): New decl.
            (sized_region::print_dump_widget_label): New decl.
            (sized_region::add_dump_widget_children): New decl.
            (cast_region::print_dump_widget_label): New decl.
            (cast_region::add_dump_widget_children): New decl.
            (heap_allocated_region::print_dump_widget_label): New decl.
            (alloca_region::print_dump_widget_label): New decl.
            (string_region::print_dump_widget_label): New decl.
            (bit_range_region::print_dump_widget_label): New decl.
            (var_arg_region::print_dump_widget_label): New decl.
            (errno_region::print_dump_widget_label): New decl.
            (private_region::print_dump_widget_label): New decl.
            (unknown_region::print_dump_widget_label): New decl.
            * sm-fd.cc: Define INCLUDE_VECTOR.
            * sm-file.cc: Likewise.
            * sm-malloc.cc: Likewise.
            * sm-pattern-test.cc: Likewise.
            * sm-signal.cc: Likewise.
            * sm-taint.cc: Likewise.
            * sm.cc: Likewise.
            * state-purge.cc: Likewise.
            * store.cc: Likewise.  Include "text-art/tree-widget.h".
            (add_binding_to_tree_widget): New.
            (binding_map::add_to_tree_widget): New.
            (binding_cluster::make_dump_widget): New.
            (store::make_dump_widget): New.
            * store.h: Include "text-art/tree-widget.h".
            (binding_map::add_to_tree_widget): New decl.
            (binding_cluster::make_dump_widget): New decl.
            (store::make_dump_widget): New decl.
            * svalue.cc: Define INCLUDE_VECTOR.  Include "make-unique.h" and
            "text-art/dump.h".
            (svalue::dump): New.
            (svalue::make_dump_widget): New.
            (region_svalue::print_dump_widget_label): New.
            (region_svalue::add_dump_widget_children): New.
            (constant_svalue::print_dump_widget_label): New.
            (constant_svalue::add_dump_widget_children): New.
            (unknown_svalue::print_dump_widget_label): New.
            (unknown_svalue::add_dump_widget_children): New.
            (poisoned_svalue::print_dump_widget_label): New.
            (poisoned_svalue::add_dump_widget_children): New.
            (initial_svalue::print_dump_widget_label): New.
            (initial_svalue::add_dump_widget_children): New.
            (unaryop_svalue::print_dump_widget_label): New.
            (unaryop_svalue::add_dump_widget_children): New.
            (binop_svalue::print_dump_widget_label): New.
            (binop_svalue::add_dump_widget_children): New.
            (sub_svalue::print_dump_widget_label): New.
            (sub_svalue::add_dump_widget_children): New.
            (repeated_svalue::print_dump_widget_label): New.
            (repeated_svalue::add_dump_widget_children): New.
            (bits_within_svalue::print_dump_widget_label): New.
            (bits_within_svalue::add_dump_widget_children): New.
            (widening_svalue::print_dump_widget_label): New.
            (widening_svalue::add_dump_widget_children): New.
            (placeholder_svalue::print_dump_widget_label): New.
            (placeholder_svalue::add_dump_widget_children): New.
            (unmergeable_svalue::print_dump_widget_label): New.
            (unmergeable_svalue::add_dump_widget_children): New.
            (compound_svalue::print_dump_widget_label): New.
            (compound_svalue::add_dump_widget_children): New.
            (conjured_svalue::print_dump_widget_label): New.
            (conjured_svalue::add_dump_widget_children): New.
            (asm_output_svalue::print_dump_widget_label): New.
            (asm_output_svalue::add_dump_widget_children): New.
            (const_fn_result_svalue::print_dump_widget_label): New.
            (const_fn_result_svalue::add_dump_widget_children): New.
            * svalue.h: Include "text-art/widget.h".  Add "using
            text_art::dump_widget_info".
            (svalue::dump): New decl.
            (svalue::make_dump_widget): New decl.
            (svalue::print_dump_widget_label): New decl.
            (svalue::print_dump_widget_label): New decl.
            (svalue::add_dump_widget_children): New decl.
            (region_svalue::print_dump_widget_label): New decl.
            (region_svalue::add_dump_widget_children): New decl.
            (constant_svalue::print_dump_widget_label): New decl.
            (constant_svalue::add_dump_widget_children): New decl.
            (unknown_svalue::print_dump_widget_label): New decl.
            (unknown_svalue::add_dump_widget_children): New decl.
            (poisoned_svalue::print_dump_widget_label): New decl.
            (poisoned_svalue::add_dump_widget_children): New decl.
            (initial_svalue::print_dump_widget_label): New decl.
            (initial_svalue::add_dump_widget_children): New decl.
            (unaryop_svalue::print_dump_widget_label): New decl.
            (unaryop_svalue::add_dump_widget_children): New decl.
            (binop_svalue::print_dump_widget_label): New decl.
            (binop_svalue::add_dump_widget_children): New decl.
            (sub_svalue::print_dump_widget_label): New decl.
            (sub_svalue::add_dump_widget_children): New decl.
            (repeated_svalue::print_dump_widget_label): New decl.
            (repeated_svalue::add_dump_widget_children): New decl.
            (bits_within_svalue::print_dump_widget_label): New decl.
            (bits_within_svalue::add_dump_widget_children): New decl.
            (widening_svalue::print_dump_widget_label): New decl.
            (widening_svalue::add_dump_widget_children): New decl.
            (placeholder_svalue::print_dump_widget_label): New decl.
            (placeholder_svalue::add_dump_widget_children): New decl.
            (unmergeable_svalue::print_dump_widget_label): New decl.
            (unmergeable_svalue::add_dump_widget_children): New decl.
            (compound_svalue::print_dump_widget_label): New decl.
            (compound_svalue::add_dump_widget_children): New decl.
            (conjured_svalue::print_dump_widget_label): New decl.
            (conjured_svalue::add_dump_widget_children): New decl.
            (asm_output_svalue::print_dump_widget_label): New decl.
            (asm_output_svalue::add_dump_widget_children): New decl.
            (const_fn_result_svalue::print_dump_widget_label): New decl.
            (const_fn_result_svalue::add_dump_widget_children): New decl.
            * trimmed-graph.cc: Define INCLUDE_VECTOR.
            * varargs.cc: Likewise.
    
    gcc/testsuite/ChangeLog:
            * gcc.dg/plugin/analyzer_cpython_plugin.c: Define INCLUDE_VECTOR.
            * gcc.dg/plugin/analyzer_gil_plugin.c: Likewise.
            * gcc.dg/plugin/analyzer_kernel_plugin.c: Likewise.
            * gcc.dg/plugin/analyzer_known_fns_plugin.c: Likewise.
    
    Signed-off-by: David Malcolm <dmalc...@redhat.com>

Diff:
---
 gcc/Makefile.in                                    |   1 +
 gcc/analyzer/call-details.cc                       |   1 +
 gcc/analyzer/call-info.cc                          |   1 +
 gcc/analyzer/call-summary.cc                       |   1 +
 gcc/analyzer/checker-event.cc                      |   1 +
 gcc/analyzer/checker-path.cc                       |   1 +
 gcc/analyzer/complexity.cc                         |   1 +
 gcc/analyzer/constraint-manager.cc                 | 100 +++++
 gcc/analyzer/constraint-manager.h                  |  20 +
 gcc/analyzer/diagnostic-manager.cc                 |   1 +
 gcc/analyzer/engine.cc                             |  29 +-
 gcc/analyzer/feasible-graph.cc                     |   1 +
 gcc/analyzer/infinite-recursion.cc                 |   1 +
 gcc/analyzer/kf-analyzer.cc                        |   1 +
 gcc/analyzer/kf-lang-cp.cc                         |   1 +
 gcc/analyzer/kf.cc                                 |   1 +
 gcc/analyzer/known-function-manager.cc             |   1 +
 gcc/analyzer/pending-diagnostic.cc                 |   1 +
 gcc/analyzer/program-point.cc                      |   1 +
 gcc/analyzer/program-state.cc                      | 112 ++++++
 gcc/analyzer/program-state.h                       |  10 +
 gcc/analyzer/ranges.cc                             |   1 +
 gcc/analyzer/record-layout.cc                      |   1 +
 gcc/analyzer/region-model-asm.cc                   |   1 +
 gcc/analyzer/region-model-manager.cc               |   1 +
 gcc/analyzer/region-model-reachability.cc          |   1 +
 gcc/analyzer/region-model.cc                       |  78 ++++
 gcc/analyzer/region-model.h                        |   9 +
 gcc/analyzer/region.cc                             | 251 ++++++++++++
 gcc/analyzer/region.h                              |  68 ++++
 gcc/analyzer/sm-fd.cc                              |   1 +
 gcc/analyzer/sm-file.cc                            |   1 +
 gcc/analyzer/sm-malloc.cc                          |   1 +
 gcc/analyzer/sm-pattern-test.cc                    |   1 +
 gcc/analyzer/sm-signal.cc                          |   1 +
 gcc/analyzer/sm-taint.cc                           |   1 +
 gcc/analyzer/sm.cc                                 |   1 +
 gcc/analyzer/state-purge.cc                        |   1 +
 gcc/analyzer/store.cc                              | 156 ++++++++
 gcc/analyzer/store.h                               |  13 +
 gcc/analyzer/svalue.cc                             | 421 +++++++++++++++++++++
 gcc/analyzer/svalue.h                              | 143 ++++++-
 gcc/analyzer/trimmed-graph.cc                      |   1 +
 gcc/analyzer/varargs.cc                            |   1 +
 gcc/doc/analyzer.texi                              | 175 +++++++--
 .../gcc.dg/plugin/analyzer_cpython_plugin.c        |   1 +
 gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c  |   1 +
 .../gcc.dg/plugin/analyzer_kernel_plugin.c         |   1 +
 .../gcc.dg/plugin/analyzer_known_fns_plugin.c      |   1 +
 gcc/text-art/dump-widget-info.h                    |  53 +++
 gcc/text-art/dump.h                                |  83 ++++
 gcc/text-art/selftests.cc                          |   1 +
 gcc/text-art/selftests.h                           |   1 +
 gcc/text-art/theme.cc                              |  18 +
 gcc/text-art/theme.h                               |   8 +-
 gcc/text-art/tree-widget.cc                        | 237 ++++++++++++
 gcc/text-art/tree-widget.h                         |  83 ++++
 57 files changed, 2071 insertions(+), 33 deletions(-)

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 66d42cc41f8..e200c131064 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1836,6 +1836,7 @@ OBJS-libcommon = diagnostic-spec.o diagnostic.o 
diagnostic-color.o \
        text-art/styled-string.o \
        text-art/table.o \
        text-art/theme.o \
+       text-art/tree-widget.o \
        text-art/widget.o
 
 # Objects in libcommon-target.a, used by drivers and by the core
diff --git a/gcc/analyzer/call-details.cc b/gcc/analyzer/call-details.cc
index ca47953f146..fda925edb96 100644
--- a/gcc/analyzer/call-details.cc
+++ b/gcc/analyzer/call-details.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/call-info.cc b/gcc/analyzer/call-info.cc
index 4c965b2136c..828ece5c7f0 100644
--- a/gcc/analyzer/call-info.cc
+++ b/gcc/analyzer/call-info.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/call-summary.cc b/gcc/analyzer/call-summary.cc
index c2c9c71f79b..60ca78a334d 100644
--- a/gcc/analyzer/call-summary.cc
+++ b/gcc/analyzer/call-summary.cc
@@ -19,6 +19,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/checker-event.cc b/gcc/analyzer/checker-event.cc
index b64c58ef770..ee3ceb407ea 100644
--- a/gcc/analyzer/checker-event.cc
+++ b/gcc/analyzer/checker-event.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc
index dc041ec8823..c8361915ad9 100644
--- a/gcc/analyzer/checker-path.cc
+++ b/gcc/analyzer/checker-path.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/complexity.cc b/gcc/analyzer/complexity.cc
index f6caa23235c..0f1f8cad579 100644
--- a/gcc/analyzer/complexity.cc
+++ b/gcc/analyzer/complexity.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/constraint-manager.cc 
b/gcc/analyzer/constraint-manager.cc
index 06a2bb860e6..707385d3fa6 100644
--- a/gcc/analyzer/constraint-manager.cc
+++ b/gcc/analyzer/constraint-manager.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
@@ -462,6 +463,14 @@ bounded_range::to_json () const
   return range_obj;
 }
 
+std::unique_ptr<text_art::widget>
+bounded_range::make_dump_widget (const text_art::dump_widget_info &dwi) const
+{
+  using text_art::tree_widget;
+  return tree_widget::from_fmt (dwi, default_tree_printer,
+                               "%qE ... %qE", m_lower, m_upper);
+}
+
 /* Subroutine of bounded_range::to_json.  */
 
 void
@@ -729,6 +738,14 @@ bounded_ranges::to_json () const
   return arr_obj;
 }
 
+void
+bounded_ranges::add_to_dump_widget (text_art::tree_widget &parent,
+                                   const text_art::dump_widget_info &dwi) const
+{
+  for (auto &range : m_ranges)
+    parent.add_child (range.make_dump_widget (dwi));
+}
+
 /* Determine whether (X OP RHS_CONST) is known to be true or false
    for all X in the ranges expressed by this object.  */
 
@@ -1129,6 +1146,39 @@ equiv_class::to_json () const
   return ec_obj;
 }
 
+std::unique_ptr<text_art::widget>
+equiv_class::make_dump_widget (const text_art::dump_widget_info &dwi,
+                              unsigned id) const
+{
+  using text_art::tree_widget;
+  std::unique_ptr<tree_widget> ec_widget;
+
+  {
+    pretty_printer pp;
+    pp_string (&pp, "Equivalence class ");
+    equiv_class_id (id).print (&pp);
+    ec_widget = tree_widget::make (dwi, &pp);
+  }
+
+  for (const svalue *sval : m_vars)
+    {
+      pretty_printer pp;
+      pp_format_decoder (&pp) = default_tree_printer;
+      sval->dump_to_pp (&pp, true);
+      ec_widget->add_child (tree_widget::make (dwi, &pp));
+    }
+
+  if (m_constant)
+    {
+      pretty_printer pp;
+      pp_format_decoder (&pp) = default_tree_printer;
+      pp_printf (&pp, "%qE", m_constant);
+      ec_widget->add_child (tree_widget::make (dwi, &pp));
+    }
+
+  return ec_widget;
+}
+
 /* Generate a hash value for this equiv_class.
    This relies on the ordering of m_vars, and so this object needs to
    have been canonicalized for this to be meaningful.  */
@@ -1354,6 +1404,17 @@ constraint::to_json () const
   return con_obj;
 }
 
+std::unique_ptr<text_art::widget>
+constraint::make_dump_widget (const text_art::dump_widget_info &dwi,
+                             const constraint_manager &cm) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = true;
+  print (&pp, cm);
+  return text_art::tree_widget::make (dwi, &pp);
+}
+
 /* Generate a hash value for this constraint.  */
 
 hashval_t
@@ -1430,6 +1491,18 @@ bounded_ranges_constraint::to_json () const
   return con_obj;
 }
 
+std::unique_ptr<text_art::widget>
+bounded_ranges_constraint::
+make_dump_widget (const text_art::dump_widget_info &dwi) const
+{
+  using text_art::tree_widget;
+  std::unique_ptr<tree_widget> brc_widget
+    (tree_widget::from_fmt (dwi, nullptr,
+                           "ec%i bounded ranges", m_ec_id.as_int ()));
+  m_ranges->add_to_dump_widget (*brc_widget.get (), dwi);
+  return brc_widget;
+}
+
 bool
 bounded_ranges_constraint::
 operator== (const bounded_ranges_constraint &other) const
@@ -1756,6 +1829,33 @@ constraint_manager::to_json () const
   return cm_obj;
 }
 
+std::unique_ptr<text_art::widget>
+constraint_manager::make_dump_widget (const text_art::dump_widget_info &dwi) 
const
+{
+  using text_art::tree_widget;
+  std::unique_ptr<tree_widget> cm_widget
+    (tree_widget::make (dwi, "Constraints"));
+
+  /* Equivalence classes.  */
+  unsigned i;
+  equiv_class *ec;
+  FOR_EACH_VEC_ELT (m_equiv_classes, i, ec)
+    cm_widget->add_child (ec->make_dump_widget (dwi, i));
+
+  /* Constraints.  */
+  for (const constraint &c : m_constraints)
+    cm_widget->add_child (c.make_dump_widget (dwi, *this));
+
+  /* m_bounded_ranges_constraints.  */
+  for (const auto &brc : m_bounded_ranges_constraints)
+    cm_widget->add_child (brc.make_dump_widget (dwi));
+
+  if (cm_widget->get_num_children () == 0)
+    return nullptr;
+
+  return cm_widget;
+}
+
 /* Attempt to add the constraint LHS OP RHS to this constraint_manager.
    Return true if the constraint could be added (or is already true).
    Return false if the constraint contradicts existing knowledge.  */
diff --git a/gcc/analyzer/constraint-manager.h 
b/gcc/analyzer/constraint-manager.h
index 3b8189d6b73..31556aebc7a 100644
--- a/gcc/analyzer/constraint-manager.h
+++ b/gcc/analyzer/constraint-manager.h
@@ -87,6 +87,9 @@ struct bounded_range
 
   json::object *to_json () const;
 
+  std::unique_ptr<text_art::widget>
+  make_dump_widget (const text_art::dump_widget_info &dwi) const;
+
   bool contains_p (tree cst) const;
 
   bool intersects_p (const bounded_range &other,
@@ -134,6 +137,9 @@ public:
 
   json::value *to_json () const;
 
+  void add_to_dump_widget (text_art::tree_widget &parent,
+                          const text_art::dump_widget_info &dwi) const;
+
   tristate eval_condition (enum tree_code op,
                           tree rhs_const,
                           bounded_ranges_manager *mgr) const;
@@ -267,6 +273,10 @@ public:
 
   json::object *to_json () const;
 
+  std::unique_ptr<text_art::widget>
+  make_dump_widget (const text_art::dump_widget_info &dwi,
+                   unsigned id) const;
+
   bool contains_non_constant_p () const;
 
   /* An equivalence class can contain multiple constants (e.g. multiple
@@ -343,6 +353,10 @@ class constraint
 
   json::object *to_json () const;
 
+  std::unique_ptr<text_art::widget>
+  make_dump_widget (const text_art::dump_widget_info &dwi,
+                   const constraint_manager &cm) const;
+
   hashval_t hash () const;
   bool operator== (const constraint &other) const;
 
@@ -394,6 +408,9 @@ public:
 
   void add_to_hash (inchash::hash *hstate) const;
 
+  std::unique_ptr<text_art::widget>
+  make_dump_widget (const text_art::dump_widget_info &dwi) const;
+
   equiv_class_id m_ec_id;
   const bounded_ranges *m_ranges;
 };
@@ -427,6 +444,9 @@ public:
 
   json::object *to_json () const;
 
+  std::unique_ptr<text_art::widget>
+  make_dump_widget (const text_art::dump_widget_info &dwi) const;
+
   const equiv_class &get_equiv_class_by_index (unsigned idx) const
   {
     return *m_equiv_classes[idx];
diff --git a/gcc/analyzer/diagnostic-manager.cc 
b/gcc/analyzer/diagnostic-manager.cc
index 08d92f9780e..da98b9679cb 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index 556e8a1c828..46d9b80f4f7 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "make-unique.h"
@@ -68,6 +69,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "tree-dfa.h"
 #include "analyzer/known-function-manager.h"
 #include "analyzer/call-summary.h"
+#include "text-art/dump.h"
 
 /* For an overview, see gcc/doc/analyzer.texi.  */
 
@@ -263,6 +265,26 @@ setjmp_svalue::dump_to_pp (pretty_printer *pp, bool 
simple) const
     pp_printf (pp, "setjmp_svalue(EN%i)", get_enode_index ());
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   setjmp_svalue.  */
+
+void
+setjmp_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "setjmp_svalue(EN: %i)", get_enode_index ());
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   setjmp_svalue.  */
+
+void
+setjmp_svalue::
+add_dump_widget_children (text_art::tree_widget &,
+                         const text_art::dump_widget_info &) const
+{
+  /* No children.  */
+}
+
 /* Get the index of the stored exploded_node.  */
 
 int
@@ -5420,7 +5442,7 @@ exploded_graph::dump_exploded_nodes () const
          pretty_printer pp;
          enode->get_point ().print (&pp, format (true));
          fprintf (outf, "%s\n", pp_formatted_text (&pp));
-         enode->get_state ().dump_to_file (m_ext_state, false, true, outf);
+         text_art::dump_to_file (enode->get_state (), outf);
        }
 
       fclose (outf);
@@ -5439,7 +5461,8 @@ exploded_graph::dump_exploded_nodes () const
            = xasprintf ("%s.en-%i.txt", dump_base_name, i);
          FILE *outf = fopen (filename, "w");
          if (!outf)
-           error_at (UNKNOWN_LOCATION, "unable to open %qs for writing", 
filename);
+           error_at (UNKNOWN_LOCATION, "unable to open %qs for writing",
+                     filename);
          free (filename);
 
          fprintf (outf, "EN %i:\n", enode->m_index);
@@ -5447,7 +5470,7 @@ exploded_graph::dump_exploded_nodes () const
          pretty_printer pp;
          enode->get_point ().print (&pp, format (true));
          fprintf (outf, "%s\n", pp_formatted_text (&pp));
-         enode->get_state ().dump_to_file (m_ext_state, false, true, outf);
+         text_art::dump_to_file (enode->get_state (), outf);
 
          fclose (outf);
        }
diff --git a/gcc/analyzer/feasible-graph.cc b/gcc/analyzer/feasible-graph.cc
index b475c7855a0..a1812231572 100644
--- a/gcc/analyzer/feasible-graph.cc
+++ b/gcc/analyzer/feasible-graph.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/infinite-recursion.cc 
b/gcc/analyzer/infinite-recursion.cc
index 65f136ddad6..6103c2b564f 100644
--- a/gcc/analyzer/infinite-recursion.cc
+++ b/gcc/analyzer/infinite-recursion.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/kf-analyzer.cc b/gcc/analyzer/kf-analyzer.cc
index a7fb1bce980..4c7302bd844 100644
--- a/gcc/analyzer/kf-analyzer.cc
+++ b/gcc/analyzer/kf-analyzer.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/kf-lang-cp.cc b/gcc/analyzer/kf-lang-cp.cc
index d42495eb9d5..8d1c566c545 100644
--- a/gcc/analyzer/kf-lang-cp.cc
+++ b/gcc/analyzer/kf-lang-cp.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/kf.cc b/gcc/analyzer/kf.cc
index c60e220dd1b..4213b89ac9f 100644
--- a/gcc/analyzer/kf.cc
+++ b/gcc/analyzer/kf.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/known-function-manager.cc 
b/gcc/analyzer/known-function-manager.cc
index d24e5b8f407..522d2aa9e57 100644
--- a/gcc/analyzer/known-function-manager.cc
+++ b/gcc/analyzer/known-function-manager.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/pending-diagnostic.cc 
b/gcc/analyzer/pending-diagnostic.cc
index b2961c21bfc..ff37ae6f7b4 100644
--- a/gcc/analyzer/pending-diagnostic.cc
+++ b/gcc/analyzer/pending-diagnostic.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/program-point.cc b/gcc/analyzer/program-point.cc
index 6e225cfe725..14ce5be4bcf 100644
--- a/gcc/analyzer/program-point.cc
+++ b/gcc/analyzer/program-point.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc
index 7842b596a4b..dc2d4bdf7b0 100644
--- a/gcc/analyzer/program-state.cc
+++ b/gcc/analyzer/program-state.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
@@ -53,6 +54,8 @@ 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 "text-art/tree-widget.h"
+#include "text-art/dump.h"
 
 #if ENABLE_ANALYZER
 
@@ -303,6 +306,85 @@ sm_state_map::to_json () const
   return map_obj;
 }
 
+/* Make a text_art::tree_widget describing this sm_state_map,
+   using MODEL if non-null to describe svalues.  */
+
+std::unique_ptr<text_art::widget>
+sm_state_map::make_dump_widget (const text_art::dump_widget_info &dwi,
+                               const region_model *model) const
+{
+  using text_art::styled_string;
+  using text_art::tree_widget;
+  std::unique_ptr<tree_widget> state_widget
+    (tree_widget::from_fmt (dwi, nullptr,
+                           "%qs state machine", m_sm.get_name ()));
+
+  if (m_global_state != m_sm.get_start_state ())
+    {
+      pretty_printer the_pp;
+      pretty_printer * const pp = &the_pp;
+      pp_format_decoder (pp) = default_tree_printer;
+      pp_string (pp, "Global State: ");
+      m_global_state->dump_to_pp (pp);
+      state_widget->add_child (tree_widget::make (dwi, pp));
+    }
+
+  auto_vec <const svalue *> keys (m_map.elements ());
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end ();
+       ++iter)
+    keys.quick_push ((*iter).first);
+  keys.qsort (svalue::cmp_ptr_ptr);
+  unsigned i;
+  const svalue *sval;
+  FOR_EACH_VEC_ELT (keys, i, sval)
+    {
+      pretty_printer the_pp;
+      pretty_printer * const pp = &the_pp;
+      const bool simple = true;
+      pp_format_decoder (pp) = default_tree_printer;
+      if (!flag_dump_noaddr)
+       {
+         pp_pointer (pp, sval);
+         pp_string (pp, ": ");
+       }
+      sval->dump_to_pp (pp, simple);
+
+      entry_t e = *const_cast <map_t &> (m_map).get (sval);
+      pp_string (pp, ": ");
+      e.m_state->dump_to_pp (pp);
+      if (model)
+       if (tree rep = model->get_representative_tree (sval))
+         {
+           pp_string (pp, " (");
+           dump_quoted_tree (pp, rep);
+           pp_character (pp, ')');
+         }
+      if (e.m_origin)
+       {
+         pp_string (pp, " (origin: ");
+         if (!flag_dump_noaddr)
+           {
+             pp_pointer (pp, e.m_origin);
+             pp_string (pp, ": ");
+           }
+         e.m_origin->dump_to_pp (pp, simple);
+         if (model)
+           if (tree rep = model->get_representative_tree (e.m_origin))
+             {
+               pp_string (pp, " (");
+               dump_quoted_tree (pp, rep);
+               pp_character (pp, ')');
+             }
+         pp_string (pp, ")");
+       }
+
+      state_widget->add_child (tree_widget::make (dwi, pp));
+    }
+
+  return state_widget;
+}
+
 /* Return true if no states have been set within this map
    (all expressions are for the start state).  */
 
@@ -1101,6 +1183,14 @@ program_state::dump (const extrinsic_state &ext_state,
   dump_to_file (ext_state, summarize, true, stderr);
 }
 
+/* Dump a tree-like representation of this state to stderr.  */
+
+DEBUG_FUNCTION void
+program_state::dump () const
+{
+  text_art::dump (*this);
+}
+
 /* Return a new json::object of the form
    {"store"  : object for store,
     "constraints" : object for constraint_manager,
@@ -1138,6 +1228,28 @@ program_state::to_json (const extrinsic_state 
&ext_state) const
   return state_obj;
 }
 
+
+std::unique_ptr<text_art::widget>
+program_state::make_dump_widget (const text_art::dump_widget_info &dwi) const
+{
+  using text_art::tree_widget;
+  std::unique_ptr<tree_widget> state_widget
+    (tree_widget::from_fmt (dwi, nullptr, "State"));
+
+  state_widget->add_child (m_region_model->make_dump_widget (dwi));
+
+  /* Add nodes for any sm_state_maps with state.  */
+  {
+    int i;
+    sm_state_map *smap;
+    FOR_EACH_VEC_ELT (m_checker_states, i, smap)
+      if (!smap->is_empty_p ())
+       state_widget->add_child (smap->make_dump_widget (dwi, m_region_model));
+  }
+
+  return state_widget;
+}
+
 /* 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 3ba6a931cd6..7e751386b21 100644
--- a/gcc/analyzer/program-state.h
+++ b/gcc/analyzer/program-state.h
@@ -21,6 +21,8 @@ along with GCC; see the file COPYING3.  If not see
 #ifndef GCC_ANALYZER_PROGRAM_STATE_H
 #define GCC_ANALYZER_PROGRAM_STATE_H
 
+#include "text-art/widget.h"
+
 namespace ana {
 
 /* Data shared by all program_state instances.  */
@@ -117,6 +119,10 @@ public:
 
   json::object *to_json () const;
 
+  std::unique_ptr<text_art::widget>
+  make_dump_widget (const text_art::dump_widget_info &dwi,
+                   const region_model *model) const;
+
   bool is_empty_p () const;
 
   hashval_t hash () const;
@@ -223,9 +229,13 @@ public:
   void dump_to_file (const extrinsic_state &ext_state, bool simple,
                     bool multiline, FILE *outf) const;
   void dump (const extrinsic_state &ext_state, bool simple) const;
+  void dump () const;
 
   json::object *to_json (const extrinsic_state &ext_state) const;
 
+  std::unique_ptr<text_art::widget>
+  make_dump_widget (const text_art::dump_widget_info &dwi) const;
+
   void push_frame (const extrinsic_state &ext_state, const function &fun);
   const function * get_current_function () const;
 
diff --git a/gcc/analyzer/ranges.cc b/gcc/analyzer/ranges.cc
index 659ada7609d..f591efae43a 100644
--- a/gcc/analyzer/ranges.cc
+++ b/gcc/analyzer/ranges.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/record-layout.cc b/gcc/analyzer/record-layout.cc
index 567dfd9809a..af311551848 100644
--- a/gcc/analyzer/record-layout.cc
+++ b/gcc/analyzer/record-layout.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/region-model-asm.cc b/gcc/analyzer/region-model-asm.cc
index a88c3538d96..2a074b64775 100644
--- a/gcc/analyzer/region-model-asm.cc
+++ b/gcc/analyzer/region-model-asm.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/region-model-manager.cc 
b/gcc/analyzer/region-model-manager.cc
index f155eeb87c0..b094b2f7e43 100644
--- a/gcc/analyzer/region-model-manager.cc
+++ b/gcc/analyzer/region-model-manager.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/region-model-reachability.cc 
b/gcc/analyzer/region-model-reachability.cc
index 2a1df312d21..b9887902980 100644
--- a/gcc/analyzer/region-model-reachability.cc
+++ b/gcc/analyzer/region-model-reachability.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 0dd5671db1b..e71c6ec693b 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -21,6 +21,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "config.h"
 #define INCLUDE_MEMORY
 #define INCLUDE_ALGORITHM
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "make-unique.h"
@@ -80,6 +81,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/feasible-graph.h"
 #include "analyzer/record-layout.h"
 #include "diagnostic-format-sarif.h"
+#include "text-art/tree-widget.h"
 
 #if ENABLE_ANALYZER
 
@@ -256,6 +258,39 @@ region_to_value_map::to_json () const
   return map_obj;
 }
 
+std::unique_ptr<text_art::widget>
+region_to_value_map::
+make_dump_widget (const text_art::dump_widget_info &dwi) const
+{
+  if (is_empty ())
+    return nullptr;
+
+  std::unique_ptr<text_art::tree_widget> w
+    (text_art::tree_widget::make (dwi, "Dynamic Extents"));
+
+  auto_vec<const region *> regs;
+  for (iterator iter = begin (); iter != end (); ++iter)
+    regs.safe_push ((*iter).first);
+  regs.qsort (region::cmp_ptr_ptr);
+
+  unsigned i;
+  const region *reg;
+  FOR_EACH_VEC_ELT (regs, i, reg)
+    {
+      pretty_printer the_pp;
+      pretty_printer * const pp = &the_pp;
+      pp_format_decoder (pp) = default_tree_printer;
+      const bool simple = true;
+
+      reg->dump_to_pp (pp, simple);
+      pp_string (pp, ": ");
+      const svalue *sval = *get (reg);
+      sval->dump_to_pp (pp, true);
+      w->add_child (text_art::tree_widget::make (dwi, pp));
+    }
+  return w;
+}
+
 /* Attempt to merge THIS with OTHER, writing the result
    to OUT.
 
@@ -465,6 +500,14 @@ region_model::dump (bool simple) const
   dump (stderr, simple, true);
 }
 
+/* Dump a tree-like representation of this state to stderr.  */
+
+DEBUG_FUNCTION void
+region_model::dump () const
+{
+  text_art::dump (*this);
+}
+
 /* Dump a multiline representation of this model to stderr.  */
 
 DEBUG_FUNCTION void
@@ -489,6 +532,33 @@ region_model::to_json () const
   return model_obj;
 }
 
+std::unique_ptr<text_art::widget>
+region_model::make_dump_widget (const text_art::dump_widget_info &dwi) const
+{
+  using text_art::tree_widget;
+  std::unique_ptr<tree_widget> model_widget
+    (tree_widget::from_fmt (dwi, nullptr, "Region Model"));
+
+  if (m_current_frame)
+    {
+      pretty_printer the_pp;
+      pretty_printer * const pp = &the_pp;
+      pp_format_decoder (pp) = default_tree_printer;
+      pp_show_color (pp) = true;
+      const bool simple = true;
+
+      pp_string (pp, "Current Frame: ");
+      m_current_frame->dump_to_pp (pp, simple);
+      model_widget->add_child (tree_widget::make (dwi, pp));
+    }
+  model_widget->add_child
+    (m_store.make_dump_widget (dwi,
+                              m_mgr->get_store_manager ()));
+  model_widget->add_child (m_constraints->make_dump_widget (dwi));
+  model_widget->add_child (m_dynamic_extents.make_dump_widget (dwi));
+  return model_widget;
+}
+
 /* Assert that this object is valid.  */
 
 void
@@ -7307,6 +7377,14 @@ test_dump ()
                  "constraint_manager:\n"
                  "  equiv classes:\n"
                  "  constraints:\n");
+
+  text_art::ascii_theme theme;
+  pretty_printer pp;
+  dump_to_pp (model, &theme, &pp);
+  ASSERT_STREQ ("Region Model\n"
+               "`- Store\n"
+               "   `- m_called_unknown_fn: false\n",
+               pp_formatted_text (&pp));
 }
 
 /* Helper function for selftests.  Create a struct or union type named NAME,
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 118e0f25427..400d80b2568 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -35,6 +35,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/known-function-manager.h"
 #include "analyzer/region-model-manager.h"
 #include "analyzer/pending-diagnostic.h"
+#include "text-art/widget.h"
+#include "text-art/dump.h"
 
 using namespace ana;
 
@@ -177,6 +179,9 @@ public:
 
   json::object *to_json () const;
 
+  std::unique_ptr<text_art::widget>
+  make_dump_widget (const text_art::dump_widget_info &dwi) const;
+
   bool can_merge_with_p (const region_to_value_map &other,
                         region_to_value_map *out) const;
 
@@ -277,11 +282,15 @@ class region_model
   void dump_to_pp (pretty_printer *pp, bool simple, bool multiline) const;
   void dump (FILE *fp, bool simple, bool multiline) const;
   void dump (bool simple) const;
+  void dump () const;
 
   void debug () const;
 
   json::object *to_json () const;
 
+  std::unique_ptr<text_art::widget>
+  make_dump_widget (const text_art::dump_widget_info &dwi) const;
+
   void validate () const;
 
   void canonicalize ();
diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc
index 7d79b45563f..71bae97b6f1 100644
--- a/gcc/analyzer/region.cc
+++ b/gcc/analyzer/region.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
@@ -57,6 +58,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/region-model.h"
 #include "analyzer/sm.h"
 #include "analyzer/program-state.h"
+#include "text-art/dump.h"
 
 #if ENABLE_ANALYZER
 
@@ -1040,6 +1042,23 @@ region::dump (bool simple) const
   pp_flush (&pp);
 }
 
+/* Dump a tree-like representation of this region and its constituent symbols
+   to stderr, using global_dc's colorization and theming options.
+
+   For example:
+   . (gdb) call reg->dump()
+   . (26): ‘int’: decl_region(‘x_10(D)’)
+   . ╰─ parent: (9): frame_region(‘test_bitmask_2’, index: 0, depth: 1)
+   .    ╰─ parent: (1): stack region
+   .       ╰─ parent: (0): root region
+  */
+
+DEBUG_FUNCTION void
+region::dump () const
+{
+  text_art::dump (*this);
+}
+
 /* Return a new json::string describing the region.  */
 
 json::value *
@@ -1072,6 +1091,44 @@ region::maybe_print_for_user (pretty_printer *pp,
   return false;
 }
 
+/* Use DWI to create a text_art::widget describing this region in
+   a tree-like form, using PREFIX as a prefix (e.g. for field names).  */
+
+std::unique_ptr<text_art::widget>
+region::make_dump_widget (const text_art::dump_widget_info &dwi,
+                         const char *prefix) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = true;
+
+  if (prefix)
+    pp_printf (&pp, "%s: ", prefix);
+
+  pp_printf (&pp, "(%i): ", get_id ());
+  if (get_type ())
+    pp_printf (&pp, "%qT: ", get_type ());
+
+  print_dump_widget_label (&pp);
+
+  std::unique_ptr<text_art::tree_widget> w
+    (text_art::tree_widget::make (dwi, &pp));
+
+  add_dump_widget_children (*w, dwi);
+
+  if (m_parent)
+    w->add_child (m_parent->make_dump_widget (dwi, "parent"));
+
+  return w;
+}
+
+void
+region::add_dump_widget_children (text_art::tree_widget &,
+                                 const text_art::dump_widget_info &) const
+{
+  /* By default, add nothing (parent is added in make_dump_widget).  */
+}
+
 /* Generate a description of this region.  */
 
 DEBUG_FUNCTION label_text
@@ -1341,6 +1398,13 @@ frame_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
               function_name (&m_fun), m_index, get_stack_depth ());
 }
 
+void
+frame_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "frame_region(%qs, index: %i, depth: %i)",
+            function_name (&m_fun), m_index, get_stack_depth ());
+}
+
 const decl_region *
 frame_region::get_region_for_local (region_model_manager *mgr,
                                    tree expr,
@@ -1411,6 +1475,12 @@ globals_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     pp_string (pp, "globals");
 }
 
+void
+globals_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_string (pp, "globals");
+}
+
 /* class code_region : public map_region.  */
 
 /* Implementation of region::dump_to_pp vfunc for code_region.  */
@@ -1424,6 +1494,12 @@ code_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     pp_string (pp, "code_region()");
 }
 
+void
+code_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_string (pp, "code region");
+}
+
 /* class function_region : public region.  */
 
 /* Implementation of region::dump_to_pp vfunc for function_region.  */
@@ -1443,6 +1519,14 @@ function_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+void
+function_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_string (pp, "function_region(");
+  dump_quoted_tree (pp, m_fndecl);
+  pp_string (pp, ")");
+}
+
 /* class label_region : public region.  */
 
 /* Implementation of region::dump_to_pp vfunc for label_region.  */
@@ -1462,6 +1546,14 @@ label_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+void
+label_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_string (pp, "label_region(");
+  dump_quoted_tree (pp, m_label);
+  pp_string (pp, ")");
+}
+
 /* class stack_region : public region.  */
 
 /* Implementation of region::dump_to_pp vfunc for stack_region.  */
@@ -1475,6 +1567,12 @@ stack_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     pp_string (pp, "stack_region()");
 }
 
+void
+stack_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_string (pp, "stack region");
+}
+
 /* class heap_region : public region.  */
 
 /* Implementation of region::dump_to_pp vfunc for heap_region.  */
@@ -1488,6 +1586,12 @@ heap_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     pp_string (pp, "heap_region()");
 }
 
+void
+heap_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_string (pp, "heap_region");
+}
+
 /* class root_region : public region.  */
 
 /* root_region's ctor.  */
@@ -1508,6 +1612,12 @@ root_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     pp_string (pp, "root_region()");
 }
 
+void
+root_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_string (pp, "root region");
+}
+
 /* class thread_local_region : public space_region.  */
 
 void
@@ -1519,6 +1629,12 @@ thread_local_region::dump_to_pp (pretty_printer *pp, 
bool simple) const
     pp_string (pp, "thread_local_region()");
 }
 
+void
+thread_local_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_string (pp, "thread_local_region");
+}
+
 /* class symbolic_region : public map_region.  */
 
 /* symbolic_region's ctor.  */
@@ -1568,6 +1684,20 @@ symbolic_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+void
+symbolic_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_string (pp, "symbolic_region: %<*%>");
+}
+
+void
+symbolic_region::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  w.add_child (m_sval_ptr->make_dump_widget (dwi, "m_sval_ptr"));
+}
+
 /* class decl_region : public region.  */
 
 /* Implementation of region::dump_to_pp vfunc for decl_region.  */
@@ -1587,6 +1717,12 @@ decl_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+void
+decl_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "decl_region(%qE)", m_decl);
+}
+
 /* Get the stack depth for the frame containing this decl, or 0
    for a global.  */
 
@@ -1816,6 +1952,12 @@ field_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+void
+field_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "field_region(%qE)", m_field);
+}
+
 /* Implementation of region::get_relative_concrete_offset vfunc
    for field_region.  */
 
@@ -1892,6 +2034,20 @@ element_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+void
+element_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "element_region: %<[]%>");
+}
+
+void
+element_region::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  w.add_child (m_index->make_dump_widget (dwi, "m_index"));
+}
+
 /* Implementation of region::get_relative_concrete_offset vfunc
    for element_region.  */
 
@@ -1980,6 +2136,20 @@ offset_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+void
+offset_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "offset_region");
+}
+
+void
+offset_region::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  w.add_child (m_byte_offset->make_dump_widget (dwi, "m_byte_offset"));
+}
+
 const svalue *
 offset_region::get_bit_offset (region_model_manager *mgr) const
 {
@@ -2053,6 +2223,20 @@ sized_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+void
+sized_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "sized_region");
+}
+
+void
+sized_region::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  w.add_child (m_byte_size_sval->make_dump_widget (dwi, "m_byte_size_sval"));
+}
+
 /* Implementation of region::get_byte_size vfunc for sized_region.  */
 
 bool
@@ -2124,6 +2308,21 @@ cast_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+void
+cast_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "cast_region");
+}
+
+void
+cast_region::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  w.add_child
+    (m_original_region->make_dump_widget (dwi, "m_original_region"));
+}
+
 /* Implementation of region::get_relative_concrete_offset vfunc
    for cast_region.  */
 
@@ -2147,6 +2346,12 @@ heap_allocated_region::dump_to_pp (pretty_printer *pp, 
bool simple) const
     pp_printf (pp, "heap_allocated_region(%i)", get_id ());
 }
 
+void
+heap_allocated_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "heap_allocated_region");
+}
+
 /* class alloca_region : public region.  */
 
 /* Implementation of region::dump_to_pp vfunc for alloca_region.  */
@@ -2160,6 +2365,12 @@ alloca_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     pp_printf (pp, "alloca_region(%i)", get_id ());
 }
 
+void
+alloca_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "alloca_region");
+}
+
 /* class string_region : public region.  */
 
 /* Implementation of region::dump_to_pp vfunc for string_region.  */
@@ -2182,6 +2393,14 @@ string_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+void
+string_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_string (pp, "string_region(");
+  dump_tree (pp, m_string_cst);
+  pp_string (pp, ")");
+}
+
 /* class bit_range_region : public region.  */
 
 /* Implementation of region::dump_to_pp vfunc for bit_range_region.  */
@@ -2207,6 +2426,14 @@ bit_range_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+void
+bit_range_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "bit_range_region(m_bits: ");
+  m_bits.dump_to_pp (pp);
+  pp_string (pp, ")");
+}
+
 /* Implementation of region::get_byte_size vfunc for bit_range_region.  */
 
 bool
@@ -2293,6 +2520,12 @@ var_arg_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+void
+var_arg_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "var_arg_region(arg_idx: %i)", m_idx);
+}
+
 /* Get the frame_region for this var_arg_region.  */
 
 const frame_region *
@@ -2313,6 +2546,12 @@ errno_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     pp_string (pp, "errno_region()");
 }
 
+void
+errno_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "errno_region");
+}
+
 /* class private_region : public region.  */
 
 void
@@ -2324,6 +2563,12 @@ private_region::dump_to_pp (pretty_printer *pp, bool 
simple) const
     pp_printf (pp, "private_region(%qs)", m_desc);
 }
 
+void
+private_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "private_region(%qs)", m_desc);
+}
+
 /* class unknown_region : public region.  */
 
 /* Implementation of region::dump_to_pp vfunc for unknown_region.  */
@@ -2334,6 +2579,12 @@ unknown_region::dump_to_pp (pretty_printer *pp, bool 
/*simple*/) const
   pp_string (pp, "UNKNOWN_REGION");
 }
 
+void
+unknown_region::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "unknown_region");
+}
+
 } // namespace ana
 
 #endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/region.h b/gcc/analyzer/region.h
index 0c190c2f0a2..5d58abc2693 100644
--- a/gcc/analyzer/region.h
+++ b/gcc/analyzer/region.h
@@ -22,6 +22,7 @@ along with GCC; see the file COPYING3.  If not see
 #define GCC_ANALYZER_REGION_H
 
 #include "analyzer/symbol.h"
+#include "text-art/widget.h"
 
 namespace ana {
 
@@ -173,12 +174,18 @@ public:
 
   virtual void dump_to_pp (pretty_printer *pp, bool simple) const = 0;
   void dump (bool simple) const;
+  void dump () const;
 
   json::value *to_json () const;
 
+
   bool maybe_print_for_user (pretty_printer *pp,
                             const region_model &model) const;
 
+  std::unique_ptr<text_art::widget>
+  make_dump_widget (const text_art::dump_widget_info &dwi,
+                   const char *prefix = nullptr) const;
+
   bool non_null_p () const;
 
   static int cmp_ptr_ptr (const void *, const void *);
@@ -257,6 +264,12 @@ public:
   region_offset calc_offset (region_model_manager *mgr) const;
   const svalue *calc_initial_value_at_main (region_model_manager *mgr) const;
 
+  virtual void
+  print_dump_widget_label (pretty_printer *pp) const = 0;
+  virtual void
+  add_dump_widget_children (text_art::tree_widget &,
+                           const text_art::dump_widget_info &dwi) const;
+
   const region *m_parent;
   tree m_type;
 
@@ -357,6 +370,8 @@ public:
   void accept (visitor *v) const final override;
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
 
+  void print_dump_widget_label (pretty_printer *pp) const final override;
+
   /* Accessors.  */
   const frame_region *get_calling_frame () const { return m_calling_frame; }
   const function &get_function () const { return m_fun; }
@@ -416,6 +431,7 @@ class globals_region : public space_region
   /* region vfuncs.  */
   enum region_kind get_kind () const final override { return RK_GLOBALS; }
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
 };
 
 } // namespace ana
@@ -442,6 +458,7 @@ public:
 
   /* region vfuncs.  */
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
   enum region_kind get_kind () const final override { return RK_CODE; }
 };
 
@@ -472,6 +489,8 @@ public:
 
   /* region vfuncs.  */
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
+
   enum region_kind get_kind () const final override { return RK_FUNCTION; }
   const function_region *
   dyn_cast_function_region () const final override{ return this; }
@@ -508,6 +527,7 @@ public:
 
   /* region vfuncs.  */
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
   enum region_kind get_kind () const final override { return RK_LABEL; }
 
   tree get_label () const { return m_label; }
@@ -539,6 +559,7 @@ public:
   {}
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
 
   enum region_kind get_kind () const final override { return RK_STACK; }
 };
@@ -567,6 +588,7 @@ public:
 
   enum region_kind get_kind () const final override { return RK_HEAP; }
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
 };
 
 } // namespace ana
@@ -593,6 +615,7 @@ public:
 
   enum region_kind get_kind () const final override { return RK_THREAD_LOCAL; }
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
 };
 
 } // namespace ana
@@ -618,6 +641,7 @@ public:
 
   enum region_kind get_kind () const final override { return RK_ROOT; }
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
 };
 
 } // namespace ana
@@ -680,6 +704,11 @@ public:
   enum region_kind get_kind () const final override { return RK_SYMBOLIC; }
   void accept (visitor *v) const final override;
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const text_art::dump_widget_info &dwi)
+    const final override;
 
   const svalue *get_pointer () const { return m_sval_ptr; }
 
@@ -724,6 +753,9 @@ public:
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
 
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+
   bool tracked_p () const final override { return m_tracked; }
 
   tree get_decl () const { return m_decl; }
@@ -808,6 +840,8 @@ public:
   enum region_kind get_kind () const final override { return RK_FIELD; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
+
   const field_region *
   dyn_cast_field_region () const final override { return this; }
 
@@ -896,6 +930,13 @@ public:
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
 
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &,
+                           const text_art::dump_widget_info &dwi)
+    const final override;
+
   const svalue *get_index () const { return m_index; }
 
   virtual bool
@@ -983,6 +1024,13 @@ public:
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
 
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &,
+                           const text_art::dump_widget_info &dwi)
+    const final override;
+
   const svalue *get_byte_offset () const { return m_byte_offset; }
   const svalue *get_bit_offset (region_model_manager *mgr) const;
 
@@ -1073,6 +1121,12 @@ public:
   void accept (visitor *v) const final override;
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &,
+                           const text_art::dump_widget_info &dwi)
+    const final override;
 
   bool get_byte_size (byte_size_t *out) const final override;
   bool get_bit_size (bit_size_t *out) const final override;
@@ -1162,6 +1216,12 @@ public:
   dyn_cast_cast_region () const final override { return this; }
   void accept (visitor *v) const final override;
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &,
+                           const text_art::dump_widget_info &dwi)
+    const final override;
 
   bool get_relative_concrete_offset (bit_offset_t *out) const final override;
 
@@ -1203,6 +1263,7 @@ public:
   get_kind () const final override { return RK_HEAP_ALLOCATED; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
 };
 
 /* An untyped region dynamically allocated on the stack via "alloca".  */
@@ -1217,6 +1278,7 @@ public:
   enum region_kind get_kind () const final override { return RK_ALLOCA; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
 };
 
 /* A region for a STRING_CST.  */
@@ -1235,6 +1297,7 @@ public:
   enum region_kind get_kind () const final override { return RK_STRING; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
 
   /* We assume string literals are immutable, so we don't track them in
      the store.  */
@@ -1314,6 +1377,7 @@ public:
   enum region_kind get_kind () const final override { return RK_BIT_RANGE; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
 
   const bit_range &get_bits () const { return m_bits; }
 
@@ -1402,6 +1466,7 @@ public:
   enum region_kind get_kind () const final override { return RK_VAR_ARG; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
 
   const frame_region *get_frame_region () const;
   unsigned get_index () const { return m_idx; }
@@ -1440,6 +1505,7 @@ public:
   enum region_kind get_kind () const final override { return RK_ERRNO; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
 };
 
 } // namespace ana
@@ -1473,6 +1539,7 @@ public:
   enum region_kind get_kind () const final override { return RK_PRIVATE; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
 
 private:
   const char *m_desc;
@@ -1502,6 +1569,7 @@ public:
   enum region_kind get_kind () const final override { return RK_UNKNOWN; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+  void print_dump_widget_label (pretty_printer *pp) const final override;
 };
 
 } // namespace ana
diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc
index 6baac2a0c69..ded20576fd1 100644
--- a/gcc/analyzer/sm-fd.cc
+++ b/gcc/analyzer/sm-fd.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "make-unique.h"
diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc
index f85b4004969..e7af46a6eb8 100644
--- a/gcc/analyzer/sm-file.cc
+++ b/gcc/analyzer/sm-file.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "make-unique.h"
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index fc6718ad74b..8bdcb4bc33c 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "make-unique.h"
diff --git a/gcc/analyzer/sm-pattern-test.cc b/gcc/analyzer/sm-pattern-test.cc
index 866e07b43fb..674d95859b2 100644
--- a/gcc/analyzer/sm-pattern-test.cc
+++ b/gcc/analyzer/sm-pattern-test.cc
@@ -22,6 +22,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "make-unique.h"
diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc
index 84603c5b77b..6a94b5f5c5f 100644
--- a/gcc/analyzer/sm-signal.cc
+++ b/gcc/analyzer/sm-signal.cc
@@ -22,6 +22,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "make-unique.h"
diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc
index a9c6d4db43f..bd85f0d5611 100644
--- a/gcc/analyzer/sm-taint.cc
+++ b/gcc/analyzer/sm-taint.cc
@@ -22,6 +22,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "make-unique.h"
diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc
index fcae035adea..f23a94b6243 100644
--- a/gcc/analyzer/sm.cc
+++ b/gcc/analyzer/sm.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/state-purge.cc b/gcc/analyzer/state-purge.cc
index 324b548f75b..7f5a40ae4ad 100644
--- a/gcc/analyzer/state-purge.cc
+++ b/gcc/analyzer/state-purge.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc
index a36de13c174..d14cfa329b8 100644
--- a/gcc/analyzer/store.cc
+++ b/gcc/analyzer/store.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
@@ -54,6 +55,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/call-summary.h"
 #include "analyzer/analyzer-selftests.h"
 #include "stor-layout.h"
+#include "text-art/tree-widget.h"
 
 #if ENABLE_ANALYZER
 
@@ -810,6 +812,56 @@ binding_map::to_json () const
   return map_obj;
 }
 
+/* Add a child to PARENT_WIDGET expressing a binding between
+   KEY and SVAL.  */
+
+static void
+add_binding_to_tree_widget (text_art::tree_widget &parent_widget,
+                           const text_art::dump_widget_info &dwi,
+                           const binding_key *key,
+                           const svalue *sval)
+{
+  pretty_printer the_pp;
+  pretty_printer * const pp = &the_pp;
+  pp_format_decoder (pp) = default_tree_printer;
+  pp_show_color (pp) = true;
+  const bool simple = true;
+
+  key->dump_to_pp (pp, simple);
+  pp_string (pp, ": ");
+  if (tree t = sval->get_type ())
+    dump_quoted_tree (pp, t);
+  pp_string (pp, " {");
+  sval->dump_to_pp (pp, simple);
+  pp_string (pp, "}");
+
+  parent_widget.add_child (text_art::tree_widget::make (dwi, pp));
+}
+
+void
+binding_map::add_to_tree_widget (text_art::tree_widget &parent_widget,
+                                const text_art::dump_widget_info &dwi) const
+{
+  auto_vec <const binding_key *> binding_keys;
+  for (map_t::iterator iter = m_map.begin ();
+       iter != m_map.end (); ++iter)
+    {
+      const binding_key *key = (*iter).first;
+      binding_keys.safe_push (key);
+    }
+  binding_keys.qsort (binding_key::cmp_ptrs);
+
+  const binding_key *key;
+  unsigned i;
+  FOR_EACH_VEC_ELT (binding_keys, i, key)
+    {
+      const svalue *sval = *const_cast <map_t &> (m_map).get (key);
+      add_binding_to_tree_widget (parent_widget, dwi,
+                                 key, sval);
+    }
+}
+
+
 /* Comparator for imposing an order on binding_maps.  */
 
 int
@@ -1399,6 +1451,48 @@ binding_cluster::to_json () const
   return cluster_obj;
 }
 
+std::unique_ptr<text_art::widget>
+binding_cluster::make_dump_widget (const text_art::dump_widget_info &dwi,
+                                  store_manager *mgr) const
+{
+  pretty_printer the_pp;
+  pretty_printer * const pp = &the_pp;
+  pp_format_decoder (pp) = default_tree_printer;
+  pp_show_color (pp) = true;
+  const bool simple = true;
+
+  m_base_region->dump_to_pp (pp, simple);
+  pp_string (pp, ": ");
+
+  if (const svalue *sval = maybe_get_simple_value (mgr))
+    {
+      /* Special-case to simplify dumps for the common case where
+        we just have one value directly bound to the whole of a
+        region.  */
+      sval->dump_to_pp (pp, simple);
+      if (escaped_p ())
+       pp_string (pp, " (ESCAPED)");
+      if (touched_p ())
+       pp_string (pp, " (TOUCHED)");
+
+      return text_art::tree_widget::make (dwi, pp);
+    }
+  else
+    {
+      if (escaped_p ())
+       pp_string (pp, " (ESCAPED)");
+      if (touched_p ())
+       pp_string (pp, " (TOUCHED)");
+
+      std::unique_ptr<text_art::tree_widget> cluster_widget
+       (text_art::tree_widget::make (dwi, pp));
+
+      m_map.add_to_tree_widget (*cluster_widget, dwi);
+
+      return cluster_widget;
+    }
+}
+
 /* Add a binding of SVAL of kind KIND to REG, unpacking SVAL if it is a
    compound_sval.  */
 
@@ -2613,6 +2707,68 @@ store::to_json () const
   return store_obj;
 }
 
+std::unique_ptr<text_art::widget>
+store::make_dump_widget (const text_art::dump_widget_info &dwi,
+                        store_manager *mgr) const
+{
+  std::unique_ptr<text_art::tree_widget> store_widget
+    (text_art::tree_widget::make (dwi, "Store"));
+
+  store_widget->add_child
+    (text_art::tree_widget::from_fmt (dwi, nullptr,
+                                     "m_called_unknown_fn: %s",
+                                     m_called_unknown_fn ? "true" : "false"));
+
+    /* Sort into some deterministic order.  */
+  auto_vec<const region *> base_regions;
+  for (cluster_map_t::iterator iter = m_cluster_map.begin ();
+       iter != m_cluster_map.end (); ++iter)
+    {
+      const region *base_reg = (*iter).first;
+      base_regions.safe_push (base_reg);
+    }
+  base_regions.qsort (region::cmp_ptr_ptr);
+
+  /* Gather clusters, organize by parent region, so that we can group
+     together locals, globals, etc.  */
+  auto_vec<const region *> parent_regions;
+  get_sorted_parent_regions (&parent_regions, base_regions);
+
+  const region *parent_reg;
+  unsigned i;
+  FOR_EACH_VEC_ELT (parent_regions, i, parent_reg)
+    {
+      gcc_assert (parent_reg);
+
+      pretty_printer the_pp;
+      pretty_printer * const pp = &the_pp;
+      pp_format_decoder (pp) = default_tree_printer;
+      pp_show_color (pp) = true;
+      const bool simple = true;
+
+      parent_reg->dump_to_pp (pp, simple);
+
+      std::unique_ptr<text_art::tree_widget> parent_reg_widget
+       (text_art::tree_widget::make (dwi, pp));
+
+      const region *base_reg;
+      unsigned j;
+      FOR_EACH_VEC_ELT (base_regions, j, base_reg)
+       {
+         /* This is O(N * M), but N ought to be small.  */
+         if (base_reg->get_parent_region () != parent_reg)
+           continue;
+         binding_cluster *cluster
+           = *const_cast<cluster_map_t &> (m_cluster_map).get (base_reg);
+         parent_reg_widget->add_child
+           (cluster->make_dump_widget (dwi, mgr));
+       }
+      store_widget->add_child (std::move (parent_reg_widget));
+    }
+
+  return store_widget;
+}
+
 /* Get any svalue bound to REG, or NULL.  */
 
 const svalue *
diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h
index 2b603654946..9be9df72379 100644
--- a/gcc/analyzer/store.h
+++ b/gcc/analyzer/store.h
@@ -21,6 +21,8 @@ along with GCC; see the file COPYING3.  If not see
 #ifndef GCC_ANALYZER_STORE_H
 #define GCC_ANALYZER_STORE_H
 
+#include "text-art/tree-widget.h"
+
 /* Implementation of the region-based ternary model described in:
      "A Memory Model for Static Analysis of C Programs"
       (Zhongxing Xu, Ted Kremenek, and Jian Zhang)
@@ -546,6 +548,9 @@ public:
 
   json::object *to_json () const;
 
+  void add_to_tree_widget (text_art::tree_widget &parent_widget,
+                          const text_art::dump_widget_info &dwi) const;
+
   bool apply_ctor_to_region (const region *parent_reg, tree ctor,
                             region_model_manager *mgr);
 
@@ -612,6 +617,10 @@ public:
 
   json::object *to_json () const;
 
+  std::unique_ptr<text_art::widget>
+  make_dump_widget (const text_art::dump_widget_info &dwi,
+                   store_manager *mgr) const;
+
   void bind (store_manager *mgr, const region *, const svalue *);
 
   void clobber_region (store_manager *mgr, const region *reg);
@@ -750,6 +759,10 @@ public:
 
   json::object *to_json () const;
 
+  std::unique_ptr<text_art::widget>
+  make_dump_widget (const text_art::dump_widget_info &dwi,
+                   store_manager *mgr) const;
+
   const svalue *get_any_binding (store_manager *mgr, const region *reg) const;
 
   bool called_unknown_fn_p () const { return m_called_unknown_fn; }
diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc
index e64f62d266e..f1fd21e4cda 100644
--- a/gcc/analyzer/svalue.cc
+++ b/gcc/analyzer/svalue.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
@@ -49,6 +50,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/region-model.h"
 #include "diagnostic.h"
 #include "tree-diagnostic.h"
+#include "make-unique.h"
+#include "text-art/dump.h"
 
 #if ENABLE_ANALYZER
 
@@ -60,6 +63,24 @@ static int cmp_csts_and_types (const_tree cst1, const_tree 
cst2);
 
 /* class svalue.  */
 
+/* Dump a tree-like representation of this svalue and its constituent symbols
+   to stderr, using global_dc's colorization and theming options.
+
+   For example:
+   . (gdb) call index_sval->dump()
+   . (27): ‘int’: initial_svalue
+   . ╰─ m_reg: (26): ‘int’: decl_region(‘x_10(D)’)
+   .    ╰─ parent: (9): frame_region(‘test_bitmask_2’, index: 0, depth: 1)
+   .       ╰─ parent: (1): stack region
+   .          ╰─ parent: (0): root region
+  */
+
+DEBUG_FUNCTION void
+svalue::dump () const
+{
+  text_art::dump (*this);
+}
+
 /* Dump a representation of this svalue to stderr.  */
 
 DEBUG_FUNCTION void
@@ -203,6 +224,37 @@ svalue::maybe_print_for_user (pretty_printer *pp,
   return false;
 }
 
+/* Use DWI to create a text_art::widget describing this svalue in
+   a tree-like form, using PREFIX as a prefix (e.g. for field names).
+   We do this via two vfuncs:
+   (a) print_dump_widget_label, to populate the text of a tree_widget, and
+   (b) add_dump_widget_children, to add children to the tree_widget.  */
+
+std::unique_ptr<text_art::widget>
+svalue::make_dump_widget (const text_art::dump_widget_info &dwi,
+                         const char *prefix) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = true;
+
+  if (prefix)
+    pp_printf (&pp, "%s: ", prefix);
+
+  pp_printf (&pp, "(%i): ", get_id ());
+  if (get_type ())
+    pp_printf (&pp, "%qT: ", get_type ());
+
+  print_dump_widget_label (&pp);
+
+  std::unique_ptr<text_art::tree_widget> w
+    (text_art::tree_widget::make (dwi, &pp));
+
+  add_dump_widget_children (*w, dwi);
+
+  return w;
+}
+
 /* If this svalue is a constant_svalue, return the underlying tree constant.
    Otherwise return NULL_TREE.  */
 
@@ -842,6 +894,26 @@ region_svalue::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   region_svalue.  */
+
+void
+region_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "region_svalue: %qs", "&");
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   region_svalue.  */
+
+void
+region_svalue::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  w.add_child (m_reg->make_dump_widget (dwi));
+}
+
 /* Implementation of svalue::accept vfunc for region_svalue.  */
 
 void
@@ -942,6 +1014,26 @@ constant_svalue::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   constant_svalue.  */
+
+void
+constant_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "constant_svalue (%qE)", m_cst_expr);
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   constant_svalue.  */
+
+void
+constant_svalue::
+add_dump_widget_children (text_art::tree_widget &,
+                         const text_art::dump_widget_info &) const
+{
+  /* No children.  */
+}
+
 /* Implementation of svalue::accept vfunc for constant_svalue.  */
 
 void
@@ -1085,6 +1177,26 @@ unknown_svalue::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   unknown_svalue.  */
+
+void
+unknown_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "unknown_svalue");
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   unknown_svalue.  */
+
+void
+unknown_svalue::
+add_dump_widget_children (text_art::tree_widget &,
+                         const text_art::dump_widget_info &) const
+{
+  /* No children.  */
+}
+
 /* Implementation of svalue::accept vfunc for unknown_svalue.  */
 
 void
@@ -1146,6 +1258,26 @@ poisoned_svalue::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   poisoned_svalue.  */
+
+void
+poisoned_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "poisoned_svalue(%s)", poison_kind_to_str (m_kind));
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   poisoned_svalue.  */
+
+void
+poisoned_svalue::
+add_dump_widget_children (text_art::tree_widget &,
+                         const text_art::dump_widget_info &) const
+{
+  /* No children.  */
+}
+
 /* Implementation of svalue::accept vfunc for poisoned_svalue.  */
 
 void
@@ -1195,6 +1327,26 @@ initial_svalue::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   initial_svalue.  */
+
+void
+initial_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "initial_svalue");
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   initial_svalue.  */
+
+void
+initial_svalue::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  w.add_child (m_reg->make_dump_widget (dwi, "m_reg"));
+}
+
 /* Implementation of svalue::accept vfunc for initial_svalue.  */
 
 void
@@ -1287,6 +1439,28 @@ unaryop_svalue::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   unaryop_svalue.  */
+
+void
+unaryop_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp,
+            "unaryop_svalue(%s)",
+            get_tree_code_name (m_op));
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   unaryop_svalue.  */
+
+void
+unaryop_svalue::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  w.add_child (m_arg->make_dump_widget (dwi));
+}
+
 /* Implementation of svalue::accept vfunc for unaryop_svalue.  */
 
 void
@@ -1389,6 +1563,30 @@ binop_svalue::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   binop_svalue.  */
+
+void
+binop_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp,
+            "binop_svalue(%s: %qs)",
+            get_tree_code_name (m_op),
+            op_symbol_code (m_op));
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   binop_svalue.  */
+
+void
+binop_svalue::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  w.add_child (m_arg0->make_dump_widget (dwi));
+  w.add_child (m_arg1->make_dump_widget (dwi));
+}
+
 /* Implementation of svalue::accept vfunc for binop_svalue.  */
 
 void
@@ -1449,6 +1647,27 @@ sub_svalue::dump_to_pp (pretty_printer *pp, bool simple) 
const
     }
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   sub_svalue.  */
+
+void
+sub_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "sub_svalue");
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   sub_svalue.  */
+
+void
+sub_svalue::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  w.add_child (m_parent_svalue->make_dump_widget (dwi, "m_parent_svalue"));
+  w.add_child (m_subregion->make_dump_widget (dwi, "m_subregion"));
+}
+
 /* Implementation of svalue::accept vfunc for sub_svalue.  */
 
 void
@@ -1519,6 +1738,27 @@ repeated_svalue::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   repeated_svalue.  */
+
+void
+repeated_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "repeated_svalue");
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   repeated_svalue.  */
+
+void
+repeated_svalue::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  w.add_child (m_outer_size->make_dump_widget (dwi, "m_outer_size"));
+  w.add_child (m_inner_svalue->make_dump_widget (dwi, "m_inner_svalue"));
+}
+
 /* Implementation of svalue::accept vfunc for repeated_svalue.  */
 
 void
@@ -1644,6 +1884,27 @@ bits_within_svalue::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   bits_within_svalue.  */
+
+void
+bits_within_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "bits_within_svalue: ");
+  m_bits.dump_to_pp (pp);
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   bits_within_svalue.  */
+
+void
+bits_within_svalue::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  w.add_child (m_inner_svalue->make_dump_widget (dwi, "m_inner_svalue"));
+}
+
 /* Implementation of svalue::maybe_fold_bits_within vfunc
    for bits_within_svalue.  */
 
@@ -1712,6 +1973,28 @@ widening_svalue::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   widening_svalue.  */
+
+void
+widening_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "widening_svalue at ");
+  m_point.print (pp, format (false));
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   widening_svalue.  */
+
+void
+widening_svalue::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  w.add_child (m_base_sval->make_dump_widget (dwi, "m_base_sval"));
+  w.add_child (m_iter_sval->make_dump_widget (dwi, "m_iter_sval"));
+}
+
 /* Implementation of svalue::accept vfunc for widening_svalue.  */
 
 void
@@ -1850,6 +2133,26 @@ placeholder_svalue::dump_to_pp (pretty_printer *pp, bool 
simple) const
     pp_printf (pp, "placeholder_svalue (%qs)", m_name);
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   placeholder_svalue.  */
+
+void
+placeholder_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "placeholder_svalue: %qs", m_name);
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   placeholder_svalue.  */
+
+void
+placeholder_svalue::
+add_dump_widget_children (text_art::tree_widget &,
+                         const text_art::dump_widget_info &) const
+{
+  /* No children.  */
+}
+
 /* Implementation of svalue::accept vfunc for placeholder_svalue.  */
 
 void
@@ -1879,6 +2182,26 @@ unmergeable_svalue::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   unmergeable_svalue.  */
+
+void
+unmergeable_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "unmergeable_svalue");
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   unmergeable_svalue.  */
+
+void
+unmergeable_svalue::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  w.add_child (m_arg->make_dump_widget (dwi));
+}
+
 /* Implementation of svalue::accept vfunc for unmergeable_svalue.  */
 
 void
@@ -1950,6 +2273,26 @@ compound_svalue::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   compound_svalue.  */
+
+void
+compound_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "compound_svalue");
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   compound_svalue.  */
+
+void
+compound_svalue::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  m_map.add_to_tree_widget (w, dwi);
+}
+
 /* Implementation of svalue::accept vfunc for compound_svalue.  */
 
 void
@@ -2081,6 +2424,30 @@ conjured_svalue::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   conjured_svalue.  */
+
+void
+conjured_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "conjured_svalue (");
+  pp_gimple_stmt_1 (pp, m_stmt, 0, (dump_flags_t)0);
+  if (m_idx != 0)
+    pp_printf (pp, ", %i", m_idx);
+  pp_character (pp, ')');
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   conjured_svalue.  */
+
+void
+conjured_svalue::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  w.add_child (m_id_reg->make_dump_widget (dwi));
+}
+
 /* Implementation of svalue::accept vfunc for conjured_svalue.  */
 
 void
@@ -2136,6 +2503,34 @@ asm_output_svalue::dump_to_pp (pretty_printer *pp, bool 
simple) const
     }
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   asm_output_svalue.  */
+
+void
+asm_output_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "asm_output_svalue(%qs, %%%i)",
+            get_asm_string (),
+            get_output_idx ());
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   asm_output_svalue.  */
+
+void
+asm_output_svalue::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  for (unsigned i = 0; i < m_num_inputs; i++)
+    {
+      pretty_printer pp;
+      pp_printf (&pp, "arg %i", i);
+      w.add_child (m_input_arr[i]->make_dump_widget (dwi,
+                                                    pp_formatted_text (&pp)));
+    }
+}
+
 /* Subroutine of asm_output_svalue::dump_to_pp.  */
 
 void
@@ -2198,6 +2593,32 @@ const_fn_result_svalue::dump_to_pp (pretty_printer *pp, 
bool simple) const
     }
 }
 
+/* Implementation of svalue::print_dump_widget_label vfunc for
+   const_fn_result_svalue.  */
+
+void
+const_fn_result_svalue::print_dump_widget_label (pretty_printer *pp) const
+{
+  pp_printf (pp, "const_fn_result_svalue: %qD", m_fndecl);
+}
+
+/* Implementation of svalue::add_dump_widget_children vfunc for
+   const_fn_result_svalue.  */
+
+void
+const_fn_result_svalue::
+add_dump_widget_children (text_art::tree_widget &w,
+                         const text_art::dump_widget_info &dwi) const
+{
+  for (unsigned i = 0; i < m_num_inputs; i++)
+    {
+      pretty_printer pp;
+      pp_printf (&pp, "arg %i", i);
+      w.add_child (m_input_arr[i]->make_dump_widget (dwi,
+                                                    pp_formatted_text (&pp)));
+    }
+}
+
 /* Subroutine of const_fn_result_svalue::dump_to_pp.  */
 
 void
diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h
index 3a2b0319a19..e5503a54177 100644
--- a/gcc/analyzer/svalue.h
+++ b/gcc/analyzer/svalue.h
@@ -24,8 +24,10 @@ along with GCC; see the file COPYING3.  If not see
 #include "analyzer/symbol.h"
 #include "analyzer/store.h"
 #include "analyzer/program-point.h"
+#include "text-art/widget.h"
 
 using namespace ana;
+using text_art::dump_widget_info;
 
 namespace ana {
 
@@ -99,11 +101,16 @@ public:
              pretty_printer *pp) const;
 
   virtual void dump_to_pp (pretty_printer *pp, bool simple) const = 0;
-  void dump (bool simple=true) const;
+  void dump () const;
+  void dump (bool simple) const;
   label_text get_desc (bool simple=true) const;
 
   json::value *to_json () const;
 
+  std::unique_ptr<text_art::widget>
+  make_dump_widget (const dump_widget_info &dwi,
+                   const char *prefix = nullptr) const;
+
   virtual const region_svalue *
   dyn_cast_region_svalue () const { return NULL; }
   virtual const constant_svalue *
@@ -186,7 +193,15 @@ public:
   : symbol (c, id), m_type (type)
   {}
 
+  void print_svalue_node_label (pretty_printer *pp) const;
+
  private:
+  virtual void
+  print_dump_widget_label (pretty_printer *pp) const = 0;
+  virtual void
+  add_dump_widget_children (text_art::tree_widget &,
+                           const dump_widget_info &dwi) const = 0;
+
   tree m_type;
 };
 
@@ -237,6 +252,13 @@ public:
   dyn_cast_region_svalue () const final override { return this; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
   bool implicitly_live_p (const svalue_set *,
                          const region_model *) const final override;
@@ -319,6 +341,13 @@ public:
   dyn_cast_constant_svalue () const final override { return this; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
   bool implicitly_live_p (const svalue_set *,
                          const region_model *) const final override;
@@ -372,6 +401,13 @@ public:
   enum svalue_kind get_kind () const final override { return SK_UNKNOWN; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
 
   const svalue *
@@ -445,6 +481,13 @@ public:
   dyn_cast_poisoned_svalue () const final override { return this; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
 
   const svalue *
@@ -555,6 +598,13 @@ public:
   dyn_cast_setjmp_svalue () const final override { return this; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
 
   int get_enode_index () const;
@@ -605,6 +655,13 @@ public:
   dyn_cast_initial_svalue () const final override { return this; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
   bool implicitly_live_p (const svalue_set *,
                          const region_model *) const final override;
@@ -680,6 +737,13 @@ public:
   dyn_cast_unaryop_svalue () const final override { return this; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
   bool implicitly_live_p (const svalue_set *,
                          const region_model *) const final override;
@@ -777,6 +841,13 @@ public:
   }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
   bool implicitly_live_p (const svalue_set *,
                          const region_model *) const final override;
@@ -858,6 +929,13 @@ public:
   }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
   bool implicitly_live_p (const svalue_set *,
                          const region_model *) const final override;
@@ -941,6 +1019,13 @@ public:
   }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
 
   const svalue *get_outer_size () const { return m_outer_size; }
@@ -1030,6 +1115,13 @@ public:
   }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
   bool implicitly_live_p (const svalue_set *,
                          const region_model *) const final override;
@@ -1089,6 +1181,13 @@ public:
   dyn_cast_unmergeable_svalue () const final override { return this; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
   bool implicitly_live_p (const svalue_set *,
                          const region_model *) const final override;
@@ -1127,6 +1226,13 @@ public:
   enum svalue_kind get_kind () const final override { return SK_PLACEHOLDER; }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
 
   const char *get_name () const { return m_name; }
@@ -1223,6 +1329,13 @@ public:
   }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
 
   const function_point &get_point () const { return m_point; }
@@ -1318,6 +1431,13 @@ public:
   }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
 
   const binding_map &get_map () const { return m_map; }
@@ -1455,6 +1575,13 @@ public:
   }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
 
   const gimple *get_stmt () const { return m_stmt; }
@@ -1580,6 +1707,13 @@ public:
   }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
 
   const char *get_asm_string () const { return m_asm_string; }
@@ -1713,6 +1847,13 @@ public:
   }
 
   void dump_to_pp (pretty_printer *pp, bool simple) const final override;
+
+  void
+  print_dump_widget_label (pretty_printer *pp) const final override;
+  void
+  add_dump_widget_children (text_art::tree_widget &w,
+                           const dump_widget_info &dwi) const final override;
+
   void accept (visitor *v) const final override;
 
   tree get_fndecl () const { return m_fndecl; }
diff --git a/gcc/analyzer/trimmed-graph.cc b/gcc/analyzer/trimmed-graph.cc
index b24b334c2ee..a03cae5862b 100644
--- a/gcc/analyzer/trimmed-graph.cc
+++ b/gcc/analyzer/trimmed-graph.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
diff --git a/gcc/analyzer/varargs.cc b/gcc/analyzer/varargs.cc
index 3348121a0ef..684e1db2066 100644
--- a/gcc/analyzer/varargs.cc
+++ b/gcc/analyzer/varargs.cc
@@ -20,6 +20,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "config.h"
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "system.h"
 #include "coretypes.h"
 #include "make-unique.h"
diff --git a/gcc/doc/analyzer.texi b/gcc/doc/analyzer.texi
index b53096e7b7d..14034737f83 100644
--- a/gcc/doc/analyzer.texi
+++ b/gcc/doc/analyzer.texi
@@ -259,56 +259,175 @@ memory, with a @code{store} recording a binding between 
@code{region}
 instances, to @code{svalue} instances.  The bindings are organized into
 clusters, where regions accessible via well-defined pointer arithmetic
 are in the same cluster.  The representation is graph-like because values
-can be pointers to regions.  It also stores a constraint_manager,
+can be pointers to regions.  It also stores a @code{constraint_manager},
 capturing relationships between the values.
 
 Because each node in the @code{exploded_graph} has a @code{region_model},
 and each of the latter is graph-like, the @code{exploded_graph} is in some
 ways a graph of graphs.
 
-Here's an example of printing a @code{program_state}, showing the
-@code{region_model} within it, along with state for the @code{malloc}
-state machine.
+There are several ``dump'' functions for use when debugging the analyzer.
+
+Consider this example C code:
+
+@smallexample
+void *
+calls_malloc (size_t n)
+@{
+  void *result = malloc (1024);
+  return result; /* HERE */
+@}
+
+void test (size_t n)
+@{
+  void *ptr = calls_malloc (n * 4);
+  /* etc.  */
+@}
+@end smallexample
+
+and the state at the point @code{/* HERE */} for the interprocedural
+analysis case where @code{calls_malloc} returns back to @code{test}.
+
+Here's an example of printing a @code{program_state} at @code{/* HERE */},
+showing the @code{region_model} within it, along with state for the
+@code{malloc} state machine.
 
 @smallexample
-(gdb) call debug (*this)
+(gdb) break region_model::on_return
+[..snip...]
+(gdb) run
+[..snip...]
+(gdb) up
+[..snip...]
+(gdb) call state->dump()
+State
+├─ Region Model
+│  ├─ Current Frame: frame: ‘calls_malloc’@@2
+│  ├─ Store
+│  │  ├─ m_called_unknown_fn: false
+│  │  ├─ frame: ‘test’@@1
+│  │  │  ╰─ _1: (INIT_VAL(n_2(D))*(size_t)4)
+│  │  ╰─ frame: ‘calls_malloc’@@2
+│  │     ├─ result_4: &HEAP_ALLOCATED_REGION(27)
+│  │     ╰─ _5: &HEAP_ALLOCATED_REGION(27)
+│  ╰─ Dynamic Extents
+│     ╰─ HEAP_ALLOCATED_REGION(27): (INIT_VAL(n_2(D))*(size_t)4)
+╰─ ‘malloc’ state machine
+   ╰─ 0x468cb40: &HEAP_ALLOCATED_REGION(27): unchecked (@{free@}) (‘result_4’)
+@end smallexample
+
+Within the store, there are bindings clusters for the SSA names for the
+various local variables within frames for @code{test} and
+@code{calls_malloc}.  For example,
+
+@itemize @bullet
+@item
+within @code{test} the whole cluster for @code{_1} is bound
+to a @code{binop_svalue} representing @code{n * 4}, and
+@item
+within @code{test} the whole cluster for @code{result_4} is bound to a
+@code{region_svalue} pointing at @code{HEAP_ALLOCATED_REGION(12)}.
+@end itemize
+
+Additionally, this latter pointer has the @code{unchecked} state for the
+@code{malloc} state machine indicating it hasn't yet been checked against
+@code{NULL} since the allocation call.
+
+We also see that the state has captured the size of the heap-allocated
+region (``Dynamic Extents'').
+
+This visualization can also be seen within the output of
+@option{-fdump-analyzer-exploded-nodes-2} and
+@option{-fdump-analyzer-exploded-nodes-3}.
+
+As well as the above visualizations of states, there are tree-like
+visualizations for instances of @code{svalue} and @code{region}, showing
+their IDs and how they are constructed from simpler symbols:
+
+@smallexample
+(gdb) break region_model::set_dynamic_extents
+[..snip...]
+(gdb) run
+[..snip...]
+(gdb) up
+[..snip...]
+(gdb) call size_in_bytes->dump()
+(17): ‘long unsigned int’: binop_svalue(mult_expr: ‘*’)
+├─ (15): ‘size_t’: initial_svalue
+│  ╰─ m_reg: (12): ‘size_t’: decl_region(‘n_2(D)’)
+│     ╰─ parent: (9): frame_region(‘test’, index: 0, depth: 1)
+│        ╰─ parent: (1): stack region
+│           ╰─ parent: (0): root region
+╰─ (16): ‘size_t’: constant_svalue (‘4’)
+@end smallexample
+
+i.e. that @code{size_in_bytes} is a @code{binop_svalue} expressing
+the result of multiplying
+
+@itemize @bullet
+@item
+the initial value of the @code{PARM_DECL} @code{n_2(D)} for the
+parameter @code{n} within the frame for @code{test} by
+@item
+the constant value @code{4}.
+@end itemize
+
+The above visualizations rely on the @code{text_art::widget} framework,
+which performs significant work to lay out the output, so there is also
+an earlier, simpler, form of dumping available.  For states there is:
+
+@smallexample
+(gdb) call state->dump(eg.m_ext_state, true)
 rmodel:
-stack depth: 1
+stack depth: 2
+  frame (index 1): frame: ‘calls_malloc’@@2
   frame (index 0): frame: ‘test’@@1
 clusters within frame: ‘test’@@1
-  cluster for: ptr_3: &HEAP_ALLOCATED_REGION(12)
+  cluster for: _1: (INIT_VAL(n_2(D))*(size_t)4)
+clusters within frame: ‘calls_malloc’@@2
+  cluster for: result_4: &HEAP_ALLOCATED_REGION(27)
+  cluster for: _5: &HEAP_ALLOCATED_REGION(27)
 m_called_unknown_fn: FALSE
 constraint_manager:
   equiv classes:
   constraints:
+dynamic_extents:
+  HEAP_ALLOCATED_REGION(27): (INIT_VAL(n_2(D))*(size_t)4)
 malloc:
-  0x2e89590: &HEAP_ALLOCATED_REGION(12): unchecked ('ptr_3')
+  0x468cb40: &HEAP_ALLOCATED_REGION(27): unchecked (@{free@}) (‘result_4’)
 @end smallexample
 
-This is the state at the point of returning from @code{calls_malloc} back
-to @code{test} in the following:
+or for @code{region_model} just:
 
 @smallexample
-void *
-calls_malloc (void)
-@{
-  void *result = malloc (1024);
-  return result;
-@}
-
-void test (void)
-@{
-  void *ptr = calls_malloc ();
-  /* etc.  */
-@}
+(gdb) call state->m_region_model->debug()
+stack depth: 2
+  frame (index 1): frame: ‘calls_malloc’@@2
+  frame (index 0): frame: ‘test’@@1
+clusters within frame: ‘test’@@1
+  cluster for: _1: (INIT_VAL(n_2(D))*(size_t)4)
+clusters within frame: ‘calls_malloc’@@2
+  cluster for: result_4: &HEAP_ALLOCATED_REGION(27)
+  cluster for: _5: &HEAP_ALLOCATED_REGION(27)
+m_called_unknown_fn: FALSE
+constraint_manager:
+  equiv classes:
+  constraints:
+dynamic_extents:
+  HEAP_ALLOCATED_REGION(27): (INIT_VAL(n_2(D))*(size_t)4)
 @end smallexample
 
-Within the store, there is the cluster for @code{ptr_3} within the frame
-for @code{test}, where the whole cluster is bound to a pointer value,
-pointing at @code{HEAP_ALLOCATED_REGION(12)}.  Additionally, this pointer
-has the @code{unchecked} state for the @code{malloc} state machine
-indicating it hasn't yet been checked against NULL since the allocation
-call.
+and for instances of @code{svalue} and @code{region} there is this
+older dump implementation, which takes a @code{bool simple} flag
+controlling the verbosity of the dump:
+
+@smallexample
+(gdb) call size_in_bytes->dump(true)
+(INIT_VAL(n_2(D))*(size_t)4)
+
+(gdb) call size_in_bytes->dump(false)
+binop_svalue (mult_expr, initial_svalue(‘size_t’, 
decl_region(frame_region(‘test’, index: 0, depth: 1), ‘size_t’, ‘n_2(D)’)), 
constant_svalue(‘size_t’, 4))
+@end smallexample
 
 @subsection Analyzer Paths
 
diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c 
b/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c
index b5814dd709e..1fc43007cf1 100644
--- a/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c
+++ b/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c
@@ -2,6 +2,7 @@
 /* { dg-options "-g" } */
 
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "gcc-plugin.h"
 #include "config.h"
 #include "system.h"
diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c 
b/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c
index 6ea6c03e1d5..57135fc5904 100644
--- a/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c
+++ b/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c
@@ -5,6 +5,7 @@
 /* { dg-options "-g" } */
 
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "gcc-plugin.h"
 #include "config.h"
 #include "system.h"
diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.c 
b/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.c
index 5a32f8cc620..7f2158ec54d 100644
--- a/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.c
+++ b/gcc/testsuite/gcc.dg/plugin/analyzer_kernel_plugin.c
@@ -2,6 +2,7 @@
 /* { dg-options "-g" } */
 
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "gcc-plugin.h"
 #include "config.h"
 #include "system.h"
diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_known_fns_plugin.c 
b/gcc/testsuite/gcc.dg/plugin/analyzer_known_fns_plugin.c
index 806cb90ef56..c0604070552 100644
--- a/gcc/testsuite/gcc.dg/plugin/analyzer_known_fns_plugin.c
+++ b/gcc/testsuite/gcc.dg/plugin/analyzer_known_fns_plugin.c
@@ -2,6 +2,7 @@
 /* { dg-options "-g" } */
 
 #define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
 #include "gcc-plugin.h"
 #include "config.h"
 #include "system.h"
diff --git a/gcc/text-art/dump-widget-info.h b/gcc/text-art/dump-widget-info.h
new file mode 100644
index 00000000000..8676852aeeb
--- /dev/null
+++ b/gcc/text-art/dump-widget-info.h
@@ -0,0 +1,53 @@
+/* Support for creating dump widgets.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalc...@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_TEXT_ART_DUMP_WIDGET_INFO_H
+#define GCC_TEXT_ART_DUMP_WIDGET_INFO_H
+
+namespace text_art {
+
+/* A bundle of state for use by make_dump_widget implementations.
+   The referenced objects are expected to outlive the widgets
+   themselves.  */
+
+struct dump_widget_info
+{
+  dump_widget_info (text_art::style_manager &sm,
+                   const text_art::theme &theme,
+                   text_art::style::id_t tree_style_id)
+  : m_sm (sm),
+    m_theme (theme),
+    m_tree_style_id (tree_style_id)
+  {
+  }
+
+  text_art::style::id_t get_tree_style_id () const
+  {
+    return m_tree_style_id;
+  }
+
+  text_art::style_manager &m_sm;
+  const text_art::theme &m_theme;
+  text_art::style::id_t m_tree_style_id;
+};
+
+} // namespace text_art
+
+#endif /* GCC_TEXT_ART_DUMP_WIDGET_INFO_H */
diff --git a/gcc/text-art/dump.h b/gcc/text-art/dump.h
new file mode 100644
index 00000000000..e94f308f8ce
--- /dev/null
+++ b/gcc/text-art/dump.h
@@ -0,0 +1,83 @@
+/* Templates for dumping objects.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalc...@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_TEXT_ART_DUMP_H
+#define GCC_TEXT_ART_DUMP_H
+
+#include "tree-diagnostic.h"
+#include "text-art/canvas.h"
+#include "text-art/widget.h"
+#include "text-art/dump-widget-info.h"
+
+/* A family of templates for dumping objects via the text_art::widget
+   system.
+   Any type T that has a make_dump_widget member function ought to be
+   dumpable via these functions.  */
+
+namespace text_art {
+
+/* Dump OBJ to PP, using OBJ's make_dump_widget member function.  */
+
+template <typename T>
+void dump_to_pp (const T &obj, text_art::theme *theme, pretty_printer *pp)
+{
+  if (!theme)
+    return;
+
+  style_manager sm;
+  style tree_style (get_style_from_color_cap_name ("note"));
+
+  style::id_t tree_style_id (sm.get_or_create_id (tree_style));
+
+  dump_widget_info dwi (sm, *theme, tree_style_id);
+  if (std::unique_ptr<widget> w = obj.make_dump_widget (dwi))
+    {
+      text_art::canvas c (w->to_canvas (dwi.m_sm));
+      c.print_to_pp (pp);
+    }
+}
+
+/* Dump OBJ to OUTF, using OBJ's make_dump_widget member function.  */
+
+template <typename T>
+void dump_to_file (const T &obj, FILE *outf)
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  if (outf == stderr)
+    pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = outf;
+
+  text_art::theme *theme = global_dc->get_diagram_theme ();
+  dump_to_pp (obj, theme, &pp);
+  pp_flush (&pp);
+}
+
+/* Dump OBJ to stderr, using OBJ's make_dump_widget member function.  */
+
+template <typename T>
+void dump (const T &obj)
+{
+  dump_to_file (obj, stderr);
+}
+
+} // namespace text_art
+
+#endif /* GCC_TEXT_ART_DUMP_H */
diff --git a/gcc/text-art/selftests.cc b/gcc/text-art/selftests.cc
index bda1fc8e14a..5b4679b0cda 100644
--- a/gcc/text-art/selftests.cc
+++ b/gcc/text-art/selftests.cc
@@ -42,6 +42,7 @@ selftest::text_art_tests ()
   text_art_ruler_cc_tests ();
   text_art_table_cc_tests ();
   text_art_widget_cc_tests ();
+  text_art_tree_widget_cc_tests ();
 }
 
 /* Implementation detail of ASSERT_CANVAS_STREQ.  */
diff --git a/gcc/text-art/selftests.h b/gcc/text-art/selftests.h
index 2be6279f861..01e94539ced 100644
--- a/gcc/text-art/selftests.h
+++ b/gcc/text-art/selftests.h
@@ -34,6 +34,7 @@ extern void text_art_ruler_cc_tests ();
 extern void text_art_style_cc_tests ();
 extern void text_art_styled_string_cc_tests ();
 extern void text_art_table_cc_tests ();
+extern void text_art_tree_widget_cc_tests ();
 extern void text_art_widget_cc_tests ();
 
 extern void text_art_tests ();
diff --git a/gcc/text-art/theme.cc b/gcc/text-art/theme.cc
index 1ee86c61028..e51b8ef7a84 100644
--- a/gcc/text-art/theme.cc
+++ b/gcc/text-art/theme.cc
@@ -155,6 +155,15 @@ ascii_theme::get_cppchar (enum cell_kind kind) const
       return '+';
     case cell_kind::CFG_FROM_DOWN_TO_RIGHT:
       return '+';
+
+    case cell_kind::TREE_CHILD_NON_FINAL:
+      return '+';
+    case cell_kind::TREE_CHILD_FINAL:
+      return '`';
+    case cell_kind::TREE_X_CONNECTOR:
+      return '-';
+    case cell_kind::TREE_Y_CONNECTOR:
+      return '|';
     }
 }
 
@@ -240,5 +249,14 @@ unicode_theme::get_cppchar (enum cell_kind kind) const
       return 0x250c; /* "┌" U+250C: BOX DRAWINGS LIGHT DOWN AND RIGHT */
     case cell_kind::CFG_FROM_DOWN_TO_RIGHT:
       return 0x2514; /* "└": U+2514: BOX DRAWINGS LIGHT UP AND RIGHT  */
+
+    case cell_kind::TREE_CHILD_NON_FINAL:
+      return 0x251C; /* "├": U+251C: BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
+    case cell_kind::TREE_CHILD_FINAL:
+      return 0x2570; /* "╰": U+2570 BOX DRAWINGS LIGHT ARC UP AND RIGHT.  */
+    case cell_kind::TREE_X_CONNECTOR:
+      return 0x2500; /* "─": U+2500: BOX DRAWINGS LIGHT HORIZONTAL */
+    case cell_kind::TREE_Y_CONNECTOR:
+      return 0x2502; /* "│": U+2502: BOX DRAWINGS LIGHT VERTICAL */
     }
 }
diff --git a/gcc/text-art/theme.h b/gcc/text-art/theme.h
index e41fcc87230..dec09f8439b 100644
--- a/gcc/text-art/theme.h
+++ b/gcc/text-art/theme.h
@@ -81,7 +81,13 @@ class theme
     CFG_FROM_DOWN_TO_LEFT,  /* e.g. "+".  */
     CFG_LEFT,               /* e.g. "-".  */
     CFG_FROM_LEFT_TO_DOWN,  /* e.g. "+".  */
-    CFG_FROM_DOWN_TO_RIGHT  /* e.g. "+".  */
+    CFG_FROM_DOWN_TO_RIGHT, /* e.g. "+".  */
+
+    /* Tree stuff.  */
+    TREE_CHILD_NON_FINAL, /* e.g. "├" or "+".  */
+    TREE_CHILD_FINAL,     /* e.g. "╰" or "`".  */
+    TREE_X_CONNECTOR,     /* e.g. "─" or "-".  */
+    TREE_Y_CONNECTOR      /* e.g. "|" or "|".  */
   };
 
   virtual ~theme () = default;
diff --git a/gcc/text-art/tree-widget.cc b/gcc/text-art/tree-widget.cc
new file mode 100644
index 00000000000..69282e46a44
--- /dev/null
+++ b/gcc/text-art/tree-widget.cc
@@ -0,0 +1,237 @@
+/* Tree diagrams.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalc...@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
+#include "system.h"
+#include "coretypes.h"
+#include "pretty-print.h"
+#include "selftest.h"
+#include "make-unique.h"
+#include "text-art/selftests.h"
+#include "text-art/tree-widget.h"
+#include "text-art/dump-widget-info.h"
+
+using namespace text_art;
+
+/* class text_art::tree_widget : public text_art::widget.  */
+
+static const int margin_width = 3;
+
+std::unique_ptr<tree_widget>
+tree_widget::make (styled_string str, const theme &theme, style::id_t style_id)
+{
+  return ::make_unique <tree_widget>
+    (::make_unique <text_widget> (std::move (str)),
+     theme,
+     style_id);
+}
+
+std::unique_ptr<tree_widget>
+tree_widget::make (const dump_widget_info &dwi, pretty_printer *pp)
+{
+  return tree_widget::make (styled_string (dwi.m_sm, pp_formatted_text (pp)),
+                           dwi.m_theme,
+                           dwi.get_tree_style_id ());
+}
+
+std::unique_ptr<tree_widget>
+tree_widget::make (const dump_widget_info &dwi, const char *str)
+{
+  return tree_widget::make (styled_string (dwi.m_sm, str),
+                           dwi.m_theme,
+                           dwi.get_tree_style_id ());
+}
+
+std::unique_ptr<tree_widget>
+tree_widget::from_fmt (const dump_widget_info &dwi,
+                      printer_fn format_decoder,
+                      const char *fmt, ...)
+{
+  va_list ap;
+  va_start (ap, fmt);
+  styled_string styled_str
+    (styled_string::from_fmt_va (dwi.m_sm, format_decoder, fmt, &ap));
+  va_end (ap);
+  return make (std::move (styled_str), dwi.m_theme, dwi.get_tree_style_id ());
+}
+
+const char *
+tree_widget::get_desc () const
+{
+  return "tree_widget";
+}
+
+canvas::size_t
+tree_widget::calc_req_size ()
+{
+  canvas::size_t result (0, 0);
+  if (m_node)
+    {
+      canvas::size_t node_req_size = m_node->get_req_size ();
+      result.h += node_req_size.h;
+      result.w = std::max (result.w, node_req_size.w);
+    }
+  for (auto &child : m_children)
+    {
+      canvas::size_t child_req_size = child->get_req_size ();
+      result.h += child_req_size.h;
+      result.w = std::max (result.w, child_req_size.w + margin_width);
+    }
+  return result;
+}
+
+void
+tree_widget::update_child_alloc_rects ()
+{
+  const int x = get_min_x ();
+  int y = get_min_y ();
+  if (m_node)
+    {
+      m_node->set_alloc_rect
+       (canvas::rect_t (canvas::coord_t (x, y),
+                        canvas::size_t (get_alloc_w (),
+                                        m_node->get_req_h ())));
+      y += m_node->get_req_h ();
+    }
+  for (auto &child : m_children)
+    {
+      child->set_alloc_rect
+       (canvas::rect_t (canvas::coord_t (x + margin_width, y),
+                        canvas::size_t (get_alloc_w () - margin_width,
+                                        child->get_req_h ())));
+      y += child->get_req_h ();
+    }
+}
+
+void
+tree_widget::paint_to_canvas (canvas &canvas)
+{
+  if (m_node)
+    m_node->paint_to_canvas (canvas);
+  const int min_x = get_min_x ();
+  const canvas::cell_t cell_child_non_final
+    (m_theme.get_cell (theme::cell_kind::TREE_CHILD_NON_FINAL, m_style_id));
+  const canvas::cell_t cell_child_final
+    (m_theme.get_cell (theme::cell_kind::TREE_CHILD_FINAL, m_style_id));
+  const canvas::cell_t cell_x_connector
+    (m_theme.get_cell (theme::cell_kind::TREE_X_CONNECTOR, m_style_id));
+  const canvas::cell_t cell_y_connector
+    (m_theme.get_cell (theme::cell_kind::TREE_Y_CONNECTOR, m_style_id));
+  size_t idx = 0;
+  for (auto &child : m_children)
+    {
+      child->paint_to_canvas (canvas);
+
+      const bool last_child = (++idx == m_children.size ());
+      canvas.paint (canvas::coord_t (min_x + 1, child->get_min_y ()),
+                   cell_x_connector);
+      canvas.paint (canvas::coord_t (min_x, child->get_min_y ()),
+                   last_child ? cell_child_final : cell_child_non_final);
+      if (!last_child)
+       for (int y = child->get_min_y () + 1; y <= child ->get_max_y (); y++)
+         canvas.paint (canvas::coord_t (min_x, y), cell_y_connector);
+    }
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+static std::unique_ptr<tree_widget>
+make_test_tree_widget (const dump_widget_info &dwi)
+{
+  std::unique_ptr<tree_widget> w
+    (tree_widget::from_fmt (dwi, nullptr, "Root"));
+  for (int i = 0; i < 3; i++)
+    {
+      std::unique_ptr<tree_widget> c
+       (tree_widget::from_fmt (dwi, nullptr, "Child %i", i));
+      for (int j = 0; j < 3; j++)
+       c->add_child (tree_widget::from_fmt (dwi, nullptr,
+                                            "Grandchild %i %i", i, j));
+      w->add_child (std::move (c));
+    }
+  return w;
+}
+
+static void
+test_tree_widget ()
+{
+  style_manager sm;
+
+  style::id_t default_style_id (sm.get_or_create_id (style ()));
+
+  {
+    ascii_theme theme;
+    dump_widget_info dwi (sm, theme, default_style_id);
+    canvas c (make_test_tree_widget (dwi)->to_canvas (sm));
+    ASSERT_CANVAS_STREQ
+      (c, false,
+       ("Root\n"
+       "+- Child 0\n"
+       "|  +- Grandchild 0 0\n"
+       "|  +- Grandchild 0 1\n"
+       "|  `- Grandchild 0 2\n"
+       "+- Child 1\n"
+       "|  +- Grandchild 1 0\n"
+       "|  +- Grandchild 1 1\n"
+       "|  `- Grandchild 1 2\n"
+       "`- Child 2\n"
+       "   +- Grandchild 2 0\n"
+       "   +- Grandchild 2 1\n"
+       "   `- Grandchild 2 2\n"));
+  }
+
+  {
+    unicode_theme theme;
+    dump_widget_info dwi (sm, theme, default_style_id);
+    canvas c (make_test_tree_widget (dwi)->to_canvas (sm));
+    ASSERT_CANVAS_STREQ
+      (c, false,
+       ("Root\n"
+       "├─ Child 0\n"
+       "│  ├─ Grandchild 0 0\n"
+       "│  ├─ Grandchild 0 1\n"
+       "│  ╰─ Grandchild 0 2\n"
+       "├─ Child 1\n"
+       "│  ├─ Grandchild 1 0\n"
+       "│  ├─ Grandchild 1 1\n"
+       "│  ╰─ Grandchild 1 2\n"
+       "╰─ Child 2\n"
+       "   ├─ Grandchild 2 0\n"
+       "   ├─ Grandchild 2 1\n"
+       "   ╰─ Grandchild 2 2\n"));
+  }
+}
+
+/* Run all selftests in this file.  */
+
+void
+text_art_tree_widget_cc_tests ()
+{
+  test_tree_widget ();
+}
+
+} // namespace selftest
+
+
+#endif /* #if CHECKING_P */
diff --git a/gcc/text-art/tree-widget.h b/gcc/text-art/tree-widget.h
new file mode 100644
index 00000000000..73550ccf4ea
--- /dev/null
+++ b/gcc/text-art/tree-widget.h
@@ -0,0 +1,83 @@
+/* Tree diagrams.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalc...@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_TEXT_ART_TREE_WIDGET_H
+#define GCC_TEXT_ART_TREE_WIDGET_H
+
+#include "text-art/canvas.h"
+#include "text-art/widget.h"
+
+namespace text_art {
+
+class dump_widget_info;
+
+class tree_widget : public widget
+{
+public:
+  tree_widget (std::unique_ptr<widget> node,
+              const theme &theme,
+              style::id_t style_id)
+  : m_node (std::move (node)),
+    m_theme (theme),
+    m_style_id (style_id)
+  {
+  }
+
+  static std::unique_ptr<tree_widget>
+  make (styled_string str, const theme &theme, style::id_t style_id);
+
+  static std::unique_ptr<tree_widget>
+  make (const dump_widget_info &dwi, pretty_printer *pp);
+
+  static std::unique_ptr<tree_widget>
+  make (const dump_widget_info &dwi, const char *str);
+
+  static std::unique_ptr<tree_widget>
+  from_fmt (const dump_widget_info &dwi,
+           printer_fn format_decoder,
+           const char *fmt, ...)
+    ATTRIBUTE_GCC_PPDIAG(3, 4);
+
+  const char *get_desc () const override;
+  canvas::size_t calc_req_size () final override;
+  void update_child_alloc_rects () final override;
+  void paint_to_canvas (canvas &canvas) final override;
+
+  void add_child (std::unique_ptr<widget> child)
+  {
+    if (child)
+      m_children.push_back (std::move (child));
+  }
+
+  size_t get_num_children () const
+  {
+    return m_children.size ();
+  }
+
+private:
+  std::unique_ptr<widget> m_node;
+  std::vector<std::unique_ptr<widget>> m_children;
+  const theme &m_theme;
+  style::id_t m_style_id;
+};
+
+} // namespace text_art
+
+#endif /* GCC_TEXT_ART_TREE_WIDGET_H */

Reply via email to