diff --git a/gcc/analyzer/access-diagram.cc b/gcc/analyzer/access-diagram.cc index a9c5c899950..4cb6570e90b 100644 --- a/gcc/analyzer/access-diagram.cc +++ b/gcc/analyzer/access-diagram.cc @@ -40,6 +40,7 @@ along with GCC; see the file COPYING3. If not see #include "analyzer/access-diagram.h" #include "text-art/ruler.h" #include "fold-const.h" +#include "analyzer/analyzer-selftests.h" #if ENABLE_ANALYZER @@ -237,16 +238,14 @@ get_access_size_str (style_manager &sm, access_range accessed_range, tree type) { - bit_size_expr num_bits; - if (accessed_range.get_size (op.m_model, &num_bits)) + bit_size_expr num_bits (accessed_range.get_size (op.m_model.get_manager ())); + if (type) { - if (type) + styled_string s; + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + if (num_bits.maybe_print_for_user (&pp, op.m_model)) { - 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)"), @@ -258,22 +257,30 @@ get_access_size_str (style_manager &sm, 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 (op.m_dir == DIR_READ) + { + if (auto p + = num_bits.maybe_get_formatted_str (sm, op.m_model, + _("read of %wi bit"), + _("read of %wi bits"), + _("read of %wi byte"), + _("read of %wi bytes"), + _("read of %qs bits"), + _("read of %qs bytes"))) + return std::move (*p.get ()); + } + else + { + if (auto p + = num_bits.maybe_get_formatted_str (sm, op.m_model, + _("write of %wi bit"), + _("write of %wi bits"), + _("write of %wi byte"), + _("write of %wi bytes"), + _("write of %qs bits"), + _("write of %qs bytes"))) + return std::move (*p.get ()); } if (type) @@ -301,9 +308,9 @@ strip_any_cast (tree expr) return expr; } -/* Subroutine of clean_up_for_diagram. */ +/* Duplicate EXPR, replacing any SSA names with the underlying variable. */ -static tree +tree remove_ssa_names (tree expr) { if (TREE_CODE (expr) == SSA_NAME @@ -343,112 +350,124 @@ clean_up_for_diagram (tree expr) /* 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 +/* Attempt to generate a user-facing styled string that mentions this + bit_size_expr. + Use MODEL for extracting representative tree values where necessary. + The CONCRETE_* format strings should contain a single %wi. + The SYMBOLIC_* format strings should contain a single %qs. + Return nullptr if unable to represent the expression. */ + +std::unique_ptr +bit_size_expr::maybe_get_formatted_str (text_art::style_manager &sm, + const region_model &model, + const char *concrete_single_bit_fmt, + const char *concrete_plural_bits_fmt, + const char *concrete_single_byte_fmt, + const char *concrete_plural_bytes_fmt, + const char *symbolic_bits_fmt, + const char *symbolic_bytes_fmt) const { - if (TREE_CODE (m_num_bits) == INTEGER_CST) + region_model_manager &mgr = *model.get_manager (); + if (const svalue *num_bytes = maybe_get_as_bytes (mgr)) { - bit_size_t concrete_num_bits = wi::to_offset (m_num_bits); - if (concrete_num_bits % BITS_PER_UNIT == 0) + if (tree cst = num_bytes->maybe_get_constant ()) { - byte_size_t concrete_num_bytes = concrete_num_bits / BITS_PER_UNIT; + byte_size_t concrete_num_bytes = wi::to_offset (cst); if (concrete_num_bytes == 1) - return fmt_styled_string (sm, concrete_single_byte_fmt, - concrete_num_bytes.to_uhwi ()); + return ::make_unique + (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 ()); + return ::make_unique + (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 ()); + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + if (!num_bytes->maybe_print_for_user (&pp, model)) + return nullptr; + return ::make_unique + (fmt_styled_string (sm, symbolic_bytes_fmt, + pp_formatted_text (&pp))); } } - else + else if (tree cst = m_num_bits.maybe_get_constant ()) { - 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); + bit_size_t concrete_num_bits = wi::to_offset (cst); + if (concrete_num_bits == 1) + return ::make_unique + (fmt_styled_string (sm, concrete_single_bit_fmt, + concrete_num_bits.to_uhwi ())); else - pp_printf (pp, _("%qE bits"), m_num_bits); + return ::make_unique + (fmt_styled_string (sm, concrete_plural_bits_fmt, + concrete_num_bits.to_uhwi ())); + } + else + { + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + if (!m_num_bits.maybe_print_for_user (&pp, model)) + return nullptr; + return ::make_unique + (fmt_styled_string (sm, symbolic_bits_fmt, + pp_formatted_text (&pp))); } } -tree -bit_size_expr::maybe_get_as_bytes () const +bool +bit_size_expr::maybe_print_for_user (pretty_printer *pp, + const region_model &model) const { - switch (TREE_CODE (m_num_bits)) + if (tree cst = m_num_bits.maybe_get_constant ()) { - 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; + bit_size_t concrete_num_bits = wi::to_offset (cst); + pp_bit_size_t (pp, concrete_num_bits); + return true; } - return NULL_TREE; + else + { + if (const svalue *num_bytes = maybe_get_as_bytes (*model.get_manager ())) + { + pretty_printer tmp_pp; + pp_format_decoder (&tmp_pp) = default_tree_printer; + if (!num_bytes->maybe_print_for_user (&tmp_pp, model)) + return false; + pp_printf (pp, _("%qs bytes"), pp_formatted_text (&tmp_pp)); + return true; + } + else + { + pretty_printer tmp_pp; + pp_format_decoder (&tmp_pp) = default_tree_printer; + if (!m_num_bits.maybe_print_for_user (&tmp_pp, model)) + return false; + pp_printf (pp, _("%qs bits"), pp_formatted_text (&tmp_pp)); + return true; + } + } +} + +/* Attempt to get a symbolic value for this symbolic bit size, + expressed in bytes. + Return null if it's not known to divide exactly. */ + +const svalue * +bit_size_expr::maybe_get_as_bytes (region_model_manager &mgr) const +{ + if (tree cst = m_num_bits.maybe_get_constant ()) + { + bit_offset_t concrete_bits = wi::to_offset (cst); + if (concrete_bits % BITS_PER_UNIT != 0) + /* Not an exact multiple, so fail. */ + return nullptr; + } + const svalue *bits_per_byte + = mgr.get_or_create_int_cst (NULL_TREE, BITS_PER_UNIT); + return mgr.maybe_fold_binop (NULL_TREE, EXACT_DIV_EXPR, + &m_num_bits, bits_per_byte); } /* struct access_range. */ @@ -470,23 +489,19 @@ access_range::access_range (const region *base_region, const byte_range &bytes) } access_range::access_range (const region ®, region_model_manager *mgr) -: m_start (reg.get_offset (mgr)), - m_next (reg.get_next_offset (mgr)) +: m_start (strip_types (reg.get_offset (mgr), *mgr)), + m_next (strip_types (reg.get_next_offset (mgr), *mgr)) { } -bool -access_range::get_size (const region_model &model, bit_size_expr *out) const +bit_size_expr +access_range::get_size (region_model_manager *mgr) 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; + const svalue &start_bit_offset = m_start.calc_symbolic_bit_offset (mgr); + const svalue &next_bit_offset = m_next.calc_symbolic_bit_offset (mgr); + return bit_size_expr + (*mgr->get_or_create_binop (NULL_TREE, MINUS_EXPR, + &next_bit_offset, &start_bit_offset)); } bool @@ -551,7 +566,8 @@ 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)); + region_offset::make_byte_offset (m_base_region, capacity_in_bytes_sval), + *get_manager ()); } access_range @@ -579,7 +595,8 @@ access_operation::maybe_get_invalid_before_bits (access_range *out) const 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); + *out = access_range (actual_bits.m_start, valid_bits.m_start, + *get_manager ()); return true; } else @@ -608,7 +625,8 @@ access_operation::maybe_get_invalid_after_bits (access_range *out) const 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); + *out = access_range (valid_bits.m_next, actual_bits.m_next, + *get_manager ()); return true; } else @@ -663,7 +681,8 @@ public: void add (const region ®, region_model_manager *mgr, enum kind kind) { add (access_range (reg.get_offset (mgr), - reg.get_next_offset (mgr)), + reg.get_next_offset (mgr), + *mgr), kind); } @@ -1004,7 +1023,9 @@ 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) + void populate (const boundaries &boundaries, + region_model_manager &mgr, + logger *logger) { LOG_SCOPE (logger); @@ -1043,7 +1064,8 @@ public: { 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); + m_range_for_table_x[table_x] + = access_range (offset, next_offset, mgr); } table_x += 1; } @@ -2072,7 +2094,7 @@ public: 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); + m_btm.populate (*m_boundaries, *m_op.get_manager (), logger); /* Gracefully reject cases where the boundary sorting has gone wrong (due to awkward combinations of symbolic values). */ @@ -2291,30 +2313,19 @@ private: 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); + region_model_manager *mgr = m_op.get_manager (); + const svalue &lower_next = lower.m_next.calc_symbolic_bit_offset (mgr); + const svalue &upper_start = upper.m_start.calc_symbolic_bit_offset (mgr); + const svalue *num_bits_gap + = mgr->get_or_create_binop (NULL_TREE, MINUS_EXPR, + &upper_start, &lower_next); if (m_logger) - m_logger->log ("num_bits_gap: %qE", num_bits_gap); - tree zero = build_int_cst (size_type_node, 0); + m_logger->log ("num_bits_gap: %qs", num_bits_gap->get_desc ().get ()); + + const svalue *zero = mgr->get_or_create_int_cst (NULL_TREE, 0); tristate ts_gt_zero = m_op.m_model.eval_condition (num_bits_gap, GT_EXPR, - zero, - NULL); + zero); if (ts_gt_zero.is_false ()) { if (m_logger) @@ -2322,18 +2333,23 @@ private: 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); + bit_size_expr num_bits (*num_bits_gap); + if (auto p = num_bits.maybe_get_formatted_str (m_sm, m_op.m_model, + _("%wi bit"), + _("%wi bits"), + _("%wi byte"), + _("%wi bytes"), + _("%qs bits"), + _("%qs bytes"))) + { + styled_string label = std::move (*p.get ()); + w->add_range (m_btm.get_table_x_for_range + (access_range (lower.m_next, + upper.m_start, + *mgr)), + std::move (label), + style::id_plain); + } } styled_string @@ -2367,32 +2383,31 @@ private: { 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); - } + bit_size_expr num_before_bits + (invalid_before_bits.get_size (m_op.get_manager ())); + std::unique_ptr label; + if (m_op.m_dir == DIR_READ) + label = num_before_bits.maybe_get_formatted_str + (m_sm, m_op.m_model, + _("under-read of %wi bit"), + _("under-read of %wi bits"), + _("under-read of %wi byte"), + _("under-read of %wi bytes"), + _("under-read of %qs bits"), + _("under-read of %qs bytes")); + else + label = num_before_bits.maybe_get_formatted_str + (m_sm, m_op.m_model, + _("underwrite of %wi bit"), + _("underwrite of %wi bits"), + _("underwrite of %wi byte"), + _("underwrite of %wi bytes"), + _("underwrite of %qs bits"), + _("underwrite of %qs bytes")); + if (label) + w->add_range (m_btm.get_table_x_for_range (invalid_before_bits), + make_warning_string (std::move (*label)), + m_invalid_style_id); } else { @@ -2404,36 +2419,37 @@ private: 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); + bit_size_expr num_valid_bits (valid_bits.get_size (m_op.get_manager ())); + if (m_logger) + valid_bits.log ("valid_bits", *m_logger); - got_valid_bits = true; - maybe_add_gap (w, invalid_before_bits, valid_bits); + 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); - } + std::unique_ptr label; + if (m_op.m_dir == DIR_READ) + label = num_valid_bits.maybe_get_formatted_str (m_sm, + m_op.m_model, + _("size: %wi bit"), + _("size: %wi bits"), + _("size: %wi byte"), + _("size: %wi bytes"), + _("size: %qs bits"), + _("size: %qs bytes")); + else + label + = num_valid_bits.maybe_get_formatted_str (m_sm, + m_op.m_model, + _("capacity: %wi bit"), + _("capacity: %wi bits"), + _("capacity: %wi byte"), + _("capacity: %wi bytes"), + _("capacity: %qs bits"), + _("capacity: %qs bytes")); + if (label) + w->add_range (m_btm.get_table_x_for_range (m_op.get_valid_bits ()), + std::move (*label), + m_valid_style_id); access_range invalid_after_bits; if (m_op.maybe_get_invalid_after_bits (&invalid_after_bits)) @@ -2444,32 +2460,31 @@ private: 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); - } + bit_size_expr num_after_bits + (invalid_after_bits.get_size (m_op.get_manager ())); + std::unique_ptr label; + if (m_op.m_dir == DIR_READ) + label = num_after_bits.maybe_get_formatted_str + (m_sm, m_op.m_model, + _("over-read of %wi bit"), + _("over-read of %wi bits"), + _("over-read of %wi byte"), + _("over-read of %wi bytes"), + _("over-read of %qs bits"), + _("over-read of %qs bytes")); + else + label = num_after_bits.maybe_get_formatted_str + (m_sm, m_op.m_model, + _("overflow of %wi bit"), + _("overflow of %wi bits"), + _("overflow of %wi byte"), + _("overflow of %wi bytes"), + _("overflow of %qs bits"), + _("overflow of %qs bytes")); + if (label) + w->add_range (m_btm.get_table_x_for_range (invalid_after_bits), + make_warning_string (std::move (*label)), + m_invalid_style_id); } else { @@ -2673,6 +2688,101 @@ access_diagram::access_diagram (const access_operation &op, { } +#if CHECKING_P + +namespace selftest { + +/* Implementation detail of ASSERT_EQ_TYPELESS_INTEGER. */ + +static void +assert_eq_typeless_integer (const location &loc, + const svalue *sval, + int expected_int_val) +{ + ASSERT_NE_AT (loc, sval, nullptr); + ASSERT_EQ_AT (loc, sval->get_kind (), SK_CONSTANT); + ASSERT_EQ_AT (loc, + wi::to_offset (sval->maybe_get_constant ()), + expected_int_val); + ASSERT_EQ_AT (loc, sval->get_type (), NULL_TREE); +} + +/* Assert that SVAL is a constant_svalue equal to EXPECTED_INT_VAL, + with NULL_TREE as its type. */ + +#define ASSERT_EQ_TYPELESS_INTEGER(SVAL, EXPECTED_INT_VAL) \ + SELFTEST_BEGIN_STMT \ + assert_eq_typeless_integer ((SELFTEST_LOCATION), \ + (SVAL), \ + (EXPECTED_INT_VAL)); \ + SELFTEST_END_STMT + + +/* Various tests of bit_size_expr::maybe_get_as_bytes. */ + +static void +test_bit_size_expr_to_bytes () +{ + region_model_manager mgr; + + /* 40 bits: should be 5 bytes. */ + { + bit_size_expr num_bits (*mgr.get_or_create_int_cst (NULL_TREE, 40)); + const svalue *as_bytes = num_bits.maybe_get_as_bytes (mgr); + ASSERT_EQ_TYPELESS_INTEGER (as_bytes, 5); + } + + /* 41 bits: should not convert to bytes. */ + { + bit_size_expr num_bits (*mgr.get_or_create_int_cst (NULL_TREE, 41)); + const svalue *as_bytes = num_bits.maybe_get_as_bytes (mgr); + ASSERT_EQ (as_bytes, nullptr); + } + + tree n = build_global_decl ("n", size_type_node); + + const svalue *init_n + = mgr.get_or_create_initial_value (mgr.get_region_for_global (n)); + + const svalue *n_times_8 + = mgr.get_or_create_binop (NULL_TREE, MULT_EXPR, + init_n, + mgr.get_or_create_int_cst (NULL_TREE, 8)); + + /* (n * 8) bits should be n bytes */ + { + bit_size_expr num_bits (*n_times_8); + const svalue *as_bytes = num_bits.maybe_get_as_bytes (mgr); + ASSERT_EQ (as_bytes, mgr.get_or_create_cast (NULL_TREE, init_n)); + } + + /* (n * 8) + 16 bits should be n + 2 bytes */ + { + bit_size_expr num_bits + (*mgr.get_or_create_binop (NULL_TREE, PLUS_EXPR, + n_times_8, + mgr.get_or_create_int_cst (NULL_TREE, 16))); + const svalue *as_bytes = num_bits.maybe_get_as_bytes (mgr); + ASSERT_EQ (as_bytes->get_kind (), SK_BINOP); + const binop_svalue *binop = as_bytes->dyn_cast_binop_svalue (); + ASSERT_EQ (binop->get_op (), PLUS_EXPR); + ASSERT_EQ (binop->get_arg0 (), mgr.get_or_create_cast (NULL_TREE, init_n)); + ASSERT_EQ_TYPELESS_INTEGER (binop->get_arg1 (), 2); + } +} + +/* Run all of the selftests within this file. */ + +void +analyzer_access_diagram_cc_tests () +{ + test_bit_size_expr_to_bytes (); +} + +} // namespace selftest + +#endif /* CHECKING_P */ + } // namespace ana #endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/access-diagram.h b/gcc/analyzer/access-diagram.h index 435690b4772..a54d1e6cf52 100644 --- a/gcc/analyzer/access-diagram.h +++ b/gcc/analyzer/access-diagram.h @@ -32,23 +32,24 @@ namespace ana { class bit_size_expr { public: - bit_size_expr () : m_num_bits (NULL) {} - bit_size_expr (tree num_bits) : m_num_bits (num_bits) {} + bit_size_expr (const svalue &num_bits) : m_num_bits (num_bits) {} - text_art::styled_string - 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; - void print (pretty_printer *pp) const; + std::unique_ptr + maybe_get_formatted_str (text_art::style_manager &sm, + const region_model &model, + const char *concrete_single_bit_fmt, + const char *concrete_plural_bits_fmt, + const char *concrete_single_byte_fmt, + const char *concrete_plural_bytes_fmt, + const char *symbolic_bits_fmt, + const char *symbolic_bytes_fmt) const; + bool maybe_print_for_user (pretty_printer *pp, + const region_model &model) const; - tree maybe_get_as_bytes () const; + const svalue *maybe_get_as_bytes (region_model_manager &mgr) const; private: - tree m_num_bits; + const svalue &m_num_bits; }; /* A range of bits within a base region, where each endpoint @@ -60,8 +61,9 @@ struct access_range : m_start (), m_next () { } - access_range (region_offset start, region_offset next) - : m_start (start), m_next (next) + access_range (region_offset start, region_offset next, + region_model_manager &mgr) + : m_start (strip_types (start, mgr)), m_next (strip_types (next, mgr)) {} access_range (const region *base_region, const bit_range &bits); access_range (const region *base_region, const byte_range &bytes); @@ -74,7 +76,7 @@ struct access_range bool empty_p () const; - bool get_size (const region_model &model, bit_size_expr *out) const; + bit_size_expr get_size (region_model_manager *mgr) const; bool get_size_in_bits (bit_size_t *out) const { diff --git a/gcc/analyzer/analyzer-selftests.cc b/gcc/analyzer/analyzer-selftests.cc index 20fdba768ad..4500e3a78f3 100644 --- a/gcc/analyzer/analyzer-selftests.cc +++ b/gcc/analyzer/analyzer-selftests.cc @@ -51,6 +51,7 @@ void run_analyzer_selftests () { #if ENABLE_ANALYZER + analyzer_access_diagram_cc_tests (); analyzer_constraint_manager_cc_tests (); analyzer_function_set_cc_tests (); analyzer_program_point_cc_tests (); diff --git a/gcc/analyzer/analyzer-selftests.h b/gcc/analyzer/analyzer-selftests.h index 20a2bc4ea39..9cfe37a1b1d 100644 --- a/gcc/analyzer/analyzer-selftests.h +++ b/gcc/analyzer/analyzer-selftests.h @@ -33,7 +33,7 @@ extern void run_analyzer_selftests (); /* Declarations for specific families of tests (by source file), in alphabetical order. */ -extern void analyzer_checker_script_cc_tests (); +extern void analyzer_access_diagram_cc_tests (); extern void analyzer_constraint_manager_cc_tests (); extern void analyzer_function_set_cc_tests (); extern void analyzer_program_point_cc_tests (); diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h index 1d792ed23ec..d43812e3963 100644 --- a/gcc/analyzer/analyzer.h +++ b/gcc/analyzer/analyzer.h @@ -140,6 +140,7 @@ class known_function; extern void dump_tree (pretty_printer *pp, tree t); extern void dump_quoted_tree (pretty_printer *pp, tree t); extern void print_quoted_type (pretty_printer *pp, tree t); +extern void print_expr_for_user (pretty_printer *pp, tree t); extern int readability_comparator (const void *p1, const void *p2); extern int tree_cmp (const void *p1, const void *p2); extern tree fixup_tree_for_diagnostic (tree); @@ -236,7 +237,7 @@ public: return m_sym_offset; } - tree calc_symbolic_bit_offset (const region_model &model) const; + const svalue &calc_symbolic_bit_offset (region_model_manager *mgr) const; const svalue *calc_symbolic_byte_offset (region_model_manager *mgr) const; bool operator== (const region_offset &other) const @@ -436,6 +437,14 @@ get_string_cst_size (const_tree string_cst); extern tree get_ssa_default_def (const function &fun, tree var); +extern const svalue * +strip_types (const svalue *sval, region_model_manager &mgr); + +extern region_offset +strip_types (const region_offset &offset, region_model_manager &mgr); + +extern tree remove_ssa_names (tree expr); + } // namespace ana extern bool is_special_named_call_p (const gcall *call, const char *funcname, diff --git a/gcc/analyzer/bounds-checking.cc b/gcc/analyzer/bounds-checking.cc index f46058ea478..f8644addbfd 100644 --- a/gcc/analyzer/bounds-checking.cc +++ b/gcc/analyzer/bounds-checking.cc @@ -1154,6 +1154,149 @@ public: enum access_direction get_dir () const final override { return DIR_READ; } }; +const svalue * +strip_types (const svalue *sval, + region_model_manager &mgr) +{ + switch (sval->get_kind ()) + { + default: + gcc_unreachable (); + case SK_REGION: + { + const region_svalue *region_sval = (const region_svalue *)sval; + return mgr.get_ptr_svalue (NULL_TREE, region_sval->get_pointee ()); + } + case SK_CONSTANT: + return sval; + case SK_UNKNOWN: + return mgr.get_or_create_unknown_svalue (NULL_TREE); + case SK_POISONED: + { + const poisoned_svalue *poisoned_sval = (const poisoned_svalue *)sval; + return mgr.get_or_create_poisoned_svalue + (poisoned_sval->get_poison_kind (), + NULL_TREE); + } + case SK_SETJMP: + return sval; + case SK_INITIAL: + return sval; + case SK_UNARYOP: + { + const unaryop_svalue *unaryop_sval = (const unaryop_svalue *)sval; + const enum tree_code op = unaryop_sval->get_op (); + if (op == VIEW_CONVERT_EXPR || op == NOP_EXPR) + return strip_types (unaryop_sval->get_arg (), mgr); + return mgr.get_or_create_unaryop + (NULL_TREE, + op, + strip_types (unaryop_sval->get_arg (), mgr)); + } + case SK_BINOP: + { + const binop_svalue *binop_sval = (const binop_svalue *)sval; + const enum tree_code op = binop_sval->get_op (); + return mgr.get_or_create_binop + (NULL_TREE, + op, + strip_types (binop_sval->get_arg0 (), mgr), + strip_types (binop_sval->get_arg1 (), mgr)); + } + case SK_SUB: + { + const sub_svalue *sub_sval = (const sub_svalue *)sval; + return mgr.get_or_create_sub_svalue + (NULL_TREE, + strip_types (sub_sval->get_parent (), mgr), + sub_sval->get_subregion ()); + } + case SK_REPEATED: + { + const repeated_svalue *repeated_sval = (const repeated_svalue *)sval; + return mgr.get_or_create_repeated_svalue + (NULL_TREE, + strip_types (repeated_sval->get_outer_size (), mgr), + strip_types (repeated_sval->get_inner_svalue (), mgr)); + } + case SK_BITS_WITHIN: + { + const bits_within_svalue *bits_within_sval + = (const bits_within_svalue *)sval; + return mgr.get_or_create_bits_within + (NULL_TREE, + bits_within_sval->get_bits (), + strip_types (bits_within_sval->get_inner_svalue (), mgr)); + } + case SK_UNMERGEABLE: + { + const unmergeable_svalue *unmergeable_sval + = (const unmergeable_svalue *)sval; + return mgr.get_or_create_unmergeable + (strip_types (unmergeable_sval->get_arg (), mgr)); + } + case SK_PLACEHOLDER: + return sval; + case SK_WIDENING: + { + const widening_svalue *widening_sval = (const widening_svalue *)sval; + return mgr.get_or_create_widening_svalue + (NULL_TREE, + widening_sval->get_point (), + strip_types (widening_sval->get_base_svalue (), mgr), + strip_types (widening_sval->get_iter_svalue (), mgr)); + } + case SK_COMPOUND: + { + const compound_svalue *compound_sval = (const compound_svalue *)sval; + binding_map typeless_map; + for (auto iter : compound_sval->get_map ()) + { + const binding_key *key = iter.first; + const svalue *bound_sval = iter.second; + typeless_map.put (key, strip_types (bound_sval, mgr)); + } + return mgr.get_or_create_compound_svalue (NULL_TREE, typeless_map); + } + case SK_CONJURED: + return sval; + case SK_ASM_OUTPUT: + { + const asm_output_svalue *asm_output_sval + = (const asm_output_svalue *)sval; + auto_vec typeless_inputs + (asm_output_sval->get_num_inputs ()); + for (unsigned idx = 0; idx < asm_output_sval->get_num_inputs (); idx++) + typeless_inputs.quick_push + (strip_types (asm_output_sval->get_input (idx), + mgr)); + return mgr.get_or_create_asm_output_svalue + (NULL_TREE, + asm_output_sval->get_asm_string (), + asm_output_sval->get_output_idx (), + asm_output_sval->get_num_outputs (), + typeless_inputs); + } + case SK_CONST_FN_RESULT: + { + const const_fn_result_svalue *const_fn_result_sval + = (const const_fn_result_svalue *)sval; + auto_vec typeless_inputs + (const_fn_result_sval->get_num_inputs ()); + for (unsigned idx = 0; + idx < const_fn_result_sval->get_num_inputs (); + idx++) + typeless_inputs.quick_push + (strip_types (const_fn_result_sval->get_input (idx), + mgr)); + return mgr.get_or_create_const_fn_result_svalue + (NULL_TREE, + const_fn_result_sval->get_fndecl (), + typeless_inputs); + } + } +} + /* Check whether an access is past the end of the BASE_REG. Return TRUE if the access was valid, FALSE otherwise. */ @@ -1169,9 +1312,12 @@ region_model::check_symbolic_bounds (const region *base_reg, gcc_assert (ctxt); const svalue *next_byte - = m_mgr->get_or_create_binop (num_bytes_sval->get_type (), PLUS_EXPR, + = m_mgr->get_or_create_binop (NULL_TREE, PLUS_EXPR, sym_byte_offset, num_bytes_sval); + next_byte = strip_types (next_byte, *m_mgr); + capacity = strip_types (capacity, *m_mgr); + if (eval_condition (next_byte, GT_EXPR, capacity).is_true ()) { tree diag_arg = get_representative_tree (base_reg); diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc index 8530f49e74a..4feb349c914 100644 --- a/gcc/analyzer/region-model-manager.cc +++ b/gcc/analyzer/region-model-manager.cc @@ -222,21 +222,29 @@ region_model_manager::reject_if_too_complex (svalue *sval) of trees */ const svalue * -region_model_manager::get_or_create_constant_svalue (tree cst_expr) +region_model_manager::get_or_create_constant_svalue (tree type, tree cst_expr) { gcc_assert (cst_expr); gcc_assert (CONSTANT_CLASS_P (cst_expr)); + gcc_assert (type == TREE_TYPE (cst_expr) || type == NULL_TREE); - constant_svalue **slot = m_constants_map.get (cst_expr); + constant_svalue::key_t key (type, cst_expr); + constant_svalue **slot = m_constants_map.get (key); if (slot) return *slot; constant_svalue *cst_sval - = new constant_svalue (alloc_symbol_id (), cst_expr); + = new constant_svalue (alloc_symbol_id (), type, cst_expr); RETURN_UNKNOWN_IF_TOO_COMPLEX (cst_sval); - m_constants_map.put (cst_expr, cst_sval); + m_constants_map.put (key, cst_sval); return cst_sval; } +const svalue * +region_model_manager::get_or_create_constant_svalue (tree cst_expr) +{ + return get_or_create_constant_svalue (TREE_TYPE (cst_expr), cst_expr); +} + /* Return the svalue * for a constant_svalue for the INTEGER_CST for VAL of type TYPE, creating it if necessary. */ @@ -244,10 +252,13 @@ const svalue * region_model_manager::get_or_create_int_cst (tree type, const poly_wide_int_ref &cst) { - gcc_assert (type); - gcc_assert (INTEGRAL_TYPE_P (type) || POINTER_TYPE_P (type)); - tree tree_cst = wide_int_to_tree (type, cst); - return get_or_create_constant_svalue (tree_cst); + tree effective_type = type; + if (!type) + effective_type = ptrdiff_type_node; + gcc_assert (INTEGRAL_TYPE_P (effective_type) + || POINTER_TYPE_P (effective_type)); + tree tree_cst = wide_int_to_tree (effective_type, cst); + return get_or_create_constant_svalue (type, tree_cst); } /* Return the svalue * for the constant_svalue for the NULL pointer @@ -432,6 +443,9 @@ region_model_manager::maybe_fold_unaryop (tree type, enum tree_code op, case VIEW_CONVERT_EXPR: case NOP_EXPR: { + if (!type) + return nullptr; + /* Handle redundant casts. */ if (arg->get_type () && useless_type_conversion_p (arg->get_type (), type)) @@ -442,13 +456,13 @@ region_model_manager::maybe_fold_unaryop (tree type, enum tree_code op, unless INNER_TYPE is narrower than TYPE. */ if (const svalue *innermost_arg = arg->maybe_undo_cast ()) { - tree inner_type = arg->get_type (); - if (TYPE_SIZE (type) - && TYPE_SIZE (inner_type) - && (fold_binary (LE_EXPR, boolean_type_node, - TYPE_SIZE (type), TYPE_SIZE (inner_type)) - == boolean_true_node)) - return maybe_fold_unaryop (type, op, innermost_arg); + if (tree inner_type = arg->get_type ()) + if (TYPE_SIZE (type) + && TYPE_SIZE (inner_type) + && (fold_binary (LE_EXPR, boolean_type_node, + TYPE_SIZE (type), TYPE_SIZE (inner_type)) + == boolean_true_node)) + return maybe_fold_unaryop (type, op, innermost_arg); } /* Avoid creating symbolic regions for pointer casts by simplifying (T*)(®ION) to ((T*)®ION). */ @@ -546,7 +560,8 @@ region_model_manager::get_or_create_unaryop (tree type, enum tree_code op, static enum tree_code get_code_for_cast (tree dst_type, tree src_type) { - gcc_assert (dst_type); + if (!dst_type) + return NOP_EXPR; if (!src_type) return NOP_EXPR; @@ -567,17 +582,16 @@ get_code_for_cast (tree dst_type, tree src_type) const svalue * region_model_manager::get_or_create_cast (tree type, const svalue *arg) { - gcc_assert (type); - /* No-op if the types are the same. */ if (type == arg->get_type ()) return arg; /* Don't attempt to handle casts involving vector types for now. */ - if (VECTOR_TYPE_P (type) - || (arg->get_type () - && VECTOR_TYPE_P (arg->get_type ()))) - return get_or_create_unknown_svalue (type); + if (type) + if (VECTOR_TYPE_P (type) + || (arg->get_type () + && VECTOR_TYPE_P (arg->get_type ()))) + return get_or_create_unknown_svalue (type); enum tree_code op = get_code_for_cast (type, arg->get_type ()); return get_or_create_unaryop (type, op, arg); @@ -650,9 +664,17 @@ region_model_manager::maybe_fold_binop (tree type, enum tree_code op, /* (CST OP CST). */ if (cst0 && cst1) { - if (tree result = fold_binary (op, type, cst0, cst1)) - if (CONSTANT_CLASS_P (result)) - return get_or_create_constant_svalue (result); + if (type) + { + if (tree result = fold_binary (op, type, cst0, cst1)) + if (CONSTANT_CLASS_P (result)) + return get_or_create_constant_svalue (result); + } + else + { + if (tree result = int_const_binop (op, cst0, cst1, -1)) + return get_or_create_constant_svalue (NULL_TREE, result); + } } if ((type && FLOAT_TYPE_P (type)) @@ -685,20 +707,21 @@ region_model_manager::maybe_fold_binop (tree type, enum tree_code op, break; case MULT_EXPR: /* (VAL * 0). */ - if (cst1 && zerop (cst1) && INTEGRAL_TYPE_P (type)) - return get_or_create_constant_svalue (build_int_cst (type, 0)); + if (cst1 + && zerop (cst1) + && (type == NULL_TREE || INTEGRAL_TYPE_P (type))) + return get_or_create_int_cst (type, 0); /* (VAL * 1) -> VAL. */ if (cst1 && integer_onep (cst1)) - /* TODO: we ought to have a cast to TYPE here, but doing so introduces - regressions; see PR analyzer/110902. */ - return arg0; + return get_or_create_cast (type, arg0); break; case BIT_AND_EXPR: if (cst1) { - if (zerop (cst1) && INTEGRAL_TYPE_P (type)) + if (zerop (cst1) + && (type == NULL_TREE || INTEGRAL_TYPE_P (type))) /* "(ARG0 & 0)" -> "0". */ - return get_or_create_constant_svalue (build_int_cst (type, 0)); + return get_or_create_int_cst (type, 0); if (const compound_svalue *compound_sval = arg0->dyn_cast_compound_svalue ()) @@ -810,6 +833,80 @@ region_model_manager::maybe_fold_binop (tree type, enum tree_code op, binop->get_arg1 (), arg1)); } + + /* Typeless operations, assumed to be effectively arbitrary sized + integers following normal arithmetic rules. */ + if (!type) + switch (op) + { + default: + break; + case MINUS_EXPR: + { + /* (X - X) -> 0. */ + if (arg0 == arg1) + return get_or_create_int_cst (type, 0); + + /* (X + A) - (A + B) -> (A - B). */ + if (const binop_svalue *binop0 = arg0->dyn_cast_binop_svalue ()) + if (const binop_svalue *binop1 = arg1->dyn_cast_binop_svalue ()) + if (binop0->get_op () == PLUS_EXPR + && binop1->get_op () == PLUS_EXPR + && binop0->get_arg0 () == binop1->get_arg0 ()) + return get_or_create_binop (NULL_TREE, op, + binop0->get_arg1 (), + binop1->get_arg1 ()); + } + break; + + case EXACT_DIV_EXPR: + { + if (const unaryop_svalue *unaryop0 = arg0->dyn_cast_unaryop_svalue ()) + { + if (unaryop0->get_op () == NOP_EXPR) + if (const svalue *sval = maybe_fold_binop (NULL_TREE, op, + unaryop0->get_arg (), + arg1)) + return sval; + } + if (const binop_svalue *binop0 = arg0->dyn_cast_binop_svalue ()) + { + switch (binop0->get_op ()) + { + default: + break; + + case PLUS_EXPR: + case MINUS_EXPR: + /* (A op B) / C -> (A / C) op (B / C). */ + { + if (const svalue *op_on_a + = maybe_fold_binop (NULL_TREE, op, + binop0->get_arg0 (), arg1)) + if (const svalue *op_on_b + = maybe_fold_binop (NULL_TREE, op, + binop0->get_arg1 (), arg1)) + return get_or_create_binop (NULL_TREE, + binop0->get_op (), + op_on_a, op_on_b); + } + break; + + case MULT_EXPR: + /* (A * B) / C -> A * (B / C) if C is a divisor of B. + In particular, this should also handle the case + (A * B) / B -> A. */ + if (const svalue *b_div_c + = maybe_fold_binop (NULL_TREE, op, + binop0->get_arg1 (), arg1)) + return get_or_create_binop (NULL_TREE, binop0->get_op (), + binop0->get_arg0 (), b_div_c); + } + } + } + break; + } + /* etc. */ return NULL; @@ -1391,7 +1488,6 @@ get_or_create_const_fn_result_svalue (tree type, tree fndecl, const vec &inputs) { - gcc_assert (type); gcc_assert (fndecl); gcc_assert (DECL_P (fndecl)); gcc_assert (TREE_READONLY (fndecl)); diff --git a/gcc/analyzer/region-model-manager.h b/gcc/analyzer/region-model-manager.h index 7d1208c70a7..857bdf94c3a 100644 --- a/gcc/analyzer/region-model-manager.h +++ b/gcc/analyzer/region-model-manager.h @@ -44,6 +44,7 @@ public: } /* svalue consolidation. */ + const svalue *get_or_create_constant_svalue (tree type, tree cst_expr); const svalue *get_or_create_constant_svalue (tree cst_expr); const svalue *get_or_create_int_cst (tree type, const poly_wide_int_ref &cst); const svalue *get_or_create_null_ptr (tree pointer_type); @@ -170,14 +171,14 @@ public: void dump_untracked_regions () const; + const svalue *maybe_fold_binop (tree type, enum tree_code op, + const svalue *arg0, const svalue *arg1); private: bool too_complex_p (const complexity &c) const; bool reject_if_too_complex (svalue *sval); const svalue *maybe_fold_unaryop (tree type, enum tree_code op, const svalue *arg); - const svalue *maybe_fold_binop (tree type, enum tree_code op, - const svalue *arg0, const svalue *arg1); const svalue *maybe_fold_sub_svalue (tree type, const svalue *parent_svalue, const region *subregion); @@ -204,7 +205,7 @@ private: heap_region m_heap_region; /* svalue consolidation. */ - typedef hash_map constants_map_t; + typedef hash_map constants_map_t; constants_map_t m_constants_map; typedef hash_map unknowns_map_t; diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 33a4584841e..f079d1fb37e 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -120,11 +120,29 @@ dump_quoted_tree (pretty_printer *pp, tree t) void print_quoted_type (pretty_printer *pp, tree t) { + if (!t) + return; pp_begin_quote (pp, pp_show_color (pp)); dump_generic_node (pp, t, 0, TDF_SLIM, 0); pp_end_quote (pp, pp_show_color (pp)); } +/* Print EXPR to PP, without quotes. + For use within svalue::maybe_print_for_user + and region::maybe_print_for_user. */ + +void +print_expr_for_user (pretty_printer *pp, tree expr) +{ + /* Workaround for C++'s lang_hooks.decl_printable_name, + which unhelpfully (for us) prefixes the decl with its + type. */ + if (DECL_P (expr)) + dump_generic_node (pp, expr, 0, TDF_SLIM, 0); + else + pp_printf (pp, "%E", expr); +} + /* class region_to_value_map. */ /* Assignment operator for region_to_value_map. */ @@ -8973,7 +8991,10 @@ test_array_2 () const region *arr_i_reg = model.get_lvalue (arr_i, NULL); region_offset offset = arr_i_reg->get_offset (&mgr); ASSERT_EQ (offset.get_base_region (), model.get_lvalue (arr, NULL)); - ASSERT_EQ (offset.get_symbolic_byte_offset ()->get_kind (), SK_BINOP); + const svalue *offset_sval = offset.get_symbolic_byte_offset (); + if (const svalue *cast = offset_sval->maybe_undo_cast ()) + offset_sval = cast; + ASSERT_EQ (offset_sval->get_kind (), SK_BINOP); } /* "arr[i] = i;" - this should remove the earlier bindings. */ diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc index d45706141fb..705816b6245 100644 --- a/gcc/analyzer/region.cc +++ b/gcc/analyzer/region.cc @@ -78,23 +78,18 @@ region_offset::make_byte_offset (const region *base_region, } } -tree -region_offset::calc_symbolic_bit_offset (const region_model &model) const +const svalue & +region_offset::calc_symbolic_bit_offset (region_model_manager *mgr) const { if (symbolic_p ()) { - tree num_bytes_expr = model.get_representative_tree (m_sym_offset); - if (!num_bytes_expr) - return NULL_TREE; - tree bytes_to_bits_scale = build_int_cst (size_type_node, BITS_PER_UNIT); - return fold_build2 (MULT_EXPR, size_type_node, - num_bytes_expr, bytes_to_bits_scale); + const svalue *bits_per_byte + = mgr->get_or_create_int_cst (NULL_TREE, BITS_PER_UNIT); + return *mgr->get_or_create_binop (NULL_TREE, MULT_EXPR, + m_sym_offset, bits_per_byte); } else - { - tree cst = wide_int_to_tree (size_type_node, m_offset); - return cst; - } + return *mgr->get_or_create_int_cst (size_type_node, m_offset); } const svalue * @@ -388,6 +383,18 @@ operator>= (const region_offset &a, const region_offset &b) return b <= a; } +region_offset +strip_types (const region_offset &offset, region_model_manager &mgr) +{ + if (offset.symbolic_p ()) + return region_offset::make_symbolic + (offset.get_base_region (), + strip_types (offset.get_symbolic_byte_offset (), + mgr)); + else + return offset; +} + /* class region and its various subclasses. */ /* class region. */ @@ -923,7 +930,7 @@ region::calc_offset (region_model_manager *mgr) const const svalue *sval = iter_region->get_relative_symbolic_offset (mgr); accum_byte_sval - = mgr->get_or_create_binop (sval->get_type (), PLUS_EXPR, + = mgr->get_or_create_binop (ptrdiff_type_node, PLUS_EXPR, accum_byte_sval, sval); iter_region = iter_region->get_parent_region (); } @@ -941,7 +948,7 @@ region::calc_offset (region_model_manager *mgr) const accumulated bits to a svalue in bytes and revisit the iter_region collecting the symbolic value. */ byte_offset_t byte_offset = accum_bit_offset / BITS_PER_UNIT; - tree offset_tree = wide_int_to_tree (integer_type_node, + tree offset_tree = wide_int_to_tree (ptrdiff_type_node, byte_offset); accum_byte_sval = mgr->get_or_create_constant_svalue (offset_tree); @@ -1043,6 +1050,28 @@ region::to_json () const return reg_js; } +bool +region::maybe_print_for_user (pretty_printer *pp, + const region_model &) const +{ + switch (get_kind ()) + { + default: + break; + case RK_DECL: + { + const decl_region *reg = (const decl_region *)this; + tree decl = reg->get_decl (); + if (TREE_CODE (decl) == SSA_NAME) + decl = SSA_NAME_VAR (decl); + print_expr_for_user (pp, decl); + return true; + } + } + + return false; +} + /* Generate a description of this region. */ DEBUG_FUNCTION label_text @@ -1909,7 +1938,7 @@ element_region::get_relative_symbolic_offset (region_model_manager *mgr) const hwi_byte_size); const svalue *byte_size_sval = mgr->get_or_create_constant_svalue (byte_size_tree); - return mgr->get_or_create_binop (ptrdiff_type_node, MULT_EXPR, + return mgr->get_or_create_binop (NULL_TREE, MULT_EXPR, m_index, byte_size_sval); } return mgr->get_or_create_unknown_svalue (ptrdiff_type_node); @@ -1955,8 +1984,8 @@ const svalue * offset_region::get_bit_offset (region_model_manager *mgr) const { const svalue *bits_per_byte_sval - = mgr->get_or_create_int_cst (size_type_node, BITS_PER_UNIT); - return mgr->get_or_create_binop (size_type_node, MULT_EXPR, + = mgr->get_or_create_int_cst (NULL_TREE, BITS_PER_UNIT); + return mgr->get_or_create_binop (NULL_TREE, MULT_EXPR, m_byte_offset, bits_per_byte_sval); } @@ -2056,8 +2085,8 @@ const svalue * sized_region::get_bit_size_sval (region_model_manager *mgr) const { const svalue *bits_per_byte_sval - = mgr->get_or_create_int_cst (size_type_node, BITS_PER_UNIT); - return mgr->get_or_create_binop (size_type_node, MULT_EXPR, + = mgr->get_or_create_int_cst (NULL_TREE, BITS_PER_UNIT); + return mgr->get_or_create_binop (NULL_TREE, MULT_EXPR, m_byte_size_sval, bits_per_byte_sval); } diff --git a/gcc/analyzer/region.h b/gcc/analyzer/region.h index 73f35f55ba7..0c190c2f0a2 100644 --- a/gcc/analyzer/region.h +++ b/gcc/analyzer/region.h @@ -176,6 +176,9 @@ public: json::value *to_json () const; + bool maybe_print_for_user (pretty_printer *pp, + const region_model &model) const; + bool non_null_p () const; static int cmp_ptr_ptr (const void *, const void *); diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index 8d8f0d4579a..e64f62d266e 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -95,6 +95,114 @@ svalue::to_json () const return sval_js; } +/* Class for optionally adding open/close paren pairs within + svalue::maybe_print_for_user. */ + +class auto_add_parens +{ +public: + auto_add_parens (pretty_printer *pp, + const svalue *outer_sval, + const svalue &inner_sval) + : m_pp (pp), + m_needs_parens (needs_parens_p (outer_sval, inner_sval)) + { + if (m_needs_parens) + pp_string (m_pp, "("); + } + ~auto_add_parens () + { + if (m_needs_parens) + pp_string (m_pp, ")"); + } + +private: + static bool needs_parens_p (const svalue *outer_sval, + const svalue &inner_sval) + { + if (!outer_sval) + return false; + if (inner_sval.get_kind () == SK_BINOP) + return true; + return false; + } + + pretty_printer *m_pp; + bool m_needs_parens; +}; + +/* Attempt to print a user-facing description of this svalue to PP, + using MODEL for extracting representative tree values if necessary. + Use OUTER_SVAL (which can be null) to determine if we need to wrap + this value in parentheses. */ + +bool +svalue::maybe_print_for_user (pretty_printer *pp, + const region_model &model, + const svalue *outer_sval) const +{ + auto_add_parens p (pp, outer_sval, *this); + + switch (get_kind ()) + { + default: + break; + case SK_CONSTANT: + { + const constant_svalue *sval = (const constant_svalue *)this; + pp_printf (pp, "%E", sval->get_constant ()); + return true; + } + case SK_INITIAL: + { + const initial_svalue *sval = (const initial_svalue *)this; + return sval->get_region ()->maybe_print_for_user (pp, model); + } + case SK_UNARYOP: + { + const unaryop_svalue *sval = (const unaryop_svalue *)this; + if (sval->get_op () == NOP_EXPR) + { + if (!sval->get_arg ()->maybe_print_for_user (pp, model, outer_sval)) + return false; + return true; + } + } + break; + case SK_BINOP: + { + const binop_svalue *sval = (const binop_svalue *)this; + switch (sval->get_op ()) + { + default: + break; + + case PLUS_EXPR: + case MINUS_EXPR: + case MULT_EXPR: + { + if (!sval->get_arg0 ()->maybe_print_for_user (pp, model, this)) + return false; + pp_printf (pp, " %s ", op_symbol_code (sval->get_op ())); + if (!sval->get_arg1 ()->maybe_print_for_user (pp, model, this)) + return false; + return true; + } + } + } + break; + } + + if (tree expr = model.get_representative_tree (this)) + { + expr = remove_ssa_names (expr); + print_expr_for_user (pp, expr); + return true; + } + + return false; +} + /* If this svalue is a constant_svalue, return the underlying tree constant. Otherwise return NULL_TREE. */ @@ -389,7 +497,9 @@ svalue::cmp_ptr (const svalue *sval1, const svalue *sval2) const constant_svalue *constant_sval2 = (const constant_svalue *)sval2; const_tree cst1 = constant_sval1->get_constant (); const_tree cst2 = constant_sval2->get_constant (); - return cmp_csts_same_type (cst1, cst2); + /* The svalues have the same type, but the underlying trees + might not (for the case where both svalues are typeless). */ + return cmp_csts_and_types (cst1, cst2); } break; case SK_UNKNOWN: @@ -850,14 +960,31 @@ constant_svalue::implicitly_live_p (const svalue_set *, return true; } +/* Given EXPR, a non-NULL expression of boolean type, convert to + a tristate based on whether this is known to be true, false, + or is not known. */ + +static tristate +tristate_from_boolean_tree_node (tree expr) +{ + gcc_assert (TREE_TYPE (expr) == boolean_type_node); + + if (expr == boolean_true_node) + return tristate (tristate::TS_TRUE); + else if (expr == boolean_false_node) + return tristate (tristate::TS_FALSE); + else + return tristate (tristate::TS_UNKNOWN); +} + /* Evaluate the condition LHS OP RHS. Subroutine of region_model::eval_condition for when we have a pair of constants. */ tristate constant_svalue::eval_condition (const constant_svalue *lhs, - enum tree_code op, - const constant_svalue *rhs) + enum tree_code op, + const constant_svalue *rhs) { tree lhs_const = lhs->get_constant (); tree rhs_const = rhs->get_constant (); @@ -865,15 +992,28 @@ constant_svalue::eval_condition (const constant_svalue *lhs, gcc_assert (CONSTANT_CLASS_P (lhs_const)); gcc_assert (CONSTANT_CLASS_P (rhs_const)); + if ((lhs->get_type () == NULL_TREE || rhs->get_type () == NULL_TREE) + && TREE_CODE (lhs_const) == INTEGER_CST + && TREE_CODE (rhs_const) == INTEGER_CST + ) + { + if (tree tree_cmp = const_binop (op, boolean_type_node, + lhs_const, rhs_const)) + { + tristate ts = tristate_from_boolean_tree_node (tree_cmp); + if (ts.is_known ()) + return ts; + } + } + /* Check for comparable types. */ if (types_compatible_p (TREE_TYPE (lhs_const), TREE_TYPE (rhs_const))) { - tree comparison + tree tree_cmp = fold_binary (op, boolean_type_node, lhs_const, rhs_const); - if (comparison == boolean_true_node) - return tristate (tristate::TS_TRUE); - if (comparison == boolean_false_node) - return tristate (tristate::TS_FALSE); + tristate ts = tristate_from_boolean_tree_node (tree_cmp); + if (ts.is_known ()) + return ts; } return tristate::TS_UNKNOWN; } diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h index 4f7171b0412..3a2b0319a19 100644 --- a/gcc/analyzer/svalue.h +++ b/gcc/analyzer/svalue.h @@ -177,6 +177,10 @@ public: const region *maybe_get_deref_base_region () const; + bool maybe_print_for_user (pretty_printer *pp, + const region_model &model, + const svalue *outer_sval = nullptr) const; + protected: svalue (complexity c, symbol::id_t id, tree type) : symbol (c, id), m_type (type) @@ -265,16 +269,49 @@ template <> struct default_hash_traits namespace ana { -/* Concrete subclass of svalue representing a specific constant value. */ +/* Concrete subclass of svalue representing a specific constant value. + The type will either be the same as that of the underlying tree constant, + or NULL_TREE indicating the constant is intended to be "typeless". */ class constant_svalue : public svalue { public: - constant_svalue (symbol::id_t id, tree cst_expr) - : svalue (complexity (1, 1), id, TREE_TYPE (cst_expr)), m_cst_expr (cst_expr) + /* A support class for uniquifying instances of region_svalue. */ + struct key_t + { + key_t (tree type, tree cst) + : m_type (type), m_cst (cst) + {} + + hashval_t hash () const + { + inchash::hash hstate; + hstate.add_ptr (m_type); + hstate.add_ptr (m_cst); + return hstate.end (); + } + + bool operator== (const key_t &other) const + { + return (m_type == other.m_type && m_cst == other.m_cst); + } + + void mark_deleted () { m_type = reinterpret_cast (1); } + void mark_empty () { m_type = reinterpret_cast (2); } + bool is_deleted () const { return m_type == reinterpret_cast (1); } + bool is_empty () const { return m_type == reinterpret_cast (2); } + + tree m_type; + tree m_cst; + }; + + constant_svalue (symbol::id_t id, tree type, tree cst_expr) + : svalue (complexity (1, 1), id, type), + m_cst_expr (cst_expr) { gcc_assert (cst_expr); gcc_assert (CONSTANT_CLASS_P (cst_expr)); + gcc_assert (type == TREE_TYPE (cst_expr) || type == NULL_TREE); } enum svalue_kind get_kind () const final override { return SK_CONSTANT; } @@ -312,6 +349,12 @@ is_a_helper ::test (const svalue *sval) return sval->get_kind () == SK_CONSTANT; } +template <> struct default_hash_traits +: public member_function_hash_traits +{ + static const bool empty_zero_p = false; +}; + namespace ana { /* Concrete subclass of svalue representing an unknowable value, the bottom diff --git a/gcc/selftest.h b/gcc/selftest.h index 49fed5bebac..3bddaf1c322 100644 --- a/gcc/selftest.h +++ b/gcc/selftest.h @@ -371,6 +371,18 @@ extern int num_passes; ::selftest::fail (SELFTEST_LOCATION, desc_); \ SELFTEST_END_STMT +/* Like ASSERT_NE, but treat LOC as the effective location of the + selftest. */ + +#define ASSERT_NE_AT(LOC, VAL1, VAL2) \ + SELFTEST_BEGIN_STMT \ + const char *desc_ = "ASSERT_NE (" #VAL1 ", " #VAL2 ")"; \ + if ((VAL1) != (VAL2)) \ + ::selftest::pass ((LOC), desc_); \ + else \ + ::selftest::fail ((LOC), desc_); \ + SELFTEST_END_STMT + /* Evaluate VAL1 and VAL2 and compare them with maybe_ne, calling ::selftest::pass if they might be non-equal, ::selftest::fail if they are known to be equal. */ diff --git a/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-const-fn.c b/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-const-fn.c new file mode 100644 index 00000000000..7cb8b5f5361 --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-const-fn.c @@ -0,0 +1,48 @@ +/* Reduced from analyzer ICE seen with git-2.39.0's pack-bitmap.c + when bounds-checking the result of __builtin_ctzll. */ + +#include +#include + +typedef uint64_t eword_t; +struct ewah_bitmap; +struct ewah_iterator +{ + /* [...] */ +}; +struct bitmap; + +void ewah_iterator_init(struct ewah_iterator *it, struct ewah_bitmap *parent); +int ewah_iterator_next(eword_t *next, struct ewah_iterator *it); +void bitmap_set(struct bitmap *self, size_t pos); + +int rebuild_bitmap(const uint32_t *reposition, + struct ewah_bitmap *source, + struct bitmap *dest) +{ + uint32_t pos = 0; + struct ewah_iterator it; + eword_t word; + + ewah_iterator_init(&it, source); + + while (ewah_iterator_next(&word, &it)) { + uint32_t offset, bit_pos; + + for (offset = 0; offset < (sizeof(eword_t) * 8); ++offset) { + if ((word >> offset) == 0) + break; + + offset += __builtin_ctzll(word >> offset); + + bit_pos = reposition[pos + offset]; + if (bit_pos > 0) + bitmap_set(dest, bit_pos - 1); + else + return -1; + } + + pos += (sizeof(eword_t) * 8); + } + return 0; +} diff --git a/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-diagram-11.c b/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-diagram-11.c index d68ac8f25ba..63ae08347aa 100644 --- a/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-diagram-11.c +++ b/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-diagram-11.c @@ -30,9 +30,12 @@ void test6 (size_t size) │ │ ╭──────────┴─────────╮ │ │ │over-read of 4 bytes│ │ │ ╰────────────────────╯ - ╭───────────┴──────────╮ ╭────────┴────────╮ - │size: 'size * 4' bytes│ │'size * 12' bytes│ - ╰──────────────────────╯ ╰─────────────────╯ + │ ╭────────────────┴───────────────╮ + │ │'(size * 16) - (size * 4)' bytes│ + │ ╰────────────────────────────────╯ + ╭───────────┴──────────╮ + │size: 'size * 4' bytes│ + ╰──────────────────────╯ { dg-end-multiline-output "" } */ @@ -55,9 +58,9 @@ void test7 (size_t size) └──────────────────────────────────────────────────┘└──────────────────┘ ├────────────────────────┬─────────────────────────┤├────────┬─────────┤ │ │ - ╭───────────────┴──────────────╮ ╭─────────┴────────╮ - │capacity: 'size * 4 + 3' bytes│ │overflow of 1 byte│ - ╰──────────────────────────────╯ ╰──────────────────╯ + ╭────────────────┴───────────────╮ ╭─────────┴────────╮ + │capacity: '(size * 4) + 3' bytes│ │overflow of 1 byte│ + ╰────────────────────────────────╯ ╰──────────────────╯ { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-diagram-pr110928.c b/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-diagram-pr110928.c new file mode 100644 index 00000000000..a0164c9d046 --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-diagram-pr110928.c @@ -0,0 +1,28 @@ +/* Verify we don't ICE generating out-of-bounds diagram. */ + +/* { dg-additional-options "-O0 -fdiagnostics-text-art-charset=unicode" } */ + +#include +#include + +uint64_t d(int32_t h) { + uint64_t j[2][6]; + int32_t k; + for (k = 1;;) { + printf("FLAG\n"); + if (h < 106 || j[k][h]) /* { dg-warning "stack-based buffer over-read" } */ + return 0; + } +} +int16_t e() { + int32_t f[5]; + for (f[2] = 3; f[2]; --f[2]) + d(0); +} + +int main() { e(); } + +/* We don't care about the exact diagram, just that we don't ICE. */ + +/* { dg-allow-blank-lines-in-output 1 } */ +/* { dg-prune-output ".*" } */ diff --git a/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-diagram-pr111305.c b/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-diagram-pr111305.c new file mode 100644 index 00000000000..9cb37a54ef9 --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-diagram-pr111305.c @@ -0,0 +1,26 @@ +/* Verify we don't ICE generating out-of-bounds diagram. */ + +/* { dg-additional-options "-O0 -fdiagnostics-text-art-charset=unicode" } */ + +#include +#include + +struct a { + uint32_t b; +}; +union c { + int8_t b; +}; + +int32_t *d( int32_t *j, int32_t k, struct a l) { + int64_t m[1]= {0}; + for (l.b = 0; l.b <= 0; l.b++) { + printf("FLAG\n"); + l.b == 12 && m[l.b]; /* { dg-bogus "stack-based buffer over-read" } */ + } +} + +/* We don't care about the exact diagram, just that we don't ICE. */ + +/* { dg-allow-blank-lines-in-output 1 } */ +/* { dg-prune-output ".*" } */ diff --git a/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-diagram-pr111441.c b/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-diagram-pr111441.c new file mode 100644 index 00000000000..df7c71405d5 --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-diagram-pr111441.c @@ -0,0 +1,19 @@ +/* Verify we don't ICE generating out-of-bounds diagram. */ + +/* { dg-additional-options "-fdiagnostics-text-art-charset=unicode" } */ + +#include "analyzer-decls.h" + +void f() { + int a[] = {3, 0, 3, 3, 0, 3, 40883}; + for (int c = 6; c; c--) { + __analyzer_describe(0, a[c]); + 90 > c || a[c]; /* { dg-bogus "stack-based buffer over-read" } */ + } +} +int main() { f(); } + +/* We don't care about the exact diagram, just that we don't ICE. */ + +/* { dg-allow-blank-lines-in-output 1 } */ +/* { dg-prune-output ".*" } */