]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
analyzer: fix pointer comparisons [PR125304]
authorDavid Malcolm <dmalcolm@redhat.com>
Tue, 19 May 2026 19:04:30 +0000 (15:04 -0400)
committerDavid Malcolm <dmalcolm@redhat.com>
Tue, 19 May 2026 19:04:30 +0000 (15:04 -0400)
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 <dmalcolm@redhat.com>
13 files changed:
gcc/analyzer/common.h
gcc/analyzer/region-model.cc
gcc/analyzer/region.cc
gcc/analyzer/store.cc
gcc/analyzer/store.h
gcc/analyzer/svalue.cc
gcc/analyzer/svalue.h
gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-eq.c [new file with mode: 0644]
gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-ge.c [new file with mode: 0644]
gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-gt.c [new file with mode: 0644]
gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-le.c [new file with mode: 0644]
gcc/testsuite/c-c++-common/analyzer/pointer-comparison-pr125304-lt.c [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/pointer-casts-pr125304.C [new file with mode: 0644]

index 81db5997f2ed5f9a92d61a2fe439b4b6eeff100e..8ce1475bc5eb4c329ec518b694ddecb20f431701 100644 (file)
@@ -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);
index 9555f72c307416b52d66c794d2a4f52f11825a2c..5e1a895b1504edb27b021590400f34878fd582c5 100644 (file)
@@ -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.  */
index b03c79ffa72c1f497f90eeda53b5726419772483..61c82dacec23fd300ae5c7f82c471226ac6ae8ef 100644 (file)
@@ -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.  */
index d198aec9d2b86e9e4ead896a8370b52ea6abff68..d25709516d9bc27704e76faeab3ce9818b78cd51 100644 (file)
@@ -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;
 
index 021a438222fd18b0861d4b44e3a30be3e1b1de02..7d0a98244ef7d3a1cd4658213f87fa5a8089ac85 100644 (file)
@@ -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;
 
index 2df7627dd30daad32c8005b9654a813716fe729a..6e031da62d3acae3a19820a794e7a68f0e26b428 100644 (file)
@@ -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.  */
index a3fc41056c451806efeed3d91108b360c082b78b..506981a8329497d5f787c06de9a5ca5f273a3d73 100644 (file)
@@ -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 (file)
index 0000000..117afa3
--- /dev/null
@@ -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 (file)
index 0000000..407e996
--- /dev/null
@@ -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 (file)
index 0000000..80e8ab8
--- /dev/null
@@ -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 (file)
index 0000000..a842bd9
--- /dev/null
@@ -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 (file)
index 0000000..a84c1b5
--- /dev/null
@@ -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 (file)
index 0000000..d354f16
--- /dev/null
@@ -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" }
+}