https://gcc.gnu.org/g:573b66baa6cb8d38771b864d40a42ca2401ac5b9
commit r17-609-g573b66baa6cb8d38771b864d40a42ca2401ac5b9 Author: David Malcolm <[email protected]> Date: Tue May 19 15:04:30 2026 -0400 analyzer: fix pointer comparisons [PR125304] PR analyzer/125304 describes a false positive from -fanalyzer on a trivial use of std::string, due to the analyzer getting confused about the paths for the small-string optimization versus heap-allocated strings. The root cause is a bug in region_svalue::eval_condition which handles many kinds of pointer comparison, but which seems to have often been hidden by the optimizer. Previously, it simply compared for identity of the underlying "region" instance, returning true if identical, false otherwise. This is wrong: (a) for some cases, including the above one, different "region" instances might represent the same memory (and thus we were returning "false" when we should have returned "true") (b) for some cases, different "region" instances we might not be able to determine if they are the same address (and thus we were returning "false" when we should have returned "unknown") This patch rewrites region_svalue::eval_condition so that rather than comparing the regions by identity, it compares their region_offset values, taking into account their base regions and byte offsets within those regions. Doing so requires using store::eval_alias, and so the patch extends that to handle more cases precisely. This new implementation fixes (a) and (b) above. There are some cases where precision could be improved (where with the patch we return "unknown" when we ought to return a known bool), but fixing these would be more invasive and so are left to followup work. gcc/analyzer/ChangeLog: PR analyzer/125304 * common.h (compare_bit_offsets_p): New decl. (eval_region_offset_comparison): New decl. * region-model.cc (region_model::on_assignment): Pass *this to store::set_value to help determination of aliasing. (region_model::set_value): Likewise. (region_model::eval_condition): Likewise for region_svalue::eval_condition. * region.cc (compare_bit_offsets_p): New. (region_offset::dump_to_pp): Dump the base region, wrapping the whole thing in braces. (eval_byte_offset_comparison): New. (eval_region_offset_comparison): New. * store.cc (store::set_value): Add "model" param and pass it to eval_alias. (store::eval_alias): Add "model" param and pass to eval_alias_1. Add early return of true when checking a base region against itself. Replace final return of "unknown" with logic that compares the kinds of the two base regions, and may be able to return "false" rather than "unknown". (store::eval_alias_1): Add "model" param and pass to eval_alias. Assert that we have two different base_regions. (store::replay_call_summary_cluster): Pass model to set_value. * store.h (store::set_value): Add "model" param. (store::eval_alias): Likewise. (store::eval_alias_1): Likewise. * svalue.cc (region_svalue::eval_condition): Likewise. Reimplement in terms of eval_region_offset_comparison. * svalue.h (region_svalue::eval_condition): Add "model" param. gcc/testsuite/ChangeLog: PR analyzer/125304 * c-c++-common/analyzer/pointer-comparison-pr125304-eq.c: New test. * c-c++-common/analyzer/pointer-comparison-pr125304-ge.c: New test. * c-c++-common/analyzer/pointer-comparison-pr125304-gt.c: New test. * c-c++-common/analyzer/pointer-comparison-pr125304-le.c: New test. * c-c++-common/analyzer/pointer-comparison-pr125304-lt.c: New test. * g++.dg/analyzer/pointer-casts-pr125304.C: New test. Signed-off-by: David Malcolm <[email protected]> Diff: --- gcc/analyzer/common.h | 11 ++ gcc/analyzer/region-model.cc | 9 +- gcc/analyzer/region.cc | 131 ++++++++++++- gcc/analyzer/store.cc | 208 +++++++++++++++++++-- gcc/analyzer/store.h | 9 +- gcc/analyzer/svalue.cc | 41 +--- gcc/analyzer/svalue.h | 3 +- .../analyzer/pointer-comparison-pr125304-eq.c | 179 ++++++++++++++++++ .../analyzer/pointer-comparison-pr125304-ge.c | 73 ++++++++ .../analyzer/pointer-comparison-pr125304-gt.c | 74 ++++++++ .../analyzer/pointer-comparison-pr125304-le.c | 73 ++++++++ .../analyzer/pointer-comparison-pr125304-lt.c | 74 ++++++++ .../g++.dg/analyzer/pointer-casts-pr125304.C | 22 +++ 13 files changed, 850 insertions(+), 57 deletions(-) diff --git a/gcc/analyzer/common.h b/gcc/analyzer/common.h index 81db5997f2ed..8ce1475bc5eb 100644 --- a/gcc/analyzer/common.h +++ b/gcc/analyzer/common.h @@ -210,6 +210,11 @@ extern bool int_size_in_bits (const_tree type, bit_size_t *out); extern tree get_field_at_bit_offset (tree record_type, bit_offset_t bit_offset); +extern bool +compare_bit_offsets_p (bit_offset_t a, + enum tree_code op, + bit_offset_t b); + /* The location of a region expressesd as an offset relative to a base region. */ @@ -291,6 +296,12 @@ extern bool operator<= (const region_offset &, const region_offset &); extern bool operator> (const region_offset &, const region_offset &); extern bool operator>= (const region_offset &, const region_offset &); +extern tristate +eval_region_offset_comparison (region_offset lhs_offset, + enum tree_code op, + region_offset rhs_offset, + const region_model &model); + extern location_t get_stmt_location (const gimple *stmt, function *fun); extern bool compat_types_p (tree src_type, tree dst_type); diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 9555f72c3074..5e1a895b1504 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1680,7 +1680,8 @@ region_model::on_assignment (const gassign *assign, region_model_context *ctxt) /* e.g. "struct s2 x = {{'A', 'B', 'C', 'D'}};". */ const svalue *rhs_sval = get_rvalue (rhs1, ctxt); m_store.set_value (m_mgr->get_store_manager(), lhs_reg, rhs_sval, - ctxt ? ctxt->get_uncertainty () : nullptr); + ctxt ? ctxt->get_uncertainty () : nullptr, + *this); } break; } @@ -4012,7 +4013,8 @@ region_model::set_value (const region *lhs_reg, const svalue *rhs_sval, check_region_for_write (lhs_reg, rhs_sval, ctxt); m_store.set_value (m_mgr->get_store_manager(), lhs_reg, rhs_sval, - ctxt ? ctxt->get_uncertainty () : nullptr); + ctxt ? ctxt->get_uncertainty () : nullptr, + *this); } /* Set the value of the region given by LHS to the value given by RHS. */ @@ -4985,7 +4987,8 @@ region_model::eval_condition (const svalue *lhs, if (const region_svalue *lhs_ptr = lhs->dyn_cast_region_svalue ()) if (const region_svalue *rhs_ptr = rhs->dyn_cast_region_svalue ()) { - tristate res = region_svalue::eval_condition (lhs_ptr, op, rhs_ptr); + tristate res = region_svalue::eval_condition (lhs_ptr, op, rhs_ptr, + *this); if (res.is_known ()) return res; /* Otherwise, only known through constraints. */ diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc index b03c79ffa72c..61c82dacec23 100644 --- a/gcc/analyzer/region.cc +++ b/gcc/analyzer/region.cc @@ -44,6 +44,24 @@ along with GCC; see the file COPYING3. If not see namespace ana { +bool +compare_bit_offsets_p (bit_offset_t a, + enum tree_code op, + bit_offset_t b) +{ + switch (op) + { + default: + gcc_unreachable (); + case EQ_EXPR: return a == b; + case NE_EXPR: return a != b; + case GE_EXPR: return a >= b; + case LE_EXPR: return a <= b; + case GT_EXPR: return a > b; + case LT_EXPR: return a < b; + } +} + region_offset region_offset::make_byte_offset (const region *base_region, const svalue *num_bytes_sval) @@ -94,25 +112,27 @@ region_offset::calc_symbolic_byte_offset (region_model_manager *mgr) const void region_offset::dump_to_pp (pretty_printer *pp, bool simple) const { + pp_string (pp, "{"); + m_base_region->dump_to_pp (pp, simple); if (symbolic_p ()) { - /* We don't bother showing the base region. */ - pp_string (pp, "byte "); + pp_string (pp, " byte "); m_sym_offset->dump_to_pp (pp, simple); } else { if (m_offset % BITS_PER_UNIT == 0) { - pp_string (pp, "byte "); + pp_string (pp, " byte "); pp_wide_int (pp, m_offset / BITS_PER_UNIT, SIGNED); } else { - pp_string (pp, "bit "); + pp_string (pp, " bit "); pp_wide_int (pp, m_offset, SIGNED); } } + pp_string (pp, "}"); } DEBUG_FUNCTION void @@ -373,6 +393,109 @@ strip_types (const region_offset &offset, region_model_manager &mgr) return offset; } +/* Ignoring base regions, compare the byte offset of OFFSET_A and OFFSET_B + within their respective base regions. */ + +static tristate +eval_byte_offset_comparison (region_offset offset_a, + enum tree_code op, + region_offset offset_b, + const region_model &model) +{ + if (offset_a.concrete_p ()) + { + if (offset_b.concrete_p ()) + { + // Concrete vs concrete: we know the result: + return compare_bit_offsets_p (offset_a.get_bit_offset (), + op, + offset_b.get_bit_offset ()); + } + else + { + // Concrete vs symbolic + // TODO: There may be room for improved precision here + return tristate::unknown (); + } + } + else + { + if (offset_b.concrete_p ()) + { + // Symbolic vs concrete + // TODO: There may be room for improved precision here + return tristate::unknown (); + } + else + { + // Symbolic vs symbolic + const svalue *offset_sval_a = offset_a.get_symbolic_byte_offset (); + const svalue *offset_sval_b = offset_b.get_symbolic_byte_offset (); + return model.eval_condition (offset_sval_a, op, offset_sval_b); + } + } +} + +/* Evaluate the condition LHS_OFFSET OP RHS_OFFSET for comparing + pointers. + + See if they point to the same base region, and if we know about + the offsets within their regions. + + Use MODEL for aliasing information and any knowledge from + constraint_manager about ordering of pointers. */ + +tristate +eval_region_offset_comparison (region_offset lhs_offset, + enum tree_code op, + region_offset rhs_offset, + const region_model &model) +{ + /* Try to determine if they're the same base region. */ + const tristate same_base_region + = model.get_store ()->eval_alias (lhs_offset.get_base_region (), + rhs_offset.get_base_region (), + model); + + /* Try to determine if they're the same offset relative to their + base region. */ + const tristate same_byte_offset + = eval_byte_offset_comparison (lhs_offset, EQ_EXPR, rhs_offset, model); + + /* With that, we might know if they're equal/non-equal. */ + const tristate equality = same_base_region.and_(same_byte_offset); + + switch (op) + { + default: + gcc_unreachable (); + + case EQ_EXPR: + return equality; + + case NE_EXPR: + return equality.not_ (); + + case GE_EXPR: + case LE_EXPR: + if (equality.is_true ()) + return tristate (true); + else if (same_base_region.is_true ()) + return eval_byte_offset_comparison (lhs_offset, op, rhs_offset, model); + else + return tristate::unknown (); + + case GT_EXPR: + case LT_EXPR: + if (equality.is_true ()) + return tristate (false); + else if (same_base_region.is_true ()) + return eval_byte_offset_comparison (lhs_offset, op, rhs_offset, model); + else + return tristate::unknown (); + } +} + /* class region and its various subclasses. */ /* class region. */ diff --git a/gcc/analyzer/store.cc b/gcc/analyzer/store.cc index d198aec9d2b8..d25709516d9b 100644 --- a/gcc/analyzer/store.cc +++ b/gcc/analyzer/store.cc @@ -3133,12 +3133,14 @@ store::get_any_binding (store_manager *mgr, const region *reg) const return (*cluster_slot)->get_any_binding (mgr, reg); } -/* Set the value of LHS_REG to RHS_SVAL. */ +/* Set the value of LHS_REG to RHS_SVAL. + Use MODEL when determining if regions alias each other. */ void store::set_value (store_manager *mgr, const region *lhs_reg, const svalue *rhs_sval, - uncertainty_t *uncertainty) + uncertainty_t *uncertainty, + const region_model &model) { logger *logger = mgr->get_logger (); LOG_SCOPE (logger); @@ -3201,7 +3203,8 @@ store::set_value (store_manager *mgr, const region *lhs_reg, || lhs_cluster->symbolic_p () || iter_cluster->symbolic_p ())) { - tristate t_alias = eval_alias (lhs_base_reg, iter_base_reg); + tristate t_alias = eval_alias (lhs_base_reg, iter_base_reg, + model); switch (t_alias.get_value ()) { default: @@ -3249,12 +3252,17 @@ store::set_value (store_manager *mgr, const region *lhs_reg, on_maybe_live_values (*mgr, maybe_live_values); } -/* Determine if BASE_REG_A could be an alias of BASE_REG_B. */ +/* Determine if BASE_REG_A could be an alias of BASE_REG_B. + Use information from MODEL when comparing pointers. */ tristate store::eval_alias (const region *base_reg_a, - const region *base_reg_b) const + const region *base_reg_b, + const region_model &model) const { + if (base_reg_a == base_reg_b) + return tristate (true); + /* SSA names can't alias. */ tree decl_a = base_reg_a->maybe_get_decl (); if (decl_a && TREE_CODE (decl_a) == SSA_NAME) @@ -3268,21 +3276,193 @@ store::eval_alias (const region *base_reg_a, return tristate::TS_FALSE; /* Try both ways, for symmetry. */ - tristate ts_ab = eval_alias_1 (base_reg_a, base_reg_b); + tristate ts_ab = eval_alias_1 (base_reg_a, base_reg_b, model); if (ts_ab.is_false ()) return tristate::TS_FALSE; - tristate ts_ba = eval_alias_1 (base_reg_b, base_reg_a); + tristate ts_ba = eval_alias_1 (base_reg_b, base_reg_a, model); if (ts_ba.is_false ()) return tristate::TS_FALSE; - return tristate::TS_UNKNOWN; + + gcc_assert (base_reg_a != base_reg_b); + enum region_kind kind_a = base_reg_a->get_kind (); + enum region_kind kind_b = base_reg_b->get_kind (); + if (kind_a == kind_b) + { + /* We have two different base region instances of the same kind. + For many kinds of region this implies they must have different + addresses. */ + switch (kind_a) + { + /* Invalid cases. */ + + default: + gcc_unreachable (); + + case RK_CODE: + case RK_ERRNO: + case RK_GLOBALS: + case RK_HEAP: + case RK_ROOT: + case RK_STACK: + case RK_THREAD_LOCAL: + /* We expect these to be singletons. */ + gcc_unreachable (); + + case RK_BIT_RANGE: + case RK_CAST: + case RK_ELEMENT: + case RK_FIELD: + case RK_OFFSET: + case RK_SIZED: + /* These aren't base regions. */ + gcc_unreachable (); + + /* Valid cases. */ + + case RK_ALLOCA: + case RK_DECL: + case RK_FRAME: + case RK_FUNCTION: + case RK_LABEL: + case RK_PRIVATE: + case RK_VAR_ARG: + /* We expect different regions of these kinds to have + different addresses. */ + return tristate (false); + + case RK_HEAP_ALLOCATED: + /* We have results of two different heap allocations. + If they were non-zero-sized allocations they are different from + each other, but zero-sized allocations might alias. + Note that this doesn't handle the case where of a freed pointer + possibly being reused, but we don't have a way to distinguish + this case from the "two different allocations case", and it seems + much more important to keep the latter from aliasing each + other (for precision when tracking writes). */ + { + tristate size_a_gt_zero (tristate::unknown ()); + tristate size_b_gt_zero (tristate::unknown ()); + const svalue *zero_size + = model.get_manager ()->get_or_create_int_cst (size_type_node, 0); + + if (auto sval_size_a = model.get_dynamic_extents (base_reg_a)) + { + size_a_gt_zero = model.eval_condition (sval_size_a, + GT_EXPR, + zero_size); + } + if (auto sval_size_b = model.get_dynamic_extents (base_reg_b)) + { + size_b_gt_zero = model.eval_condition (sval_size_b, + GT_EXPR, + zero_size); + } + if (size_a_gt_zero.is_known () && size_b_gt_zero.is_known ()) + { + /* We have results of two different heap allocations, and for + each we know if the size was non-zero. */ + if (size_a_gt_zero.is_false () && size_b_gt_zero.is_false ()) + { + /* Different allocations, both zero-sized. We don't + know if they have the same address. */ + return tristate::unknown (); + } + else + { + /* We either have different allocations of non-zero size, + or different allocations where one has non-zero size, the + other is zero-sized. We know these have different + addresses. */ + return tristate (false); + } + } + if (size_a_gt_zero.is_true () || size_b_gt_zero.is_true ()) + { + /* Different allocations, of which at least one > 0 in size. + They must have different addresses. */ + return tristate (false); + } + /* At least one size might be zero. */ + return tristate::unknown (); + } + + case RK_SYMBOLIC: + case RK_UNKNOWN: + /* We don't know that such regions are different from each other. */ + return tristate::unknown (); + + case RK_STRING: + /* For now, don't attempt to decide if string literals alias + each other. */ + return tristate::unknown (); + } + } + else + { + /* We have two base region instances of different kinds. + For many kinds of region this implies they must have different + addresses. */ + if (kind_b == RK_SYMBOLIC || kind_b == RK_UNKNOWN) + return tristate::unknown (); + + switch (kind_a) + { + /* Invalid cases. */ + + default: + gcc_unreachable (); + + case RK_CODE: + case RK_ERRNO: + case RK_GLOBALS: + case RK_HEAP: + case RK_ROOT: + case RK_STACK: + case RK_THREAD_LOCAL: + /* We expect these to be singletons. */ + gcc_unreachable (); + + case RK_BIT_RANGE: + case RK_CAST: + case RK_ELEMENT: + case RK_FIELD: + case RK_OFFSET: + case RK_SIZED: + /* These aren't base regions. */ + gcc_unreachable (); + + /* Valid cases. */ + + case RK_ALLOCA: + case RK_DECL: + case RK_FRAME: + case RK_FUNCTION: + case RK_HEAP_ALLOCATED: + case RK_LABEL: + case RK_PRIVATE: + case RK_STRING: + case RK_VAR_ARG: + /* We expect regions of these kinds to have different addresses + from regions of other kinds (apart from symbolic/unknown). */ + return tristate (false); + + case RK_SYMBOLIC: + case RK_UNKNOWN: + /* We don't know anything about such regions. */ + return tristate::unknown (); + } + } } /* Half of store::eval_alias; called twice for symmetry. */ tristate store::eval_alias_1 (const region *base_reg_a, - const region *base_reg_b) const + const region *base_reg_b, + const region_model &model) const { + gcc_assert (base_reg_a != base_reg_b); + /* If they're in different memory spaces, they can't alias. */ { enum memory_space memspace_a = base_reg_a->get_memory_space (); @@ -3334,12 +3514,14 @@ store::eval_alias_1 (const region *base_reg_a, We want to ensure that "*ptr" can only clobber things within REGION's base region. */ tristate ts = eval_alias (pointee->get_base_region (), - base_reg_b); + base_reg_b, + model); if (ts.is_false ()) return tristate::TS_FALSE; } } } + return tristate::TS_UNKNOWN; } @@ -3952,7 +4134,8 @@ store::replay_call_summary_cluster (call_summary_replay &r, caller_sval = reg_mgr->get_or_create_unknown_svalue (summary_sval->get_type ()); set_value (mgr, caller_dest_reg, - caller_sval, nullptr /* uncertainty_t * */); + caller_sval, nullptr /* uncertainty_t * */, + *cd.get_model ()); } break; @@ -3977,7 +4160,8 @@ store::replay_call_summary_cluster (call_summary_replay &r, caller_sval = reg_mgr->get_or_create_unknown_svalue (summary_sval->get_type ()); set_value (mgr, caller_dest_reg, - caller_sval, nullptr /* uncertainty_t * */); + caller_sval, nullptr /* uncertainty_t * */, + *cd.get_model ()); } break; diff --git a/gcc/analyzer/store.h b/gcc/analyzer/store.h index 021a438222fd..7d0a98244ef7 100644 --- a/gcc/analyzer/store.h +++ b/gcc/analyzer/store.h @@ -957,7 +957,8 @@ public: void set_value (store_manager *mgr, const region *lhs_reg, const svalue *rhs_sval, - uncertainty_t *uncertainty); + uncertainty_t *uncertainty, + const region_model &model); void clobber_region (store_manager *mgr, const region *reg); void purge_region (store_manager *mgr, const region *reg); void fill_region (store_manager *mgr, const region *reg, const svalue *sval); @@ -1002,7 +1003,8 @@ public: cluster_map_t::iterator end () const { return m_cluster_map.end (); } tristate eval_alias (const region *base_reg_a, - const region *base_reg_b) const; + const region *base_reg_b, + const region_model &model) const; void canonicalize (store_manager *mgr); void loop_replay_fixup (const store *other_store, @@ -1020,7 +1022,8 @@ private: void remove_overlapping_bindings (store_manager *mgr, const region *reg, uncertainty_t *uncertainty); tristate eval_alias_1 (const region *base_reg_a, - const region *base_reg_b) const; + const region *base_reg_b, + const region_model &model) const; cluster_map_t m_cluster_map; diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index 2df7627dd30d..6e031da62d3a 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -998,45 +998,18 @@ region_svalue::implicitly_live_p (const svalue_set *, tristate region_svalue::eval_condition (const region_svalue *lhs, enum tree_code op, - const region_svalue *rhs) + const region_svalue *rhs, + const region_model &model) { - /* See if they point to the same region. */ + /* Convert to region_offset representation, and work with that. */ const region *lhs_reg = lhs->get_pointee (); const region *rhs_reg = rhs->get_pointee (); - bool ptr_equality = lhs_reg == rhs_reg; - switch (op) - { - default: - gcc_unreachable (); - case EQ_EXPR: - if (ptr_equality) - return tristate::TS_TRUE; - else - return tristate::TS_FALSE; - break; + region_model_manager *mgr = model.get_manager (); + region_offset lhs_offset = lhs_reg->get_offset (mgr); + region_offset rhs_offset = rhs_reg->get_offset (mgr); - case NE_EXPR: - if (ptr_equality) - return tristate::TS_FALSE; - else - return tristate::TS_TRUE; - break; - - case GE_EXPR: - case LE_EXPR: - if (ptr_equality) - return tristate::TS_TRUE; - break; - - case GT_EXPR: - case LT_EXPR: - if (ptr_equality) - return tristate::TS_FALSE; - break; - } - - return tristate::TS_UNKNOWN; + return eval_region_offset_comparison (lhs_offset, op, rhs_offset, model); } /* class constant_svalue : public svalue. */ diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h index a3fc41056c45..506981a83294 100644 --- a/gcc/analyzer/svalue.h +++ b/gcc/analyzer/svalue.h @@ -284,7 +284,8 @@ public: static tristate eval_condition (const region_svalue *lhs_ptr, enum tree_code op, - const region_svalue *rhs_ptr); + const region_svalue *rhs_ptr, + const region_model &model); private: const region *m_reg; diff --git a/gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-eq.c b/gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-eq.c new file mode 100644 index 000000000000..117afa3c0bba --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-eq.c @@ -0,0 +1,179 @@ +#include "analyzer-decls.h" + +typedef __SIZE_TYPE__ size_t; + +void * +hide_from_optimizer (const void *ptr) __attribute__((noinline)); + +void * +hide_from_optimizer (const void *ptr) +{ + return (void *)ptr; +} + +unsigned char global_buf[1024]; + +void +test_pointer_eq (unsigned *p, int i, int j) +{ + unsigned char local_declared_before_local_buf; + unsigned char local_buf[16]; + unsigned char local_declared_after_local_buf; + + __analyzer_eval (p == hide_from_optimizer (p)); // { dg-warning "TRUE" } + __analyzer_eval (local_buf == &local_buf[0]); // { dg-warning "TRUE" } + + /* Comparisons with NULL. */ + __analyzer_eval (hide_from_optimizer (local_buf) == NULL); // { dg-warning "FALSE" } + __analyzer_eval (hide_from_optimizer (NULL) == NULL); // { dg-warning "TRUE" } + __analyzer_eval (hide_from_optimizer (NULL) == p); // { dg-warning "UNKNOWN" } + + /* Same concrete base region. */ + { + // Same concrete offsets + __analyzer_eval (local_buf == hide_from_optimizer (&local_buf[0])); // { dg-warning "TRUE" } + + // Different concrete offsets + __analyzer_eval (local_buf == hide_from_optimizer (&local_buf[1])); // { dg-warning "FALSE" } + + // Same symbolic offset + __analyzer_eval (&local_buf[i] == hide_from_optimizer (&local_buf[i])); // { dg-warning "TRUE" } + + // Possibly different symbolic offset + { + __analyzer_eval (&local_buf[i] == hide_from_optimizer (&local_buf[j])); // { dg-warning "UNKNOWN" } + if (i == j) + { + __analyzer_eval (&local_buf[i] == hide_from_optimizer (&local_buf[j])); // { dg-warning "TRUE" "ideal behavior" { xfail *-*-* } } + // { dg-message "UNKNOWN" "current behavior" { target *-*-* } .-1 } + } + else + { + __analyzer_eval (&local_buf[i] == hide_from_optimizer (&local_buf[j])); // { dg-warning "FALSE" "ideal behavior" { xfail *-*-* } } + // { dg-message "UNKNOWN" "current behavior" { target *-*-* } .-1 } + } + } + } + + /* Different concrete base regions. */ + { + __analyzer_eval (local_buf == hide_from_optimizer (global_buf)); // { dg-warning "FALSE" } + + // Within the buffer, we're definitely not pointing at other locals + __analyzer_eval (&local_buf[0] == hide_from_optimizer (&local_declared_before_local_buf)); // { dg-warning "FALSE" } + __analyzer_eval (&local_buf[15] == hide_from_optimizer (&local_declared_after_local_buf)); // { dg-warning "FALSE" } + + /* Outside the valid bounds of the buffer is undefined behavior. + We currently happen to return FALSE for such attempts to form + pointers to neighboring locals. */ + __analyzer_eval (&local_buf[-1] == hide_from_optimizer (&local_declared_before_local_buf)); // { dg-warning "FALSE" } + __analyzer_eval (&local_buf[16] == hide_from_optimizer (&local_declared_after_local_buf)); // { dg-warning "FALSE" } + } + + /* Concrete vs symbolic where we know they're different + (buf is local, so param p can't point to it). */ + __analyzer_eval (local_buf == hide_from_optimizer (p)); // { dg-warning "FALSE" } + + /* Concrete vs symbolic where we don't know they're different + (glob is global, so param p could point to it). */ + __analyzer_eval (global_buf == hide_from_optimizer (p)); // { dg-warning "UNKNOWN" } + + // What about symbolic, could different offsets be the same? + // TODO: what about overflow of locals, to try to alias (undefined behavior) +} + +void +test_nonempty_heap_allocs (void) +{ + int i; + + // Known non-zero size + void *p = __builtin_malloc (1024); + void *q = __builtin_malloc (1024); + + __analyzer_eval (hide_from_optimizer (p) == p); // { dg-warning "TRUE" } + __analyzer_eval (hide_from_optimizer (p) == q); // { dg-warning "FALSE" } + + __analyzer_eval (hide_from_optimizer (p) == &i); // { dg-warning "FALSE" } + __analyzer_eval (hide_from_optimizer (q) == &i); // { dg-warning "FALSE" } + + __builtin_free (p); + __builtin_free (q); +} + +void +test_maybe_empty_heap_allocs (size_t sz_p, size_t sz_q) +{ + int i; + + /* These could be zero-sized buffers, which some implementations + return null for. */ + void *p = __builtin_malloc (sz_p); + void *q = __builtin_malloc (sz_q); + + __analyzer_eval (hide_from_optimizer (p) == p); // { dg-warning "TRUE" } + __analyzer_eval (hide_from_optimizer (p) == q); // { dg-warning "UNKNOWN" } + + __analyzer_eval (hide_from_optimizer (p) == &i); // { dg-warning "FALSE" } + __analyzer_eval (hide_from_optimizer (q) == &i); // { dg-warning "FALSE" } + + __builtin_free (p); + __builtin_free (q); +} + +void +test_one_past_end (void) +{ + int arr[10]; + int *end = &arr[10]; + __analyzer_eval (hide_from_optimizer (end) == &arr[10]); // { dg-warning "TRUE" } + __analyzer_eval (hide_from_optimizer (end) == &arr[9]); // { dg-warning "FALSE" } +} + +void +test_string_literals (void) +{ + const char *abc = "abc"; + const char *def = "def"; + + __analyzer_eval (hide_from_optimizer (abc) == &abc[0]); // { dg-warning "TRUE" } + + __analyzer_eval (hide_from_optimizer (abc) == &abc[1]); // { dg-warning "FALSE" "ideal" { xfail *-*-* } } + // { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } + + __analyzer_eval (hide_from_optimizer (abc) == &def[0]); // { dg-warning "FALSE" "ideal" { xfail *-*-* } } + // { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } + + __analyzer_eval (hide_from_optimizer (abc) == &def[1]); // { dg-warning "FALSE" "ideal" { xfail *-*-* } } + // { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } +} + +struct coord {int x; int y; }; + +void +test_struct_pointers (void) +{ + struct coord c1; + struct coord c2; + + __analyzer_eval (hide_from_optimizer (&c1) == &c1.x); // { dg-warning "TRUE" } + __analyzer_eval (hide_from_optimizer (&c1) == &c1.y); // { dg-warning "FALSE" } + + __analyzer_eval (hide_from_optimizer (&c1) == &c2.x); // { dg-warning "FALSE" } + __analyzer_eval (hide_from_optimizer (&c1) == &c2.y); // { dg-warning "FALSE" } +} + +union u {int x; char y; }; + +void +test_union_pointers (void) +{ + union u u1; + union u u2; + + __analyzer_eval (hide_from_optimizer (&u1) == &u1.x); // { dg-warning "TRUE" } + __analyzer_eval (hide_from_optimizer (&u1) == &u1.y); // { dg-warning "TRUE" } + + __analyzer_eval (hide_from_optimizer (&u1) == &u2.x); // { dg-warning "FALSE" } + __analyzer_eval (hide_from_optimizer (&u1) == &u2.y); // { dg-warning "FALSE" } +} diff --git a/gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-ge.c b/gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-ge.c new file mode 100644 index 000000000000..407e996a124d --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-ge.c @@ -0,0 +1,73 @@ +/* { dg-additional-options "-Wno-compare-distinct-pointer-types" { target c } } */ + +#include "analyzer-decls.h" + +const void * +hide_from_optimizer (void *ptr) __attribute__((noinline)); + +const void * +hide_from_optimizer (void *ptr) +{ + return ptr; +} + +unsigned char global_buf[1024]; + +void +test_pointer_ge (unsigned *p, int i, int j) +{ + unsigned char local_declared_before_local_buf; + unsigned char local_buf[16]; + unsigned char local_declared_after_local_buf; + + __analyzer_eval (p >= hide_from_optimizer (p)); // { dg-warning "TRUE" } + __analyzer_eval (local_buf >= &local_buf[0]); // { dg-warning "TRUE" } + + /* Same concrete base region. */ + { + // Same concrete offsets + __analyzer_eval (local_buf >= hide_from_optimizer (&local_buf[0])); // { dg-warning "TRUE" } + + // Different concrete offsets + __analyzer_eval (&local_buf[0] >= hide_from_optimizer (&local_buf[1])); // { dg-warning "FALSE" } + __analyzer_eval (&local_buf[1] >= hide_from_optimizer (&local_buf[0])); // { dg-warning "TRUE" } + + // Same symbolic offset + __analyzer_eval (&local_buf[i] >= hide_from_optimizer (&local_buf[i])); // { dg-warning "TRUE" } + + // Possibly different symbolic offset + { + __analyzer_eval (&local_buf[i] >= hide_from_optimizer (&local_buf[j])); // { dg-warning "UNKNOWN" } + if (i == j) + { + __analyzer_eval (&local_buf[i] >= hide_from_optimizer (&local_buf[j])); // { dg-warning "TRUE" "ideal behavior" { xfail *-*-* } } + // { dg-message "UNKNOWN" "current behavior" { target *-*-* } .-1 } + } + else + { + __analyzer_eval (&local_buf[i] >= hide_from_optimizer (&local_buf[j])); // { dg-warning "UNKNOWN" } + } + } + } + + /* Different concrete base regions. */ + { + __analyzer_eval (local_buf >= hide_from_optimizer (global_buf)); // { dg-warning "UNKNOWN" } + + // We can't compare the addresses of locals + __analyzer_eval (&local_buf[0] >= hide_from_optimizer (&local_declared_before_local_buf)); // { dg-warning "UNKNOWN" } + __analyzer_eval (&local_buf[15] >= hide_from_optimizer (&local_declared_after_local_buf)); // { dg-warning "UNKNOWN" } + } + + /* Concrete vs symbolic where we know they're different + (buf is local, so param p can't point to it). */ + __analyzer_eval (local_buf >= hide_from_optimizer (p)); // { dg-warning "UNKNOWN" "ideal behavior" { xfail *-*-* } } + // { dg-message "FALSE" "current behavior" { target *-*-* } .-1 } + + /* Concrete vs symbolic where we don't know they're different + (glob is global, so param p could point to it). */ + __analyzer_eval (global_buf >= hide_from_optimizer (p)); // { dg-warning "UNKNOWN" } + + // What about symbolic, could different offsets be the same? + // TODO: what about overflow of locals, to try to alias (undefined behavior) +} diff --git a/gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-gt.c b/gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-gt.c new file mode 100644 index 000000000000..80e8ab87e24c --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-gt.c @@ -0,0 +1,74 @@ +/* { dg-additional-options "-Wno-compare-distinct-pointer-types" { target c } } */ + +#include "analyzer-decls.h" + +const void * +hide_from_optimizer (void *ptr) __attribute__((noinline)); + +const void * +hide_from_optimizer (void *ptr) +{ + return ptr; +} + +unsigned char global_buf[1024]; + +void +test_pointer_gt (unsigned *p, int i, int j) +{ + unsigned char local_declared_before_local_buf; + unsigned char local_buf[16]; + unsigned char local_declared_after_local_buf; + + __analyzer_eval (p > hide_from_optimizer (p)); // { dg-warning "FALSE" } + __analyzer_eval (local_buf > &local_buf[0]); // { dg-warning "FALSE" } + + /* Same concrete base region. */ + { + // Same concrete offsets + __analyzer_eval (local_buf > hide_from_optimizer (&local_buf[0])); // { dg-warning "FALSE" } + + // Different concrete offsets + __analyzer_eval (&local_buf[0] > hide_from_optimizer (&local_buf[1])); // { dg-warning "FALSE" } + __analyzer_eval (&local_buf[1] > hide_from_optimizer (&local_buf[0])); // { dg-warning "TRUE" } + + // Same symbolic offset + __analyzer_eval (&local_buf[i] > hide_from_optimizer (&local_buf[i])); // { dg-warning "FALSE" } + + // Possibly different symbolic offset + { + __analyzer_eval (&local_buf[i] > hide_from_optimizer (&local_buf[j])); // { dg-warning "UNKNOWN" } + if (i == j) + { + __analyzer_eval (&local_buf[i] > hide_from_optimizer (&local_buf[j])); // { dg-warning "FALSE" "ideal behavior" { xfail *-*-* } } + // { dg-message "UNKNOWN" "current behavior" { target *-*-* } .-1 } + } + else + { + __analyzer_eval (&local_buf[i] > hide_from_optimizer (&local_buf[j])); // { dg-warning "TRUE" "ideal behavior" { xfail *-*-* } } + // { dg-message "UNKNOWN" "current behavior" { target *-*-* } .-1 } + } + } + } + + /* Different concrete base regions. */ + { + __analyzer_eval (local_buf > hide_from_optimizer (global_buf)); // { dg-warning "UNKNOWN" } + + // We can't compare the addresses of locals + __analyzer_eval (&local_buf[0] > hide_from_optimizer (&local_declared_before_local_buf)); // { dg-warning "UNKNOWN" } + __analyzer_eval (&local_buf[15] > hide_from_optimizer (&local_declared_after_local_buf)); // { dg-warning "UNKNOWN" } + } + + /* Concrete vs symbolic where we know they're different + (buf is local, so param p can't point to it). */ + __analyzer_eval (local_buf > hide_from_optimizer (p)); // { dg-warning "UNKNOWN" "ideal behavior" { xfail *-*-* } } + // { dg-message "FALSE" "current behavior" { target *-*-* } .-1 } + + /* Concrete vs symbolic where we don't know they're different + (glob is global, so param p could point to it). */ + __analyzer_eval (global_buf > hide_from_optimizer (p)); // { dg-warning "UNKNOWN" } + + // What about symbolic, could different offsets be the same? + // TODO: what about overflow of locals, to try to alias (undefined behavior) +} diff --git a/gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-le.c b/gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-le.c new file mode 100644 index 000000000000..a842bd986e1b --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-le.c @@ -0,0 +1,73 @@ +/* { dg-additional-options "-Wno-compare-distinct-pointer-types" { target c } } */ + +#include "analyzer-decls.h" + +const void * +hide_from_optimizer (void *ptr) __attribute__((noinline)); + +const void * +hide_from_optimizer (void *ptr) +{ + return ptr; +} + +unsigned char global_buf[1024]; + +void +test_pointer_le (unsigned *p, int i, int j) +{ + unsigned char local_declared_before_local_buf; + unsigned char local_buf[16]; + unsigned char local_declared_after_local_buf; + + __analyzer_eval (p <= hide_from_optimizer (p)); // { dg-warning "TRUE" } + __analyzer_eval (local_buf <= &local_buf[0]); // { dg-warning "TRUE" } + + /* Same concrete base region. */ + { + // Same concrete offsets + __analyzer_eval (local_buf <= hide_from_optimizer (&local_buf[0])); // { dg-warning "TRUE" } + + // Different concrete offsets + __analyzer_eval (&local_buf[0] <= hide_from_optimizer (&local_buf[1])); // { dg-warning "TRUE" } + __analyzer_eval (&local_buf[1] <= hide_from_optimizer (&local_buf[0])); // { dg-warning "FALSE" } + + // Same symbolic offset + __analyzer_eval (&local_buf[i] <= hide_from_optimizer (&local_buf[i])); // { dg-warning "TRUE" } + + // Possibly different symbolic offset + { + __analyzer_eval (&local_buf[i] <= hide_from_optimizer (&local_buf[j])); // { dg-warning "UNKNOWN" } + if (i == j) + { + __analyzer_eval (&local_buf[i] <= hide_from_optimizer (&local_buf[j])); // { dg-warning "TRUE" "ideal behavior" { xfail *-*-* } } + // { dg-message "UNKNOWN" "current behavior" { target *-*-* } .-1 } + } + else + { + __analyzer_eval (&local_buf[i] <= hide_from_optimizer (&local_buf[j])); // { dg-warning "UNKNOWN" } + } + } + } + + /* Different concrete base regions. */ + { + __analyzer_eval (local_buf <= hide_from_optimizer (global_buf)); // { dg-warning "UNKNOWN" } + + // We can't compare the addresses of locals + __analyzer_eval (&local_buf[0] <= hide_from_optimizer (&local_declared_before_local_buf)); // { dg-warning "UNKNOWN" } + __analyzer_eval (&local_buf[15] <= hide_from_optimizer (&local_declared_after_local_buf)); // { dg-warning "UNKNOWN" } + } + + /* Concrete vs symbolic where we know they're different + (buf is local, so param p can't point to it). */ + __analyzer_eval (local_buf <= hide_from_optimizer (p)); // { dg-warning "UNKNOWN" "ideal behavior" { xfail *-*-* } } + // { dg-message "FALSE" "current behavior" { target *-*-* } .-1 } + + /* Concrete vs symbolic where we don't know they're different + (glob is global, so param p could point to it). */ + __analyzer_eval (global_buf <= hide_from_optimizer (p)); // { dg-warning "UNKNOWN" } + + // What about symbolic, could different offsets be the same? + // TODO: what about overflow of locals, to try to alias (undefined behavior) +} diff --git a/gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-lt.c b/gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-lt.c new file mode 100644 index 000000000000..a84c1b5d2c7f --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-lt.c @@ -0,0 +1,74 @@ +/* { dg-additional-options "-Wno-compare-distinct-pointer-types" { target c } } */ + +#include "analyzer-decls.h" + +const void * +hide_from_optimizer (void *ptr) __attribute__((noinline)); + +const void * +hide_from_optimizer (void *ptr) +{ + return ptr; +} + +unsigned char global_buf[1024]; + +void +test_pointer_lt (unsigned *p, int i, int j) +{ + unsigned char local_declared_before_local_buf; + unsigned char local_buf[16]; + unsigned char local_declared_after_local_buf; + + __analyzer_eval (p < hide_from_optimizer (p)); // { dg-warning "FALSE" } + __analyzer_eval (local_buf < &local_buf[0]); // { dg-warning "FALSE" } + + /* Same concrete base region. */ + { + // Same concrete offsets + __analyzer_eval (local_buf < hide_from_optimizer (&local_buf[0])); // { dg-warning "FALSE" } + + // Different concrete offsets + __analyzer_eval (&local_buf[0] < hide_from_optimizer (&local_buf[1])); // { dg-warning "TRUE" } + __analyzer_eval (&local_buf[1] < hide_from_optimizer (&local_buf[0])); // { dg-warning "FALSE" } + + // Same symbolic offset + __analyzer_eval (&local_buf[i] < hide_from_optimizer (&local_buf[i])); // { dg-warning "FALSE" } + + // Possibly different symbolic offset + { + __analyzer_eval (&local_buf[i] < hide_from_optimizer (&local_buf[j])); // { dg-warning "UNKNOWN" } + if (i == j) + { + __analyzer_eval (&local_buf[i] < hide_from_optimizer (&local_buf[j])); // { dg-warning "FALSE" "ideal behavior" { xfail *-*-* } } + // { dg-message "UNKNOWN" "current behavior" { target *-*-* } .-1 } + } + else + { + __analyzer_eval (&local_buf[i] < hide_from_optimizer (&local_buf[j])); // { dg-warning "TRUE" "ideal behavior" { xfail *-*-* } } + // { dg-message "UNKNOWN" "current behavior" { target *-*-* } .-1 } + } + } + } + + /* Different concrete base regions. */ + { + __analyzer_eval (local_buf < hide_from_optimizer (global_buf)); // { dg-warning "UNKNOWN" } + + // We can't compare the addresses of locals + __analyzer_eval (&local_buf[0] < hide_from_optimizer (&local_declared_before_local_buf)); // { dg-warning "UNKNOWN" } + __analyzer_eval (&local_buf[15] < hide_from_optimizer (&local_declared_after_local_buf)); // { dg-warning "UNKNOWN" } + } + + /* Concrete vs symbolic where we know they're different + (buf is local, so param p can't point to it). */ + __analyzer_eval (local_buf < hide_from_optimizer (p)); // { dg-warning "UNKNOWN" "ideal behavior" { xfail *-*-* } } + // { dg-message "FALSE" "current behavior" { target *-*-* } .-1 } + + /* Concrete vs symbolic where we don't know they're different + (glob is global, so param p could point to it). */ + __analyzer_eval (global_buf < hide_from_optimizer (p)); // { dg-warning "UNKNOWN" } + + // What about symbolic, could different offsets be the same? + // TODO: what about overflow of locals, to try to alias (undefined behavior) +} diff --git a/gcc/testsuite/g++.dg/analyzer/pointer-casts-pr125304.C b/gcc/testsuite/g++.dg/analyzer/pointer-casts-pr125304.C new file mode 100644 index 000000000000..d354f162302c --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/pointer-casts-pr125304.C @@ -0,0 +1,22 @@ +#include "../../gcc.dg/analyzer/analyzer-decls.h" + +template <typename Src, typename Dst> +Dst * +ptr_cast (Src *ptr) __attribute__((noinline)); + +template <typename Src, typename Dst> +Dst * +ptr_cast (Src *ptr) +{ + return (Dst *)ptr; +} + +void test_equality (unsigned *p) +{ + __analyzer_eval (p == (unsigned *)ptr_cast<unsigned, signed> (p)); // { dg-warning "TRUE" } + + unsigned char buf[16]; + __analyzer_eval (buf == &buf[0]); // { dg-warning "TRUE" } + + __analyzer_eval (buf == (unsigned char*)ptr_cast<unsigned char, signed char> (&buf[0])); // { dg-warning "TRUE" } +}
