/* 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
. */
#include "config.h"
#define INCLUDE_ALGORITHM
#define INCLUDE_MEMORY
#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.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 ®, 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 ®, 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::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 ();
}
private:
const region &m_base_reg;
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,
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