aboutsummaryrefslogtreecommitdiff
path: root/gcc/analyzer/access-diagram.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/analyzer/access-diagram.cc')
-rw-r--r--gcc/analyzer/access-diagram.cc2406
1 files changed, 2406 insertions, 0 deletions
diff --git a/gcc/analyzer/access-diagram.cc b/gcc/analyzer/access-diagram.cc
new file mode 100644
index 0000000..467c9bd
--- /dev/null
+++ b/gcc/analyzer/access-diagram.cc
@@ -0,0 +1,2406 @@
+/* Text art visualizations within -fanalyzer.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#define INCLUDE_ALGORITHM
+#define INCLUDE_MEMORY
+#define INCLUDE_MAP
+#define INCLUDE_SET
+#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 &reg, region_model_manager *mgr)
+: m_start (reg.get_offset (mgr)),
+ m_next (reg.get_next_offset (mgr))
+{
+}
+
+bool
+access_range::get_size (const region_model &model, bit_size_expr *out) const
+{
+ tree start_expr = m_start.calc_symbolic_bit_offset (model);
+ if (!start_expr)
+ return false;
+ tree next_expr = m_next.calc_symbolic_bit_offset (model);
+ if (!next_expr)
+ return false;
+ *out = bit_size_expr (fold_build2 (MINUS_EXPR, size_type_node,
+ next_expr, start_expr));
+ return true;
+}
+
+bool
+access_range::contains_p (const access_range &other) const
+{
+ return (m_start <= other.m_start
+ && other.m_next <= m_next);
+}
+
+bool
+access_range::empty_p () const
+{
+ bit_range concrete_bits (0, 0);
+ if (!as_concrete_bit_range (&concrete_bits))
+ return false;
+ return concrete_bits.empty_p ();
+}
+
+void
+access_range::dump_to_pp (pretty_printer *pp, bool simple) const
+{
+ if (m_start.concrete_p () && m_next.concrete_p ())
+ {
+ bit_range bits (m_start.get_bit_offset (),
+ m_next.get_bit_offset () - m_start.get_bit_offset ());
+ bits.dump_to_pp (pp);
+ return;
+ }
+ pp_character (pp, '[');
+ m_start.dump_to_pp (pp, simple);
+ pp_string (pp, " to ");
+ m_next.dump_to_pp (pp, simple);
+ pp_character (pp, ')');
+}
+
+DEBUG_FUNCTION void
+access_range::dump (bool simple) const
+{
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ pp_show_color (&pp) = pp_show_color (global_dc->printer);
+ pp.buffer->stream = stderr;
+ dump_to_pp (&pp, simple);
+ pp_newline (&pp);
+ pp_flush (&pp);
+}
+
+void
+access_range::log (const char *title, logger &logger) const
+{
+ logger.start_log_line ();
+ logger.log_partial ("%s: ", title);
+ dump_to_pp (logger.get_printer (), true);
+ logger.end_log_line ();
+}
+
+/* struct access_operation. */
+
+access_range
+access_operation::get_valid_bits () const
+{
+ const svalue *capacity_in_bytes_sval = m_model.get_capacity (m_base_region);
+ return access_range
+ (region_offset::make_concrete (m_base_region, 0),
+ region_offset::make_byte_offset (m_base_region, capacity_in_bytes_sval));
+}
+
+access_range
+access_operation::get_actual_bits () const
+{
+ return access_range (m_reg, get_manager ());
+}
+
+/* If there are any bits accessed invalidly before the valid range,
+ return true and write their range to *OUT.
+ Return false if there aren't, or if there's a problem
+ (e.g. symbolic ranges. */
+
+bool
+access_operation::maybe_get_invalid_before_bits (access_range *out) const
+{
+ access_range valid_bits (get_valid_bits ());
+ access_range actual_bits (get_actual_bits ());
+
+ if (actual_bits.m_start >= valid_bits.m_start)
+ {
+ /* No part of accessed range is before the valid range. */
+ return false;
+ }
+ else if (actual_bits.m_next > valid_bits.m_start)
+ {
+ /* Get part of accessed range that's before the valid range. */
+ *out = access_range (actual_bits.m_start, valid_bits.m_start);
+ return true;
+ }
+ else
+ {
+ /* Accessed range is fully before valid range. */
+ *out = actual_bits;
+ return true;
+ }
+}
+
+/* If there are any bits accessed invalidly after the valid range,
+ return true and write their range to *OUT.
+ Return false if there aren't, or if there's a problem. */
+
+bool
+access_operation::maybe_get_invalid_after_bits (access_range *out) const
+{
+ access_range valid_bits (get_valid_bits ());
+ access_range actual_bits (get_actual_bits ());
+
+ if (actual_bits.m_next <= valid_bits.m_next)
+ {
+ /* No part of accessed range is after the valid range. */
+ return false;
+ }
+ else if (actual_bits.m_start < valid_bits.m_next)
+ {
+ /* Get part of accessed range that's after the valid range. */
+ *out = access_range (valid_bits.m_next, actual_bits.m_next);
+ return true;
+ }
+ else
+ {
+ /* Accessed range is fully after valid range. */
+ *out = actual_bits;
+ return true;
+ }
+}
+
+/* A class for capturing all of the region offsets of interest (both concrete
+ and symbolic), to help align everything in the diagram.
+ Boundaries can be soft or hard; hard boundaries are emphasized visually
+ (e.g. the boundary between valid vs invalid accesses).
+
+ Offsets in the boundaries are all expressed relative to the base
+ region of the access_operation. */
+
+class boundaries
+{
+public:
+ enum class kind { HARD, SOFT};
+
+ boundaries (const region &base_reg)
+ : m_base_reg (base_reg)
+ {
+ }
+
+ void add (region_offset offset, enum kind k)
+ {
+ m_all_offsets.insert (offset);
+ if (k == kind::HARD)
+ m_hard_offsets.insert (offset);
+ }
+
+ void add (const access_range &range, enum kind kind)
+ {
+ add (range.m_start, kind);
+ add (range.m_next, kind);
+ }
+
+ void add (const region &reg, region_model_manager *mgr, enum kind kind)
+ {
+ add (access_range (reg.get_offset (mgr),
+ reg.get_next_offset (mgr)),
+ kind);
+ }
+
+ void add (const byte_range bytes, enum kind kind)
+ {
+ add (access_range (&m_base_reg, bytes), kind);
+ }
+
+ void add_all_bytes_in_range (const byte_range &bytes)
+ {
+ for (byte_offset_t byte_idx = bytes.get_start_byte_offset ();
+ byte_idx <= bytes.get_next_byte_offset ();
+ byte_idx = byte_idx + 1)
+ add (region_offset::make_concrete (&m_base_reg, byte_idx * 8),
+ kind::SOFT);
+ }
+
+ void add_all_bytes_in_range (const access_range &range)
+ {
+ byte_range bytes (0, 0);
+ bool valid = range.as_concrete_byte_range (&bytes);
+ gcc_assert (valid);
+ add_all_bytes_in_range (bytes);
+ }
+
+ void log (logger &logger) const
+ {
+ logger.log ("boundaries:");
+ logger.inc_indent ();
+ for (auto offset : m_all_offsets)
+ {
+ enum kind k = get_kind (offset);
+ logger.start_log_line ();
+ logger.log_partial ("%s: ", (k == kind::HARD) ? "HARD" : "soft");
+ offset.dump_to_pp (logger.get_printer (), true);
+ logger.end_log_line ();
+ }
+ logger.dec_indent ();
+ }
+
+ enum kind get_kind (region_offset offset) const
+ {
+ gcc_assert (m_all_offsets.find (offset) != m_all_offsets.end ());
+ if (m_hard_offsets.find (offset) != m_hard_offsets.end ())
+ return kind::HARD;
+ else
+ return kind::SOFT;
+ }
+
+ std::set<region_offset>::const_iterator begin () const
+ {
+ return m_all_offsets.begin ();
+ }
+ std::set<region_offset>::const_iterator end () const
+ {
+ return m_all_offsets.end ();
+ }
+ std::set<region_offset>::size_type size () const
+ {
+ return m_all_offsets.size ();
+ }
+
+private:
+ const region &m_base_reg;
+ std::set<region_offset> m_all_offsets;
+ std::set<region_offset> m_hard_offsets;
+};
+
+/* A widget that wraps a table but offloads column-width calculation
+ to a shared object, so that we can vertically line up multiple tables
+ and have them all align their columns.
+
+ For example, in:
+
+ 01| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+ 02| |[0]|[1]|[2]|[3]|[4]|[5]| ... |[440]|[441]|[442]|[443]|[444]|[445]|
+ 03| +---+---+---+---+---+---+ +-----+-----+-----+-----+-----+-----+
+ 04| |'L'|'o'|'r'|'e'|'m'|' '| | 'o' | 'r' | 'u' | 'm' | '.' | NUL |
+ 05| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+ 06| | string literal (type: 'char[446]') |
+ 07| +----------------------------------------------------------------------+
+ 08| | | | | | | | | | | | | | | |
+ 09| | | | | | | | | | | | | | | |
+ 10| v v v v v v v v v v v v v v v
+ 11|+---+---------------------+----++--------------------------------------+
+ 12||[0]| ... |[99]|| after valid range |
+ 13|+---+---------------------+----+| |
+ 14|| 'buf' (type: 'char[100]') || |
+ 15|+------------------------------++--------------------------------------+
+ 16||~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~||~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~|
+ 17| | |
+ 18| +---------+---------+ +----------+----------+
+ 19| |capacity: 100 bytes| |overflow of 346 bytes|
+ 20| +-------------------+ +---------------------+
+
+ rows 01-07 and rows 11-15 are x_aligned_table_widget instances. */
+
+class x_aligned_table_widget : public leaf_widget
+{
+public:
+ x_aligned_table_widget (table t,
+ const theme &theme,
+ table_dimension_sizes &col_widths)
+ : m_table (std::move (t)),
+ m_theme (theme),
+ m_col_widths (col_widths),
+ m_row_heights (t.get_size ().h),
+ m_cell_sizes (m_col_widths, m_row_heights),
+ m_tg (m_table, m_cell_sizes)
+ {
+ }
+
+ const char *get_desc () const override
+ {
+ return "x_aligned_table_widget";
+ }
+
+ canvas::size_t calc_req_size () final override
+ {
+ /* We don't compute the size requirements;
+ the parent should have done this. */
+ return m_tg.get_canvas_size ();
+ }
+
+ void paint_to_canvas (canvas &canvas) final override
+ {
+ m_table.paint_to_canvas (canvas,
+ get_top_left (),
+ m_tg,
+ m_theme);
+ }
+
+ const table &get_table () const { return m_table; }
+ table_cell_sizes &get_cell_sizes () { return m_cell_sizes; }
+ void recalc_coords ()
+ {
+ m_tg.recalc_coords ();
+ }
+
+private:
+ table m_table;
+ const theme &m_theme;
+ table_dimension_sizes &m_col_widths; // Reference to shared column widths
+ table_dimension_sizes m_row_heights; // Unique row heights
+ table_cell_sizes m_cell_sizes;
+ table_geometry m_tg;
+};
+
+/* A widget for printing arrows between the accessed region
+ and the svalue, showing the direction of the access.
+
+ For example, in:
+
+ 01| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+ 02| |[0]|[1]|[2]|[3]|[4]|[5]| ... |[440]|[441]|[442]|[443]|[444]|[445]|
+ 03| +---+---+---+---+---+---+ +-----+-----+-----+-----+-----+-----+
+ 04| |'L'|'o'|'r'|'e'|'m'|' '| | 'o' | 'r' | 'u' | 'm' | '.' | NUL |
+ 05| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+ 06| | string literal (type: 'char[446]') |
+ 07| +----------------------------------------------------------------------+
+ 08| | | | | | | | | | | | | | | |
+ 09| | | | | | | | | | | | | | | |
+ 10| v v v v v v v v v v v v v v v
+ 11|+---+---------------------+----++--------------------------------------+
+ 12||[0]| ... |[99]|| after valid range |
+ 13|+---+---------------------+----+| |
+ 14|| 'buf' (type: 'char[100]') || |
+ 15|+------------------------------++--------------------------------------+
+ 16||~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~||~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~|
+ 17| | |
+ 18| +---------+---------+ +----------+----------+
+ 19| |capacity: 100 bytes| |overflow of 346 bytes|
+ 20| +-------------------+ +---------------------+
+
+ rows 8-10 are the direction widget. */
+
+class direction_widget : public leaf_widget
+{
+public:
+ direction_widget (const access_diagram_impl &dia_impl,
+ const bit_to_table_map &btm)
+ : leaf_widget (),
+ m_dia_impl (dia_impl),
+ m_btm (btm)
+ {
+ }
+ const char *get_desc () const override
+ {
+ return "direction_widget";
+ }
+ canvas::size_t calc_req_size () final override
+ {
+ /* Get our width from our siblings. */
+ return canvas::size_t (0, 3);
+ }
+ void paint_to_canvas (canvas &canvas) final override;
+
+private:
+ const access_diagram_impl &m_dia_impl;
+ const bit_to_table_map &m_btm;
+};
+
+/* A widget for adding an x_ruler to a diagram based on table columns,
+ offloading column-width calculation to shared objects, so that the ruler
+ lines up with other tables in the diagram.
+
+ For example, in:
+
+ 01| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+ 02| |[0]|[1]|[2]|[3]|[4]|[5]| ... |[440]|[441]|[442]|[443]|[444]|[445]|
+ 03| +---+---+---+---+---+---+ +-----+-----+-----+-----+-----+-----+
+ 04| |'L'|'o'|'r'|'e'|'m'|' '| | 'o' | 'r' | 'u' | 'm' | '.' | NUL |
+ 05| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+ 06| | string literal (type: 'char[446]') |
+ 07| +----------------------------------------------------------------------+
+ 08| | | | | | | | | | | | | | | |
+ 09| | | | | | | | | | | | | | | |
+ 10| v v v v v v v v v v v v v v v
+ 11|+---+---------------------+----++--------------------------------------+
+ 12||[0]| ... |[99]|| after valid range |
+ 13|+---+---------------------+----+| |
+ 14|| 'buf' (type: 'char[100]') || |
+ 15|+------------------------------++--------------------------------------+
+ 16||~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~||~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~|
+ 17| | |
+ 18| +---------+---------+ +----------+----------+
+ 19| |capacity: 100 bytes| |overflow of 346 bytes|
+ 20| +-------------------+ +---------------------+
+
+ rows 16-20 are the x_aligned_x_ruler_widget. */
+
+class x_aligned_x_ruler_widget : public leaf_widget
+{
+public:
+ x_aligned_x_ruler_widget (const access_diagram_impl &dia_impl,
+ const theme &theme,
+ table_dimension_sizes &col_widths)
+ : m_dia_impl (dia_impl),
+ m_theme (theme),
+ m_col_widths (col_widths)
+ {
+ }
+
+ const char *get_desc () const override
+ {
+ return "x_aligned_ruler_widget";
+ }
+
+ void add_range (const table::range_t &x_range,
+ styled_string text,
+ style::id_t style_id)
+ {
+ m_labels.push_back (label (x_range, std::move (text), style_id));
+ }
+
+ canvas::size_t calc_req_size () final override
+ {
+ x_ruler r (make_x_ruler ());
+ return r.get_size ();
+ }
+
+ void paint_to_canvas (canvas &canvas) final override
+ {
+ x_ruler r (make_x_ruler ());
+ r.paint_to_canvas (canvas,
+ get_top_left (),
+ m_theme);
+ }
+
+private:
+ struct label
+ {
+ label (const table::range_t &table_x_range,
+ styled_string text,
+ style::id_t style_id)
+ : m_table_x_range (table_x_range),
+ m_text (std::move (text)),
+ m_style_id (style_id)
+ {
+ }
+ table::range_t m_table_x_range;
+ styled_string m_text;
+ style::id_t m_style_id;
+ };
+
+ x_ruler make_x_ruler () const;
+
+ const access_diagram_impl &m_dia_impl;
+ const theme &m_theme;
+ table_dimension_sizes &m_col_widths;
+ std::vector<label> m_labels;
+};
+
+/* A two-way mapping between access_ranges and table columns, for use by
+ spatial_item subclasses for creating tables.
+ For example when visualizing a bogus access of 'int arr[10];'
+ at 'arr[10]', we might have:
+ - table column 0 is "bytes 0-3" (for arr[0])
+ - table column 1 is "bytes 4-35" (for arr[1] through arr[8])
+ - table column 2 is "bytes 36-39 (for arr[9])
+ - table column 3 is blank to emphasize a hard boundary between
+ valid/invalid accesses.
+ - table column 4 is "bytes 40-44" (for arr[10])
+
+ We store this as a pair of maps from region_offset to table x; in
+ the abvove example:
+
+ region offset table_x prev_table_x
+ bit 0 (aka byte 0) 0 (none)
+ bit 32 (aka byte 4) 1 0
+ bit 288 (aka byte 36) 2 1
+ bit 320 (aka byte 40) 4 2
+ bit 352 (aka byte 44) (none) (none)
+
+ so that e.g given the half-open byte range [0, 40)
+ we can determine the closed range of table x [0, 2]. */
+
+class bit_to_table_map
+{
+public:
+ /* Populate m_table_x_for_bit and m_bit_for_table_x. */
+ void populate (const boundaries &boundaries, logger *logger)
+ {
+ LOG_SCOPE (logger);
+
+ int table_x = 0;
+ std::vector <region_offset> vec_boundaries (boundaries.begin (),
+ boundaries.end ());
+
+ /* Sort into an order that makes sense. */
+ std::sort (vec_boundaries.begin (),
+ vec_boundaries.end ());
+
+ if (logger)
+ {
+ logger->log ("vec_boundaries");
+ logger->inc_indent ();
+ for (unsigned idx = 0; idx < vec_boundaries.size (); idx++)
+ {
+ logger->start_log_line ();
+ logger->log_partial ("idx: %i: ", idx);
+ vec_boundaries[idx].dump_to_pp (logger->get_printer (), true);
+ logger->end_log_line ();
+ }
+ logger->dec_indent ();
+ }
+
+ for (size_t idx = 0; idx < vec_boundaries.size (); idx++)
+ {
+ const region_offset &offset = vec_boundaries[idx];
+ if (idx > 0 && (idx + 1) < vec_boundaries.size ())
+ {
+ if (boundaries.get_kind (offset) == boundaries::kind::HARD)
+ table_x += 1;
+ }
+ m_table_x_for_offset[offset] = table_x;
+ if ((idx + 1) < vec_boundaries.size ())
+ {
+ const region_offset &next_offset = vec_boundaries[idx + 1];
+ m_table_x_for_prev_offset[next_offset] = table_x;
+ m_range_for_table_x[table_x] = access_range (offset, next_offset);
+ }
+ table_x += 1;
+ }
+ m_num_columns = table_x - 1;
+
+ if (logger)
+ log (*logger);
+ }
+
+ unsigned get_num_columns () const
+ {
+ return m_num_columns;
+ }
+
+ table::range_t get_table_x_for_range (const access_range &range) const
+ {
+ return table::range_t (get_table_x_for_offset (range.m_start),
+ get_table_x_for_prev_offset (range.m_next) + 1);
+ }
+
+ table::rect_t get_table_rect (const access_range &range,
+ const int table_y, const int table_h) const
+ {
+ const table::range_t x_range (get_table_x_for_range (range));
+ return table::rect_t (table::coord_t (x_range.start, table_y),
+ table::size_t (x_range.get_size (), table_h));
+ }
+
+ table::rect_t get_table_rect (const region *base_reg,
+ const bit_range &bits,
+ const int table_y, const int table_h) const
+ {
+ const access_range range (base_reg, bits);
+ return get_table_rect (range, table_y, table_h);
+ }
+
+ table::rect_t get_table_rect (const region *base_reg,
+ const byte_range &bytes,
+ const int table_y, const int table_h) const
+ {
+ return get_table_rect (base_reg, bytes.as_bit_range (), table_y, table_h);
+ }
+
+ bool maybe_get_access_range_for_table_x (int table_x,
+ access_range *out) const
+ {
+ auto slot = m_range_for_table_x.find (table_x);
+ if (slot == m_range_for_table_x.end ())
+ return false;
+ *out = slot->second;
+ return true;
+ }
+
+ void log (logger &logger) const
+ {
+ logger.log ("table columns");
+ logger.inc_indent ();
+ for (unsigned table_x = 0; table_x < get_num_columns (); table_x++)
+ {
+ logger.start_log_line ();
+ logger.log_partial ("table_x: %i", table_x);
+ access_range range_for_column (NULL, bit_range (0, 0));
+ if (maybe_get_access_range_for_table_x (table_x, &range_for_column))
+ {
+ logger.log_partial (": range: ");
+ range_for_column.dump_to_pp (logger.get_printer (), true);
+ }
+ logger.end_log_line ();
+ }
+ logger.dec_indent ();
+ }
+
+private:
+ int get_table_x_for_offset (region_offset offset) const
+ {
+ auto slot = m_table_x_for_offset.find (offset);
+
+ /* If this fails, then we probably failed to fully populate m_boundaries
+ in find_boundaries. */
+ gcc_assert (slot != m_table_x_for_offset.end ());
+
+ return slot->second;
+ }
+
+ int get_table_x_for_prev_offset (region_offset offset) const
+ {
+ auto slot = m_table_x_for_prev_offset.find (offset);
+
+ /* If this fails, then we probably failed to fully populate m_boundaries
+ in find_boundaries. */
+ gcc_assert (slot != m_table_x_for_prev_offset.end ());
+
+ return slot->second;
+ }
+
+ std::map<region_offset, int> m_table_x_for_offset;
+ std::map<region_offset, int> m_table_x_for_prev_offset;
+ std::map<int, access_range> m_range_for_table_x;
+ unsigned m_num_columns;
+};
+
+/* Base class for something in the diagram that participates
+ in two steps of diagram creation:
+ (a) populating a boundaries instance with the boundaries of interest
+ (b) creating a table instance for itself.
+
+ Offsets in the boundaries are all expressed relative to the base
+ region of the access_operation. */
+
+class spatial_item
+{
+public:
+ virtual void add_boundaries (boundaries &out, logger *) const = 0;
+
+ virtual table make_table (const bit_to_table_map &btm,
+ style_manager &sm) const = 0;
+};
+
+/* Subclass of spatial_item for visualizing the region of memory
+ that's valid to access relative to the base region of region accessed in
+ the operation. */
+
+class valid_region_spatial_item : public spatial_item
+{
+public:
+ valid_region_spatial_item (const access_operation &op,
+ diagnostic_event_id_t region_creation_event_id)
+ : m_op (op),
+ m_region_creation_event_id (region_creation_event_id)
+ {}
+
+ void add_boundaries (boundaries &out, logger *logger) const final override
+ {
+ LOG_SCOPE (logger);
+ access_range valid_bits = m_op.get_valid_bits ();
+ if (logger)
+ {
+ logger->start_log_line ();
+ logger->log_partial ("valid bits: ");
+ valid_bits.dump_to_pp (logger->get_printer (), true);
+ logger->end_log_line ();
+ }
+ out.add (valid_bits, boundaries::kind::HARD);
+
+ /* Support for showing first and final element in array types. */
+ if (tree base_type = m_op.m_base_region->get_type ())
+ if (TREE_CODE (base_type) == ARRAY_TYPE)
+ {
+ if (logger)
+ logger->log ("showing first and final element in array type");
+ region_model_manager *mgr = m_op.m_model.get_manager ();
+ tree domain = TYPE_DOMAIN (base_type);
+ if (TYPE_MIN_VALUE (domain) && TYPE_MAX_VALUE (domain))
+ {
+ const svalue *min_idx_sval
+ = mgr->get_or_create_constant_svalue (TYPE_MIN_VALUE (domain));
+ const svalue *max_idx_sval
+ = mgr->get_or_create_constant_svalue (TYPE_MAX_VALUE (domain));
+ const region *min_element =
+ mgr->get_element_region (m_op.m_base_region,
+ TREE_TYPE (base_type),
+ min_idx_sval);
+ out.add (*min_element, mgr, boundaries::kind::SOFT);
+ const region *max_element =
+ mgr->get_element_region (m_op.m_base_region,
+ TREE_TYPE (base_type),
+ max_idx_sval);
+ out.add (*max_element, mgr, boundaries::kind::SOFT);
+ }
+ }
+ }
+
+ /* Subroutine of make_table when base region has ARRAY_TYPE. */
+ void add_array_elements_to_table (table &t,
+ const bit_to_table_map &btm,
+ style_manager &sm) const
+ {
+ tree base_type = m_op.m_base_region->get_type ();
+ gcc_assert (TREE_CODE (base_type) == ARRAY_TYPE);
+
+ tree domain = TYPE_DOMAIN (base_type);
+ if (!(TYPE_MIN_VALUE (domain) && TYPE_MAX_VALUE (domain)))
+ return;
+
+ region_model_manager * const mgr = m_op.get_manager ();
+ const int table_y = 0;
+ const int table_h = 1;
+ const table::range_t table_y_range (table_y, table_y + table_h);
+
+ t.add_row ();
+ const svalue *min_idx_sval
+ = mgr->get_or_create_constant_svalue (TYPE_MIN_VALUE (domain));
+ const region *min_element = mgr->get_element_region (m_op.m_base_region,
+ TREE_TYPE (base_type),
+ min_idx_sval);
+ const access_range min_element_range (*min_element, mgr);
+ const table::range_t min_element_x_range
+ = btm.get_table_x_for_range (min_element_range);
+
+ t.set_cell_span (table::rect_t (min_element_x_range,
+ table_y_range),
+ fmt_styled_string (sm, "[%E]",
+ TYPE_MIN_VALUE (domain)));
+
+ const svalue *max_idx_sval
+ = mgr->get_or_create_constant_svalue (TYPE_MAX_VALUE (domain));
+ const region *max_element = mgr->get_element_region (m_op.m_base_region,
+ TREE_TYPE (base_type),
+ max_idx_sval);
+ if (min_element == max_element)
+ return; // 1-element array
+
+ const access_range max_element_range (*max_element, mgr);
+ const table::range_t max_element_x_range
+ = btm.get_table_x_for_range (max_element_range);
+ t.set_cell_span (table::rect_t (max_element_x_range,
+ table_y_range),
+ fmt_styled_string (sm, "[%E]",
+ TYPE_MAX_VALUE (domain)));
+
+ const table::range_t other_elements_x_range (min_element_x_range.next,
+ max_element_x_range.start);
+ if (other_elements_x_range.get_size () > 0)
+ t.set_cell_span (table::rect_t (other_elements_x_range, table_y_range),
+ styled_string (sm, "..."));
+ }
+
+ table make_table (const bit_to_table_map &btm,
+ style_manager &sm) const final override
+ {
+ table t (table::size_t (btm.get_num_columns (), 1));
+
+ if (tree base_type = m_op.m_base_region->get_type ())
+ if (TREE_CODE (base_type) == ARRAY_TYPE)
+ add_array_elements_to_table (t, btm, sm);
+
+ access_range valid_bits = m_op.get_valid_bits ();
+ const int table_y = t.get_size ().h - 1;
+ const int table_h = 1;
+ table::rect_t rect = btm.get_table_rect (valid_bits, table_y, table_h);
+ styled_string s;
+ switch (m_op.m_base_region->get_kind ())
+ {
+ default:
+ s = styled_string (sm, _("region"));
+ break;
+ case RK_DECL:
+ {
+ const decl_region *decl_reg
+ = as_a <const decl_region *> (m_op.m_base_region);
+ tree decl = decl_reg->get_decl ();
+ s = fmt_styled_string (sm, "%qE (type: %qT)",
+ decl,
+ TREE_TYPE (decl));
+ }
+ break;
+ case RK_HEAP_ALLOCATED:
+ {
+ if (m_region_creation_event_id.known_p ())
+ s = fmt_styled_string (sm, _("buffer allocated on heap at %@"),
+ &m_region_creation_event_id);
+ else
+ s = styled_string (sm, _("heap-allocated buffer"));
+ }
+ break;
+ case RK_ALLOCA:
+ {
+ if (m_region_creation_event_id.known_p ())
+ s = fmt_styled_string (sm, _("buffer allocated on stack at %@"),
+ &m_region_creation_event_id);
+ else
+ s = styled_string (sm, _("stack-allocated buffer"));
+ }
+ break;
+ case RK_STRING:
+ {
+ const string_region *string_reg
+ = as_a <const string_region *> (m_op.m_base_region);
+ tree string_cst = string_reg->get_string_cst ();
+ s = fmt_styled_string (sm, _("string literal (type: %qT)"),
+ TREE_TYPE (string_cst));
+ }
+ break;
+ }
+ t.set_cell_span (rect, std::move (s));
+
+ return t;
+ }
+
+private:
+ const access_operation &m_op;
+ diagnostic_event_id_t m_region_creation_event_id;
+};
+
+/* Subclass of spatial_item for visualizing the region of memory
+ that's actually accessed by the read or write, for reads and
+ for write cases where we don't know the svalue written. */
+
+class accessed_region_spatial_item : public spatial_item
+{
+public:
+ accessed_region_spatial_item (const access_operation &op) : m_op (op) {}
+
+ void add_boundaries (boundaries &out, logger *logger) const final override
+ {
+ LOG_SCOPE (logger);
+ access_range actual_bits = m_op.get_actual_bits ();
+ if (logger)
+ {
+ logger->start_log_line ();
+ logger->log_partial ("actual bits: ");
+ actual_bits.dump_to_pp (logger->get_printer (), true);
+ logger->end_log_line ();
+ }
+ out.add (actual_bits, boundaries::kind::HARD);
+ }
+
+ table make_table (const bit_to_table_map &btm,
+ style_manager &sm) const final override
+ {
+ table t (table::size_t (btm.get_num_columns (), 1));
+
+ access_range actual_bits = m_op.get_actual_bits ();
+ const int table_y = 0;
+ const int table_h = 1;
+ table::rect_t rect = btm.get_table_rect (actual_bits, table_y, table_h);
+ t.set_cell_span (rect, styled_string (get_label_string (sm)));
+
+ return t;
+ }
+
+private:
+ styled_string get_label_string (style_manager &sm) const
+ {
+ const access_range accessed_bits (m_op.get_actual_bits ());
+ return get_access_size_str (sm,
+ m_op,
+ accessed_bits,
+ m_op.m_reg.get_type ());
+ }
+
+ const access_operation &m_op;
+};
+
+/* Subclass of spatial_item for when we know the svalue being written
+ to the accessed region.
+ Can be subclassed to give visualizations of specific kinds of svalue. */
+
+class svalue_spatial_item : public spatial_item
+{
+public:
+ static std::unique_ptr<svalue_spatial_item> make (const access_operation &op,
+ const svalue &sval,
+ access_range actual_bits,
+ const theme &theme);
+
+ svalue_spatial_item (const access_operation &op,
+ const svalue &sval,
+ access_range actual_bits)
+ : m_op (op), m_sval (sval), m_actual_bits (actual_bits)
+ {}
+
+ void add_boundaries (boundaries &out, logger *logger) const override
+ {
+ LOG_SCOPE (logger);
+ out.add (m_actual_bits, boundaries::kind::HARD);
+ }
+
+ table make_table (const bit_to_table_map &btm,
+ style_manager &sm) const override
+ {
+ table t (table::size_t (btm.get_num_columns (), 0));
+
+ const int table_y = t.add_row ();
+ const int table_h = 1;
+ table::rect_t rect = btm.get_table_rect (m_actual_bits, table_y, table_h);
+ t.set_cell_span (rect, styled_string (get_label_string (sm)));
+ return t;
+ }
+
+protected:
+ styled_string get_label_string (style_manager &sm) const
+ {
+ tree rep_tree = m_op.m_model.get_representative_tree (&m_sval);
+ if (rep_tree)
+ {
+ if (TREE_CODE (rep_tree) == SSA_NAME)
+ rep_tree = SSA_NAME_VAR (rep_tree);
+ switch (TREE_CODE (rep_tree))
+ {
+ default:
+ break;
+ case INTEGER_CST:
+ return fmt_styled_string (sm, _("write of %<(%T) %E%>"),
+ TREE_TYPE (rep_tree),
+ rep_tree);
+
+ case PARM_DECL:
+ case VAR_DECL:
+ return fmt_styled_string (sm, _("write from %qE (type: %qT)"),
+ rep_tree,
+ TREE_TYPE (rep_tree));
+ break;
+ }
+ }
+
+ const access_range accessed_bits (m_op.get_actual_bits ());
+ return get_access_size_str (sm,
+ m_op,
+ accessed_bits,
+ m_sval.get_type ());
+ }
+
+ const access_operation &m_op;
+ const svalue &m_sval;
+ access_range m_actual_bits;
+};
+
+/* Subclass of svalue_spatial_item for initial_svalue of a string_region
+ i.e. for string literals.
+
+ There are three cases:
+ (a) for long strings, show just the head and tail of the string,
+ with an ellipsis:
+ +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+ |[0]|[1]|[2]|[3]|[4]|[5]| |[440]|[441]|[442]|[443]|[444]|[445]|
+ +---+---+---+---+---+---+ ... +-----+-----+-----+-----+-----+-----+
+ |‘L’|‘o’|‘r’|‘e’|‘m’|‘ ’| | ‘o’ | ‘r’ | ‘u’ | ‘m’ | ‘.’ | NUL |
+ +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+
+ | string literal (type: ‘char[446]’) |
+ +----------------------------------------------------------------------+
+ (b) For sufficiently short strings, show the full string:
+ +----------+---------+---------+---------+---------+ +-----------------+
+ | [0] | [1] | [2] | [3] | [4] | | [5] |
+ +----------+---------+---------+---------+---------+ +-----------------+
+ | ‘h’ | ‘e’ | ‘l’ | ‘l’ | ‘o’ | | NUL |
+ +----------+---------+---------+---------+---------+-+-----------------+
+ | string literal (type: ‘char[6]’) |
+ +----------------------------------------------------------------------+
+ (c) for non-ASCII strings that are short enough to show the full string,
+ show how unicode code points of the bytes decoded as UTF-8:
+ +-----+-----+-----+----+----++----+----+----+----+----+----+----+------+
+ | [0] | [1] | [2] |[3] |[4] ||[5] |[6] |[7] |[8] |[9] |[10]|[11]| [12] |
+ +-----+-----+-----+----+----++----+----+----+----+----+----+----+------+
+ |0xe6 |0x96 |0x87 |0xe5|0xad||0x97|0xe5|0x8c|0x96|0xe3|0x81|0x91| 0x00 |
+ +-----+-----+-----+----+----++----+----+----+----+----+----+----+------+
+ | U+6587 | U+5b57 | U+5316 | U+3051 |U+0000|
+ +-----------------+---------------+--------------+--------------+------+
+ | string literal (type: ‘char[13]’) |
+ +----------------------------------------------------------------------+
+ and show the characters themselves if unicode is supported and they are not
+ control characters:
+ ┌─────┬─────┬─────┬────┬────┐┌────┬────┬────┬────┬────┬────┬────┬──────┐
+ │ [0] │ [1] │ [2] │[3] │[4] ││[5] │[6] │[7] │[8] │[9] │[10]│[11]│ [12] │
+ ├─────┼─────┼─────┼────┼────┤├────┼────┼────┼────┼────┼────┼────┼──────┤
+ │0xe6 │0x96 │0x87 │0xe5│0xad││0x97│0xe5│0x8c│0x96│0xe3│0x81│0x91│ 0x00 │
+ ├─────┴─────┴─────┼────┴────┴┴────┼────┴────┴────┼────┴────┴────┼──────┤
+ │ U+6587 │ U+5b57 │ U+5316 │ U+3051 │U+0000│
+ ├─────────────────┼───────────────┼──────────────┼──────────────┼──────┤
+ │ 文 │ 字 │ 化 │ け │ NUL │
+ ├─────────────────┴───────────────┴──────────────┴──────────────┴──────┤
+ │ string literal (type: ‘char[13]’) │
+ └──────────────────────────────────────────────────────────────────────┘
+*/
+
+class string_region_spatial_item : public svalue_spatial_item
+{
+public:
+ string_region_spatial_item (const access_operation &op,
+ const svalue &sval,
+ access_range actual_bits,
+ const string_region &string_reg,
+ const theme &theme)
+ : svalue_spatial_item (op, sval, actual_bits),
+ m_string_reg (string_reg),
+ m_theme (theme),
+ m_ellipsis_threshold (param_analyzer_text_art_string_ellipsis_threshold),
+ m_ellipsis_head_len (param_analyzer_text_art_string_ellipsis_head_len),
+ m_ellipsis_tail_len (param_analyzer_text_art_string_ellipsis_tail_len),
+ m_show_full_string (calc_show_full_string ()),
+ m_show_utf8 (m_show_full_string && !pure_ascii_p ())
+ {
+ }
+
+ void add_boundaries (boundaries &out, logger *logger) const override
+ {
+ LOG_SCOPE (logger);
+ out.add (m_actual_bits, boundaries::kind::HARD);
+
+ tree string_cst = get_string_cst ();
+ /* TREE_STRING_LENGTH is sizeof, not strlen. */
+ if (m_show_full_string)
+ out.add_all_bytes_in_range (m_actual_bits);
+ else
+ {
+ byte_range head_of_string (0, m_ellipsis_head_len);
+ out.add_all_bytes_in_range (head_of_string);
+ byte_range tail_of_string
+ (TREE_STRING_LENGTH (string_cst) - m_ellipsis_tail_len,
+ m_ellipsis_tail_len);
+ out.add_all_bytes_in_range (tail_of_string);
+ /* Adding the above pair of ranges will also effectively add
+ the boundaries of the range of ellipsized chars, as they're
+ exactly in between head_of_string and tail_of_string. */
+ }
+ }
+
+ table make_table (const bit_to_table_map &btm,
+ style_manager &sm) const override
+ {
+ table t (table::size_t (btm.get_num_columns (), 0));
+
+ const int byte_idx_table_y = t.add_row ();
+ const int byte_val_table_y = t.add_row ();
+
+ byte_range bytes (0, 0);
+ bool valid = m_actual_bits.as_concrete_byte_range (&bytes);
+ gcc_assert (valid);
+ tree string_cst = get_string_cst ();
+ if (m_show_full_string)
+ {
+ for (byte_offset_t byte_idx = bytes.get_start_byte_offset ();
+ byte_idx < bytes.get_next_byte_offset ();
+ byte_idx = byte_idx + 1)
+ add_column_for_byte (t, btm, sm, byte_idx,
+ byte_idx_table_y, byte_val_table_y);
+
+ if (m_show_utf8)
+ {
+ const bool show_unichars = m_theme.unicode_p ();
+ const int utf8_code_point_table_y = t.add_row ();
+ int utf8_character_table_y;
+ if (show_unichars)
+ utf8_character_table_y = t.add_row ();
+
+ /* We don't actually want the display widths here, but
+ it's an easy way to decode UTF-8. */
+ cpp_char_column_policy policy (8, cpp_wcwidth);
+ cpp_display_width_computation dw (TREE_STRING_POINTER (string_cst),
+ TREE_STRING_LENGTH (string_cst),
+ policy);
+ while (!dw.done ())
+ {
+ cpp_decoded_char decoded_char;
+ dw.process_next_codepoint (&decoded_char);
+
+ if (!decoded_char.m_valid_ch)
+ continue;
+ size_t start_byte_idx
+ = decoded_char.m_start_byte - TREE_STRING_POINTER (string_cst);
+ byte_size_t size_in_bytes
+ = decoded_char.m_next_byte - decoded_char.m_start_byte;
+ byte_range bytes (start_byte_idx, size_in_bytes);
+
+ const table::rect_t code_point_table_rect
+ = btm.get_table_rect (&m_string_reg, bytes,
+ utf8_code_point_table_y, 1);
+ char buf[100];
+ sprintf (buf, "U+%04x", decoded_char.m_ch);
+ t.set_cell_span (code_point_table_rect,
+ styled_string (sm, buf));
+
+ if (show_unichars)
+ {
+ const table::rect_t character_table_rect
+ = btm.get_table_rect (&m_string_reg, bytes,
+ utf8_character_table_y, 1);
+ if (cpp_is_printable_char (decoded_char.m_ch))
+ t.set_cell_span (character_table_rect,
+ styled_string (decoded_char.m_ch));
+ else if (decoded_char.m_ch == 0)
+ t.set_cell_span (character_table_rect,
+ styled_string (sm, "NUL"));
+ else
+ t.set_cell_span (character_table_rect,
+ styled_string (sm, ""));
+ }
+ }
+ }
+ }
+ else
+ {
+ /* Head of string. */
+ for (int byte_idx = 0; byte_idx < m_ellipsis_head_len; byte_idx++)
+ add_column_for_byte (t, btm, sm, byte_idx,
+ byte_idx_table_y, byte_val_table_y);
+
+ /* Ellipsis (two rows high). */
+ const byte_range ellipsis_bytes
+ (m_ellipsis_head_len,
+ TREE_STRING_LENGTH (string_cst)
+ - (m_ellipsis_head_len + m_ellipsis_tail_len));
+ const table::rect_t table_rect
+ = btm.get_table_rect (&m_string_reg, ellipsis_bytes,
+ byte_idx_table_y, 2);
+ t.set_cell_span(table_rect, styled_string (sm, "..."));
+
+ /* Tail of string. */
+ for (int byte_idx
+ = (TREE_STRING_LENGTH (string_cst) - m_ellipsis_tail_len);
+ byte_idx < TREE_STRING_LENGTH (string_cst);
+ byte_idx++)
+ add_column_for_byte (t, btm, sm, byte_idx,
+ byte_idx_table_y, byte_val_table_y);
+ }
+
+ const int summary_table_y = t.add_row ();
+ t.set_cell_span (btm.get_table_rect (&m_string_reg, bytes,
+ summary_table_y, 1),
+ fmt_styled_string (sm,
+ _("string literal (type: %qT)"),
+ TREE_TYPE (string_cst)));
+
+ return t;
+ }
+
+ tree get_string_cst () const { return m_string_reg.get_string_cst (); }
+
+private:
+ bool calc_show_full_string () const
+ {
+ tree string_cst = get_string_cst ();
+ if (TREE_STRING_LENGTH (string_cst) < m_ellipsis_threshold)
+ return true;
+ if (TREE_STRING_LENGTH (string_cst) <
+ (m_ellipsis_head_len + m_ellipsis_tail_len))
+ return true;
+ return false;
+ }
+
+ bool pure_ascii_p () const
+ {
+ tree string_cst = get_string_cst ();
+ for (unsigned byte_idx = 0;
+ byte_idx < (unsigned) TREE_STRING_LENGTH (string_cst);
+ byte_idx++)
+ {
+ unsigned char ch = TREE_STRING_POINTER (string_cst)[byte_idx];
+ if (ch >= 0x80)
+ return false;
+ }
+ return true;
+ }
+
+ void add_column_for_byte (table &t, const bit_to_table_map &btm,
+ style_manager &sm,
+ const byte_offset_t byte_idx,
+ const int byte_idx_table_y,
+ const int byte_val_table_y) const
+ {
+ tree string_cst = get_string_cst ();
+ gcc_assert (byte_idx >= 0);
+ gcc_assert (byte_idx < TREE_STRING_LENGTH (string_cst));
+
+ const byte_range bytes (byte_idx, 1);
+ if (1) // show_byte_indices
+ {
+ const table::rect_t idx_table_rect
+ = btm.get_table_rect (&m_string_reg, bytes, byte_idx_table_y, 1);
+ t.set_cell_span (idx_table_rect,
+ fmt_styled_string (sm, "[%li]",
+ byte_idx.ulow ()));
+ }
+
+ char byte_val = TREE_STRING_POINTER (string_cst)[byte_idx.ulow ()];
+ const table::rect_t val_table_rect
+ = btm.get_table_rect (&m_string_reg, bytes, byte_val_table_y, 1);
+ table_cell_content content (make_cell_content_for_byte (sm, byte_val));
+ t.set_cell_span (val_table_rect, std::move (content));
+ }
+
+ table_cell_content make_cell_content_for_byte (style_manager &sm,
+ unsigned char byte_val) const
+ {
+ if (!m_show_utf8)
+ {
+ if (byte_val == '\0')
+ return styled_string (sm, "NUL");
+ else if (byte_val < 0x80)
+ if (ISPRINT (byte_val))
+ return fmt_styled_string (sm, "%qc", byte_val);
+ }
+ char buf[100];
+ sprintf (buf, "0x%02x", byte_val);
+ return styled_string (sm, buf);
+ }
+
+ const string_region &m_string_reg;
+ const theme &m_theme;
+ const int m_ellipsis_threshold;
+ const int m_ellipsis_head_len;
+ const int m_ellipsis_tail_len;
+ const bool m_show_full_string;
+ const bool m_show_utf8;
+};
+
+std::unique_ptr<svalue_spatial_item>
+svalue_spatial_item::make (const access_operation &op,
+ const svalue &sval,
+ access_range actual_bits,
+ const theme &theme)
+{
+ if (const initial_svalue *initial_sval = sval.dyn_cast_initial_svalue ())
+ if (const string_region *string_reg
+ = initial_sval->get_region ()->dyn_cast_string_region ())
+ return make_unique <string_region_spatial_item> (op, sval, actual_bits,
+ *string_reg, theme);
+ return make_unique <svalue_spatial_item> (op, sval, actual_bits);
+}
+
+/* Widget subclass implementing access diagrams. */
+
+class access_diagram_impl : public vbox_widget
+{
+public:
+ access_diagram_impl (const access_operation &op,
+ diagnostic_event_id_t region_creation_event_id,
+ style_manager &sm,
+ const theme &theme,
+ logger *logger)
+ : m_op (op),
+ m_region_creation_event_id (region_creation_event_id),
+ m_sm (sm),
+ m_theme (theme),
+ m_logger (logger),
+ m_invalid (false),
+ m_valid_region_spatial_item (op, region_creation_event_id),
+ m_accessed_region_spatial_item (op),
+ m_btm (),
+ m_calc_req_size_called (false)
+ {
+ LOG_SCOPE (logger);
+
+ if (logger)
+ {
+ access_range invalid_before_bits;
+ if (op.maybe_get_invalid_before_bits (&invalid_before_bits))
+ invalid_before_bits.log ("invalid before range", *logger);
+ access_range invalid_after_bits;
+ if (op.maybe_get_invalid_after_bits (&invalid_after_bits))
+ invalid_after_bits.log ("invalid after range", *logger);
+
+ if (op.m_sval_hint)
+ {
+ logger->start_log_line ();
+ logger->log_partial ("sval_hint: ");
+ op.m_sval_hint->dump_to_pp (logger->get_printer (), true);
+ logger->end_log_line ();
+ }
+ }
+
+ /* Register painting styles. */
+ {
+ style valid_style;
+ valid_style.m_fg_color = style::named_color::GREEN;
+ valid_style.m_bold = true;
+ m_valid_style_id = m_sm.get_or_create_id (valid_style);
+
+ style invalid_style;
+ invalid_style.m_fg_color = style::named_color::RED;
+ invalid_style.m_bold = true;
+ m_invalid_style_id = m_sm.get_or_create_id (invalid_style);
+ }
+
+ if (op.m_sval_hint)
+ {
+ access_range actual_bits = m_op.get_actual_bits ();
+ m_svalue_spatial_item = svalue_spatial_item::make (m_op,
+ *op.m_sval_hint,
+ actual_bits,
+ m_theme);
+ }
+
+ /* Two passes:
+ First, figure out all of the boundaries of interest.
+ Then use that to build child widgets showing the regions of interest,
+ with a common tabular layout. */
+
+ m_boundaries = find_boundaries ();
+ if (logger)
+ m_boundaries->log (*logger);
+
+ /* Populate m_table_x_for_bit and m_bit_for_table_x.
+ Each table column represents the range [offset, next_offset).
+ We don't create a column in the table for the final offset, but we
+ do populate it, so that looking at the table_x of one beyond the
+ final table column gives us the upper bound offset. */
+ m_btm.populate (*m_boundaries, logger);
+
+ /* Gracefully reject cases where the boundary sorting has gone wrong
+ (due to awkward combinations of symbolic values). */
+ {
+ table::range_t actual_bits_x_range
+ = m_btm.get_table_x_for_range (m_op.get_actual_bits ());
+ if (actual_bits_x_range.get_size () <= 0)
+ {
+ if (logger)
+ logger->log ("giving up: bad table columns for actual_bits");
+ m_invalid = true;
+ return;
+ }
+ table::range_t valid_bits_x_range
+ = m_btm.get_table_x_for_range (m_op.get_valid_bits ());
+ if (valid_bits_x_range.get_size () <= 0)
+ {
+ if (logger)
+ logger->log ("giving up: bad table columns for valid_bits");
+ m_invalid = true;
+ return;
+ }
+ }
+
+ m_col_widths
+ = make_unique <table_dimension_sizes> (m_btm.get_num_columns ());
+
+ /* Now create child widgets. */
+
+ if (flag_analyzer_debug_text_art)
+ {
+ table t_headings (make_headings_table ());
+ add_aligned_child_table (std::move (t_headings));
+ }
+
+ if (m_svalue_spatial_item)
+ {
+ table t_sval (m_svalue_spatial_item->make_table (m_btm, m_sm));
+ add_aligned_child_table (std::move (t_sval));
+ }
+ else
+ {
+ table t_accessed
+ (m_accessed_region_spatial_item.make_table (m_btm, m_sm));
+ add_aligned_child_table (std::move (t_accessed));
+ }
+
+ add_direction_widget ();
+
+ table t_valid (m_valid_region_spatial_item.make_table (m_btm, m_sm));
+ add_invalid_accesses_to_region_table (t_valid);
+ add_aligned_child_table (std::move (t_valid));
+
+ add_valid_vs_invalid_ruler ();
+ }
+
+ const char *get_desc () const override
+ {
+ return "access_diagram_impl";
+ }
+
+ canvas::size_t calc_req_size () final override
+ {
+ if (m_invalid)
+ return canvas::size_t (0, 0);
+
+ /* Now compute the size requirements for the tables. */
+ for (auto iter : m_aligned_table_widgets)
+ iter->get_cell_sizes ().pass_1 (iter->get_table ());
+ for (auto iter : m_aligned_table_widgets)
+ iter->get_cell_sizes ().pass_2 (iter->get_table ());
+
+ adjust_to_scale();
+
+ /* ...and relayout the tables. */
+ for (auto iter : m_aligned_table_widgets)
+ iter->recalc_coords ();
+
+ /* Populate the canvas_x per table_x. */
+ m_col_start_x.clear ();
+ int iter_canvas_x = 0;
+ for (auto w : m_col_widths->m_requirements)
+ {
+ m_col_start_x.push_back (iter_canvas_x);
+ iter_canvas_x += w + 1;
+ }
+ m_col_start_x.push_back (iter_canvas_x);
+
+ m_calc_req_size_called = true;
+
+ return vbox_widget::calc_req_size ();
+ }
+
+ int get_canvas_x_for_table_x (int table_x) const
+ {
+ gcc_assert (m_calc_req_size_called);
+ return m_col_start_x[table_x];
+ }
+
+ canvas::range_t get_canvas_x_range (const table::range_t &table_x_range) const
+ {
+ gcc_assert (m_calc_req_size_called);
+ return canvas::range_t (get_canvas_x_for_table_x (table_x_range.start),
+ get_canvas_x_for_table_x (table_x_range.next));
+ }
+
+ const access_operation &get_op () const { return m_op; }
+
+ style::id_t get_style_id_for_validity (bool is_valid) const
+ {
+ return is_valid ? m_valid_style_id : m_invalid_style_id;
+ }
+
+ const theme &get_theme () const { return m_theme; }
+
+private:
+ /* Figure out all of the boundaries of interest when visualizing ths op. */
+ std::unique_ptr<boundaries>
+ find_boundaries () const
+ {
+ std::unique_ptr<boundaries> result
+ = make_unique<boundaries> (*m_op.m_base_region);
+
+ m_valid_region_spatial_item.add_boundaries (*result, m_logger);
+ m_accessed_region_spatial_item.add_boundaries (*result, m_logger);
+ if (m_svalue_spatial_item)
+ m_svalue_spatial_item->add_boundaries (*result, m_logger);
+
+ return result;
+ }
+
+ void add_aligned_child_table (table t)
+ {
+ x_aligned_table_widget *w
+ = new x_aligned_table_widget (std::move (t), m_theme, *m_col_widths);
+ m_aligned_table_widgets.push_back (w);
+ add_child (std::unique_ptr<widget> (w));
+ }
+
+ /* Create a table showing headings for use by -fanalyzer-debug-text-art, for
+ example:
+ +---------+-----------+-----------+---+--------------------------------+
+ | tc0 | tc1 | tc2 |tc3| tc4 |
+ +---------+-----------+-----------+---+--------------------------------+
+ |bytes 0-3|bytes 4-35 |bytes 36-39| | bytes 40-43 |
+ +---------+-----------+-----------+ +--------------------------------+
+ which has:
+ - a row showing the table column numbers, labelled "tc0", "tc1", etc
+ - a row showing the memory range of each table column that has one. */
+
+ table make_headings_table () const
+ {
+ table t (table::size_t (m_btm.get_num_columns (), 2));
+
+ for (int table_x = 0; table_x < t.get_size ().w; table_x++)
+ {
+ const int table_y = 0;
+ t.set_cell (table::coord_t (table_x, table_y),
+ fmt_styled_string (m_sm, "tc%i", table_x));
+ }
+ for (int table_x = 0; table_x < t.get_size ().w; table_x++)
+ {
+ const int table_y = 1;
+ access_range range_for_column (NULL, bit_range (0, 0));
+ if (m_btm.maybe_get_access_range_for_table_x (table_x,
+ &range_for_column))
+ {
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ range_for_column.dump_to_pp (&pp, true);
+ t.set_cell (table::coord_t (table_x, table_y),
+ styled_string (m_sm, pp_formatted_text (&pp)));
+ }
+ }
+
+ return t;
+ }
+
+ void add_direction_widget ()
+ {
+ add_child (::make_unique<direction_widget> (*this, m_btm));
+ }
+
+ void add_invalid_accesses_to_region_table (table &t_region)
+ {
+ gcc_assert (t_region.get_size ().w == (int)m_btm.get_num_columns ());
+
+ const int table_y = 0;
+ const int table_h = t_region.get_size ().h;
+
+ access_range invalid_before_bits;
+ if (m_op.maybe_get_invalid_before_bits (&invalid_before_bits))
+ {
+ t_region.set_cell_span (m_btm.get_table_rect (invalid_before_bits,
+ table_y, table_h),
+ styled_string (m_sm,
+ _("before valid range")));
+ }
+ access_range invalid_after_bits;
+ if (m_op.maybe_get_invalid_after_bits (&invalid_after_bits))
+ {
+ t_region.set_cell_span (m_btm.get_table_rect (invalid_after_bits,
+ table_y, table_h),
+ styled_string (m_sm,
+ _("after valid range")));
+ }
+ }
+
+ void maybe_add_gap (x_aligned_x_ruler_widget *w,
+ const access_range &lower,
+ const access_range &upper) const
+ {
+ LOG_SCOPE (m_logger);
+ if (m_logger)
+ {
+ lower.log ("lower", *m_logger);
+ upper.log ("upper", *m_logger);
+ }
+ tree lower_next = lower.m_next.calc_symbolic_bit_offset (m_op.m_model);
+ if (!lower_next)
+ {
+ if (m_logger)
+ m_logger->log ("failed to get lower_next");
+ return;
+ }
+ tree upper_start = upper.m_start.calc_symbolic_bit_offset (m_op.m_model);
+ if (!upper_start)
+ {
+ if (m_logger)
+ m_logger->log ("failed to get upper_start");
+ return;
+ }
+ tree num_bits_gap = fold_build2 (MINUS_EXPR,
+ size_type_node,
+ upper_start, lower_next);
+ if (m_logger)
+ m_logger->log ("num_bits_gap: %qE", num_bits_gap);
+ tree zero = build_int_cst (size_type_node, 0);
+ tristate ts_gt_zero = m_op.m_model.eval_condition (num_bits_gap,
+ GT_EXPR,
+ zero,
+ NULL);
+ if (ts_gt_zero.is_false ())
+ {
+ if (m_logger)
+ m_logger->log ("rejecting as not > 0");
+ return;
+ }
+
+ bit_size_expr num_bits (num_bits_gap);
+ styled_string label = num_bits.get_formatted_str (m_sm,
+ _("%wi bit"),
+ _("%wi bits"),
+ _("%wi byte"),
+ _("%wi bytes"),
+ _("%qE bits"),
+ _("%qE bytes"));
+ w->add_range (m_btm.get_table_x_for_range (access_range (lower.m_next,
+ upper.m_start)),
+ std::move (label),
+ style::id_plain);
+ }
+
+ styled_string
+ make_warning_string (styled_string &&text)
+ {
+ styled_string result;
+ if (!m_theme.emojis_p ())
+ return std::move (text);
+
+ result.append (styled_string (0x26A0, /* U+26A0 WARNING SIGN. */
+ true));
+ /* U+26A0 WARNING SIGN has East_Asian_Width == Neutral, but in its
+ emoji variant is printed (by vte at least) with a 2nd half
+ overlapping the next char. Hence we add two spaces here: a space
+ to be covered by this overlap, plus another space of padding. */
+ result.append (styled_string (m_sm, " "));
+ result.append (std::move (text));
+ return result;
+ }
+
+ /* Add a ruler child widet showing valid, invalid, and gaps. */
+ void add_valid_vs_invalid_ruler ()
+ {
+ LOG_SCOPE (m_logger);
+
+ x_aligned_x_ruler_widget *w
+ = new x_aligned_x_ruler_widget (*this, m_theme, *m_col_widths);
+
+ access_range invalid_before_bits;
+ if (m_op.maybe_get_invalid_before_bits (&invalid_before_bits))
+ {
+ if (m_logger)
+ invalid_before_bits.log ("invalid_before_bits", *m_logger);
+ bit_size_expr num_before_bits;
+ if (invalid_before_bits.get_size (m_op.m_model, &num_before_bits))
+ {
+ styled_string label;
+ if (m_op.m_dir == DIR_READ)
+ label = num_before_bits.get_formatted_str
+ (m_sm,
+ _("under-read of %wi bit"),
+ _("under-read of %wi bits"),
+ _("under-read of %wi byte"),
+ _("under-read of %wi bytes"),
+ _("under-read of %qE bits"),
+ _("under-read of %qE bytes"));
+ else
+ label = num_before_bits.get_formatted_str
+ (m_sm,
+ _("underwrite of %wi bit"),
+ _("underwrite of %wi bits"),
+ _("underwrite of %wi byte"),
+ _("underwrite of %wi bytes"),
+ _("underwrite of %qE bits"),
+ _("underwrite of %qE bytes"));
+ w->add_range (m_btm.get_table_x_for_range (invalid_before_bits),
+ make_warning_string (std::move (label)),
+ m_invalid_style_id);
+ }
+ }
+ else
+ {
+ if (m_logger)
+ m_logger->log ("no invalid_before_bits");
+ }
+
+ /* It would be nice to be able to use std::optional<access_range> here,
+ but std::optional is C++17. */
+ bool got_valid_bits = false;
+ access_range valid_bits (m_op.get_valid_bits ());
+ bit_size_expr num_valid_bits;
+ if (valid_bits.get_size (m_op.m_model, &num_valid_bits))
+ {
+ if (m_logger)
+ valid_bits.log ("valid_bits", *m_logger);
+
+ got_valid_bits = true;
+ maybe_add_gap (w, invalid_before_bits, valid_bits);
+
+ styled_string label;
+ if (m_op.m_dir == DIR_READ)
+ label = num_valid_bits.get_formatted_str (m_sm,
+ _("size: %wi bit"),
+ _("size: %wi bits"),
+ _("size: %wi byte"),
+ _("size: %wi bytes"),
+ _("size: %qE bits"),
+ _("size: %qE bytes"));
+ else
+ label = num_valid_bits.get_formatted_str (m_sm,
+ _("capacity: %wi bit"),
+ _("capacity: %wi bits"),
+ _("capacity: %wi byte"),
+ _("capacity: %wi bytes"),
+ _("capacity: %qE bits"),
+ _("capacity: %qE bytes"));
+ w->add_range (m_btm.get_table_x_for_range (m_op.get_valid_bits ()),
+ std::move (label),
+ m_valid_style_id);
+ }
+
+ access_range invalid_after_bits;
+ if (m_op.maybe_get_invalid_after_bits (&invalid_after_bits))
+ {
+ if (got_valid_bits)
+ maybe_add_gap (w, valid_bits, invalid_after_bits);
+
+ if (m_logger)
+ invalid_before_bits.log ("invalid_after_bits", *m_logger);
+
+ bit_size_expr num_after_bits;
+ if (invalid_after_bits.get_size (m_op.m_model, &num_after_bits))
+ {
+ styled_string label;
+ if (m_op.m_dir == DIR_READ)
+ label = num_after_bits.get_formatted_str
+ (m_sm,
+ _("over-read of %wi bit"),
+ _("over-read of %wi bits"),
+ _("over-read of %wi byte"),
+ _("over-read of %wi bytes"),
+ _("over-read of %qE bits"),
+ _("over-read of %qE bytes"));
+ else
+ label = num_after_bits.get_formatted_str
+ (m_sm,
+ _("overflow of %wi bit"),
+ _("overflow of %wi bits"),
+ _("overflow of %wi byte"),
+ _("overflow of %wi bytes"),
+ _("over-read of %qE bits"),
+ _("overflow of %qE bytes"));
+ w->add_range (m_btm.get_table_x_for_range (invalid_after_bits),
+ make_warning_string (std::move (label)),
+ m_invalid_style_id);
+ }
+ }
+ else
+ {
+ if (m_logger)
+ m_logger->log ("no invalid_after_bits");
+ }
+
+ add_child (std::unique_ptr<widget> (w));
+ }
+
+ /* Subroutine of calc_req_size.
+ Try to allocate surplus canvas width to table columns to make the
+ per table-column canvas widths closer to being to scale.
+ See e.g.:
+ https://en.wikipedia.org/wiki/Fair_item_allocation
+ https://en.wikipedia.org/wiki/Mathematics_of_apportionment
+ */
+ void adjust_to_scale ()
+ {
+ LOG_SCOPE (m_logger);
+ const unsigned num_columns = m_btm.get_num_columns ();
+ std::vector<bit_offset_t> bit_sizes (num_columns);
+ for (unsigned table_x = 0; table_x < num_columns; table_x++)
+ {
+ access_range range_for_column (NULL, bit_range (0, 0));
+ if (m_btm.maybe_get_access_range_for_table_x (table_x,
+ &range_for_column))
+ {
+ bit_size_t size_in_bits;
+ if (!range_for_column.get_size_in_bits (&size_in_bits))
+ size_in_bits = BITS_PER_UNIT; // arbitrary non-zero value
+ gcc_assert (size_in_bits > 0);
+ bit_sizes[table_x] = size_in_bits;
+ }
+ else
+ bit_sizes[table_x] = 0;
+ }
+
+ while (adjust_to_scale_once (bit_sizes))
+ {
+ }
+ }
+ bool adjust_to_scale_once (const std::vector<bit_offset_t> &bit_sizes)
+ {
+ LOG_SCOPE (m_logger);
+
+ const unsigned num_columns = m_btm.get_num_columns ();
+
+ /* Find the total canvas width currently required.
+ Require one extra canvas column for the right-hand border
+ of the table. */
+ int total_width = 1;
+ for (unsigned table_x = 0; table_x < num_columns; table_x++)
+ {
+ int canvas_w = m_col_widths->m_requirements[table_x];
+ gcc_assert (canvas_w >= 0);
+ total_width += canvas_w + 1;
+ }
+
+ const int max_width = param_analyzer_text_art_ideal_canvas_width;
+ if (total_width >= max_width)
+ {
+ if (m_logger)
+ m_logger->log ("bailing out: total_width=%i ,>= max_width (%i)\n",
+ total_width, max_width);
+ return false;
+ }
+
+ const int fixed_point = 1024;
+ std::vector<bit_offset_t> canvas_w_per_bit (num_columns);
+ for (unsigned table_x = 0; table_x < num_columns; table_x++)
+ {
+ bit_offset_t bit_size = bit_sizes[table_x];
+ if (bit_size > 0)
+ canvas_w_per_bit[table_x]
+ = (m_col_widths->m_requirements[table_x] * fixed_point) / bit_size;
+ else
+ canvas_w_per_bit[table_x] = INT_MAX;
+ }
+
+ /* Find the min canvas per bit, and give an extra canvas column to
+ the table column that has least. */
+ size_t min_idx = std::distance (canvas_w_per_bit.begin (),
+ std::min_element (canvas_w_per_bit.begin (),
+ canvas_w_per_bit.end ()));
+ m_col_widths->m_requirements[min_idx] += 1;
+ if (m_logger)
+ m_logger->log ("adding 1 canvas_w to column %i\n", (int)min_idx);
+
+ return true; // keep going
+ }
+
+ const access_operation &m_op;
+ diagnostic_event_id_t m_region_creation_event_id;
+ style_manager &m_sm;
+ const theme &m_theme;
+ logger *m_logger;
+ /* In lieu of being able to throw exceptions, a flag to mark this object
+ as "invalid". */
+ bool m_invalid;
+
+ style::id_t m_valid_style_id;
+ style::id_t m_invalid_style_id;
+
+ valid_region_spatial_item m_valid_region_spatial_item;
+ accessed_region_spatial_item m_accessed_region_spatial_item;
+ std::unique_ptr<svalue_spatial_item> m_svalue_spatial_item;
+
+ std::unique_ptr<boundaries> m_boundaries;
+
+ bit_to_table_map m_btm;
+
+ bool m_calc_req_size_called;
+
+ /* Column widths shared by all x_aligned_table_widget,
+ created once we know how many columns we need. */
+ std::unique_ptr<table_dimension_sizes> m_col_widths;
+
+ /* All of the child x_aligned_table_widget that share
+ column widths. */
+ std::vector<x_aligned_table_widget *> m_aligned_table_widgets;
+
+/* Mapping from table_x to canvas_x. */
+ std::vector<int> m_col_start_x;
+};
+
+x_ruler
+x_aligned_x_ruler_widget::make_x_ruler () const
+{
+ x_ruler r (x_ruler::label_dir::BELOW);
+ for (auto& iter : m_labels)
+ {
+ canvas::range_t canvas_x_range
+ = m_dia_impl.get_canvas_x_range (iter.m_table_x_range);
+ /* Include the end-point. */
+ canvas_x_range.next++;
+ r.add_label (canvas_x_range, iter.m_text.copy (), iter.m_style_id,
+ x_ruler::label_kind::TEXT_WITH_BORDER);
+ }
+ return r;
+}
+
+/* class direction_widget : public leaf_widget. */
+
+/* Paint arrows indicating the direction of the access (read vs write),
+ but only in the X-extent corresponding to the region that's actually
+ accessed. */
+
+void
+direction_widget::paint_to_canvas (canvas &canvas)
+{
+ const access_range accessed_bits (m_dia_impl.get_op ().get_actual_bits ());
+
+ const access_range valid_bits (m_dia_impl.get_op ().get_valid_bits ());
+
+ for (unsigned table_x = 0; table_x < m_btm.get_num_columns (); table_x++)
+ {
+ access_range column_access_range;
+ if (m_btm.maybe_get_access_range_for_table_x (table_x,
+ &column_access_range))
+ {
+ /* Only paint arrows in the accessed region. */
+ if (!accessed_bits.contains_p (column_access_range))
+ continue;
+
+ /* Are we within the valid region? */
+ const bool is_valid (valid_bits.contains_p (column_access_range));
+ const style::id_t style_id
+ = m_dia_impl.get_style_id_for_validity (is_valid);
+ const canvas::range_t x_canvas_range
+ = m_dia_impl.get_canvas_x_range (table::range_t (table_x,
+ table_x + 1));
+ const int canvas_x = x_canvas_range.get_midpoint ();
+ m_dia_impl.get_theme ().paint_y_arrow
+ (canvas,
+ canvas_x,
+ canvas::range_t (get_y_range ()),
+ (m_dia_impl.get_op ().m_dir == DIR_READ
+ ? theme::y_arrow_dir::UP
+ : theme::y_arrow_dir::DOWN),
+ style_id);
+ }
+ }
+}
+
+/* class access_diagram : public text_art::wrapper_widget. */
+
+/* To hide the implementation details, this is merely a wrapper around
+ an access_diagram_impl. */
+
+access_diagram::access_diagram (const access_operation &op,
+ diagnostic_event_id_t region_creation_event_id,
+ style_manager &sm,
+ const theme &theme,
+ logger *logger)
+: wrapper_widget (make_unique <access_diagram_impl> (op,
+ region_creation_event_id,
+ sm,
+ theme,
+ logger))
+{
+}
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */