diff mbox series

[PING,3] track dynamic allocation in strlen (PR 91582)

Message ID 4e6a8395-0bc7-8820-7222-99a3bf3d2f3a@gmail.com
State New
Headers show
Series [PING,3] track dynamic allocation in strlen (PR 91582) | expand

Commit Message

Martin Sebor Dec. 7, 2019, 12:19 a.m. UTC
With part 2 (below) of this work committed, I've rebased the patch
on the top of trunk and on top of the updated part 1 (also below).
Attached is the result, retested on x86_64-linux.

[1] include size and offset in -Wstringop-overflow
     https://gcc.gnu.org/ml/gcc-patches/2019-12/msg00392.html

[2] extend -Wstringop-overflow to allocated objects
     (committed in r278983)
     https://gcc.gnu.org/ml/gcc-patches/2019-12/msg00263.html

On 11/25/19 10:54 AM, Martin Sebor wrote:
> Ping: https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00812.html
> 
> On 11/18/19 11:23 AM, Martin Sebor wrote:
>> Ping: https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00812.html
>>
>> On 11/11/19 6:27 PM, Martin Sebor wrote:
>>> The attached patch extends the strlen pass to detect out-of-bounds
>>> accesses to memory allocated by calls to other allocation functions
>>> besides calloc and malloc, as well as VLAs, and user-defined
>>> functions declared with attribute alloc_size.  There is some
>>> overlap with the _FORTIFY_SOURCE detection but thanks to
>>> the extensive use of ranges, this enhancement detects many more
>>> cases of overflow.
>>>
>>> The solution primarily improves warnings but some of the changes
>>> also improve codegen in some cases as a side-effect.  I hope to
>>> take better advantage of the optimization opportunities the dynamic
>>> memory tracking opens up (and also better buffer overflow and array
>>> out-of-bounds detection) in GCC 11.
>>>
>>> Although the strlen pass already tracks some dynamic memory calls
>>> (calloc and malloc) rather than extending the same infrastructure
>>> (strinfo::stmt) to others I took the approach of adding a separate
>>> data member for the other calls (strinfo::alloc) and tracking those
>>> independently.  I did this to keep the changes only minimally
>>> intrusive.  In the future (post GCC 10) it might be worth
>>> considering merging both.
>>>
>>> Besides introducing the new member and making use of it, the rest
>>> of the changes were prompted by weaknesses exposed by test cases
>>> involving dynamically allocated objects.
>>>
>>> The patch is intended to apply on top of the two related patches
>>> posted last week ([1] and [2]).  For all tests to pass also expects
>>> the fix for PR 92412 posted earlier today ([3]).
>>>
>>> Martin
>>>
>>> [1] https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00429.html
>>> [2] https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00652.html
>>> [3] https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00800.html
>>
>

Comments

Jakub Jelinek Dec. 7, 2019, 12:35 a.m. UTC | #1
On Fri, Dec 06, 2019 at 05:19:36PM -0700, Martin Sebor wrote:
> With part 2 (below) of this work committed, I've rebased the patch
> on the top of trunk and on top of the updated part 1 (also below).
> Attached is the result, retested on x86_64-linux.

> --- a/gcc/tree-ssa-strlen.c
> +++ b/gcc/tree-ssa-strlen.c
> @@ -61,6 +61,7 @@ along with GCC; see the file COPYING3.  If not see
>  #include "vr-values.h"
>  #include "gimple-ssa-evrp-analyze.h"
>  
> +#pragma GCC optimize ("0")
>  /* A vector indexed by SSA_NAME_VERSION.  0 means unknown, positive value
>     is an index into strinfo vector, negative value stands for
>     string length of a string literal (~strlen).  */

Why this?  Some debugging left-over?

	Jakub
Martin Sebor Dec. 11, 2019, 11:23 p.m. UTC | #2
Jeff's buildbot exposed a bug in the patch that caused false
positives in cases involving negative offsets into destinations
involving pointers pointing into multiple regions of the same
object.  The attached revision fixes that bug, plus makes
a few minor other fixes pointed out in PR 92868.

On 12/6/19 5:19 PM, Martin Sebor wrote:
> With part 2 (below) of this work committed, I've rebased the patch
> on the top of trunk and on top of the updated part 1 (also below).
> Attached is the result, retested on x86_64-linux.
> 
> [1] include size and offset in -Wstringop-overflow
>      https://gcc.gnu.org/ml/gcc-patches/2019-12/msg00392.html
> 
> [2] extend -Wstringop-overflow to allocated objects
>      (committed in r278983)
>      https://gcc.gnu.org/ml/gcc-patches/2019-12/msg00263.html
> 
> On 11/25/19 10:54 AM, Martin Sebor wrote:
>> Ping: https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00812.html
>>
>> On 11/18/19 11:23 AM, Martin Sebor wrote:
>>> Ping: https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00812.html
>>>
>>> On 11/11/19 6:27 PM, Martin Sebor wrote:
>>>> The attached patch extends the strlen pass to detect out-of-bounds
>>>> accesses to memory allocated by calls to other allocation functions
>>>> besides calloc and malloc, as well as VLAs, and user-defined
>>>> functions declared with attribute alloc_size.  There is some
>>>> overlap with the _FORTIFY_SOURCE detection but thanks to
>>>> the extensive use of ranges, this enhancement detects many more
>>>> cases of overflow.
>>>>
>>>> The solution primarily improves warnings but some of the changes
>>>> also improve codegen in some cases as a side-effect.  I hope to
>>>> take better advantage of the optimization opportunities the dynamic
>>>> memory tracking opens up (and also better buffer overflow and array
>>>> out-of-bounds detection) in GCC 11.
>>>>
>>>> Although the strlen pass already tracks some dynamic memory calls
>>>> (calloc and malloc) rather than extending the same infrastructure
>>>> (strinfo::stmt) to others I took the approach of adding a separate
>>>> data member for the other calls (strinfo::alloc) and tracking those
>>>> independently.  I did this to keep the changes only minimally
>>>> intrusive.  In the future (post GCC 10) it might be worth
>>>> considering merging both.
>>>>
>>>> Besides introducing the new member and making use of it, the rest
>>>> of the changes were prompted by weaknesses exposed by test cases
>>>> involving dynamically allocated objects.
>>>>
>>>> The patch is intended to apply on top of the two related patches
>>>> posted last week ([1] and [2]).  For all tests to pass also expects
>>>> the fix for PR 92412 posted earlier today ([3]).
>>>>
>>>> Martin
>>>>
>>>> [1] https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00429.html
>>>> [2] https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00652.html
>>>> [3] https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00800.html
>>>
>>
>
Martin Sebor Dec. 14, 2019, 12:55 a.m. UTC | #3
After more testing by Jeff's buildbot and correcting the problems
it exposed I have committed the attached patch in r279392.

Martin

On 12/11/19 4:23 PM, Martin Sebor wrote:
> Jeff's buildbot exposed a bug in the patch that caused false
> positives in cases involving negative offsets into destinations
> involving pointers pointing into multiple regions of the same
> object.  The attached revision fixes that bug, plus makes
> a few minor other fixes pointed out in PR 92868.
> 
> On 12/6/19 5:19 PM, Martin Sebor wrote:
>> With part 2 (below) of this work committed, I've rebased the patch
>> on the top of trunk and on top of the updated part 1 (also below).
>> Attached is the result, retested on x86_64-linux.
>>
>> [1] include size and offset in -Wstringop-overflow
>>      https://gcc.gnu.org/ml/gcc-patches/2019-12/msg00392.html
>>
>> [2] extend -Wstringop-overflow to allocated objects
>>      (committed in r278983)
>>      https://gcc.gnu.org/ml/gcc-patches/2019-12/msg00263.html
>>
>> On 11/25/19 10:54 AM, Martin Sebor wrote:
>>> Ping: https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00812.html
>>>
>>> On 11/18/19 11:23 AM, Martin Sebor wrote:
>>>> Ping: https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00812.html
>>>>
>>>> On 11/11/19 6:27 PM, Martin Sebor wrote:
>>>>> The attached patch extends the strlen pass to detect out-of-bounds
>>>>> accesses to memory allocated by calls to other allocation functions
>>>>> besides calloc and malloc, as well as VLAs, and user-defined
>>>>> functions declared with attribute alloc_size.  There is some
>>>>> overlap with the _FORTIFY_SOURCE detection but thanks to
>>>>> the extensive use of ranges, this enhancement detects many more
>>>>> cases of overflow.
>>>>>
>>>>> The solution primarily improves warnings but some of the changes
>>>>> also improve codegen in some cases as a side-effect.  I hope to
>>>>> take better advantage of the optimization opportunities the dynamic
>>>>> memory tracking opens up (and also better buffer overflow and array
>>>>> out-of-bounds detection) in GCC 11.
>>>>>
>>>>> Although the strlen pass already tracks some dynamic memory calls
>>>>> (calloc and malloc) rather than extending the same infrastructure
>>>>> (strinfo::stmt) to others I took the approach of adding a separate
>>>>> data member for the other calls (strinfo::alloc) and tracking those
>>>>> independently.  I did this to keep the changes only minimally
>>>>> intrusive.  In the future (post GCC 10) it might be worth
>>>>> considering merging both.
>>>>>
>>>>> Besides introducing the new member and making use of it, the rest
>>>>> of the changes were prompted by weaknesses exposed by test cases
>>>>> involving dynamically allocated objects.
>>>>>
>>>>> The patch is intended to apply on top of the two related patches
>>>>> posted last week ([1] and [2]).  For all tests to pass also expects
>>>>> the fix for PR 92412 posted earlier today ([3]).
>>>>>
>>>>> Martin
>>>>>
>>>>> [1] https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00429.html
>>>>> [2] https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00652.html
>>>>> [3] https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00800.html
>>>>
>>>
>>
>
Jeff Law Dec. 14, 2019, 9:35 p.m. UTC | #4
On Fri, 2019-12-13 at 17:55 -0700, Martin Sebor wrote:
> After more testing by Jeff's buildbot and correcting the problems
> it exposed I have committed the attached patch in r279392.
And just to close the loop on this.  Your last version fixed all the
issues I saw in the tester.

jeff
Christophe Lyon Dec. 17, 2019, 3:30 p.m. UTC | #5
On Sat, 14 Dec 2019 at 22:35, Jeff Law <law@redhat.com> wrote:
>
> On Fri, 2019-12-13 at 17:55 -0700, Martin Sebor wrote:
> > After more testing by Jeff's buildbot and correcting the problems
> > it exposed I have committed the attached patch in r279392.
> And just to close the loop on this.  Your last version fixed all the
> issues I saw in the tester.
>

Hi,

On my side, I've noticed that r279392 caused regressions on arm.
On arm-none-linux-gnueabi
--with-mode arm
--with-cpu cortex-a9
I see
gcc.dg/strlenopt-8.c: pattern found 2 times
FAIL: gcc.dg/strlenopt-8.c scan-tree-dump-times strlen1 "strlen \\(" 0
FAIL: gcc.dg/tree-ssa/pr87022.c (test for excess errors)
Excess errors:
/gcc/testsuite/gcc.dg/tree-ssa/pr87022.c:26:19: warning: writing 1
byte into a region of size 0 [-Wstringop-overflow=]
/gcc/testsuite/gcc.dg/tree-ssa/pr87022.c:26:19: warning: writing 1
byte into a region of size 0 [-Wstringop-overflow=]
/gcc/testsuite/gcc.dg/tree-ssa/pr87022.c:26:19: warning: writing 1
byte into a region of size 0 [-Wstringop-overflow=]
/gcc/testsuite/gcc.dg/tree-ssa/pr87022.c:26:19: warning: writing 1
byte into a region of size 0 [-Wstringop-overflow=]
/gcc/testsuite/gcc.dg/tree-ssa/pr87022.c:26:19: warning: writing 1
byte into a region of size 0 [-Wstringop-overflow=]
/gcc/testsuite/gcc.dg/tree-ssa/pr87022.c:26:19: warning: writing 1
byte into a region of size 0 [-Wstringop-overflow=]

Christophe


> jeff
>
>
Andreas Schwab Jan. 8, 2020, 11:52 a.m. UTC | #6
On Dez 06 2019, Martin Sebor wrote:

> diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c
> new file mode 100644
> index 00000000000..249ce2b6ad5
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c

> +void test_strcpy_warn (const char *s)
> +{
> +  {
> +    const char a[] = "123";
> +    /* Verify that using signed int for the strlen result works (i.e.,
> +       that the conversion from signed int to size_t doesn't prevent
> +       the detection.  */
> +    int n = strlen (a);
> +    char *t = (char*)calloc (n, 1);     // { dg-message "at offset 0 to an object with size 3 allocated by 'calloc' here" "calloc note" { xfail *-*-* } }
> +                                        // { dg-message "at offset 0 to an object with size at most 3 allocated by 'calloc' here" "calloc note" { target *-*-* } .-1 }

Please make the test name unique.

> +    strcpy (t, a);                      // { dg-warning "writing 4 bytes into a region of size (between 0 and )?3 " }
> +
> +    sink (t);
> +  }
> +
> +  {
> +    const char a[] = "1234";
> +    size_t n = strlen (a);
> +    char *t = (char*)malloc (n);        // { dg-message "at offset 0 to an object with size 4 allocated by 'malloc' here" "malloc note" { xfail *-*-* } }
> +                                        // { dg-message "at offset 0 to an object with size at most 4 allocated by 'malloc' here" "malloc note" { target *-*-* } .-1 }

Likewise.

Andreas.
Jeff Law Jan. 8, 2020, 6:42 p.m. UTC | #7
On Wed, 2020-01-08 at 12:52 +0100, Andreas Schwab wrote:
> On Dez 06 2019, Martin Sebor wrote:
> 
> > diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c
> > new file mode 100644
> > index 00000000000..249ce2b6ad5
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c
> > +void test_strcpy_warn (const char *s)
> > +{
> > +  {
> > +    const char a[] = "123";
> > +    /* Verify that using signed int for the strlen result works (i.e.,
> > +       that the conversion from signed int to size_t doesn't prevent
> > +       the detection.  */
> > +    int n = strlen (a);
> > +    char *t = (char*)calloc (n, 1);     // { dg-message "at offset 0 to an object with size 3 allocated by 'calloc' here" "calloc note" { xfail *-*-* } }
> > +                                        // { dg-message "at offset 0 to an object with size at most 3 allocated by 'calloc' here" "calloc note" { target *-*-* } .-1 }
> 
> Please make the test name unique.
I've got a patch to do that in my local tree.  I'll push it
momentarily.

jeff
diff mbox series

Patch

PR middle-end/91582 - missing heap overflow detection for strcpy

gcc/ChangeLog:

	PR middle-end/91582
	* builtins.c (gimple_call_alloc_size): Add argument.
	* builtins.h (gimple_call_alloc_size): Same.
	* tree-ssa-strlen.c (strinfo::alloc): New member.
	(get_addr_stridx): Add argument.
	(get_stridx): Use ptrdiff_t.  Add argument.
	(new_strinfo): Set new member.
	(get_string_length): Handle alloca and VLA.
	(dump_strlen_info): Dump more state.
	(maybe_invalidate): Print more info.  Decrease indentation.
	(unshare_strinfo): Set new member.
	(valid_builtin_call): Handle alloca and VLA.
	(maybe_warn_overflow): Check and set no-warning bit.  Improve
	handling of offsets.  Print allocated objects.
	(handle_builtin_strlen): Handle strinfo records with null lengths.
	(handle_builtin_strcpy): Add argument.  Call maybe_warn_overflow.
	(is_strlen_related_p): Handle dynamically allocated objects.
	(get_range): Add argument.
	(handle_builtin_malloc): Rename...
	(handle_aalloc): ...to this and handle all allocation functions.
	(handle_builtin_memset): Call maybe_warn_overflow.
	(count_nonzero_bytes): Handle more MEM_REF forms.
	(strlen_check_and_optimize_call): Call handle_alloc_call.  Pass
	arguments to more callees.
	(handle_integral_assign): Add argument.  Create strinfo entries
	for MEM_REF assignments.
	(check_and_optimize_stmt): Handle more MEM_REF forms.

gcc/testsuite/ChangeLog:

	PR middle-end/91582
	* c-c++-common/Wrestrict.c: Adjust expected warnings.
	* gcc.dg/Warray-bounds-46.c: Disable -Wstringop-overflow.
	* gcc.dg/Warray-bounds-47.c: Same.
	* gcc.dg/Warray-bounds-52.c: New test.
	* gcc.dg/Wstringop-overflow-26.c: New test.
	* gcc.dg/Wstringop-overflow-27.c: New test.
	* gcc.dg/Wstringop-overflow-28.c: New test.
	* gcc.dg/attr-alloc_size.c (test): Disable -Warray-bounds.
	* gcc.dg/attr-copy-2.c: Adjust expected warnings.
	* gcc.dg/builtin-stringop-chk-5.c: Adjust text of expected messages.
	* gcc.dg/strlenopt-86.c: Relax test.
	* gcc.target/i386/pr82002-1.c: Prune expected warnings.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 1ee84f343a3..5db0bd3226c 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -48,6 +48,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "calls.h"
 #include "varasm.h"
 #include "tree-object-size.h"
+#include "tree-ssa-strlen.h"
 #include "realmpfr.h"
 #include "cfgrtl.h"
 #include "except.h"
@@ -3697,10 +3698,12 @@  check_access (tree exp, tree, tree, tree dstwrite,
 }
 
 /* If STMT is a call to an allocation function, returns the size
-   of the object allocated by the call.  */
+   of the object allocated by the call.  If nonnull, set RNG1[]
+   to the range of the size.  */
 
 tree
-gimple_call_alloc_size (gimple *stmt)
+gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
+			const vr_values *rvals /* = NULL */)
 {
   if (!stmt)
     return NULL_TREE;
@@ -3747,11 +3750,12 @@  gimple_call_alloc_size (gimple *stmt)
 
   tree size = gimple_call_arg (stmt, argidx1);
 
-  wide_int rng1[2];
-  if (TREE_CODE (size) == INTEGER_CST)
-    rng1[0] = rng1[1] = wi::to_wide (size);
-  else if (TREE_CODE (size) != SSA_NAME
-	   || get_range_info (size, rng1, rng1 + 1) != VR_RANGE)
+  wide_int rng1_buf[2];
+  /* If RNG1 is not set, use the buffer.  */
+  if (!rng1)
+    rng1 = rng1_buf;
+
+  if (!get_range (size, rng1, rvals))
     return NULL_TREE;
 
   if (argidx2 > nargs && TREE_CODE (size) == INTEGER_CST)
@@ -3761,10 +3765,7 @@  gimple_call_alloc_size (gimple *stmt)
      of the upper bounds as a constant.  Ignore anti-ranges.  */
   tree n = argidx2 < nargs ? gimple_call_arg (stmt, argidx2) : integer_one_node;
   wide_int rng2[2];
-  if (TREE_CODE (n) == INTEGER_CST)
-    rng2[0] = rng2[1] = wi::to_wide (n);
-  else if (TREE_CODE (n) != SSA_NAME
-	   || get_range_info (n, rng2, rng2 + 1) != VR_RANGE)
+  if (!get_range (n, rng2, rvals))
     return NULL_TREE;
 
   /* Extend to the maximum precsion to avoid overflow.  */
@@ -3802,7 +3803,7 @@  gimple_call_alloc_size (gimple *stmt)
 
 tree
 compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */,
-		 tree *poff /* = NULL */)
+		 tree *poff /* = NULL */, const vr_values *rvals /* = NULL */)
 {
   tree dummy_decl = NULL_TREE;
   if (!pdecl)
@@ -3826,8 +3827,14 @@  compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */,
       if (is_gimple_call (stmt))
 	{
 	  /* If STMT is a call to an allocation function get the size
-	     from its argument(s).  */
-	  return gimple_call_alloc_size (stmt);
+	     from its argument(s).  If successful, also set *PDECL to
+	     DEST for the caller to include in diagnostics.  */
+	  if (tree size = gimple_call_alloc_size (stmt))
+	    {
+	      *pdecl = dest;
+	      return size;
+	    }
+	  return NULL_TREE;
 	}
 
       if (!is_gimple_assign (stmt))
@@ -3857,13 +3864,13 @@  compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */,
 		    ;
 		  else if (wi::ltu_p (wioff, wisiz))
 		    {
-		      *poff = size_binop (PLUS_EXPR, *poff, off);
+		      *poff = *poff ? size_binop (PLUS_EXPR, *poff, off) : off;
 		      return wide_int_to_tree (TREE_TYPE (size),
 					       wi::sub (wisiz, wioff));
 		    }
 		  else
 		    {
-		      *poff = size_binop (PLUS_EXPR, *poff, off);
+		      *poff = *poff ? size_binop (PLUS_EXPR, *poff, off) : off;
 		      return size_zero_node;
 		    }
 		}
@@ -3888,15 +3895,15 @@  compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */,
 			;
 		      else if (wi::ltu_p (min, wisiz))
 			{
-			  *poff = size_binop (PLUS_EXPR, *poff,
-					      wide_int_to_tree (sizetype, min));
+			  tree t = wide_int_to_tree (sizetype, min);
+			  *poff = *poff ? size_binop (PLUS_EXPR, *poff, t) : t;
 			  return wide_int_to_tree (TREE_TYPE (size),
 						   wi::sub (wisiz, min));
 			}
 		      else
 			{
-			  *poff = size_binop (PLUS_EXPR, *poff,
-					      wide_int_to_tree (sizetype, min));
+			  tree t = wide_int_to_tree (sizetype, min);
+			  *poff = *poff ? size_binop (PLUS_EXPR, *poff, t) : t;
 			  return size_zero_node;
 			}
 		    }
@@ -3926,10 +3933,19 @@  compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */,
 	      && *poff && integer_zerop (*poff))
 	    return size_zero_node;
 
-	  /* A valid offset into a declared object cannot be negative.  */
-	  if (tree_int_cst_sgn (*poff) < 0)
+	  /* A valid offset into a declared object cannot be negative.
+	     A zero size with a zero "inner" offset is still zero size
+	     regardless of the "other" offset OFF.  */
+	  if (*poff
+	      && ((integer_zerop (*poff) && integer_zerop (size))
+		  || (TREE_CODE (*poff) == INTEGER_CST
+		      && tree_int_cst_sgn (*poff) < 0)))
 	    return size_zero_node;
 
+	  wide_int offrng[2];
+	  if (!get_range (off, offrng, rvals))
+	    return NULL_TREE;
+
 	  /* Adjust SIZE either up or down by the sum of *POFF and OFF
 	     above.  */
 	  if (TREE_CODE (dest) == ARRAY_REF)
@@ -3938,24 +3954,28 @@  compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */,
 	      tree eltype = TREE_TYPE (dest);
 	      tree tpsize = TYPE_SIZE_UNIT (eltype);
 	      if (tpsize && TREE_CODE (tpsize) == INTEGER_CST)
-		off = fold_build2 (MULT_EXPR, size_type_node, off, tpsize);
+		{
+		  wide_int wsz = wi::to_wide (tpsize, offrng->get_precision ());
+		  offrng[0] *= wsz;
+		  offrng[1] *= wsz;
+		}
 	      else
 		return NULL_TREE;
 	    }
 
-	  wide_int offrng[2];
-	  if (TREE_CODE (off) == INTEGER_CST)
-	    offrng[0] = offrng[1] = wi::to_wide (off);
-	  else if (TREE_CODE (off) == SSA_NAME)
+	  if (!*poff)
 	    {
-	      wide_int min, max;
-	      enum value_range_kind rng
-		= get_range_info (off, offrng, offrng + 1);
-	      if (rng != VR_RANGE)
-		return NULL_TREE;
+	      /* If the "inner" offset is unknown and the "outer" offset
+		 is either negative or less than SIZE, return the size
+		 minus the offset.  This may be overly optimistic in
+		 the first case if the inner offset happens to be less
+		 than the absolute value of the outer offset.  */
+	      off = fold_convert (ptrdiff_type_node, off);
+	      if (tree_int_cst_sgn (off) < 0
+		  || tree_int_cst_lt (off, size))
+		return fold_build2 (MINUS_EXPR, size_type_node, size, off);
+	      return integer_zero_node;
 	    }
-	  else
-	    return NULL_TREE;
 
 	  /* Convert to the same precision to keep wide_int from "helpfuly"
 	     crashing whenever it sees other argumments.  */
@@ -4022,6 +4042,9 @@  compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */,
       return component_ref_size (dest);
     }
 
+  if (TREE_CODE (dest) == VAR_DECL)
+    return DECL_SIZE_UNIT (dest);
+
   if (TREE_CODE (dest) != ADDR_EXPR)
     return NULL_TREE;
 
diff --git a/gcc/builtins.h b/gcc/builtins.h
index 0fcccc12a39..2736f161b6b 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -133,8 +133,12 @@  extern tree fold_call_stmt (gcall *, bool);
 extern void set_builtin_user_assembler_name (tree decl, const char *asmspec);
 extern bool is_simple_builtin (tree);
 extern bool is_inexpensive_builtin (tree);
-extern tree gimple_call_alloc_size (gimple *);
-extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL);
+
+class vr_values;
+tree gimple_call_alloc_size (gimple *, wide_int[2] = NULL,
+			     const vr_values * = NULL);
+extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL,
+			     const vr_values * = NULL);
 
 extern bool readonly_data_expr (tree exp);
 extern bool init_target_chars (void);
diff --git a/gcc/testsuite/c-c++-common/Wrestrict.c b/gcc/testsuite/c-c++-common/Wrestrict.c
index c852b06bbd7..1903f502abd 100644
--- a/gcc/testsuite/c-c++-common/Wrestrict.c
+++ b/gcc/testsuite/c-c++-common/Wrestrict.c
@@ -731,10 +731,16 @@  void test_strcpy_range (void)
 
   r = SR (3, DIFF_MAX - 3);
   T (8, "01",  a + r, a);
-  T (8, "012", a + r, a);            /* { dg-warning "accessing 4 bytes at offsets \\\[3, \[0-9\]+] and 0 may overlap 1 byte at offset 3" "strcpy" } */
+
+  /* The accesses below might trigger either
+       -Wrestrict: accessing 4 bytes at offsets [3, \[0-9\]+] and 0 may overlap 1 byte at offset 3
+     or
+       -Wstringop-overflow: writing 4 bytes into a region of size 0
+     Either of the two is appropriate.  */
+  T (8, "012", a + r, a);            /* { dg-warning "\\\[-Wrestrict|-Wstringop-overflow" } */
 
   r = SR (DIFF_MAX - 2, DIFF_MAX - 1);
-  T (8, "012", a + r, a);            /* { dg-warning "accessing 4 bytes at offsets \\\[\[0-9\]+, \[0-9\]+] and 0 overlaps" "strcpy" } */
+  T (8, "012", a + r, a);            /* { dg-warning "\\\[-Wrestrict|-Wstringop-overflow" } */
 
   /* Exercise the full range of ptrdiff_t.  */
   r = signed_value ();
diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-46.c b/gcc/testsuite/gcc.dg/Warray-bounds-46.c
index 4980f93a470..74e78cbdbe8 100644
--- a/gcc/testsuite/gcc.dg/Warray-bounds-46.c
+++ b/gcc/testsuite/gcc.dg/Warray-bounds-46.c
@@ -3,7 +3,7 @@ 
    Test to verify that past-the-end accesses by string functions to member
    arrays by-reference objects are diagnosed.
    { dg-do compile }
-   { dg-options "-O2 -Wall -Wno-unused-local-typedefs -ftrack-macro-expansion=0" }  */
+   { dg-options "-O2 -Wall -Wno-unused-local-typedefs -Wno-stringop-overflow -ftrack-macro-expansion=0" }  */
 
 #define SA(expr) typedef int StaticAssert [2 * !!(expr) - 1]
 
diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-47.c b/gcc/testsuite/gcc.dg/Warray-bounds-47.c
index 06ad488d1e0..848ef365163 100644
--- a/gcc/testsuite/gcc.dg/Warray-bounds-47.c
+++ b/gcc/testsuite/gcc.dg/Warray-bounds-47.c
@@ -1,7 +1,7 @@ 
 /* PR middle-end/91830 - Bogus -Warray-bounds on strcpy into a member
    of a subobject compiling binutils
    { dg-do compile }
-   { dg-options "-O2 -Wall -ftrack-macro-expansion=0" } */
+   { dg-options "-O2 -Wall -Wno-stringop-overflow -ftrack-macro-expansion=0" } */
 
 extern char* strcpy (char*, const char*);
 extern void sink (void*);
diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size.c b/gcc/testsuite/gcc.dg/attr-alloc_size.c
index 7b0dc6e4535..4c0cd9a14c4 100644
--- a/gcc/testsuite/gcc.dg/attr-alloc_size.c
+++ b/gcc/testsuite/gcc.dg/attr-alloc_size.c
@@ -22,15 +22,15 @@  test (void)
   strcpy (p, "Hello");
   p = malloc1 (6);
   strcpy (p, "Hello");
-  strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */
+  strcpy (p, "Hello World"); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "strcpy" } */
   p = malloc2 (__INT_MAX__ >= 1700000 ? 424242 : __INT_MAX__ / 4, 6);
   strcpy (p, "World");
-  strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */
+  strcpy (p, "Hello World"); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "strcpy" } */
   p = calloc1 (2, 5);
   strcpy (p, "World");
-  strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */
+  strcpy (p, "Hello World"); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "strcpy" } */
   p = calloc2 (2, __INT_MAX__ >= 1700000 ? 424242 : __INT_MAX__ / 4, 5);
   strcpy (p, "World");
-  strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */
+  strcpy (p, "Hello World"); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "strcpy" } */
 }
 
diff --git a/gcc/testsuite/gcc.dg/attr-copy-2.c b/gcc/testsuite/gcc.dg/attr-copy-2.c
index f311ca32aa6..ffc7208f4a7 100644
--- a/gcc/testsuite/gcc.dg/attr-copy-2.c
+++ b/gcc/testsuite/gcc.dg/attr-copy-2.c
@@ -99,7 +99,7 @@  void* xref12 (int);
 void* call_xref12 (void)
 {
   void *p = xref12 (3);
-  __builtin___strcpy_chk (p, "123", __builtin_object_size (p, 0));   /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+  __builtin___strcpy_chk (p, "123", __builtin_object_size (p, 0));   /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" } */
   return p;
 }
 
@@ -197,7 +197,7 @@  void* falias_malloc (void);
 void* call_falias_malloc (void)
 {
   char *p = falias_malloc ();
-  __builtin___strcpy_chk (p, "123", __builtin_object_size (p, 0));   /* { dg-warning "\\\[-Wstringop-overflow=]" } */
+  __builtin___strcpy_chk (p, "123", __builtin_object_size (p, 0));   /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" } */
   return p;
 }
 
diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c
index 320cd51fcf2..87dd6ac4e89 100644
--- a/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c
+++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c
@@ -110,7 +110,7 @@  void test_memop_warn_alloc (const void *src)
 
   struct A *a = __builtin_malloc (sizeof *a * 2);
 
-  memcpy (a, src, n);   /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" "memcpy into allocated" } */
+  memcpy (a, src, n);   /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 " "memcpy into allocated" } */
   escape (a, src);
 
   /* At -Wstringop-overflow=1 the destination is considered to be
@@ -127,7 +127,7 @@  void test_memop_warn_alloc (const void *src)
 
   struct B *b = __builtin_malloc (sizeof *b * 2);
 
-  memcpy (&b[0], src, n);   /* { dg-warning "writing between 12 and 32 bytes into a region of size 8 overflows the destination" "memcpy into allocated" } */
+  memcpy (&b[0], src, n);   /* { dg-warning "writing between 12 and 32 bytes into a region of size 8 " "memcpy into allocated" } */
   escape (b);
 
   /* The following idiom of clearing multiple members of a struct is
diff --git a/gcc/testsuite/gcc.dg/strlenopt-86.c b/gcc/testsuite/gcc.dg/strlenopt-86.c
index 3e86fa3c90a..d2029443556 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-86.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-86.c
@@ -9,11 +9,11 @@ 
 unsigned n0, n1;
 
 void*
-keep_strlen_calloc_store_cst_memset (unsigned a, unsigned b)
+keep_strlen_calloc_store_cst_memset (int i, unsigned a, unsigned b)
 {
   char *p = __builtin_calloc (a, 1);
 
-  p[1] = 'x';
+  p[i] = 'x';
 
   __builtin_memset (p, 0, b);
 
@@ -23,11 +23,11 @@  keep_strlen_calloc_store_cst_memset (unsigned a, unsigned b)
 }
 
 void*
-keep_strlen_calloc_store_var_memset (int x, unsigned a, unsigned b)
+keep_strlen_calloc_store_var_memset (int i, int x, unsigned a, unsigned b)
 {
   char *p = __builtin_calloc (a, 1);
 
-  p[1] = x;
+  p[i] = x;
 
   __builtin_memset (p, 0, b);
 
@@ -37,11 +37,11 @@  keep_strlen_calloc_store_var_memset (int x, unsigned a, unsigned b)
 }
 
 void*
-keep_strlen_calloc_store_memset_2 (int x, unsigned a, unsigned b, unsigned c)
+keep_strlen_calloc_store_memset_2 (int i, int x, unsigned a, unsigned b, unsigned c)
 {
   char *p = __builtin_calloc (a, 1);
 
-  p[1] = x;
+  p[i] = x;
   __builtin_memset (p, 0, b);
 
   n0 = __builtin_strlen (p);
diff --git a/gcc/testsuite/gcc.target/i386/pr82002-1.c b/gcc/testsuite/gcc.target/i386/pr82002-1.c
index 86678a01992..b4d4bd3d125 100644
--- a/gcc/testsuite/gcc.target/i386/pr82002-1.c
+++ b/gcc/testsuite/gcc.target/i386/pr82002-1.c
@@ -10,3 +10,5 @@  b ()
   a (c);
   a (c);
 }
+
+// { dg-prune-output "\\\[-Wstringop-overflow" }
diff --git a/gcc/tree-ssa-strlen.h b/gcc/tree-ssa-strlen.h
index 4d43fc65e9e..46f2c0a3996 100644
--- a/gcc/tree-ssa-strlen.h
+++ b/gcc/tree-ssa-strlen.h
@@ -25,8 +25,10 @@  extern bool is_strlen_related_p (tree, tree);
 extern bool maybe_diag_stxncpy_trunc (gimple_stmt_iterator, tree, tree);
 extern tree set_strlen_range (tree, wide_int, wide_int, tree = NULL_TREE);
 
-struct c_strlen_data;
 class vr_values;
+extern tree get_range (tree, wide_int[2], const vr_values * = NULL);
+
+struct c_strlen_data;
 extern void get_range_strlen_dynamic (tree , c_strlen_data *, const vr_values *);
 
 /* APIs internal to strlen pass.  Defined in in gimple-ssa-sprintf.c.  */
diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-52.c b/gcc/testsuite/gcc.dg/Warray-bounds-52.c
new file mode 100644
index 00000000000..1a7d76fcc2a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-bounds-52.c
@@ -0,0 +1,97 @@ 
+/* PR middle-end/92341 - missing -Warray-bounds indexing past the end
+   of a compound literal
+   { dg-do compile }
+   { dg-options "-O2 -Wall -ftrack-macro-expansion=0" } */
+
+#include "range.h"
+
+#define INT_MAX    __INT_MAX__
+#define INT_MIN    (-__INT_MAX__ - 1)
+
+void sink (int, ...);
+
+
+#define T(...) sink (__LINE__, (__VA_ARGS__))
+
+
+void direct_idx_cst (void)
+{
+  T ((int[]){ }[-1]);           // { dg-warning "array subscript -1 is outside array bounds of 'int\\\[0]'" }
+  T ((int[]){ }[0]);            // { dg-warning "array subscript 0 is outside array bounds of 'int\\\[0]'" }
+  T ((int[]){ }[1]);            // { dg-warning "array subscript 1 is outside array bounds of 'int\\\[0]'" }
+
+  T ((int[]){ 1 }[-1]);         // { dg-warning "array subscript -1 is below array bounds of 'int\\\[1]'" }
+  T ((int[]){ 1 }[0]);
+  T ((int[]){ 1 }[1]);          // { dg-warning "array subscript 1 is above array bounds of 'int\\\[1]'" }
+  T ((int[]){ 1 }[INT_MIN]);    // { dg-warning "array subscript -\[0-9\]+ is below array bounds of 'int\\\[1]'" }
+  T ((int[]){ 1 }[INT_MAX]);    // { dg-warning "array subscript \[0-9\]+ is above array bounds of 'int\\\[1]'" }
+  T ((int[]){ 1 }[SIZE_MAX]);   // { dg-warning "array subscript \[0-9\]+ is above array bounds of 'int\\\[1]'" }
+}
+
+
+void direct_idx_var (int i)
+{
+  T ((char[]){ }[i]);           // { dg-warning "array subscript i is outside array bounds of 'char\\\[0]'" }
+  T ((int[]){ }[i]);            // { dg-warning "array subscript i is outside array bounds of 'int\\\[0]'" }
+}
+
+
+void direct_idx_range (void)
+{
+  ptrdiff_t i = SR (-2, -1);
+
+  T ((int[]){ 1 }[i]);          // { dg-warning "array subscript \[ \n\r]+ is outside array bounds of 'int\\\[0]'" "pr?????" { xfail *-*-* } }
+}
+
+
+#undef T
+#define T(idx, ...) do {			\
+    int *p = (__VA_ARGS__);			\
+    sink (p[idx]);				\
+  } while (0)
+
+void ptr_idx_cst (void)
+{
+  T (-1, (int[]){ });           // { dg-warning "array subscript -1 is outside array bounds of 'int\\\[0]'" }
+  T ( 0, (int[]){ });           // { dg-warning "array subscript 0 is outside array bounds of 'int\\\[0]'" }
+  T (+1, (int[]){ });           // { dg-warning "array subscript 1 is outside array bounds of 'int\\\[0]'" }
+
+  T (-1, (int[]){ 1 });         // { dg-warning "array subscript -1 is outside array bounds of 'int\\\[1]'" }
+  T ( 0, (int[]){ 1 });
+  T (+1, (int[]){ 1 });         // { dg-warning "array subscript 1 is outside array bounds of 'int\\\[1]'" }
+  T (INT_MIN, (int[]){ 1 });    // { dg-warning "array subscript -\[0-9\]+ is outside array bounds of 'int\\\[1]'" "lp64" { xfail ilp32 } }
+  T (INT_MAX, (int[]){ 1 });    // { dg-warning "array subscript \[0-9\]+ is outside array bounds of 'int\\\[1]'" "lp64" { target lp64 } }
+                                // { dg-warning "array subscript -1 is outside array bounds of 'int\\\[1]'" "ilp32" { target ilp32 } .-1 }
+  T (SIZE_MAX, (int[]){ 1 });   // { dg-warning "array subscript -?\[0-9\]+ is outside array bounds of 'int\\\[1]'" }
+}
+
+
+void ptr_idx_var (int i)
+{
+  T (i, (int[]){ });            // { dg-warning "array subscript \[^\n\r\]+ is outside array bounds of 'int\\\[0]'" }
+  T (i, (int[]){ 1 });
+  T (i, (int[]){ i, 1 });
+}
+
+void ptr_idx_range (void)
+{
+  ptrdiff_t i = SR (-2, -1);
+
+  T (i, (int[]){ });            // { dg-warning "array subscript \\\[-2, -1] is outside array bounds of 'int\\\[0]'" }
+  T (i, (int[]){ 1 });          // { dg-warning "array subscript \\\[-2, -1] is outside array bounds of 'int\\\[1]'" }
+  T (i, (int[]){ i });          // { dg-warning "array subscript \\\[-2, -1] is outside array bounds of 'int\\\[1]'" }
+
+  i = SR (0, 1);
+
+  T (i, (int[]){ });            // { dg-warning "array subscript \\\[0, 1] is outside array bounds of 'int\\\[0]'" }
+  T (i, (int[]){ 1 });
+
+  i = SR (1, 2);
+  T (i, (int[]){ 1 });          // { dg-warning "array subscript \\\[1, 2] is outside array bounds of 'int\\\[1]'" }
+
+  i = SR (2, 3);
+  T (i, (int[]){ 1, 2, 3 });
+
+  i = SR (3, 4);
+  T (i, (int[]){ 2, 3, 4 });          // { dg-warning "array subscript \\\[3, 4] is outside array bounds of 'int\\\[3]'" }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-26.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-26.c
new file mode 100644
index 00000000000..b59f90d2f8e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-26.c
@@ -0,0 +1,390 @@ 
+/* PR middle-end/91582 - missing heap overflow detection for strcpy
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wno-array-bounds -Wno-memset-transposed-args -ftrack-macro-expansion=0" } */
+
+#include "range.h"
+
+#define INT_MAX    __INT_MAX__
+#define INT_MIN    (-INT_MAX - 1)
+
+typedef __SIZE_TYPE__ size_t;
+
+extern void* calloc (size_t, size_t);
+extern void* malloc (size_t);
+extern void* memset (void*, int, size_t);
+extern char* strcpy (char*, const char*);
+extern size_t strlen (const char*);
+
+void sink (void*);
+
+#define T(N, ACCESS)				\
+  do {						\
+    char *p = ALLOC (N);			\
+    ACCESS;					\
+    sink (p);					\
+  } while (0)
+
+
+void test_byte_store_malloc (void)
+{
+#define ALLOC __builtin_malloc
+
+  T (1, p[-1] = 0);
+  // { dg-warning "writing 1 byte into a region of size 0 " "warning" { target *-*-* } .-1 }
+  // { dg-message "at offset -1 to an object with size 1 allocated by '__builtin_malloc'" "note" { target *-*-* } .-2 }
+
+  T (1, p[ 0] = 0);
+
+  T (1, p[ 1] = 0);
+  // { dg-warning "writing 1 byte into a region of size 0 " "warning" { target *-*-* } .-1 }
+  // { dg-message "at offset 1 to an object with size 1 allocated by '__builtin_malloc'" "note" { target *-*-* } .-2 }
+
+  T (1, p[ 2] = 0);
+  // { dg-warning "writing 1 byte into a region of size 0 " "warning" { target *-*-* } .-1 }
+  // { dg-message "at offset 2 to an object with size 1 allocated by '__builtin_malloc'" "note" { target *-*-* } .-2 }
+
+  T (9, p[-2] = 0);
+  // { dg-warning "writing 1 byte into a region of size 0 " "warning" { target *-*-* } .-1 }
+  // { dg-message "at offset -2 to an object with size 9 allocated by '__builtin_malloc'" "note" { target *-*-* } .-2 }
+
+  T (9, p[ 0] = 0);
+  T (9, p[ 8] = 0);
+  T (9, p[ 9] = 0);         // { dg-warning "\\\[-Wstringop-overflow" }
+
+  // Exercise boundary conditions.
+  T (1, p[DIFF_MIN] = 0);   // { dg-warning "\\\[-Wstringop-overflow" }
+  // { dg-message "at offset -\[1-9\]\[0-9\]+ to an object with size 1 allocated by '__builtin_malloc'" "note" { target *-*-* } .-1 }
+  T (1, p[INT_MIN] = 0);    // { dg-warning "\\\[-Wstringop-overflow" }
+  T (1, p[DIFF_MAX] = 0);   // { dg-warning "\\\[-Wstringop-overflow" }
+  T (1, p[SIZE_MAX] = 0);   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  size_t i = UR (3, 5);
+
+  T (1, p[i] = 0);          // { dg-warning "\\\[-Wstringop-overflow" }
+  // { dg-message "at offset \\\[3, 5] to an object with size 1 allocated by '__builtin_malloc'" "note" { target *-*-* } .-1 }
+  T (3, p[i] = 0);          // { dg-warning "\\\[-Wstringop-overflow" }
+  T (4, p[i] = 0);
+
+  size_t n = UR (6, 7);
+
+  T (n, p[-1] = 0);         // { dg-warning "\\\[-Wstringop-overflow" }
+  // { dg-message "at offset -1 to an object with size between 6 and 7 allocated by '__builtin_malloc'" "note" { target *-*-* } .-1 }
+  T (n, p[ 0] = 0);
+  T (n, p[ 6] = 0);
+  T (n, p[ 7] = 0);         // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (n, p[i] = 0);
+  T (n, p[i + 3] = 0);
+  T (n, p[i + 4] = 0);      // { dg-warning "\\\[-Wstringop-overflow" }
+  // { dg-message "at offset \\\[7, 9] to an object with size between 6 and 7 allocated by '__builtin_malloc' here" "note" { target *-*-* } .-1 }
+  T (n + 1, p[i + 4] = 0);
+
+  char s[] = "123";
+  n = strlen (s);
+  T (n, p[n] = 0);          // { dg-warning "\\\[-Wstringop-overflow" "pr?????" { xfail *-*-* } }
+  // { dg-message "at offset 3 to an object with size 3 allocated by '__builtin_malloc' here" "note" { xfail *-*-* } .-1 }
+}
+
+
+void test_byte_store_alloca (void)
+{
+#undef ALLOC
+#define ALLOC __builtin_alloca
+
+  T (1, p[-1] = 0);   // { dg-warning "writing 1 byte into a region of size 0 " }
+  T (1, p[ 0] = 0);
+  T (1, p[ 1] = 0);         // { dg-warning "\\\[-Wstringop-overflow" }
+  T (1, p[ 2] = 0);         // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (9, p[-1] = 0);         // { dg-warning "\\\[-Wstringop-overflow" }
+  T (9, p[ 0] = 0);
+  T (9, p[ 8] = 0);
+  T (9, p[ 9] = 0);         // { dg-warning "\\\[-Wstringop-overflow" }
+
+  // Exercise boundary conditions.
+  T (1, p[DIFF_MIN] = 0);   // { dg-warning "\\\[-Wstringop-overflow" }
+  T (1, p[INT_MIN] = 0);    // { dg-warning "\\\[-Wstringop-overflow" }
+  T (1, p[DIFF_MAX] = 0);   // { dg-warning "\\\[-Wstringop-overflow" }
+  T (1, p[SIZE_MAX] = 0);   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  size_t i = UR (3, 5);
+
+  T (1, p[i] = 0);          // { dg-warning "\\\[-Wstringop-overflow" }
+  T (3, p[i] = 0);          // { dg-warning "\\\[-Wstringop-overflow" }
+  T (4, p[i] = 0);
+
+  size_t n = UR (6, 7);
+
+  T (n, p[-1] = 0);         // { dg-warning "\\\[-Wstringop-overflow" }
+  T (n, p[ 0] = 0);
+  T (n, p[ 6] = 0);
+  T (n, p[ 7] = 0);         // { dg-warning "\\\[-Wstringop-overflow" }
+
+  char s[] = "1234";
+  n = strlen (s);
+  T (n, p[n] = 0);          // { dg-warning "\\\[-Wstringop-overflow" "pr?????" { xfail *-*-* } }
+  // { dg-message "at offset 4 to an object with size 4 allocated by '__builtin_alloca' here" "note" { xfail *-*-* } .-1 }
+}
+
+
+void test_byte_store_vla (void)
+{
+  {
+    size_t n = 3;
+    char vla[n];            // { dg-message "at offset 3 to object 'vla\[^\\n\\r\'\]*' with size 3 declared here" "note" }
+    vla[3] = 0;             // { dg-warning "writing 1 byte into a region of size 0 \\\[-Wstringop-overflow=]" }
+    sink (vla);
+  }
+
+#define VLA(N, ACCESS)				\
+  do {						\
+    size_t nelts = N;				\
+    char a[nelts];				\
+    ACCESS;					\
+    sink (a);					\
+  } while (0)
+
+  VLA (1, a[-2] = 0);       // { dg-warning "writing 1 byte into a region of size 0 " }
+  VLA (1, a[-1] = 0);       // { dg-warning "writing 1 byte into a region of size 0 " }
+  VLA (1, a[ 0] = 0);
+  VLA (1, a[ 1] = 0);       // { dg-warning "\\\[-Wstringop-overflow" }
+  VLA (1, a[ 2] = 0);       // { dg-warning "\\\[-Wstringop-overflow" }
+
+  VLA (9, a[-10] = 0);      // { dg-warning "\\\[-Wstringop-overflow" }
+  // The following fails because the no-warning bit is set on the store
+  // for some unknown reason.
+  VLA (9, a[-1] = 0);       // { dg-warning "\\\[-Wstringop-overflow" }
+  VLA (9, a[ 0] = 0);
+  VLA (9, a[ 8] = 0);
+  VLA (9, a[ 9] = 0);       // { dg-warning "\\\[-Wstringop-overflow" }
+
+  // Exercise boundary conditions.
+  VLA (1, a[DIFF_MIN] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+  VLA (1, a[INT_MIN] = 0);  // { dg-warning "\\\[-Wstringop-overflow" }
+  VLA (1, a[DIFF_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+  VLA (1, a[SIZE_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" }
+
+  {
+    int i = SR (-5, -1);
+
+    {
+      size_t n = 3;
+      char vla[n];          // { dg-message "at offset \\\[-5, -1] to object 'vla\[^\n\r\]*' with size 3 declared here" "note" }
+      vla[i] = 0;           // { dg-warning "writing 1 byte into a region of size 0" }
+      sink (vla);
+    }
+
+    VLA (1, a[i] = 0);      // { dg-warning "\\\[-Wstringop-overflow" }
+    VLA (3, a[i] = 0);      // { dg-warning "\\\[-Wstringop-overflow" }
+    VLA (4, a[i] = 0);      // { dg-warning "\\\[-Wstringop-overflow" }
+  }
+
+  {
+    int i = SR (3, 5);
+
+    {
+      size_t n = 3;
+      char vla[n];          // { dg-message "at offset \\\[3, 5] to object 'vla\[^\n\r\]*' with size 3 declared here" "note" }
+      vla[i] = 0;           // { dg-warning "writing 1 byte into a region of size 0" }
+      sink (vla);
+    }
+
+    VLA (1, a[i] = 0);      // { dg-warning "\\\[-Wstringop-overflow" }
+    VLA (3, a[i] = 0);      // { dg-warning "\\\[-Wstringop-overflow" }
+    VLA (4, a[i] = 0);
+  }
+
+  {
+    size_t i = UR (3, 5);
+
+    {
+      size_t n = 3;
+      char vla[n];          // { dg-message "at offset \\\[3, 5] to object 'vla\[^\n\r\]*' with size 3 declared here" "note" }
+      vla[i] = 0;           // { dg-warning "writing 1 byte into a region of size 0" }
+      sink (vla);
+    }
+
+    VLA (1, a[i] = 0);      // { dg-warning "\\\[-Wstringop-overflow" }
+    VLA (3, a[i] = 0);      // { dg-warning "\\\[-Wstringop-overflow" }
+    VLA (4, a[i] = 0);
+  }
+
+  size_t n = UR (6, 7);
+
+  VLA (n, a[-1] = 0);       // { dg-warning "\\\[-Wstringop-overflow" }
+  VLA (n, a[ 0] = 0);
+  VLA (n, a[ 6] = 0);
+  VLA (n, a[ 7] = 0);       // { dg-warning "\\\[-Wstringop-overflow" }
+}
+
+void test_byte_store_vla_strlen (void)
+{
+  char s[] = "12345";
+  size_t n = strlen (s);
+  VLA (n, a[n] = 0);        // { dg-warning "\\\[-Wstringop-overflow" "pr?????" { xfail *-*-* } }
+  // { dg-message "at offset 5 to an object with size 5 declared here" "note" { xfail *-*-* } .-1 }
+}
+
+
+void test_byte_store_alloc_size (void)
+{
+  extern __attribute__ ((alloc_size (1), malloc)) void*
+    alloc_1_size (size_t);
+
+  {
+    size_t n = 3;
+    char *p = alloc_1_size (n);   // { dg-message "at offset 3 to an object with size 3 allocated by 'alloc_1_size' here" "note" }
+    p[3] = 0;               // { dg-warning "writing 1 byte into a region of size 0 \\\[-Wstringop-overflow=]" }
+    sink (p);
+  }
+
+  extern __attribute__ ((alloc_size (2), malloc)) void*
+    alloc_2_int (size_t, int);
+
+  {
+    int n = 5;
+    char *p = alloc_2_int (9, n);
+    p[4] = 0;
+    sink (p);
+  }
+
+  extern __attribute__ ((alloc_size (2), malloc)) void*
+    alloc_2_int (size_t, int);
+
+  {
+    int n = 5;
+    char *p = alloc_2_int (9, n);
+    p[4] = 0;
+    // The access below was not diagnosed because GCC didn't recognize
+    // that the pointer returned by malloc functions doesn't alias any
+    // live object in the process.  I.e., it assumed that the store to
+    // p[4] above modified p itself.  This is pr87313 but now it is
+    // diagnosed so something might be off with the analysis.
+    p[5] = 0;               // { dg-warning "writing 1 byte into a region of size 0 \\\[-Wstringop-overflow=]" }
+   sink (p);
+  }
+
+  {
+    int n = 5;
+    char *p = alloc_2_int (9, n);    // { dg-message "at offset 5 to an object with size 5 allocated by 'alloc_2_int' here" "note" }
+    p[5] = 0;               // { dg-warning "writing 1 byte into a region of size 0 \\\[-Wstringop-overflow=]" }
+    sink (p);
+  }
+
+#undef ALLOC
+#define ALLOC alloc_1_size
+
+  T (1, p[-1] = 0);   // { dg-warning "writing 1 byte into a region of size 0 " }
+  T (1, p[ 0] = 0);
+  T (1, p[ 1] = 0);         // { dg-warning "\\\[-Wstringop-overflow" }
+  T (1, p[ 2] = 0);         // { dg-warning "\\\[-Wstringop-overflow" }
+
+  T (9, p[-1] = 0);         // { dg-warning "\\\[-Wstringop-overflow" }
+  T (9, p[ 0] = 0);
+  T (9, p[ 8] = 0);
+  T (9, p[ 9] = 0);         // { dg-warning "\\\[-Wstringop-overflow" }
+
+  // Exercise boundary conditions.
+  T (1, p[DIFF_MIN] = 0);   // { dg-warning "\\\[-Wstringop-overflow" }
+  T (1, p[INT_MIN] = 0);    // { dg-warning "\\\[-Wstringop-overflow" }
+  T (1, p[DIFF_MAX] = 0);   // { dg-warning "\\\[-Wstringop-overflow" }
+  T (1, p[SIZE_MAX] = 0);   // { dg-warning "\\\[-Wstringop-overflow" }
+
+  size_t i = UR (3, 5);
+
+  T (1, p[i] = 0);          // { dg-warning "\\\[-Wstringop-overflow" }
+  T (3, p[i] = 0);          // { dg-warning "\\\[-Wstringop-overflow" }
+  T (4, p[i] = 0);
+
+  size_t n = UR (6, 7);
+
+  T (n, p[-1] = 0);         // { dg-warning "\\\[-Wstringop-overflow" }
+  T (n, p[ 0] = 0);
+  T (n, p[ 6] = 0);
+  T (n, p[ 7] = 0);         // { dg-warning "\\\[-Wstringop-overflow" }
+}
+
+
+void test_memset_malloc (void)
+{
+#undef ALLOC
+#define ALLOC __builtin_malloc
+
+  T (0, memset (p, 0, 0));
+  T (0, memset (p, 0, 1));  // { dg-warning "writing 1 byte into a region of size 0 " }
+
+  T (0, memset (p, 1, 0));
+  T (0, memset (p, 1, 1));  // { dg-warning "writing 1 byte into a region of size 0 " }
+
+  T (3, memset (p, 2, 0));
+  T (3, memset (p, 2, 1));
+  T (3, memset (p, 2, 3));
+  T (3, memset (p, 2, 4));  // { dg-warning "writing 4 bytes into a region of size 3 " }
+
+  T (3, memset (p + 1, 2, 1));
+  T (3, memset (p + 1, 3, 2));
+  T (3, memset (p + 1, 4, 3));  // { dg-warning "writing 3 bytes into a region of size 2 " }
+
+  T (4, memset (&p[2], 2, 2));
+  T (4, memset (&p[2], 4, 3));  // { dg-warning "writing 3 bytes into a region of size 2 " }
+
+  T (3, memset (p, 5, INT_MAX));  // { dg-warning "\\\[-Wstringop-overflow" }
+
+  size_t n = UR (3, 5);
+
+  T (n, memset (p, 6, 0));
+  T (n, memset (p, 7, 1));
+  T (n, memset (p, 8, 3));
+  T (n, memset (p, 9, 4));
+  T (n, memset (p, 0, 5));
+  T (n, memset (p, 1, 6));  // { dg-warning "writing 6 bytes into a region of size between 3 and 5" }
+
+  int i = SR (-32767, 32767);
+  T (n, memset (p + i, 0, 1));
+  T (n, memset (p + i, 0, 3));
+  T (n, memset (p + i, 0, 5));
+  T (n, memset (p + i, 0, 6));    // { dg-warning "writing 6 bytes into a region of size between 3 and 5" }
+}
+
+
+#define S(N) ("0123456789" + 10 - N)
+
+const char str0[] = "";
+const char str1[] = "0";
+const char str2[] = "01";
+
+void test_strcpy_malloc (const void *s, size_t n)
+{
+  const char s0[] = "";
+  const char s1[] = "0";
+  const char s2[] = "01";
+
+  // Each of the strcpy calls below is represented differently.  Verify
+  // that they are all handled.
+  //   __builtin_memcpy (p_6, "", 1);
+  T (0, strcpy (p, ""));      // { dg-warning "writing 1 byte into a region of size 0 " }
+  //   MEM[(char * {ref-all})p_10] = 0;
+  T (0, strcpy (p, S (0)));   // { dg-warning "writing 1 byte into a region of size 0 " }
+  //   MEM[(char * {ref-all})p_14] = MEM[(char * {ref-all})&str0];
+  T (0, strcpy (p, str0));    // { dg-warning "writing 1 byte into a region of size 0 " }
+  //   s0 = "";
+  //   __builtin_memcpy (p_18, &s0, 1);
+  T (0, strcpy (p, s0));      // { dg-warning "writing 1 byte into a region of size 0 " }
+
+  T (1, strcpy (p, ""));
+  T (1, strcpy (p, S (0)));
+  T (1, strcpy (p, str0));
+  T (1, strcpy (p, s0));
+
+  T (1, strcpy (p, "1"));     // { dg-warning "writing 2 bytes into a region of size 1 " }
+  //   __builtin_memcpy (p_42, &MEM <char[11]> [(void *)"0123456789" + 9B], 2);
+  T (1, strcpy (p, S (1)));   // { dg-warning "writing 2 bytes into a region of size 1 " }
+  //   MEM <unsigned char[2]> [(char * {ref-all})p_46] = MEM <unsigned char[2]> [(char * {ref-all})&str1];
+  T (1, strcpy (p, str1));    // { dg-warning "writing 2 bytes into a region of size 1 " }
+  T (1, strcpy (p, s1));      // { dg-warning "writing 2 bytes into a region of size 1 " }
+
+  T (2, strcpy (p, S (0)));
+  T (2, strcpy (p, S (2)));   // { dg-warning "writing 3 bytes into a region of size 2 " }
+  T (2, strcpy (p, str2));    // { dg-warning "writing 3 bytes into a region of size 2 " }
+  T (2, strcpy (p, s2));      // { dg-warning "writing 3 bytes into a region of size 2 " }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c
new file mode 100644
index 00000000000..249ce2b6ad5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c
@@ -0,0 +1,293 @@ 
+/* PR middle-end/91582 - missing heap overflow detection for strcpy
+   PR middle-end/85484 - missing -Wstringop-overflow for strcpy with
+   a string of non-const length
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wno-array-bounds" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern void* calloc (size_t, size_t);
+extern void* malloc (size_t);
+extern void* memcpy (void*, const void*, size_t);
+extern void* memset (void*, int, size_t);
+extern char* strcpy (char*, const char*);
+extern size_t strlen (const char*);
+
+void sink (void*);
+
+
+void test_memcpy_nowarn (const void *s, int i, size_t n)
+{
+  sink (memcpy (calloc (1, 1), s, 1));
+  sink (memcpy (calloc (1, 2), s, 1));
+  sink (memcpy (calloc (2, 1), s, 1));
+  sink (memcpy (calloc (3, 1), s, 2));
+  sink (memcpy (calloc (3, 1), "12", 2));
+  sink (memcpy (calloc (3, 1), s, 3));
+  sink (memcpy (calloc (3, 1), "12", 3));
+  sink (memcpy (calloc (i, 1), s, 1));
+  sink (memcpy (calloc (n, 1), s, 1));
+  sink (memcpy (calloc (1, n), "", 1));
+  sink (memcpy (calloc (1, i), "", 1));
+  sink (memcpy (calloc (i, 1), "123", 3));
+  sink (memcpy (calloc (n, 1), "123", 3));
+  sink (memcpy (calloc (1, i), "123456", 7));
+  sink (memcpy (calloc (1, n), "123456", 7));
+  sink (memcpy (calloc (n, 1), s, 12345));
+  sink (memcpy (calloc (1, n), s, n - 1));
+  sink (memcpy (calloc (n, 1), s, n));
+
+  sink (memcpy ((char*)calloc (1, 1) + i, "123", 1));
+  sink (memcpy ((char*)calloc (n, 1) + i, "123", n));
+
+  sink (memcpy ((char*)calloc (1, 1) + i, s, 1));
+  sink (memcpy ((char*)calloc (n, 1) + i, s, n));
+
+  sink (memcpy (malloc (1), s, 1));
+  sink (memcpy (malloc (2), s, 1));
+  sink (memcpy (malloc (3), s, 2));
+  sink (memcpy (malloc (3), "12", 2));
+  sink (memcpy (malloc (3), s, 3));
+  sink (memcpy (malloc (3), "12", 3));
+  sink (memcpy (malloc (n), s, 1));
+  sink (memcpy (malloc (n), "", 1));
+  sink (memcpy (malloc (n), "123", 3));
+  sink (memcpy (malloc (n), "123456", 7));
+  sink (memcpy (malloc (n), s, 12345));
+  sink (memcpy (malloc (n), s, n - 1));
+  sink (memcpy (malloc (n), s, n));
+
+  {
+    const int a[] = { 1, 2, 3, 4 };
+    void *p = (char*)malloc (sizeof a);
+    memcpy (p, a, sizeof a);
+    sink (p);
+  }
+
+  {
+    const int a[] = { 1, 2, 3, 4, 5 };
+    size_t nelts = sizeof a / sizeof *a;
+    int vla[nelts];
+    memcpy (vla, a, nelts * sizeof *vla);
+    sink (vla);
+  }
+}
+
+
+void test_memcpy_warn (const int *s, size_t n)
+{
+  {
+    void *p = (char*)malloc (0);
+    memcpy (p, s, 1);                    // { dg-warning "writing 1 byte into a region of size 0" }
+    sink (p);
+  }
+
+  {
+    void *p = (char*)malloc (1);
+    memcpy (p, s, 2);                    // { dg-warning "writing 2 bytes into a region of size 1" }
+    sink (p);
+  }
+
+  {
+    void *p = (char*)malloc (2);
+    memcpy (p, s, 3);                    // { dg-warning "writing 3 bytes into a region of size 2" }
+    sink (p);
+  }
+
+  {
+    void *p = (char*)malloc (3);
+    memcpy (p, s, 4);                    // { dg-warning "writing 4 bytes into a region of size 3" }
+    sink (p);
+  }
+
+  {
+    const int a[] = { 1, 2, 3, 4 };
+    void *p = (char*)malloc (sizeof *a);
+    memcpy (p, a, sizeof a);              // { dg-warning "" }
+    sink (p);
+  }
+
+  {
+    const int a[] = { 1, 2, 3, 4, 5 };
+    size_t nelts = sizeof a / sizeof *a;
+    char vla[nelts];
+    memcpy (vla, a, nelts * sizeof *a);   // { dg-warning "" }
+    sink (vla);
+  }
+
+  {
+    void *p = malloc (n);
+    memcpy (p, s, n * sizeof *s);         // { dg-warning "\\\[-Wstringop-overflow" "" { xfail *-*-* } }
+    sink (p);
+  }
+}
+
+void test_memset_nowarn (int x, size_t n)
+{
+  sink (memset (calloc (1, 1), x, 1));
+  sink (memset (calloc (1, 2), x, 1));
+  sink (memset (calloc (2, 1), x, 1));
+  sink (memset (calloc (3, 1), x, 2));
+  sink (memset (calloc (3, 1), x, 3));
+  sink (memset (calloc (n, 1), x, 1));
+  sink (memset (calloc (n, 1), x, 12345));
+  sink (memset (calloc (1, n), x, n - 1));
+  sink (memset (calloc (n, 1), x, n));
+
+  sink (memset (malloc (1), x, 1));
+  sink (memset (malloc (2), x, 1));
+  sink (memset (malloc (3), x, 2));
+  sink (memset (malloc (3), x, 3));
+  sink (memset (malloc (n), x, 1));
+  sink (memset (malloc (n), x, 12345));
+  sink (memset (malloc (n), x, n - 1));
+  sink (memset (malloc (n), x, n));
+
+  {
+    const int a[] = { 1, 2, 3, 4 };
+    void *p = (char*)malloc (sizeof a);
+    memset (p, x, sizeof a);
+    sink (p);
+  }
+
+  {
+    const int a[] = { 1, 2, 3, 4, 5 };
+    size_t nelts = sizeof a / sizeof *a;
+    int vla[nelts];
+    memset (vla, x, nelts * sizeof *vla);
+    sink (vla);
+  }
+}
+
+
+void test_memset_warn (int x, size_t n)
+{
+  {
+    void *p = (char*)malloc (0);
+    memset (p, x, 1);                    // { dg-warning "writing 1 byte into a region of size 0" }
+    sink (p);
+  }
+
+  {
+    void *p = (char*)malloc (1);
+    memset (p, x, 2);                    // { dg-warning "writing 2 bytes into a region of size 1" }
+    sink (p);
+  }
+
+  {
+    void *p = (char*)malloc (2);
+    memset (p, x, 3);                    // { dg-warning "writing 3 bytes into a region of size 2" }
+    sink (p);
+  }
+
+  {
+    void *p = (char*)malloc (3);
+    memset (p, x, 4);                    // { dg-warning "writing 4 bytes into a region of size 3" }
+    sink (p);
+  }
+
+  {
+    const int a[] = { 1, 2, 3, 4 };
+    void *p = (char*)malloc (sizeof *a);
+    memset (p, 0, sizeof a);              // { dg-warning "" }
+    sink (p);
+  }
+
+  {
+    const int a[] = { 1, 2, 3, 4, 5 };
+    size_t nelts = sizeof a / sizeof *a;
+    char vla[nelts];
+    memset (vla, 0, nelts * sizeof *a);   // { dg-warning "" }
+    sink (vla);
+  }
+
+  {
+    void *p = malloc (n);
+    memset (p, x, n * sizeof (int));      // { dg-warning "\\\[-Wstringop-overflow" "" { xfail *-*-* } }
+    sink (p);
+  }
+}
+
+
+void test_strcpy_nowarn (const char *s)
+{
+  {
+    const char a[] = "12";
+    int n = strlen (a);
+    char *t = (char*)calloc (2, n);
+    strcpy (t, a);
+    sink (t);
+  }
+
+  {
+    const char a[] = "123";
+    unsigned n = strlen (a) + 1;
+    char *t = (char*)calloc (n, 1);
+    strcpy (t, a);
+    sink (t);
+  }
+
+  {
+    const char a[] = "1234";
+    size_t n = strlen (a) * 2;
+    char *t = (char*)malloc (n);
+    strcpy (t, a);
+    sink (t);
+  }
+
+  {
+    const char a[] = "1234";
+    size_t len = strlen (a) + 1;
+    char vla[len];
+    strcpy (vla, a);
+    sink (vla);
+  }
+
+  {
+    size_t n = strlen (s) + 1;
+    char *t = (char*)malloc (n);
+    strcpy (t, s);
+    sink (t);
+  }
+}
+
+
+void test_strcpy_warn (const char *s)
+{
+  {
+    const char a[] = "123";
+    /* Verify that using signed int for the strlen result works (i.e.,
+       that the conversion from signed int to size_t doesn't prevent
+       the detection.  */
+    int n = strlen (a);
+    char *t = (char*)calloc (n, 1);     // { dg-message "at offset 0 to an object with size 3 allocated by 'calloc' here" "calloc note" { xfail *-*-* } }
+                                        // { dg-message "at offset 0 to an object with size at most 3 allocated by 'calloc' here" "calloc note" { target *-*-* } .-1 }
+    strcpy (t, a);                      // { dg-warning "writing 4 bytes into a region of size (between 0 and )?3 " }
+
+    sink (t);
+  }
+
+  {
+    const char a[] = "1234";
+    size_t n = strlen (a);
+    char *t = (char*)malloc (n);        // { dg-message "at offset 0 to an object with size 4 allocated by 'malloc' here" "malloc note" { xfail *-*-* } }
+                                        // { dg-message "at offset 0 to an object with size at most 4 allocated by 'malloc' here" "malloc note" { target *-*-* } .-1 }
+    strcpy (t, a);                      // { dg-warning "writing 5 bytes into a region of size (between 0 and )?4 " }
+    sink (t);
+  }
+
+  // Exercise PR middle-end/85484.
+  {
+    size_t len = strlen (s);
+    char vla[len];                      // { dg-message "at offset 0 to an object declared here" "vla note" }
+    strcpy (vla, s);                    // { dg-warning "writing one too many bytes into a region of a size that depends on 'strlen'" }
+    sink (vla);
+  }
+
+  {
+    size_t n = strlen (s);
+    char *t = (char*)malloc (n);        // { dg-message "at offset 0 to an object allocated by 'malloc' here" "malloc note" }
+    strcpy (t, s);                      // { dg-warning "writing one too many bytes into a region of a size that depends on 'strlen'" }
+    sink (t);
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-28.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-28.c
new file mode 100644
index 00000000000..861c9206be4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-28.c
@@ -0,0 +1,32 @@ 
+/* Simple test to verify that constants computed by the strlen pass
+   are propagated through the CFG and subsequently made available
+   within the pass.
+  { dg-do compile }
+  { dg-options "-O2 -Wall " } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern void* calloc (size_t, size_t);
+extern void* malloc (size_t);
+extern void* memcpy (void*, const void*, size_t);
+extern void* memset (void*, int, size_t);
+extern char* strcpy (char*, const char*);
+extern size_t strlen (const char*);
+
+void sink (void*);
+
+void test_strlen_memcpy (void)
+{
+  char a[8] = "1234567";
+  char b[7];
+
+  // The value of N is computed to be 7 below.  Verify that it's
+  // available in the store to B[N] as well as in B[N + 1].
+  size_t n = strlen (a);
+  memcpy (b, a, n);
+  b[n] = 0;         // { dg-warning "writing 1 byte into a region of size 0" "constant propagation" { xfail *-*-* } }
+  sink (b);
+
+  b[n + 1] = 0;     // { dg-warning "writing 1 byte into a region of size 0" "constant propagation" { xfail *-*-* } }
+  sink (b);
+}
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
index 212ac7152bf..525bb6550b4 100644
--- a/gcc/tree-ssa-strlen.c
+++ b/gcc/tree-ssa-strlen.c
@@ -61,6 +61,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "vr-values.h"
 #include "gimple-ssa-evrp-analyze.h"
 
+#pragma GCC optimize ("0")
 /* A vector indexed by SSA_NAME_VERSION.  0 means unknown, positive value
    is an index into strinfo vector, negative value stands for
    string length of a string literal (~strlen).  */
@@ -84,14 +85,20 @@  struct strinfo
   tree nonzero_chars;
   /* Any of the corresponding pointers for querying alias oracle.  */
   tree ptr;
-  /* This is used for two things:
+  /* STMT is used for two things:
 
      - To record the statement that should be used for delayed length
        computations.  We maintain the invariant that all related strinfos
        have delayed lengths or none do.
 
-     - To record the malloc or calloc call that produced this result.  */
+     - To record the malloc or calloc call that produced this result
+       to optimize away malloc/memset sequences.  STMT is reset after
+       a calloc-allocated object has been stored a non-zero value into.  */
   gimple *stmt;
+  /* Set to the dynamic allocation statement for the object (alloca,
+     calloc, malloc, or VLA).  Unlike STMT, once set for a strinfo
+     object, ALLOC doesn't change.  */
+  gimple *alloc;
   /* Pointer to '\0' if known, if NULL, it can be computed as
      ptr + length.  */
   tree endptr;
@@ -189,20 +196,20 @@  static int get_stridx_plus_constant (strinfo *, unsigned HOST_WIDE_INT, tree);
 static void handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *);
 
 /* Sets MINMAX to either the constant value or the range VAL is in
-   and returns true on success.  When nonnull, uses RVALS to get
-   VAL's range.  Otherwise uses get_range_info.  */
+   and returns either the constant value or VAL on success or null
+   when the range couldn't be determined .  */
 
-static bool
-get_range (tree val, wide_int minmax[2], const vr_values *rvals = NULL)
+tree
+get_range (tree val, wide_int minmax[2], const vr_values *rvals /* = NULL */)
 {
-  if (tree_fits_uhwi_p (val))
+  if (TREE_CODE (val) == INTEGER_CST)
     {
       minmax[0] = minmax[1] = wi::to_wide (val);
-      return true;
+      return val;
     }
 
   if (TREE_CODE (val) != SSA_NAME)
-    return false;
+    return NULL_TREE;
 
   if (rvals)
     {
@@ -215,20 +222,20 @@  get_range (tree val, wide_int minmax[2], const vr_values *rvals = NULL)
 	= (CONST_CAST (class vr_values *, rvals)->get_value_range (val));
       value_range_kind rng = vr->kind ();
       if (rng != VR_RANGE || !range_int_cst_p (vr))
-	return false;
+	return NULL_TREE;
 
       minmax[0] = wi::to_wide (vr->min ());
       minmax[1] = wi::to_wide (vr->max ());
-      return true;
+      return val;
     }
 
   value_range_kind rng = get_range_info (val, minmax, minmax + 1);
   if (rng == VR_RANGE)
-    return true;
+    return val;
 
   /* Do not handle anti-ranges and instead make use of the on-demand
      VRP if/when it becomes available (hopefully in GCC 11).  */
-  return false;
+  return NULL_TREE;
 }
 
 /* Return:
@@ -383,10 +390,10 @@  get_addr_stridx (tree exp, tree ptr, unsigned HOST_WIDE_INT *offset_out,
    must not be used in for functions that modify the string.  */
 
 static int
-get_stridx (tree exp, wide_int offrng[2] = NULL)
+get_stridx (tree exp, wide_int offrng[2] = NULL, const vr_values *rvals = NULL)
 {
   if (offrng)
-    offrng[0] = offrng[1] = wi::zero (TYPE_PRECISION (sizetype));
+    offrng[0] = offrng[1] = wi::zero (TYPE_PRECISION (ptrdiff_type_node));
 
   if (TREE_CODE (exp) == SSA_NAME)
     {
@@ -465,7 +472,7 @@  get_stridx (tree exp, wide_int offrng[2] = NULL)
 		       return the index corresponding to the SSA_NAME.
 		       Do this irrespective of the whether the offset
 		       is known.  */
-		    if (get_range (off, offrng))
+		    if (get_range (off, offrng, rvals))
 		      {
 			/* When the offset range is known, increment it
 			   it by the constant offset computed in prior
@@ -672,6 +679,7 @@  new_strinfo (tree ptr, int idx, tree nonzero_chars, bool full_string_p)
   si->nonzero_chars = nonzero_chars;
   si->ptr = ptr;
   si->stmt = NULL;
+  si->alloc = NULL;
   si->endptr = NULL_TREE;
   si->refcount = 1;
   si->idx = idx;
@@ -838,6 +846,8 @@  get_string_length (strinfo *si)
 	    if (chainsi->nonzero_chars == NULL)
 	      set_endptr_and_length (loc, chainsi, lhs);
 	  break;
+	case BUILT_IN_ALLOCA:
+	case BUILT_IN_ALLOCA_WITH_ALIGN:
 	case BUILT_IN_MALLOC:
 	  break;
 	/* BUILT_IN_CALLOC always has si->nonzero_chars set.  */
@@ -885,45 +895,57 @@  dump_strlen_info (FILE *fp, gimple *stmt, const vr_values *rvals)
 		  fprintf (fp, ", ptr = ");
 		  print_generic_expr (fp, si->ptr);
 		}
-	      fprintf (fp, ", nonzero_chars = ");
-	      print_generic_expr (fp, si->nonzero_chars);
-	      if (TREE_CODE (si->nonzero_chars) == SSA_NAME)
+
+	      if (si->nonzero_chars)
 		{
-		  value_range_kind rng = VR_UNDEFINED;
-		  wide_int min, max;
-		  if (rvals)
+		  fprintf (fp, ", nonzero_chars = ");
+		  print_generic_expr (fp, si->nonzero_chars);
+		  if (TREE_CODE (si->nonzero_chars) == SSA_NAME)
 		    {
-		      const value_range_equiv *vr
-			= CONST_CAST (class vr_values *, rvals)
-			->get_value_range (si->nonzero_chars);
-		      rng = vr->kind ();
-		      if (range_int_cst_p (vr))
+		      value_range_kind rng = VR_UNDEFINED;
+		      wide_int min, max;
+		      if (rvals)
 			{
-			  min = wi::to_wide (vr->min ());
-			  max = wi::to_wide (vr->max ());
+			  const value_range *vr
+			    = CONST_CAST (class vr_values *, rvals)
+			    ->get_value_range (si->nonzero_chars);
+			  rng = vr->kind ();
+			  if (range_int_cst_p (vr))
+			    {
+			      min = wi::to_wide (vr->min ());
+			      max = wi::to_wide (vr->max ());
+			    }
+			  else
+			    rng = VR_UNDEFINED;
 			}
 		      else
-			rng = VR_UNDEFINED;
-		    }
-		  else
-		    rng = get_range_info (si->nonzero_chars, &min, &max);
+			rng = get_range_info (si->nonzero_chars, &min, &max);
 
-		  if (rng == VR_RANGE || rng == VR_ANTI_RANGE)
-		    {
-		      fprintf (fp, " %s[%llu, %llu]",
-			       rng == VR_RANGE ? "" : "~",
-			       (long long) min.to_uhwi (),
-			       (long long) max.to_uhwi ());
+		      if (rng == VR_RANGE || rng == VR_ANTI_RANGE)
+			{
+			  fprintf (fp, " %s[%llu, %llu]",
+				   rng == VR_RANGE ? "" : "~",
+				   (long long) min.to_uhwi (),
+				   (long long) max.to_uhwi ());
+			}
 		    }
 		}
-	      fprintf (fp, " , refcount = %i", si->refcount);
+
+	      fprintf (fp, ", refcount = %i", si->refcount);
 	      if (si->stmt)
 		{
 		  fprintf (fp, ", stmt = ");
 		  print_gimple_expr (fp, si->stmt, 0);
 		}
+	      if (si->alloc)
+		{
+		  fprintf (fp, ", alloc = ");
+		  print_gimple_expr (fp, si->alloc, 0);
+		}
 	      if (si->writable)
 		fprintf (fp, ", writable");
+	      if (si->dont_invalidate)
+		fprintf (fp, ", dont_invalidate");
 	      if (si->full_string_p)
 		fprintf (fp, ", full_string_p");
 	      if (strinfo *next = get_next_strinfo (si))
@@ -1197,80 +1219,87 @@  get_range_strlen_dynamic (tree src, c_strlen_data *pdata,
     BITMAP_FREE (visited);
 }
 
-/* Invalidate string length information for strings whose length
-   might change due to stores in stmt, except those marked DON'T
-   INVALIDATE.  For string-modifying statements, ZERO_WRITE is
-   set when the statement wrote only zeros.  */
+/* Invalidate string length information for strings whose length might
+   change due to stores in STMT, except those marked DONT_INVALIDATE.
+   For string-modifying statements, ZERO_WRITE is set when the statement
+   wrote only zeros.
+   Returns true if any STRIDX_TO_STRINFO entries were considered
+   for invalidation.  */
 
 static bool
 maybe_invalidate (gimple *stmt, bool zero_write = false)
 {
   if (dump_file && (dump_flags & TDF_DETAILS))
-    fprintf (dump_file, "  %s()\n", __func__);
+    {
+      fprintf (dump_file, "%s called for ", __func__);
+      print_gimple_stmt (dump_file, stmt, TDF_LINENO);
+    }
 
   strinfo *si;
-  unsigned int i;
   bool nonempty = false;
 
-  for (i = 1; vec_safe_iterate (stridx_to_strinfo, i, &si); ++i)
-    if (si != NULL)
-      {
-	if (!si->dont_invalidate)
-	  {
-	    ao_ref r;
-	    tree size = NULL_TREE;
-	    if (si->nonzero_chars)
-	      {
-		/* Include the terminating nul in the size of the string
-		   to consider when determining possible clobber.  */
-		tree type = TREE_TYPE (si->nonzero_chars);
-		size = fold_build2 (PLUS_EXPR, type, si->nonzero_chars,
-				    build_int_cst (type, 1));
-	      }
-	    ao_ref_init_from_ptr_and_size (&r, si->ptr, size);
-	    if (stmt_may_clobber_ref_p_1 (stmt, &r))
-	      {
-		if (dump_file && (dump_flags & TDF_DETAILS))
-		  {
-		    if (size && tree_fits_uhwi_p (size))
-		      fprintf (dump_file,
-			       "  statement may clobber string "
-			       HOST_WIDE_INT_PRINT_UNSIGNED " long\n",
-			       tree_to_uhwi (size));
-		    else
-		      fprintf (dump_file,
-			       "  statement may clobber string\n");
-		  }
+  for (unsigned i = 1; vec_safe_iterate (stridx_to_strinfo, i, &si); ++i)
+    {
+      if (si == NULL || !POINTER_TYPE_P (TREE_TYPE (si->ptr)))
+	continue;
 
-		set_strinfo (i, NULL);
-		free_strinfo (si);
-		continue;
-	      }
+      nonempty = true;
 
-	    if (size
-		&& !zero_write
-		&& si->stmt
-		&& is_gimple_call (si->stmt)
-		&& (DECL_FUNCTION_CODE (gimple_call_fndecl (si->stmt))
-		    == BUILT_IN_CALLOC))
-	      {
-		/* If the clobber test above considered the length of
-		   the string (including the nul), then for (potentially)
-		   non-zero writes that might modify storage allocated by
-		   calloc consider the whole object and if it might be
-		   clobbered by the statement reset the allocation
-		   statement.  */
-		ao_ref_init_from_ptr_and_size (&r, si->ptr, NULL_TREE);
-		if (stmt_may_clobber_ref_p_1 (stmt, &r))
-		  si->stmt = NULL;
-	      }
-	  }
-	si->dont_invalidate = false;
-	nonempty = true;
-      }
+      /* Unconditionally reset DONT_INVALIDATE.  */
+      bool dont_invalidate = si->dont_invalidate;
+      si->dont_invalidate = false;
+
+      if (dont_invalidate)
+	continue;
+
+      ao_ref r;
+      tree size = NULL_TREE;
+      if (si->nonzero_chars)
+	{
+	  /* Include the terminating nul in the size of the string
+	     to consider when determining possible clobber.  */
+	  tree type = TREE_TYPE (si->nonzero_chars);
+	  size = fold_build2 (PLUS_EXPR, type, si->nonzero_chars,
+			      build_int_cst (type, 1));
+	}
+      ao_ref_init_from_ptr_and_size (&r, si->ptr, size);
+      if (stmt_may_clobber_ref_p_1 (stmt, &r))
+	{
+	  if (dump_file && (dump_flags & TDF_DETAILS))
+	    {
+	      fputs ("  statement may clobber object ", dump_file);
+	      print_generic_expr (dump_file, si->ptr);
+	      if (size && tree_fits_uhwi_p (size))
+		fprintf (dump_file, " " HOST_WIDE_INT_PRINT_UNSIGNED
+			 " bytes in size", tree_to_uhwi (size));
+	      fputc ('\n', dump_file);
+	    }
+
+	  set_strinfo (i, NULL);
+	  free_strinfo (si);
+	  continue;
+	}
+
+      if (size
+	  && !zero_write
+	  && si->stmt
+	  && is_gimple_call (si->stmt)
+	  && (DECL_FUNCTION_CODE (gimple_call_fndecl (si->stmt))
+	      == BUILT_IN_CALLOC))
+	{
+	  /* If the clobber test above considered the length of
+	     the string (including the nul), then for (potentially)
+	     non-zero writes that might modify storage allocated by
+	     calloc consider the whole object and if it might be
+	     clobbered by the statement reset the statement.  */
+	  ao_ref_init_from_ptr_and_size (&r, si->ptr, NULL_TREE);
+	  if (stmt_may_clobber_ref_p_1 (stmt, &r))
+	    si->stmt = NULL;
+	}
+    }
 
   if (dump_file && (dump_flags & TDF_DETAILS))
-    fprintf (dump_file, "  %s() ==> %i\n", __func__, nonempty);
+    fprintf (dump_file, "%s returns %i\n", __func__, nonempty);
 
   return nonempty;
 }
@@ -1289,6 +1318,7 @@  unshare_strinfo (strinfo *si)
 
   nsi = new_strinfo (si->ptr, si->idx, si->nonzero_chars, si->full_string_p);
   nsi->stmt = si->stmt;
+  nsi->alloc = si->alloc;
   nsi->endptr = si->endptr;
   nsi->first = si->first;
   nsi->prev = si->prev;
@@ -1582,6 +1612,8 @@  valid_builtin_call (gimple *stmt)
 	return false;
       break;
 
+    case BUILT_IN_ALLOCA:
+    case BUILT_IN_ALLOCA_WITH_ALIGN:
     case BUILT_IN_CALLOC:
     case BUILT_IN_MALLOC:
     case BUILT_IN_MEMCPY:
@@ -1858,7 +1890,8 @@  maybe_set_strlen_range (tree lhs, tree src, tree bound)
 }
 
 /* Diagnose buffer overflow by a STMT writing LEN + PLUS_ONE bytes,
-   into an object designated by the LHS of STMT otherise.  */
+   either into a region allocated for the object SI when non-null,
+   or into an object designated by the LHS of STMT otherwise.  */
 
 static void
 maybe_warn_overflow (gimple *stmt, tree len,
@@ -1868,82 +1901,144 @@  maybe_warn_overflow (gimple *stmt, tree len,
   if (!len || gimple_no_warning_p (stmt))
     return;
 
+  /* The DECL of the function performing the write if it is done
+     by one.  */
   tree writefn = NULL_TREE;
-  tree destdecl = NULL_TREE;
-  tree destsize = NULL_TREE;
+  /* The destination expression involved in the store STMT.  */
   tree dest = NULL_TREE;
 
-  /* The offset into the destination object set by compute_objsize
-     but already reflected in DESTSIZE.  */
-  tree destoff = NULL_TREE;
-
   if (is_gimple_assign (stmt))
-    {
-      dest = gimple_assign_lhs (stmt);
-      if (TREE_NO_WARNING (dest))
-	return;
-
-      /* For assignments try to determine the size of the destination
-	 first.  Set DESTOFF to the the offset on success.  */
-      tree off = size_zero_node;
-      destsize = compute_objsize (dest, 1, &destdecl, &off);
-      if (destsize)
-	destoff = off;
-    }
+    dest = gimple_assign_lhs (stmt);
   else if (is_gimple_call (stmt))
     {
-      writefn = gimple_call_fndecl (stmt);
       dest = gimple_call_arg (stmt, 0);
+      writefn = gimple_call_fndecl (stmt);
     }
 
+  if (TREE_NO_WARNING (dest))
+    return;
+
   /* The offset into the destination object computed below and not
-     reflected in DESTSIZE.  Either DESTOFF is set above or OFFRNG
-     below.  */
+     reflected in DESTSIZE (computed below).  */
   wide_int offrng[2];
-  offrng[0] = wi::zero (TYPE_PRECISION (sizetype));
-  offrng[1] = offrng[0];
+  const int off_prec = TYPE_PRECISION (ptrdiff_type_node);
+  offrng[0] = offrng[1] = wi::zero (off_prec);
 
-  if (!destsize && !si && dest)
+  if (!si)
     {
-      /* For both assignments and calls, if no destination STRINFO was
-	 provided, try to get it from the DEST.  */
+      /* If no destination STRINFO was provided try to get it from
+	 the DEST argument.  */
       tree ref = dest;
-      tree off = NULL_TREE;
       if (TREE_CODE (ref) == ARRAY_REF)
 	{
 	  /* Handle stores to VLAs (represented as
 	     ARRAY_REF (MEM_REF (vlaptr, 0), N].  */
-	  off = TREE_OPERAND (ref, 1);
+	  tree off = TREE_OPERAND (ref, 1);
 	  ref = TREE_OPERAND (ref, 0);
+	  if (get_range (off, offrng, rvals))
+	    {
+	      offrng[0] = offrng[0].from (offrng[0], off_prec, SIGNED);
+	      offrng[1] = offrng[1].from (offrng[1], off_prec, SIGNED);
+	    }
+	  else
+	    {
+	      offrng[0] = wi::to_wide (TYPE_MIN_VALUE (ptrdiff_type_node));
+	      offrng[1] = wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node));
+	    }
 	}
 
       if (TREE_CODE (ref) == MEM_REF)
 	{
 	  tree mem_off = TREE_OPERAND (ref, 1);
-	  if (off)
+	  ref = TREE_OPERAND (ref, 0);
+	  wide_int memoffrng[2];
+	  if (get_range (mem_off, memoffrng, rvals))
 	    {
-	      if (!integer_zerop (mem_off))
-		return;
+	      offrng[0] += memoffrng[0];
+	      offrng[1] += memoffrng[1];
 	    }
 	  else
-	    off = mem_off;
-	  ref = TREE_OPERAND (ref, 0);
+	    {
+	      offrng[0] = wi::to_wide (TYPE_MIN_VALUE (ptrdiff_type_node));
+	      offrng[1] = wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node));
+	    }
 	}
 
-      if (int idx = get_stridx (ref, offrng))
+      wide_int stroffrng[2];
+      if (int idx = get_stridx (ref, stroffrng, rvals))
 	{
 	  si = get_strinfo (idx);
-	  if (off && TREE_CODE (off) == INTEGER_CST)
+	  offrng[0] += stroffrng[0];
+	  offrng[1] += stroffrng[1];
+	}
+    }
+
+  /* The allocation call if the destination object was allocated
+     by one.  */
+  gimple *alloc_call = NULL;
+  /* The DECL of the destination object if known and not dynamically
+     allocated.  */
+  tree destdecl = NULL_TREE;
+  /* The offset into the destination object set by compute_objsize
+     but already reflected in DESTSIZE.  */
+  tree destoff = NULL_TREE;
+  /* The size of the destination region (which is smaller than
+     the destination object for stores at a non-zero offset).  */
+  tree destsize = NULL_TREE;
+
+  /* Compute the range of sizes of the destination object.  The range
+     is constant for declared objects but may be a range for allocated
+     objects.  */
+  const int siz_prec = TYPE_PRECISION (size_type_node);
+  wide_int sizrng[2];
+  if (si)
+    {
+      destsize = gimple_call_alloc_size (si->alloc, sizrng, rvals);
+      alloc_call = si->alloc;
+    }
+  else
+    offrng[0] = offrng[1] = wi::zero (off_prec);
+
+  if (!destsize)
+    {
+      /* If there is no STRINFO for DEST, fall back on compute_objsize.  */
+      tree off = size_zero_node;
+      destsize = compute_objsize (dest, 1, &destdecl, &off, rvals);
+      if (destsize)
+	{
+	  /* Remember OFF but clear OFFRNG that may have been set above.  */
+	  destoff = off;
+	  offrng[0] = offrng[1] = wi::zero (off_prec);
+
+	  if (destdecl && TREE_CODE (destdecl) == SSA_NAME)
+	    {
+	      gimple *stmt = SSA_NAME_DEF_STMT (destdecl);
+	      if (is_gimple_call (stmt))
+		alloc_call = stmt;
+	      destdecl = NULL_TREE;
+	    }
+
+	  if (!get_range (destsize, sizrng, rvals))
 	    {
-	      wide_int wioff = wi::to_wide (off, offrng->get_precision ());
-	      offrng[0] += wioff;
-	      offrng[1] += wioff;
+	      /* On failure, rather than failing, set the maximum range
+		 so that overflow in allocated objects whose size depends
+		 on the strlen of the source can still be diagnosed
+		 below.  */
+	      sizrng[0] = wi::zero (siz_prec);
+	      sizrng[1] = wi::to_wide (TYPE_MAX_VALUE (sizetype));
 	    }
 	}
-      else
-	return;
     }
 
+  if (!destsize)
+    {
+      sizrng[0] = wi::zero (siz_prec);
+      sizrng[1] = wi::to_wide (TYPE_MAX_VALUE (sizetype));
+    };
+
+  sizrng[0] = sizrng[0].from (sizrng[0], siz_prec, UNSIGNED);
+  sizrng[1] = sizrng[1].from (sizrng[1], siz_prec, UNSIGNED);
+
   /* Return early if the DESTSIZE size expression is the same as LEN
      and the offset into the destination is zero.  This might happen
      in the case of a pair of malloc and memset calls to allocate
@@ -1961,26 +2056,13 @@  maybe_warn_overflow (gimple *stmt, tree len,
       lenrng[1] += 1;
     }
 
-  /* Compute the range of sizes of the destination object.  The range
-     is constant for declared objects but may be a range for allocated
-     objects.  */
-  wide_int sizrng[2];
-  if (!destsize || !get_range (destsize, sizrng, rvals))
-    {
-      /* On failure, rather than bailing outright, use the maximum range
-	 so that overflow in allocated objects whose size depends on
-	 the strlen of the source can still be diagnosed below.  */
-      sizrng[0] = wi::zero (lenrng->get_precision ());
-      sizrng[1] = wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node));
-    }
-
-  /* The size of the remaining space in the destination computed as
+  /* The size of the remaining space in the destiation computed as
      the size of the latter minus the offset into it.  */
   wide_int spcrng[2] = { sizrng[0], sizrng[1] };
   if (wi::sign_mask (offrng[0]))
     {
       /* FIXME: Handle negative offsets into allocated objects.  */
-      if (destdecl)
+      if (destsize)
 	spcrng[0] = spcrng[1] = wi::zero (spcrng->get_precision ());
       else
 	return;
@@ -1991,7 +2073,8 @@  maybe_warn_overflow (gimple *stmt, tree len,
       spcrng[1] -= wi::ltu_p (offrng[0], spcrng[1]) ? offrng[0] : spcrng[1];
     }
 
-  if (wi::leu_p (lenrng[0], spcrng[0]))
+  if (wi::leu_p (lenrng[0], spcrng[0])
+      && wi::leu_p (lenrng[1], spcrng[1]))
     return;
 
   if (lenrng[0] == spcrng[1]
@@ -2092,6 +2175,8 @@  maybe_warn_overflow (gimple *stmt, tree len,
   if (!warned)
     return;
 
+  gimple_set_no_warning (stmt, true);
+
   /* If DESTOFF is not null, use it to format the offset value/range.  */
   if (destoff)
     get_range (destoff, offrng);
@@ -2117,6 +2202,62 @@  maybe_warn_overflow (gimple *stmt, tree len,
 		offstr, destdecl);
       return;
     }
+
+  if (!alloc_call)
+    return;
+
+  tree allocfn = gimple_call_fndecl (alloc_call);
+
+  if (gimple_call_builtin_p (alloc_call, BUILT_IN_ALLOCA_WITH_ALIGN))
+    {
+      if (sizrng[0] == sizrng[1])
+	inform (gimple_location (alloc_call),
+		"at offset %s to an object with size %wu declared here",
+		offstr, sizrng[0].to_uhwi ());
+      else if (sizrng[0] == 0)
+	{
+	  /* Avoid printing impossible sizes.  */
+	  if (wi::ltu_p (sizrng[1],
+			 wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)) - 2))
+	    inform (gimple_location (alloc_call),
+		    "at offset %s to an object with size at most %wu "
+		    "declared here",
+		    offstr, sizrng[1].to_uhwi ());
+	  else
+	    inform (gimple_location (alloc_call),
+		    "at offset %s to an object declared here", offstr);
+	}
+      else
+	inform (gimple_location (alloc_call),
+		"at offset %s to an object with size between %wu and %wu "
+		"declared here",
+		offstr, sizrng[0].to_uhwi (), sizrng[1].to_uhwi ());
+      return;
+    }
+
+  if (sizrng[0] == sizrng[1])
+    inform (gimple_location (alloc_call),
+	    "at offset %s to an object with size %wu allocated by %qD here",
+	    offstr, sizrng[0].to_uhwi (), allocfn);
+  else if (sizrng[0] == 0)
+    {
+      /* Avoid printing impossible sizes.  */
+      if (wi::ltu_p (sizrng[1],
+		     wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)) - 2))
+	inform (gimple_location (alloc_call),
+		"at offset %s to an object with size at most %wu allocated "
+		"by %qD here",
+		offstr, sizrng[1].to_uhwi (), allocfn);
+      else
+	inform (gimple_location (alloc_call),
+		"at offset %s to an object allocated by %qD here",
+		offstr, allocfn);
+    }
+  else
+    inform (gimple_location (alloc_call),
+	    "at offset %s to an object with size between %wu and %wu "
+	    "allocated by %qD here",
+	    offstr, sizrng[0].to_uhwi (), sizrng[1].to_uhwi (), allocfn);
 }
 
 /* Convenience wrapper for the above.  */
@@ -2243,7 +2384,7 @@  handle_builtin_strlen (gimple_stmt_iterator *gsi)
 	      tree old = si->nonzero_chars;
 	      si->nonzero_chars = lhs;
 	      si->full_string_p = true;
-	      if (TREE_CODE (old) == INTEGER_CST)
+	      if (old && TREE_CODE (old) == INTEGER_CST)
 		{
 		  old = fold_convert_loc (loc, TREE_TYPE (lhs), old);
 		  tree adj = fold_build2_loc (loc, MINUS_EXPR,
@@ -2425,7 +2566,8 @@  handle_builtin_strchr (gimple_stmt_iterator *gsi)
    memcpy.  */
 
 static void
-handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi)
+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;
@@ -2459,6 +2601,11 @@  handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi)
   else if (idx < 0)
     srclen = build_int_cst (size_type_node, ~idx);
 
+  maybe_warn_overflow (stmt, srclen, rvals, olddsi, true);
+
+  if (olddsi != NULL)
+    adjust_last_stmt (olddsi, stmt, false);
+
   loc = gimple_location (stmt);
   if (srclen == NULL_TREE)
     switch (bcode)
@@ -2709,26 +2856,58 @@  is_strlen_related_p (tree src, tree len)
   if (TREE_CODE (len) != SSA_NAME)
     return false;
 
-  gimple *def_stmt = SSA_NAME_DEF_STMT (len);
-  if (!def_stmt)
+  if (TREE_CODE (src) == SSA_NAME)
+    {
+      gimple *srcdef = SSA_NAME_DEF_STMT (src);
+      if (is_gimple_assign (srcdef))
+	{
+	  /* Handle bitwise AND used in conversions from wider size_t
+	     to narrower unsigned types.  */
+	  tree_code code = gimple_assign_rhs_code (srcdef);
+	  if (code == BIT_AND_EXPR
+	      || code == NOP_EXPR)
+	    return is_strlen_related_p (gimple_assign_rhs1 (srcdef), len);
+
+	  return false;
+	}
+
+      if (gimple_call_builtin_p (srcdef, BUILT_IN_NORMAL))
+	{
+	  /* If SRC is the result of a call to an allocation function
+	     or strlen, use the function's argument instead.  */
+	  tree func = gimple_call_fndecl (srcdef);
+	  built_in_function code = DECL_FUNCTION_CODE (func);
+	  if (code == BUILT_IN_ALLOCA
+	      || code == BUILT_IN_ALLOCA_WITH_ALIGN
+	      || code == BUILT_IN_MALLOC
+	      || code == BUILT_IN_STRLEN)
+	    return is_strlen_related_p (gimple_call_arg (srcdef, 0), len);
+
+	  /* FIXME: Handle other functions with attribute alloc_size.  */
+	  return false;
+	}
+    }
+
+  gimple *lendef = SSA_NAME_DEF_STMT (len);
+  if (!lendef)
     return false;
 
-  if (is_gimple_call (def_stmt))
+  if (is_gimple_call (lendef))
     {
-      tree func = gimple_call_fndecl (def_stmt);
-      if (!valid_builtin_call (def_stmt)
+      tree func = gimple_call_fndecl (lendef);
+      if (!valid_builtin_call (lendef)
 	  || DECL_FUNCTION_CODE (func) != BUILT_IN_STRLEN)
 	return false;
 
-      tree arg = gimple_call_arg (def_stmt, 0);
+      tree arg = gimple_call_arg (lendef, 0);
       return is_strlen_related_p (src, arg);
     }
 
-  if (!is_gimple_assign (def_stmt))
+  if (!is_gimple_assign (lendef))
     return false;
 
-  tree_code code = gimple_assign_rhs_code (def_stmt);
-  tree rhs1 = gimple_assign_rhs1 (def_stmt);
+  tree_code code = gimple_assign_rhs_code (lendef);
+  tree rhs1 = gimple_assign_rhs1 (lendef);
   tree rhstype = TREE_TYPE (rhs1);
 
   if ((TREE_CODE (rhstype) == POINTER_TYPE && code == POINTER_PLUS_EXPR)
@@ -2741,7 +2920,7 @@  is_strlen_related_p (tree src, tree len)
       return is_strlen_related_p (src, rhs1);
     }
 
-  if (tree rhs2 = gimple_assign_rhs2 (def_stmt))
+  if (tree rhs2 = gimple_assign_rhs2 (lendef))
     {
       /* Integer subtraction is considered strlen-related when both
 	 arguments are integers and second one is strlen-related.  */
@@ -3190,31 +3369,34 @@  handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *gsi)
    call.  */
 
 static void
-handle_builtin_memcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi)
+handle_builtin_memcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi,
+		       const vr_values *rvals)
 {
-  int idx, didx;
-  tree src, dst, len, lhs, oldlen, newlen;
+  tree lhs, oldlen, newlen;
   gimple *stmt = gsi_stmt (*gsi);
-  strinfo *si, *dsi, *olddsi;
+  strinfo *si, *dsi;
 
-  len = gimple_call_arg (stmt, 2);
-  src = gimple_call_arg (stmt, 1);
-  dst = gimple_call_arg (stmt, 0);
-  idx = get_stridx (src);
-  if (idx == 0)
-    return;
+  tree len = gimple_call_arg (stmt, 2);
+  tree src = gimple_call_arg (stmt, 1);
+  tree dst = gimple_call_arg (stmt, 0);
 
-  didx = get_stridx (dst);
-  olddsi = NULL;
+  int didx = get_stridx (dst);
+  strinfo *olddsi = NULL;
   if (didx > 0)
     olddsi = get_strinfo (didx);
   else if (didx < 0)
     return;
 
   if (olddsi != NULL
-      && tree_fits_uhwi_p (len)
       && !integer_zerop (len))
-    adjust_last_stmt (olddsi, stmt, false);
+    {
+      maybe_warn_overflow (stmt, len, rvals, olddsi);
+      adjust_last_stmt (olddsi, stmt, false);
+    }
+
+  int idx = get_stridx (src);
+  if (idx == 0)
+    return;
 
   bool full_string_p;
   if (idx > 0)
@@ -3611,10 +3793,11 @@  handle_builtin_strcat (enum built_in_function bcode, gimple_stmt_iterator *gsi)
     gimple_set_no_warning (stmt, true);
 }
 
-/* Handle a call to malloc or calloc.  */
+/* Handle a call to an allocation function like alloca, malloc or calloc,
+   or an ordinary allocation function declared with attribute alloc_size.  */
 
 static void
-handle_builtin_malloc (enum built_in_function bcode, gimple_stmt_iterator *gsi)
+handle_alloc_call (enum built_in_function bcode, gimple_stmt_iterator *gsi)
 {
   gimple *stmt = gsi_stmt (*gsi);
   tree lhs = gimple_call_lhs (stmt);
@@ -3628,10 +3811,19 @@  handle_builtin_malloc (enum built_in_function bcode, gimple_stmt_iterator *gsi)
     length = build_int_cst (size_type_node, 0);
   strinfo *si = new_strinfo (lhs, idx, length, length != NULL_TREE);
   if (bcode == BUILT_IN_CALLOC)
-    si->endptr = lhs;
+    {
+      /* Only set STMT for calloc and malloc.  */
+      si->stmt = stmt;
+      /* Only set ENDPTR for calloc.  */
+      si->endptr = lhs;
+    }
+  else if (bcode == BUILT_IN_MALLOC)
+    si->stmt = stmt;
+
+  /* Set ALLOC is set for all allocation functions.  */
+  si->alloc = stmt;
   set_strinfo (idx, si);
   si->writable = true;
-  si->stmt = stmt;
   si->dont_invalidate = true;
 }
 
@@ -3641,46 +3833,66 @@  handle_builtin_malloc (enum built_in_function bcode, gimple_stmt_iterator *gsi)
    return true when the call is transformed, false otherwise.  */
 
 static bool
-handle_builtin_memset (gimple_stmt_iterator *gsi, bool *zero_write)
+handle_builtin_memset (gimple_stmt_iterator *gsi, bool *zero_write,
+		       const vr_values *rvals)
 {
-  gimple *stmt2 = gsi_stmt (*gsi);
-  if (!integer_zerop (gimple_call_arg (stmt2, 1)))
-    return false;
-
-  /* Let the caller know the memset call cleared the destination.  */
-  *zero_write = true;
-
-  tree ptr = gimple_call_arg (stmt2, 0);
-  int idx1 = get_stridx (ptr);
+  gimple *memset_stmt = gsi_stmt (*gsi);
+  tree ptr = gimple_call_arg (memset_stmt, 0);
+  /* Set to the non-constant offset added to PTR.  */
+  wide_int offrng[2];
+  int idx1 = get_stridx (ptr, offrng, rvals);
   if (idx1 <= 0)
     return false;
   strinfo *si1 = get_strinfo (idx1);
   if (!si1)
     return false;
-  gimple *stmt1 = si1->stmt;
-  if (!stmt1 || !is_gimple_call (stmt1))
+  gimple *alloc_stmt = si1->alloc;
+  if (!alloc_stmt || !is_gimple_call (alloc_stmt))
     return false;
-  tree callee1 = gimple_call_fndecl (stmt1);
-  if (!valid_builtin_call (stmt1))
+  tree callee1 = gimple_call_fndecl (alloc_stmt);
+  if (!valid_builtin_call (alloc_stmt))
     return false;
+  tree alloc_size = gimple_call_arg (alloc_stmt, 0);
+  tree memset_size = gimple_call_arg (memset_stmt, 2);
+
+  /* Check for overflow.  */
+  maybe_warn_overflow (memset_stmt, memset_size, rvals);
+
+  /* Bail when there is no statement associated with the destination
+     (the statement may be null even when SI1->ALLOC is not).  */
+  if (!si1->stmt)
+    return false;
+
+  /* Avoid optimizing if store is at a variable offset from the beginning
+     of the allocated object.  */
+  if (offrng[0] != 0 || offrng[0] != offrng[1])
+    return false;
+
+  /* Bail when the call writes a non-zero value.  */
+  if (!integer_zerop (gimple_call_arg (memset_stmt, 1)))
+    return false;
+
+  /* Let the caller know the memset call cleared the destination.  */
+  *zero_write = true;
+
   enum built_in_function code1 = DECL_FUNCTION_CODE (callee1);
-  tree size = gimple_call_arg (stmt2, 2);
   if (code1 == BUILT_IN_CALLOC)
-    /* Not touching stmt1 */ ;
+    /* Not touching alloc_stmt */ ;
   else if (code1 == BUILT_IN_MALLOC
-	   && operand_equal_p (gimple_call_arg (stmt1, 0), size, 0))
+	   && operand_equal_p (memset_size, alloc_size, 0))
     {
-      gimple_stmt_iterator gsi1 = gsi_for_stmt (stmt1);
+      /* Replace the malloc + memset calls with calloc.  */
+      gimple_stmt_iterator gsi1 = gsi_for_stmt (si1->stmt);
       update_gimple_call (&gsi1, builtin_decl_implicit (BUILT_IN_CALLOC), 2,
-			  size, build_one_cst (size_type_node));
+			  alloc_size, build_one_cst (size_type_node));
       si1->nonzero_chars = build_int_cst (size_type_node, 0);
       si1->full_string_p = true;
       si1->stmt = gsi_stmt (gsi1);
     }
   else
     return false;
-  tree lhs = gimple_call_lhs (stmt2);
-  unlink_stmt_vdef (stmt2);
+  tree lhs = gimple_call_lhs (memset_stmt);
+  unlink_stmt_vdef (memset_stmt);
   if (lhs)
     {
       gimple *assign = gimple_build_assign (lhs, ptr);
@@ -3689,7 +3901,7 @@  handle_builtin_memset (gimple_stmt_iterator *gsi, bool *zero_write)
   else
     {
       gsi_remove (gsi, true);
-      release_defs (stmt2);
+      release_defs (memset_stmt);
     }
 
   return true;
@@ -4438,6 +4650,29 @@  count_nonzero_bytes (tree exp, unsigned HOST_WIDE_INT offset,
       if (maxlen + 1 < nbytes)
 	return false;
 
+      if (!nbytes
+	  && TREE_CODE (si->ptr) == SSA_NAME
+	  && !POINTER_TYPE_P (TREE_TYPE (si->ptr)))
+	{
+	  /* SI->PTR is an SSA_NAME with a DEF_STMT like
+	       _1 = MEM <unsigned int> [(char * {ref-all})s_4(D)];  */
+	  gimple *stmt = SSA_NAME_DEF_STMT (exp);
+	  if (gimple_assign_single_p (stmt)
+	      && gimple_assign_rhs_code (stmt) == MEM_REF)
+	    {
+	      tree rhs = gimple_assign_rhs1 (stmt);
+	      if (tree refsize = TYPE_SIZE_UNIT (TREE_TYPE (rhs)))
+		if (tree_fits_uhwi_p (refsize))
+		  {
+		    nbytes = tree_to_uhwi (refsize);
+		    maxlen = nbytes;
+		  }
+	    }
+
+	  if (!nbytes)
+	    return false;
+	}
+
       if (nbytes <= minlen)
 	*nulterm = false;
 
@@ -4454,7 +4689,7 @@  count_nonzero_bytes (tree exp, unsigned HOST_WIDE_INT offset,
 	lenrange[1] = maxlen;
 
       if (lenrange[2] < nbytes)
-	(lenrange[2] = nbytes);
+	lenrange[2] = nbytes;
 
       /* Since only the length of the string are known and not its contents,
 	 clear ALLNUL and ALLNONNUL purely on the basis of the length.  */
@@ -4672,7 +4907,8 @@  count_nonzero_bytes (tree exp, unsigned lenrange[3], bool *nulterm,
    the next statement in the basic block and false otherwise.  */
 
 static bool
-handle_store (gimple_stmt_iterator *gsi, bool *zero_write, const vr_values *rvals)
+handle_store (gimple_stmt_iterator *gsi, bool *zero_write,
+	      const vr_values *rvals)
 {
   int idx = -1;
   strinfo *si = NULL;
@@ -5080,12 +5316,18 @@  is_char_type (tree type)
    in the basic block and false otherwise.  */
 
 static bool
-strlen_check_and_optimize_call (gimple_stmt_iterator *gsi,
-				bool *zero_write,
+strlen_check_and_optimize_call (gimple_stmt_iterator *gsi, bool *zero_write,
 				const vr_values *rvals)
 {
   gimple *stmt = gsi_stmt (*gsi);
 
+  if (!gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
+    {
+      tree fntype = gimple_call_fntype (stmt);
+      if (fntype && lookup_attribute ("alloc_size", TYPE_ATTRIBUTES (fntype)))
+	handle_alloc_call (BUILT_IN_NONE, gsi);
+    }
+
   /* When not optimizing we must be checking printf calls which
      we do even for user-defined functions when they are declared
      with attribute format.  */
@@ -5108,7 +5350,7 @@  strlen_check_and_optimize_call (gimple_stmt_iterator *gsi,
     case BUILT_IN_STRCPY_CHK:
     case BUILT_IN_STPCPY:
     case BUILT_IN_STPCPY_CHK:
-      handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi);
+      handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi, rvals);
       break;
 
     case BUILT_IN_STRNCAT:
@@ -5127,18 +5369,20 @@  strlen_check_and_optimize_call (gimple_stmt_iterator *gsi,
     case BUILT_IN_MEMCPY_CHK:
     case BUILT_IN_MEMPCPY:
     case BUILT_IN_MEMPCPY_CHK:
-      handle_builtin_memcpy (DECL_FUNCTION_CODE (callee), gsi);
+      handle_builtin_memcpy (DECL_FUNCTION_CODE (callee), gsi, rvals);
       break;
     case BUILT_IN_STRCAT:
     case BUILT_IN_STRCAT_CHK:
       handle_builtin_strcat (DECL_FUNCTION_CODE (callee), gsi);
       break;
+    case BUILT_IN_ALLOCA:
+    case BUILT_IN_ALLOCA_WITH_ALIGN:
     case BUILT_IN_MALLOC:
     case BUILT_IN_CALLOC:
-      handle_builtin_malloc (DECL_FUNCTION_CODE (callee), gsi);
+      handle_alloc_call (DECL_FUNCTION_CODE (callee), gsi);
       break;
     case BUILT_IN_MEMSET:
-      if (handle_builtin_memset (gsi, zero_write))
+      if (handle_builtin_memset (gsi, zero_write, rvals))
 	return false;
       break;
     case BUILT_IN_MEMCMP:
@@ -5163,7 +5407,8 @@  strlen_check_and_optimize_call (gimple_stmt_iterator *gsi,
    If GSI's basic block needs clean-up of EH, set *CLEANUP_EH to true.  */
 
 static void
-handle_integral_assign (gimple_stmt_iterator *gsi, bool *cleanup_eh)
+handle_integral_assign (gimple_stmt_iterator *gsi, bool *cleanup_eh,
+			const vr_values *rvals)
 {
   gimple *stmt = gsi_stmt (*gsi);
   tree lhs = gimple_assign_lhs (stmt);
@@ -5266,6 +5511,31 @@  handle_integral_assign (gimple_stmt_iterator *gsi, bool *cleanup_eh)
 	    }
 	}
     }
+  else if (code == MEM_REF && TREE_CODE (lhs) == SSA_NAME)
+    {
+      if (int idx = new_stridx (lhs))
+	{
+	  /* Record multi-byte assignments from MEM_REFs.  */
+	  bool storing_all_nonzero_p;
+	  bool storing_all_zeros_p;
+	  bool full_string_p;
+	  unsigned lenrange[] = { UINT_MAX, 0, 0 };
+	  tree rhs = gimple_assign_rhs1 (stmt);
+	  const bool ranges_valid
+	    = count_nonzero_bytes (rhs, lenrange, &full_string_p,
+				   &storing_all_zeros_p, &storing_all_nonzero_p,
+				   rvals);
+	  if (ranges_valid)
+	    {
+	      tree length = build_int_cst (sizetype, lenrange[0]);
+	      strinfo *si = new_strinfo (lhs, idx, length, full_string_p);
+	      set_strinfo (idx, si);
+	      si->writable = true;
+	      si->dont_invalidate = true;
+	      maybe_warn_overflow (stmt, lenrange[2], rvals);
+	    }
+	}
+    }
 
   if (strlen_to_stridx)
     {
@@ -5318,29 +5588,35 @@  check_and_optimize_stmt (gimple_stmt_iterator *gsi, bool *cleanup_eh,
 	}
       else if (TREE_CODE (lhs) == SSA_NAME && INTEGRAL_TYPE_P (lhs_type))
 	/* Handle assignment to a character.  */
-	handle_integral_assign (gsi, cleanup_eh);
+	handle_integral_assign (gsi, cleanup_eh, rvals);
       else if (TREE_CODE (lhs) != SSA_NAME && !TREE_SIDE_EFFECTS (lhs))
 	{
 	  tree type = TREE_TYPE (lhs);
 	  if (TREE_CODE (type) == ARRAY_TYPE)
 	    type = TREE_TYPE (type);
 
-	  bool is_char_store = is_char_type (type);
-	  if (!is_char_store && TREE_CODE (lhs) == MEM_REF)
-	    {
-	      /* To consider stores into char objects via integer types
-		 other than char but not those to non-character objects,
-		 determine the type of the destination rather than just
-		 the type of the access.  */
-	      tree ref = TREE_OPERAND (lhs, 0);
-	      type = TREE_TYPE (ref);
-	      if (TREE_CODE (type) == POINTER_TYPE)
-		type = TREE_TYPE (type);
-	      if (TREE_CODE (type) == ARRAY_TYPE)
-		type = TREE_TYPE (type);
-	      if (is_char_type (type))
-		is_char_store = true;
-	    }
+	bool is_char_store = is_char_type (type);
+	if (!is_char_store && TREE_CODE (lhs) == MEM_REF)
+	  {
+	    /* To consider stores into char objects via integer types
+	       other than char but not those to non-character objects,
+	       determine the type of the destination rather than just
+	       the type of the access.  */
+	    for (int i = 0; i != 2; ++i)
+	      {
+		tree ref = TREE_OPERAND (lhs, i);
+		type = TREE_TYPE (ref);
+		if (TREE_CODE (type) == POINTER_TYPE)
+		  type = TREE_TYPE (type);
+		if (TREE_CODE (type) == ARRAY_TYPE)
+		  type = TREE_TYPE (type);
+		if (is_char_type (type))
+		  {
+		    is_char_store = true;
+		    break;
+		  }
+	      }
+	  }
 
 	  /* Handle a single or multibyte assignment.  */
 	  if (is_char_store && !handle_store (gsi, &zero_write, rvals))