/* Text art visualizations within -fanalyzer. Copyright (C) 2023 Free Software Foundation, Inc. This file is part of GCC. GCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see . */ #include "config.h" #define INCLUDE_ALGORITHM #define INCLUDE_MEMORY #define INCLUDE_MAP #define INCLUDE_SET #define INCLUDE_VECTOR #include "system.h" #include "coretypes.h" #include "coretypes.h" #include "tree.h" #include "function.h" #include "basic-block.h" #include "gimple.h" #include "diagnostic.h" #include "intl.h" #include "make-unique.h" #include "tree-diagnostic.h" /* for default_tree_printer. */ #include "analyzer/analyzer.h" #include "analyzer/region-model.h" #include "analyzer/access-diagram.h" #include "text-art/ruler.h" #include "fold-const.h" #if ENABLE_ANALYZER /* Consider this code: int32_t arr[10]; arr[10] = x; where we've emitted a buffer overflow diagnostic like this: out-of-bounds write from byte 40 till byte 43 but 'arr' ends at byte 40 We want to emit a diagram that visualizes: - the spatial relationship between the valid region to access, versus the region that was actually accessed: does it overlap, was it touching, close, or far away? Was it before or after in memory? What are the relative sizes involved? - the direction of the access (read vs write) The following code supports emitting diagrams similar to the following: # +--------------------------------+ # |write from ‘x’ (type: ‘int32_t’)| # +--------------------------------+ # | # | # v # +---------+-----------+-----------+ +--------------------------------+ # | [0] | ... | [9] | | after valid range | # +---------+-----------+-----------+ | | # | ‘arr’ (type: ‘int32_t[10]’) | | | # +---------------------------------+ +--------------------------------+ # |~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~| |~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~| # | | # +---------+--------+ +---------+---------+ # |capacity: 40 bytes| |overflow of 4 bytes| # +------------------+ +-------------------+ where the diagram is laid out via table columns where each table column represents either a range of bits/bytes, or is a spacing column (to highlight the boundary between valid vs invalid accesses). The table columns can be seen via -fanalyzer-debug-text-art. For example, here there are 5 table columns ("tc0" through "tc4"): # +---------+-----------+-----------+---+--------------------------------+ # | tc0 | tc1 | tc2 |tc3| tc4 | # +---------+-----------+-----------+---+--------------------------------+ # |bytes 0-3|bytes 4-35 |bytes 36-39| | bytes 40-43 | # +---------+-----------+-----------+ +--------------------------------+ # # +--------------------------------+ # |write from ‘x’ (type: ‘int32_t’)| # +--------------------------------+ # | # | # v # +---------+-----------+-----------+ +--------------------------------+ # | [0] | ... | [9] | | after valid range | # +---------+-----------+-----------+ | | # | ‘arr’ (type: ‘int32_t[10]’) | | | # +---------------------------------+ +--------------------------------+ # |~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~| |~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~| # | | # +---------+--------+ +---------+---------+ # |capacity: 40 bytes| |overflow of 4 bytes| # +------------------+ +-------------------+ The diagram is built up from the following: # +--------------------------------+ # | ITEM FOR SVALUE/ACCESSED REGION| # +--------------------------------+ # | # | DIRECTION WIDGET # v # +---------------------------------+ +--------------------------------+ # | VALID REGION | | INVALID ACCESS | # +---------------------------------+ +--------------------------------+ # # | VALID-VS-INVALID RULER | i.e. a vbox_widget containing 4 child widgets laid out vertically: - ALIGNED CHILD WIDGET: ITEM FOR SVALUE/ACCESSED REGION - DIRECTION WIDGET - ALIGNED CHILD WIDGET: VALID AND INVALID ACCESSES - VALID-VS-INVALID RULER. A more complicated example, given this overflow: char buf[100]; strcpy (buf, LOREM_IPSUM); 01| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+ 02| |[0]|[1]|[2]|[3]|[4]|[5]| ... |[440]|[441]|[442]|[443]|[444]|[445]| 03| +---+---+---+---+---+---+ +-----+-----+-----+-----+-----+-----+ 04| |'L'|'o'|'r'|'e'|'m'|' '| | 'o' | 'r' | 'u' | 'm' | '.' | NUL | 05| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+ 06| | string literal (type: 'char[446]') | 07| +----------------------------------------------------------------------+ 08| | | | | | | | | | | | | | | | 09| | | | | | | | | | | | | | | | 10| v v v v v v v v v v v v v v v 11| +---+---------------------+----++--------------------------------------+ 12| |[0]| ... |[99]|| after valid range | 13| +---+---------------------+----+| | 14| | 'buf' (type: 'char[100]') || | 15| +------------------------------++--------------------------------------+ 16| |~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~||~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~| 17| | | 18| +---------+---------+ +----------+----------+ 19| |capacity: 100 bytes| |overflow of 346 bytes| 20| +-------------------+ +---------------------+ which is: 01| ALIGNED CHILD WIDGET (lines 01-07): (string_region_spatial_item)-+-----+ 02| |[0]|[1]|[2]|[3]|[4]|[5]| ... |[440]|[441]|[442]|[443]|[444]|[445]| 03| +---+---+---+---+---+---+ +-----+-----+-----+-----+-----+-----+ 04| |'L'|'o'|'r'|'e'|'m'|' '| | 'o' | 'r' | 'u' | 'm' | '.' | NUL | 05| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+ 06| | string literal (type: 'char[446]') | 07| +----------------------------------------------------------------------+ 08| DIRECTION WIDGET (lines 08-10) | | | | | | | 09| | | | | | | | | | | | | | | | 10| v v v v v v v v v v v v v v v 11| ALIGNED CHILD WIDGET (lines 11-15)-------------------------------------+ 12| VALID REGION ... |[99]|| INVALID ACCESS | 13| +---+---------------------+----+| | 14| | 'buf' (type: 'char[100]') || | 15| +------------------------------++--------------------------------------+ 16| VALID-VS-INVALID RULER (lines 16-20): ~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~| 17| | | 18| +---------+---------+ +----------+----------+ 19| |capacity: 100 bytes| |overflow of 346 bytes| 20| +-------------------+ +---------------------+ We build the diagram in several phases: - (1) we construct an access_diagram_impl widget. Within the ctor, we have these subphases: - (1.1) find all of the boundaries of interest - (1.2) use the boundaries to build a bit_table_map, associating bit ranges with table columns (e.g. "byte 0 is column 0, bytes 1-98 are column 2" etc) - (1.3) create child widgets that share this table-based geometry - (2) ask the widget for its size request - (2.1) column widths and row heights for the table are computed by access_diagram_impl::calc_req_size - (2.2) child widgets request sizes based on these widths/heights - (3) create a canvas of the appropriate size - (4) paint the widget hierarchy to the canvas. */ using namespace text_art; namespace ana { static styled_string fmt_styled_string (style_manager &sm, const char *fmt, ...) ATTRIBUTE_GCC_DIAG(2, 3); static styled_string fmt_styled_string (style_manager &sm, const char *fmt, ...) { va_list ap; va_start (ap, fmt); styled_string result = styled_string::from_fmt_va (sm, default_tree_printer, fmt, &ap); va_end (ap); return result; } class access_diagram_impl; class bit_to_table_map; static void pp_bit_size_t (pretty_printer *pp, bit_size_t num_bits) { if (num_bits % BITS_PER_UNIT == 0) { byte_size_t num_bytes = num_bits / BITS_PER_UNIT; if (num_bytes == 1) pp_printf (pp, _("%wi byte"), num_bytes.to_uhwi ()); else pp_printf (pp, _("%wi bytes"), num_bytes.to_uhwi ()); } else { if (num_bits == 1) pp_printf (pp, _("%wi bit"), num_bits.to_uhwi ()); else pp_printf (pp, _("%wi bits"), num_bits.to_uhwi ()); } } static styled_string get_access_size_str (style_manager &sm, const access_operation &op, access_range accessed_range, tree type) { bit_size_expr num_bits; if (accessed_range.get_size (op.m_model, &num_bits)) { if (type) { styled_string s; pretty_printer pp; num_bits.print (&pp); if (op.m_dir == DIR_READ) return fmt_styled_string (sm, _("read of %qT (%s)"), type, pp_formatted_text (&pp)); else return fmt_styled_string (sm, _("write of %qT (%s)"), type, pp_formatted_text (&pp)); } if (op.m_dir == DIR_READ) return num_bits.get_formatted_str (sm, _("read of %wi bit"), _("read of %wi bits"), _("read of %wi byte"), _("read of %wi bytes"), _("read of %qE bits"), _("read of %qE bytes")); else return num_bits.get_formatted_str (sm, _("write of %wi bit"), _("write of %wi bits"), _("write of %wi byte"), _("write of %wi bytes"), _("write of %qE bits"), _("write of %qE bytes")); } if (type) { if (op.m_dir == DIR_READ) return fmt_styled_string (sm, _("read of %qT"), type); else return fmt_styled_string (sm, _("write of %qT"), type); } if (op.m_dir == DIR_READ) return styled_string (sm, _("read")); else return styled_string (sm, _("write")); } /* Subroutine of clean_up_for_diagram. */ static tree strip_any_cast (tree expr) { if (TREE_CODE (expr) == NOP_EXPR || TREE_CODE (expr) == NON_LVALUE_EXPR) expr = TREE_OPERAND (expr, 0); return expr; } /* Subroutine of clean_up_for_diagram. */ static tree remove_ssa_names (tree expr) { if (TREE_CODE (expr) == SSA_NAME && SSA_NAME_VAR (expr)) return SSA_NAME_VAR (expr); tree t = copy_node (expr); for (int i = 0; i < TREE_OPERAND_LENGTH (expr); i++) TREE_OPERAND (t, i) = remove_ssa_names (TREE_OPERAND (expr, i)); return t; } /* We want to be able to print tree expressions from the analyzer, which is in the middle end. We could use the front-end pretty_printer's formatting routine, but: (a) some have additional state in a pretty_printer subclass, so we'd need to clone global_dc->printer (b) the "aka" type information added by the C and C++ frontends are too verbose when building a diagram, and there isn't a good way to ask for a less verbose version of them. Hence we use default_tree_printer. However, we want to avoid printing SSA names, and instead print the underlying var name. Ideally there would be a better tree printer for use by middle end warnings, but as workaround, this function clones a tree, replacing SSA names with the var names. */ tree clean_up_for_diagram (tree expr) { tree without_ssa_names = remove_ssa_names (expr); return strip_any_cast (without_ssa_names); } /* struct bit_size_expr. */ text_art::styled_string bit_size_expr::get_formatted_str (text_art::style_manager &sm, const char *concrete_single_bit_fmt, const char *concrete_plural_bits_fmt, const char *concrete_single_byte_fmt, const char *concrete_plural_bytes_fmt, const char *symbolic_bits_fmt, const char *symbolic_bytes_fmt) const { if (TREE_CODE (m_num_bits) == INTEGER_CST) { bit_size_t concrete_num_bits = wi::to_offset (m_num_bits); if (concrete_num_bits % BITS_PER_UNIT == 0) { byte_size_t concrete_num_bytes = concrete_num_bits / BITS_PER_UNIT; if (concrete_num_bytes == 1) return fmt_styled_string (sm, concrete_single_byte_fmt, concrete_num_bytes.to_uhwi ()); else return fmt_styled_string (sm, concrete_plural_bytes_fmt, concrete_num_bytes.to_uhwi ()); } else { if (concrete_num_bits == 1) return fmt_styled_string (sm, concrete_single_bit_fmt, concrete_num_bits.to_uhwi ()); else return fmt_styled_string (sm, concrete_plural_bits_fmt, concrete_num_bits.to_uhwi ()); } } else { if (tree bytes_expr = maybe_get_as_bytes ()) return fmt_styled_string (sm, symbolic_bytes_fmt, clean_up_for_diagram (bytes_expr)); return fmt_styled_string (sm, symbolic_bits_fmt, clean_up_for_diagram (m_num_bits)); } } void bit_size_expr::print (pretty_printer *pp) const { if (TREE_CODE (m_num_bits) == INTEGER_CST) { bit_size_t concrete_num_bits = wi::to_offset (m_num_bits); pp_bit_size_t (pp, concrete_num_bits); } else { if (tree bytes_expr = maybe_get_as_bytes ()) pp_printf (pp, _("%qE bytes"), bytes_expr); else pp_printf (pp, _("%qE bits"), m_num_bits); } } tree bit_size_expr::maybe_get_as_bytes () const { switch (TREE_CODE (m_num_bits)) { default: break; case INTEGER_CST: { const bit_size_t num_bits = wi::to_offset (m_num_bits); if (num_bits % BITS_PER_UNIT != 0) return NULL_TREE; const bit_size_t num_bytes = num_bits / BITS_PER_UNIT; return wide_int_to_tree (size_type_node, num_bytes); } break; case PLUS_EXPR: case MINUS_EXPR: { bit_size_expr op0 = bit_size_expr (TREE_OPERAND (m_num_bits, 0)); tree op0_as_bytes = op0.maybe_get_as_bytes (); if (!op0_as_bytes) return NULL_TREE; bit_size_expr op1 = bit_size_expr (TREE_OPERAND (m_num_bits, 1)); tree op1_as_bytes = op1.maybe_get_as_bytes (); if (!op1_as_bytes) return NULL_TREE; return fold_build2 (TREE_CODE (m_num_bits), size_type_node, op0_as_bytes, op1_as_bytes); } break; case MULT_EXPR: { bit_size_expr op1 = bit_size_expr (TREE_OPERAND (m_num_bits, 1)); if (tree op1_as_bytes = op1.maybe_get_as_bytes ()) return fold_build2 (MULT_EXPR, size_type_node, TREE_OPERAND (m_num_bits, 0), op1_as_bytes); } break; } return NULL_TREE; } /* struct access_range. */ access_range::access_range (const region *base_region, const bit_range &bits) : m_start (region_offset::make_concrete (base_region, bits.get_start_bit_offset ())), m_next (region_offset::make_concrete (base_region, bits.get_next_bit_offset ())) { } access_range::access_range (const region *base_region, const byte_range &bytes) : m_start (region_offset::make_concrete (base_region, bytes.get_start_bit_offset ())), m_next (region_offset::make_concrete (base_region, bytes.get_next_bit_offset ())) { } access_range::access_range (const region ®, region_model_manager *mgr) : m_start (reg.get_offset (mgr)), m_next (reg.get_next_offset (mgr)) { } bool access_range::get_size (const region_model &model, bit_size_expr *out) const { tree start_expr = m_start.calc_symbolic_bit_offset (model); if (!start_expr) return false; tree next_expr = m_next.calc_symbolic_bit_offset (model); if (!next_expr) return false; *out = bit_size_expr (fold_build2 (MINUS_EXPR, size_type_node, next_expr, start_expr)); return true; } bool access_range::contains_p (const access_range &other) const { return (m_start <= other.m_start && other.m_next <= m_next); } bool access_range::empty_p () const { bit_range concrete_bits (0, 0); if (!as_concrete_bit_range (&concrete_bits)) return false; return concrete_bits.empty_p (); } void access_range::dump_to_pp (pretty_printer *pp, bool simple) const { if (m_start.concrete_p () && m_next.concrete_p ()) { bit_range bits (m_start.get_bit_offset (), m_next.get_bit_offset () - m_start.get_bit_offset ()); bits.dump_to_pp (pp); return; } pp_character (pp, '['); m_start.dump_to_pp (pp, simple); pp_string (pp, " to "); m_next.dump_to_pp (pp, simple); pp_character (pp, ')'); } DEBUG_FUNCTION void access_range::dump (bool simple) const { pretty_printer pp; pp_format_decoder (&pp) = default_tree_printer; pp_show_color (&pp) = pp_show_color (global_dc->printer); pp.buffer->stream = stderr; dump_to_pp (&pp, simple); pp_newline (&pp); pp_flush (&pp); } void access_range::log (const char *title, logger &logger) const { logger.start_log_line (); logger.log_partial ("%s: ", title); dump_to_pp (logger.get_printer (), true); logger.end_log_line (); } /* struct access_operation. */ access_range access_operation::get_valid_bits () const { const svalue *capacity_in_bytes_sval = m_model.get_capacity (m_base_region); return access_range (region_offset::make_concrete (m_base_region, 0), region_offset::make_byte_offset (m_base_region, capacity_in_bytes_sval)); } access_range access_operation::get_actual_bits () const { return access_range (m_reg, get_manager ()); } /* If there are any bits accessed invalidly before the valid range, return true and write their range to *OUT. Return false if there aren't, or if there's a problem (e.g. symbolic ranges. */ bool access_operation::maybe_get_invalid_before_bits (access_range *out) const { access_range valid_bits (get_valid_bits ()); access_range actual_bits (get_actual_bits ()); if (actual_bits.m_start >= valid_bits.m_start) { /* No part of accessed range is before the valid range. */ return false; } else if (actual_bits.m_next > valid_bits.m_start) { /* Get part of accessed range that's before the valid range. */ *out = access_range (actual_bits.m_start, valid_bits.m_start); return true; } else { /* Accessed range is fully before valid range. */ *out = actual_bits; return true; } } /* If there are any bits accessed invalidly after the valid range, return true and write their range to *OUT. Return false if there aren't, or if there's a problem. */ bool access_operation::maybe_get_invalid_after_bits (access_range *out) const { access_range valid_bits (get_valid_bits ()); access_range actual_bits (get_actual_bits ()); if (actual_bits.m_next <= valid_bits.m_next) { /* No part of accessed range is after the valid range. */ return false; } else if (actual_bits.m_start < valid_bits.m_next) { /* Get part of accessed range that's after the valid range. */ *out = access_range (valid_bits.m_next, actual_bits.m_next); return true; } else { /* Accessed range is fully after valid range. */ *out = actual_bits; return true; } } /* A class for capturing all of the region offsets of interest (both concrete and symbolic), to help align everything in the diagram. Boundaries can be soft or hard; hard boundaries are emphasized visually (e.g. the boundary between valid vs invalid accesses). Offsets in the boundaries are all expressed relative to the base region of the access_operation. */ class boundaries { public: enum class kind { HARD, SOFT}; boundaries (const region &base_reg) : m_base_reg (base_reg) { } void add (region_offset offset, enum kind k) { m_all_offsets.insert (offset); if (k == kind::HARD) m_hard_offsets.insert (offset); } void add (const access_range &range, enum kind kind) { add (range.m_start, kind); add (range.m_next, kind); } void add (const region ®, region_model_manager *mgr, enum kind kind) { add (access_range (reg.get_offset (mgr), reg.get_next_offset (mgr)), kind); } void add (const byte_range bytes, enum kind kind) { add (access_range (&m_base_reg, bytes), kind); } void add_all_bytes_in_range (const byte_range &bytes) { for (byte_offset_t byte_idx = bytes.get_start_byte_offset (); byte_idx <= bytes.get_next_byte_offset (); byte_idx = byte_idx + 1) add (region_offset::make_concrete (&m_base_reg, byte_idx * 8), kind::SOFT); } void add_all_bytes_in_range (const access_range &range) { byte_range bytes (0, 0); bool valid = range.as_concrete_byte_range (&bytes); gcc_assert (valid); add_all_bytes_in_range (bytes); } void log (logger &logger) const { logger.log ("boundaries:"); logger.inc_indent (); for (auto offset : m_all_offsets) { enum kind k = get_kind (offset); logger.start_log_line (); logger.log_partial ("%s: ", (k == kind::HARD) ? "HARD" : "soft"); offset.dump_to_pp (logger.get_printer (), true); logger.end_log_line (); } logger.dec_indent (); } enum kind get_kind (region_offset offset) const { gcc_assert (m_all_offsets.find (offset) != m_all_offsets.end ()); if (m_hard_offsets.find (offset) != m_hard_offsets.end ()) return kind::HARD; else return kind::SOFT; } std::set::const_iterator begin () const { return m_all_offsets.begin (); } std::set::const_iterator end () const { return m_all_offsets.end (); } std::set::size_type size () const { return m_all_offsets.size (); } private: const region &m_base_reg; std::set m_all_offsets; std::set m_hard_offsets; }; /* A widget that wraps a table but offloads column-width calculation to a shared object, so that we can vertically line up multiple tables and have them all align their columns. For example, in: 01| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+ 02| |[0]|[1]|[2]|[3]|[4]|[5]| ... |[440]|[441]|[442]|[443]|[444]|[445]| 03| +---+---+---+---+---+---+ +-----+-----+-----+-----+-----+-----+ 04| |'L'|'o'|'r'|'e'|'m'|' '| | 'o' | 'r' | 'u' | 'm' | '.' | NUL | 05| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+ 06| | string literal (type: 'char[446]') | 07| +----------------------------------------------------------------------+ 08| | | | | | | | | | | | | | | | 09| | | | | | | | | | | | | | | | 10| v v v v v v v v v v v v v v v 11|+---+---------------------+----++--------------------------------------+ 12||[0]| ... |[99]|| after valid range | 13|+---+---------------------+----+| | 14|| 'buf' (type: 'char[100]') || | 15|+------------------------------++--------------------------------------+ 16||~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~||~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~| 17| | | 18| +---------+---------+ +----------+----------+ 19| |capacity: 100 bytes| |overflow of 346 bytes| 20| +-------------------+ +---------------------+ rows 01-07 and rows 11-15 are x_aligned_table_widget instances. */ class x_aligned_table_widget : public leaf_widget { public: x_aligned_table_widget (table t, const theme &theme, table_dimension_sizes &col_widths) : m_table (std::move (t)), m_theme (theme), m_col_widths (col_widths), m_row_heights (t.get_size ().h), m_cell_sizes (m_col_widths, m_row_heights), m_tg (m_table, m_cell_sizes) { } const char *get_desc () const override { return "x_aligned_table_widget"; } canvas::size_t calc_req_size () final override { /* We don't compute the size requirements; the parent should have done this. */ return m_tg.get_canvas_size (); } void paint_to_canvas (canvas &canvas) final override { m_table.paint_to_canvas (canvas, get_top_left (), m_tg, m_theme); } const table &get_table () const { return m_table; } table_cell_sizes &get_cell_sizes () { return m_cell_sizes; } void recalc_coords () { m_tg.recalc_coords (); } private: table m_table; const theme &m_theme; table_dimension_sizes &m_col_widths; // Reference to shared column widths table_dimension_sizes m_row_heights; // Unique row heights table_cell_sizes m_cell_sizes; table_geometry m_tg; }; /* A widget for printing arrows between the accessed region and the svalue, showing the direction of the access. For example, in: 01| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+ 02| |[0]|[1]|[2]|[3]|[4]|[5]| ... |[440]|[441]|[442]|[443]|[444]|[445]| 03| +---+---+---+---+---+---+ +-----+-----+-----+-----+-----+-----+ 04| |'L'|'o'|'r'|'e'|'m'|' '| | 'o' | 'r' | 'u' | 'm' | '.' | NUL | 05| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+ 06| | string literal (type: 'char[446]') | 07| +----------------------------------------------------------------------+ 08| | | | | | | | | | | | | | | | 09| | | | | | | | | | | | | | | | 10| v v v v v v v v v v v v v v v 11|+---+---------------------+----++--------------------------------------+ 12||[0]| ... |[99]|| after valid range | 13|+---+---------------------+----+| | 14|| 'buf' (type: 'char[100]') || | 15|+------------------------------++--------------------------------------+ 16||~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~||~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~| 17| | | 18| +---------+---------+ +----------+----------+ 19| |capacity: 100 bytes| |overflow of 346 bytes| 20| +-------------------+ +---------------------+ rows 8-10 are the direction widget. */ class direction_widget : public leaf_widget { public: direction_widget (const access_diagram_impl &dia_impl, const bit_to_table_map &btm) : leaf_widget (), m_dia_impl (dia_impl), m_btm (btm) { } const char *get_desc () const override { return "direction_widget"; } canvas::size_t calc_req_size () final override { /* Get our width from our siblings. */ return canvas::size_t (0, 3); } void paint_to_canvas (canvas &canvas) final override; private: const access_diagram_impl &m_dia_impl; const bit_to_table_map &m_btm; }; /* A widget for adding an x_ruler to a diagram based on table columns, offloading column-width calculation to shared objects, so that the ruler lines up with other tables in the diagram. For example, in: 01| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+ 02| |[0]|[1]|[2]|[3]|[4]|[5]| ... |[440]|[441]|[442]|[443]|[444]|[445]| 03| +---+---+---+---+---+---+ +-----+-----+-----+-----+-----+-----+ 04| |'L'|'o'|'r'|'e'|'m'|' '| | 'o' | 'r' | 'u' | 'm' | '.' | NUL | 05| +---+---+---+---+---+---+----------+-----+-----+-----+-----+-----+-----+ 06| | string literal (type: 'char[446]') | 07| +----------------------------------------------------------------------+ 08| | | | | | | | | | | | | | | | 09| | | | | | | | | | | | | | | | 10| v v v v v v v v v v v v v v v 11|+---+---------------------+----++--------------------------------------+ 12||[0]| ... |[99]|| after valid range | 13|+---+---------------------+----+| | 14|| 'buf' (type: 'char[100]') || | 15|+------------------------------++--------------------------------------+ 16||~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~||~~~~~~~~~~~~~~~~~~+~~~~~~~~~~~~~~~~~~~| 17| | | 18| +---------+---------+ +----------+----------+ 19| |capacity: 100 bytes| |overflow of 346 bytes| 20| +-------------------+ +---------------------+ rows 16-20 are the x_aligned_x_ruler_widget. */ class x_aligned_x_ruler_widget : public leaf_widget { public: x_aligned_x_ruler_widget (const access_diagram_impl &dia_impl, const theme &theme, table_dimension_sizes &col_widths) : m_dia_impl (dia_impl), m_theme (theme), m_col_widths (col_widths) { } const char *get_desc () const override { return "x_aligned_ruler_widget"; } void add_range (const table::range_t &x_range, styled_string text, style::id_t style_id) { m_labels.push_back (label (x_range, std::move (text), style_id)); } canvas::size_t calc_req_size () final override { x_ruler r (make_x_ruler ()); return r.get_size (); } void paint_to_canvas (canvas &canvas) final override { x_ruler r (make_x_ruler ()); r.paint_to_canvas (canvas, get_top_left (), m_theme); } private: struct label { label (const table::range_t &table_x_range, styled_string text, style::id_t style_id) : m_table_x_range (table_x_range), m_text (std::move (text)), m_style_id (style_id) { } table::range_t m_table_x_range; styled_string m_text; style::id_t m_style_id; }; x_ruler make_x_ruler () const; const access_diagram_impl &m_dia_impl; const theme &m_theme; table_dimension_sizes &m_col_widths; std::vector