Hi,

below is the patch for the buffer overlap checker in the analyzer. It
contains a working buffer overlap warning as well as most of the code
needed to also warn on the general case aka when arguments alias with an
argument passed to restrict-qualified parameter.

The current C standard draft states that aliases to restrict-qualified
parameters are defined behavior if neither the alias nor the
restrict-qualified parameter is written. We've not yet come to a
conclusion how to handle this and thus, the function doing the general
case check is not used. A possible call site has a TODO stating that and
the current limitations are documented in invoke.texi.

- Tim

This patch adds a new checker to complain about overlapping buffers on
calls to memcpy and mempcpy.

Regression-tested on Linux x86_64 and tested as usual on coreutils, curl,
httpd and openssh.

2022-08-21  Tim Lange  <m...@tim-lange.me>

gcc/analyzer/ChangeLog:

        PR analyzer/105898
        * analyzer.opt: Add Wanalyzer-restrict.
        * region-model-impl-calls.cc (region_model::impl_call_memcpy):
        Add call to region_model::check_region_overlap.
        (region_model::impl_call_mempcpy): New function.
        * region-model.cc (class restrict_alias): Concrete diagnostic to
        complain about the disregard of the restrict qualifier.
        (class region_overlap): Concrete diagnostic to complain about
        overlapping buffers.
        (region_model::check_region_aliases): New function.
        (region_model::check_region_overlap): New function.
        (region_model::on_call_pre):
        Add call to region_model::impl_call_mempcpy.
        * region-model.h (class region_model):
        Add check_region_aliases and check_region_overlap.
        * region.cc (region::unwrap_cast): New helper function.
        * region.h: Add region::unwrap_cast.
        * svalue.cc (svalue::unwrap_cast): New helper function.
        * svalue.h: Add svalue::unwrap_cast.

gcc/ChangeLog:

        PR analyzer/105898
        * doc/invoke.texi: Add Wanalyzer-restrict.

gcc/testsuite/ChangeLog:

        PR analyzer/105898
        * gcc.dg/analyzer/restrict-1.c: New test.

---
 gcc/analyzer/analyzer.opt                  |   4 +
 gcc/analyzer/region-model-impl-calls.cc    |  25 +-
 gcc/analyzer/region-model.cc               | 284 ++++++++++++++
 gcc/analyzer/region-model.h                |   8 +
 gcc/analyzer/region.cc                     |  11 +
 gcc/analyzer/region.h                      |   1 +
 gcc/analyzer/svalue.cc                     |  11 +
 gcc/analyzer/svalue.h                      |   1 +
 gcc/doc/invoke.texi                        |  17 +
 gcc/testsuite/gcc.dg/analyzer/restrict-1.c | 413 +++++++++++++++++++++
 10 files changed, 774 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/restrict-1.c

diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index 437ea92e130..ae5ebdb0d41 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -142,6 +142,10 @@ Wanalyzer-putenv-of-auto-var
 Common Var(warn_analyzer_putenv_of_auto_var) Init(1) Warning
 Warn about code paths in which an on-stack buffer is passed to putenv.
 
+Wanalyzer-restrict
+Common Var(warn_analyzer_restrict) Init(1) Warning
+Warn about code paths in which an argument passed to a restrict-qualified 
parameter aliases with another argument.
+
 Wanalyzer-shift-count-negative
 Common Var(warn_analyzer_shift_count_negative) Init(1) Warning
 Warn about code paths in which a shift with negative count is attempted.
diff --git a/gcc/analyzer/region-model-impl-calls.cc 
b/gcc/analyzer/region-model-impl-calls.cc
index 8eebd122d42..bc8e343643a 100644
--- a/gcc/analyzer/region-model-impl-calls.cc
+++ b/gcc/analyzer/region-model-impl-calls.cc
@@ -502,7 +502,6 @@ region_model::impl_call_malloc (const call_details &cd)
 }
 
 /* Handle the on_call_pre part of "memcpy" and "__builtin_memcpy".  */
-// TODO: complain about overlapping src and dest.
 
 void
 region_model::impl_call_memcpy (const call_details &cd)
@@ -516,6 +515,9 @@ region_model::impl_call_memcpy (const call_details &cd)
   const region *src_reg = deref_rvalue (src_ptr_sval, cd.get_arg_tree (1),
                                        cd.get_ctxt ());
 
+  check_region_overlap (src_reg, /* src_idx */ 1, dest_reg, /* dst_idx */ 0,
+                       num_bytes_sval, cd);
+
   cd.maybe_set_lhs (dest_ptr_sval);
 
   const region *sized_src_reg
@@ -527,6 +529,27 @@ region_model::impl_call_memcpy (const call_details &cd)
   set_value (sized_dest_reg, src_contents_sval, cd.get_ctxt ());
 }
 
+/* Handle the on_call_pre part of "mempcpy" and "__builtin_mempcpy".  */
+
+void
+region_model::impl_call_mempcpy (const call_details &cd)
+{
+  const svalue *dest_ptr_sval = cd.get_arg_svalue (0);
+  const svalue *src_ptr_sval = cd.get_arg_svalue (1);
+  const svalue *num_bytes_sval = cd.get_arg_svalue (2);
+
+  const region *dest_reg = deref_rvalue (dest_ptr_sval, cd.get_arg_tree (0),
+                                        cd.get_ctxt ());
+  const region *src_reg = deref_rvalue (src_ptr_sval, cd.get_arg_tree (1),
+                                       cd.get_ctxt ());
+
+  check_region_for_write (dest_reg, cd.get_ctxt ());
+  check_region_overlap (src_reg, /* src_idx */ 1, dest_reg, /* dst_idx */ 0,
+                       num_bytes_sval, cd);
+
+  /* TODO: model the return value behavior.  */
+}
+
 /* Handle the on_call_pre part of "memset" and "__builtin_memset".  */
 
 void
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index ec29be259b5..0c9d8833ccf 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -1676,6 +1676,277 @@ void region_model::check_region_bounds (const region 
*reg,
     }
 }
 
+/* Concrete subclass of pending_diagnostic_subclass complaining about arguments
+   passed to restrict-qualified parameters aliasing with another argument.  */
+
+class restrict_alias_diagnostic
+  : public pending_diagnostic_subclass<restrict_alias_diagnostic>
+{
+public:
+  restrict_alias_diagnostic (tree src_tree, unsigned src_idx,
+                            tree dst_tree, unsigned dst_idx, tree fndecl)
+  : m_src_idx (src_idx), m_dst_idx (dst_idx), m_fndecl (fndecl)
+  {
+    m_src_tree = fixup_tree_for_diagnostic (src_tree);
+    m_dst_tree = fixup_tree_for_diagnostic (dst_tree);
+  }
+
+  const char *get_kind () const final override
+  {
+    return "restrict_diagnostic";
+  }
+
+  bool operator== (const restrict_alias_diagnostic &other) const
+  {
+    return m_src_idx == other.m_src_idx && m_dst_idx == other.m_dst_idx
+          && pending_diagnostic::same_tree_p (m_src_tree, other.m_src_tree)
+          && pending_diagnostic::same_tree_p (m_dst_tree, other.m_dst_tree)
+          && pending_diagnostic::same_tree_p (m_fndecl, other.m_fndecl);
+  }
+
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_restrict;
+  }
+
+  bool emit (rich_location *rich_loc) override
+  {
+    diagnostic_metadata m;
+    bool warned = warning_meta (rich_loc, m, get_controlling_option (),
+                               "argument %u passed to %<restrict%>-qualified"
+                               " parameter aliases with argument %u",
+                               m_src_idx + 1, m_dst_idx + 1);
+
+    if (warned)
+      inform (DECL_SOURCE_LOCATION (m_fndecl), "declared here");
+
+    return warned;
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) override
+  {
+    if (m_src_tree && m_dst_tree)
+      return ev.formatted_print ("%qE and %qE point to the same memory"
+                                " location", m_src_tree, m_dst_tree);
+    return ev.formatted_print ("argument %u and %u point to the same location",
+                              m_src_idx + 1, m_dst_idx + 1);
+  }
+
+protected:
+  tree m_src_tree;
+  unsigned m_src_idx;
+  tree m_dst_tree;
+  unsigned m_dst_idx;
+  tree m_fndecl;
+};
+
+/* Concrete subclass of restrict_alias to warn on the special case where a
+   number of bytes are copied and the buffers shall not overlap.  */
+
+class region_overlap_diagnostic : public restrict_alias_diagnostic
+{
+public:
+  region_overlap_diagnostic (tree src_tree, unsigned src_idx,
+                            tree dst_tree, unsigned dst_idx, tree num,
+                            tree overlapping_bytes, tree fndecl)
+  : restrict_alias_diagnostic (src_tree, src_idx, dst_tree, dst_idx, fndecl),
+    m_num (num), m_overlapping_bytes (overlapping_bytes)
+  {}
+
+  bool operator== (const region_overlap_diagnostic &other) const
+  {
+    return restrict_alias_diagnostic::operator== (other)
+          && pending_diagnostic::same_tree_p (m_num, other.m_num)
+          && pending_diagnostic::same_tree_p (m_overlapping_bytes,
+                                              other.m_overlapping_bytes);
+  }
+
+  bool emit (rich_location *rich_loc) final override
+  {
+    diagnostic_metadata m;
+    if (m_fndecl)
+      {
+       bool warned = warning_meta (rich_loc, m, get_controlling_option (),
+                                   "calling %qE with overlapping buffers"
+                                   " results in undefined behavior",
+                                   m_fndecl);
+       if (warned)
+         inform (rich_loc->get_loc (),
+                 "use %<memmove%> instead of %qE with overlapping buffers",
+                 m_fndecl);
+       return warned;
+      }
+
+    bool warned = warning_meta (rich_loc, m, get_controlling_option (),
+                               "calling with overlapping buffers"
+                               " results in undefined behavior");
+    if (warned)
+      inform (rich_loc->get_loc (),
+             "use %<memmove%> instead with overlapping buffers");
+    return warned;
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev)
+  final override
+  {
+    const char *unit = integer_onep (m_overlapping_bytes) ? "byte" : "bytes";
+    if (m_num && m_src_tree && m_dst_tree)
+      return ev.formatted_print ("copying %E bytes from %qE to %qE overlaps by"
+                                " %E %s",
+                                m_num, m_src_tree, m_dst_tree,
+                                m_overlapping_bytes, unit);
+    return ev.formatted_print ("copying %E bytes from argument %u to argument"
+                              " %u overlaps by %E %s",
+                              m_num, m_src_idx + 1, m_dst_idx + 1,
+                              m_overlapping_bytes, unit);
+  }
+
+private:
+  tree m_num;
+  tree m_overlapping_bytes;
+};
+
+/* Check whether RQ_PARAM and OTHER_PARAM point to the same memory location
+   and might emit a warning.
+
+   For correct output in the diagnostics, provide the region of the
+   restrict-qualified parameter in RQ_PARAM and the other parameter in
+   OTHER_PARAM.  RQ_PARAM_IDX and OTHER_PARAM_IDX are 0-based indices to
+   retrieve the argument from CD.  */
+
+void region_model::check_region_aliases (const region *rq_param,
+                                        unsigned rq_param_idx,
+                                        const region *other_param,
+                                        unsigned other_param_idx,
+                                        const call_details &cd) const
+{
+  /* Do not warn again if Wrestrict already warned at this statement.  */
+  if (warning_suppressed_p (cd.get_call_stmt (), OPT_Wrestrict))
+    return;
+
+  region_model_context *ctxt = cd.get_ctxt ();
+  if (!ctxt)
+    return;
+
+  /* Remove possibly wrapping casts and check whether SRC and DST are equal
+     and not symbolic.  */
+  rq_param = rq_param->unwrap_cast ();
+  other_param = other_param->unwrap_cast ();
+  if (rq_param == other_param && !rq_param->symbolic_p ())
+    {
+      tree rq_param_tree = cd.get_arg_tree (rq_param_idx);
+      tree other_param_tree = cd.get_arg_tree (other_param_idx);
+      ctxt->warn (new restrict_alias_diagnostic (rq_param_tree, rq_param_idx,
+                                                other_param_tree,
+                                                other_param_idx,
+                                                cd.get_fndecl_for_call ()));
+    }
+}
+
+/* Checks whether SRC and DST overlap and might emit a warning.
+
+   src_idx and dst_idx are 0-based indices to retrieve the argument from CD.
+   NUM_BYTES_SVAL is the number of bytes that are copied starting from SRC,
+   i.e. the third argument of memcpy.  If NUM_BYTES_SVAL is non-constant, it
+   falls back to check whether SRC and DST point to the same location.  */
+
+void region_model::check_region_overlap (const region *src,
+                                        unsigned src_idx,
+                                        const region *dst,
+                                        unsigned dst_idx,
+                                        const svalue *num_bytes_sval,
+                                        const call_details &cd) const
+{
+  gcc_assert (num_bytes_sval);
+
+  /* Do not warn again if Wrestrict already warned at this statement.  */
+  if (warning_suppressed_p (cd.get_call_stmt (), OPT_Wrestrict))
+    return;
+
+  region_model_context *ctxt = cd.get_ctxt ();
+  if (!ctxt)
+    return;
+
+  num_bytes_sval = num_bytes_sval->unwrap_cast ();
+  if (tree num_bytes_tree = num_bytes_sval->maybe_get_constant ())
+    {
+      /* Bail out if the constant is no integer.  */
+      if (!INTEGRAL_TYPE_P (TREE_TYPE (num_bytes_tree)))
+       return;
+      byte_size_t num_bytes = TREE_INT_CST_LOW (num_bytes_tree);
+
+      region_offset src_offset = src->get_offset ();
+      region_offset dst_offset = dst->get_offset ();
+      const region *src_base_reg = src_offset.get_base_region ();
+      const region *dst_base_reg = dst_offset.get_base_region ();
+
+      /* Check that the base_regions are the same, not symbolic and that
+        both offsets are also not symbolic.  */
+      if (src_base_reg == dst_base_reg && !src_base_reg->symbolic_p ()
+               && !src_offset.symbolic_p () && !dst_offset.symbolic_p ())
+       {
+         /* It is prohibited to get the pointer address of bit fields, thus
+           we can assume that the offset here is always a multiple of 8.  */
+         byte_size_t src_byte_offset
+           = src_offset.get_bit_offset () >> LOG2_BITS_PER_UNIT;
+         byte_size_t dst_byte_offset
+           = dst_offset.get_bit_offset () >> LOG2_BITS_PER_UNIT;
+
+         /* If the SRC pointer < DST pointer, we need to check that
+           SRC + NUM_BYTES_SVAL is still before the beginning of DST.
+           Overlapping buffers would yield unexpected behavior if the
+           implementation copies in the forward direction.
+
+           Otherwise, vice versa.  */
+         byte_range src_range (src_byte_offset, num_bytes);
+         byte_range dst_range (dst_byte_offset, num_bytes);
+         byte_size_t num_overlap_bytes;
+         /* Emit a warning if the buffers overlap.  */
+         if (src_range.intersects_p (dst_range, &num_overlap_bytes))
+           {
+             tree src_tree = cd.get_arg_tree (src_idx);
+             tree dst_tree = cd.get_arg_tree (dst_idx);
+             tree num_overlap_tree
+               = wide_int_to_tree (size_type_node, num_overlap_bytes);
+             tree fndecl = cd.get_fndecl_for_call ();
+             ctxt->warn (new region_overlap_diagnostic (src_tree, src_idx,
+                                                        dst_tree, dst_idx,
+                                                        num_bytes_tree,
+                                                        num_overlap_tree,
+                                                        fndecl));
+           }
+       }
+    }
+  else if (const widening_svalue *w_sval
+             = dyn_cast <const widening_svalue *> (num_bytes_sval))
+    {
+      /* Recheck for both values of widening_svalue.  */
+      region_model::check_region_overlap (src, src_idx, dst, dst_idx,
+                                         w_sval->get_base_svalue (), cd);
+      region_model::check_region_overlap (src, src_idx, dst, dst_idx,
+                                         w_sval->get_iter_svalue (), cd);
+    }
+  else
+    {
+      /* Fall back and only check for aliases.  */
+      src = src->unwrap_cast ();
+      dst = dst->unwrap_cast ();
+      if (src == dst && !src->symbolic_p ())
+       {
+         tree src_tree = cd.get_arg_tree (src_idx);
+         tree dst_tree = cd.get_arg_tree (dst_idx);
+         /* The number of copied bytes is equal to the overlapping bytes.  */
+         tree num_bytes_tree = get_representative_tree (num_bytes_sval);
+         tree fndecl = cd.get_fndecl_for_call ();
+         ctxt->warn (new region_overlap_diagnostic (src_tree, src_idx,
+                                                   dst_tree, dst_idx,
+                                                   num_bytes_tree,
+                                                   num_bytes_tree,
+                                                   fndecl));
+       }
+    }
+}
+
 /* Ensure that all arguments at the call described by CD are checked
    for poisoned values, by calling get_rvalue on each argument.  */
 
@@ -1844,6 +2115,10 @@ region_model::on_call_pre (const gcall *call, 
region_model_context *ctxt,
          case BUILT_IN_MEMCPY_CHK:
            impl_call_memcpy (cd);
            return false;
+         case BUILT_IN_MEMPCPY:
+         case BUILT_IN_MEMPCPY_CHK:
+           impl_call_mempcpy (cd);
+           return false;
          case BUILT_IN_MEMSET:
          case BUILT_IN_MEMSET_CHK:
            impl_call_memset (cd);
@@ -1991,6 +2266,15 @@ region_model::on_call_pre (const gcall *call, 
region_model_context *ctxt,
               && (!(callee_fndecl_flags & (ECF_CONST | ECF_PURE)))
               && !fndecl_built_in_p (callee_fndecl))
        unknown_side_effects = true;
+
+      /* TODO: Check for function calls that any restrict-qualified parameter
+              does not alias with another parameter with the
+              region_model::check_region_aliases method.
+
+              The C standard states that aliases to restrict-qualified
+              parameters are defined behavior if neither the alias nor the
+              restrict-qualified parameter is written.  We did not come to a
+              conclusion on how to handle this case yet.  */
     }
   else
     unknown_side_effects = true;
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 7ce832f6ce4..0a6fd431876 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -629,6 +629,7 @@ class region_model
   void impl_call_free (const call_details &cd);
   void impl_call_malloc (const call_details &cd);
   void impl_call_memcpy (const call_details &cd);
+  void impl_call_mempcpy (const call_details &cd);
   void impl_call_memset (const call_details &cd);
   void impl_call_putenv (const call_details &cd);
   void impl_call_realloc (const call_details &cd);
@@ -873,6 +874,13 @@ class region_model
                          region_model_context *ctxt) const;
   void check_region_bounds (const region *reg, enum access_direction dir,
                            region_model_context *ctxt) const;
+  void check_region_aliases (const region *src, unsigned src_idx,
+                            const region *dst, unsigned dst_idx,
+                            const call_details &cd) const;
+  void check_region_overlap (const region *src, unsigned src_idx,
+                            const region *dst, unsigned dst_idx,
+                            const svalue *num_sval,
+                            const call_details &cd) const;
 
   void check_call_args (const call_details &cd) const;
   void check_external_function_for_access_attr (const gcall *call,
diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc
index f4aba6b9c88..6408ce5bbf2 100644
--- a/gcc/analyzer/region.cc
+++ b/gcc/analyzer/region.cc
@@ -275,6 +275,17 @@ region::can_have_initial_svalue_p () const
     }
 }
 
+/* If this region is a cast_region, return the original region.
+   Otherwise, return this region.  */
+
+const region *
+region::unwrap_cast () const
+{
+  if (const cast_region *cast_reg = dyn_cast_cast_region ())
+    return cast_reg->get_original_region ();
+  return this;
+}
+
 /* If this region is a decl_region, return the decl.
    Otherwise return NULL.  */
 
diff --git a/gcc/analyzer/region.h b/gcc/analyzer/region.h
index d37584b7285..82d655e619e 100644
--- a/gcc/analyzer/region.h
+++ b/gcc/analyzer/region.h
@@ -155,6 +155,7 @@ public:
   const frame_region *maybe_get_frame_region () const;
   enum memory_space get_memory_space () const;
   bool can_have_initial_svalue_p () const;
+  const region *unwrap_cast () const;
 
   tree maybe_get_decl () const;
 
diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc
index f5a5f1c9697..6e3461fd2be 100644
--- a/gcc/analyzer/svalue.cc
+++ b/gcc/analyzer/svalue.cc
@@ -153,6 +153,17 @@ svalue::unwrap_any_unmergeable () const
   return this;
 }
 
+/* If this svalue is a cast (i.e a unaryop NOP_EXPR or VIEW_CONVERT_EXPR),
+   return the underlying svalue.
+   Otherwise return this svalue.  */
+
+const svalue *
+svalue::unwrap_cast () const
+{
+  const svalue *sval = maybe_undo_cast ();
+  return sval ? sval : this;
+}
+
 /* Attempt to merge THIS with OTHER, returning the merged svalue.
    Return NULL if not mergeable.  */
 
diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h
index f4cab0d4134..92e6917f360 100644
--- a/gcc/analyzer/svalue.h
+++ b/gcc/analyzer/svalue.h
@@ -139,6 +139,7 @@ public:
   const region *maybe_get_region () const;
   const svalue *maybe_undo_cast () const;
   const svalue *unwrap_any_unmergeable () const;
+  const svalue *unwrap_cast () const;
 
   const svalue *can_merge_p (const svalue *other,
                              region_model_manager *mgr,
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index f65d351a5fc..d88ede653c0 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -463,6 +463,7 @@ Objective-C and Objective-C++ Dialects}.
 -Wno-analyzer-possible-null-argument @gol
 -Wno-analyzer-possible-null-dereference @gol
 -Wno-analyzer-putenv-of-auto-var @gol
+-Wno-analyzer-restrict @gol
 -Wno-analyzer-shift-count-negative @gol
 -Wno-analyzer-shift-count-overflow @gol
 -Wno-analyzer-stale-setjmp-buffer @gol
@@ -9769,6 +9770,7 @@ Enabling this option effectively enables the following 
warnings:
 -Wanalyzer-possible-null-argument @gol
 -Wanalyzer-possible-null-dereference @gol
 -Wanalyzer-putenv-of-auto-var @gol
+-Wanalyzer-restrict @gol
 -Wanalyzer-shift-count-negative @gol
 -Wanalyzer-shift-count-overflow @gol
 -Wanalyzer-stale-setjmp-buffer @gol
@@ -10072,6 +10074,21 @@ or an on-stack buffer.
 
 See @uref{https://wiki.sei.cmu.edu/confluence/x/6NYxBQ, POS34-C. Do not call 
putenv() with a pointer to an automatic variable as the argument}.
 
+@item -Wno-analyzer-restrict
+@opindex Wanalyzer-restrict
+@opindex Wno-analyzer-restrict
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-restrict} to disable it.
+
+This diagnostic warns for paths through the code in which the destination
+and source arguments passed to @code{memcpy} and @code{mempcpy} overlap.
+
+This diagnostic similar to @option{-Wrestrict} but inside the analyzer.
+The region model of the analyzer allows it to catch more bugs than
+@option{-Wrestrict} in presence of aliases and interprocedural flows.
+Note that this diagnostic, unlike @option{-Wrestrict}, does only warn on
+calls to @code{memcpy} and @code{mempcpy}.
+
 @item -Wno-analyzer-shift-count-negative
 @opindex Wanalyzer-shift-count-negative
 @opindex Wno-analyzer-shift-count-negative
diff --git a/gcc/testsuite/gcc.dg/analyzer/restrict-1.c 
b/gcc/testsuite/gcc.dg/analyzer/restrict-1.c
new file mode 100644
index 00000000000..8d6732ce602
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/restrict-1.c
@@ -0,0 +1,413 @@
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+/* Wanalyzer-restrict tests for memcpy.  */
+
+/* Avoid folding of memcpy.  */
+typedef void * (*memcpy_t) (void *dst, const void *src, size_t n);
+
+static memcpy_t __attribute__((noinline))
+get_memcpy (void)
+{
+  return memcpy;
+}
+
+static memcpy_t __attribute__((noinline))
+get_mempcpy (void)
+{
+  return mempcpy;
+}
+
+/* element_region & decl_region.  */
+
+void test1 (void)
+{
+  memcpy_t fn = get_memcpy ();
+
+  int32_t buf[4] = {0};
+  fn (&buf[2], buf, 2 * sizeof(int32_t));
+}
+
+void test2 (void)
+{
+  memcpy_t fn = get_memcpy ();
+
+  int32_t buf[4] = {0};
+  fn (&buf[1], buf, 2 * sizeof(int32_t)); /* { dg-line test2 } */
+
+  /* { dg-warning "calling 'memcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test2 } */
+  /* { dg-message "copying 8 bytes from '&buf' to '&buf\\\[1\\\]' overlaps by 
4 bytes" "note" { target *-*-* } test2 } */
+  /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" 
"note" { target *-*-* } test2 } */
+}
+
+/* element_region & element_region.  */
+
+void test3 (void)
+{
+  memcpy_t fn = get_memcpy ();
+
+  int32_t buf[4] = {0};
+  fn (&buf[2], &buf[0], 2 * sizeof(int32_t));
+}
+
+void test4 (void)
+{
+  memcpy_t fn = get_memcpy ();
+
+  int32_t buf[4] = {0};
+  fn (&buf[1], &buf[0], 2 * sizeof(int32_t)); /* { dg-line test4 } */
+
+  /* { dg-warning "calling 'memcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test4 } */
+  /* { dg-message "copying 8 bytes from '&buf\\\[0\\\]' to '&buf\\\[1\\\]' 
overlaps by 4 bytes" "note" { target *-*-* } test4 } */
+  /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" 
"note" { target *-*-* } test4 } */
+}
+
+/* offset_region & decl_region.  */
+
+void test5 (void)
+{
+  memcpy_t fn = get_mempcpy ();
+
+  int32_t buf[4] = {0};
+  fn (buf + 2, buf, 2 * sizeof(int32_t));
+}
+
+void test6 (void)
+{
+  memcpy_t fn = get_memcpy ();
+
+  int32_t buf[4] = {0};
+  fn (buf + 1, buf, 2 * sizeof(int32_t)); /* { dg-line test6 } */
+
+  /* { dg-warning "calling 'memcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test6 } */
+  /* { dg-message "copying 8 bytes from '&buf' to '&buf \\\+ 4' overlaps by 4 
bytes" "note" { target *-*-* } test6 } */
+  /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" 
"note" { target *-*-* } test6 } */
+}
+
+/* offset_region & offset_region.  */
+
+void test7 (void)
+{
+  memcpy_t fn = get_memcpy ();
+
+  int32_t buf[5] = {0};
+  fn (buf + 3, buf + 1, 2 * sizeof(int32_t));
+}
+
+void test8 (void)
+{
+  memcpy_t fn = get_mempcpy ();
+
+  int32_t buf[4] = {0};
+  fn (buf + 3, buf + 2, 2 * sizeof(int32_t)); /* { dg-line test8 } */
+
+  /* { dg-warning "calling 'mempcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test8 } */
+  /* { dg-message "copying 8 bytes from '&buf \\\+ 8' to '&buf \\\+ 12' 
overlaps by 4 bytes" "note" { target *-*-* } test8 } */
+  /* { dg-message "use 'memmove' instead of 'mempcpy' with overlapping 
buffers" "note" { target *-*-* } test8 } */
+}
+
+/* element_region & heap_allocated_region.  */
+
+void test9 (void)
+{
+  memcpy_t fn = get_memcpy ();
+
+  int32_t *buf = malloc (4 * sizeof (int32_t));
+  if (!buf)
+    return;
+  fn (&buf[2], buf, 2 * sizeof(int32_t));
+  free (buf);
+}
+
+void test10 (void)
+{
+  memcpy_t fn = get_memcpy ();
+
+  int32_t *buf = malloc (4 * sizeof (int32_t));
+  if (!buf)
+    return;
+  fn (&buf[1], buf, 2 * sizeof(int32_t)); /* { dg-line test10 } */
+  free (buf);
+
+  /* { dg-warning "calling 'memcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test10 } */
+  /* { dg-message "copying 8 bytes from 'buf' to 'buf \\\+ 4' overlaps by 4 
bytes" "note" { target *-*-* } test10 } */
+  /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" 
"note" { target *-*-* } test10 } */
+}
+
+/* offset_region & heap_allocated_region.  */
+
+void test11 (void)
+{
+  memcpy_t fn = get_mempcpy ();
+
+  int32_t *buf = malloc (4 * sizeof (int32_t));
+  if (!buf)
+    return;
+  fn (buf + 2, buf, 2 * sizeof(int32_t));
+  free (buf);
+}
+
+void test12 (void)
+{
+  memcpy_t fn = get_memcpy ();
+
+  int32_t *buf = malloc (4 * sizeof (int32_t));
+  if (!buf)
+    return;
+  fn (buf + 1, buf, 2 * sizeof(int32_t)); /* { dg-line test12 } */
+  free (buf);
+
+  /* { dg-warning "calling 'memcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test12 } */
+  /* { dg-message "copying 8 bytes from 'buf' to 'buf \\\+ 4' overlaps by 4 
bytes" "note" { target *-*-* } test12 } */
+  /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" 
"note" { target *-*-* } test12 } */
+}
+
+/* aliased region.  */
+
+void test13 (void)
+{
+  memcpy_t fn = get_memcpy ();
+  
+  int32_t buf[4] = {0};
+  void *view = buf;
+  fn (view, buf, 2 * sizeof(int32_t)); /* { dg-line test13 } */
+
+  /* { dg-warning "calling 'memcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test13 } */
+  /* { dg-message "copying 8 bytes from '&buf' to 'view' overlaps by 8 bytes" 
"note" { target *-*-* } test13 } */
+  /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" 
"note" { target *-*-* } test13 } */
+}
+
+void test14 (void)
+{
+  memcpy_t fn = get_memcpy ();
+  
+  int32_t buf[4] = {0};
+  void *view = buf;
+  fn (view + 2 * sizeof(int32_t), buf, 2 * sizeof(int32_t));
+}
+
+void test15 (void)
+{
+  memcpy_t fn = get_memcpy ();
+  
+  int32_t buf[4] = {0};
+  void *view = buf;
+  fn (view + sizeof(int32_t), buf, 2 * sizeof(int32_t)); /* { dg-line test15 } 
*/
+
+  /* { dg-warning "calling 'memcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test15 } */
+  /* { dg-message "copying 8 bytes from '&buf' to 'view \\\+ 4' overlaps by 4 
bytes" "note" { target *-*-* } test15 } */
+  /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" 
"note" { target *-*-* } test15 } */
+}
+
+void test16 (void)
+{
+  memcpy_t fn = get_mempcpy ();
+
+  int16_t buf[2];
+  int32_t *view = (int32_t *) buf;
+  fn (view, buf, sizeof (int32_t)); /* { dg-line test16 } */
+
+  /* { dg-warning "calling 'mempcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test16 } */
+  /* { dg-message "copying 4 bytes from '&buf' to 'view' overlaps by 4 bytes" 
"note" { target *-*-* } test16 } */
+  /* { dg-message "use 'memmove' instead of 'mempcpy' with overlapping 
buffers" "note" { target *-*-* } test16 } */
+}
+
+void test17 (void)
+{
+  memcpy_t fn = get_memcpy ();
+
+  int16_t buf[4];
+  int32_t *view = (int32_t *) buf;
+  fn (&view[1], buf, sizeof (int32_t));
+}
+
+/* field_region.  */
+
+struct my_struct {
+  int32_t i[4];
+};
+
+void test18 (void)
+{
+  memcpy_t fn = get_memcpy ();
+
+  struct my_struct s;
+  for (int i = 0; i < 4; i++)
+    s.i[i] = i;
+  fn (s.i + 2, s.i, 2 * sizeof (int32_t));
+}
+
+void test19 (void)
+{
+  memcpy_t fn = get_memcpy ();
+
+  struct my_struct s;
+  for (int i = 0; i < 4; i++)
+    s.i[i] = i;
+  fn (s.i + 1, s.i, 2 * sizeof (int32_t)); /* { dg-line test19 } */
+
+  /* { dg-warning "calling 'memcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test19 } */
+  /* { dg-message "copying 8 bytes from '&s.i' to '&s.i \\\+ 4' overlaps by 4 
bytes" "note" { target *-*-* } test19 } */
+  /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" 
"note" { target *-*-* } test19 } */
+}
+
+void test20 (int cond)
+{
+  memcpy_t fn = get_memcpy ();
+
+  int32_t buf[4];
+  int32_t n;
+  if (cond)
+    n = 2 * sizeof (int32_t);
+  else
+    n = 3 * sizeof (int32_t);
+  fn (buf + 2, buf, n); /* { dg-line test20 } */
+
+  /* { dg-warning "calling 'memcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test20 } */
+  /* { dg-message "copying 12 bytes from '&buf' to '&buf \\\+ 8' overlaps by 4 
bytes" "note" { target *-*-* } test20 } */
+  /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" 
"note" { target *-*-* } test20 } */
+}
+
+struct nested_struct {
+  struct my_struct *ptr;
+};
+
+void test21 (void)
+{
+  memcpy_t fn = get_memcpy ();
+
+  struct my_struct first;
+  memset (first.i, 0, sizeof (first.i));
+  struct my_struct second;
+  memset (second.i, 0, sizeof (second.i));
+
+  struct my_struct arr[2];
+  arr[0] = first;
+  arr[1] = second;
+  struct nested_struct ns;
+  ns.ptr = arr;
+
+  fn (&ns.ptr[0], &ns.ptr[1], 3 * sizeof (int32_t));
+  fn (ns.ptr, ((char *) ns.ptr) + 2 * sizeof (int32_t), 2 * sizeof (int32_t));
+  fn (ns.ptr, ((char *) ns.ptr) + 2 * sizeof (int32_t), 3 * sizeof (int32_t)); 
/* { dg-line test21a } */
+  fn (ns.ptr[0].i, ns.ptr[1].i, 3 * sizeof (int32_t));
+  fn (ns.ptr[0].i, ns.ptr[0].i + 2, 2 * sizeof (int32_t));
+  fn (ns.ptr[0].i, ns.ptr[0].i + 2, 3 * sizeof (int32_t)); /* { dg-line 
test21b } */
+
+  /* { dg-warning "calling 'memcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test21a } */
+  /* { dg-message "copying 12 bytes from" "note" { target *-*-* } test21a } */
+  /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" 
"note" { target *-*-* } test21a } */
+  /* { dg-warning "calling 'memcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test21b } */
+  /* { dg-message "copying 12 bytes from" "note" { target *-*-* } test21b } */
+  /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" 
"note" { target *-*-* } test21b } */
+}
+
+/* 35 bits aka more than 4 bytes.  */
+struct bit_struct {
+  unsigned int a : 7;
+  unsigned int b : 7;
+  unsigned int c : 7;
+  unsigned int d : 7;
+  unsigned int e : 7;
+};
+
+void test22 (void)
+{
+  memcpy_t fn = get_memcpy ();
+  struct bit_struct bs;
+  memset (&bs, 0, sizeof (bs));
+
+  char *ptr = (char *) &bs;
+  fn (ptr, ptr + 2, 2);
+  fn (ptr + 2, ptr, 3); /* { dg-line test22 } */
+
+  /* { dg-warning "calling 'memcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test22 } */
+  /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" 
"note" { target *-*-* } test22 } */
+  /* { dg-message "copying 3 bytes from 'ptr' to 'ptr \\\+ 2' overlaps by 1 
byte\\n" "note" { target *-*-* } test22 } */
+}
+
+/* Union.  */
+
+union my_union {
+  int16_t s;
+  int32_t m;
+};
+
+void test23 (void)
+{
+  memcpy_t fn = get_memcpy ();
+  
+  union my_union u;
+  void *ptr = (void *) &u;
+  fn (ptr + 2, &u, 2);
+}
+
+void test24 (void)
+{
+  memcpy_t fn = get_memcpy ();
+  
+  union my_union u;
+  void *ptr = (void *) &u;
+  fn (ptr + 1, &u, 2); /* { dg-line test24 } */
+
+  /* { dg-warning "calling 'memcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test24 } */
+  /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" 
"note" { target *-*-* } test24 } */
+  /* { dg-message "copying 2 bytes from '&u' to 'ptr \\\+ 1' overlaps by 1 
byte\\n" "note" { target *-*-* } test24 } */
+}
+
+/* Test fallback.  */
+
+void test25 (int n)
+{
+  memcpy_t fn = get_memcpy ();
+
+  int32_t buf[4];
+  fn (buf, buf, 2 * n); /* { dg-line test25 } */
+
+  /* { dg-warning "calling 'memcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test25 } */
+  /* { dg-message "copying n \\\* 2 bytes from '&buf' to '&buf' overlaps by n 
\\\* 2 bytes" "note" { target *-*-* } test25 } */
+  /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" 
"note" { target *-*-* } test25 } */
+}
+
+void test26 (int n)
+{
+  memcpy_t fn = get_memcpy ();
+
+  int32_t buf[4];
+  void *alias = buf;
+  fn (alias, buf, 2 * n); /* { dg-line test26 } */
+
+  /* { dg-warning "calling 'memcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test26 } */
+  /* { dg-message "copying n \\\* 2 bytes from '&buf' to 'alias' overlaps by n 
\\\* 2 bytes" "note" { target *-*-* } test26 } */
+  /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" 
"note" { target *-*-* } test26 } */
+}
+
+void test27 (int n)
+{
+  memcpy_t fn = get_memcpy ();
+
+  int buf[4];
+  int *alias = buf;
+  fn (alias + 1, buf, 2 * n);
+}
+
+/* Interprocedural.  */
+
+void __attribute__((noinline)) memcpy_wrapper (void *dst, void *src, size_t 
count)
+{
+  memcpy_t fn = get_memcpy ();
+
+  fn (dst, src, count); /* { dg-line test28 } */
+
+  /* { dg-warning "calling 'memcpy' with overlapping buffers results in 
undefined behavior" "warning" { target *-*-* } test28 } */
+  /* { dg-message "copying 12 bytes from 'src' to 'dst' overlaps by 4 bytes" 
"note" { target *-*-* } test28 } */
+  /* { dg-message "use 'memmove' instead of 'memcpy' with overlapping buffers" 
"note" { target *-*-* } test28 } */
+}
+
+void test28 (void)
+{
+  int32_t buf[5];
+  memcpy_wrapper (buf + 2, buf, 3 * sizeof (int32_t));
+}
-- 
2.37.2

Reply via email to