[3/3] analyzer: add text-art visualizations of out-of-bounds accesses [PR106626]

Message ID 20230531180630.3127108-4-dmalcolm@redhat.com
State Committed
Headers
Series Add diagram support to gcc diagnostics |

Commit Message

David Malcolm May 31, 2023, 6:06 p.m. UTC
  This patch extends -Wanalyzer-out-of-bounds so that, where possible, it
will emit a text art diagram visualizing the spatial relationship between
(a) the memory region that the analyzer predicts would be accessed, versus
(b) the range of memory that is valid to access - whether they overlap,
are touching, are close or far apart; which one is before or after in
memory, the relative sizes involved, the direction of the access (read vs
write), and, in some cases, the values of data involved.  This diagram
can be suppressed using -fdiagnostics-text-art-charset=none.

For example, given:

int32_t arr[10];

int32_t int_arr_read_element_before_start_far(void)
{
  return arr[-100];
}

it emits:

demo-1.c: In function ‘int_arr_read_element_before_start_far’:
demo-1.c:7:13: warning: buffer under-read [CWE-127] [-Wanalyzer-out-of-bounds]
    7 |   return arr[-100];
      |          ~~~^~~~~~
  ‘int_arr_read_element_before_start_far’: event 1
    |
    |    7 |   return arr[-100];
    |      |          ~~~^~~~~~
    |      |             |
    |      |             (1) out-of-bounds read from byte -400 till byte -397 but ‘arr’ starts at byte 0
    |
demo-1.c:7:13: note: valid subscripts for ‘arr’ are ‘[0]’ to ‘[9]’

  ┌───────────────────────────┐
  │read of ‘int32_t’ (4 bytes)│
  └───────────────────────────┘
                ^
                │
                │
  ┌───────────────────────────┐              ┌────────┬────────┬─────────┐
  │                           │              │  [0]   │  ...   │   [9]   │
  │    before valid range     │              ├────────┴────────┴─────────┤
  │                           │              │‘arr’ (type: ‘int32_t[10]’)│
  └───────────────────────────┘              └───────────────────────────┘
  ├─────────────┬─────────────┤├─────┬──────┤├─────────────┬─────────────┤
                │                    │                     │
   ╭────────────┴───────────╮   ╭────┴────╮        ╭───────┴──────╮
   │⚠️  under-read of 4 bytes│   │396 bytes│        │size: 40 bytes│
   ╰────────────────────────╯   ╰─────────╯        ╰──────────────╯

and given:

  #include <string.h>

  void
  test_non_ascii ()
  {
    char buf[5];
    strcpy (buf, "文字化け");
  }

it emits:

demo-2.c: In function ‘test_non_ascii’:
demo-2.c:7:3: warning: stack-based buffer overflow [CWE-121] [-Wanalyzer-out-of-bounds]
    7 |   strcpy (buf, "文字化け");
      |   ^~~~~~~~~~~~~~~~~~~~~~~~
  ‘test_non_ascii’: events 1-2
    |
    |    6 |   char buf[5];
    |      |        ^~~
    |      |        |
    |      |        (1) capacity: 5 bytes
    |    7 |   strcpy (buf, "文字化け");
    |      |   ~~~~~~~~~~~~~~~~~~~~~~~~
    |      |   |
    |      |   (2) out-of-bounds write from byte 5 till byte 12 but ‘buf’ ends at byte 5
    |
demo-2.c:7:3: note: write of 8 bytes to beyond the end of ‘buf’
    7 |   strcpy (buf, "文字化け");
      |   ^~~~~~~~~~~~~~~~~~~~~~~~
demo-2.c:7:3: note: valid subscripts for ‘buf’ are ‘[0]’ to ‘[4]’

  ┌─────┬─────┬─────┬────┬────┐┌────┬────┬────┬────┬────┬────┬────┬──────┐
  │ [0] │ [1] │ [2] │[3] │[4] ││[5] │[6] │[7] │[8] │[9] │[10]│[11]│ [12] │
  ├─────┼─────┼─────┼────┼────┤├────┼────┼────┼────┼────┼────┼────┼──────┤
  │0xe6 │0x96 │0x87 │0xe5│0xad││0x97│0xe5│0x8c│0x96│0xe3│0x81│0x91│ 0x00 │
  ├─────┴─────┴─────┼────┴────┴┴────┼────┴────┴────┼────┴────┴────┼──────┤
  │     U+6587      │    U+5b57     │    U+5316    │    U+3051    │U+0000│
  ├─────────────────┼───────────────┼──────────────┼──────────────┼──────┤
  │       文        │      字       │      化      │      け      │ NUL  │
  ├─────────────────┴───────────────┴──────────────┴──────────────┴──────┤
  │                  string literal (type: ‘char[13]’)                   │
  └──────────────────────────────────────────────────────────────────────┘
     │     │     │    │    │     │    │    │    │    │    │    │     │
     │     │     │    │    │     │    │    │    │    │    │    │     │
     v     v     v    v    v     v    v    v    v    v    v    v     v
  ┌─────┬────────────────┬────┐┌─────────────────────────────────────────┐
  │ [0] │      ...       │[4] ││                                         │
  ├─────┴────────────────┴────┤│            after valid range            │
  │  ‘buf’ (type: ‘char[5]’)  ││                                         │
  └───────────────────────────┘└─────────────────────────────────────────┘
  ├─────────────┬─────────────┤├────────────────────┬────────────────────┤
                │                                   │
       ╭────────┴────────╮              ╭───────────┴──────────╮
       │capacity: 5 bytes│              │⚠️  overflow of 8 bytes│
       ╰─────────────────╯              ╰──────────────────────╯

showing that the overflow occurs partway through the UTF-8 encoding of
the U+5b57 code point.

There are lots more examples in the test suite.

It doesn't show up in this email, but the above diagrams are colorized
to constrast the valid and invalid access ranges.

gcc/ChangeLog:
	PR analyzer/106626
	* Makefile.in (ANALYZER_OBJS): Add analyzer/access-diagram.o.
	* doc/invoke.texi (Wanalyzer-out-of-bounds): Add description of
	text art.
	(fanalyzer-debug-text-art): New.

gcc/analyzer/ChangeLog:
	PR analyzer/106626
	* access-diagram.cc: New file.
	* access-diagram.h: New file.
	* analyzer.h (class region_offset): Add default ctor.
	(region_offset::make_byte_offset): New decl.
	(region_offset::concrete_p): New.
	(region_offset::get_concrete_byte_offset): New.
	(region_offset::calc_symbolic_bit_offset): New decl.
	(region_offset::calc_symbolic_byte_offset): New decl.
	(region_offset::dump_to_pp): New decl.
	(region_offset::dump): New decl.
	(operator<, operator<=, operator>, operator>=): New decls for
	region_offset.
	* analyzer.opt
	(-param=analyzer-text-art-string-ellipsis-threshold=): New.
	(-param=analyzer-text-art-string-ellipsis-head-len=): New.
	(-param=analyzer-text-art-string-ellipsis-tail-len=): New.
	(-param=analyzer-text-art-ideal-canvas-width=): New.
	(fanalyzer-debug-text-art): New.
	* bounds-checking.cc: Include "intl.h", "diagnostic-diagram.h",
	and "analyzer/access-diagram.h".
	(class out_of_bounds::oob_region_creation_event_capacity): New.
	(out_of_bounds::out_of_bounds): Add "model" and "sval_hint"
	params.
	(out_of_bounds::mark_interesting_stuff): Use the base region.
	(out_of_bounds::add_region_creation_events): Use
	oob_region_creation_event_capacity.
	(out_of_bounds::get_dir): New pure vfunc.
	(out_of_bounds::maybe_show_notes): New.
	(out_of_bounds::maybe_show_diagram): New.
	(out_of_bounds::make_access_diagram): New.
	(out_of_bounds::m_model): New field.
	(out_of_bounds::m_sval_hint): New field.
	(out_of_bounds::m_region_creation_event_id): New field.
	(concrete_out_of_bounds::concrete_out_of_bounds): Update for new
	fields.
	(concrete_past_the_end::concrete_past_the_end): Likewise.
	(concrete_past_the_end::add_region_creation_events): Use
	oob_region_creation_event_capacity.
	(concrete_buffer_overflow::concrete_buffer_overflow): Update for
	new fields.
	(concrete_buffer_overflow::emit): Replace call to
	maybe_describe_array_bounds with maybe_show_notes.
	(concrete_buffer_overflow::get_dir): New.
	(concrete_buffer_over_read::concrete_buffer_over_read): Update for
	new fields.
	(concrete_buffer_over_read::emit): Replace call to
	maybe_describe_array_bounds with maybe_show_notes.
	(concrete_buffer_overflow::get_dir): New.
	(concrete_buffer_underwrite::concrete_buffer_underwrite): Update
	for new fields.
	(concrete_buffer_underwrite::emit): Replace call to
	maybe_describe_array_bounds with maybe_show_notes.
	(concrete_buffer_underwrite::get_dir): New.
	(concrete_buffer_under_read::concrete_buffer_under_read): Update
	for new fields.
	(concrete_buffer_under_read::emit): Replace call to
	maybe_describe_array_bounds with maybe_show_notes.
	(concrete_buffer_under_read::get_dir): New.
	(symbolic_past_the_end::symbolic_past_the_end): Update for new
	fields.
	(symbolic_buffer_overflow::symbolic_buffer_overflow): Likewise.
	(symbolic_buffer_overflow::emit): Call maybe_show_notes.
	(symbolic_buffer_overflow::get_dir): New.
	(symbolic_buffer_over_read::symbolic_buffer_over_read): Update for
	new fields.
	(symbolic_buffer_over_read::emit): Call maybe_show_notes.
	(symbolic_buffer_over_read::get_dir): New.
	(region_model::check_symbolic_bounds): Add "sval_hint" param.  Pass
	it and sized_offset_reg to diagnostics.
	(region_model::check_region_bounds): Add "sval_hint" param, passing
	it to diagnostics.
	* diagnostic-manager.cc
	(diagnostic_manager::emit_saved_diagnostic): Pass logger to
	pending_diagnostic::emit.
	* engine.cc: Add logger param to pending_diagnostic::emit
	implementations.
	* infinite-recursion.cc: Likewise.
	* kf-analyzer.cc: Likewise.
	* kf.cc: Likewise.  Add nullptr for new param of
	check_region_for_write.
	* pending-diagnostic.h: Likewise in decl.
	* region-model-manager.cc
	(region_model_manager::get_or_create_int_cst): Convert param from
	poly_int64 to const poly_wide_int_ref &.
	(region_model_manager::maybe_fold_binop): Support type being NULL
	when checking for floating-point types.
	Check for (X + Y) - X => Y.  Be less strict about types when folding
	associative ops.  Check for (X + Y) * CST => (X * CST) + (Y * CST).
	* region-model-manager.h
	(region_model_manager::get_or_create_int_cst): Convert param from
	poly_int64 to const poly_wide_int_ref &.
	* region-model.cc: Add logger param to pending_diagnostic::emit
	implementations.
	(region_model::check_external_function_for_access_attr): Update
	for new param of check_region_for_write.
	(region_model::deref_rvalue): Use nullptr rather than NULL.
	(region_model::get_capacity): Handle RK_STRING.
	(region_model::check_region_access): Add "sval_hint" param; pass it to
	check_region_bounds.
	(region_model::check_region_for_write): Add "sval_hint" param;
	pass it to check_region_access.
	(region_model::check_region_for_read): Add NULL for new param to
	check_region_access.
	(region_model::set_value): Pass rhs_sval to
	check_region_for_write.
	(region_model::get_representative_path_var_1): Handle SK_CONSTANT
	in the check for infinite recursion.
	* region-model.h (region_model::check_region_for_write): Add
	"sval_hint" param.
	(region_model::check_region_access): Likewise.
	(region_model::check_symbolic_bounds): Likewise.
	(region_model::check_region_bounds): Likewise.
	* region.cc (region_offset::make_byte_offset): New.
	(region_offset::calc_symbolic_bit_offset): New.
	(region_offset::calc_symbolic_byte_offset): New.
	(region_offset::dump_to_pp): New.
	(region_offset::dump): New.
	(struct linear_op): New.
	(operator<, operator<=, operator>, operator>=): New, for
	region_offset.
	(region::get_next_offset): New.
	(region::get_relative_symbolic_offset): Use ptrdiff_type_node.
	(field_region::get_relative_symbolic_offset): Likewise.
	(element_region::get_relative_symbolic_offset): Likewise.
	(bit_range_region::get_relative_symbolic_offset): Likewise.
	* region.h (region::get_next_offset): New decl.
	* sm-fd.cc: Add logger param to pending_diagnostic::emit
	implementations.
	* sm-file.cc: Likewise.
	* sm-malloc.cc: Likewise.
	* sm-pattern-test.cc: Likewise.
	* sm-sensitive.cc: Likewise.
	* sm-signal.cc: Likewise.
	* sm-taint.cc: Likewise.
	* store.cc (bit_range::contains_p): Allow "out" to be null.
	* store.h (byte_range::get_start_bit_offset): New.
	(byte_range::get_next_bit_offset): New.
	* varargs.cc: Add logger param to pending_diagnostic::emit
	implementations.

gcc/testsuite/ChangeLog:
	PR analyzer/106626
	* gcc.dg/analyzer/data-model-1.c (test_16): Update for
	out-of-bounds working.
	* gcc.dg/analyzer/out-of-bounds-diagram-1-ascii.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-1-debug.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-1-emoji.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-1-json.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-1-sarif.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-1-unicode.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-10.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-11.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-12.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-13.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-14.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-15.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-2.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-3.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-4.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-5-ascii.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-5-unicode.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-6.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-7.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-8.c: New test.
	* gcc.dg/analyzer/out-of-bounds-diagram-9.c: New test.
	* gcc.dg/analyzer/pattern-test-2.c: Update expected results.
	* gcc.dg/plugin/analyzer_gil_plugin.c:  Add logger param to
	pending_diagnostic::emit implementations.
---
 gcc/Makefile.in                               |    1 +
 gcc/analyzer/access-diagram.cc                | 2405 +++++++++++++++++
 gcc/analyzer/access-diagram.h                 |  165 ++
 gcc/analyzer/analyzer.h                       |   30 +
 gcc/analyzer/analyzer.opt                     |   20 +
 gcc/analyzer/bounds-checking.cc               |  270 +-
 gcc/analyzer/diagnostic-manager.cc            |    2 +-
 gcc/analyzer/engine.cc                        |    4 +-
 gcc/analyzer/infinite-recursion.cc            |    2 +-
 gcc/analyzer/kf-analyzer.cc                   |    2 +-
 gcc/analyzer/kf.cc                            |    6 +-
 gcc/analyzer/pending-diagnostic.h             |    2 +-
 gcc/analyzer/region-model-manager.cc          |   32 +-
 gcc/analyzer/region-model-manager.h           |    2 +-
 gcc/analyzer/region-model.cc                  |   52 +-
 gcc/analyzer/region-model.h                   |    4 +
 gcc/analyzer/region.cc                        |  369 ++-
 gcc/analyzer/region.h                         |    1 +
 gcc/analyzer/sm-fd.cc                         |   14 +-
 gcc/analyzer/sm-file.cc                       |    4 +-
 gcc/analyzer/sm-malloc.cc                     |   20 +-
 gcc/analyzer/sm-pattern-test.cc               |    2 +-
 gcc/analyzer/sm-sensitive.cc                  |    3 +-
 gcc/analyzer/sm-signal.cc                     |    2 +-
 gcc/analyzer/sm-taint.cc                      |   16 +-
 gcc/analyzer/store.cc                         |   11 +-
 gcc/analyzer/store.h                          |    9 +
 gcc/analyzer/varargs.cc                       |    8 +-
 gcc/doc/invoke.texi                           |   15 +
 gcc/testsuite/gcc.dg/analyzer/data-model-1.c  |    4 +-
 .../analyzer/out-of-bounds-diagram-1-ascii.c  |   55 +
 .../analyzer/out-of-bounds-diagram-1-debug.c  |   40 +
 .../analyzer/out-of-bounds-diagram-1-emoji.c  |   55 +
 .../analyzer/out-of-bounds-diagram-1-json.c   |   13 +
 .../analyzer/out-of-bounds-diagram-1-sarif.c  |   24 +
 .../out-of-bounds-diagram-1-unicode.c         |   55 +
 .../analyzer/out-of-bounds-diagram-10.c       |   29 +
 .../analyzer/out-of-bounds-diagram-11.c       |   82 +
 .../analyzer/out-of-bounds-diagram-12.c       |   54 +
 .../analyzer/out-of-bounds-diagram-13.c       |   43 +
 .../analyzer/out-of-bounds-diagram-14.c       |  110 +
 .../analyzer/out-of-bounds-diagram-15.c       |   42 +
 .../gcc.dg/analyzer/out-of-bounds-diagram-2.c |   30 +
 .../gcc.dg/analyzer/out-of-bounds-diagram-3.c |   45 +
 .../gcc.dg/analyzer/out-of-bounds-diagram-4.c |   45 +
 .../analyzer/out-of-bounds-diagram-5-ascii.c  |   40 +
 .../out-of-bounds-diagram-5-unicode.c         |   42 +
 .../gcc.dg/analyzer/out-of-bounds-diagram-6.c |  125 +
 .../gcc.dg/analyzer/out-of-bounds-diagram-7.c |   36 +
 .../gcc.dg/analyzer/out-of-bounds-diagram-8.c |   34 +
 .../gcc.dg/analyzer/out-of-bounds-diagram-9.c |   42 +
 .../gcc.dg/analyzer/pattern-test-2.c          |    4 +-
 .../gcc.dg/plugin/analyzer_gil_plugin.c       |    6 +-
 53 files changed, 4382 insertions(+), 146 deletions(-)
 create mode 100644 gcc/analyzer/access-diagram.cc
 create mode 100644 gcc/analyzer/access-diagram.h
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-ascii.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-debug.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-emoji.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-json.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-sarif.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-unicode.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-10.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-11.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-12.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-13.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-14.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-15.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-4.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-5-ascii.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-5-unicode.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-6.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-7.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-8.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-9.c
  

Comments

Martin Jambor June 30, 2023, 2:40 p.m. UTC | #1
Hi David,

On Wed, May 31 2023, David Malcolm via Gcc-patches wrote:
> This patch extends -Wanalyzer-out-of-bounds so that, where possible, it
> will emit a text art diagram visualizing the spatial relationship between

[...]


>
> gcc/ChangeLog:
> 	PR analyzer/106626
> 	* Makefile.in (ANALYZER_OBJS): Add analyzer/access-diagram.o.
> 	* doc/invoke.texi (Wanalyzer-out-of-bounds): Add description of
> 	text art.
> 	(fanalyzer-debug-text-art): New.
>
> gcc/analyzer/ChangeLog:
> 	PR analyzer/106626
> 	* access-diagram.cc: New file.
> 	* access-diagram.h: New file.
> 	* analyzer.h (class region_offset): Add default ctor.
> 	(region_offset::make_byte_offset): New decl.
> 	(region_offset::concrete_p): New.
> 	(region_offset::get_concrete_byte_offset): New.
> 	(region_offset::calc_symbolic_bit_offset): New decl.
> 	(region_offset::calc_symbolic_byte_offset): New decl.
> 	(region_offset::dump_to_pp): New decl.
> 	(region_offset::dump): New decl.
> 	(operator<, operator<=, operator>, operator>=): New decls for
> 	region_offset.
> 	* analyzer.opt
> 	(-param=analyzer-text-art-string-ellipsis-threshold=): New.
> 	(-param=analyzer-text-art-string-ellipsis-head-len=): New.
> 	(-param=analyzer-text-art-string-ellipsis-tail-len=): New.
> 	(-param=analyzer-text-art-ideal-canvas-width=): New.

contrib/check-params-in-docs.py now complains that:

  $ ./gcc/xgcc -Bgcc --help=param &>/tmp/params.txt
  $ ../src/contrib/check-params-in-docs.py ../src/gcc/doc/invoke.texi /tmp/params.txt 
  Missing:
  @item analyzer-text-art-string-ellipsis-threshold
  The number of bytes at which to ellipsize string literals in

  @item analyzer-text-art-string-ellipsis-head-len
  The number of literal bytes to show at the head of a string

  @item analyzer-text-art-string-ellipsis-tail-len
  The number of literal bytes to show at the tail of a string

  @item analyzer-text-art-ideal-canvas-width
  The ideal width in characters of text art diagrams generated by the

Can you please add the respective documentation entries?

Thanks!

Martin
  

Patch

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index c1e7257ed24..1be7460b9d0 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1275,6 +1275,7 @@  C_COMMON_OBJS = c-family/c-common.o c-family/c-cppbuiltin.o c-family/c-dump.o \
 
 # Analyzer object files
 ANALYZER_OBJS = \
+	analyzer/access-diagram.o \
 	analyzer/analysis-plan.o \
 	analyzer/analyzer.o \
 	analyzer/analyzer-language.o \
diff --git a/gcc/analyzer/access-diagram.cc b/gcc/analyzer/access-diagram.cc
new file mode 100644
index 00000000000..968ff50a0b7
--- /dev/null
+++ b/gcc/analyzer/access-diagram.cc
@@ -0,0 +1,2405 @@ 
+/* Text art visualizations within -fanalyzer.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+
+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_ALGORITHM
+#define INCLUDE_MEMORY
+#define INCLUDE_MAP
+#define INCLUDE_SET
+#include "system.h"
+#include "coretypes.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "function.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "diagnostic.h"
+#include "intl.h"
+#include "make-unique.h"
+#include "tree-diagnostic.h" /* for default_tree_printer.  */
+#include "analyzer/analyzer.h"
+#include "analyzer/region-model.h"
+#include "analyzer/access-diagram.h"
+#include "text-art/ruler.h"
+#include "fold-const.h"
+
+#if ENABLE_ANALYZER
+
+/* Consider this code:
+     int32_t arr[10];
+     arr[10] = x;
+   where we've emitted a buffer overflow diagnostic like this:
+     out-of-bounds write from byte 40 till byte 43 but 'arr' ends at byte 40
+
+   We want to emit a diagram that visualizes:
+   - the spatial relationship between the valid region to access, versus
+   the region that was actually accessed: does it overlap, was it touching,
+   close, or far away?  Was it before or after in memory?  What are the
+   relative sizes involved?
+   - the direction of the access (read vs write)
+
+   The following code supports emitting diagrams similar to the following:
+
+   #                                        +--------------------------------+
+   #                                        |write from ‘x’ (type: ‘int32_t’)|
+   #                                        +--------------------------------+
+   #                                                        |
+   #                                                        |
+   #                                                        v
+   #  +---------+-----------+-----------+   +--------------------------------+
+   #  |   [0]   |    ...    |    [9]    |   |       after valid range        |
+   #  +---------+-----------+-----------+   |                                |
+   #  |   ‘arr’ (type: ‘int32_t[10]’)   |   |                                |
+   #  +---------------------------------+   +--------------------------------+
+   #  |~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~|   |~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~|
+   #                   |                                    |
+   #         +---------+--------+                 +---------+---------+
+   #         |capacity: 40 bytes|                 |overflow of 4 bytes|
+   #         +------------------+                 +-------------------+
+
+  where the diagram is laid out via table columns where each table column
+  represents either a range of bits/bytes, or is a spacing column (to highlight
+  the boundary between valid vs invalid accesses).  The table columns can be
+  seen via -fanalyzer-debug-text-art.  For example, here there are 5 table
+  columns ("tc0" through "tc4"):
+
+   #  +---------+-----------+-----------+---+--------------------------------+
+   #  |   tc0   |    tc1    |    tc2    |tc3|              tc4               |
+   #  +---------+-----------+-----------+---+--------------------------------+
+   #  |bytes 0-3|bytes 4-35 |bytes 36-39|   |          bytes 40-43           |
+   #  +---------+-----------+-----------+   +--------------------------------+
+   #
+   #                                        +--------------------------------+
+   #                                        |write from ‘x’ (type: ‘int32_t’)|
+   #                                        +--------------------------------+
+   #                                                        |
+   #                                                        |
+   #                                                        v
+   #  +---------+-----------+-----------+   +--------------------------------+
+   #  |   [0]   |    ...    |    [9]    |   |       after valid range        |
+   #  +---------+-----------+-----------+   |                                |
+   #  |   ‘arr’ (type: ‘int32_t[10]’)   |   |                                |
+   #  +---------------------------------+   +--------------------------------+
+   #  |~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~|   |~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~|
+   #                   |                                    |
+   #         +---------+--------+                 +---------+---------+
+   #         |capacity: 40 bytes|                 |overflow of 4 bytes|
+   #         +------------------+                 +-------------------+
+
+  The diagram is built up from the following:
+
+   #                                        +--------------------------------+
+   #                                        | ITEM FOR SVALUE/ACCESSED REGION|
+   #                                        +--------------------------------+
+   #                                                        |
+   #                                                        | DIRECTION WIDGET
+   #                                                        v
+   #  +---------------------------------+   +--------------------------------+
+   #  |   VALID REGION                  |   | INVALID ACCESS                 |
+   #  +---------------------------------+   +--------------------------------+
+   #
+   #  |                       VALID-VS-INVALID RULER                         |
+
+  i.e. a vbox_widget containing 4 child widgets laid out vertically:
+  - ALIGNED CHILD WIDGET: ITEM FOR SVALUE/ACCESSED REGION
+  - DIRECTION WIDGET
+  - ALIGNED CHILD WIDGET: VALID AND INVALID ACCESSES
+  - VALID-VS-INVALID RULER.
+
+  A more complicated example, given this overflow:
+     char buf[100];
+     strcpy (buf, LOREM_IPSUM);
+
+   01| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+   02| |[0]|[1]|[2]|[3]|[4]|[5]|   ...    |[440]|[441]|[442]|[443]|[444]|[445]|
+   03| +---+---+---+---+---+---+          +-----+-----+-----+-----+-----+-----+
+   04| |'L'|'o'|'r'|'e'|'m'|' '|          | 'o' | 'r' | 'u' | 'm' | '.' | NUL |
+   05| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+   06| |                  string literal (type: 'char[446]')                  |
+   07| +----------------------------------------------------------------------+
+   08|   |   |   |   |   |   |  |  |    |    |     |     |     |     |     |
+   09|   |   |   |   |   |   |  |  |    |    |     |     |     |     |     |
+   10|   v   v   v   v   v   v  v  v    v    v     v     v     v     v     v
+   11| +---+---------------------+----++--------------------------------------+
+   12| |[0]|         ...         |[99]||          after valid range           |
+   13| +---+---------------------+----+|                                      |
+   14| |  'buf' (type: 'char[100]')   ||                                      |
+   15| +------------------------------++--------------------------------------+
+   16| |~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~||~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~|
+   17|                |                                   |
+   18|      +---------+---------+              +----------+----------+
+   19|      |capacity: 100 bytes|              |overflow of 346 bytes|
+   20|      +-------------------+              +---------------------+
+
+ which is:
+
+   01| ALIGNED CHILD WIDGET (lines 01-07): (string_region_spatial_item)-+-----+
+   02| |[0]|[1]|[2]|[3]|[4]|[5]|   ...    |[440]|[441]|[442]|[443]|[444]|[445]|
+   03| +---+---+---+---+---+---+          +-----+-----+-----+-----+-----+-----+
+   04| |'L'|'o'|'r'|'e'|'m'|' '|          | 'o' | 'r' | 'u' | 'm' | '.' | NUL |
+   05| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+   06| |                  string literal (type: 'char[446]')                  |
+   07| +----------------------------------------------------------------------+
+   08| DIRECTION WIDGET (lines 08-10)   |    |     |     |     |     |     |
+   09|   |   |   |   |   |   |  |  |    |    |     |     |     |     |     |
+   10|   v   v   v   v   v   v  v  v    v    v     v     v     v     v     v
+   11| ALIGNED CHILD WIDGET (lines 11-15)-------------------------------------+
+   12| VALID REGION  ...         |[99]|| INVALID ACCESS                       |
+   13| +---+---------------------+----+|                                      |
+   14| |  'buf' (type: 'char[100]')   ||                                      |
+   15| +------------------------------++--------------------------------------+
+   16| VALID-VS-INVALID RULER (lines 16-20): ~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~|
+   17|                |                                   |
+   18|      +---------+---------+              +----------+----------+
+   19|      |capacity: 100 bytes|              |overflow of 346 bytes|
+   20|      +-------------------+              +---------------------+
+
+   We build the diagram in several phases:
+   - (1) we construct an access_diagram_impl widget.  Within the ctor, we have
+   these subphases:
+   -   (1.1) find all of the boundaries of interest
+   -   (1.2) use the boundaries to build a bit_table_map, associating bit ranges
+   with table columns (e.g. "byte 0 is column 0, bytes 1-98 are column 2" etc)
+   -   (1.3) create child widgets that share this table-based geometry
+   - (2) ask the widget for its size request
+   -   (2.1) column widths and row heights for the table are computed by
+   access_diagram_impl::calc_req_size
+   -   (2.2) child widgets request sizes based on these widths/heights
+   - (3) create a canvas of the appropriate size
+   - (4) paint the widget hierarchy to the canvas.  */
+
+
+using namespace text_art;
+
+namespace ana {
+
+static styled_string
+fmt_styled_string (style_manager &sm,
+		   const char *fmt, ...)
+    ATTRIBUTE_GCC_DIAG(2, 3);
+
+static styled_string
+fmt_styled_string (style_manager &sm,
+		   const char *fmt, ...)
+{
+  va_list ap;
+  va_start (ap, fmt);
+  styled_string result
+    = styled_string::from_fmt_va (sm, default_tree_printer, fmt, &ap);
+  va_end (ap);
+  return result;
+}
+
+class access_diagram_impl;
+class bit_to_table_map;
+
+static void
+pp_bit_size_t (pretty_printer *pp, bit_size_t num_bits)
+{
+  if (num_bits % BITS_PER_UNIT == 0)
+    {
+      byte_size_t num_bytes = num_bits / BITS_PER_UNIT;
+      if (num_bytes == 1)
+	pp_printf (pp, _("%wi byte"), num_bytes.to_uhwi ());
+      else
+	pp_printf (pp, _("%wi bytes"), num_bytes.to_uhwi ());
+    }
+  else
+    {
+      if (num_bits == 1)
+	pp_printf (pp, _("%wi bit"), num_bits.to_uhwi ());
+      else
+	pp_printf (pp, _("%wi bits"), num_bits.to_uhwi ());
+    }
+}
+
+static styled_string
+get_access_size_str (style_manager &sm,
+		     const access_operation &op,
+		     access_range accessed_range,
+		     tree type)
+{
+  bit_size_expr num_bits;
+  if (accessed_range.get_size (op.m_model, &num_bits))
+    {
+      if (type)
+	{
+	  styled_string s;
+
+	  pretty_printer pp;
+	  num_bits.print (&pp);
+
+	  if (op.m_dir == DIR_READ)
+	    return fmt_styled_string (sm,
+				      _("read of %qT (%s)"),
+				      type,
+				      pp_formatted_text (&pp));
+	  else
+	    return fmt_styled_string (sm,
+				      _("write of %qT (%s)"),
+				      type,
+				      pp_formatted_text (&pp));
+	}
+      if (op.m_dir == DIR_READ)
+	return num_bits.get_formatted_str (sm,
+					   _("read of %wi bit"),
+					   _("read of %wi bits"),
+					   _("read of %wi byte"),
+					   _("read of %wi bytes"),
+					   _("read of %qE bits"),
+					   _("read of %qE bytes"));
+      else
+	return num_bits.get_formatted_str (sm,
+					   _("write of %wi bit"),
+					   _("write of %wi bits"),
+					   _("write of %wi byte"),
+					   _("write of %wi bytes"),
+					   _("write of %qE bits"),
+					   _("write of %qE bytes"));
+    }
+
+  if (type)
+    {
+      if (op.m_dir == DIR_READ)
+	return fmt_styled_string (sm, _("read of %qT"), type);
+      else
+	return fmt_styled_string (sm, _("write of %qT"), type);
+    }
+
+  if (op.m_dir == DIR_READ)
+    return styled_string (sm, _("read"));
+  else
+    return styled_string (sm, _("write"));
+}
+
+/* Subroutine of clean_up_for_diagram.  */
+
+static tree
+strip_any_cast (tree expr)
+{
+  if (TREE_CODE (expr) == NOP_EXPR
+      || TREE_CODE (expr) == NON_LVALUE_EXPR)
+    expr = TREE_OPERAND (expr, 0);
+  return expr;
+}
+
+/* Subroutine of clean_up_for_diagram.  */
+
+static tree
+remove_ssa_names (tree expr)
+{
+  if (TREE_CODE (expr) == SSA_NAME
+      && SSA_NAME_VAR (expr))
+    return SSA_NAME_VAR (expr);
+  tree t = copy_node (expr);
+  for (int i = 0; i < TREE_OPERAND_LENGTH (expr); i++)
+    TREE_OPERAND (t, i) = remove_ssa_names (TREE_OPERAND (expr, i));
+  return t;
+}
+
+/* We want to be able to print tree expressions from the analyzer,
+   which is in the middle end.
+
+   We could use the front-end pretty_printer's formatting routine,
+   but:
+   (a) some have additional state in a pretty_printer subclass, so we'd
+   need to clone global_dc->printer
+   (b) the "aka" type information added by the C and C++ frontends are
+   too verbose when building a diagram, and there isn't a good way to ask
+   for a less verbose version of them.
+
+   Hence we use default_tree_printer.
+   However, we want to avoid printing SSA names, and instead print the
+   underlying var name.
+   Ideally there would be a better tree printer for use by middle end
+   warnings, but as workaround, this function clones a tree, replacing
+   SSA names with the var names.  */
+
+tree
+clean_up_for_diagram (tree expr)
+{
+  tree without_ssa_names = remove_ssa_names (expr);
+  return strip_any_cast (without_ssa_names);
+}
+
+/* struct bit_size_expr.  */
+
+text_art::styled_string
+bit_size_expr::get_formatted_str (text_art::style_manager &sm,
+				  const char *concrete_single_bit_fmt,
+				  const char *concrete_plural_bits_fmt,
+				  const char *concrete_single_byte_fmt,
+				  const char *concrete_plural_bytes_fmt,
+				  const char *symbolic_bits_fmt,
+				  const char *symbolic_bytes_fmt) const
+{
+  if (TREE_CODE (m_num_bits) == INTEGER_CST)
+    {
+      bit_size_t concrete_num_bits = wi::to_offset (m_num_bits);
+      if (concrete_num_bits % BITS_PER_UNIT == 0)
+	{
+	  byte_size_t concrete_num_bytes = concrete_num_bits / BITS_PER_UNIT;
+	  if (concrete_num_bytes == 1)
+	    return fmt_styled_string (sm, concrete_single_byte_fmt,
+				      concrete_num_bytes.to_uhwi ());
+	  else
+	    return fmt_styled_string (sm, concrete_plural_bytes_fmt,
+				      concrete_num_bytes.to_uhwi ());
+	}
+      else
+	{
+	  if (concrete_num_bits == 1)
+	    return fmt_styled_string (sm, concrete_single_bit_fmt,
+				      concrete_num_bits.to_uhwi ());
+	  else
+	    return fmt_styled_string (sm, concrete_plural_bits_fmt,
+				      concrete_num_bits.to_uhwi ());
+	}
+    }
+  else
+    {
+      if (tree bytes_expr = maybe_get_as_bytes ())
+	return fmt_styled_string (sm,
+				  symbolic_bytes_fmt,
+				  clean_up_for_diagram (bytes_expr));
+      return fmt_styled_string (sm,
+				symbolic_bits_fmt,
+				clean_up_for_diagram (m_num_bits));
+    }
+}
+
+void
+bit_size_expr::print (pretty_printer *pp) const
+{
+  if (TREE_CODE (m_num_bits) == INTEGER_CST)
+    {
+      bit_size_t concrete_num_bits = wi::to_offset (m_num_bits);
+      pp_bit_size_t (pp, concrete_num_bits);
+    }
+  else
+    {
+      if (tree bytes_expr = maybe_get_as_bytes ())
+	pp_printf (pp, _("%qE bytes"), bytes_expr);
+      else
+	pp_printf (pp, _("%qE bits"), m_num_bits);
+    }
+}
+
+tree
+bit_size_expr::maybe_get_as_bytes () const
+{
+  switch (TREE_CODE (m_num_bits))
+    {
+    default:
+      break;
+    case INTEGER_CST:
+      {
+	const bit_size_t num_bits = wi::to_offset (m_num_bits);
+	if (num_bits % BITS_PER_UNIT != 0)
+	  return NULL_TREE;
+	const bit_size_t num_bytes = num_bits / BITS_PER_UNIT;
+	return wide_int_to_tree (size_type_node, num_bytes);
+      }
+      break;
+    case PLUS_EXPR:
+    case MINUS_EXPR:
+      {
+	bit_size_expr op0
+	  = bit_size_expr (TREE_OPERAND (m_num_bits, 0));
+	tree op0_as_bytes = op0.maybe_get_as_bytes ();
+	if (!op0_as_bytes)
+	  return NULL_TREE;
+	bit_size_expr op1
+	  = bit_size_expr (TREE_OPERAND (m_num_bits, 1));
+	tree op1_as_bytes = op1.maybe_get_as_bytes ();
+	if (!op1_as_bytes)
+	  return NULL_TREE;
+	return fold_build2 (TREE_CODE (m_num_bits), size_type_node,
+			    op0_as_bytes, op1_as_bytes);
+      }
+      break;
+    case MULT_EXPR:
+      {
+	bit_size_expr op1
+	  = bit_size_expr (TREE_OPERAND (m_num_bits, 1));
+	if (tree op1_as_bytes = op1.maybe_get_as_bytes ())
+	  return fold_build2 (MULT_EXPR, size_type_node,
+			      TREE_OPERAND (m_num_bits, 0),
+			      op1_as_bytes);
+      }
+      break;
+    }
+  return NULL_TREE;
+}
+
+/* struct access_range.  */
+
+access_range::access_range (const region *base_region, const bit_range &bits)
+: m_start (region_offset::make_concrete (base_region,
+					 bits.get_start_bit_offset ())),
+  m_next (region_offset::make_concrete (base_region,
+					bits.get_next_bit_offset ()))
+{
+}
+
+access_range::access_range (const region *base_region, const byte_range &bytes)
+: m_start (region_offset::make_concrete (base_region,
+					 bytes.get_start_bit_offset ())),
+  m_next (region_offset::make_concrete (base_region,
+					bytes.get_next_bit_offset ()))
+{
+}
+
+access_range::access_range (const region &reg, region_model_manager *mgr)
+: m_start (reg.get_offset (mgr)),
+  m_next (reg.get_next_offset (mgr))
+{
+}
+
+bool
+access_range::get_size (const region_model &model, bit_size_expr *out) const
+{
+  tree start_expr = m_start.calc_symbolic_bit_offset (model);
+  if (!start_expr)
+    return false;
+  tree next_expr = m_next.calc_symbolic_bit_offset (model);
+  if (!next_expr)
+    return false;
+  *out = bit_size_expr (fold_build2 (MINUS_EXPR, size_type_node,
+					 next_expr, start_expr));
+  return true;
+}
+
+bool
+access_range::contains_p (const access_range &other) const
+{
+  return (m_start <= other.m_start
+	  && other.m_next <= m_next);
+}
+
+bool
+access_range::empty_p () const
+{
+  bit_range concrete_bits (0, 0);
+  if (!as_concrete_bit_range (&concrete_bits))
+    return false;
+  return concrete_bits.empty_p ();
+}
+
+void
+access_range::dump_to_pp (pretty_printer *pp, bool simple) const
+{
+  if (m_start.concrete_p () && m_next.concrete_p ())
+    {
+      bit_range bits (m_start.get_bit_offset (),
+		      m_next.get_bit_offset () - m_start.get_bit_offset ());
+      bits.dump_to_pp (pp);
+      return;
+    }
+  pp_character (pp, '[');
+  m_start.dump_to_pp (pp, simple);
+  pp_string (pp, " to ");
+  m_next.dump_to_pp (pp, simple);
+  pp_character (pp, ')');
+}
+
+DEBUG_FUNCTION void
+access_range::dump (bool simple) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = stderr;
+  dump_to_pp (&pp, simple);
+  pp_newline (&pp);
+  pp_flush (&pp);
+}
+
+void
+access_range::log (const char *title, logger &logger) const
+{
+  logger.start_log_line ();
+  logger.log_partial ("%s: ", title);
+  dump_to_pp (logger.get_printer (), true);
+  logger.end_log_line ();
+}
+
+/* struct access_operation.  */
+
+access_range
+access_operation::get_valid_bits () const
+{
+  const svalue *capacity_in_bytes_sval = m_model.get_capacity (m_base_region);
+  return access_range
+    (region_offset::make_concrete (m_base_region, 0),
+     region_offset::make_byte_offset (m_base_region, capacity_in_bytes_sval));
+}
+
+access_range
+access_operation::get_actual_bits () const
+{
+  return access_range (m_reg, get_manager ());
+}
+
+/* If there are any bits accessed invalidly before the valid range,
+   return true and write their range to *OUT.
+   Return false if there aren't, or if there's a problem
+   (e.g. symbolic ranges.  */
+
+bool
+access_operation::maybe_get_invalid_before_bits (access_range *out) const
+{
+  access_range valid_bits (get_valid_bits ());
+  access_range actual_bits (get_actual_bits ());
+
+  if (actual_bits.m_start >= valid_bits.m_start)
+    {
+      /* No part of accessed range is before the valid range.  */
+      return false;
+    }
+  else if (actual_bits.m_next > valid_bits.m_start)
+    {
+      /* Get part of accessed range that's before the valid range.  */
+      *out = access_range (actual_bits.m_start, valid_bits.m_start);
+      return true;
+    }
+  else
+    {
+      /* Accessed range is fully before valid range.  */
+      *out = actual_bits;
+      return true;
+    }
+}
+
+/* If there are any bits accessed invalidly after the valid range,
+   return true and write their range to *OUT.
+   Return false if there aren't, or if there's a problem.  */
+
+bool
+access_operation::maybe_get_invalid_after_bits (access_range *out) const
+{
+  access_range valid_bits (get_valid_bits ());
+  access_range actual_bits (get_actual_bits ());
+
+  if (actual_bits.m_next <= valid_bits.m_next)
+    {
+      /* No part of accessed range is after the valid range.  */
+      return false;
+    }
+  else if (actual_bits.m_start < valid_bits.m_next)
+    {
+      /* Get part of accessed range that's after the valid range.  */
+      *out = access_range (valid_bits.m_next, actual_bits.m_next);
+      return true;
+    }
+  else
+    {
+      /* Accessed range is fully after valid range.  */
+      *out = actual_bits;
+      return true;
+    }
+}
+
+/* A class for capturing all of the region offsets of interest (both concrete
+   and symbolic), to help align everything in the diagram.
+   Boundaries can be soft or hard; hard boundaries are emphasized visually
+   (e.g. the boundary between valid vs invalid accesses).
+
+   Offsets in the boundaries are all expressed relative to the base
+   region of the access_operation.  */
+
+class boundaries
+{
+public:
+  enum class kind { HARD, SOFT};
+
+  boundaries (const region &base_reg)
+  : m_base_reg (base_reg)
+  {
+  }
+
+  void add (region_offset offset, enum kind k)
+  {
+    m_all_offsets.insert (offset);
+    if (k == kind::HARD)
+      m_hard_offsets.insert (offset);
+  }
+
+  void add (const access_range &range, enum kind kind)
+  {
+    add (range.m_start, kind);
+    add (range.m_next, kind);
+  }
+
+  void add (const region &reg, region_model_manager *mgr, enum kind kind)
+  {
+    add (access_range (reg.get_offset (mgr),
+		       reg.get_next_offset (mgr)),
+	 kind);
+  }
+
+  void add (const byte_range bytes, enum kind kind)
+  {
+    add (access_range (&m_base_reg, bytes), kind);
+  }
+
+  void add_all_bytes_in_range (const byte_range &bytes)
+  {
+    for (byte_offset_t byte_idx = bytes.get_start_byte_offset ();
+	 byte_idx <= bytes.get_next_byte_offset ();
+	 byte_idx = byte_idx + 1)
+      add (region_offset::make_concrete (&m_base_reg, byte_idx * 8),
+	   kind::SOFT);
+  }
+
+  void add_all_bytes_in_range (const access_range &range)
+  {
+    byte_range bytes (0, 0);
+    bool valid = range.as_concrete_byte_range (&bytes);
+    gcc_assert (valid);
+    add_all_bytes_in_range (bytes);
+  }
+
+  void log (logger &logger) const
+  {
+    logger.log ("boundaries:");
+    logger.inc_indent ();
+    for (auto offset : m_all_offsets)
+      {
+	enum kind k = get_kind (offset);
+	logger.start_log_line ();
+	logger.log_partial ("%s: ", (k == kind::HARD) ? "HARD" : "soft");
+	offset.dump_to_pp (logger.get_printer (), true);
+	logger.end_log_line ();
+      }
+    logger.dec_indent ();
+  }
+
+  enum kind get_kind (region_offset offset) const
+  {
+    gcc_assert (m_all_offsets.find (offset) != m_all_offsets.end ());
+    if (m_hard_offsets.find (offset) != m_hard_offsets.end ())
+      return kind::HARD;
+    else
+      return kind::SOFT;
+  }
+
+  std::set<region_offset>::const_iterator begin () const
+  {
+    return m_all_offsets.begin ();
+  }
+  std::set<region_offset>::const_iterator end () const
+  {
+    return m_all_offsets.end ();
+  }
+  std::set<region_offset>::size_type size () const
+  {
+    return m_all_offsets.size ();
+  }
+
+private:
+  const region &m_base_reg;
+  std::set<region_offset> m_all_offsets;
+  std::set<region_offset> m_hard_offsets;
+};
+
+/* A widget that wraps a table but offloads column-width calculation
+   to a shared object, so that we can vertically line up multiple tables
+   and have them all align their columns.
+
+   For example, in:
+
+   01| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+   02| |[0]|[1]|[2]|[3]|[4]|[5]|   ...    |[440]|[441]|[442]|[443]|[444]|[445]|
+   03| +---+---+---+---+---+---+          +-----+-----+-----+-----+-----+-----+
+   04| |'L'|'o'|'r'|'e'|'m'|' '|          | 'o' | 'r' | 'u' | 'm' | '.' | NUL |
+   05| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+   06| |                  string literal (type: 'char[446]')                  |
+   07| +----------------------------------------------------------------------+
+   08|   |   |   |   |   |   |  |  |    |    |     |     |     |     |     |
+   09|   |   |   |   |   |   |  |  |    |    |     |     |     |     |     |
+   10|   v   v   v   v   v   v  v  v    v    v     v     v     v     v     v
+   11|+---+---------------------+----++--------------------------------------+
+   12||[0]|         ...         |[99]||          after valid range           |
+   13|+---+---------------------+----+|                                      |
+   14||  'buf' (type: 'char[100]')   ||                                      |
+   15|+------------------------------++--------------------------------------+
+   16||~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~||~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~|
+   17|               |                                   |
+   18|     +---------+---------+              +----------+----------+
+   19|     |capacity: 100 bytes|              |overflow of 346 bytes|
+   20|     +-------------------+              +---------------------+
+
+   rows 01-07 and rows 11-15 are x_aligned_table_widget instances.  */
+
+class x_aligned_table_widget : public leaf_widget
+{
+public:
+  x_aligned_table_widget (table t,
+			  const theme &theme,
+			  table_dimension_sizes &col_widths)
+  : m_table (std::move (t)),
+    m_theme (theme),
+    m_col_widths (col_widths),
+    m_row_heights (t.get_size ().h),
+    m_cell_sizes (m_col_widths, m_row_heights),
+    m_tg (m_table, m_cell_sizes)
+  {
+  }
+
+  const char *get_desc () const override
+  {
+    return "x_aligned_table_widget";
+  }
+
+  canvas::size_t calc_req_size () final override
+  {
+    /* We don't compute the size requirements;
+       the parent should have done this.  */
+    return m_tg.get_canvas_size ();
+  }
+
+  void paint_to_canvas (canvas &canvas) final override
+  {
+    m_table.paint_to_canvas (canvas,
+			     get_top_left (),
+			     m_tg,
+			     m_theme);
+  }
+
+  const table &get_table () const { return m_table; }
+  table_cell_sizes &get_cell_sizes () { return m_cell_sizes; }
+  void recalc_coords ()
+  {
+    m_tg.recalc_coords ();
+  }
+
+private:
+  table m_table;
+  const theme &m_theme;
+  table_dimension_sizes &m_col_widths; // Reference to shared column widths
+  table_dimension_sizes m_row_heights; // Unique row heights
+  table_cell_sizes m_cell_sizes;
+  table_geometry m_tg;
+};
+
+/* A widget for printing arrows between the accessed region
+   and the svalue, showing the direction of the access.
+
+   For example, in:
+
+   01| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+   02| |[0]|[1]|[2]|[3]|[4]|[5]|   ...    |[440]|[441]|[442]|[443]|[444]|[445]|
+   03| +---+---+---+---+---+---+          +-----+-----+-----+-----+-----+-----+
+   04| |'L'|'o'|'r'|'e'|'m'|' '|          | 'o' | 'r' | 'u' | 'm' | '.' | NUL |
+   05| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+   06| |                  string literal (type: 'char[446]')                  |
+   07| +----------------------------------------------------------------------+
+   08|   |   |   |   |   |   |  |  |    |    |     |     |     |     |     |
+   09|   |   |   |   |   |   |  |  |    |    |     |     |     |     |     |
+   10|   v   v   v   v   v   v  v  v    v    v     v     v     v     v     v
+   11|+---+---------------------+----++--------------------------------------+
+   12||[0]|         ...         |[99]||          after valid range           |
+   13|+---+---------------------+----+|                                      |
+   14||  'buf' (type: 'char[100]')   ||                                      |
+   15|+------------------------------++--------------------------------------+
+   16||~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~||~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~|
+   17|               |                                   |
+   18|     +---------+---------+              +----------+----------+
+   19|     |capacity: 100 bytes|              |overflow of 346 bytes|
+   20|     +-------------------+              +---------------------+
+
+   rows 8-10 are the direction widget.  */
+
+class direction_widget : public leaf_widget
+{
+public:
+  direction_widget (const access_diagram_impl &dia_impl,
+		    const bit_to_table_map &btm)
+  : leaf_widget (),
+    m_dia_impl (dia_impl),
+    m_btm (btm)
+  {
+  }
+  const char *get_desc () const override
+  {
+    return "direction_widget";
+  }
+  canvas::size_t calc_req_size () final override
+  {
+    /* Get our width from our siblings.  */
+    return canvas::size_t (0, 3);
+  }
+  void paint_to_canvas (canvas &canvas) final override;
+
+private:
+  const access_diagram_impl &m_dia_impl;
+  const bit_to_table_map &m_btm;
+};
+
+/* A widget for adding an x_ruler to a diagram based on table columns,
+   offloading column-width calculation to shared objects, so that the ruler
+   lines up with other tables in the diagram.
+
+   For example, in:
+
+   01| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+   02| |[0]|[1]|[2]|[3]|[4]|[5]|   ...    |[440]|[441]|[442]|[443]|[444]|[445]|
+   03| +---+---+---+---+---+---+          +-----+-----+-----+-----+-----+-----+
+   04| |'L'|'o'|'r'|'e'|'m'|' '|          | 'o' | 'r' | 'u' | 'm' | '.' | NUL |
+   05| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+   06| |                  string literal (type: 'char[446]')                  |
+   07| +----------------------------------------------------------------------+
+   08|   |   |   |   |   |   |  |  |    |    |     |     |     |     |     |
+   09|   |   |   |   |   |   |  |  |    |    |     |     |     |     |     |
+   10|   v   v   v   v   v   v  v  v    v    v     v     v     v     v     v
+   11|+---+---------------------+----++--------------------------------------+
+   12||[0]|         ...         |[99]||          after valid range           |
+   13|+---+---------------------+----+|                                      |
+   14||  'buf' (type: 'char[100]')   ||                                      |
+   15|+------------------------------++--------------------------------------+
+   16||~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~||~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~|
+   17|               |                                   |
+   18|     +---------+---------+              +----------+----------+
+   19|     |capacity: 100 bytes|              |overflow of 346 bytes|
+   20|     +-------------------+              +---------------------+
+
+   rows 16-20 are the x_aligned_x_ruler_widget.  */
+
+class x_aligned_x_ruler_widget : public leaf_widget
+{
+public:
+  x_aligned_x_ruler_widget (const access_diagram_impl &dia_impl,
+			    const theme &theme,
+			    table_dimension_sizes &col_widths)
+  : m_dia_impl (dia_impl),
+    m_theme (theme),
+    m_col_widths (col_widths)
+  {
+  }
+
+  const char *get_desc () const override
+  {
+    return "x_aligned_ruler_widget";
+  }
+
+  void add_range (const table::range_t &x_range,
+		  styled_string text,
+		  style::id_t style_id)
+  {
+    m_labels.push_back (label (x_range, std::move (text), style_id));
+  }
+
+  canvas::size_t calc_req_size () final override
+  {
+    x_ruler r (make_x_ruler ());
+    return r.get_size ();
+  }
+
+  void paint_to_canvas (canvas &canvas) final override
+  {
+    x_ruler r (make_x_ruler ());
+    r.paint_to_canvas (canvas,
+		       get_top_left (),
+		       m_theme);
+  }
+
+private:
+  struct label
+  {
+    label (const table::range_t &table_x_range,
+	   styled_string text,
+	   style::id_t style_id)
+    : m_table_x_range (table_x_range),
+      m_text (std::move (text)),
+      m_style_id (style_id)
+    {
+    }
+    table::range_t m_table_x_range;
+    styled_string m_text;
+    style::id_t m_style_id;
+  };
+
+  x_ruler make_x_ruler () const;
+
+  const access_diagram_impl &m_dia_impl;
+  const theme &m_theme;
+  table_dimension_sizes &m_col_widths;
+  std::vector<label> m_labels;
+};
+
+/* A two-way mapping between access_ranges and table columns, for use by
+   spatial_item subclasses for creating tables.
+   For example when visualizing a bogus access of 'int arr[10];'
+   at 'arr[10]', we might have:
+   - table column 0 is "bytes 0-3" (for arr[0])
+   - table column 1 is "bytes 4-35" (for arr[1] through arr[8])
+   - table column 2 is "bytes 36-39 (for arr[9])
+   - table column 3 is blank to emphasize a hard boundary between
+     valid/invalid accesses.
+   - table column 4 is "bytes 40-44" (for arr[10])
+
+   We store this as a pair of maps from region_offset to table x; in
+   the abvove example:
+
+     region offset          table_x  prev_table_x
+     bit 0 (aka byte 0)     0        (none)
+     bit 32 (aka byte 4)    1        0
+     bit 288 (aka byte 36)  2        1
+     bit 320 (aka byte 40)  4        2
+     bit 352 (aka byte 44)  (none)   (none)
+
+     so that e.g given the half-open byte range [0, 40)
+     we can determine the closed range of table x [0, 2].  */
+
+class bit_to_table_map
+{
+public:
+  /* Populate m_table_x_for_bit and m_bit_for_table_x.  */
+  void populate (const boundaries &boundaries, logger *logger)
+  {
+    LOG_SCOPE (logger);
+
+    int table_x = 0;
+    std::vector <region_offset> vec_boundaries (boundaries.begin (),
+						boundaries.end ());
+
+    /* Sort into an order that makes sense.  */
+    std::sort (vec_boundaries.begin (),
+	       vec_boundaries.end ());
+
+    if (logger)
+      {
+	logger->log ("vec_boundaries");
+	logger->inc_indent ();
+	for (unsigned idx = 0; idx < vec_boundaries.size (); idx++)
+	  {
+	    logger->start_log_line ();
+	    logger->log_partial ("idx: %i: ", idx);
+	    vec_boundaries[idx].dump_to_pp (logger->get_printer (), true);
+	    logger->end_log_line ();
+	  }
+	logger->dec_indent ();
+      }
+
+    for (size_t idx = 0; idx < vec_boundaries.size (); idx++)
+      {
+	const region_offset &offset = vec_boundaries[idx];
+	if (idx > 0 && (idx + 1) < vec_boundaries.size ())
+	  {
+	    if (boundaries.get_kind (offset) == boundaries::kind::HARD)
+	      table_x += 1;
+	  }
+	m_table_x_for_offset[offset] = table_x;
+	if ((idx + 1) < vec_boundaries.size ())
+	  {
+	    const region_offset &next_offset = vec_boundaries[idx + 1];
+	    m_table_x_for_prev_offset[next_offset] = table_x;
+	    m_range_for_table_x[table_x] = access_range (offset, next_offset);
+	  }
+	table_x += 1;
+      }
+    m_num_columns = table_x - 1;
+
+    if (logger)
+      log (*logger);
+  }
+
+  unsigned get_num_columns () const
+  {
+    return m_num_columns;
+  }
+
+  table::range_t get_table_x_for_range (const access_range &range) const
+  {
+    return table::range_t (get_table_x_for_offset (range.m_start),
+			   get_table_x_for_prev_offset (range.m_next) + 1);
+  }
+
+  table::rect_t get_table_rect (const access_range &range,
+				const int table_y, const int table_h) const
+  {
+    const table::range_t x_range (get_table_x_for_range (range));
+    return table::rect_t (table::coord_t (x_range.start, table_y),
+			  table::size_t (x_range.get_size (), table_h));
+  }
+
+  table::rect_t get_table_rect (const region *base_reg,
+				const bit_range &bits,
+				const int table_y, const int table_h) const
+  {
+    const access_range range (base_reg, bits);
+    return get_table_rect (range, table_y, table_h);
+  }
+
+  table::rect_t get_table_rect (const region *base_reg,
+				const byte_range &bytes,
+				const int table_y, const int table_h) const
+  {
+    return get_table_rect (base_reg, bytes.as_bit_range (), table_y, table_h);
+  }
+
+  bool maybe_get_access_range_for_table_x (int table_x,
+					   access_range *out) const
+  {
+    auto slot = m_range_for_table_x.find (table_x);
+    if (slot == m_range_for_table_x.end ())
+      return false;
+    *out = slot->second;
+    return true;
+  }
+
+  void log (logger &logger) const
+  {
+    logger.log ("table columns");
+    logger.inc_indent ();
+    for (unsigned table_x = 0; table_x < get_num_columns (); table_x++)
+      {
+	logger.start_log_line ();
+	logger.log_partial ("table_x: %i", table_x);
+	access_range range_for_column (NULL, bit_range (0, 0));
+	if (maybe_get_access_range_for_table_x (table_x, &range_for_column))
+	  {
+	    logger.log_partial (": range: ");
+	    range_for_column.dump_to_pp (logger.get_printer (), true);
+	  }
+	logger.end_log_line ();
+      }
+    logger.dec_indent ();
+  }
+
+private:
+  int get_table_x_for_offset (region_offset offset) const
+  {
+    auto slot = m_table_x_for_offset.find (offset);
+
+    /* If this fails, then we probably failed to fully populate m_boundaries
+       in find_boundaries.  */
+    gcc_assert (slot != m_table_x_for_offset.end ());
+
+    return slot->second;
+  }
+
+  int get_table_x_for_prev_offset (region_offset offset) const
+  {
+    auto slot = m_table_x_for_prev_offset.find (offset);
+
+    /* If this fails, then we probably failed to fully populate m_boundaries
+       in find_boundaries.  */
+    gcc_assert (slot != m_table_x_for_prev_offset.end ());
+
+    return slot->second;
+  }
+
+  std::map<region_offset, int> m_table_x_for_offset;
+  std::map<region_offset, int> m_table_x_for_prev_offset;
+  std::map<int, access_range> m_range_for_table_x;
+  unsigned m_num_columns;
+};
+
+/* Base class for something in the diagram that participates
+   in two steps of diagram creation:
+   (a) populating a boundaries instance with the boundaries of interest
+   (b) creating a table instance for itself.
+
+   Offsets in the boundaries are all expressed relative to the base
+   region of the access_operation.  */
+
+class spatial_item
+{
+public:
+  virtual void add_boundaries (boundaries &out, logger *) const = 0;
+
+  virtual table make_table (const bit_to_table_map &btm,
+			    style_manager &sm) const = 0;
+};
+
+/* Subclass of spatial_item for visualizing the region of memory
+   that's valid to access relative to the base region of region accessed in
+   the operation.  */
+
+class valid_region_spatial_item : public spatial_item
+{
+public:
+  valid_region_spatial_item (const access_operation &op,
+			     diagnostic_event_id_t region_creation_event_id)
+  : m_op (op),
+    m_region_creation_event_id (region_creation_event_id)
+  {}
+
+  void add_boundaries (boundaries &out, logger *logger) const final override
+  {
+    LOG_SCOPE (logger);
+    access_range valid_bits = m_op.get_valid_bits ();
+    if (logger)
+      {
+	logger->start_log_line ();
+	logger->log_partial ("valid bits: ");
+	valid_bits.dump_to_pp (logger->get_printer (), true);
+	logger->end_log_line ();
+      }
+    out.add (valid_bits, boundaries::kind::HARD);
+
+    /* Support for showing first and final element in array types.  */
+    if (tree base_type = m_op.m_base_region->get_type ())
+      if (TREE_CODE (base_type) == ARRAY_TYPE)
+	{
+	  if (logger)
+	    logger->log ("showing first and final element in array type");
+	  region_model_manager *mgr = m_op.m_model.get_manager ();
+	  tree domain = TYPE_DOMAIN (base_type);
+	  if (TYPE_MIN_VALUE (domain) && TYPE_MAX_VALUE (domain))
+	    {
+	      const svalue *min_idx_sval
+		= mgr->get_or_create_constant_svalue (TYPE_MIN_VALUE (domain));
+	      const svalue *max_idx_sval
+		= mgr->get_or_create_constant_svalue (TYPE_MAX_VALUE (domain));
+	      const region *min_element =
+		mgr->get_element_region (m_op.m_base_region,
+					 TREE_TYPE (base_type),
+					 min_idx_sval);
+	      out.add (*min_element, mgr, boundaries::kind::SOFT);
+	      const region *max_element =
+		mgr->get_element_region (m_op.m_base_region,
+					 TREE_TYPE (base_type),
+					 max_idx_sval);
+	      out.add (*max_element, mgr, boundaries::kind::SOFT);
+	    }
+	}
+  }
+
+  /* Subroutine of make_table when base region has ARRAY_TYPE.  */
+  void add_array_elements_to_table (table &t,
+				    const bit_to_table_map &btm,
+				    style_manager &sm) const
+  {
+    tree base_type = m_op.m_base_region->get_type ();
+    gcc_assert (TREE_CODE (base_type) == ARRAY_TYPE);
+
+    tree domain = TYPE_DOMAIN (base_type);
+    if (!(TYPE_MIN_VALUE (domain) && TYPE_MAX_VALUE (domain)))
+      return;
+
+    region_model_manager * const mgr = m_op.get_manager ();
+    const int table_y = 0;
+    const int table_h = 1;
+    const table::range_t table_y_range (table_y, table_y + table_h);
+
+    t.add_row ();
+    const svalue *min_idx_sval
+      = mgr->get_or_create_constant_svalue (TYPE_MIN_VALUE (domain));
+    const region *min_element = mgr->get_element_region (m_op.m_base_region,
+							 TREE_TYPE (base_type),
+							 min_idx_sval);
+    const access_range min_element_range (*min_element, mgr);
+    const table::range_t min_element_x_range
+      = btm.get_table_x_for_range (min_element_range);
+
+    t.set_cell_span (table::rect_t (min_element_x_range,
+				    table_y_range),
+		     fmt_styled_string (sm, "[%E]",
+					TYPE_MIN_VALUE (domain)));
+
+    const svalue *max_idx_sval
+      = mgr->get_or_create_constant_svalue (TYPE_MAX_VALUE (domain));
+    const region *max_element = mgr->get_element_region (m_op.m_base_region,
+							 TREE_TYPE (base_type),
+							 max_idx_sval);
+    if (min_element == max_element)
+      return; // 1-element array
+
+    const access_range max_element_range (*max_element, mgr);
+    const table::range_t max_element_x_range
+      = btm.get_table_x_for_range (max_element_range);
+    t.set_cell_span (table::rect_t (max_element_x_range,
+				    table_y_range),
+		     fmt_styled_string (sm, "[%E]",
+					TYPE_MAX_VALUE (domain)));
+
+    const table::range_t other_elements_x_range (min_element_x_range.next,
+						 max_element_x_range.start);
+    if (other_elements_x_range.get_size () > 0)
+      t.set_cell_span (table::rect_t (other_elements_x_range, table_y_range),
+		       styled_string (sm, "..."));
+  }
+
+  table make_table (const bit_to_table_map &btm,
+		    style_manager &sm) const final override
+  {
+    table t (table::size_t (btm.get_num_columns (), 1));
+
+    if (tree base_type = m_op.m_base_region->get_type ())
+      if (TREE_CODE (base_type) == ARRAY_TYPE)
+	add_array_elements_to_table (t, btm, sm);
+
+    access_range valid_bits = m_op.get_valid_bits ();
+    const int table_y = t.get_size ().h - 1;
+    const int table_h = 1;
+    table::rect_t rect = btm.get_table_rect (valid_bits, table_y, table_h);
+    styled_string s;
+    switch (m_op.m_base_region->get_kind ())
+      {
+      default:
+	s = styled_string (sm, _("region"));
+	break;
+      case RK_DECL:
+	{
+	  const decl_region *decl_reg
+	    = as_a <const decl_region *> (m_op.m_base_region);
+	  tree decl = decl_reg->get_decl ();
+	  s = fmt_styled_string (sm, "%qE (type: %qT)",
+				 decl,
+				 TREE_TYPE (decl));
+	}
+	break;
+      case RK_HEAP_ALLOCATED:
+	{
+	  if (m_region_creation_event_id.known_p ())
+	    s = fmt_styled_string (sm, _("buffer allocated on heap at %@"),
+				   &m_region_creation_event_id);
+	  else
+	    s = styled_string (sm, _("heap-allocated buffer"));
+	}
+	break;
+      case RK_ALLOCA:
+	{
+	  if (m_region_creation_event_id.known_p ())
+	    s = fmt_styled_string (sm, _("buffer allocated on stack at %@"),
+				   &m_region_creation_event_id);
+	  else
+	    s = styled_string (sm, _("stack-allocated buffer"));
+	}
+	break;
+      case RK_STRING:
+	{
+	  const string_region *string_reg
+	    = as_a <const string_region *> (m_op.m_base_region);
+	  tree string_cst = string_reg->get_string_cst ();
+	  s = fmt_styled_string (sm, _("string literal (type: %qT)"),
+				 TREE_TYPE (string_cst));
+	}
+	break;
+      }
+    t.set_cell_span (rect, std::move (s));
+
+    return t;
+  }
+
+private:
+  const access_operation &m_op;
+  diagnostic_event_id_t m_region_creation_event_id;
+};
+
+/* Subclass of spatial_item for visualizing the region of memory
+   that's actually accessed by the read or write, for reads and
+   for write cases where we don't know the svalue written.  */
+
+class accessed_region_spatial_item : public spatial_item
+{
+public:
+  accessed_region_spatial_item (const access_operation &op) : m_op (op) {}
+
+  void add_boundaries (boundaries &out, logger *logger) const final override
+  {
+    LOG_SCOPE (logger);
+    access_range actual_bits = m_op.get_actual_bits ();
+    if (logger)
+      {
+	logger->start_log_line ();
+	logger->log_partial ("actual bits: ");
+	actual_bits.dump_to_pp (logger->get_printer (), true);
+	logger->end_log_line ();
+      }
+    out.add (actual_bits, boundaries::kind::HARD);
+  }
+
+  table make_table (const bit_to_table_map &btm,
+		    style_manager &sm) const final override
+  {
+    table t (table::size_t (btm.get_num_columns (), 1));
+
+    access_range actual_bits = m_op.get_actual_bits ();
+    const int table_y = 0;
+    const int table_h = 1;
+    table::rect_t rect = btm.get_table_rect (actual_bits, table_y, table_h);
+    t.set_cell_span (rect, styled_string (get_label_string (sm)));
+
+    return t;
+  }
+
+private:
+  styled_string get_label_string (style_manager &sm) const
+  {
+    const access_range accessed_bits (m_op.get_actual_bits ());
+    return get_access_size_str (sm,
+				m_op,
+				accessed_bits,
+				m_op.m_reg.get_type ());
+  }
+
+  const access_operation &m_op;
+};
+
+/* Subclass of spatial_item for when we know the svalue being written
+   to the accessed region.
+   Can be subclassed to give visualizations of specific kinds of svalue.  */
+
+class svalue_spatial_item : public spatial_item
+{
+public:
+  static std::unique_ptr<svalue_spatial_item> make (const access_operation &op,
+						    const svalue &sval,
+						    access_range actual_bits,
+						    const theme &theme);
+
+  svalue_spatial_item (const access_operation &op,
+		       const svalue &sval,
+		       access_range actual_bits)
+  : m_op (op), m_sval (sval), m_actual_bits (actual_bits)
+  {}
+
+  void add_boundaries (boundaries &out, logger *logger) const override
+  {
+    LOG_SCOPE (logger);
+    out.add (m_actual_bits, boundaries::kind::HARD);
+  }
+
+  table make_table (const bit_to_table_map &btm,
+		    style_manager &sm) const override
+  {
+    table t (table::size_t (btm.get_num_columns (), 0));
+
+    const int table_y = t.add_row ();
+    const int table_h = 1;
+    table::rect_t rect = btm.get_table_rect (m_actual_bits, table_y, table_h);
+    t.set_cell_span (rect, styled_string (get_label_string (sm)));
+    return t;
+  }
+
+protected:
+  styled_string get_label_string (style_manager &sm) const
+  {
+    tree rep_tree = m_op.m_model.get_representative_tree (&m_sval);
+    if (rep_tree)
+      {
+	if (TREE_CODE (rep_tree) == SSA_NAME)
+	  rep_tree = SSA_NAME_VAR (rep_tree);
+	switch (TREE_CODE (rep_tree))
+	  {
+	  default:
+	    break;
+	  case INTEGER_CST:
+	    return fmt_styled_string (sm, _("write of %<(%T) %E%>"),
+				      TREE_TYPE (rep_tree),
+				      rep_tree);
+
+	  case PARM_DECL:
+	  case VAR_DECL:
+	    return fmt_styled_string (sm, _("write from %qE (type: %qT)"),
+				      rep_tree,
+				      TREE_TYPE (rep_tree));
+	    break;
+	  }
+	}
+
+    const access_range accessed_bits (m_op.get_actual_bits ());
+    return get_access_size_str (sm,
+				m_op,
+				accessed_bits,
+				m_sval.get_type ());
+  }
+
+  const access_operation &m_op;
+  const svalue &m_sval;
+  access_range m_actual_bits;
+};
+
+/* Subclass of svalue_spatial_item for initial_svalue of a string_region
+   i.e. for string literals.
+
+   There are three cases:
+   (a) for long strings, show just the head and tail of the string,
+   with an ellipsis:
+     +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+     |[0]|[1]|[2]|[3]|[4]|[5]|          |[440]|[441]|[442]|[443]|[444]|[445]|
+     +---+---+---+---+---+---+   ...    +-----+-----+-----+-----+-----+-----+
+     |‘L’|‘o’|‘r’|‘e’|‘m’|‘ ’|          | ‘o’ | ‘r’ | ‘u’ | ‘m’ | ‘.’ | NUL |
+     +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+     |                  string literal (type: ‘char[446]’)                  |
+     +----------------------------------------------------------------------+
+   (b) For sufficiently short strings, show the full string:
+     +----------+---------+---------+---------+---------+ +-----------------+
+     |   [0]    |   [1]   |   [2]   |   [3]   |   [4]   | |       [5]       |
+     +----------+---------+---------+---------+---------+ +-----------------+
+     |   ‘h’    |   ‘e’   |   ‘l’   |   ‘l’   |   ‘o’   | |       NUL       |
+     +----------+---------+---------+---------+---------+-+-----------------+
+     |                   string literal (type: ‘char[6]’)                   |
+     +----------------------------------------------------------------------+
+   (c) for non-ASCII strings that are short enough to show the full string,
+   show how unicode code points of the bytes decoded as UTF-8:
+     +-----+-----+-----+----+----++----+----+----+----+----+----+----+------+
+     | [0] | [1] | [2] |[3] |[4] ||[5] |[6] |[7] |[8] |[9] |[10]|[11]| [12] |
+     +-----+-----+-----+----+----++----+----+----+----+----+----+----+------+
+     |0xe6 |0x96 |0x87 |0xe5|0xad||0x97|0xe5|0x8c|0x96|0xe3|0x81|0x91| 0x00 |
+     +-----+-----+-----+----+----++----+----+----+----+----+----+----+------+
+     |     U+6587      |    U+5b57     |    U+5316    |    U+3051    |U+0000|
+     +-----------------+---------------+--------------+--------------+------+
+     |                  string literal (type: ‘char[13]’)                   |
+     +----------------------------------------------------------------------+
+   and show the characters themselves if unicode is supported and they are not
+   control characters:
+     ┌─────┬─────┬─────┬────┬────┐┌────┬────┬────┬────┬────┬────┬────┬──────┐
+     │ [0] │ [1] │ [2] │[3] │[4] ││[5] │[6] │[7] │[8] │[9] │[10]│[11]│ [12] │
+     ├─────┼─────┼─────┼────┼────┤├────┼────┼────┼────┼────┼────┼────┼──────┤
+     │0xe6 │0x96 │0x87 │0xe5│0xad││0x97│0xe5│0x8c│0x96│0xe3│0x81│0x91│ 0x00 │
+     ├─────┴─────┴─────┼────┴────┴┴────┼────┴────┴────┼────┴────┴────┼──────┤
+     │     U+6587      │    U+5b57     │    U+5316    │    U+3051    │U+0000│
+     ├─────────────────┼───────────────┼──────────────┼──────────────┼──────┤
+     │       文        │      字       │      化      │      け      │ NUL  │
+     ├─────────────────┴───────────────┴──────────────┴──────────────┴──────┤
+     │                  string literal (type: ‘char[13]’)                   │
+     └──────────────────────────────────────────────────────────────────────┘
+*/
+
+class string_region_spatial_item : public svalue_spatial_item
+{
+public:
+  string_region_spatial_item (const access_operation &op,
+			      const svalue &sval,
+			      access_range actual_bits,
+			      const string_region &string_reg,
+			      const theme &theme)
+  : svalue_spatial_item (op, sval, actual_bits),
+    m_string_reg (string_reg),
+    m_theme (theme),
+    m_ellipsis_threshold (param_analyzer_text_art_string_ellipsis_threshold),
+    m_ellipsis_head_len (param_analyzer_text_art_string_ellipsis_head_len),
+    m_ellipsis_tail_len (param_analyzer_text_art_string_ellipsis_tail_len),
+    m_show_full_string (calc_show_full_string ()),
+    m_show_utf8 (m_show_full_string && !pure_ascii_p ())
+  {
+  }
+
+  void add_boundaries (boundaries &out, logger *logger) const override
+  {
+    LOG_SCOPE (logger);
+    out.add (m_actual_bits, boundaries::kind::HARD);
+
+    tree string_cst = get_string_cst ();
+    /* TREE_STRING_LENGTH is sizeof, not strlen.  */
+    if (m_show_full_string)
+      out.add_all_bytes_in_range (m_actual_bits);
+    else
+      {
+	byte_range head_of_string (0, m_ellipsis_head_len);
+	out.add_all_bytes_in_range (head_of_string);
+	byte_range tail_of_string
+	  (TREE_STRING_LENGTH (string_cst) - m_ellipsis_tail_len,
+	   m_ellipsis_tail_len);
+	out.add_all_bytes_in_range (tail_of_string);
+	/* Adding the above pair of ranges will also effectively add
+	   the boundaries of the range of ellipsized chars, as they're
+	   exactly in between head_of_string and tail_of_string.  */
+      }
+  }
+
+  table make_table (const bit_to_table_map &btm,
+		    style_manager &sm) const override
+  {
+    table t (table::size_t (btm.get_num_columns (), 0));
+
+    const int byte_idx_table_y = t.add_row ();
+    const int byte_val_table_y = t.add_row ();
+
+    byte_range bytes (0, 0);
+    bool valid = m_actual_bits.as_concrete_byte_range (&bytes);
+    gcc_assert (valid);
+    tree string_cst = get_string_cst ();
+    if (m_show_full_string)
+      {
+       for (byte_offset_t byte_idx = bytes.get_start_byte_offset ();
+	    byte_idx < bytes.get_next_byte_offset ();
+	    byte_idx = byte_idx + 1)
+	 add_column_for_byte (t, btm, sm, byte_idx,
+			      byte_idx_table_y, byte_val_table_y);
+
+       if (m_show_utf8)
+	 {
+	   const bool show_unichars = m_theme.unicode_p ();
+	   const int utf8_code_point_table_y = t.add_row ();
+	   int utf8_character_table_y;
+	   if (show_unichars)
+	     utf8_character_table_y = t.add_row ();
+
+	   /* We don't actually want the display widths here, but
+	      it's an easy way to decode UTF-8.  */
+	   cpp_char_column_policy policy (8, cpp_wcwidth);
+	   cpp_display_width_computation dw (TREE_STRING_POINTER (string_cst),
+					     TREE_STRING_LENGTH (string_cst),
+					     policy);
+	   while (!dw.done ())
+	     {
+	       cpp_decoded_char decoded_char;
+	       dw.process_next_codepoint (&decoded_char);
+
+	       if (!decoded_char.m_valid_ch)
+		 continue;
+	       size_t start_byte_idx
+		 = decoded_char.m_start_byte - TREE_STRING_POINTER (string_cst);
+	       byte_size_t size_in_bytes
+		 = decoded_char.m_next_byte - decoded_char.m_start_byte;
+	       byte_range bytes (start_byte_idx, size_in_bytes);
+
+	       const table::rect_t code_point_table_rect
+		 = btm.get_table_rect (&m_string_reg, bytes,
+				       utf8_code_point_table_y, 1);
+	       char buf[100];
+	       sprintf (buf, "U+%04x", decoded_char.m_ch);
+	       t.set_cell_span (code_point_table_rect,
+				styled_string (sm, buf));
+
+	       if (show_unichars)
+		 {
+		   const table::rect_t character_table_rect
+		     = btm.get_table_rect (&m_string_reg, bytes,
+					   utf8_character_table_y, 1);
+		   if (cpp_is_printable_char (decoded_char.m_ch))
+		     t.set_cell_span (character_table_rect,
+				      styled_string (decoded_char.m_ch));
+		   else if (decoded_char.m_ch == 0)
+		     t.set_cell_span (character_table_rect,
+				      styled_string (sm, "NUL"));
+		   else
+		     t.set_cell_span (character_table_rect,
+				      styled_string (sm, ""));
+		 }
+	     }
+	 }
+      }
+    else
+      {
+	/* Head of string.  */
+	for (int byte_idx = 0; byte_idx < m_ellipsis_head_len; byte_idx++)
+	  add_column_for_byte (t, btm, sm, byte_idx,
+			       byte_idx_table_y, byte_val_table_y);
+
+	/* Ellipsis (two rows high).  */
+	const byte_range ellipsis_bytes
+	  (m_ellipsis_head_len,
+	   TREE_STRING_LENGTH (string_cst)
+	   - (m_ellipsis_head_len + m_ellipsis_tail_len));
+	const table::rect_t table_rect
+	  = btm.get_table_rect (&m_string_reg, ellipsis_bytes,
+				byte_idx_table_y, 2);
+	t.set_cell_span(table_rect, styled_string (sm, "..."));
+
+	/* Tail of string.  */
+	for (int byte_idx
+	       = (TREE_STRING_LENGTH (string_cst) - m_ellipsis_tail_len);
+	     byte_idx < TREE_STRING_LENGTH (string_cst);
+	     byte_idx++)
+	  add_column_for_byte (t, btm, sm, byte_idx,
+			       byte_idx_table_y, byte_val_table_y);
+      }
+
+    const int summary_table_y = t.add_row ();
+    t.set_cell_span (btm.get_table_rect (&m_string_reg, bytes,
+					 summary_table_y, 1),
+		     fmt_styled_string (sm,
+					_("string literal (type: %qT)"),
+					TREE_TYPE (string_cst)));
+
+    return t;
+  }
+
+  tree get_string_cst () const { return m_string_reg.get_string_cst (); }
+
+private:
+  bool calc_show_full_string () const
+  {
+    tree string_cst = get_string_cst ();
+    if (TREE_STRING_LENGTH (string_cst) < m_ellipsis_threshold)
+      return true;
+    if (TREE_STRING_LENGTH (string_cst) <
+	(m_ellipsis_head_len + m_ellipsis_tail_len))
+      return true;
+    return false;
+  }
+
+  bool pure_ascii_p () const
+  {
+    tree string_cst = get_string_cst ();
+    for (unsigned byte_idx = 0;
+	 byte_idx < (unsigned) TREE_STRING_LENGTH (string_cst);
+	 byte_idx++)
+      {
+	unsigned char ch = TREE_STRING_POINTER (string_cst)[byte_idx];
+	if (ch >= 0x80)
+	  return false;
+      }
+    return true;
+  }
+
+  void add_column_for_byte (table &t, const bit_to_table_map &btm,
+			    style_manager &sm,
+			    const byte_offset_t byte_idx,
+			    const int byte_idx_table_y,
+			    const int byte_val_table_y) const
+  {
+    tree string_cst = get_string_cst ();
+    gcc_assert (byte_idx >= 0);
+    gcc_assert (byte_idx < TREE_STRING_LENGTH (string_cst));
+
+    const byte_range bytes (byte_idx, 1);
+    if (1) // show_byte_indices
+      {
+	const table::rect_t idx_table_rect
+	  = btm.get_table_rect (&m_string_reg, bytes, byte_idx_table_y, 1);
+	t.set_cell_span (idx_table_rect,
+			 fmt_styled_string (sm, "[%li]",
+					    byte_idx.ulow ()));
+      }
+
+    char byte_val = TREE_STRING_POINTER (string_cst)[byte_idx.ulow ()];
+    const table::rect_t val_table_rect
+      = btm.get_table_rect (&m_string_reg, bytes, byte_val_table_y, 1);
+    table_cell_content content (make_cell_content_for_byte (sm, byte_val));
+    t.set_cell_span (val_table_rect, std::move (content));
+  }
+
+  table_cell_content make_cell_content_for_byte (style_manager &sm,
+						 unsigned char byte_val) const
+  {
+    if (!m_show_utf8)
+       {
+	if (byte_val == '\0')
+	  return styled_string (sm, "NUL");
+	else if (byte_val < 0x80)
+	  if (ISPRINT (byte_val))
+	    return fmt_styled_string (sm, "%qc", byte_val);
+       }
+    char buf[100];
+    sprintf (buf, "0x%02x", byte_val);
+    return styled_string (sm, buf);
+  }
+
+  const string_region &m_string_reg;
+  const theme &m_theme;
+  const int m_ellipsis_threshold;
+  const int m_ellipsis_head_len;
+  const int m_ellipsis_tail_len;
+  const bool m_show_full_string;
+  const bool m_show_utf8;
+};
+
+std::unique_ptr<svalue_spatial_item>
+svalue_spatial_item::make (const access_operation &op,
+			   const svalue &sval,
+			   access_range actual_bits,
+			   const theme &theme)
+{
+  if (const initial_svalue *initial_sval = sval.dyn_cast_initial_svalue ())
+    if (const string_region *string_reg
+	= initial_sval->get_region ()->dyn_cast_string_region ())
+      return make_unique <string_region_spatial_item> (op, sval, actual_bits,
+						       *string_reg, theme);
+  return make_unique <svalue_spatial_item> (op, sval, actual_bits);
+}
+
+/* Widget subclass implementing access diagrams.  */
+
+class access_diagram_impl : public vbox_widget
+{
+public:
+  access_diagram_impl (const access_operation &op,
+		       diagnostic_event_id_t region_creation_event_id,
+		       style_manager &sm,
+		       const theme &theme,
+		       logger *logger)
+  : m_op (op),
+    m_region_creation_event_id (region_creation_event_id),
+    m_sm (sm),
+    m_theme (theme),
+    m_logger (logger),
+    m_invalid (false),
+    m_valid_region_spatial_item (op, region_creation_event_id),
+    m_accessed_region_spatial_item (op),
+    m_btm (),
+    m_calc_req_size_called (false)
+  {
+    LOG_SCOPE (logger);
+
+    if (logger)
+      {
+	access_range invalid_before_bits;
+	if (op.maybe_get_invalid_before_bits (&invalid_before_bits))
+	  invalid_before_bits.log ("invalid before range", *logger);
+	access_range invalid_after_bits;
+	if (op.maybe_get_invalid_after_bits (&invalid_after_bits))
+	  invalid_after_bits.log ("invalid after range", *logger);
+
+	if (op.m_sval_hint)
+	  {
+	    logger->start_log_line ();
+	    logger->log_partial ("sval_hint: ");
+	    op.m_sval_hint->dump_to_pp (logger->get_printer (), true);
+	    logger->end_log_line ();
+	  }
+      }
+
+    /* Register painting styles.  */
+    {
+      style valid_style;
+      valid_style.m_fg_color = style::named_color::GREEN;
+      valid_style.m_bold = true;
+      m_valid_style_id = m_sm.get_or_create_id (valid_style);
+
+      style invalid_style;
+      invalid_style.m_fg_color = style::named_color::RED;
+      invalid_style.m_bold = true;
+      m_invalid_style_id = m_sm.get_or_create_id (invalid_style);
+    }
+
+    if (op.m_sval_hint)
+      {
+	access_range actual_bits = m_op.get_actual_bits ();
+	m_svalue_spatial_item = svalue_spatial_item::make (m_op,
+							   *op.m_sval_hint,
+							   actual_bits,
+							   m_theme);
+      }
+
+    /* Two passes:
+       First, figure out all of the boundaries of interest.
+       Then use that to build child widgets showing the regions of interest,
+       with a common tabular layout.  */
+
+    m_boundaries = find_boundaries ();
+    if (logger)
+      m_boundaries->log (*logger);
+
+    /* Populate m_table_x_for_bit and m_bit_for_table_x.
+       Each table column represents the range [offset, next_offset).
+       We don't create a column in the table for the final offset, but we
+       do populate it, so that looking at the table_x of one beyond the
+       final table column gives us the upper bound offset.  */
+    m_btm.populate (*m_boundaries, logger);
+
+    /* Gracefully reject cases where the boundary sorting has gone wrong
+       (due to awkward combinations of symbolic values).  */
+    {
+      table::range_t actual_bits_x_range
+	= m_btm.get_table_x_for_range (m_op.get_actual_bits ());
+      if (actual_bits_x_range.get_size () <= 0)
+	{
+	  if (logger)
+	    logger->log ("giving up: bad table columns for actual_bits");
+	  m_invalid = true;
+	  return;
+	}
+      table::range_t valid_bits_x_range
+	= m_btm.get_table_x_for_range (m_op.get_valid_bits ());
+      if (valid_bits_x_range.get_size () <= 0)
+	{
+	  if (logger)
+	    logger->log ("giving up: bad table columns for valid_bits");
+	  m_invalid = true;
+	  return;
+	}
+    }
+
+    m_col_widths
+      = make_unique <table_dimension_sizes> (m_btm.get_num_columns ());
+
+    /* Now create child widgets.  */
+
+    if (flag_analyzer_debug_text_art)
+      {
+	table t_headings (make_headings_table ());
+	add_aligned_child_table (std::move (t_headings));
+      }
+
+    if (m_svalue_spatial_item)
+      {
+	table t_sval (m_svalue_spatial_item->make_table (m_btm, m_sm));
+	add_aligned_child_table (std::move (t_sval));
+      }
+    else
+      {
+	table t_accessed
+	  (m_accessed_region_spatial_item.make_table (m_btm, m_sm));
+	add_aligned_child_table (std::move (t_accessed));
+      }
+
+    add_direction_widget ();
+
+    table t_valid (m_valid_region_spatial_item.make_table (m_btm, m_sm));
+    add_invalid_accesses_to_region_table (t_valid);
+    add_aligned_child_table (std::move (t_valid));
+
+    add_valid_vs_invalid_ruler ();
+  }
+
+  const char *get_desc () const override
+  {
+    return "access_diagram_impl";
+  }
+
+  canvas::size_t calc_req_size () final override
+  {
+    if (m_invalid)
+      return canvas::size_t (0, 0);
+
+    /* Now compute the size requirements for the tables.  */
+    for (auto iter : m_aligned_table_widgets)
+      iter->get_cell_sizes ().pass_1 (iter->get_table ());
+    for (auto iter : m_aligned_table_widgets)
+      iter->get_cell_sizes ().pass_2 (iter->get_table ());
+
+    adjust_to_scale();
+
+    /* ...and relayout the tables.  */
+    for (auto iter : m_aligned_table_widgets)
+      iter->recalc_coords ();
+
+    /* Populate the canvas_x per table_x.  */
+    m_col_start_x.clear ();
+    int iter_canvas_x = 0;
+    for (auto w : m_col_widths->m_requirements)
+      {
+	m_col_start_x.push_back (iter_canvas_x);
+	iter_canvas_x += w + 1;
+      }
+    m_col_start_x.push_back (iter_canvas_x);
+
+    m_calc_req_size_called = true;
+
+    return vbox_widget::calc_req_size ();
+  }
+
+  int get_canvas_x_for_table_x (int table_x) const
+  {
+    gcc_assert (m_calc_req_size_called);
+    return m_col_start_x[table_x];
+  }
+
+  canvas::range_t get_canvas_x_range (const table::range_t &table_x_range) const
+  {
+    gcc_assert (m_calc_req_size_called);
+    return canvas::range_t (get_canvas_x_for_table_x (table_x_range.start),
+			    get_canvas_x_for_table_x (table_x_range.next));
+  }
+
+  const access_operation &get_op () const { return m_op; }
+
+  style::id_t get_style_id_for_validity (bool is_valid) const
+  {
+    return is_valid ? m_valid_style_id : m_invalid_style_id;
+  }
+
+  const theme &get_theme () const { return m_theme; }
+
+private:
+  /* Figure out all of the boundaries of interest when visualizing ths op.  */
+  std::unique_ptr<boundaries>
+  find_boundaries () const
+  {
+    std::unique_ptr<boundaries> result
+      = make_unique<boundaries> (*m_op.m_base_region);
+
+    m_valid_region_spatial_item.add_boundaries (*result, m_logger);
+    m_accessed_region_spatial_item.add_boundaries (*result, m_logger);
+    if (m_svalue_spatial_item)
+      m_svalue_spatial_item->add_boundaries (*result, m_logger);
+
+    return result;
+  }
+
+  void add_aligned_child_table (table t)
+  {
+    x_aligned_table_widget *w
+      = new x_aligned_table_widget (std::move (t), m_theme, *m_col_widths);
+    m_aligned_table_widgets.push_back (w);
+    add_child (std::unique_ptr<widget> (w));
+  }
+
+  /* Create a table showing headings for use by -fanalyzer-debug-text-art, for
+     example:
+     +---------+-----------+-----------+---+--------------------------------+
+     |   tc0   |    tc1    |    tc2    |tc3|              tc4               |
+     +---------+-----------+-----------+---+--------------------------------+
+     |bytes 0-3|bytes 4-35 |bytes 36-39|   |          bytes 40-43           |
+     +---------+-----------+-----------+   +--------------------------------+
+     which has:
+     - a row showing the table column numbers, labelled "tc0", "tc1", etc
+     - a row showing the memory range of each table column that has one.  */
+
+  table make_headings_table () const
+  {
+    table t (table::size_t (m_btm.get_num_columns (), 2));
+
+    for (int table_x = 0; table_x < t.get_size ().w; table_x++)
+      {
+	const int table_y = 0;
+	t.set_cell (table::coord_t (table_x, table_y),
+		    fmt_styled_string (m_sm, "tc%i", table_x));
+      }
+    for (int table_x = 0; table_x < t.get_size ().w; table_x++)
+      {
+	const int table_y = 1;
+	access_range range_for_column (NULL, bit_range (0, 0));
+	if (m_btm.maybe_get_access_range_for_table_x (table_x,
+						      &range_for_column))
+	  {
+	    pretty_printer pp;
+	    pp_format_decoder (&pp) = default_tree_printer;
+	    range_for_column.dump_to_pp (&pp, true);
+	    t.set_cell (table::coord_t (table_x, table_y),
+			styled_string (m_sm, pp_formatted_text (&pp)));
+	  }
+      }
+
+    return t;
+  }
+
+  void add_direction_widget ()
+  {
+    add_child (::make_unique<direction_widget> (*this, m_btm));
+  }
+
+  void add_invalid_accesses_to_region_table (table &t_region)
+  {
+    gcc_assert (t_region.get_size ().w == (int)m_btm.get_num_columns ());
+
+    const int table_y = 0;
+    const int table_h = t_region.get_size ().h;
+
+    access_range invalid_before_bits;
+    if (m_op.maybe_get_invalid_before_bits (&invalid_before_bits))
+      {
+	t_region.set_cell_span (m_btm.get_table_rect (invalid_before_bits,
+						      table_y, table_h),
+				styled_string (m_sm,
+					       _("before valid range")));
+      }
+    access_range invalid_after_bits;
+    if (m_op.maybe_get_invalid_after_bits (&invalid_after_bits))
+      {
+	t_region.set_cell_span (m_btm.get_table_rect (invalid_after_bits,
+						      table_y, table_h),
+				styled_string (m_sm,
+					       _("after valid range")));
+      }
+  }
+
+  void maybe_add_gap (x_aligned_x_ruler_widget *w,
+		      const access_range &lower,
+		      const access_range &upper) const
+  {
+    LOG_SCOPE (m_logger);
+    if (m_logger)
+      {
+	lower.log ("lower", *m_logger);
+	upper.log ("upper", *m_logger);
+      }
+    tree lower_next = lower.m_next.calc_symbolic_bit_offset (m_op.m_model);
+    if (!lower_next)
+      {
+	if (m_logger)
+	  m_logger->log ("failed to get lower_next");
+	return;
+      }
+    tree upper_start = upper.m_start.calc_symbolic_bit_offset (m_op.m_model);
+    if (!upper_start)
+      {
+	if (m_logger)
+	  m_logger->log ("failed to get upper_start");
+	return;
+      }
+    tree num_bits_gap = fold_build2 (MINUS_EXPR,
+				     size_type_node,
+				     upper_start, lower_next);
+    if (m_logger)
+      m_logger->log ("num_bits_gap: %qE", num_bits_gap);
+    tree zero = build_int_cst (size_type_node, 0);
+    tristate ts_gt_zero = m_op.m_model.eval_condition (num_bits_gap,
+						       GT_EXPR,
+						       zero,
+						       NULL);
+    if (ts_gt_zero.is_false ())
+      {
+	if (m_logger)
+	  m_logger->log ("rejecting as not > 0");
+	return;
+      }
+
+    bit_size_expr num_bits (num_bits_gap);
+    styled_string label = num_bits.get_formatted_str (m_sm,
+						      _("%wi bit"),
+						      _("%wi bits"),
+						      _("%wi byte"),
+						      _("%wi bytes"),
+						      _("%qE bits"),
+						      _("%qE bytes"));
+    w->add_range (m_btm.get_table_x_for_range (access_range (lower.m_next,
+							     upper.m_start)),
+		  std::move (label),
+		  style::id_plain);
+  }
+
+  styled_string
+  make_warning_string (styled_string &&text)
+  {
+    styled_string result;
+    if (!m_theme.emojis_p ())
+      return std::move (text);
+
+    result.append (styled_string (0x26A0, /* U+26A0 WARNING SIGN.  */
+				  true));
+    /* U+26A0 WARNING SIGN has East_Asian_Width == Neutral, but in its
+       emoji variant is printed (by vte at least) with a 2nd half
+       overlapping the next char.  Hence we add two spaces here: a space
+       to be covered by this overlap, plus another space of padding.  */
+    result.append (styled_string (m_sm, "  "));
+    result.append (std::move (text));
+    return result;
+  }
+
+  /* Add a ruler child widet showing valid, invalid, and gaps.  */
+  void add_valid_vs_invalid_ruler ()
+  {
+    LOG_SCOPE (m_logger);
+
+    x_aligned_x_ruler_widget *w
+      = new x_aligned_x_ruler_widget (*this, m_theme, *m_col_widths);
+
+    access_range invalid_before_bits;
+    if (m_op.maybe_get_invalid_before_bits (&invalid_before_bits))
+      {
+	if (m_logger)
+	  invalid_before_bits.log ("invalid_before_bits", *m_logger);
+	bit_size_expr num_before_bits;
+	if (invalid_before_bits.get_size (m_op.m_model, &num_before_bits))
+	  {
+	    styled_string label;
+	    if (m_op.m_dir == DIR_READ)
+	      label = num_before_bits.get_formatted_str
+		(m_sm,
+		 _("under-read of %wi bit"),
+		 _("under-read of %wi bits"),
+		 _("under-read of %wi byte"),
+		 _("under-read of %wi bytes"),
+		 _("under-read of %qE bits"),
+		 _("under-read of %qE bytes"));
+	    else
+	      label = num_before_bits.get_formatted_str
+		(m_sm,
+		 _("underwrite of %wi bit"),
+		 _("underwrite of %wi bits"),
+		 _("underwrite of %wi byte"),
+		 _("underwrite of %wi bytes"),
+		 _("underwrite of %qE bits"),
+		 _("underwrite of %qE bytes"));
+	    w->add_range (m_btm.get_table_x_for_range (invalid_before_bits),
+			  make_warning_string (std::move (label)),
+			  m_invalid_style_id);
+	  }
+      }
+    else
+      {
+	if (m_logger)
+	  m_logger->log ("no invalid_before_bits");
+      }
+
+    /* It would be nice to be able to use std::optional<access_range> here,
+       but std::optional is C++17.  */
+    bool got_valid_bits = false;
+    access_range valid_bits (m_op.get_valid_bits ());
+    bit_size_expr num_valid_bits;
+    if (valid_bits.get_size (m_op.m_model, &num_valid_bits))
+      {
+	if (m_logger)
+	  valid_bits.log ("valid_bits", *m_logger);
+
+	got_valid_bits = true;
+	maybe_add_gap (w, invalid_before_bits, valid_bits);
+
+	styled_string label;
+	if (m_op.m_dir == DIR_READ)
+	  label = num_valid_bits.get_formatted_str (m_sm,
+						    _("size: %wi bit"),
+						    _("size: %wi bits"),
+						    _("size: %wi byte"),
+						    _("size: %wi bytes"),
+						    _("size: %qE bits"),
+						    _("size: %qE bytes"));
+	else
+	  label = num_valid_bits.get_formatted_str (m_sm,
+						    _("capacity: %wi bit"),
+						    _("capacity: %wi bits"),
+						    _("capacity: %wi byte"),
+						    _("capacity: %wi bytes"),
+						    _("capacity: %qE bits"),
+						    _("capacity: %qE bytes"));
+	w->add_range (m_btm.get_table_x_for_range (m_op.get_valid_bits ()),
+		      std::move (label),
+		      m_valid_style_id);
+      }
+
+    access_range invalid_after_bits;
+    if (m_op.maybe_get_invalid_after_bits (&invalid_after_bits))
+      {
+	if (got_valid_bits)
+	  maybe_add_gap (w, valid_bits, invalid_after_bits);
+
+	if (m_logger)
+	  invalid_before_bits.log ("invalid_after_bits", *m_logger);
+
+	bit_size_expr num_after_bits;
+	if (invalid_after_bits.get_size (m_op.m_model, &num_after_bits))
+	  {
+	    styled_string label;
+	    if (m_op.m_dir == DIR_READ)
+	      label = num_after_bits.get_formatted_str
+		(m_sm,
+		 _("over-read of %wi bit"),
+		 _("over-read of %wi bits"),
+		 _("over-read of %wi byte"),
+		 _("over-read of %wi bytes"),
+		 _("over-read of %qE bits"),
+		 _("over-read of %qE bytes"));
+	    else
+	      label = num_after_bits.get_formatted_str
+		(m_sm,
+		 _("overflow of %wi bit"),
+		 _("overflow of %wi bits"),
+		 _("overflow of %wi byte"),
+		 _("overflow of %wi bytes"),
+		 _("over-read of %qE bits"),
+		 _("overflow of %qE bytes"));
+	    w->add_range (m_btm.get_table_x_for_range (invalid_after_bits),
+			  make_warning_string (std::move (label)),
+			  m_invalid_style_id);
+	  }
+      }
+    else
+      {
+	if (m_logger)
+	  m_logger->log ("no invalid_after_bits");
+      }
+
+    add_child (std::unique_ptr<widget> (w));
+  }
+
+  /* Subroutine of calc_req_size.
+     Try to allocate surplus canvas width to table columns to make the
+     per table-column canvas widths closer to being to scale.
+     See e.g.:
+       https://en.wikipedia.org/wiki/Fair_item_allocation
+       https://en.wikipedia.org/wiki/Mathematics_of_apportionment
+  */
+  void adjust_to_scale ()
+  {
+    LOG_SCOPE (m_logger);
+    const unsigned num_columns = m_btm.get_num_columns ();
+    std::vector<bit_offset_t> bit_sizes (num_columns);
+    for (unsigned table_x = 0; table_x < num_columns; table_x++)
+      {
+	access_range range_for_column (NULL, bit_range (0, 0));
+	if (m_btm.maybe_get_access_range_for_table_x (table_x,
+						      &range_for_column))
+	  {
+	    bit_size_t size_in_bits;
+	    if (!range_for_column.get_size_in_bits (&size_in_bits))
+	      size_in_bits = BITS_PER_UNIT; // arbitrary non-zero value
+	    gcc_assert (size_in_bits > 0);
+	    bit_sizes[table_x] = size_in_bits;
+	  }
+	else
+	  bit_sizes[table_x] = 0;
+      }
+
+    while (adjust_to_scale_once (bit_sizes))
+      {
+      }
+  }
+  bool adjust_to_scale_once (const std::vector<bit_offset_t> &bit_sizes)
+  {
+    LOG_SCOPE (m_logger);
+
+    const unsigned num_columns = m_btm.get_num_columns ();
+
+    /* Find the total canvas width currently required.
+       Require one extra canvas column for the right-hand border
+       of the table.  */
+    int total_width = 1;
+    for (unsigned table_x = 0; table_x < num_columns; table_x++)
+      {
+	int canvas_w = m_col_widths->m_requirements[table_x];
+	gcc_assert (canvas_w >= 0);
+	total_width += canvas_w + 1;
+      }
+
+    const int max_width = param_analyzer_text_art_ideal_canvas_width;
+    if (total_width >= max_width)
+      {
+	if (m_logger)
+	  m_logger->log ("bailing out: total_width=%i ,>= max_width (%i)\n",
+			 total_width, max_width);
+	return false;
+      }
+
+    const int fixed_point = 1024;
+    std::vector<bit_offset_t> canvas_w_per_bit (num_columns);
+    for (unsigned table_x = 0; table_x < num_columns; table_x++)
+      {
+	bit_offset_t bit_size = bit_sizes[table_x];
+	if (bit_size > 0)
+	  canvas_w_per_bit[table_x]
+	    = (m_col_widths->m_requirements[table_x] * fixed_point) / bit_size;
+	else
+	  canvas_w_per_bit[table_x] = INT_MAX;
+      }
+
+    /* Find the min canvas per bit, and give an extra canvas column to
+       the table column that has least.  */
+    size_t min_idx = std::distance (canvas_w_per_bit.begin (),
+				    std::min_element (canvas_w_per_bit.begin (),
+						      canvas_w_per_bit.end ()));
+    m_col_widths->m_requirements[min_idx] += 1;
+    if (m_logger)
+      m_logger->log ("adding 1 canvas_w to column %i\n", (int)min_idx);
+
+    return true; // keep going
+  }
+
+  const access_operation &m_op;
+  diagnostic_event_id_t m_region_creation_event_id;
+  style_manager &m_sm;
+  const theme &m_theme;
+  logger *m_logger;
+  /* In lieu of being able to throw exceptions, a flag to mark this object
+     as "invalid".  */
+  bool m_invalid;
+
+  style::id_t m_valid_style_id;
+  style::id_t m_invalid_style_id;
+
+  valid_region_spatial_item m_valid_region_spatial_item;
+  accessed_region_spatial_item m_accessed_region_spatial_item;
+  std::unique_ptr<svalue_spatial_item> m_svalue_spatial_item;
+
+  std::unique_ptr<boundaries> m_boundaries;
+
+  bit_to_table_map m_btm;
+
+  bool m_calc_req_size_called;
+
+  /* Column widths shared by all x_aligned_table_widget,
+     created once we know how many columns we need.  */
+  std::unique_ptr<table_dimension_sizes> m_col_widths;
+
+  /* All of the child x_aligned_table_widget that share
+     column widths.  */
+  std::vector<x_aligned_table_widget *> m_aligned_table_widgets;
+
+/* Mapping from table_x to canvas_x.  */
+  std::vector<int> m_col_start_x;
+};
+
+x_ruler
+x_aligned_x_ruler_widget::make_x_ruler () const
+{
+  x_ruler r (x_ruler::label_dir::BELOW);
+  for (auto& iter : m_labels)
+    {
+      canvas::range_t canvas_x_range
+	= m_dia_impl.get_canvas_x_range (iter.m_table_x_range);
+      /* Include the end-point.  */
+      canvas_x_range.next++;
+      r.add_label (canvas_x_range, iter.m_text.copy (), iter.m_style_id,
+		   x_ruler::label_kind::TEXT_WITH_BORDER);
+    }
+  return r;
+}
+
+/* class direction_widget : public leaf_widget.  */
+
+/* Paint arrows indicating the direction of the access (read vs write),
+   but only in the X-extent corresponding to the region that's actually
+   accessed.  */
+
+void
+direction_widget::paint_to_canvas (canvas &canvas)
+{
+  const access_range accessed_bits (m_dia_impl.get_op ().get_actual_bits ());
+
+  const access_range valid_bits (m_dia_impl.get_op ().get_valid_bits ());
+
+  for (unsigned table_x = 0; table_x < m_btm.get_num_columns (); table_x++)
+    {
+      access_range column_access_range;
+      if (m_btm.maybe_get_access_range_for_table_x (table_x,
+						    &column_access_range))
+	{
+	  /* Only paint arrows in the accessed region.  */
+	  if (!accessed_bits.contains_p (column_access_range))
+	    continue;
+
+	  /* Are we within the valid region?  */
+	  const bool is_valid (valid_bits.contains_p (column_access_range));
+	  const style::id_t style_id
+	    = m_dia_impl.get_style_id_for_validity (is_valid);
+	  const canvas::range_t x_canvas_range
+	    = m_dia_impl.get_canvas_x_range (table::range_t (table_x,
+							     table_x + 1));
+	  const int canvas_x = x_canvas_range.get_midpoint ();
+	  m_dia_impl.get_theme ().paint_y_arrow
+	    (canvas,
+	     canvas_x,
+	     canvas::range_t (get_y_range ()),
+	     (m_dia_impl.get_op ().m_dir == DIR_READ
+	      ? theme::y_arrow_dir::UP
+	      : theme::y_arrow_dir::DOWN),
+	     style_id);
+	}
+    }
+}
+
+/* class access_diagram : public text_art::wrapper_widget.  */
+
+/* To hide the implementation details, this is merely a wrapper around
+   an access_diagram_impl.  */
+
+access_diagram::access_diagram (const access_operation &op,
+				diagnostic_event_id_t region_creation_event_id,
+				style_manager &sm,
+				const theme &theme,
+				logger *logger)
+: wrapper_widget (make_unique <access_diagram_impl> (op,
+						     region_creation_event_id,
+						     sm,
+						     theme,
+						     logger))
+{
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/access-diagram.h b/gcc/analyzer/access-diagram.h
new file mode 100644
index 00000000000..c124e80ac2e
--- /dev/null
+++ b/gcc/analyzer/access-diagram.h
@@ -0,0 +1,165 @@ 
+/* Text art visualizations within -fanalyzer.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_ANALYZER_ACCESS_DIAGRAM_H
+#define GCC_ANALYZER_ACCESS_DIAGRAM_H
+
+#include "text-art/canvas.h"
+#include "text-art/theme.h"
+#include "text-art/widget.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/store.h"
+
+namespace ana {
+
+class bit_size_expr
+{
+public:
+  bit_size_expr () : m_num_bits (NULL) {}
+  bit_size_expr (tree num_bits) : m_num_bits (num_bits) {}
+
+  text_art::styled_string
+  get_formatted_str (text_art::style_manager &sm,
+		     const char *concrete_single_bit_fmt,
+		     const char *concrete_plural_bits_fmt,
+		     const char *concrete_single_byte_fmt,
+		     const char *concrete_plural_bytes_fmt,
+		     const char *symbolic_bits_fmt,
+		     const char *symbolic_bytes_fmt) const;
+  void print (pretty_printer *pp) const;
+
+  tree maybe_get_as_bytes () const;
+
+private:
+  tree m_num_bits;
+};
+
+/* A range of bits within a base region, where each endpoint
+   could be concrete or symbolic (not necessarily the same).  */
+
+struct access_range
+{
+  access_range ()
+  : m_start (), m_next ()
+  {
+  }
+  access_range (region_offset start, region_offset next)
+  : m_start (start), m_next (next)
+  {}
+  access_range (const region *base_region, const bit_range &bits);
+  access_range (const region *base_region, const byte_range &bytes);
+  access_range (const region &reg, region_model_manager *);
+
+  bool concrete_p () const
+  {
+    return m_start.concrete_p () && m_next.concrete_p ();
+  }
+
+  bool empty_p () const;
+
+  bool get_size (const region_model &model, bit_size_expr *out) const;
+
+  bool get_size_in_bits (bit_size_t *out) const
+  {
+    if (concrete_p ())
+      {
+	*out = m_next.get_bit_offset () - m_start.get_bit_offset ();
+	return true;
+      }
+    return false;
+  }
+
+  bool as_concrete_bit_range (bit_range *out) const
+  {
+    if (!concrete_p ())
+      return false;
+    bit_size_t size = m_next.get_bit_offset () - m_start.get_bit_offset ();
+    *out = bit_range (m_start.get_bit_offset (), size);
+    return true;
+  }
+  bool as_concrete_byte_range (byte_range *out) const
+  {
+    bit_range bits (0, 0);
+    if (!as_concrete_bit_range (&bits))
+      return false;
+    return bits.as_byte_range (out);
+  }
+
+  bool contains_p (const access_range &other) const;
+
+  void dump_to_pp (pretty_printer *pp, bool) const;
+  void dump (bool) const;
+  void log (const char *title, logger &) const;
+
+  region_offset m_start;
+  region_offset m_next;
+};
+
+struct access_operation
+{
+  access_operation (const region_model &model,
+		    enum access_direction dir,
+		    const region &reg,
+		    const svalue *sval_hint)
+  : m_model (model),
+    m_dir (dir),
+    m_reg (reg),
+    m_sval_hint (sval_hint),
+    m_base_region (reg.get_base_region ())
+  {}
+
+  region_model_manager *get_manager () const
+  {
+    return m_model.get_manager ();
+  }
+
+  /* Get the valid bits to access within the base region.  */
+  access_range get_valid_bits () const;
+
+  /* Get the actual bits accessed within the base region.  */
+  access_range get_actual_bits () const;
+
+  bool maybe_get_invalid_before_bits (access_range *out) const;
+  bool maybe_get_invalid_after_bits (access_range *out) const;
+
+  const region_model &m_model;
+  enum access_direction m_dir;
+  const region &m_reg;
+  const svalue *m_sval_hint;
+  const region *m_base_region;
+};
+
+class access_diagram : public text_art::wrapper_widget
+{
+public:
+  access_diagram (const access_operation &op,
+		  diagnostic_event_id_t region_creation_event_id,
+		  text_art::style_manager &sm,
+		  const text_art::theme &theme,
+		  logger *logger);
+  const char *get_desc () const override
+  {
+    return "access_diagram";
+  }
+};
+
+} // namespace ana
+
+#endif /* GCC_ANALYZER_ACCESS_DIAGRAM_H */
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index a1619525afa..579517c23e6 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -183,6 +183,11 @@  extern tree get_field_at_bit_offset (tree record_type, bit_offset_t bit_offset);
 class region_offset
 {
 public:
+  region_offset ()
+  : m_base_region (NULL), m_offset (0), m_sym_offset (NULL)
+  {
+  }
+
   static region_offset make_concrete (const region *base_region,
 				      bit_offset_t offset)
   {
@@ -193,9 +198,12 @@  public:
   {
     return region_offset (base_region, 0, sym_offset);
   }
+  static region_offset make_byte_offset (const region *base_region,
+					 const svalue *num_bytes_sval);
 
   const region *get_base_region () const { return m_base_region; }
 
+  bool concrete_p () const { return m_sym_offset == NULL; }
   bool symbolic_p () const { return m_sym_offset != NULL; }
 
   bit_offset_t get_bit_offset () const
@@ -204,12 +212,26 @@  public:
     return m_offset;
   }
 
+  bool get_concrete_byte_offset (byte_offset_t *out) const
+  {
+    gcc_assert (!symbolic_p ());
+    if (m_offset % BITS_PER_UNIT == 0)
+      {
+	*out = m_offset / BITS_PER_UNIT;
+	return true;
+      }
+    return false;
+  }
+
   const svalue *get_symbolic_byte_offset () const
   {
     gcc_assert (symbolic_p ());
     return m_sym_offset;
   }
 
+  tree calc_symbolic_bit_offset (const region_model &model) const;
+  const svalue *calc_symbolic_byte_offset (region_model_manager *mgr) const;
+
   bool operator== (const region_offset &other) const
   {
     return (m_base_region == other.m_base_region
@@ -217,6 +239,9 @@  public:
 	    && m_sym_offset == other.m_sym_offset);
   }
 
+  void dump_to_pp (pretty_printer *pp, bool) const;
+  void dump (bool) const;
+
 private:
   region_offset (const region *base_region, bit_offset_t offset,
 		 const svalue *sym_offset)
@@ -228,6 +253,11 @@  private:
   const svalue *m_sym_offset;
 };
 
+extern bool operator< (const region_offset &, const region_offset &);
+extern bool operator<= (const region_offset &, const region_offset &);
+extern bool operator> (const region_offset &, const region_offset &);
+extern bool operator>= (const region_offset &, const region_offset &);
+
 extern location_t get_stmt_location (const gimple *stmt, function *fun);
 
 extern bool compat_types_p (tree src_type, tree dst_type);
diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index 9d1a937e403..2760aaa8151 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -54,6 +54,22 @@  The minimum number of supernodes within a function for the analyzer to consider
 Common Joined UInteger Var(param_analyzer_max_enodes_for_full_dump) Init(200) Param
 The maximum depth of exploded nodes that should appear in a dot dump before switching to a less verbose format.
 
+-param=analyzer-text-art-string-ellipsis-threshold=
+Common Joined UInteger Var(param_analyzer_text_art_string_ellipsis_threshold) Init(15) Param
+The number of bytes at which to ellipsize string literals in analyzer text art diagrams.
+
+-param=analyzer-text-art-string-ellipsis-head-len=
+Common Joined UInteger Var(param_analyzer_text_art_string_ellipsis_head_len) Init(6) Param
+The number of literal bytes to show at the head of a string literal in text art when ellipsizing it.
+
+-param=analyzer-text-art-string-ellipsis-tail-len=
+Common Joined UInteger Var(param_analyzer_text_art_string_ellipsis_tail_len) Init(6) Param
+The number of literal bytes to show at the tail of a string literal in text art when ellipsizing it.
+
+-param=analyzer-text-art-ideal-canvas-width=
+Common Joined UInteger Var(param_analyzer_text_art_ideal_canvas_width) Init(72) Param
+The ideal width in characters of text art diagrams generated by the analyzer.
+
 Wanalyzer-allocation-size
 Common Var(warn_analyzer_allocation_size) Init(1) Warning
 Warn about code paths in which a pointer to a buffer is assigned to an incompatible type.
@@ -242,6 +258,10 @@  fanalyzer-checker=
 Common Joined RejectNegative Var(flag_analyzer_checker)
 Restrict the analyzer to run just the named checker.
 
+fanalyzer-debug-text-art
+Common Var(flag_analyzer_debug_text_art) Init(0)
+Add extra annotations to diagrams.
+
 fanalyzer-fine-grained
 Common Var(flag_analyzer_fine_grained) Init(0)
 Avoid combining multiple statements into one exploded edge.
diff --git a/gcc/analyzer/bounds-checking.cc b/gcc/analyzer/bounds-checking.cc
index 3bf542a8eba..6b618b5d398 100644
--- a/gcc/analyzer/bounds-checking.cc
+++ b/gcc/analyzer/bounds-checking.cc
@@ -25,15 +25,18 @@  along with GCC; see the file COPYING3.  If not see
 #include "tree.h"
 #include "function.h"
 #include "basic-block.h"
+#include "intl.h"
 #include "gimple.h"
 #include "gimple-iterator.h"
 #include "diagnostic-core.h"
 #include "diagnostic-metadata.h"
+#include "diagnostic-diagram.h"
 #include "analyzer/analyzer.h"
 #include "analyzer/analyzer-logging.h"
 #include "analyzer/region-model.h"
 #include "analyzer/checker-event.h"
 #include "analyzer/checker-path.h"
+#include "analyzer/access-diagram.h"
 
 #if ENABLE_ANALYZER
 
@@ -44,8 +47,35 @@  namespace ana {
 class out_of_bounds : public pending_diagnostic
 {
 public:
-  out_of_bounds (const region *reg, tree diag_arg)
-  : m_reg (reg), m_diag_arg (diag_arg)
+  class oob_region_creation_event_capacity : public region_creation_event_capacity
+  {
+  public:
+    oob_region_creation_event_capacity (tree capacity,
+					const event_loc_info &loc_info,
+					out_of_bounds &oob)
+      : region_creation_event_capacity (capacity,
+					loc_info),
+	m_oob (oob)
+    {
+    }
+    void prepare_for_emission (checker_path *path,
+			       pending_diagnostic *pd,
+			       diagnostic_event_id_t emission_id) override
+    {
+      region_creation_event_capacity::prepare_for_emission (path,
+							    pd,
+							    emission_id);
+      m_oob.m_region_creation_event_id = emission_id;
+    }
+    private:
+    out_of_bounds &m_oob;
+  };
+
+  out_of_bounds (const region_model &model,
+		 const region *reg,
+		 tree diag_arg,
+		 const svalue *sval_hint)
+  : m_model (model), m_reg (reg), m_diag_arg (diag_arg), m_sval_hint (sval_hint)
   {}
 
   bool subclass_equal_p (const pending_diagnostic &base_other) const override
@@ -63,7 +93,7 @@  public:
 
   void mark_interesting_stuff (interesting_t *interest) final override
   {
-    interest->add_region_creation (m_reg);
+    interest->add_region_creation (m_reg->get_base_region ());
   }
 
   void add_region_creation_events (const region *,
@@ -75,15 +105,25 @@  public:
        so we don't need an event for that.  */
     if (capacity)
       emission_path.add_event
-	(make_unique<region_creation_event_capacity> (capacity, loc_info));
+	(make_unique<oob_region_creation_event_capacity> (capacity, loc_info,
+							  *this));
   }
 
+  virtual enum access_direction get_dir () const = 0;
+
 protected:
   enum memory_space get_memory_space () const
   {
     return m_reg->get_memory_space ();
   }
 
+  void
+  maybe_show_notes (location_t loc, logger *logger) const
+  {
+    maybe_describe_array_bounds (loc);
+    maybe_show_diagram (logger);
+  }
+
   /* Potentially add a note about valid ways to index this array, such
      as (given "int arr[10];"):
        note: valid subscripts for 'arr' are '[0]' to '[9]'
@@ -112,8 +152,49 @@  protected:
 	    m_diag_arg, min_idx, max_idx);
   }
 
+  void
+  maybe_show_diagram (logger *logger) const
+  {
+    access_operation op (m_model, get_dir (), *m_reg, m_sval_hint);
+
+    /* Don't attempt to make a diagram if there's no valid way of
+       accessing the base region (e.g. a 0-element array).  */
+    if (op.get_valid_bits ().empty_p ())
+      return;
+
+    if (const text_art::theme *theme = global_dc->m_diagrams.m_theme)
+      {
+	text_art::style_manager sm;
+	text_art::canvas canvas (make_access_diagram (op, sm, *theme, logger));
+	if (canvas.get_size ().w == 0 && canvas.get_size ().h == 0)
+	  {
+	    /* In lieu of exceptions, return a zero-sized diagram if there's
+	       a problem.  Give up if that's happened.  */
+	    return;
+	  }
+	diagnostic_diagram diagram
+	  (canvas,
+	   /* Alt text.  */
+	   _("Diagram visualizing the predicted out-of-bounds access"));
+	diagnostic_emit_diagram (global_dc, diagram);
+      }
+  }
+
+  text_art::canvas
+  make_access_diagram (const access_operation &op,
+		       text_art::style_manager &sm,
+		       const text_art::theme &theme,
+		       logger *logger) const
+  {
+    access_diagram d (op, m_region_creation_event_id, sm, theme, logger);
+    return d.to_canvas (sm);
+  }
+
+  region_model m_model;
   const region *m_reg;
   tree m_diag_arg;
+  const svalue *m_sval_hint;
+  diagnostic_event_id_t m_region_creation_event_id;
 };
 
 /* Abstract base class for all out-of-bounds warnings where the
@@ -122,9 +203,11 @@  protected:
 class concrete_out_of_bounds : public out_of_bounds
 {
 public:
-  concrete_out_of_bounds (const region *reg, tree diag_arg,
-			  byte_range out_of_bounds_range)
-  : out_of_bounds (reg, diag_arg),
+  concrete_out_of_bounds (const region_model &model,
+			  const region *reg, tree diag_arg,
+			  byte_range out_of_bounds_range,
+			  const svalue *sval_hint)
+  : out_of_bounds (model, reg, diag_arg, sval_hint),
     m_out_of_bounds_range (out_of_bounds_range)
   {}
 
@@ -146,9 +229,12 @@  protected:
 class concrete_past_the_end : public concrete_out_of_bounds
 {
 public:
-  concrete_past_the_end (const region *reg, tree diag_arg, byte_range range,
-			 tree byte_bound)
-  : concrete_out_of_bounds (reg, diag_arg, range), m_byte_bound (byte_bound)
+  concrete_past_the_end (const region_model &model,
+			 const region *reg, tree diag_arg, byte_range range,
+			 tree byte_bound,
+			 const svalue *sval_hint)
+  : concrete_out_of_bounds (model, reg, diag_arg, range, sval_hint),
+    m_byte_bound (byte_bound)
   {}
 
   bool
@@ -168,7 +254,9 @@  public:
   {
     if (m_byte_bound && TREE_CODE (m_byte_bound) == INTEGER_CST)
       emission_path.add_event
-	(make_unique<region_creation_event_capacity> (m_byte_bound, loc_info));
+	(make_unique<oob_region_creation_event_capacity> (m_byte_bound,
+							  loc_info,
+							  *this));
   }
 
 protected:
@@ -180,9 +268,11 @@  protected:
 class concrete_buffer_overflow : public concrete_past_the_end
 {
 public:
-  concrete_buffer_overflow (const region *reg, tree diag_arg,
-		   byte_range range, tree byte_bound)
-  : concrete_past_the_end (reg, diag_arg, range, byte_bound)
+  concrete_buffer_overflow (const region_model &model,
+			    const region *reg, tree diag_arg,
+			    byte_range range, tree byte_bound,
+			    const svalue *sval_hint)
+  : concrete_past_the_end (model, reg, diag_arg, range, byte_bound, sval_hint)
   {}
 
   const char *get_kind () const final override
@@ -190,7 +280,8 @@  public:
     return "concrete_buffer_overflow";
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc,
+	     logger *logger) final override
   {
     diagnostic_metadata m;
     bool warned;
@@ -238,7 +329,7 @@  public:
 		  "write to beyond the end of %qE",
 		  m_diag_arg);
 
-	maybe_describe_array_bounds (rich_loc->get_loc ());
+	maybe_show_notes (rich_loc->get_loc (), logger);
       }
 
     return warned;
@@ -276,6 +367,8 @@  public:
 				   start_buf, end_buf, m_byte_bound);
       }
   }
+
+  enum access_direction get_dir () const final override { return DIR_WRITE; }
 };
 
 /* Concrete subclass to complain about buffer over-reads.  */
@@ -283,9 +376,10 @@  public:
 class concrete_buffer_over_read : public concrete_past_the_end
 {
 public:
-  concrete_buffer_over_read (const region *reg, tree diag_arg,
+  concrete_buffer_over_read (const region_model &model,
+			     const region *reg, tree diag_arg,
 			     byte_range range, tree byte_bound)
-  : concrete_past_the_end (reg, diag_arg, range, byte_bound)
+  : concrete_past_the_end (model, reg, diag_arg, range, byte_bound, NULL)
   {}
 
   const char *get_kind () const final override
@@ -293,7 +387,7 @@  public:
     return "concrete_buffer_over_read";
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *logger) final override
   {
     diagnostic_metadata m;
     bool warned;
@@ -339,7 +433,7 @@  public:
 		  "read from after the end of %qE",
 		  m_diag_arg);
 
-	maybe_describe_array_bounds (rich_loc->get_loc ());
+	maybe_show_notes (rich_loc->get_loc (), logger);
       }
 
     return warned;
@@ -377,6 +471,8 @@  public:
 				   start_buf, end_buf, m_byte_bound);
       }
   }
+
+  enum access_direction get_dir () const final override { return DIR_READ; }
 };
 
 /* Concrete subclass to complain about buffer underwrites.  */
@@ -384,9 +480,11 @@  public:
 class concrete_buffer_underwrite : public concrete_out_of_bounds
 {
 public:
-  concrete_buffer_underwrite (const region *reg, tree diag_arg,
-			      byte_range range)
-  : concrete_out_of_bounds (reg, diag_arg, range)
+  concrete_buffer_underwrite (const region_model &model,
+			      const region *reg, tree diag_arg,
+			      byte_range range,
+			      const svalue *sval_hint)
+  : concrete_out_of_bounds (model, reg, diag_arg, range, sval_hint)
   {}
 
   const char *get_kind () const final override
@@ -394,7 +492,7 @@  public:
     return "concrete_buffer_underwrite";
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *logger) final override
   {
     diagnostic_metadata m;
     bool warned;
@@ -415,7 +513,7 @@  public:
 	break;
       }
     if (warned)
-      maybe_describe_array_bounds (rich_loc->get_loc ());
+      maybe_show_notes (rich_loc->get_loc (), logger);
     return warned;
   }
 
@@ -449,6 +547,8 @@  public:
 				   start_buf, end_buf);;
       }
   }
+
+  enum access_direction get_dir () const final override { return DIR_WRITE; }
 };
 
 /* Concrete subclass to complain about buffer under-reads.  */
@@ -456,9 +556,10 @@  public:
 class concrete_buffer_under_read : public concrete_out_of_bounds
 {
 public:
-  concrete_buffer_under_read (const region *reg, tree diag_arg,
+  concrete_buffer_under_read (const region_model &model,
+			      const region *reg, tree diag_arg,
 			      byte_range range)
-  : concrete_out_of_bounds (reg, diag_arg, range)
+  : concrete_out_of_bounds (model, reg, diag_arg, range, NULL)
   {}
 
   const char *get_kind () const final override
@@ -466,7 +567,7 @@  public:
     return "concrete_buffer_under_read";
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *logger) final override
   {
     diagnostic_metadata m;
     bool warned;
@@ -487,7 +588,7 @@  public:
 	break;
       }
     if (warned)
-      maybe_describe_array_bounds (rich_loc->get_loc ());
+      maybe_show_notes (rich_loc->get_loc (), logger);
     return warned;
   }
 
@@ -521,6 +622,8 @@  public:
 				   start_buf, end_buf);;
       }
   }
+
+  enum access_direction get_dir () const final override { return DIR_READ; }
 };
 
 /* Abstract class to complain about out-of-bounds read/writes where
@@ -529,9 +632,11 @@  public:
 class symbolic_past_the_end : public out_of_bounds
 {
 public:
-  symbolic_past_the_end (const region *reg, tree diag_arg, tree offset,
-			 tree num_bytes, tree capacity)
-  : out_of_bounds (reg, diag_arg),
+  symbolic_past_the_end (const region_model &model,
+			 const region *reg, tree diag_arg, tree offset,
+			 tree num_bytes, tree capacity,
+			 const svalue *sval_hint)
+  : out_of_bounds (model, reg, diag_arg, sval_hint),
     m_offset (offset),
     m_num_bytes (num_bytes),
     m_capacity (capacity)
@@ -559,9 +664,12 @@  protected:
 class symbolic_buffer_overflow : public symbolic_past_the_end
 {
 public:
-  symbolic_buffer_overflow (const region *reg, tree diag_arg, tree offset,
-			    tree num_bytes, tree capacity)
-  : symbolic_past_the_end (reg, diag_arg, offset, num_bytes, capacity)
+  symbolic_buffer_overflow (const region_model &model,
+			    const region *reg, tree diag_arg, tree offset,
+			    tree num_bytes, tree capacity,
+			    const svalue *sval_hint)
+  : symbolic_past_the_end (model, reg, diag_arg, offset, num_bytes, capacity,
+			   sval_hint)
   {
   }
 
@@ -570,24 +678,31 @@  public:
     return "symbolic_buffer_overflow";
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *logger) final override
   {
     diagnostic_metadata m;
+    bool warned;
     switch (get_memory_space ())
       {
       default:
 	m.add_cwe (787);
-	return warning_meta (rich_loc, m, get_controlling_option (),
-			     "buffer overflow");
+	warned = warning_meta (rich_loc, m, get_controlling_option (),
+			       "buffer overflow");
+	break;
       case MEMSPACE_STACK:
 	m.add_cwe (121);
-	return warning_meta (rich_loc, m, get_controlling_option (),
-			     "stack-based buffer overflow");
+	warned = warning_meta (rich_loc, m, get_controlling_option (),
+			       "stack-based buffer overflow");
+	break;
       case MEMSPACE_HEAP:
 	m.add_cwe (122);
-	return warning_meta (rich_loc, m, get_controlling_option (),
-			     "heap-based buffer overflow");
+	warned =  warning_meta (rich_loc, m, get_controlling_option (),
+				"heap-based buffer overflow");
+	break;
       }
+    if (warned)
+      maybe_show_notes (rich_loc->get_loc (), logger);
+    return warned;
   }
 
   label_text
@@ -658,6 +773,8 @@  public:
 				 m_diag_arg);
     return ev.formatted_print ("out-of-bounds write");
   }
+
+  enum access_direction get_dir () const final override { return DIR_WRITE; }
 };
 
 /* Concrete subclass to complain about over-reads with symbolic values.  */
@@ -665,9 +782,11 @@  public:
 class symbolic_buffer_over_read : public symbolic_past_the_end
 {
 public:
-  symbolic_buffer_over_read (const region *reg, tree diag_arg, tree offset,
+  symbolic_buffer_over_read (const region_model &model,
+			     const region *reg, tree diag_arg, tree offset,
 			     tree num_bytes, tree capacity)
-  : symbolic_past_the_end (reg, diag_arg, offset, num_bytes, capacity)
+  : symbolic_past_the_end (model, reg, diag_arg, offset, num_bytes, capacity,
+			   NULL)
   {
   }
 
@@ -676,25 +795,32 @@  public:
     return "symbolic_buffer_over_read";
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *logger) final override
   {
     diagnostic_metadata m;
     m.add_cwe (126);
+    bool warned;
     switch (get_memory_space ())
       {
       default:
 	m.add_cwe (787);
-	return warning_meta (rich_loc, m, get_controlling_option (),
-			     "buffer over-read");
+	warned = warning_meta (rich_loc, m, get_controlling_option (),
+			       "buffer over-read");
+	break;
       case MEMSPACE_STACK:
 	m.add_cwe (121);
-	return warning_meta (rich_loc, m, get_controlling_option (),
-			     "stack-based buffer over-read");
+	warned = warning_meta (rich_loc, m, get_controlling_option (),
+			       "stack-based buffer over-read");
+	break;
       case MEMSPACE_HEAP:
 	m.add_cwe (122);
-	return warning_meta (rich_loc, m, get_controlling_option (),
-			     "heap-based buffer over-read");
+	warned = warning_meta (rich_loc, m, get_controlling_option (),
+			       "heap-based buffer over-read");
+	break;
       }
+    if (warned)
+      maybe_show_notes (rich_loc->get_loc (), logger);
+    return warned;
   }
 
   label_text
@@ -765,6 +891,8 @@  public:
 				 m_diag_arg);
     return ev.formatted_print ("out-of-bounds read");
   }
+
+  enum access_direction get_dir () const final override { return DIR_READ; }
 };
 
 /* Check whether an access is past the end of the BASE_REG.  */
@@ -775,6 +903,7 @@  region_model::check_symbolic_bounds (const region *base_reg,
 				     const svalue *num_bytes_sval,
 				     const svalue *capacity,
 				     enum access_direction dir,
+				     const svalue *sval_hint,
 				     region_model_context *ctxt) const
 {
   gcc_assert (ctxt);
@@ -789,24 +918,34 @@  region_model::check_symbolic_bounds (const region *base_reg,
       tree offset_tree = get_representative_tree (sym_byte_offset);
       tree num_bytes_tree = get_representative_tree (num_bytes_sval);
       tree capacity_tree = get_representative_tree (capacity);
+      const region *offset_reg = m_mgr->get_offset_region (base_reg,
+							   NULL_TREE,
+							   sym_byte_offset);
+      const region *sized_offset_reg = m_mgr->get_sized_region (offset_reg,
+								NULL_TREE,
+								num_bytes_sval);
       switch (dir)
 	{
 	default:
 	  gcc_unreachable ();
 	  break;
 	case DIR_READ:
-	  ctxt->warn (make_unique<symbolic_buffer_over_read> (base_reg,
+	  gcc_assert (sval_hint == nullptr);
+	  ctxt->warn (make_unique<symbolic_buffer_over_read> (*this,
+							      sized_offset_reg,
 							      diag_arg,
 							      offset_tree,
 							      num_bytes_tree,
 							      capacity_tree));
 	  break;
 	case DIR_WRITE:
-	  ctxt->warn (make_unique<symbolic_buffer_overflow> (base_reg,
+	  ctxt->warn (make_unique<symbolic_buffer_overflow> (*this,
+							     sized_offset_reg,
 							     diag_arg,
 							     offset_tree,
 							     num_bytes_tree,
-							     capacity_tree));
+							     capacity_tree,
+							     sval_hint));
 	  break;
 	}
     }
@@ -827,6 +966,7 @@  maybe_get_integer_cst_tree (const svalue *sval)
 void
 region_model::check_region_bounds (const region *reg,
 				   enum access_direction dir,
+				   const svalue *sval_hint,
 				   region_model_context *ctxt) const
 {
   gcc_assert (ctxt);
@@ -878,7 +1018,7 @@  region_model::check_region_bounds (const region *reg,
       else
 	byte_offset_sval = reg_offset.get_symbolic_byte_offset ();
       check_symbolic_bounds (base_reg, byte_offset_sval, num_bytes_sval,
-			     capacity, dir, ctxt);
+			     capacity, dir, sval_hint, ctxt);
       return;
     }
 
@@ -897,12 +1037,16 @@  region_model::check_region_bounds (const region *reg,
 	  gcc_unreachable ();
 	  break;
 	case DIR_READ:
-	  ctxt->warn (make_unique<concrete_buffer_under_read> (reg, diag_arg,
+	  gcc_assert (sval_hint == nullptr);
+	  ctxt->warn (make_unique<concrete_buffer_under_read> (*this, reg,
+							       diag_arg,
 							       out));
 	  break;
 	case DIR_WRITE:
-	  ctxt->warn (make_unique<concrete_buffer_underwrite> (reg, diag_arg,
-							       out));
+	  ctxt->warn (make_unique<concrete_buffer_underwrite> (*this,
+							       reg, diag_arg,
+							       out,
+							       sval_hint));
 	  break;
 	}
     }
@@ -927,12 +1071,16 @@  region_model::check_region_bounds (const region *reg,
 	  gcc_unreachable ();
 	  break;
 	case DIR_READ:
-	  ctxt->warn (make_unique<concrete_buffer_over_read> (reg, diag_arg,
+	  gcc_assert (sval_hint == nullptr);
+	  ctxt->warn (make_unique<concrete_buffer_over_read> (*this,
+							      reg, diag_arg,
 							      out, byte_bound));
 	  break;
 	case DIR_WRITE:
-	  ctxt->warn (make_unique<concrete_buffer_overflow> (reg, diag_arg,
-							     out, byte_bound));
+	  ctxt->warn (make_unique<concrete_buffer_overflow> (*this,
+							     reg, diag_arg,
+							     out, byte_bound,
+							     sval_hint));
 	  break;
 	}
     }
diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
index 0a447f7ba26..cfca305d552 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -1421,7 +1421,7 @@  diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
 
   auto_diagnostic_group d;
   auto_cfun sentinel (sd.m_snode->m_fun);
-  if (sd.m_d->emit (&rich_loc))
+  if (sd.m_d->emit (&rich_loc, get_logger ()))
     {
       sd.emit_any_notes ();
 
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index a5965c2b8ff..61685f43fba 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -1771,7 +1771,7 @@  public:
     return OPT_Wanalyzer_stale_setjmp_buffer;
   }
 
-  bool emit (rich_location *richloc) final override
+  bool emit (rich_location *richloc, logger *) final override
   {
     return warning_at
       (richloc, get_controlling_option (),
@@ -3925,7 +3925,7 @@  public:
     return OPT_Wanalyzer_jump_through_null;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     return warning_at (rich_loc, get_controlling_option (),
 		       "jump through null pointer");
diff --git a/gcc/analyzer/infinite-recursion.cc b/gcc/analyzer/infinite-recursion.cc
index c262e391953..3ba316ee964 100644
--- a/gcc/analyzer/infinite-recursion.cc
+++ b/gcc/analyzer/infinite-recursion.cc
@@ -95,7 +95,7 @@  public:
     return OPT_Wanalyzer_infinite_recursion;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     /* "CWE-674: Uncontrolled Recursion".  */
     diagnostic_metadata m;
diff --git a/gcc/analyzer/kf-analyzer.cc b/gcc/analyzer/kf-analyzer.cc
index 32aa87bfbdc..5aed007d6ea 100644
--- a/gcc/analyzer/kf-analyzer.cc
+++ b/gcc/analyzer/kf-analyzer.cc
@@ -255,7 +255,7 @@  public:
     return 0;
   }
 
-  bool emit (rich_location *richloc) final override
+  bool emit (rich_location *richloc, logger *) final override
   {
     inform (richloc, "path");
     return true;
diff --git a/gcc/analyzer/kf.cc b/gcc/analyzer/kf.cc
index 93c46630f36..34ff1cb8496 100644
--- a/gcc/analyzer/kf.cc
+++ b/gcc/analyzer/kf.cc
@@ -324,7 +324,9 @@  kf_memset::impl_call_pre (const call_details &cd) const
   const region *sized_dest_reg = mgr->get_sized_region (dest_reg,
 							NULL_TREE,
 							num_bytes_sval);
-  model->check_region_for_write (sized_dest_reg, cd.get_ctxt ());
+  model->check_region_for_write (sized_dest_reg,
+				 nullptr,
+				 cd.get_ctxt ());
   model->fill_region (sized_dest_reg, fill_value_u8);
 }
 
@@ -358,7 +360,7 @@  public:
     return OPT_Wanalyzer_putenv_of_auto_var;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     auto_diagnostic_group d;
     diagnostic_metadata m;
diff --git a/gcc/analyzer/pending-diagnostic.h b/gcc/analyzer/pending-diagnostic.h
index 6423c8be4ea..7582b37efe7 100644
--- a/gcc/analyzer/pending-diagnostic.h
+++ b/gcc/analyzer/pending-diagnostic.h
@@ -180,7 +180,7 @@  class pending_diagnostic
   /* Vfunc for emitting the diagnostic.  The rich_location will have been
      populated with a diagnostic_path.
      Return true if a diagnostic is actually emitted.  */
-  virtual bool emit (rich_location *) = 0;
+  virtual bool emit (rich_location *, logger *) = 0;
 
   /* Hand-coded RTTI: get an ID for the subclass.  */
   virtual const char *get_kind () const = 0;
diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc
index 3b95e432aba..1453acf7bc9 100644
--- a/gcc/analyzer/region-model-manager.cc
+++ b/gcc/analyzer/region-model-manager.cc
@@ -230,10 +230,11 @@  region_model_manager::get_or_create_constant_svalue (tree cst_expr)
    for VAL of type TYPE, creating it if necessary.  */
 
 const svalue *
-region_model_manager::get_or_create_int_cst (tree type, poly_int64 val)
+region_model_manager::get_or_create_int_cst (tree type,
+					     const poly_wide_int_ref &cst)
 {
   gcc_assert (type);
-  tree tree_cst = build_int_cst (type, val);
+  tree tree_cst = wide_int_to_tree (type, cst);
   return get_or_create_constant_svalue (tree_cst);
 }
 
@@ -612,7 +613,7 @@  region_model_manager::maybe_fold_binop (tree type, enum tree_code op,
 	  return get_or_create_constant_svalue (result);
     }
 
-  if (FLOAT_TYPE_P (type)
+  if ((type && FLOAT_TYPE_P (type))
       || (arg0->get_type () && FLOAT_TYPE_P (arg0->get_type ()))
       || (arg1->get_type () && FLOAT_TYPE_P (arg1->get_type ())))
     return NULL;
@@ -634,6 +635,11 @@  region_model_manager::maybe_fold_binop (tree type, enum tree_code op,
       /* (0 - VAL) -> -VAL.  */
       if (cst0 && zerop (cst0))
 	return get_or_create_unaryop (type, NEGATE_EXPR, arg1);
+      /* (X + Y) - X -> Y.  */
+      if (const binop_svalue *binop = arg0->dyn_cast_binop_svalue ())
+	if (binop->get_op () == PLUS_EXPR)
+	  if (binop->get_arg0 () == arg1)
+	    return get_or_create_cast (type, binop->get_arg1 ());
       break;
     case MULT_EXPR:
       /* (VAL * 0).  */
@@ -726,10 +732,7 @@  region_model_manager::maybe_fold_binop (tree type, enum tree_code op,
   if (cst1 && associative_tree_code (op))
     if (const binop_svalue *binop = arg0->dyn_cast_binop_svalue ())
       if (binop->get_op () == op
-	  && binop->get_arg1 ()->maybe_get_constant ()
-	  && type == binop->get_type ()
-	  && type == binop->get_arg0 ()->get_type ()
-	  && type == binop->get_arg1 ()->get_type ())
+	  && binop->get_arg1 ()->maybe_get_constant ())
 	return get_or_create_binop
 	  (type, op, binop->get_arg0 (),
 	   get_or_create_binop (type, op,
@@ -748,6 +751,21 @@  region_model_manager::maybe_fold_binop (tree type, enum tree_code op,
 	     get_or_create_binop (size_type_node, op,
 				  binop->get_arg1 (), arg1));
 
+  /* Distribute multiplication by a constant through addition/subtraction:
+     (X + Y) * CST => (X * CST) + (Y * CST).  */
+  if (cst1 && op == MULT_EXPR)
+    if (const binop_svalue *binop = arg0->dyn_cast_binop_svalue ())
+      if (binop->get_op () == PLUS_EXPR
+	  || binop->get_op () == MINUS_EXPR)
+	{
+	  return get_or_create_binop
+	    (type, binop->get_op (),
+	     get_or_create_binop (type, op,
+				  binop->get_arg0 (), arg1),
+	     get_or_create_binop (type, op,
+				  binop->get_arg1 (), arg1));
+	}
+
   /* etc.  */
 
   return NULL;
diff --git a/gcc/analyzer/region-model-manager.h b/gcc/analyzer/region-model-manager.h
index 273fe7b32b6..3340c3ebd1e 100644
--- a/gcc/analyzer/region-model-manager.h
+++ b/gcc/analyzer/region-model-manager.h
@@ -42,7 +42,7 @@  public:
 
   /* svalue consolidation.  */
   const svalue *get_or_create_constant_svalue (tree cst_expr);
-  const svalue *get_or_create_int_cst (tree type, poly_int64);
+  const svalue *get_or_create_int_cst (tree type, const poly_wide_int_ref &cst);
   const svalue *get_or_create_null_ptr (tree pointer_type);
   const svalue *get_or_create_unknown_svalue (tree type);
   const svalue *get_or_create_setjmp_svalue (const setjmp_record &r,
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 3bb3df2f063..d983822708f 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -507,7 +507,7 @@  public:
 
   bool terminate_path_p () const final override { return true; }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     switch (m_pkind)
       {
@@ -638,7 +638,7 @@  public:
     return OPT_Wanalyzer_shift_count_negative;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     return warning_at (rich_loc, get_controlling_option (),
 		       "shift by negative count (%qE)", m_count_cst);
@@ -685,7 +685,7 @@  public:
     return OPT_Wanalyzer_shift_count_overflow;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     return warning_at (rich_loc, get_controlling_option (),
 		       "shift by count (%qE) >= precision of type (%qi)",
@@ -1736,7 +1736,7 @@  check_external_function_for_access_attr (const gcall *call,
 	  tree ptr_tree = gimple_call_arg (call, access->ptrarg);
 	  const svalue *ptr_sval = get_rvalue (ptr_tree, &my_ctxt);
 	  const region *reg = deref_rvalue (ptr_sval, ptr_tree, &my_ctxt);
-	  check_region_for_write (reg, &my_ctxt);
+	  check_region_for_write (reg, nullptr, &my_ctxt);
 	  /* We don't use the size arg for now.  */
 	}
     }
@@ -2544,8 +2544,8 @@  region_model::deref_rvalue (const svalue *ptr_sval, tree ptr_tree,
 		const poisoned_svalue *poisoned_sval
 		  = as_a <const poisoned_svalue *> (ptr_sval);
 		enum poison_kind pkind = poisoned_sval->get_poison_kind ();
-		ctxt->warn (make_unique<poisoned_value_diagnostic>
-			      (ptr, pkind, NULL, NULL));
+		ctxt->warn (::make_unique<poisoned_value_diagnostic>
+			      (ptr, pkind, nullptr, nullptr));
 	      }
 	  }
       }
@@ -2598,7 +2598,7 @@  public:
     return OPT_Wanalyzer_write_to_const;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     auto_diagnostic_group d;
     bool warned;
@@ -2666,7 +2666,7 @@  public:
     return OPT_Wanalyzer_write_to_string_literal;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     return warning_at (rich_loc, get_controlling_option (),
 		       "write to string literal");
@@ -2764,6 +2764,15 @@  region_model::get_capacity (const region *reg) const
       /* Look through sized regions to get at the capacity
 	 of the underlying regions.  */
       return get_capacity (reg->get_parent_region ());
+    case RK_STRING:
+      {
+	/* "Capacity" here means "size".  */
+	const string_region *string_reg = as_a <const string_region *> (reg);
+	tree string_cst = string_reg->get_string_cst ();
+	return m_mgr->get_or_create_int_cst (size_type_node,
+					     TREE_STRING_LENGTH (string_cst));
+      }
+      break;
     }
 
   if (const svalue *recorded = get_dynamic_extents (reg))
@@ -2802,11 +2811,14 @@  region_model::get_string_size (const region *reg) const
 }
 
 /* If CTXT is non-NULL, use it to warn about any problems accessing REG,
-   using DIR to determine if this access is a read or write.  */
+   using DIR to determine if this access is a read or write.
+   If SVAL_HINT is non-NULL, use it as a hint in diagnostics
+   about the value that would be written to REG.  */
 
 void
 region_model::check_region_access (const region *reg,
 				   enum access_direction dir,
+				   const svalue *sval_hint,
 				   region_model_context *ctxt) const
 {
   /* Fail gracefully if CTXT is NULL.  */
@@ -2814,7 +2826,7 @@  region_model::check_region_access (const region *reg,
     return;
 
   check_region_for_taint (reg, dir, ctxt);
-  check_region_bounds (reg, dir, ctxt);
+  check_region_bounds (reg, dir, sval_hint, ctxt);
 
   switch (dir)
     {
@@ -2833,9 +2845,10 @@  region_model::check_region_access (const region *reg,
 
 void
 region_model::check_region_for_write (const region *dest_reg,
+				      const svalue *sval_hint,
 				      region_model_context *ctxt) const
 {
-  check_region_access (dest_reg, DIR_WRITE, ctxt);
+  check_region_access (dest_reg, DIR_WRITE, sval_hint, ctxt);
 }
 
 /* If CTXT is non-NULL, use it to warn about any problems reading from REG.  */
@@ -2844,7 +2857,7 @@  void
 region_model::check_region_for_read (const region *src_reg,
 				     region_model_context *ctxt) const
 {
-  check_region_access (src_reg, DIR_READ, ctxt);
+  check_region_access (src_reg, DIR_READ, NULL, ctxt);
 }
 
 /* Concrete subclass for casts of pointers that lead to trailing bytes.  */
@@ -2880,7 +2893,7 @@  public:
     return OPT_Wanalyzer_allocation_size;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     diagnostic_metadata m;
     m.add_cwe (131);
@@ -3240,7 +3253,7 @@  region_model::set_value (const region *lhs_reg, const svalue *rhs_sval,
 
   check_region_size (lhs_reg, rhs_sval, ctxt);
 
-  check_region_for_write (lhs_reg, ctxt);
+  check_region_for_write (lhs_reg, rhs_sval, ctxt);
 
   m_store.set_value (m_mgr->get_store_manager(), lhs_reg, rhs_sval,
 		     ctxt ? ctxt->get_uncertainty () : NULL);
@@ -3873,7 +3886,12 @@  region_model::get_representative_path_var_1 (const svalue *sval,
 
   /* Prevent infinite recursion.  */
   if (visited->contains (sval))
-    return path_var (NULL_TREE, 0);
+    {
+      if (sval->get_kind () == SK_CONSTANT)
+	return path_var (sval->maybe_get_constant (), 0);
+      else
+	return path_var (NULL_TREE, 0);
+    }
   visited->add (sval);
 
   /* Handle casts by recursion into get_representative_path_var.  */
@@ -4978,7 +4996,7 @@  public:
     return same_tree_p (m_arg, ((const float_as_size_arg &) other).m_arg);
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     diagnostic_metadata m;
     bool warned = warning_meta (rich_loc, m, get_controlling_option (),
@@ -5340,7 +5358,7 @@  public:
     return OPT_Wanalyzer_exposure_through_uninit_copy;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     diagnostic_metadata m;
     /* CWE-200: Exposure of Sensitive Information to an Unauthorized Actor.  */
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index fe3db0b0c98..67bdb2a4dd1 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -490,6 +490,7 @@  class region_model
 				  region_model_context *ctxt) const;
 
   void check_region_for_write (const region *dest_reg,
+			       const svalue *sval_hint,
 			       region_model_context *ctxt) const;
 
 private:
@@ -555,6 +556,7 @@  private:
 				  region_model_context *ctxt) const;
   void check_region_access (const region *reg,
 			    enum access_direction dir,
+			    const svalue *sval_hint,
 			    region_model_context *ctxt) const;
   void check_region_for_read (const region *src_reg,
 			      region_model_context *ctxt) const;
@@ -567,8 +569,10 @@  private:
 			      const svalue *num_bytes_sval,
 			      const svalue *capacity,
 			      enum access_direction dir,
+			      const svalue *sval_hint,
 			      region_model_context *ctxt) const;
   void check_region_bounds (const region *reg, enum access_direction dir,
+			    const svalue *sval_hint,
 			    region_model_context *ctxt) const;
 
   void check_call_args (const call_details &cd) const;
diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc
index 8f0eb569b33..f83e286cade 100644
--- a/gcc/analyzer/region.cc
+++ b/gcc/analyzer/region.cc
@@ -63,6 +63,332 @@  along with GCC; see the file COPYING3.  If not see
 
 namespace ana {
 
+region_offset
+region_offset::make_byte_offset (const region *base_region,
+				 const svalue *num_bytes_sval)
+{
+  if (tree num_bytes_cst = num_bytes_sval->maybe_get_constant ())
+    {
+      gcc_assert (TREE_CODE (num_bytes_cst) == INTEGER_CST);
+      bit_offset_t num_bits = wi::to_offset (num_bytes_cst) * BITS_PER_UNIT;
+      return make_concrete (base_region, num_bits);
+    }
+  else
+    {
+      return make_symbolic (base_region, num_bytes_sval);
+    }
+}
+
+tree
+region_offset::calc_symbolic_bit_offset (const region_model &model) const
+{
+  if (symbolic_p ())
+    {
+      tree num_bytes_expr = model.get_representative_tree (m_sym_offset);
+      if (!num_bytes_expr)
+	return NULL_TREE;
+      tree bytes_to_bits_scale = build_int_cst (size_type_node, BITS_PER_UNIT);
+      return fold_build2 (MULT_EXPR, size_type_node,
+			  num_bytes_expr, bytes_to_bits_scale);
+    }
+  else
+    {
+      tree cst = wide_int_to_tree (size_type_node, m_offset);
+      return cst;
+    }
+}
+
+const svalue *
+region_offset::calc_symbolic_byte_offset (region_model_manager *mgr) const
+{
+  if (symbolic_p ())
+    return m_sym_offset;
+  else
+    {
+      byte_offset_t concrete_byte_offset;
+      if (get_concrete_byte_offset (&concrete_byte_offset))
+	return mgr->get_or_create_int_cst (size_type_node,
+					   concrete_byte_offset);
+      else
+	/* Can't handle bitfields; return UNKNOWN.  */
+	return mgr->get_or_create_unknown_svalue (size_type_node);
+    }
+}
+
+void
+region_offset::dump_to_pp (pretty_printer *pp, bool simple) const
+{
+  if (symbolic_p ())
+    {
+      /* We don't bother showing the base region.  */
+      pp_string (pp, "byte ");
+      m_sym_offset->dump_to_pp (pp, simple);
+    }
+  else
+    {
+      if (m_offset % BITS_PER_UNIT == 0)
+	{
+	  pp_string (pp, "byte ");
+	  pp_wide_int (pp, m_offset / BITS_PER_UNIT, SIGNED);
+	}
+      else
+	{
+	  pp_string (pp, "bit ");
+	  pp_wide_int (pp, m_offset, SIGNED);
+	}
+    }
+}
+
+DEBUG_FUNCTION void
+region_offset::dump (bool simple) const
+{
+  pretty_printer pp;
+  pp_format_decoder (&pp) = default_tree_printer;
+  pp_show_color (&pp) = pp_show_color (global_dc->printer);
+  pp.buffer->stream = stderr;
+  dump_to_pp (&pp, simple);
+  pp_newline (&pp);
+  pp_flush (&pp);
+}
+
+/* An svalue that matches the pattern (BASE * FACTOR) + OFFSET
+   where FACTOR or OFFSET could be the identity (represented as NULL).  */
+
+struct linear_op
+{
+  linear_op (const svalue *base,
+	     const svalue *factor,
+	     const svalue *offset)
+  : m_base (base), m_factor (factor), m_offset (offset)
+  {
+  }
+
+  bool maybe_get_cst_factor (bit_offset_t *out) const
+  {
+    if (m_factor == nullptr)
+      {
+	*out = 1;
+	return true;
+      }
+    if (tree cst_factor = m_factor->maybe_get_constant ())
+      {
+	*out = wi::to_offset (cst_factor);
+	return true;
+      }
+    return false;
+  }
+
+  bool maybe_get_cst_offset (bit_offset_t *out) const
+  {
+    if (m_offset == nullptr)
+      {
+	*out = 0;
+	return true;
+      }
+    if (tree cst_offset = m_offset->maybe_get_constant ())
+      {
+	*out = wi::to_offset (cst_offset);
+	return true;
+      }
+    return false;
+  }
+
+  static tristate
+  less (const linear_op &a, const linear_op &b)
+  {
+    /* Same base.  */
+    if (a.m_base == b.m_base)
+      {
+	bit_offset_t a_wi_factor;
+	bit_offset_t b_wi_factor;
+	if (a.maybe_get_cst_factor (&a_wi_factor)
+	    && b.maybe_get_cst_factor (&b_wi_factor))
+	  {
+	    if (a_wi_factor != b_wi_factor)
+	      return tristate (a_wi_factor < b_wi_factor);
+	    else
+	      {
+		bit_offset_t a_wi_offset;
+		bit_offset_t b_wi_offset;
+		if (a.maybe_get_cst_offset (&a_wi_offset)
+		    && b.maybe_get_cst_offset (&b_wi_offset))
+		  return tristate (a_wi_offset < b_wi_offset);
+	      }
+	  }
+      }
+    return tristate::unknown ();
+  }
+
+  static tristate
+  le (const linear_op &a, const linear_op &b)
+  {
+    /* Same base.  */
+    if (a.m_base == b.m_base)
+      {
+	bit_offset_t a_wi_factor;
+	bit_offset_t b_wi_factor;
+	if (a.maybe_get_cst_factor (&a_wi_factor)
+	    && b.maybe_get_cst_factor (&b_wi_factor))
+	  {
+	    if (a_wi_factor != b_wi_factor)
+	      return tristate (a_wi_factor <= b_wi_factor);
+	    else
+	      {
+		bit_offset_t a_wi_offset;
+		bit_offset_t b_wi_offset;
+		if (a.maybe_get_cst_offset (&a_wi_offset)
+		    && b.maybe_get_cst_offset (&b_wi_offset))
+		  return tristate (a_wi_offset <= b_wi_offset);
+	      }
+	  }
+      }
+    return tristate::unknown ();
+  }
+
+  static bool
+  from_svalue (const svalue &sval, linear_op *out)
+  {
+    switch (sval.get_kind ())
+      {
+      default:
+	break;
+      case SK_BINOP:
+	{
+	  const binop_svalue &binop_sval ((const binop_svalue &)sval);
+	  if (binop_sval.get_op () == MULT_EXPR)
+	    {
+	      *out = linear_op (binop_sval.get_arg0 (),
+				binop_sval.get_arg1 (),
+				NULL);
+	      return true;
+	    }
+	  else if (binop_sval.get_op () == PLUS_EXPR)
+	    {
+	      if (binop_sval.get_arg0 ()->get_kind () == SK_BINOP)
+		{
+		  const binop_svalue &inner_binop_sval
+		    ((const binop_svalue &)*binop_sval.get_arg0 ());
+		  if (inner_binop_sval.get_op () == MULT_EXPR)
+		    {
+		      *out = linear_op (inner_binop_sval.get_arg0 (),
+					inner_binop_sval.get_arg1 (),
+					binop_sval.get_arg1 ());
+		      return true;
+		    }
+		}
+
+	      *out = linear_op (binop_sval.get_arg0 (),
+				NULL,
+				binop_sval.get_arg1 ());
+	      return true;
+	    }
+	}
+	break;
+      }
+    return false;
+  }
+
+  const svalue *m_base;
+  const svalue *m_factor;
+  const svalue *m_offset;
+};
+
+bool
+operator< (const region_offset &a, const region_offset &b)
+{
+  if (a.symbolic_p ())
+    {
+      if (b.symbolic_p ())
+	{
+	  /* Symbolic vs symbolic.  */
+	  const svalue &a_sval = *a.get_symbolic_byte_offset ();
+	  const svalue &b_sval = *b.get_symbolic_byte_offset ();
+
+	  linear_op op_a (NULL, NULL, NULL);
+	  linear_op op_b (NULL, NULL, NULL);
+	  if (linear_op::from_svalue (a_sval, &op_a)
+	      && linear_op::from_svalue (b_sval, &op_b))
+	    {
+	      tristate ts = linear_op::less (op_a, op_b);
+	      if (ts.is_true ())
+		return true;
+	      else if (ts.is_false ())
+		return false;
+	    }
+	  /* Use svalue's deterministic order, for now.  */
+	  return (svalue::cmp_ptr (a.get_symbolic_byte_offset (),
+				   b.get_symbolic_byte_offset ())
+		  < 0);
+	}
+      else
+	/* Symbolic vs concrete: put all symbolic after all concrete.  */
+	return false;
+    }
+  else
+    {
+      if (b.symbolic_p ())
+	/* Concrete vs symbolic: put all concrete before all symbolic.  */
+	return true;
+      else
+	/* Concrete vs concrete.  */
+	return a.get_bit_offset () < b.get_bit_offset ();
+    }
+}
+
+bool
+operator<= (const region_offset &a, const region_offset &b)
+{
+  if (a.symbolic_p ())
+    {
+      if (b.symbolic_p ())
+	{
+	  /* Symbolic vs symbolic.  */
+	  const svalue &a_sval = *a.get_symbolic_byte_offset ();
+	  const svalue &b_sval = *b.get_symbolic_byte_offset ();
+
+	  linear_op op_a (NULL, NULL, NULL);
+	  linear_op op_b (NULL, NULL, NULL);
+	  if (linear_op::from_svalue (a_sval, &op_a)
+	      && linear_op::from_svalue (b_sval, &op_b))
+	    {
+	      tristate ts = linear_op::le (op_a, op_b);
+	      if (ts.is_true ())
+		return true;
+	      else if (ts.is_false ())
+		return false;
+	    }
+	  /* Use svalue's deterministic order, for now.  */
+	  return (svalue::cmp_ptr (a.get_symbolic_byte_offset (),
+				   b.get_symbolic_byte_offset ())
+		  <= 0);
+	}
+      else
+	/* Symbolic vs concrete: put all symbolic after all concrete.  */
+	return false;
+    }
+  else
+    {
+      if (b.symbolic_p ())
+	/* Concrete vs symbolic: put all concrete before all symbolic.  */
+	return true;
+      else
+	/* Concrete vs concrete.  */
+	return a.get_bit_offset () <= b.get_bit_offset ();
+    }
+}
+
+bool
+operator> (const region_offset &a, const region_offset &b)
+{
+  return b < a;
+}
+
+bool
+operator>= (const region_offset &a, const region_offset &b)
+{
+  return b <= a;
+}
+
 /* class region and its various subclasses.  */
 
 /* class region.  */
@@ -294,6 +620,35 @@  region::get_offset (region_model_manager *mgr) const
   return *m_cached_offset;
 }
 
+/* Get the region_offset for immediately beyond this region.  */
+
+region_offset
+region::get_next_offset (region_model_manager *mgr) const
+{
+  region_offset start = get_offset (mgr);
+
+  bit_size_t bit_size;
+  if (get_bit_size (&bit_size))
+    {
+      if (start.concrete_p ())
+	{
+	  bit_offset_t next_bit_offset = start.get_bit_offset () + bit_size;
+	  return region_offset::make_concrete (start.get_base_region (),
+					       next_bit_offset);
+	}
+    }
+
+  const svalue *start_byte_offset_sval = start.calc_symbolic_byte_offset (mgr);
+  const svalue *byte_size_sval = get_byte_size_sval (mgr);
+  const svalue *sum_sval
+    = mgr->get_or_create_binop (size_type_node,
+				PLUS_EXPR,
+				start_byte_offset_sval,
+				byte_size_sval);
+  return region_offset::make_symbolic (start.get_base_region (),
+				       sum_sval);
+}
+
 /* Base class implementation of region::get_byte_size vfunc.
    If the size of this region (in bytes) is known statically, write it to *OUT
    and return true.
@@ -572,7 +927,7 @@  region::get_relative_concrete_offset (bit_offset_t *) const
 const svalue *
 region::get_relative_symbolic_offset (region_model_manager *mgr) const
 {
-  return mgr->get_or_create_unknown_svalue (integer_type_node);
+  return mgr->get_or_create_unknown_svalue (ptrdiff_type_node);
 }
 
 /* Attempt to get the position and size of this region expressed as a
@@ -1389,10 +1744,10 @@  field_region::get_relative_symbolic_offset (region_model_manager *mgr) const
   if (get_relative_concrete_offset (&out))
     {
       tree cst_tree
-	= wide_int_to_tree (integer_type_node, out / BITS_PER_UNIT);
+	= wide_int_to_tree (ptrdiff_type_node, out / BITS_PER_UNIT);
       return mgr->get_or_create_constant_svalue (cst_tree);
     }
-  return mgr->get_or_create_unknown_svalue (integer_type_node);
+  return mgr->get_or_create_unknown_svalue (ptrdiff_type_node);
 }
 
 /* class element_region : public region.  */
@@ -1474,14 +1829,14 @@  element_region::get_relative_symbolic_offset (region_model_manager *mgr) const
   HOST_WIDE_INT hwi_byte_size = int_size_in_bytes (elem_type);
   if (hwi_byte_size > 0)
 	  {
-      tree byte_size_tree = wide_int_to_tree (integer_type_node,
+      tree byte_size_tree = wide_int_to_tree (ptrdiff_type_node,
 					      hwi_byte_size);
       const svalue *byte_size_sval
 	= mgr->get_or_create_constant_svalue (byte_size_tree);
-      return mgr->get_or_create_binop (integer_type_node, MULT_EXPR,
+      return mgr->get_or_create_binop (ptrdiff_type_node, MULT_EXPR,
 				       m_index, byte_size_sval);
     }
-  return mgr->get_or_create_unknown_svalue (integer_type_node);
+  return mgr->get_or_create_unknown_svalue (ptrdiff_type_node);
 }
 
 /* class offset_region : public region.  */
@@ -1805,7 +2160,7 @@  bit_range_region::get_relative_symbolic_offset (region_model_manager *mgr)
   const
 {
   byte_offset_t start_byte = m_bits.get_start_bit_offset () / BITS_PER_UNIT;
-  tree start_bit_tree = wide_int_to_tree (integer_type_node, start_byte);
+  tree start_bit_tree = wide_int_to_tree (ptrdiff_type_node, start_byte);
   return mgr->get_or_create_constant_svalue (start_bit_tree);
 }
 
diff --git a/gcc/analyzer/region.h b/gcc/analyzer/region.h
index 5df0ae7487b..590f9ee7757 100644
--- a/gcc/analyzer/region.h
+++ b/gcc/analyzer/region.h
@@ -182,6 +182,7 @@  public:
   bool involves_p (const svalue *sval) const;
 
   region_offset get_offset (region_model_manager *mgr) const;
+  region_offset get_next_offset (region_model_manager *mgr) const;
 
   /* Attempt to get the size of this region as a concrete number of bytes.
      If successful, return true and write the size to *OUT.
diff --git a/gcc/analyzer/sm-fd.cc b/gcc/analyzer/sm-fd.cc
index d107390bc00..03ad3598a3c 100644
--- a/gcc/analyzer/sm-fd.cc
+++ b/gcc/analyzer/sm-fd.cc
@@ -465,7 +465,7 @@  public:
   }
 
   bool
-  emit (rich_location *rich_loc) final override
+  emit (rich_location *rich_loc, logger *) final override
   {
     /*CWE-775: Missing Release of File Descriptor or Handle after Effective
       Lifetime
@@ -550,7 +550,7 @@  public:
   }
 
   bool
-  emit (rich_location *rich_loc) final override
+  emit (rich_location *rich_loc, logger *) final override
   {
     bool warned;
     switch (m_fd_dir)
@@ -612,7 +612,7 @@  public:
     return OPT_Wanalyzer_fd_double_close;
   }
   bool
-  emit (rich_location *rich_loc) final override
+  emit (rich_location *rich_loc, logger *) final override
   {
     diagnostic_metadata m;
     // CWE-1341: Multiple Releases of Same Resource or Handle
@@ -677,7 +677,7 @@  public:
   }
 
   bool
-  emit (rich_location *rich_loc) final override
+  emit (rich_location *rich_loc, logger *) final override
   {
     bool warned;
     warned = warning_at (rich_loc, get_controlling_option (),
@@ -748,7 +748,7 @@  public:
   }
 
   bool
-  emit (rich_location *rich_loc) final override
+  emit (rich_location *rich_loc, logger *) final override
   {
     bool warned;
     warned = warning_at (rich_loc, get_controlling_option (),
@@ -859,7 +859,7 @@  public:
   }
 
   bool
-  emit (rich_location *rich_loc) final override
+  emit (rich_location *rich_loc, logger *) final override
   {
     /* CWE-666: Operation on Resource in Wrong Phase of Lifetime.  */
     diagnostic_metadata m;
@@ -1019,7 +1019,7 @@  public:
   }
 
   bool
-  emit (rich_location *rich_loc) final override
+  emit (rich_location *rich_loc, logger *) final override
   {
     switch (m_expected_type)
       {
diff --git a/gcc/analyzer/sm-file.cc b/gcc/analyzer/sm-file.cc
index d99a09b76c4..0cfe6821722 100644
--- a/gcc/analyzer/sm-file.cc
+++ b/gcc/analyzer/sm-file.cc
@@ -176,7 +176,7 @@  public:
     return OPT_Wanalyzer_double_fclose;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     diagnostic_metadata m;
     /* CWE-1341: Multiple Releases of Same Resource or Handle.  */
@@ -224,7 +224,7 @@  public:
     return OPT_Wanalyzer_file_leak;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     diagnostic_metadata m;
     /* CWE-775: "Missing Release of File Descriptor or Handle after
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index 74701375409..a8c63eb1ce8 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -835,7 +835,7 @@  public:
     return OPT_Wanalyzer_mismatching_deallocation;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     auto_diagnostic_group d;
     diagnostic_metadata m;
@@ -914,7 +914,7 @@  public:
     return OPT_Wanalyzer_double_free;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     auto_diagnostic_group d;
     diagnostic_metadata m;
@@ -1010,7 +1010,7 @@  public:
     return OPT_Wanalyzer_possible_null_dereference;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
     diagnostic_metadata m;
@@ -1099,7 +1099,7 @@  public:
     return OPT_Wanalyzer_possible_null_argument;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
     auto_diagnostic_group d;
@@ -1152,7 +1152,7 @@  public:
 
   bool terminate_path_p () const final override { return true; }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     /* CWE-476: NULL Pointer Dereference.  */
     diagnostic_metadata m;
@@ -1207,7 +1207,7 @@  public:
 
   bool terminate_path_p () const final override { return true; }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     /* CWE-476: NULL Pointer Dereference.  */
     auto_diagnostic_group d;
@@ -1264,7 +1264,7 @@  public:
     return OPT_Wanalyzer_use_after_free;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     /* CWE-416: Use After Free.  */
     diagnostic_metadata m;
@@ -1358,7 +1358,7 @@  public:
     return OPT_Wanalyzer_malloc_leak;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     /* "CWE-401: Missing Release of Memory after Effective Lifetime".  */
     diagnostic_metadata m;
@@ -1432,7 +1432,7 @@  public:
     return OPT_Wanalyzer_free_of_non_heap;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     auto_diagnostic_group d;
     diagnostic_metadata m;
@@ -1511,7 +1511,7 @@  public:
     return OPT_Wanalyzer_deref_before_check;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     /* Don't emit the warning if we can't show where the deref
        and the check occur.  */
diff --git a/gcc/analyzer/sm-pattern-test.cc b/gcc/analyzer/sm-pattern-test.cc
index 6c1c95067d8..4c88bcaeb2b 100644
--- a/gcc/analyzer/sm-pattern-test.cc
+++ b/gcc/analyzer/sm-pattern-test.cc
@@ -92,7 +92,7 @@  public:
     return 0;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     return warning_at (rich_loc, get_controlling_option (),
 		       "pattern match on %<%E %s %E%>",
diff --git a/gcc/analyzer/sm-sensitive.cc b/gcc/analyzer/sm-sensitive.cc
index d94d9e03ece..0597e390b3b 100644
--- a/gcc/analyzer/sm-sensitive.cc
+++ b/gcc/analyzer/sm-sensitive.cc
@@ -95,7 +95,8 @@  public:
     return OPT_Wanalyzer_exposure_through_output_file;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc,
+	     logger *) final override
   {
     diagnostic_metadata m;
     /* CWE-532: Information Exposure Through Log Files */
diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc
index ac01f6a4794..e3f90921df9 100644
--- a/gcc/analyzer/sm-signal.cc
+++ b/gcc/analyzer/sm-signal.cc
@@ -114,7 +114,7 @@  public:
     return OPT_Wanalyzer_unsafe_call_within_signal_handler;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     auto_diagnostic_group d;
     diagnostic_metadata m;
diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc
index f72f194460c..6d28d1f6f65 100644
--- a/gcc/analyzer/sm-taint.cc
+++ b/gcc/analyzer/sm-taint.cc
@@ -211,7 +211,7 @@  public:
     return OPT_Wanalyzer_tainted_array_index;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     diagnostic_metadata m;
     /* CWE-129: "Improper Validation of Array Index".  */
@@ -327,7 +327,7 @@  public:
     return OPT_Wanalyzer_tainted_offset;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     diagnostic_metadata m;
     /* CWE-823: "Use of Out-of-range Pointer Offset".  */
@@ -437,7 +437,7 @@  public:
     return OPT_Wanalyzer_tainted_size;
   }
 
-  bool emit (rich_location *rich_loc) override
+  bool emit (rich_location *rich_loc, logger *) override
   {
     /* "CWE-129: Improper Validation of Array Index".  */
     diagnostic_metadata m;
@@ -547,9 +547,9 @@  public:
     return "tainted_access_attrib_size";
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *logger) final override
   {
-    bool warned = tainted_size::emit (rich_loc);
+    bool warned = tainted_size::emit (rich_loc, logger);
     if (warned)
       {
 	inform (DECL_SOURCE_LOCATION (m_callee_fndecl),
@@ -583,7 +583,7 @@  public:
     return OPT_Wanalyzer_tainted_divisor;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     diagnostic_metadata m;
     /* CWE-369: "Divide By Zero".  */
@@ -645,7 +645,7 @@  public:
     return OPT_Wanalyzer_tainted_allocation_size;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     diagnostic_metadata m;
     /* "CWE-789: Memory Allocation with Excessive Size Value".  */
@@ -800,7 +800,7 @@  public:
     return OPT_Wanalyzer_tainted_assertion;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     diagnostic_metadata m;
     /* "CWE-617: Reachable Assertion".  */
diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc
index e8c927b9fe9..dcabce92771 100644
--- a/gcc/analyzer/store.cc
+++ b/gcc/analyzer/store.cc
@@ -236,8 +236,8 @@  bit_range::dump () const
   pp_flush (&pp);
 }
 
-/* If OTHER is a subset of this, return true and write
-   to *OUT the relative range of OTHER within this.
+/* If OTHER is a subset of this, return true and, if OUT is
+   non-null, write to *OUT the relative range of OTHER within this.
    Otherwise return false.  */
 
 bool
@@ -246,8 +246,11 @@  bit_range::contains_p (const bit_range &other, bit_range *out) const
   if (contains_p (other.get_start_bit_offset ())
       && contains_p (other.get_last_bit_offset ()))
     {
-      out->m_start_bit_offset = other.m_start_bit_offset - m_start_bit_offset;
-      out->m_size_in_bits = other.m_size_in_bits;
+      if (out)
+	{
+	  out->m_start_bit_offset = other.m_start_bit_offset - m_start_bit_offset;
+	  out->m_size_in_bits = other.m_size_in_bits;
+	}
       return true;
     }
   else
diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h
index 7ded650b608..af6cc7ed03c 100644
--- a/gcc/analyzer/store.h
+++ b/gcc/analyzer/store.h
@@ -350,6 +350,15 @@  struct byte_range
 		      m_size_in_bytes * BITS_PER_UNIT);
   }
 
+  bit_offset_t get_start_bit_offset () const
+  {
+    return m_start_byte_offset * BITS_PER_UNIT;
+  }
+  bit_offset_t get_next_bit_offset () const
+  {
+    return get_next_byte_offset () * BITS_PER_UNIT;
+  }
+
   static int cmp (const byte_range &br1, const byte_range &br2);
 
   byte_offset_t m_start_byte_offset;
diff --git a/gcc/analyzer/varargs.cc b/gcc/analyzer/varargs.cc
index aeea73a3899..72e1b31601c 100644
--- a/gcc/analyzer/varargs.cc
+++ b/gcc/analyzer/varargs.cc
@@ -403,7 +403,7 @@  public:
 	    && 0 == strcmp (m_usage_fnname, other.m_usage_fnname));
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     auto_diagnostic_group d;
     return warning_at (rich_loc, get_controlling_option (),
@@ -478,7 +478,7 @@  public:
     return va_list_sm_diagnostic::subclass_equal_p (other);
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     auto_diagnostic_group d;
     return warning_at (rich_loc, get_controlling_option (),
@@ -892,7 +892,7 @@  public:
     return OPT_Wanalyzer_va_arg_type_mismatch;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     auto_diagnostic_group d;
     diagnostic_metadata m;
@@ -942,7 +942,7 @@  public:
     return OPT_Wanalyzer_va_list_exhausted;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     auto_diagnostic_group d;
     diagnostic_metadata m;
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 023a56a647e..18013ebdf96 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -10520,6 +10520,15 @@  offset as well as the capacity is symbolic.
 
 See @uref{https://cwe.mitre.org/data/definitions/119.html, CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer}.
 
+For cases where the analyzer is able, it will emit a text art diagram
+visualizing the spatial relationship between the memory region that the
+analyzer predicts would be accessed, versus the range of memory that is
+valid to access: whether they overlap, are touching, are close or far
+apart; which one is before or after in memory, the relative sizes
+involved, the direction of the access (read vs write), and, in some
+cases, the values of data involved.  This diagram can be suppressed
+using @option{-fdiagnostics-text-art-charset=none}.
+
 @opindex Wanalyzer-possible-null-argument
 @opindex Wno-analyzer-possible-null-argument
 @item -Wno-analyzer-possible-null-argument
@@ -11030,6 +11039,12 @@  following warnings from @option{-fanalyzer}:
 -Wanalyzer-va-list-use-after-va-end
 }
 
+@opindex fanalyzer-debug-text-art
+@opindex fno-analyzer-debug-text-art
+@item -fanalyzer-debug-text-art-headings
+This option is intended for analyzer developers.  If enabled,
+the analyzer will add extra annotations to any diagrams it generates.
+
 @opindex fanalyzer-feasibility
 @opindex fno-analyzer-feasibility
 @item -fno-analyzer-feasibility
diff --git a/gcc/testsuite/gcc.dg/analyzer/data-model-1.c b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
index 86d1ccf11a7..3c4a45f4a4a 100644
--- a/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
@@ -240,8 +240,8 @@  void test_16 (void)
   __analyzer_eval (strlen (msg) == 11); /* { dg-warning "TRUE" } */
 
   /* Out-of-bounds.  */
-  __analyzer_eval (msg[100] == 'e'); /* { dg-warning "UNKNOWN" } */
-  // TODO: some kind of warning for the out-of-bounds access
+  __analyzer_eval (msg[100] == 'e'); /* { dg-warning "UNKNOWN" "eval result" } */
+  /* { dg-warning "buffer over-read" "out-of-bounds" { target *-*-* } .-1 } */
 }
 
 static const char *__attribute__((noinline))
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-ascii.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-ascii.c
new file mode 100644
index 00000000000..5e6eadcf27d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-ascii.c
@@ -0,0 +1,55 @@ 
+/* { dg-additional-options "-fdiagnostics-show-path-depths" } */
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+/* { dg-additional-options "-fdiagnostics-text-art-charset=ascii" } */
+
+#include <stdint.h>
+
+int32_t arr[10];
+
+void int_arr_write_element_after_end_off_by_one(int32_t x)
+{
+  arr[10] = x;  /* { dg-line line } */
+}
+/* { dg-warning "buffer overflow" "warning" { target *-*-* } line } */
+/* { dg-message "valid subscripts for 'arr' are '\\\[0\\\]' to '\\\[9\\\]'" "valid subscript note" { target *-*-* } line } */
+
+
+/* { dg-begin-multiline-output "" }
+   arr[10] = x;
+   ~~~~~~~~^~~
+  event 1 (depth 0)
+    |
+    | int32_t arr[10];
+    |         ^~~
+    |         |
+    |         (1) capacity: 40 bytes
+    |
+    +--> 'int_arr_write_element_after_end_off_by_one': event 2 (depth 1)
+           |
+           |   arr[10] = x;
+           |   ~~~~~~~~^~~
+           |           |
+           |           (2) out-of-bounds write from byte 40 till byte 43 but 'arr' ends at byte 40
+           |
+   { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+
+                                        +--------------------------------+
+                                        |write from 'x' (type: 'int32_t')|
+                                        +--------------------------------+
+                                                        |
+                                                        |
+                                                        v
+  +--------+-----------------+---------++--------------------------------+
+  |  [0]   |       ...       |   [9]   ||                                |
+  +--------+-----------------+---------+|       after valid range        |
+  |    'arr' (type: 'int32_t[10]')     ||                                |
+  +------------------------------------++--------------------------------+
+  |~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~||~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~|
+                    |                                   |
+          +---------+--------+                +---------+---------+
+          |capacity: 40 bytes|                |overflow of 4 bytes|
+          +------------------+                +-------------------+
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-debug.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-debug.c
new file mode 100644
index 00000000000..4c4d9d1b867
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-debug.c
@@ -0,0 +1,40 @@ 
+/* Test of -fanalyzer-debug-text-art.  */
+
+/* { dg-additional-options "-fdiagnostics-text-art-charset=ascii -fanalyzer-debug-text-art" } */
+
+#include <stdint.h>
+
+int32_t arr[10];
+
+void int_arr_write_element_after_end_off_by_one(int32_t x)
+{
+  arr[10] = x;  /* { dg-line line } */
+}
+/* { dg-warning "buffer overflow" "warning" { target *-*-* } line } */
+/* { dg-message "valid subscripts for 'arr' are '\\\[0\\\]' to '\\\[9\\\]'" "valid subscript note" { target *-*-* } line } */
+
+/* { dg-begin-multiline-output "" }
+
+  +---------+-----------+-----------+---+--------------------------------+
+  |   tc0   |    tc1    |    tc2    |tc3|              tc4               |
+  +---------+-----------+-----------+---+--------------------------------+
+  |bytes 0-3|bytes 4-35 |bytes 36-39|   |          bytes 40-43           |
+  +---------+-----------+-----------+   +--------------------------------+
+                                        +--------------------------------+
+                                        |write from 'x' (type: 'int32_t')|
+                                        +--------------------------------+
+                                                        |
+                                                        |
+                                                        v
+  +---------+-----------+-----------+   +--------------------------------+
+  |   [0]   |    ...    |    [9]    |   |                                |
+  +---------+-----------+-----------+   |       after valid range        |
+  |   'arr' (type: 'int32_t[10]')   |   |                                |
+  +---------------------------------+   +--------------------------------+
+  |~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~|   |~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~|
+                   |                                    |
+         +---------+--------+                 +---------+---------+
+         |capacity: 40 bytes|                 |overflow of 4 bytes|
+         +------------------+                 +-------------------+
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-emoji.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-emoji.c
new file mode 100644
index 00000000000..1c6125225ff
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-emoji.c
@@ -0,0 +1,55 @@ 
+/* { dg-additional-options "-fdiagnostics-show-path-depths" } */
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+/* { dg-additional-options "-fdiagnostics-text-art-charset=emoji" } */
+
+#include <stdint.h>
+
+int32_t arr[10];
+
+void int_arr_write_element_after_end_off_by_one(int32_t x)
+{
+  arr[10] = x;  /* { dg-line line } */
+}
+/* { dg-warning "buffer overflow" "warning" { target *-*-* } line } */
+/* { dg-message "valid subscripts for 'arr' are '\\\[0\\\]' to '\\\[9\\\]'" "valid subscript note" { target *-*-* } line } */
+
+
+/* { dg-begin-multiline-output "" }
+   arr[10] = x;
+   ~~~~~~~~^~~
+  event 1 (depth 0)
+    |
+    | int32_t arr[10];
+    |         ^~~
+    |         |
+    |         (1) capacity: 40 bytes
+    |
+    +--> 'int_arr_write_element_after_end_off_by_one': event 2 (depth 1)
+           |
+           |   arr[10] = x;
+           |   ~~~~~~~~^~~
+           |           |
+           |           (2) out-of-bounds write from byte 40 till byte 43 but 'arr' ends at byte 40
+           |
+   { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+
+                                        ┌────────────────────────────────┐
+                                        │write from 'x' (type: 'int32_t')│
+                                        └────────────────────────────────┘
+                                                        │
+                                                        │
+                                                        v
+  ┌────────┬─────────────────┬─────────┐┌────────────────────────────────┐
+  │  [0]   │       ...       │   [9]   ││                                │
+  ├────────┴─────────────────┴─────────┤│       after valid range        │
+  │    'arr' (type: 'int32_t[10]')     ││                                │
+  └────────────────────────────────────┘└────────────────────────────────┘
+  ├─────────────────┬──────────────────┤├───────────────┬────────────────┤
+                    │                                   │
+          ╭─────────┴────────╮              ╭───────────┴──────────╮
+          │capacity: 40 bytes│              │⚠️  overflow of 4 bytes│
+          ╰──────────────────╯              ╰──────────────────────╯
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-json.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-json.c
new file mode 100644
index 00000000000..0a2cc3493ab
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-json.c
@@ -0,0 +1,13 @@ 
+/* { dg-additional-options "-fdiagnostics-format=json-file" } */
+
+/* The custom JSON format doesn't support text art, so this is just a simple
+   smoketext.  */
+
+#include <stdint.h>
+
+int32_t arr[10];
+
+void int_arr_write_element_after_end_off_by_one(int32_t x)
+{
+  arr[10] = x;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-sarif.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-sarif.c
new file mode 100644
index 00000000000..051a1ceb463
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-sarif.c
@@ -0,0 +1,24 @@ 
+/* We require -fdiagnostics-text-art-charset= to get any text art here
+   because of the test suite using -fdiagnostics-plain-output.  */
+
+/* { dg-additional-options "-fdiagnostics-format=sarif-file -fdiagnostics-text-art-charset=ascii" } */
+
+#include <stdint.h>
+
+int32_t arr[10];
+
+void int_arr_write_element_after_end_off_by_one(int32_t x)
+{
+  arr[10] = x;
+}
+
+/* Verify that some JSON was written to a file with the expected name.
+
+   { dg-final { verify-sarif-file } }
+
+   Expect the "alt-text" to be captured.
+     { dg-final { scan-sarif-file "\"text\": \"Diagram visualizing the predicted out-of-bounds access\"," } }
+
+   Expect the diagram to have 4 leading spaces (to indicate a code block),
+   and that at least part of the diagram was written out.
+     { dg-final { scan-sarif-file "\"markdown\": \"    .*capacity: 40 bytes.*\"" } } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-unicode.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-unicode.c
new file mode 100644
index 00000000000..71f66ff87c9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-1-unicode.c
@@ -0,0 +1,55 @@ 
+/* { dg-additional-options "-fdiagnostics-show-path-depths" } */
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode" } */
+
+#include <stdint.h>
+
+int32_t arr[10];
+
+void int_arr_write_element_after_end_off_by_one(int32_t x)
+{
+  arr[10] = x;  /* { dg-line line } */
+}
+/* { dg-warning "buffer overflow" "warning" { target *-*-* } line } */
+/* { dg-message "valid subscripts for 'arr' are '\\\[0\\\]' to '\\\[9\\\]'" "valid subscript note" { target *-*-* } line } */
+
+
+/* { dg-begin-multiline-output "" }
+   arr[10] = x;
+   ~~~~~~~~^~~
+  event 1 (depth 0)
+    |
+    | int32_t arr[10];
+    |         ^~~
+    |         |
+    |         (1) capacity: 40 bytes
+    |
+    +--> 'int_arr_write_element_after_end_off_by_one': event 2 (depth 1)
+           |
+           |   arr[10] = x;
+           |   ~~~~~~~~^~~
+           |           |
+           |           (2) out-of-bounds write from byte 40 till byte 43 but 'arr' ends at byte 40
+           |
+   { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+
+                                        ┌────────────────────────────────┐
+                                        │write from 'x' (type: 'int32_t')│
+                                        └────────────────────────────────┘
+                                                        │
+                                                        │
+                                                        v
+  ┌────────┬─────────────────┬─────────┐┌────────────────────────────────┐
+  │  [0]   │       ...       │   [9]   ││                                │
+  ├────────┴─────────────────┴─────────┤│       after valid range        │
+  │    'arr' (type: 'int32_t[10]')     ││                                │
+  └────────────────────────────────────┘└────────────────────────────────┘
+  ├─────────────────┬──────────────────┤├───────────────┬────────────────┤
+                    │                                   │
+          ╭─────────┴────────╮                ╭─────────┴─────────╮
+          │capacity: 40 bytes│                │overflow of 4 bytes│
+          ╰──────────────────╯                ╰───────────────────╯
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-10.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-10.c
new file mode 100644
index 00000000000..4a7b8e306de
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-10.c
@@ -0,0 +1,29 @@ 
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode" } */
+
+#include <stdint.h>
+#include <stdlib.h>
+
+int32_t int_vla_write_element_symbolic_before_start (int32_t x, size_t n)
+{
+  int32_t arr[n]; /* { dg-message "\\(1\\) capacity: 'n \\* 4' bytes" } */
+  arr[-2] = 42;  /* { dg-warning "stack-based buffer underwrite" } */
+}
+
+/* { dg-begin-multiline-output "" }
+
+  ┌───────────────────┐
+  │write of '(int) 42'│
+  └───────────────────┘
+            │
+            │
+            v
+  ┌───────────────────┐                 ┌────────────────────────────────┐
+  │before valid range │                 │buffer allocated on stack at (1)│
+  └───────────────────┘                 └────────────────────────────────┘
+  ├─────────┬─────────┤├───────┬───────┤├───────────────┬────────────────┤
+            │                  │                        │
+  ╭─────────┴───────────╮  ╭───┴───╮        ╭───────────┴───────────╮
+  │underwrite of 4 bytes│  │4 bytes│        │capacity: 'n * 4' bytes│
+  ╰─────────────────────╯  ╰───────╯        ╰───────────────────────╯
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-11.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-11.c
new file mode 100644
index 00000000000..f8eb1580eb3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-11.c
@@ -0,0 +1,82 @@ 
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode" } */
+/* { dg-require-effective-target alloca } */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+void test6 (size_t size)
+{
+  int32_t *buf = __builtin_alloca (4 * size);
+  memset (buf, 0, 4 * size);
+  int32_t last = *(buf + 4 * size); /* { dg-warning "stack-based buffer over-read" } */
+}
+
+/* (size * 16) - (size * 4) leads to a gap of (size * 12).  */
+
+/* { dg-begin-multiline-output "" }
+
+                                                       ┌─────────────────┐
+                                                       │ read of 4 bytes │
+                                                       └─────────────────┘
+                                                                ^
+                                                                │
+                                                                │
+  ┌────────────────────────────────┐                   ┌─────────────────┐
+  │buffer allocated on stack at (1)│                   │after valid range│
+  └────────────────────────────────┘                   └─────────────────┘
+  ├───────────────┬────────────────┤├────────┬────────┤├────────┬────────┤
+                  │                          │                  │
+                  │                          │       ╭──────────┴─────────╮
+                  │                          │       │over-read of 4 bytes│
+                  │                          │       ╰────────────────────╯
+      ╭───────────┴──────────╮      ╭────────┴────────╮
+      │size: 'size * 4' bytes│      │'size * 12' bytes│
+      ╰──────────────────────╯      ╰─────────────────╯
+
+   { dg-end-multiline-output "" } */
+
+void test7 (size_t size)
+{
+  int32_t *buf = __builtin_alloca (4 * size + 3); /* { dg-warning "allocated buffer size is not a multiple of the pointee's size" } */
+  buf[size] = 42; /* { dg-warning "stack-based buffer overflow" } */
+}
+
+/* { dg-begin-multiline-output "" }
+
+                                 ┌───────────────────────────────────────┐
+                                 │          write of '(int) 42'          │
+                                 └───────────────────────────────────────┘
+                                           │                   │
+                                           │                   │
+                                           v                   v
+  ┌──────────────────────────────────────────────────┐┌──────────────────┐
+  │         buffer allocated on stack at (1)         ││after valid range │
+  └──────────────────────────────────────────────────┘└──────────────────┘
+  ├────────────────────────┬─────────────────────────┤├────────┬─────────┤
+                           │                                   │
+           ╭───────────────┴──────────────╮          ╭─────────┴────────╮
+           │capacity: 'size * 4 + 3' bytes│          │overflow of 1 byte│
+           ╰──────────────────────────────╯          ╰──────────────────╯
+
+   { dg-end-multiline-output "" } */
+
+
+/* We're currently not able to generate a diagram for this case;
+   make sure we handle this case gracefully.  */
+
+char *test99 (const char *x, const char *y)
+{
+  size_t len_x = __builtin_strlen (x);
+  size_t len_y = __builtin_strlen (y);
+  /* BUG (root cause): forgot to add 1 for terminator.  */
+  size_t sz = len_x + len_y;
+  char *result = __builtin_malloc (sz);
+  if (!result)
+    return NULL;
+  __builtin_memcpy (result, x, len_x);
+  __builtin_memcpy (result + len_x, y, len_y);
+  /* BUG (symptom): off-by-one out-of-bounds write to heap.  */
+  result[len_x + len_y] = '\0'; /* { dg-warning "heap-based buffer overflow" } */
+  return result;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-12.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-12.c
new file mode 100644
index 00000000000..5eb81f9fd98
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-12.c
@@ -0,0 +1,54 @@ 
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode" } */
+/* { dg-require-effective-target alloca } */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+void test8 (size_t size, size_t offset)
+{
+  char src[size];
+  char dst[size];
+  memcpy (dst, src, size + offset); /* { dg-line test8 } */
+  /* { dg-warning "over-read" "warning" { target *-*-* } test8 } */
+  /* { dg-warning "use of uninitialized value" "warning" { target *-*-* } test8 } */
+  /* { dg-warning "overflow" "warning" { target *-*-* } test8 } */
+}
+
+/* { dg-begin-multiline-output "" }
+
+  ┌──────────────────────────────────────────────────────────────────────┐
+  │                    read of 'size + offset' bytes                     │
+  └──────────────────────────────────────────────────────────────────────┘
+                   ^                                   ^
+                   │                                   │
+                   │                                   │
+  ┌──────────────────────────────────┐┌──────────────────────────────────┐
+  │ buffer allocated on stack at (1) ││        after valid range         │
+  └──────────────────────────────────┘└──────────────────────────────────┘
+  ├────────────────┬─────────────────┤├────────────────┬─────────────────┤
+                   │                                   │
+         ╭─────────┴────────╮            ╭─────────────┴─────────────╮
+         │size: 'size' bytes│            │over-read of 'offset' bytes│
+         ╰──────────────────╯            ╰───────────────────────────╯
+
+   { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+
+  ┌──────────────────────────────────────────────────────────────────────┐
+  │                    write of 'size + offset' bytes                    │
+  └──────────────────────────────────────────────────────────────────────┘
+                   │                                   │
+                   │                                   │
+                   v                                   v
+  ┌──────────────────────────────────┐┌──────────────────────────────────┐
+  │ buffer allocated on stack at (1) ││        after valid range         │
+  └──────────────────────────────────┘└──────────────────────────────────┘
+  ├────────────────┬─────────────────┤├────────────────┬─────────────────┤
+                   │                                   │
+       ╭───────────┴──────────╮          ╭─────────────┴────────────╮
+       │capacity: 'size' bytes│          │overflow of 'offset' bytes│
+       ╰──────────────────────╯          ╰──────────────────────────╯
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-13.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-13.c
new file mode 100644
index 00000000000..dcd1263dfbb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-13.c
@@ -0,0 +1,43 @@ 
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode" } */
+
+#include <string.h>
+
+void
+test_non_ascii ()
+{
+  char buf[9];
+  strcpy (buf, "Liberté\n"); /* { dg-warning "stack-based buffer overflow" } */
+  /* { dg-warning "'__builtin_memcpy' writing 10 bytes into a region of size 9 overflows the destination" "" { target *-*-* } .-1 } */
+}
+
+/* Example of non-ASCII UTF-8 that's short enough to fully quote, whilst
+   containing control characters.  */
+
+/* { dg-begin-multiline-output "" }
+
+  ┌──────┬──────┬──────┬──────┬──────┬──────┬────┬────┬──────┐┌─────────────────┐
+  │ [0]  │ [1]  │ [2]  │ [3]  │ [4]  │ [5]  │[6] │[7] │ [8]  ││       [9]       │
+  ├──────┼──────┼──────┼──────┼──────┼──────┼────┼────┼──────┤├─────────────────┤
+  │ 0x4c │ 0x69 │ 0x62 │ 0x65 │ 0x72 │ 0x74 │0xc3│0xa9│ 0x0a ││      0x00       │
+  ├──────┼──────┼──────┼──────┼──────┼──────┼────┴────┼──────┤├─────────────────┤
+  │U+004c│U+0069│U+0062│U+0065│U+0072│U+0074│ U+00e9  │U+000a││     U+0000      │
+  ├──────┼──────┼──────┼──────┼──────┼──────┼─────────┼──────┤├─────────────────┤
+  │  L   │  i   │  b   │  e   │  r   │  t   │    é    │      ││       NUL       │
+  ├──────┴──────┴──────┴──────┴──────┴──────┴─────────┴──────┴┴─────────────────┤
+  │                      string literal (type: 'char[10]')                      │
+  └─────────────────────────────────────────────────────────────────────────────┘
+     │      │      │      │      │      │     │    │     │             │
+     │      │      │      │      │      │     │    │     │             │
+     v      v      v      v      v      v     v    v     v             v
+  ┌──────┬────────────────────────────────────────────┬──────┐┌─────────────────┐
+  │ [0]  │                    ...                     │ [8]  ││                 │
+  ├──────┴────────────────────────────────────────────┴──────┤│after valid range│
+  │                 'buf' (type: 'char[9]')                  ││                 │
+  └──────────────────────────────────────────────────────────┘└─────────────────┘
+  ├────────────────────────────┬─────────────────────────────┤├────────┬────────┤
+                               │                                       │
+                      ╭────────┴────────╮                    ╭─────────┴────────╮
+                      │capacity: 9 bytes│                    │overflow of 1 byte│
+                      ╰─────────────────╯                    ╰──────────────────╯
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-14.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-14.c
new file mode 100644
index 00000000000..3cedf06d258
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-14.c
@@ -0,0 +1,110 @@ 
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode" } */
+
+#include <stdint.h>
+
+extern int32_t arr_0[0]; /* { dg-message "capacity: 0 bytes" } */
+extern int32_t arr_1[1]; /* { dg-message "capacity: 4 bytes" } */
+extern int32_t arr_2[2]; /* { dg-message "capacity: 8 bytes" } */
+extern int32_t arr_3[3]; /* { dg-message "capacity: 12 bytes" } */
+extern int32_t arr_4[4]; /* { dg-message "capacity: 16 bytes" } */
+
+void test_immediately_after (int x)
+{
+  arr_0[0] = x; /* { dg-warning "buffer overflow" } */
+  arr_1[1] = x; /* { dg-warning "buffer overflow" } */
+  arr_2[2] = x; /* { dg-warning "buffer overflow" } */
+  arr_3[3] = x; /* { dg-warning "buffer overflow" } */
+  arr_4[4] = x; /* { dg-warning "buffer overflow" } */
+}
+
+/* Expect no diagram for the arr_0 case: there's no valid region 
+to write to.  */
+
+/* The arr_1 case.  */
+/* { dg-begin-multiline-output "" }
+
+                                      ┌──────────────────────────────────┐
+                                      │   write from 'x' (type: 'int')   │
+                                      └──────────────────────────────────┘
+                                                       │
+                                                       │
+                                                       v
+  ┌──────────────────────────────────┐┌──────────────────────────────────┐
+  │               [0]                ││                                  │
+  ├──────────────────────────────────┤│        after valid range         │
+  │   'arr_1' (type: 'int32_t[1]')   ││                                  │
+  └──────────────────────────────────┘└──────────────────────────────────┘
+  ├────────────────┬─────────────────┤├────────────────┬─────────────────┤
+                   │                                   │
+          ╭────────┴────────╮                ╭─────────┴─────────╮
+          │capacity: 4 bytes│                │overflow of 4 bytes│
+          ╰─────────────────╯                ╰───────────────────╯
+
+   { dg-end-multiline-output "" } */
+
+/* The arr_2 case.  */
+/* { dg-begin-multiline-output "" }
+
+                                            ┌────────────────────────────┐
+                                            │write from 'x' (type: 'int')│
+                                            └────────────────────────────┘
+                                                          │
+                                                          │
+                                                          v
+  ┌────────────────────┬───────────────────┐┌────────────────────────────┐
+  │        [0]         │        [1]        ││                            │
+  ├────────────────────┴───────────────────┤│     after valid range      │
+  │      'arr_2' (type: 'int32_t[2]')      ││                            │
+  └────────────────────────────────────────┘└────────────────────────────┘
+  ├───────────────────┬────────────────────┤├─────────────┬──────────────┤
+                      │                                   │
+             ╭────────┴────────╮                ╭─────────┴─────────╮
+             │capacity: 8 bytes│                │overflow of 4 bytes│
+             ╰─────────────────╯                ╰───────────────────╯
+
+   { dg-end-multiline-output "" } */
+
+/* The arr_3 case.  */
+// Perhaps we should show [1] rather than ellipsize here.
+/* { dg-begin-multiline-output "" }
+
+                                            ┌────────────────────────────┐
+                                            │write from 'x' (type: 'int')│
+                                            └────────────────────────────┘
+                                                          │
+                                                          │
+                                                          v
+  ┌─────────────┬─────────────┬────────────┐┌────────────────────────────┐
+  │     [0]     │     ...     │    [2]     ││                            │
+  ├─────────────┴─────────────┴────────────┤│     after valid range      │
+  │      'arr_3' (type: 'int32_t[3]')      ││                            │
+  └────────────────────────────────────────┘└────────────────────────────┘
+  ├───────────────────┬────────────────────┤├─────────────┬──────────────┤
+                      │                                   │
+            ╭─────────┴────────╮                ╭─────────┴─────────╮
+            │capacity: 12 bytes│                │overflow of 4 bytes│
+            ╰──────────────────╯                ╰───────────────────╯
+
+   { dg-end-multiline-output "" } */
+
+/* The arr_4 case.  */
+/* { dg-begin-multiline-output "" }
+
+                                            ┌────────────────────────────┐
+                                            │write from 'x' (type: 'int')│
+                                            └────────────────────────────┘
+                                                          │
+                                                          │
+                                                          v
+  ┌──────────┬──────────────────┬──────────┐┌────────────────────────────┐
+  │   [0]    │       ...        │   [3]    ││                            │
+  ├──────────┴──────────────────┴──────────┤│     after valid range      │
+  │      'arr_4' (type: 'int32_t[4]')      ││                            │
+  └────────────────────────────────────────┘└────────────────────────────┘
+  ├───────────────────┬────────────────────┤├─────────────┬──────────────┤
+                      │                                   │
+            ╭─────────┴────────╮                ╭─────────┴─────────╮
+            │capacity: 16 bytes│                │overflow of 4 bytes│
+            ╰──────────────────╯                ╰───────────────────╯
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-15.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-15.c
new file mode 100644
index 00000000000..e2a6381d37e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-15.c
@@ -0,0 +1,42 @@ 
+/* Regression test for ICE with short values of
+   --param=analyzer-text-art-string-ellipsis-threshold=.  */
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode --param=analyzer-text-art-string-ellipsis-threshold=0" } */
+
+#include <string.h>
+
+void
+test_non_ascii ()
+{
+  char buf[9];
+  strcpy (buf, "Liberté\n"); /* { dg-warning "stack-based buffer overflow" } */
+  /* { dg-warning "'__builtin_memcpy' writing 10 bytes into a region of size 9 overflows the destination" "" { target *-*-* } .-1 } */
+}
+
+/* { dg-begin-multiline-output "" }
+
+  ┌──────┬──────┬──────┬──────┬──────┬──────┬────┬────┬──────┐┌─────────────────┐
+  │ [0]  │ [1]  │ [2]  │ [3]  │ [4]  │ [5]  │[6] │[7] │ [8]  ││       [9]       │
+  ├──────┼──────┼──────┼──────┼──────┼──────┼────┼────┼──────┤├─────────────────┤
+  │ 0x4c │ 0x69 │ 0x62 │ 0x65 │ 0x72 │ 0x74 │0xc3│0xa9│ 0x0a ││      0x00       │
+  ├──────┼──────┼──────┼──────┼──────┼──────┼────┴────┼──────┤├─────────────────┤
+  │U+004c│U+0069│U+0062│U+0065│U+0072│U+0074│ U+00e9  │U+000a││     U+0000      │
+  ├──────┼──────┼──────┼──────┼──────┼──────┼─────────┼──────┤├─────────────────┤
+  │  L   │  i   │  b   │  e   │  r   │  t   │    é    │      ││       NUL       │
+  ├──────┴──────┴──────┴──────┴──────┴──────┴─────────┴──────┴┴─────────────────┤
+  │                      string literal (type: 'char[10]')                      │
+  └─────────────────────────────────────────────────────────────────────────────┘
+     │      │      │      │      │      │     │    │     │             │
+     │      │      │      │      │      │     │    │     │             │
+     v      v      v      v      v      v     v    v     v             v
+  ┌──────┬────────────────────────────────────────────┬──────┐┌─────────────────┐
+  │ [0]  │                    ...                     │ [8]  ││                 │
+  ├──────┴────────────────────────────────────────────┴──────┤│after valid range│
+  │                 'buf' (type: 'char[9]')                  ││                 │
+  └──────────────────────────────────────────────────────────┘└─────────────────┘
+  ├────────────────────────────┬─────────────────────────────┤├────────┬────────┤
+                               │                                       │
+                      ╭────────┴────────╮                    ╭─────────┴────────╮
+                      │capacity: 9 bytes│                    │overflow of 1 byte│
+                      ╰─────────────────╯                    ╰──────────────────╯
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-2.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-2.c
new file mode 100644
index 00000000000..535dab1eb9e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-2.c
@@ -0,0 +1,30 @@ 
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode" } */
+
+#include <stdint.h>
+#include <stdlib.h>
+
+void int_vla_write_element_after_end_off_by_one(int32_t x, size_t n)
+{
+  int32_t arr[n]; /* { dg-message "\\(1\\) capacity: 'n \\* 4' bytes" } */
+
+  arr[n] = x;  /* { dg-warning "stack-based buffer overflow" } */
+}
+
+/* { dg-begin-multiline-output "" }
+
+                                      ┌──────────────────────────────────┐
+                                      │ write from 'x' (type: 'int32_t') │
+                                      └──────────────────────────────────┘
+                                                       │
+                                                       │
+                                                       v
+  ┌──────────────────────────────────┐┌──────────────────────────────────┐
+  │ buffer allocated on stack at (1) ││        after valid range         │
+  └──────────────────────────────────┘└──────────────────────────────────┘
+  ├────────────────┬─────────────────┤├────────────────┬─────────────────┤
+                   │                                   │
+       ╭───────────┴───────────╮             ╭─────────┴─────────╮
+       │capacity: 'n * 4' bytes│             │overflow of 4 bytes│
+       ╰───────────────────────╯             ╰───────────────────╯
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-3.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-3.c
new file mode 100644
index 00000000000..064f3fad47e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-3.c
@@ -0,0 +1,45 @@ 
+/* The multiline output assumes sizeof(size_t) == 8.
+   { dg-require-effective-target lp64 } */
+
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode" } */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+
+struct str {
+  size_t len;
+  char data[];
+};
+
+struct str *
+make_str_badly (const char *src)
+{
+  size_t len = strlen(src);
+  struct str *str = malloc(sizeof(str) + len); /* { dg-message "\\(1\\) capacity: 'len \\+ 8' bytes" } */
+  if (!str)
+    return NULL;
+  str->len = len;
+  memcpy(str->data, src, len);
+  str->data[len] = '\0'; /* { dg-warning "heap-based buffer overflow" } */
+  return str;
+}
+
+/* { dg-begin-multiline-output "" }
+
+                                      ┌──────────────────────────────────┐
+                                      │       write of '(char) 0'        │
+                                      └──────────────────────────────────┘
+                                                       │
+                                                       │
+                                                       v
+  ┌──────────────────────────────────┐┌──────────────────────────────────┐
+  │ buffer allocated on heap at (1)  ││        after valid range         │
+  └──────────────────────────────────┘└──────────────────────────────────┘
+  ├────────────────┬─────────────────┤├────────────────┬─────────────────┤
+                   │                                   │
+      ╭────────────┴────────────╮            ╭─────────┴────────╮
+      │capacity: 'len + 8' bytes│            │overflow of 1 byte│
+      ╰─────────────────────────╯            ╰──────────────────╯
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-4.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-4.c
new file mode 100644
index 00000000000..ec8e4abd337
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-4.c
@@ -0,0 +1,45 @@ 
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode" } */
+
+#include <string.h>
+
+#define LOREM_IPSUM \
+  "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod" \
+  " tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim" \
+  " veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea" \
+  " commodo consequat. Duis aute irure dolor in reprehenderit in voluptate" \
+  " velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint" \
+  " occaecat cupidatat non proident, sunt in culpa qui officia deserunt" \
+  " mollit anim id est laborum."
+
+void
+test_long_string ()
+{
+  char buf[100];
+  strcpy (buf, LOREM_IPSUM); /* { dg-warning "stack-based buffer overflow" } */
+  /* { dg-warning "'__builtin_memcpy' writing 446 bytes into a region of size 100 overflows the destination" "" { target *-*-* } .-1 } */
+}
+
+/* { dg-begin-multiline-output "" }
+
+  ┌───┬───┬───┬───┬───┬───┬──────────┬─────┬─────┬─────┬─────┬─────┬─────┐
+  │[0]│[1]│[2]│[3]│[4]│[5]│          │[440]│[441]│[442]│[443]│[444]│[445]│
+  ├───┼───┼───┼───┼───┼───┤   ...    ├─────┼─────┼─────┼─────┼─────┼─────┤
+  │'L'│'o'│'r'│'e'│'m'│' '│          │ 'o' │ 'r' │ 'u' │ 'm' │ '.' │ NUL │
+  ├───┴───┴───┴───┴───┴───┴──────────┴─────┴─────┴─────┴─────┴─────┴─────┤
+  │                  string literal (type: 'char[446]')                  │
+  └──────────────────────────────────────────────────────────────────────┘
+    │   │   │   │   │   │  │  │    │    │     │     │     │     │     │
+    │   │   │   │   │   │  │  │    │    │     │     │     │     │     │
+    v   v   v   v   v   v  v  v    v    v     v     v     v     v     v
+  ┌───┬─────────────────────┬────┐┌──────────────────────────────────────┐
+  │[0]│         ...         │[99]││                                      │
+  ├───┴─────────────────────┴────┤│          after valid range           │
+  │  'buf' (type: 'char[100]')   ││                                      │
+  └──────────────────────────────┘└──────────────────────────────────────┘
+  ├──────────────┬───────────────┤├──────────────────┬───────────────────┤
+                 │                                   │
+       ╭─────────┴─────────╮              ╭──────────┴──────────╮
+       │capacity: 100 bytes│              │overflow of 346 bytes│
+       ╰───────────────────╯              ╰─────────────────────╯
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-5-ascii.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-5-ascii.c
new file mode 100644
index 00000000000..e82bce98fd1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-5-ascii.c
@@ -0,0 +1,40 @@ 
+/* { dg-additional-options "-fdiagnostics-text-art-charset=ascii" } */
+
+#include <string.h>
+
+void
+test_non_ascii ()
+{
+  char buf[5];
+  strcpy (buf, "文字化け"); /* { dg-warning "stack-based buffer overflow" } */
+  /* { dg-warning "'__builtin_memcpy' writing 13 bytes into a region of size 5 overflows the destination" "" { target *-*-* } .-1 } */
+}
+
+/* Without unicode support, we shouldn't show the printable unicode chars.  */
+
+/* { dg-begin-multiline-output "" }
+
+  +-----+-----+-----+----+----++----+----+----+----+----+----+----+------+
+  | [0] | [1] | [2] |[3] |[4] ||[5] |[6] |[7] |[8] |[9] |[10]|[11]| [12] |
+  +-----+-----+-----+----+----++----+----+----+----+----+----+----+------+
+  |0xe6 |0x96 |0x87 |0xe5|0xad||0x97|0xe5|0x8c|0x96|0xe3|0x81|0x91| 0x00 |
+  +-----+-----+-----+----+----++----+----+----+----+----+----+----+------+
+  |     U+6587      |    U+5b57     |    U+5316    |    U+3051    |U+0000|
+  +-----------------+---------------+--------------+--------------+------+
+  |                  string literal (type: 'char[13]')                   |
+  +----------------------------------------------------------------------+
+     |     |     |    |    |     |    |    |    |    |    |    |     |
+     |     |     |    |    |     |    |    |    |    |    |    |     |
+     v     v     v    v    v     v    v    v    v    v    v    v     v
+  +-----+----------------+----++-----------------------------------------+
+  | [0] |      ...       |[4] ||                                         |
+  +-----+----------------+----+|            after valid range            |
+  |  'buf' (type: 'char[5]')  ||                                         |
+  +---------------------------++-----------------------------------------+
+  |~~~~~~~~~~~~~+~~~~~~~~~~~~~||~~~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~~|
+                |                                   |
+       +--------+--------+                +---------+---------+
+       |capacity: 5 bytes|                |overflow of 8 bytes|
+       +-----------------+                +-------------------+
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-5-unicode.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-5-unicode.c
new file mode 100644
index 00000000000..48fa12fd483
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-5-unicode.c
@@ -0,0 +1,42 @@ 
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode" } */
+
+#include <string.h>
+
+void
+test_non_ascii ()
+{
+  char buf[5];
+  strcpy (buf, "文字化け"); /* { dg-warning "stack-based buffer overflow" } */
+  /* { dg-warning "'__builtin_memcpy' writing 13 bytes into a region of size 5 overflows the destination" "" { target *-*-* } .-1 } */
+}
+
+/* With unicode support, we should show the printable unicode chars.  */
+
+/* { dg-begin-multiline-output "" }
+
+  ┌─────┬─────┬─────┬────┬────┐┌────┬────┬────┬────┬────┬────┬────┬──────┐
+  │ [0] │ [1] │ [2] │[3] │[4] ││[5] │[6] │[7] │[8] │[9] │[10]│[11]│ [12] │
+  ├─────┼─────┼─────┼────┼────┤├────┼────┼────┼────┼────┼────┼────┼──────┤
+  │0xe6 │0x96 │0x87 │0xe5│0xad││0x97│0xe5│0x8c│0x96│0xe3│0x81│0x91│ 0x00 │
+  ├─────┴─────┴─────┼────┴────┴┴────┼────┴────┴────┼────┴────┴────┼──────┤
+  │     U+6587      │    U+5b57     │    U+5316    │    U+3051    │U+0000│
+  ├─────────────────┼───────────────┼──────────────┼──────────────┼──────┤
+  │       文        │      字       │      化      │      け      │ NUL  │
+  ├─────────────────┴───────────────┴──────────────┴──────────────┴──────┤
+  │                  string literal (type: 'char[13]')                   │
+  └──────────────────────────────────────────────────────────────────────┘
+     │     │     │    │    │     │    │    │    │    │    │    │     │
+     │     │     │    │    │     │    │    │    │    │    │    │     │
+     v     v     v    v    v     v    v    v    v    v    v    v     v
+  ┌─────┬────────────────┬────┐┌─────────────────────────────────────────┐
+  │ [0] │      ...       │[4] ││                                         │
+  ├─────┴────────────────┴────┤│            after valid range            │
+  │  'buf' (type: 'char[5]')  ││                                         │
+  └───────────────────────────┘└─────────────────────────────────────────┘
+  ├─────────────┬─────────────┤├────────────────────┬────────────────────┤
+                │                                   │
+       ╭────────┴────────╮                ╭─────────┴─────────╮
+       │capacity: 5 bytes│                │overflow of 8 bytes│
+       ╰─────────────────╯                ╰───────────────────╯
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-6.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-6.c
new file mode 100644
index 00000000000..25bf9d53b2b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-6.c
@@ -0,0 +1,125 @@ 
+/* { dg-require-effective-target lp64 }
+   Misbehaves with -m32 due to optimization turning the pointer arithmetic into:
+      _2 = &buf + 4294967246;
+      memcpy (_2, _1, 4096);
+*/
+
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode" } */
+
+#include <string.h>
+
+#define LOREM_IPSUM \
+  "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod" \
+  " tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim" \
+  " veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea" \
+  " commodo consequat. Duis aute irure dolor in reprehenderit in voluptate" \
+  " velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint" \
+  " occaecat cupidatat non proident, sunt in culpa qui officia deserunt" \
+  " mollit anim id est laborum."
+
+/* This memcpy reads from both before and after the bounds of the
+   string literal, and writes to both before and after the bounds of "buf".  */
+
+void
+test_bad_memcpy ()
+{
+  char buf[100];
+  memcpy (buf - 50, LOREM_IPSUM - 100, 4096); /* { dg-warning "stack-based buffer overflow" } */
+  /* { dg-warning "stack-based buffer underwrite" "" { target *-*-* } .-1 } */
+  /* { dg-warning "buffer under-read" "" { target *-*-* } .-2 } */
+  /* { dg-warning "buffer over-read" "" { target *-*-* } .-3 } */
+  /* { dg-warning "'memcpy' writing 4096 bytes into a region of size 0 overflows the destination" "" { target *-*-* } .-4 } */
+}
+
+/* { dg-begin-multiline-output "" }
+
+  ┌─────────────────────────────────────────────────────────────────────────┐
+  │                           read of 4096 bytes                            │
+  └─────────────────────────────────────────────────────────────────────────┘
+           ^               ^          ^           ^                ^
+           │               │          │           │                │
+           │               │          │           │                │
+  ┌──────────────────┐┌──────────┬──────────┬────────────┐┌─────────────────┐
+  │                  ││   [0]    │   ...    │   [445]    ││                 │
+  │before valid range│├──────────┴──────────┴────────────┤│after valid range│
+  │                  ││string literal (type: 'char[446]')││                 │
+  └──────────────────┘└──────────────────────────────────┘└─────────────────┘
+  ├────────┬─────────┤├────────────────┬─────────────────┤├────────┬────────┤
+           │                           │                           │
+  ╭────────┴──────────────╮    ╭───────┴───────╮       ╭───────────┴───────────╮
+  │under-read of 100 bytes│    │size: 446 bytes│       │over-read of 3550 bytes│
+  ╰───────────────────────╯    ╰───────────────╯       ╰───────────────────────╯
+
+   { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+
+  ┌──────────────────────────────────────────────────────────────────────┐
+  │                         write of 4096 bytes                          │
+  └──────────────────────────────────────────────────────────────────────┘
+           │              │       │        │                 │
+           │              │       │        │                 │
+           v              v       v        v                 v
+  ┌──────────────────┐┌───────┬───────┬─────────┐┌───────────────────────┐
+  │                  ││  [0]  │  ...  │  [99]   ││                       │
+  │before valid range│├───────┴───────┴─────────┤│   after valid range   │
+  │                  ││'buf' (type: 'char[100]')││                       │
+  └──────────────────┘└─────────────────────────┘└───────────────────────┘
+  ├────────┬─────────┤├────────────┬────────────┤├───────────┬───────────┤
+           │                       │                         │
+           │             ╭─────────┴─────────╮   ╭───────────┴──────────╮
+           │             │capacity: 100 bytes│   │overflow of 3946 bytes│
+           │             ╰───────────────────╯   ╰──────────────────────╯
+  ╭────────┴─────────────╮
+  │underwrite of 50 bytes│
+  ╰──────────────────────╯
+
+   { dg-end-multiline-output "" } */
+
+/* The read and write diagrams are each emitted twice: once for the "before"
+   and once for the "after" diagnostic.  */
+
+/* { dg-begin-multiline-output "" }
+
+  ┌─────────────────────────────────────────────────────────────────────────┐
+  │                           read of 4096 bytes                            │
+  └─────────────────────────────────────────────────────────────────────────┘
+           ^               ^          ^           ^                ^
+           │               │          │           │                │
+           │               │          │           │                │
+  ┌──────────────────┐┌──────────┬──────────┬────────────┐┌─────────────────┐
+  │                  ││   [0]    │   ...    │   [445]    ││                 │
+  │before valid range│├──────────┴──────────┴────────────┤│after valid range│
+  │                  ││string literal (type: 'char[446]')││                 │
+  └──────────────────┘└──────────────────────────────────┘└─────────────────┘
+  ├────────┬─────────┤├────────────────┬─────────────────┤├────────┬────────┤
+           │                           │                           │
+  ╭────────┴──────────────╮    ╭───────┴───────╮       ╭───────────┴───────────╮
+  │under-read of 100 bytes│    │size: 446 bytes│       │over-read of 3550 bytes│
+  ╰───────────────────────╯    ╰───────────────╯       ╰───────────────────────╯
+
+   { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+
+  ┌──────────────────────────────────────────────────────────────────────┐
+  │                         write of 4096 bytes                          │
+  └──────────────────────────────────────────────────────────────────────┘
+           │              │       │        │                 │
+           │              │       │        │                 │
+           v              v       v        v                 v
+  ┌──────────────────┐┌───────┬───────┬─────────┐┌───────────────────────┐
+  │                  ││  [0]  │  ...  │  [99]   ││                       │
+  │before valid range│├───────┴───────┴─────────┤│   after valid range   │
+  │                  ││'buf' (type: 'char[100]')││                       │
+  └──────────────────┘└─────────────────────────┘└───────────────────────┘
+  ├────────┬─────────┤├────────────┬────────────┤├───────────┬───────────┤
+           │                       │                         │
+           │             ╭─────────┴─────────╮   ╭───────────┴──────────╮
+           │             │capacity: 100 bytes│   │overflow of 3946 bytes│
+           │             ╰───────────────────╯   ╰──────────────────────╯
+  ╭────────┴─────────────╮
+  │underwrite of 50 bytes│
+  ╰──────────────────────╯
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-7.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-7.c
new file mode 100644
index 00000000000..25a9acc2f96
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-7.c
@@ -0,0 +1,36 @@ 
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode" } */
+
+#include <string.h>
+
+void
+test_string_with_control_chars ()
+{
+  char buf[8];
+  strcpy (buf, "\tone\n\ttwo\n"); /* { dg-warning "stack-based buffer overflow" } */
+  /* { dg-warning "'__builtin_memcpy' writing 11 bytes into a region of size 8 overflows the destination" "" { target *-*-* } .-1 } */
+}
+
+/* { dg-begin-multiline-output "" }
+
+  ┌──────┬──────┬──────┬─────┬─────┬─────┬─────┬─────┐┌─────┬─────┬──────┐
+  │ [0]  │ [1]  │ [2]  │ [3] │ [4] │ [5] │ [6] │ [7] ││ [8] │ [9] │ [10] │
+  ├──────┼──────┼──────┼─────┼─────┼─────┼─────┼─────┤├─────┼─────┼──────┤
+  │ 0x09 │ 'o'  │ 'n'  │ 'e' │0x0a │0x09 │ 't' │ 'w' ││ 'o' │0x0a │ NUL  │
+  ├──────┴──────┴──────┴─────┴─────┴─────┴─────┴─────┴┴─────┴─────┴──────┤
+  │                  string literal (type: 'char[11]')                   │
+  └──────────────────────────────────────────────────────────────────────┘
+     │      │      │      │     │     │     │     │      │     │     │
+     │      │      │      │     │     │     │     │      │     │     │
+     v      v      v      v     v     v     v     v      v     v     v
+  ┌──────┬─────────────────────────────────────┬─────┐┌──────────────────┐
+  │ [0]  │                 ...                 │ [7] ││                  │
+  ├──────┴─────────────────────────────────────┴─────┤│after valid range │
+  │             'buf' (type: 'char[8]')              ││                  │
+  └──────────────────────────────────────────────────┘└──────────────────┘
+  ├────────────────────────┬─────────────────────────┤├────────┬─────────┤
+                           │                                   │
+                  ╭────────┴────────╮                ╭─────────┴─────────╮
+                  │capacity: 8 bytes│                │overflow of 3 bytes│
+                  ╰─────────────────╯                ╰───────────────────╯
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-8.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-8.c
new file mode 100644
index 00000000000..24d87357e2d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-8.c
@@ -0,0 +1,34 @@ 
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode" } */
+
+#include <stdlib.h>
+#include <stdint.h>
+
+/* Gap of 4, then an overflow of 4.  */
+
+void test2 (size_t size)
+{
+  int32_t *buf = __builtin_malloc (size * sizeof(int32_t)); /* { dg-message "\\(1\\) capacity: 'size \\* 4' bytes" } */
+  if (!buf) return;
+
+  buf[size + 1] = 42; /* { dg-warning "heap-based buffer overflow" } */
+  __builtin_free (buf);
+}
+
+/* { dg-begin-multiline-output "" }
+
+                                                     ┌───────────────────┐
+                                                     │write of '(int) 42'│
+                                                     └───────────────────┘
+                                                               │
+                                                               │
+                                                               v
+  ┌───────────────────────────────┐                  ┌───────────────────┐
+  │buffer allocated on heap at (1)│                  │ after valid range │
+  └───────────────────────────────┘                  └───────────────────┘
+  ├───────────────┬───────────────┤├───────┬────────┤├─────────┬─────────┤
+                  │                        │                   │
+    ╭─────────────┴────────────╮       ╭───┴───╮     ╭─────────┴─────────╮
+    │capacity: 'size * 4' bytes│       │4 bytes│     │overflow of 4 bytes│
+    ╰──────────────────────────╯       ╰───────╯     ╰───────────────────╯
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-9.c b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-9.c
new file mode 100644
index 00000000000..bb9ad660fae
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/out-of-bounds-diagram-9.c
@@ -0,0 +1,42 @@ 
+/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode" } */
+
+#include <stdint.h>
+
+struct st
+{
+  char buf[16];
+  int32_t x;
+  int32_t y;
+};
+
+struct st arr[10];
+
+int32_t struct_arr_read_x_element_before_start_far(void)
+{
+  return arr[-100].x; /* { dg-warning "buffer under-read" "warning" } */
+  /* { dg-message "out-of-bounds read from byte -2384 till byte -2381 but 'arr' starts at byte 0" "final event" { target *-*-* } .-1 } */
+  /* { dg-message "valid subscripts for 'arr' are '\\\[0\\\]' to '\\\[9\\\]'" "valid subscript note" { target *-*-* } .-2 } */
+}
+
+// TODO: show index of accessed element
+// TODO: show field of accessed element
+/* { dg-begin-multiline-output "" }
+
+  ┌───────────────────────────┐
+  │read of 'int32_t' (4 bytes)│
+  └───────────────────────────┘
+                ^
+                │
+                │
+  ┌───────────────────────────┐            ┌─────────┬─────────┬─────────┐
+  │                           │            │   [0]   │   ...   │   [9]   │
+  │    before valid range     │            ├─────────┴─────────┴─────────┤
+  │                           │            │'arr' (type: 'struct st[10]')│
+  └───────────────────────────┘            └─────────────────────────────┘
+  ├─────────────┬─────────────┤├────┬─────┤├──────────────┬──────────────┤
+                │                   │                     │
+     ╭──────────┴──────────╮  ╭─────┴────╮        ╭───────┴───────╮
+     │under-read of 4 bytes│  │2380 bytes│        │size: 240 bytes│
+     ╰─────────────────────╯  ╰──────────╯        ╰───────────────╯
+
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c b/gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c
index 7c8d1b33fc9..5b8ff7bc2e8 100644
--- a/gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c
+++ b/gcc/testsuite/gcc.dg/analyzer/pattern-test-2.c
@@ -26,7 +26,7 @@  void test_2 (void *p, void *q)
   foo(p);
 
   /* { dg-warning "pattern match on 'p != 0'" "p != 0" { target *-*-* } cond_2 } */
-  /* { dg-warning "pattern match on 'tmp1 | tmp2 != 0'" "tmp1 | tmp2 != 0" { target *-*-* } cond_2 } */
+  /* { dg-warning "pattern match on 'p == 0 | q == 0 != 0'" "tmp1 | tmp2 != 0" { target *-*-* } cond_2 } */
   /* { dg-warning "pattern match on 'q != 0'" "q != 0" { target *-*-* } cond_2 } */
 }
 
@@ -42,6 +42,6 @@  void test_3 (void *p, void *q)
   foo(p);
 
   /* { dg-warning "pattern match on 'p == 0'" "p == 0" { target *-*-* } cond_3 } */
-  /* { dg-warning "pattern match on 'tmp1 & tmp2 == 0'" "tmp1 & tmp2 == 0" { target *-*-* } cond_3 } */
+  /* { dg-warning "pattern match on 'p == 0 & q == 0 == 0'" "tmp1 & tmp2 == 0" { target *-*-* } cond_3 } */
   /* { dg-warning "pattern match on 'q == 0'" "q == 0" { target *-*-* } cond_3 } */
 }
diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c b/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c
index e494315bb34..e0fc9cd3e47 100644
--- a/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c
+++ b/gcc/testsuite/gcc.dg/plugin/analyzer_gil_plugin.c
@@ -155,7 +155,7 @@  class double_save_thread : public gil_diagnostic
     return m_call == sub_other.m_call;
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     return warning_at (rich_loc, get_controlling_option (),
 		       "nested usage of %qs", "Py_BEGIN_ALLOW_THREADS");
@@ -194,7 +194,7 @@  class fncall_without_gil : public gil_diagnostic
 	    && m_arg_idx == sub_other.m_arg_idx);
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     auto_diagnostic_group d;
     if (m_callee_fndecl)
@@ -245,7 +245,7 @@  class pyobject_usage_without_gil : public gil_diagnostic
 			((const pyobject_usage_without_gil&)base_other).m_expr);
   }
 
-  bool emit (rich_location *rich_loc) final override
+  bool emit (rich_location *rich_loc, logger *) final override
   {
     auto_diagnostic_group d;
     return warning_at (rich_loc, get_controlling_option (),