/* 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 ®, region_model_manager *mgr) : m_start (reg.get_offset (mgr)), m_next (reg.get_next_offset (mgr)) { } bool access_range::get_size (const region_model &model, bit_size_expr *out) const { tree start_expr = m_start.calc_symbolic_bit_offset (model); if (!start_expr) return false; tree next_expr = m_next.calc_symbolic_bit_offset (model); if (!next_expr) return false; *out = bit_size_expr (fold_build2 (MINUS_EXPR, size_type_node, next_expr, start_expr)); return true; } bool access_range::contains_p (const access_range &other) const { return (m_start <= other.m_start && other.m_next <= m_next); } bool access_range::empty_p () const { bit_range concrete_bits (0, 0); if (!as_concrete_bit_range (&concrete_bits)) return false; return concrete_bits.empty_p (); } void access_range::dump_to_pp (pretty_printer *pp, bool simple) const { if (m_start.concrete_p () && m_next.concrete_p ()) { bit_range bits (m_start.get_bit_offset (), m_next.get_bit_offset () - m_start.get_bit_offset ()); bits.dump_to_pp (pp); return; } pp_character (pp, '['); m_start.dump_to_pp (pp, simple); pp_string (pp, " to "); m_next.dump_to_pp (pp, simple); pp_character (pp, ')'); } DEBUG_FUNCTION void access_range::dump (bool simple) const { pretty_printer pp; pp_format_decoder (&pp) = default_tree_printer; pp_show_color (&pp) = pp_show_color (global_dc->printer); pp.buffer->stream = stderr; dump_to_pp (&pp, simple); pp_newline (&pp); pp_flush (&pp); } void access_range::log (const char *title, logger &logger) const { logger.start_log_line (); logger.log_partial ("%s: ", title); dump_to_pp (logger.get_printer (), true); logger.end_log_line (); } /* struct access_operation. */ access_range access_operation::get_valid_bits () const { const svalue *capacity_in_bytes_sval = m_model.get_capacity (m_base_region); return access_range (region_offset::make_concrete (m_base_region, 0), region_offset::make_byte_offset (m_base_region, capacity_in_bytes_sval)); } access_range access_operation::get_actual_bits () const { return access_range (m_reg, get_manager ()); } /* If there are any bits accessed invalidly before the valid range, return true and write their range to *OUT. Return false if there aren't, or if there's a problem (e.g. symbolic ranges. */ bool access_operation::maybe_get_invalid_before_bits (access_range *out) const { access_range valid_bits (get_valid_bits ()); access_range actual_bits (get_actual_bits ()); if (actual_bits.m_start >= valid_bits.m_start) { /* No part of accessed range is before the valid range. */ return false; } else if (actual_bits.m_next > valid_bits.m_start) { /* Get part of accessed range that's before the valid range. */ *out = access_range (actual_bits.m_start, valid_bits.m_start); return true; } else { /* Accessed range is fully before valid range. */ *out = actual_bits; return true; } } /* If there are any bits accessed invalidly after the valid range, return true and write their range to *OUT. Return false if there aren't, or if there's a problem. */ bool access_operation::maybe_get_invalid_after_bits (access_range *out) const { access_range valid_bits (get_valid_bits ()); access_range actual_bits (get_actual_bits ()); if (actual_bits.m_next <= valid_bits.m_next) { /* No part of accessed range is after the valid range. */ return false; } else if (actual_bits.m_start < valid_bits.m_next) { /* Get part of accessed range that's after the valid range. */ *out = access_range (valid_bits.m_next, actual_bits.m_next); return true; } else { /* Accessed range is fully after valid range. */ *out = actual_bits; return true; } } /* A class for capturing all of the region offsets of interest (both concrete and symbolic), to help align everything in the diagram. Boundaries can be soft or hard; hard boundaries are emphasized visually (e.g. the boundary between valid vs invalid accesses). Offsets in the boundaries are all expressed relative to the base region of the access_operation. */ class boundaries { public: enum class kind { HARD, SOFT}; boundaries (const region &base_reg) : m_base_reg (base_reg) { } void add (region_offset offset, enum kind k) { m_all_offsets.insert (offset); if (k == kind::HARD) m_hard_offsets.insert (offset); } void add (const access_range &range, enum kind kind) { add (range.m_start, kind); add (range.m_next, kind); } void add (const region ®, region_model_manager *mgr, enum kind kind) { add (access_range (reg.get_offset (mgr), reg.get_next_offset (mgr)), kind); } void add (const byte_range bytes, enum kind kind) { add (access_range (&m_base_reg, bytes), kind); } void add_all_bytes_in_range (const byte_range &bytes) { for (byte_offset_t byte_idx = bytes.get_start_byte_offset (); byte_idx <= bytes.get_next_byte_offset (); byte_idx = byte_idx + 1) add (region_offset::make_concrete (&m_base_reg, byte_idx * 8), kind::SOFT); } void add_all_bytes_in_range (const access_range &range) { byte_range bytes (0, 0); bool valid = range.as_concrete_byte_range (&bytes); gcc_assert (valid); add_all_bytes_in_range (bytes); } void log (logger &logger) const { logger.log ("boundaries:"); logger.inc_indent (); for (auto offset : m_all_offsets) { enum kind k = get_kind (offset); logger.start_log_line (); logger.log_partial ("%s: ", (k == kind::HARD) ? "HARD" : "soft"); offset.dump_to_pp (logger.get_printer (), true); logger.end_log_line (); } logger.dec_indent (); } enum kind get_kind (region_offset offset) const { gcc_assert (m_all_offsets.find (offset) != m_all_offsets.end ()); if (m_hard_offsets.find (offset) != m_hard_offsets.end ()) return kind::HARD; else return kind::SOFT; } std::set<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 ~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; }; /* 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 */