PR 93519 reports a false positive -Wrestrict issued for an inlined call
to strcpy that carefully guards against self-copying.  This is caused
by the caller's arguments substituted into the call during inlining and
before dead code elimination.

The attached patch avoids this by removing -Wrestrict from the folder
and deferring folding perfectly overlapping (and so undefined) calls
to strcpy (and mempcpy, but not memcpy) until much later.  Calls to
perfectly overlapping calls to memcpy are still folded early.

Tested on x86_64-linux.

Martin
PR middle-end/93519 - bogus -Wrestrict for strcpy(d, s) call guarded by d != s

gcc/testsuite/ChangeLog:

	PR middle-end/93519
	* gcc.dg/Wrestrict-14.c: Remove an xfail.
	* gcc.dg/Wrestrict-21.c: New test.
	* gcc.dg/Wrestrict-22.c: New test.
	* gcc.dg/strlenopt-92.c: New test.
	* gcc.dg/strlenopt.h: Declare more functions.

gcc/ChangeLog:

	PR middle-end/93519
	* builtins.c (expand_builtin_mempcpy): Diagnose perfectly overlapping
	copies and return destination.
	(expand_builtin_strcpy): Same.  Defer declaring locals until their
	initial value is known for better readability.
	(expand_builtin_strncpy): Same.
	* gimple-fold.c (gimple_fold_builtin_memory_op): Avoid folding
	perfectly overlapping calls to mempcpy.
	(gimple_fold_builtin_strcpy): Avoid folding perfectly overlapping
	calls..
	* gimple-ssa-warn-restrict.c (maybe_diag_equal_operands): New function.
	(check_bounds_or_overlap): Call it.
	* gimple-ssa-warn-restrict.h (maybe_diag_equal_operands): Declare.
	* tree-ssa-strlen.c (maybe_handle_store_to_self): New function.
	(handle_builtin_strcpy): Call it.
	(handle_builtin_memcpy): Same.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index e4a8694054e..46cb3f8ae10 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -4253,6 +4253,22 @@ expand_builtin_mempcpy (tree exp, rtx target)
   if (!check_memop_access (exp, dest, src, len))
     return NULL_RTX;
 
+  if (operand_equal_p (dest, src, 0))
+    {
+      if (!TREE_NO_WARNING (exp))
+	{
+	  location_t loc = EXPR_LOCATION (exp);
+	  tree func = get_callee_fndecl (exp);
+	  warning_at (loc, OPT_Wrestrict,
+			  "%qD source argument is the same as destination",
+		      func);
+	}
+
+      /* Replace perfectly overlapping calls with DST + LEN.  */
+      tree res = fold_build2 (POINTER_PLUS_EXPR, TREE_TYPE (dest), dest, len);
+      return expand_normal (res);
+    }
+
   return expand_builtin_mempcpy_args (dest, src, len,
 				      target, exp, /*retmode=*/ RETURN_END);
 }
@@ -4474,6 +4490,21 @@ expand_builtin_strcpy (tree exp, rtx target)
 		    src, destsize);
     }
 
+  if (operand_equal_p (dest, src, 0))
+    {
+      if (!TREE_NO_WARNING (exp))
+	{
+	  location_t loc = EXPR_LOCATION (exp);
+	  tree func = get_callee_fndecl (exp);
+	  warning_at (loc, OPT_Wrestrict,
+			  "%qD source argument is the same as destination",
+		      func);
+	}
+
+      /* Replace perfectly overlapping calls with the destination.  */
+      return expand_normal (dest);
+    }
+
   if (rtx ret = expand_builtin_strcpy_args (exp, dest, src, target))
     {
       /* Check to see if the argument was declared attribute nonstring
@@ -4810,6 +4841,22 @@ expand_builtin_strncpy (tree exp, rtx target)
     return NULL_RTX;
   tree dest = CALL_EXPR_ARG (exp, 0);
   tree src = CALL_EXPR_ARG (exp, 1);
+
+  if (operand_equal_p (dest, src, 0))
+    {
+      if (!TREE_NO_WARNING (exp))
+	{
+	  location_t loc = EXPR_LOCATION (exp);
+	  tree func = get_callee_fndecl (exp);
+	  warning_at (loc, OPT_Wrestrict,
+			  "%qD source argument is the same as destination",
+		      func);
+	}
+
+      /* Replace perfectly overlapping calls with the destination.  */
+      return expand_normal (dest);
+    }
+
   /* The number of bytes to write (not the maximum).  */
   tree len = CALL_EXPR_ARG (exp, 2);
 
diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c
index ed225922269..50b1d627760 100644
--- a/gcc/gimple-fold.c
+++ b/gcc/gimple-fold.c
@@ -725,6 +725,12 @@ gimple_fold_builtin_memory_op (gimple_stmt_iterator *gsi,
      DEST{,+LEN,+LEN-1}.  */
   if (operand_equal_p (src, dest, 0))
     {
+      /* Fail for mempcpy (but not memcpy, and certainly not memmove)
+	 if SRC and DEST are the same.  Handle the overlap later.  */
+      if ((code == BUILT_IN_MEMPCPY || code == BUILT_IN_MEMPCPY_CHK)
+	  && operand_equal_p (src, dest, 0))
+	return false;
+
       /* Avoid diagnosing exact overlap in calls to __builtin_memcpy.
 	 It's safe and may even be emitted by GCC itself (see bug
 	 32667).  */
@@ -1747,37 +1753,17 @@ static bool
 gimple_fold_builtin_strcpy (gimple_stmt_iterator *gsi,
 			    tree dest, tree src)
 {
-  gimple *stmt = gsi_stmt (*gsi);
-  location_t loc = gimple_location (stmt);
-  tree fn;
-
-  /* If SRC and DEST are the same (and not volatile), return DEST.  */
+  /* Fail if SRC and DEST are the same.  Handle the overlap later.  */
   if (operand_equal_p (src, dest, 0))
-    {
-      /* Issue -Wrestrict unless the pointers are null (those do
-	 not point to objects and so do not indicate an overlap;
-	 such calls could be the result of sanitization and jump
-	 threading).  */
-      if (!integer_zerop (dest) && !gimple_no_warning_p (stmt))
-	{
-	  tree func = gimple_call_fndecl (stmt);
-
-	  warning_at (loc, OPT_Wrestrict,
-		      "%qD source argument is the same as destination",
-		      func);
-	}
-
-      replace_call_with_value (gsi, dest);
-      return true;
-    }
-
-  if (optimize_function_for_size_p (cfun))
     return false;
 
-  fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
+  tree fn  = builtin_decl_implicit (BUILT_IN_MEMCPY);
   if (!fn)
     return false;
 
+  gimple *stmt = gsi_stmt (*gsi);
+  location_t loc = gimple_location (stmt);
+
   /* Set to non-null if ARG refers to an unterminated array.  */
   tree nonstr = NULL;
   tree len = get_maxval_strlen (src, SRK_STRLEN, &nonstr);
@@ -1794,6 +1780,9 @@ gimple_fold_builtin_strcpy (gimple_stmt_iterator *gsi,
   if (!len)
     return false;
 
+  if (optimize_function_for_size_p (cfun))
+    return false;
+
   len = fold_convert_loc (loc, size_type_node, len);
   len = size_binop_loc (loc, PLUS_EXPR, len, build_int_cst (size_type_node, 1));
   len = force_gimple_operand_gsi (gsi, len, true,
diff --git a/gcc/gimple-ssa-warn-restrict.c b/gcc/gimple-ssa-warn-restrict.c
index 2c582a670eb..46e9cc8e75e 100644
--- a/gcc/gimple-ssa-warn-restrict.c
+++ b/gcc/gimple-ssa-warn-restrict.c
@@ -2006,6 +2006,56 @@ wrestrict_dom_walker::check_call (gimple *call)
 
 } /* anonymous namespace */
 
+
+/* If DST and SRC point to the same location issues -Wrestrict.  */
+
+bool
+maybe_diag_equal_operands (gimple *stmt, tree dst, tree src)
+{
+  if (integer_zerop (dst) || gimple_no_warning_p (stmt))
+    return false;
+
+  location_t loc = gimple_location (stmt);
+  tree func = gimple_call_fndecl (stmt);
+  if (!warning_at (loc, OPT_Wrestrict,
+		   "%G%qD source argument is the same as destination",
+		   stmt, func))
+    return false;
+
+  gimple_set_no_warning (stmt, true);
+
+  /* The operands may be entirely different in the source code but
+     end up being equal as a result of inlining.  To make the warning
+     easier to understand reference the underlying decl they point to
+     in a note.  */
+  tree base = src;
+  if (TREE_CODE (base) == ADDR_EXPR)
+    base = TREE_OPERAND (base, 0);
+
+  poly_int64 poff;
+  base = get_addr_base_and_unit_offset (base, &poff);
+
+  /* Use the underlying SSA_NAME variable to point to the function
+     argument the operands were derived from.  */
+  if (TREE_CODE (base) == SSA_NAME)
+    {
+      base = SSA_NAME_VAR (base);
+      if (!base)
+	return true;
+    }
+  if (TREE_CODE (base) == PARM_DECL || DECL_P (base))
+    loc = DECL_SOURCE_LOCATION (base);
+  else if (EXPR_HAS_LOCATION (base))
+    loc = EXPR_LOCATION (base);
+  else
+    return true;
+
+  if (loc != UNKNOWN_LOCATION)
+    inform (loc, "accessing %qE declared here", base);
+
+  return true;
+}
+
 /* Attempt to detect and diagnose invalid offset bounds and (except for
    memmove) overlapping copy in a call expression EXPR from SRC to DST
    and DSTSIZE and SRCSIZE bytes, respectively.  Both DSTSIZE and
@@ -2072,14 +2122,8 @@ check_bounds_or_overlap (gimple *call, tree dst, tree src, tree dstsize,
 	 not point to objects and so do not indicate an overlap;
 	 such calls could be the result of sanitization and jump
 	 threading).  */
-      if (!integer_zerop (dst) && !gimple_no_warning_p (call))
-	{
-	  warning_at (loc, OPT_Wrestrict,
-		      "%G%qD source argument is the same as destination",
-		      call, func);
-	  gimple_set_no_warning (call, true);
-	  return OPT_Wrestrict;
-	}
+      if (maybe_diag_equal_operands (call, dst, src))
+	return OPT_Wrestrict;
 
       return 0;
     }
diff --git a/gcc/gimple-ssa-warn-restrict.h b/gcc/gimple-ssa-warn-restrict.h
index 7bae95a9ad1..91d9da9f126 100644
--- a/gcc/gimple-ssa-warn-restrict.h
+++ b/gcc/gimple-ssa-warn-restrict.h
@@ -23,4 +23,6 @@
 extern int check_bounds_or_overlap (gimple *, tree, tree, tree, tree,
 				    bool = false, bool = true);
 
+extern bool maybe_diag_equal_operands (gimple *, tree, tree);
+
 #endif /* GIMPLE_SSA_WARN_RESTRICT_H */
diff --git a/gcc/testsuite/gcc.dg/Wrestrict-14.c b/gcc/testsuite/gcc.dg/Wrestrict-14.c
index b919fa644d3..58c502a8731 100644
--- a/gcc/testsuite/gcc.dg/Wrestrict-14.c
+++ b/gcc/testsuite/gcc.dg/Wrestrict-14.c
@@ -69,7 +69,7 @@ void test_mempcpy (char *p, struct S *q, size_t n)
   mempcpy (q->p, q->p, 1);        /* { dg-warning "\\\[-Wrestrict]" } */
   sink (q);
 
-  mempcpy (&q->a[0], q->a, n);    /* { dg-warning "\\\[-Wrestrict]" "bug ????" { xfail *-*-* } } */
+  mempcpy (&q->a[0], q->a, n);    /* { dg-warning "\\\[-Wrestrict]" } */
   sink (q);
 
   mempcpy (q, q->a, n);           /* { dg-warning "\\\[-Wrestrict]" "bug ????" { xfail *-*-* } } */
diff --git a/gcc/testsuite/gcc.dg/Wrestrict-21.c b/gcc/testsuite/gcc.dg/Wrestrict-21.c
new file mode 100644
index 00000000000..4af814ff430
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wrestrict-21.c
@@ -0,0 +1,185 @@
+/* PR middle-end/93519 -bogus -Wrestrict for strcpy(d, s) call guarded by d != s
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+static inline void memcpy_nowarn (void *d, const void *s)
+{
+  if (s != d)
+    __builtin_memcpy (d, s, 32);
+}
+
+static inline void memcpy_warn (void *d, const void *s)
+{
+  /* Not diagnosed because GCC itself emits such overlapping calls and,
+     for the most part, handles them gracefully.  (Calls made by programs
+     should still be diagnosed but they're indistinguishable from GCC's
+     own.)  */
+  __builtin_memcpy (d, s, 32);          // { dg-warning "\\\[-Wrestrict]" "" { xfail *-*-* } }
+}
+
+void test_memcpy (void *s)              // { dg-message "declared here" "" { xfail *-*-* } }
+{
+  memcpy_nowarn (s, s);
+
+  memcpy_warn (s, s);
+
+  extern char memcpy_buf[];             // { dg-message "declared here" "" { xfail *-*-* } }
+  void *d = memcpy_buf;
+  s = memcpy_buf;
+
+  memcpy_warn (d, s);
+}
+
+
+static inline void* mempcpy_nowarn (void *d, const void *s)
+{
+  if (s != d)
+    return __builtin_mempcpy (d, s, 32);
+  return 0;
+}
+
+static inline void* mempcpy_warn (void *d, const void *s)
+{
+  return __builtin_mempcpy (d, s, 32);  // { dg-warning "\\\[-Wrestrict]" }
+}
+
+void test_mempcpy (void *s)             // { dg-message "declared here" }
+{
+  mempcpy_nowarn (s, s);
+
+  mempcpy_warn (s, s);
+
+  extern char mempcpy_buf[];            // { dg-message "declared here" }
+  void *d = mempcpy_buf;
+  s = mempcpy_buf;
+
+  mempcpy_warn (d, s);
+}
+
+
+static inline void strcat_nowarn (char *d, const char *s)
+{
+  if (s != d)
+    __builtin_strcat (d, s);
+}
+
+static inline void strcat_warn (char *d, const char *s)
+{
+  __builtin_strcat (d, s);              // { dg-warning "\\\[-Wrestrict]" }
+}
+
+void test_strcat (char *s)              // { dg-message "declared here" }
+{
+  strcat_nowarn (s, s);
+
+  strcat_warn (s, s);
+
+  extern char strcat_buf[];            // { dg-message "declared here" }
+  char *d = strcat_buf;
+  s = strcat_buf;
+
+  strcat_warn (d, s);
+}
+
+
+static inline char* stpcpy_nowarn (char *d, const char *s)
+{
+  if (s != d)
+    return __builtin_stpcpy (d, s);
+  return 0;
+}
+
+static inline char* stpcpy_warn (char *d, const char *s)
+{
+  return __builtin_stpcpy (d, s);       // { dg-warning "\\\[-Wrestrict]" }
+}
+
+void test_stpcpy (char *s)              // { dg-message "declared here" }
+{
+  stpcpy_nowarn (s, s);
+
+  stpcpy_warn (s, s);
+
+  extern char stpcpy_buf[];            // { dg-message "declared here" }
+  char *d = stpcpy_buf;
+  s = stpcpy_buf;
+
+  stpcpy_warn (d, s);
+}
+
+
+static inline char* stpncpy_nowarn (char *d, const char *s)
+{
+  if (s != d)
+    return __builtin_stpncpy (d, s, 32);
+  return 0;
+}
+
+static inline char* stpncpy_warn (char *d, const char *s)
+{
+  return __builtin_stpncpy (d, s, 32);  // { dg-warning "\\\[-Wrestrict]" }
+}
+
+void test_stpncpy (char *s)             // { dg-message "declared here" }
+{
+  stpncpy_nowarn (s, s);
+
+  stpncpy_warn (s, s);
+
+  extern char stpncpy_buf[];            // { dg-message "declared here" }
+  char *d = stpncpy_buf;
+  s = stpncpy_buf;
+
+  stpncpy_warn (d, s);
+}
+
+
+static inline void strcpy_nowarn (char *d, const char *s)
+{
+  if (s != d)
+    __builtin_strcpy (d, s);            // { dg-bogus "\\\[-Wrestrict]" }
+}
+
+static inline void strcpy_warn (char *d, const char *s)
+{
+  __builtin_strcpy (d, s);              // { dg-warning "\\\[-Wrestrict]" }
+}
+
+void test_strcpy (char *s)              // { dg-message "declared here" }
+{
+  strcpy_nowarn (s, s);
+
+  strcpy_warn (s, s);
+
+  extern char strcpy_buf[];             // { dg-message "declared here" }
+  char *d = strcpy_buf;
+  s = strcpy_buf;
+
+  strcpy_warn (d, s);
+}
+
+
+static inline void strncpy_nowarn (char *d, const char *s)
+{
+  if (s != d)
+    __builtin_strncpy (d, s, 32);
+}
+
+static inline void strncpy_warn (char *d, const char *s)
+{
+  __builtin_strncpy (d, s, 32);         // { dg-warning "\\\[-Wrestrict]" }
+}
+
+void test_strncpy (char *s)             // { dg-message "declared here" }
+{
+  strncpy_nowarn (s, s);
+
+  strncpy_warn (s, s);
+
+  extern char strncpy_buf[];            // { dg-message "declared here" }
+  char *d = strncpy_buf;
+  s = strncpy_buf;
+
+  strncpy_nowarn (d, s);
+  strncpy_warn (d, s);
+}
diff --git a/gcc/testsuite/gcc.dg/Wrestrict-22.c b/gcc/testsuite/gcc.dg/Wrestrict-22.c
new file mode 100644
index 00000000000..cd2577a415b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wrestrict-22.c
@@ -0,0 +1,116 @@
+/* { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern void* memcpy (void*, const void*, size_t);
+extern void* mempcpy (void*, const void*, size_t);
+
+struct A { char a[3]; };
+struct B { struct A a; char b[2]; };
+struct C { struct B b; char c[2]; };
+
+
+void* memcpy_member_to_A (struct A *p)
+{
+  char *q = p->a;
+  memcpy (p, q, sizeof *p);             // { dg-warning "\\\[-Wrestrict" }
+  return q;
+}
+
+void* memcpy_member_to_B (struct B *p)
+{
+  void *q = &p->a;
+  memcpy (p, q, sizeof *p);             // { dg-warning "\\\[-Wrestrict" }
+  return q;
+}
+
+void memcpy_member_to_C (struct C *p)
+{
+  void *q = &p->b;
+  memcpy (p, q, sizeof *p);             // { dg-warning "\\\[-Wrestrict" }
+}
+
+
+void* memcpy_member_plus_to_A (struct A *p)
+{
+  char *q = p->a + 1;
+  memcpy (p, q, sizeof *p);             // { dg-warning "\\\[-Wrestrict" }
+  return q;
+}
+
+void* memcpy_member_plus_to_B (struct B *p)
+{
+  char *q = (char*)&p->a + 1;
+  memcpy (p, q, sizeof *p);             // { dg-warning "\\\[-Wrestrict" }
+  return q;
+}
+
+void* memcpy_member_plus_to_C (struct C *p)
+{
+  char *q = (char*)&p->b + 1;
+  memcpy (p, q, sizeof *p);             // { dg-warning "\\\[-Wrestrict" }
+  return q;
+}
+
+
+void* memcpy_member_to_A_plus (struct A *p)
+{
+  char *q = (char*)p + 1;
+  memcpy (q, p->a, sizeof *p);          // { dg-warning "\\\[-Wrestrict" }
+  return q;
+}
+
+
+void* memcpy_member_to_B_plus (struct B *p)
+{
+  char *q = (char*)p + 1;
+  memcpy (q, &p->a, sizeof *p);          // { dg-warning "\\\[-Wrestrict" }
+  return q;
+}
+
+void* memcpy_member_to_C_plus (struct C *p)
+{
+  char *q = (char*)p + 1;
+  memcpy (p, &p->b, sizeof *p);          // { dg-warning "\\\[-Wrestrict" }
+  return q;
+}
+
+
+
+void* mempcpy_member_to_A (struct A *p)
+{
+  char *q = p->a;
+  return mempcpy (p, q, sizeof *p);     // { dg-warning "\\\[-Wrestrict" }
+}
+
+void* mempcpy_member_to_B (struct B *p)
+{
+  void *q = &p->a;
+  return mempcpy (p, q, sizeof *p);     // { dg-warning "\\\[-Wrestrict" }
+}
+
+void* mempcpy_member_to_C (struct C *p)
+{
+  void *q = &p->b;
+  return mempcpy (p, q, sizeof *p);     // { dg-warning "\\\[-Wrestrict" }
+}
+
+
+void* mempcpy_member_to_A_plus (struct A *p)
+{
+  char *q = (char*)p + 1;
+  return mempcpy (q, p->a, sizeof *p);  // { dg-warning "\\\[-Wrestrict" }
+}
+
+void* mempcpy_member_to_B_plus (struct B *p)
+{
+  char *q = (char*)p + 1;
+  return mempcpy (q, &p->a, sizeof *p); // { dg-warning "\\\[-Wrestrict" }
+}
+
+void* mempcpy_member_to_C_plus (struct C *p)
+{
+  char *q = (char*)p + 1;
+  return mempcpy (q, &p->b, sizeof *p); // { dg-warning "\\\[-Wrestrict" }
+}
diff --git a/gcc/testsuite/gcc.dg/strlenopt-92.c b/gcc/testsuite/gcc.dg/strlenopt-92.c
new file mode 100644
index 00000000000..97082388ce1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/strlenopt-92.c
@@ -0,0 +1,131 @@
+/* Verify that perfectly overlapping calls to raw memory and string
+   functions are eliminated (they are undefined but there's no point
+   in making the library calls).
+   { dg-do compile }
+   { dg-options "-O2 -Wall -fdump-tree-optimized" } */
+
+#define USE_GNU
+#include "strlenopt.h"
+
+void use (char*);
+
+extern char acpy[3];
+
+void test_mem_cpy_self_use (void)
+{
+  char *d = acpy, *s = acpy;
+  use (memcpy (d, s, sizeof acpy));
+}
+
+/* { dg-final { scan-tree-dump-times "use \\\(\\\&acpy" 1 "optimized" } }  */
+
+char* test_mem_cpy_self_ret (unsigned n)
+{
+  char *d = acpy, *s = acpy;
+  return memcpy (d, s, n);
+}
+
+/* { dg-final { scan-tree-dump-times "return \\\&acpy;" 1 "optimized" } }
+   { dg-final { scan-tree-dump-not "memcpy \\\(" "optimized" } }  */
+
+
+extern char apcpy[3];
+
+void test_mem_pcpy_self_use (void)
+{
+  char *d = apcpy, *s = apcpy;
+  use (mempcpy (d, s, sizeof apcpy));  // { dg-warning "\\\[-Wrestrict" }
+}
+
+/* { dg-final { scan-tree-dump-times "use \\\(\\\&apcpy" 1 "optimized" { xfail *-*-* } } }  */
+
+char* test_mem_pcpy_self_ret (unsigned n)
+{
+  char *d = apcpy, *s = apcpy;
+  return mempcpy (d, s, n);             // { dg-warning "\\\[-Wrestrict" }
+}
+
+/* { dg-final { scan-tree-dump-times "return \\\&apcpy;" 1 "optimized" { xfail *-*-* } } }
+   { dg-final { scan-tree-dump-not "mempcpy \\\(" "optimized" { xfail *-*-* } } }
+   { dg-final { scan-assembler-not "mempcpy" } } */
+
+
+extern char ascpy[3];
+
+void test_str_cpy_use (void)
+{
+  char *d = ascpy, *s = ascpy;
+  use (strcpy (d, s));                 // { dg-warning "\\\[-Wrestrict" }
+}
+
+/* { dg-final { scan-tree-dump-times "use \\\(\\\&ascpy" 1 "optimized" } } */
+
+char* test_str_cpy_ret (void)
+{
+  char *d = ascpy, *s = ascpy;
+  return strcpy (d, s);                 // { dg-warning "\\\[-Wrestrict" }
+}
+
+/* { dg-final { scan-tree-dump-times "return \\\&ascpy;" 1 "optimized" } }
+   { dg-final { scan-tree-dump-not "strcpy \\\(" "optimized" } } */
+
+
+extern char asncpy[3];
+
+void test_str_ncpy_use (void)
+{
+  char *d = asncpy, *s = asncpy;
+  use (strncpy (d, s, sizeof asncpy)); // { dg-warning "\\\[-Wrestrict" }
+}
+
+/* { dg-final { scan-tree-dump-times "use \\\(\\\&asncpy" 1 "optimized" { xfail *-*-* } } } */
+
+char* test_str_ncpy_ret (unsigned n)
+{
+  char *d = asncpy, *s = asncpy;
+  return strncpy (d, s, n);             // { dg-warning "\\\[-Wrestrict" }
+}
+
+/* { dg-final { scan-tree-dump-times "return \\\&asncpy;" 1 "optimized" { xfail *-*-* } } }
+   { dg-final { scan-tree-dump-not "strncpy \\\(" "optimized" { xfail *-*-* } } }
+   { dg-final { scan-assembler-not "strncpy" } } */
+
+
+extern char aspcpy[3];
+
+void test_stp_cpy_use (void)
+{
+  char *d = aspcpy, *s = aspcpy;
+  use (stpcpy (d, s));                 // { dg-warning "\\\[-Wrestrict" }
+}
+
+/* { dg-final { scan-tree-dump-times "use \\\(\\\&aspcpy" 1 "optimized" { xfail *-*-*} } } */
+
+char* test_stp_cpy_ret (void)
+{
+  char *d = aspcpy, *s = aspcpy;
+  return stpcpy (d, s);                 // { dg-warning "\\\[-Wrestrict" }
+}
+
+/* { dg-final { scan-tree-dump-times "return \\\&aspcpy;" 1 "optimized" { xfail *-*-* } } }
+   { dg-final { scan-tree-dump-not "stpcpy \\\(" "optimized" { xfail *-*-* } } } */
+
+
+extern char aspncpy[3];
+
+void test_stp_ncpy_use (void)
+{
+  char *d = aspcpy, *s = aspcpy;
+  use (stpncpy (d, s, 3));              // { dg-warning "\\\[-Wrestrict" }
+}
+
+/* { dg-final { scan-tree-dump-times "use \\\(\\\&aspncpy" 1 "optimized" { xfail *-*-*} } } */
+
+char* test_stp_ncpy_ret (unsigned n)
+{
+  char *d = aspcpy, *s = aspcpy;
+  return stpncpy (d, s, n);             // { dg-warning "\\\[-Wrestrict" }
+}
+
+/* { dg-final { scan-tree-dump-times "return \\\&aspncpy;" 1 "optimized" { xfail *-*-* } } }
+   { dg-final { scan-tree-dump-not "stpncpy \\\(" "optimized" { xfail *-*-* } } } */
diff --git a/gcc/testsuite/gcc.dg/strlenopt.h b/gcc/testsuite/gcc.dg/strlenopt.h
index 518d0cf08b2..ca13dc7a31a 100644
--- a/gcc/testsuite/gcc.dg/strlenopt.h
+++ b/gcc/testsuite/gcc.dg/strlenopt.h
@@ -13,6 +13,7 @@ size_t strnlen (const char *, size_t);
 void *memcpy (void *__restrict, const void *__restrict, size_t);
 void *memmove (void *, const void *, size_t);
 char *strcpy (char *__restrict, const char *__restrict);
+char *strncpy (char *__restrict, const char *__restrict, size_t);
 char *strcat (char *__restrict, const char *__restrict);
 char *strchr (const char *, int);
 int strcmp (const char *, const char *);
@@ -23,6 +24,7 @@ int strcmp (const char *, const char *);
 #ifdef USE_GNU
 void *mempcpy (void *__restrict, const void *__restrict, size_t);
 char *stpcpy (char *__restrict, const char *__restrict);
+char *stpncpy (char *__restrict, const char *__restrict, size_t);
 #endif
 
 int sprintf (char * __restrict, const char *__restrict, ...);
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
index ad9e98973b1..7a94c9a2d90 100644
--- a/gcc/tree-ssa-strlen.c
+++ b/gcc/tree-ssa-strlen.c
@@ -2312,6 +2312,41 @@ maybe_warn_overflow (gimple *stmt, unsigned HOST_WIDE_INT len,
 		       si, plus_one, rawmem);
 }
 
+/* If DST is the same as SRC, diagnoses the overlapping call at GSI.
+   If SUB is not ERROR_MARK_NODE, replaces the call if its result is
+   used with SUB if nonnull or with DST otherwise.
+   Returns true if the call has been eliminated.  */
+
+static bool
+maybe_handle_store_to_self (gimple_stmt_iterator *gsi, tree dst, tree src,
+			    tree sub = NULL_TREE)
+{
+  if (!operand_equal_p (dst, src, 0))
+    return false;
+
+  gimple *stmt = gsi_stmt (*gsi);
+
+  maybe_diag_equal_operands (stmt, dst, src);
+
+  if (gimple_call_lhs (stmt))
+    {
+      if (sub == error_mark_node)
+	return false;
+
+      /* Replace the call with SUB if non-null or with DST otherwise.  */
+      replace_call_with_value (gsi, sub ? sub : dst);
+    }
+  else
+    {
+      /* Eliminate the call.  */
+      unlink_stmt_vdef (stmt);
+      gsi_remove (gsi, true);
+      release_defs (stmt);
+    }
+
+  return true;
+}
+
 /* Handle a strlen call.  If strlen of the argument is known, replace
    the strlen call with the known value, otherwise remember that strlen
    of the argument is stored in the lhs SSA_NAME.  */
@@ -2610,24 +2645,25 @@ static void
 handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi,
 		       const vr_values *rvals)
 {
-  int idx, didx;
-  tree src, dst, srclen, len, lhs, type, fn, oldlen;
-  bool success;
   gimple *stmt = gsi_stmt (*gsi);
-  strinfo *si, *dsi, *olddsi, *zsi;
-  location_t loc;
+  tree src = gimple_call_arg (stmt, 1);
+  tree dst = gimple_call_arg (stmt, 0);
 
-  src = gimple_call_arg (stmt, 1);
-  dst = gimple_call_arg (stmt, 0);
-  lhs = gimple_call_lhs (stmt);
-  idx = get_stridx (src);
-  si = NULL;
-  if (idx > 0)
-    si = get_strinfo (idx);
+  /* Diagnose exactly overlapping strcpy and either replace it with DST
+     or eliminate it.  Only diagnose overlapping stpcpy for now without
+     replacing its LHS (but eliminate it if the result is unused).
+     FIXME: Handle overlapping stpcpy result by replacing it with
+     DST + strlen (DST).  */
+  tree sub = (bcode == BUILT_IN_STRCPY || bcode == BUILT_IN_STRCPY_CHK
+	      ? NULL_TREE : error_mark_node);
+  if (maybe_handle_store_to_self (gsi, src, dst, sub))
+    return;
 
-  didx = get_stridx (dst);
-  olddsi = NULL;
-  oldlen = NULL_TREE;
+  int idx = get_stridx (src);
+  strinfo *si = idx > 0 ? get_strinfo (idx) : NULL;
+  int didx = get_stridx (dst);
+  strinfo *olddsi = NULL;
+  tree oldlen = NULL_TREE;
   if (didx > 0)
     olddsi = get_strinfo (didx);
   else if (didx < 0)
@@ -2636,7 +2672,7 @@ handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi,
   if (olddsi != NULL)
     adjust_last_stmt (olddsi, stmt, false);
 
-  srclen = NULL_TREE;
+  tree srclen = NULL_TREE;
   if (si != NULL)
     srclen = get_string_length (si);
   else if (idx < 0)
@@ -2647,7 +2683,8 @@ handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi,
   if (olddsi != NULL)
     adjust_last_stmt (olddsi, stmt, false);
 
-  loc = gimple_location (stmt);
+  tree lhs = gimple_call_lhs (stmt);
+  location_t loc = gimple_location (stmt);
   if (srclen == NULL_TREE)
     switch (bcode)
       {
@@ -2678,6 +2715,8 @@ handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi,
       if (didx == 0)
 	return;
     }
+
+  strinfo *dsi;
   if (olddsi != NULL)
     {
       oldlen = olddsi->nonzero_chars;
@@ -2769,8 +2808,8 @@ handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi,
   if (si != NULL)
     si->dont_invalidate = true;
 
-  fn = NULL_TREE;
-  zsi = NULL;
+  tree fn = NULL_TREE;
+  strinfo *zsi = NULL;
   switch (bcode)
     {
     case BUILT_IN_STRCPY:
@@ -2811,15 +2850,14 @@ handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi,
   if (zsi != NULL)
     zsi->dont_invalidate = true;
 
+  tree type = size_type_node;
   if (fn)
     {
       tree args = TYPE_ARG_TYPES (TREE_TYPE (fn));
       type = TREE_VALUE (TREE_CHAIN (TREE_CHAIN (args)));
     }
-  else
-    type = size_type_node;
 
-  len = fold_convert_loc (loc, type, unshare_expr (srclen));
+  tree len = fold_convert_loc (loc, type, unshare_expr (srclen));
   len = fold_build2_loc (loc, PLUS_EXPR, type, len, build_int_cst (type, 1));
 
   /* Set the no-warning bit on the transformed statement?  */
@@ -2843,6 +2881,8 @@ handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi,
       fprintf (dump_file, "Optimizing: ");
       print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM);
     }
+
+  bool success;
   if (gimple_call_num_args (stmt) == 2)
     success = update_gimple_call (gsi, fn, 3, dst, src, len);
   else
@@ -3421,6 +3461,17 @@ handle_builtin_memcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi,
   tree src = gimple_call_arg (stmt, 1);
   tree dst = gimple_call_arg (stmt, 0);
 
+  /* Diagnose exactly overlapping memcpy and either replace it with DST
+     or eliminate it.  Only diagnose overlapping mempcpy for now without
+     replacing its LHS (but eliminate it if the result is unused).
+     FIXME: Handle overlapping mempcpy result by replacing it with
+     DST + LEN.
+     Also consider diagnosing overflow before overlapping copies.  */
+  tree sub = (bcode == BUILT_IN_MEMCPY || bcode == BUILT_IN_MEMCPY_CHK
+	      ? NULL_TREE : error_mark_node);
+  if (maybe_handle_store_to_self (gsi, src, dst, sub))
+    return;
+
   int didx = get_stridx (dst);
   strinfo *olddsi = NULL;
   if (didx > 0)

Reply via email to