/* Text art visualizations within -fanalyzer.
Copyright (C) 2023-2025 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
. */
#include "config.h"
#define INCLUDE_ALGORITHM
#define INCLUDE_MAP
#define INCLUDE_SET
#define INCLUDE_VECTOR
#include "system.h"
#include "coretypes.h"
#include "coretypes.h"
#include "tree.h"
#include "function.h"
#include "basic-block.h"
#include "gimple.h"
#include "diagnostic-core.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"
#include "analyzer/analyzer-selftests.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 (accessed_range.get_size (op.m_model.get_manager ()));
if (type)
{
styled_string s;
pretty_printer pp;
pp_format_decoder (&pp) = default_tree_printer;
if (num_bits.maybe_print_for_user (&pp, op.m_model))
{
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)
{
if (auto p
= num_bits.maybe_get_formatted_str (sm, op.m_model,
_("read of %wi bit"),
_("read of %wi bits"),
_("read of %wi byte"),
_("read of %wi bytes"),
_("read of %qs bits"),
_("read of %qs bytes")))
return std::move (*p.get ());
}
else
{
if (auto p
= num_bits.maybe_get_formatted_str (sm, op.m_model,
_("write of %wi bit"),
_("write of %wi bits"),
_("write of %wi byte"),
_("write of %wi bytes"),
_("write of %qs bits"),
_("write of %qs bytes")))
return std::move (*p.get ());
}
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;
}
/* Duplicate EXPR, replacing any SSA names with the underlying variable. */
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++)
if (TREE_OPERAND (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. */
/* Attempt to generate a user-facing styled string that mentions this
bit_size_expr.
Use MODEL for extracting representative tree values where necessary.
The CONCRETE_* format strings should contain a single %wi.
The SYMBOLIC_* format strings should contain a single %qs.
Return nullptr if unable to represent the expression. */
std::unique_ptr
bit_size_expr::maybe_get_formatted_str (text_art::style_manager &sm,
const region_model &model,
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
{
region_model_manager &mgr = *model.get_manager ();
if (const svalue *num_bytes = maybe_get_as_bytes (mgr))
{
if (tree cst = num_bytes->maybe_get_constant ())
{
byte_size_t concrete_num_bytes = wi::to_offset (cst);
if (!wi::fits_uhwi_p (concrete_num_bytes))
return nullptr;
if (concrete_num_bytes == 1)
return ::make_unique
(fmt_styled_string (sm, concrete_single_byte_fmt,
concrete_num_bytes.to_uhwi ()));
else
return ::make_unique
(fmt_styled_string (sm, concrete_plural_bytes_fmt,
concrete_num_bytes.to_uhwi ()));
}
else
{
pretty_printer pp;
pp_format_decoder (&pp) = default_tree_printer;
if (!num_bytes->maybe_print_for_user (&pp, model))
return nullptr;
return ::make_unique
(fmt_styled_string (sm, symbolic_bytes_fmt,
pp_formatted_text (&pp)));
}
}
else if (tree cst = m_num_bits.maybe_get_constant ())
{
bit_size_t concrete_num_bits = wi::to_offset (cst);
if (!wi::fits_uhwi_p (concrete_num_bits))
return nullptr;
if (concrete_num_bits == 1)
return ::make_unique
(fmt_styled_string (sm, concrete_single_bit_fmt,
concrete_num_bits.to_uhwi ()));
else
return ::make_unique
(fmt_styled_string (sm, concrete_plural_bits_fmt,
concrete_num_bits.to_uhwi ()));
}
else
{
pretty_printer pp;
pp_format_decoder (&pp) = default_tree_printer;
if (!m_num_bits.maybe_print_for_user (&pp, model))
return nullptr;
return ::make_unique
(fmt_styled_string (sm, symbolic_bits_fmt,
pp_formatted_text (&pp)));
}
}
bool
bit_size_expr::maybe_print_for_user (pretty_printer *pp,
const region_model &model) const
{
if (tree cst = m_num_bits.maybe_get_constant ())
{
bit_size_t concrete_num_bits = wi::to_offset (cst);
pp_bit_size_t (pp, concrete_num_bits);
return true;
}
else
{
if (const svalue *num_bytes = maybe_get_as_bytes (*model.get_manager ()))
{
pretty_printer tmp_pp;
pp_format_decoder (&tmp_pp) = default_tree_printer;
if (!num_bytes->maybe_print_for_user (&tmp_pp, model))
return false;
pp_printf (pp, _("%qs bytes"), pp_formatted_text (&tmp_pp));
return true;
}
else
{
pretty_printer tmp_pp;
pp_format_decoder (&tmp_pp) = default_tree_printer;
if (!m_num_bits.maybe_print_for_user (&tmp_pp, model))
return false;
pp_printf (pp, _("%qs bits"), pp_formatted_text (&tmp_pp));
return true;
}
}
}
/* Attempt to get a symbolic value for this symbolic bit size,
expressed in bytes.
Return null if it's not known to divide exactly. */
const svalue *
bit_size_expr::maybe_get_as_bytes (region_model_manager &mgr) const
{
if (tree cst = m_num_bits.maybe_get_constant ())
{
bit_offset_t concrete_bits = wi::to_offset (cst);
if (concrete_bits % BITS_PER_UNIT != 0)
/* Not an exact multiple, so fail. */
return nullptr;
}
const svalue *bits_per_byte
= mgr.get_or_create_int_cst (NULL_TREE, BITS_PER_UNIT);
return mgr.maybe_fold_binop (NULL_TREE, EXACT_DIV_EXPR,
&m_num_bits, bits_per_byte);
}
/* 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 ®, region_model_manager *mgr)
: m_start (strip_types (reg.get_offset (mgr), *mgr)),
m_next (strip_types (reg.get_next_offset (mgr), *mgr))
{
}
bit_size_expr
access_range::get_size (region_model_manager *mgr) const
{
const svalue &start_bit_offset = m_start.calc_symbolic_bit_offset (mgr);
const svalue &next_bit_offset = m_next.calc_symbolic_bit_offset (mgr);
return bit_size_expr
(*mgr->get_or_create_binop (NULL_TREE, MINUS_EXPR,
&next_bit_offset, &start_bit_offset));
}
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
{
tree_dump_pretty_printer pp (stderr);
dump_to_pp (&pp, simple);
pp_newline (&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),
*get_manager ());
}
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,
*get_manager ());
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,
*get_manager ());
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, logger *logger)
: m_base_reg (base_reg), m_logger (logger)
{
}
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);
if (m_logger)
{
m_logger->start_log_line ();
m_logger->log_partial ("added access_range: ");
range.dump_to_pp (m_logger->get_printer (), true);
m_logger->log_partial (" (%s)",
(kind == boundaries::kind::HARD)
? "HARD" : "soft");
m_logger->end_log_line ();
}
}
void add (const region ®, region_model_manager *mgr, enum kind kind)
{
add (access_range (reg.get_offset (mgr),
reg.get_next_offset (mgr),
*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::const_iterator begin () const
{
return m_all_offsets.begin ();
}
std::set::const_iterator end () const
{
return m_all_offsets.end ();
}
std::set::size_type size () const
{
return m_all_offsets.size ();
}
std::vector
get_hard_boundaries_in_range (byte_offset_t min_offset,
byte_offset_t max_offset) const
{
std::vector result;
for (auto &offset : m_hard_offsets)
{
if (!offset.concrete_p ())
continue;
byte_offset_t byte;
if (!offset.get_concrete_byte_offset (&byte))
continue;
if (byte < min_offset)
continue;
if (byte > max_offset)
continue;
result.push_back (offset);
}
return result;
}
private:
const region &m_base_reg;
logger *m_logger;
std::set m_all_offsets;
std::set 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)
: m_dia_impl (dia_impl),
m_theme (theme)
{
}
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;
std::vector