blob: 500480b6832835e161f2f80902e37a7199ce9a34 [file] [log] [blame]
/* Text art visualizations within -fanalyzer.
Copyright (C) 2023-2024 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
#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<text_art::styled_string>
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 <text_art::styled_string>
(fmt_styled_string (sm, concrete_single_byte_fmt,
concrete_num_bytes.to_uhwi ()));
else
return ::make_unique <text_art::styled_string>
(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 <text_art::styled_string>
(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 <text_art::styled_string>
(fmt_styled_string (sm, concrete_single_bit_fmt,
concrete_num_bits.to_uhwi ()));
else
return ::make_unique <text_art::styled_string>
(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 <text_art::styled_string>
(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 &reg, 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
{
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),
*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 &reg, 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<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 ();
}
std::vector<region_offset>
get_hard_boundaries_in_range (byte_offset_t min_offset,
byte_offset_t max_offset) const
{
std::vector<region_offset> 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<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)
: 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<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,
region_model_manager &mgr,
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, mgr);
}
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 ();
}
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;
}
private:
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 ~spatial_item () {}
virtual void add_boundaries (boundaries &out, logger *) const = 0;
virtual table make_table (const bit_to_table_map &btm,
style_manager &sm) const = 0;
};
/* A spatial_item that involves showing an svalue at a particular offset. */
class svalue_spatial_item : public spatial_item
{
public:
enum class kind
{
WRITTEN,
EXISTING
};
protected:
svalue_spatial_item (const svalue &sval,
access_range bits,
enum kind kind)
: m_sval (sval), m_bits (bits), m_kind (kind)
{
}
const svalue &m_sval;
access_range m_bits;
enum kind m_kind;
};
static std::unique_ptr<spatial_item>
make_existing_svalue_spatial_item (const svalue *sval,
const access_range &bits,
const theme &theme);
class compound_svalue_spatial_item : public svalue_spatial_item
{
public:
compound_svalue_spatial_item (const compound_svalue &sval,
const access_range &bits,
enum kind kind,
const theme &theme)
: svalue_spatial_item (sval, bits, kind),
m_compound_sval (sval)
{
const binding_map &map = m_compound_sval.get_map ();
auto_vec <const binding_key *> binding_keys;
for (auto iter : map)
{
const binding_key *key = iter.first;
const svalue *bound_sval = iter.second;
if (const concrete_binding *concrete_key
= key->dyn_cast_concrete_binding ())
{
access_range range (nullptr,
concrete_key->get_bit_range ());
if (std::unique_ptr<spatial_item> child
= make_existing_svalue_spatial_item (bound_sval,
range,
theme))
m_children.push_back (std::move (child));
}
}
}
void add_boundaries (boundaries &out, logger *logger) const final override
{
LOG_SCOPE (logger);
for (auto &iter : m_children)
iter->add_boundaries (out, logger);
}
table make_table (const bit_to_table_map &btm,
style_manager &sm) const final override
{
std::vector<table> child_tables;
int max_rows = 0;
for (auto &iter : m_children)
{
table child_table (iter->make_table (btm, sm));
max_rows = MAX (max_rows, child_table.get_size ().h);
child_tables.push_back (std::move (child_table));
}
table t (table::size_t (btm.get_num_columns (), max_rows));
for (auto &&child_table : child_tables)
t.add_other_table (std::move (child_table),
table::coord_t (0, 0));
return t;
}
private:
const compound_svalue &m_compound_sval;
std::vector<std::unique_ptr<spatial_item>> m_children;
};
/* Loop through the TABLE_X_RANGE columns of T, adding
cells containing "..." in any unoccupied ranges of table cell. */
static void
add_ellipsis_to_gaps (table &t,
style_manager &sm,
const table::range_t &table_x_range,
const table::range_t &table_y_range)
{
int table_x = table_x_range.get_min ();
while (table_x < table_x_range.get_next ())
{
/* Find a run of unoccupied table cells. */
const int start_table_x = table_x;
while (table_x < table_x_range.get_next ()
&& !t.get_placement_at (table::coord_t (table_x,
table_y_range.get_min ())))
table_x++;
const table::range_t unoccupied_x_range (start_table_x, table_x);
if (unoccupied_x_range.get_size () > 0)
t.set_cell_span (table::rect_t (unoccupied_x_range, table_y_range),
styled_string (sm, "..."));
/* Skip occupied table cells. */
while (table_x < table_x_range.get_next ()
&& t.get_placement_at (table::coord_t (table_x,
table_y_range.get_min ())))
table_x++;
}
}
/* 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,
const theme &theme)
: m_op (op),
m_region_creation_event_id (region_creation_event_id),
m_boundaries (nullptr),
m_existing_sval (op.m_model.get_store_value (op.m_base_region, nullptr)),
m_existing_sval_spatial_item
(make_existing_svalue_spatial_item (m_existing_sval,
op.get_valid_bits (),
theme))
{
}
void add_boundaries (boundaries &out, logger *logger) const final override
{
LOG_SCOPE (logger);
m_boundaries = &out;
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);
if (m_existing_sval_spatial_item)
{
if (logger)
{
logger->start_log_line ();
logger->log_partial ("existing svalue: ");
m_existing_sval->dump_to_pp (logger->get_printer (), true);
logger->end_log_line ();
}
m_existing_sval_spatial_item->add_boundaries (out, logger);
}
/* 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 (domain && 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);
gcc_assert (m_boundaries != nullptr);
tree domain = TYPE_DOMAIN (base_type);
if (!(domain && TYPE_MIN_VALUE (domain) && TYPE_MAX_VALUE (domain)))
return;
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 table::range_t min_x_range
= maybe_add_array_index_to_table (t, btm, sm, table_y_range,
TYPE_MIN_VALUE (domain));
const table::range_t max_x_range
= maybe_add_array_index_to_table (t, btm, sm, table_y_range,
TYPE_MAX_VALUE (domain));
if (TREE_TYPE (base_type) == char_type_node)
{
/* For a char array,: if there are any hard boundaries in
m_boundaries that are *within* the valid region,
then show those index values. */
std::vector<region_offset> hard_boundaries
= m_boundaries->get_hard_boundaries_in_range
(tree_to_shwi (TYPE_MIN_VALUE (domain)),
tree_to_shwi (TYPE_MAX_VALUE (domain)));
for (auto &offset : hard_boundaries)
{
const int table_x = btm.get_table_x_for_offset (offset);
if (!offset.concrete_p ())
continue;
byte_offset_t byte;
if (!offset.get_concrete_byte_offset (&byte))
continue;
table::range_t table_x_range (table_x, table_x + 1);
t.maybe_set_cell_span (table::rect_t (table_x_range,
table_y_range),
fmt_styled_string (sm, "[%wi]",
byte.to_shwi ()));
}
}
add_ellipsis_to_gaps (t, sm,
table::range_t (min_x_range.get_next (),
max_x_range.get_min ()),
table_y_range);
}
table::range_t
maybe_add_array_index_to_table (table &t,
const bit_to_table_map &btm,
style_manager &sm,
const table::range_t table_y_range,
tree idx_cst) const
{
region_model_manager * const mgr = m_op.get_manager ();
tree base_type = m_op.m_base_region->get_type ();
const svalue *idx_sval
= mgr->get_or_create_constant_svalue (idx_cst);
const region *element_reg = mgr->get_element_region (m_op.m_base_region,
TREE_TYPE (base_type),
idx_sval);
const access_range element_range (*element_reg, mgr);
const table::range_t element_x_range
= btm.get_table_x_for_range (element_range);
t.maybe_set_cell_span (table::rect_t (element_x_range,
table_y_range),
fmt_styled_string (sm, "[%E]", idx_cst));
return element_x_range;
}
table make_table (const bit_to_table_map &btm,
style_manager &sm) const final override
{
table t (table::size_t (btm.get_num_columns (), 0));
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);
/* Make use of m_existing_sval_spatial_item, if any. */
if (m_existing_sval_spatial_item)
{
table table_for_existing
= m_existing_sval_spatial_item->make_table (btm, sm);
const int table_y = t.add_rows (table_for_existing.get_size ().h);
t.add_other_table (std::move (table_for_existing),
table::coord_t (0, table_y));
}
access_range valid_bits = m_op.get_valid_bits ();
const int table_y = t.add_row ();
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;
mutable const boundaries *m_boundaries;
const svalue *m_existing_sval;
std::unique_ptr<spatial_item> m_existing_sval_spatial_item;
};
/* 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 written_svalue_spatial_item : public spatial_item
{
public:
written_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_literal_spatial_item : public svalue_spatial_item
{
public:
string_literal_spatial_item (const svalue &sval,
access_range actual_bits,
const string_region &string_reg,
const theme &theme,
enum kind kind)
: svalue_spatial_item (sval, actual_bits, kind),
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_bits, m_kind == svalue_spatial_item::kind::WRITTEN
? boundaries::kind::HARD
: boundaries::kind::SOFT);
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_bits);
else
{
byte_range bytes (0, 0);
bool valid = m_bits.as_concrete_byte_range (&bytes);
gcc_assert (valid);
byte_range head_of_string (bytes.get_start_byte_offset (),
m_ellipsis_head_len);
out.add_all_bytes_in_range (head_of_string);
byte_range tail_of_string
((bytes.get_start_byte_offset ()
+ 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 = (m_kind == svalue_spatial_item::kind::WRITTEN
? t.add_row ()
: -1);
const int byte_val_table_y = t.add_row ();
byte_range bytes (0, 0);
bool valid = m_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_within_cluster
= bytes.get_start_byte_offset ();
byte_idx_within_cluster < bytes.get_next_byte_offset ();
byte_idx_within_cluster = byte_idx_within_cluster + 1)
add_column_for_byte
(t, btm, sm, byte_idx_within_cluster,
byte_idx_within_cluster - bytes.get_start_byte_offset (),
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 cluster_bytes_for_codepoint
(start_byte_idx + bytes.get_start_byte_offset (),
size_in_bytes);
const table::rect_t code_point_table_rect
= btm.get_table_rect (&m_string_reg,
cluster_bytes_for_codepoint,
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,
cluster_bytes_for_codepoint,
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 + bytes.get_start_byte_offset (),
byte_idx,
byte_idx_table_y, byte_val_table_y);
/* Ellipsis. */
const byte_range ellipsis_bytes
(m_ellipsis_head_len + bytes.get_start_byte_offset (),
TREE_STRING_LENGTH (string_cst)
- (m_ellipsis_head_len + m_ellipsis_tail_len));
const table::rect_t table_rect
= ((byte_idx_table_y != -1)
? btm.get_table_rect (&m_string_reg, ellipsis_bytes,
byte_idx_table_y, 2)
: btm.get_table_rect (&m_string_reg, ellipsis_bytes,
byte_val_table_y, 1));
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 + bytes.get_start_byte_offset (),
byte_idx,
byte_idx_table_y, byte_val_table_y);
}
if (m_kind == svalue_spatial_item::kind::WRITTEN)
{
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_within_cluster,
const byte_offset_t byte_idx_within_string,
const int byte_idx_table_y,
const int byte_val_table_y) const
{
tree string_cst = get_string_cst ();
gcc_assert (byte_idx_within_string >= 0);
gcc_assert (byte_idx_within_string < TREE_STRING_LENGTH (string_cst));
const byte_range bytes (byte_idx_within_cluster, 1);
if (byte_idx_table_y != -1)
{
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, "[%wu]",
byte_idx_within_string.ulow ()));
}
char byte_val
= TREE_STRING_POINTER (string_cst)[byte_idx_within_string.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;
};
static std::unique_ptr<spatial_item>
make_written_svalue_spatial_item (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_literal_spatial_item>
(sval, actual_bits,
*string_reg, theme,
svalue_spatial_item::kind::WRITTEN);
return make_unique <written_svalue_spatial_item> (op, sval, actual_bits);
}
static std::unique_ptr<spatial_item>
make_existing_svalue_spatial_item (const svalue *sval,
const access_range &bits,
const theme &theme)
{
if (!sval)
return nullptr;
switch (sval->get_kind ())
{
default:
return nullptr;
case SK_INITIAL:
{
const initial_svalue *initial_sval = (const initial_svalue *)sval;
if (const string_region *string_reg
= initial_sval->get_region ()->dyn_cast_string_region ())
return make_unique <string_literal_spatial_item>
(*sval, bits,
*string_reg, theme,
svalue_spatial_item::kind::EXISTING);
return nullptr;
}
case SK_COMPOUND:
return make_unique<compound_svalue_spatial_item>
(*((const compound_svalue *)sval),
bits,
svalue_spatial_item::kind::EXISTING,
theme);
}
}
/* 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, theme),
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 (get_style_from_color_cap_name ("valid"));
m_valid_style_id = m_sm.get_or_create_id (valid_style);
style invalid_style (get_style_from_color_cap_name ("invalid"));
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_written_svalue_spatial_item
= make_written_svalue_spatial_item (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, *m_op.get_manager (), 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_written_svalue_spatial_item)
{
table t_sval (m_written_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_logger);
m_valid_region_spatial_item.add_boundaries (*result, m_logger);
m_accessed_region_spatial_item.add_boundaries (*result, m_logger);
if (m_written_svalue_spatial_item)
m_written_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);
}
region_model_manager *mgr = m_op.get_manager ();
const svalue &lower_next = lower.m_next.calc_symbolic_bit_offset (mgr);
const svalue &upper_start = upper.m_start.calc_symbolic_bit_offset (mgr);
const svalue *num_bits_gap
= mgr->get_or_create_binop (NULL_TREE, MINUS_EXPR,
&upper_start, &lower_next);
if (m_logger)
m_logger->log ("num_bits_gap: %qs", num_bits_gap->get_desc ().get ());
const svalue *zero = mgr->get_or_create_int_cst (NULL_TREE, 0);
tristate ts_gt_zero = m_op.m_model.eval_condition (num_bits_gap,
GT_EXPR,
zero);
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);
if (auto p = num_bits.maybe_get_formatted_str (m_sm, m_op.m_model,
_("%wi bit"),
_("%wi bits"),
_("%wi byte"),
_("%wi bytes"),
_("%qs bits"),
_("%qs bytes")))
{
styled_string label = std::move (*p.get ());
w->add_range (m_btm.get_table_x_for_range
(access_range (lower.m_next,
upper.m_start,
*mgr)),
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);
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
(invalid_before_bits.get_size (m_op.get_manager ()));
std::unique_ptr<styled_string> label;
if (m_op.m_dir == DIR_READ)
label = num_before_bits.maybe_get_formatted_str
(m_sm, m_op.m_model,
_("under-read of %wi bit"),
_("under-read of %wi bits"),
_("under-read of %wi byte"),
_("under-read of %wi bytes"),
_("under-read of %qs bits"),
_("under-read of %qs bytes"));
else
label = num_before_bits.maybe_get_formatted_str
(m_sm, m_op.m_model,
_("underwrite of %wi bit"),
_("underwrite of %wi bits"),
_("underwrite of %wi byte"),
_("underwrite of %wi bytes"),
_("underwrite of %qs bits"),
_("underwrite of %qs bytes"));
if (label)
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 (valid_bits.get_size (m_op.get_manager ()));
if (m_logger)
valid_bits.log ("valid_bits", *m_logger);
got_valid_bits = true;
maybe_add_gap (w, invalid_before_bits, valid_bits);
std::unique_ptr<styled_string> label;
if (m_op.m_dir == DIR_READ)
label = num_valid_bits.maybe_get_formatted_str (m_sm,
m_op.m_model,
_("size: %wi bit"),
_("size: %wi bits"),
_("size: %wi byte"),
_("size: %wi bytes"),
_("size: %qs bits"),
_("size: %qs bytes"));
else
label
= num_valid_bits.maybe_get_formatted_str (m_sm,
m_op.m_model,
_("capacity: %wi bit"),
_("capacity: %wi bits"),
_("capacity: %wi byte"),
_("capacity: %wi bytes"),
_("capacity: %qs bits"),
_("capacity: %qs bytes"));
if (label)
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
(invalid_after_bits.get_size (m_op.get_manager ()));
std::unique_ptr<styled_string> label;
if (m_op.m_dir == DIR_READ)
label = num_after_bits.maybe_get_formatted_str
(m_sm, m_op.m_model,
_("over-read of %wi bit"),
_("over-read of %wi bits"),
_("over-read of %wi byte"),
_("over-read of %wi bytes"),
_("over-read of %qs bits"),
_("over-read of %qs bytes"));
else
label = num_after_bits.maybe_get_formatted_str
(m_sm, m_op.m_model,
_("overflow of %wi bit"),
_("overflow of %wi bits"),
_("overflow of %wi byte"),
_("overflow of %wi bytes"),
_("overflow of %qs bits"),
_("overflow of %qs bytes"));
if (label)
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<spatial_item> m_written_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))
{
}
#if CHECKING_P
namespace selftest {
/* Implementation detail of ASSERT_EQ_TYPELESS_INTEGER. */
static void
assert_eq_typeless_integer (const location &loc,
const svalue *sval,
int expected_int_val)
{
ASSERT_NE_AT (loc, sval, nullptr);
ASSERT_EQ_AT (loc, sval->get_kind (), SK_CONSTANT);
ASSERT_EQ_AT (loc,
wi::to_offset (sval->maybe_get_constant ()),
expected_int_val);
ASSERT_EQ_AT (loc, sval->get_type (), NULL_TREE);
}
/* Assert that SVAL is a constant_svalue equal to EXPECTED_INT_VAL,
with NULL_TREE as its type. */
#define ASSERT_EQ_TYPELESS_INTEGER(SVAL, EXPECTED_INT_VAL) \
SELFTEST_BEGIN_STMT \
assert_eq_typeless_integer ((SELFTEST_LOCATION), \
(SVAL), \
(EXPECTED_INT_VAL)); \
SELFTEST_END_STMT
/* Various tests of bit_size_expr::maybe_get_as_bytes. */
static void
test_bit_size_expr_to_bytes ()
{
region_model_manager mgr;
/* 40 bits: should be 5 bytes. */
{
bit_size_expr num_bits (*mgr.get_or_create_int_cst (NULL_TREE, 40));
const svalue *as_bytes = num_bits.maybe_get_as_bytes (mgr);
ASSERT_EQ_TYPELESS_INTEGER (as_bytes, 5);
}
/* 41 bits: should not convert to bytes. */
{
bit_size_expr num_bits (*mgr.get_or_create_int_cst (NULL_TREE, 41));
const svalue *as_bytes = num_bits.maybe_get_as_bytes (mgr);
ASSERT_EQ (as_bytes, nullptr);
}
tree n = build_global_decl ("n", size_type_node);
const svalue *init_n
= mgr.get_or_create_initial_value (mgr.get_region_for_global (n));
const svalue *n_times_8
= mgr.get_or_create_binop (NULL_TREE, MULT_EXPR,
init_n,
mgr.get_or_create_int_cst (NULL_TREE, 8));
/* (n * 8) bits should be n bytes */
{
bit_size_expr num_bits (*n_times_8);
const svalue *as_bytes = num_bits.maybe_get_as_bytes (mgr);
ASSERT_EQ (as_bytes, mgr.get_or_create_cast (NULL_TREE, init_n));
}
/* (n * 8) + 16 bits should be n + 2 bytes */
{
bit_size_expr num_bits
(*mgr.get_or_create_binop (NULL_TREE, PLUS_EXPR,
n_times_8,
mgr.get_or_create_int_cst (NULL_TREE, 16)));
const svalue *as_bytes = num_bits.maybe_get_as_bytes (mgr);
ASSERT_EQ (as_bytes->get_kind (), SK_BINOP);
const binop_svalue *binop = as_bytes->dyn_cast_binop_svalue ();
ASSERT_EQ (binop->get_op (), PLUS_EXPR);
ASSERT_EQ (binop->get_arg0 (), mgr.get_or_create_cast (NULL_TREE, init_n));
ASSERT_EQ_TYPELESS_INTEGER (binop->get_arg1 (), 2);
}
}
/* Run all of the selftests within this file. */
void
analyzer_access_diagram_cc_tests ()
{
test_bit_size_expr_to_bytes ();
}
} // namespace selftest
#endif /* CHECKING_P */
} // namespace ana
#endif /* #if ENABLE_ANALYZER */