The compute_objsize() function started out as a thin wrapper around
compute_builtin_object_size(), but over time developed its own
features to compensate for the other function's limitations (such
as its inability to work with ranges).  The interaction of these
features and the limitations has started to become increasingly
problematic as the former function is used in more contexts.

A complete "fix" for all the problems (as well as some other
limitations) that I'm working on will be more extensive and won't
be appropriate for backports.  Until then, the attached patch
cleans up the extensions compute_objsize() has accumulated over
the years to avoid a class of false positives.

To make the warnings issued based on the results of the function
easier to understand and fix, the patch also adds an informative
message to many instances of -Wstringop-overflow to point to
the object to which the warning refers.  This is especially
helpful when the object is referenced by a series of pointer
operations.

Tested by boostrapping on x86_64-linux and building Binutils/GDB,
Glibc, and the Linux kernel with no new warnings.

Besides applying it to trunk I'm looking to backport the fix to
GCC 10.

Martin
PR middle-end/95353 - spurious -Wstringop-overflow writing to a trailing array plus offset
PR middle-end/92939 - missing -Wstringop-overflow on negative index from the end of array

gcc/ChangeLog:

	PR middle-end/95353
	PR middle-end/92939
	* builtins.c (inform_access): New function.
	(check_access): Call it.  Add argument.
	(addr_decl_size): Remove.
	(get_range): New function.
	(compute_objsize): New overload.  Only use compute_builtin_object_size
	with raw memory function.
	(check_memop_access): Pass new argument to compute_objsize and
	check_access.
	(expand_builtin_memchr, expand_builtin_strcat): Same.
	(expand_builtin_strcpy, expand_builtin_stpcpy_1): Same.
	(expand_builtin_stpncpy, check_strncat_sizes): Same.
	(expand_builtin_strncat, expand_builtin_strncpy): Same.
	(expand_builtin_memcmp): Same.
	* builtins.h (check_nul_terminated_array): Declare extern.
	(check_access): Add argument.
	(struct access_ref, struct access_data): New structs.
	* gimple-ssa-warn-restrict.c (clamp_offset): New helper.
	(builtin_access::overlap): Call it.
	* tree-object-size.c ((decl_init_size): Declare extern.
	(addr_object_size): Correct offset computation.
	* tree-object-size.h (decl_init_size): Declare.
	* tree-ssa-strlen.c (handle_integral_assign): Remove a call
	to maybe_warn_overflow when assigning to an SSA_NAME.

gcc/testsuite/ChangeLog:

	PR middle-end/95353
	PR middle-end/92939
	* c-c++-common/Wstringop-truncation.c: Remove an xfail.
	* gcc.dg/Warray-bounds-46.c: Remove a bogus warning.
	* gcc.dg/Wrestrict-9.c: Disable -Wstringop-overflow.
	* gcc.dg/Wstringop-overflow-12.c: Remove xfails.
	* gcc.dg/Wstringop-overflow-28.c: Same.
	* gcc.dg/builtin-stringop-chk-4.c: Same.
	* gcc.dg/builtin-stringop-chk-5.c: Same.
	* gcc.dg/builtin-stringop-chk-8.c: Same.
	* gcc.dg/strlenopt-74.c: Avoid buffer overflow.
	* gcc.dg/Wstringop-overflow-33.c: New test.
	* gcc.dg/Wstringop-overflow-34.c: New test.
	* gcc.dg/Wstringop-overflow-35.c: New test.
	* gcc.dg/Wstringop-overflow-36.c: New test.
	* gcc.dg/Wstringop-overflow-37.c: New test.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 53bae599d3e..ae2ed974e57 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -3310,6 +3310,130 @@ determine_block_size (tree len, rtx len_rtx,
 			  GET_MODE_MASK (GET_MODE (len_rtx)));
 }
 
+/* Issue an inform message describing the target of an access REF.
+   WRITE is set for a write access and clear for a read access.  */
+
+static void
+inform_access (const access_ref &ref, bool write)
+{
+  if (!ref.ref)
+    return;
+
+  /* Convert offset range and avoid including a zero range since it isn't
+     necessarily meaningful.  */
+  long long minoff = 0, maxoff = 0;
+  if (wi::fits_shwi_p (ref.offrng[0])
+      && wi::fits_shwi_p (ref.offrng[1]))
+    {
+      minoff = ref.offrng[0].to_shwi ();
+      maxoff = ref.offrng[1].to_shwi ();
+    }
+
+  /* Convert size range and always include it since all sizes are
+     meaningful. */
+  unsigned long long minsize = 0, maxsize = 0;
+  if (wi::fits_shwi_p (ref.sizrng[0])
+      && wi::fits_shwi_p (ref.sizrng[1]))
+    {
+      minsize = ref.sizrng[0].to_shwi ();
+      maxsize = ref.sizrng[1].to_shwi ();
+    }
+
+  char sizestr[80];
+  location_t loc;
+  tree allocfn = NULL_TREE;
+  if (TREE_CODE (ref.ref) == SSA_NAME)
+    {
+      gimple *stmt = SSA_NAME_DEF_STMT (ref.ref);
+      gcc_assert (is_gimple_call (stmt));
+      loc = gimple_location (stmt);
+      allocfn = gimple_call_fndecl (stmt);
+      if (!allocfn)
+	/* Handle calls through pointers to functions.  */
+	allocfn = gimple_call_fn (stmt);
+
+      /* SIZRNG doesn't necessarily have the same range as the allocation
+	 size determined by gimple_call_alloc_size ().  */
+
+      if (minsize == maxsize)
+	sprintf (sizestr, "%llu", minsize);
+      else
+	sprintf (sizestr, "[%llu, %llu]", minsize, maxsize);
+
+    }
+  else
+    loc = DECL_SOURCE_LOCATION (ref.ref);
+
+  if (write)
+    {
+      if (DECL_P (ref.ref))
+	{
+	  if (minoff == maxoff)
+	    {
+	      if (minoff == 0)
+		inform (loc, "destination object %qD", ref.ref);
+	      else
+		inform (loc, "at offset %lli into destination object %qD",
+			minoff, ref.ref);
+	    }
+	  else
+	    inform (loc, "at offset [%lli, %lli] into destination object %qD",
+		    minoff, maxoff, ref.ref);
+	  return;
+	}
+
+      if (minoff == maxoff)
+	{
+	  if (minoff == 0)
+	    inform (loc, "destination object of size %s allocated by %qE",
+		    sizestr, allocfn);
+	  else
+	    inform (loc,
+		    "at offset %lli into destination object of size %s "
+		    "allocated by %qE", minoff, sizestr, allocfn);
+	}
+      else
+	inform (loc,
+		"at offset [%lli, %lli] into destination object of size %s "
+		"allocated by %qE",
+		minoff, maxoff, sizestr, allocfn);
+
+      return;
+    }
+
+  if (DECL_P (ref.ref))
+    {
+      if (minoff == maxoff)
+	{
+	  if (minoff == 0)
+	    inform (loc, "source object %qD", ref.ref);
+	  else
+	    inform (loc, "at offset %lli into source object %qD",
+		    minoff, ref.ref);
+	}
+      else
+	inform (loc, "at offset [%lli, %lli] into source object %qD",
+		minoff, maxoff, ref.ref);
+      return;
+    }
+
+  if (minoff == maxoff)
+    {
+      if (minoff == 0)
+	inform (loc, "source object of size %s allocated by %qE",
+		sizestr, allocfn);
+      else
+	inform (loc,
+		"at offset %lli into source object of size %s "
+		"allocated by %qE", minoff, sizestr, allocfn);
+    }
+  else
+    inform (loc,
+	    "at offset [%lli, %lli] into source object of size %s "
+	    "allocated by %qE",
+	    minoff, maxoff, sizestr, allocfn);
+}
+
 /* Try to verify that the sizes and lengths of the arguments to a string
    manipulation function given by EXP are within valid bounds and that
    the operation does not lead to buffer overflow or read past the end.
@@ -3336,12 +3460,15 @@ determine_block_size (tree len, rtx len_rtx,
    When DSTWRITE is null LEN is checked to verify that it doesn't exceed
    SIZE_MAX.
 
+   When nonnull, PAD points to a more detailed description of the access.
+
    If the call is successfully verified as safe return true, otherwise
    return false.  */
 
 bool
 check_access (tree exp, tree, tree, tree dstwrite,
-	      tree maxread, tree srcstr, tree dstsize)
+	      tree maxread, tree srcstr, tree dstsize,
+	      const access_data *pad /* = NULL */)
 {
   int opt = OPT_Wstringop_overflow_;
 
@@ -3545,7 +3672,11 @@ check_access (tree exp, tree, tree, tree dstwrite,
 				    exp, range[0], range[1],
 				    dstsize));
 	  if (warned)
-	    TREE_NO_WARNING (exp) = true;
+	    {
+	      TREE_NO_WARNING (exp) = true;
+	      if (pad)
+		inform_access (pad->dst, true);
+	    }
 
 	  /* Return error when an overflow has been detected.  */
 	  return false;
@@ -3687,8 +3818,11 @@ check_access (tree exp, tree, tree, tree dstwrite,
 				"a region of size %E",
 				exp, range[0], range[1], slen));
       if (warned)
-	TREE_NO_WARNING (exp) = true;
-
+	{
+	  TREE_NO_WARNING (exp) = true;
+	  if (pad)
+	    inform_access (pad->src, false);
+	}
       return false;
     }
 
@@ -3787,185 +3921,128 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
   return wide_int_to_tree (sizetype, rng1[1]);
 }
 
-/* Helper for compute_objsize.  Returns the constant size of the DEST
-   if it refers to a variable or field and sets *PDECL to the DECL and
-   *POFF to zero.  Otherwise returns null for other nodes.  */
+/* Wrapper around the wide_int overload of get_range.  Returns the same
+   result but accepts offset_int instead.  */
 
-static tree
-addr_decl_size (tree dest, tree *pdecl, tree *poff)
+static bool
+get_range (tree x, signop sgn, offset_int r[2],
+	   const vr_values *rvals /* = NULL */)
 {
-  if (TREE_CODE (dest) == ADDR_EXPR)
-    dest = TREE_OPERAND (dest, 0);
-
-  if (DECL_P (dest))
-    {
-      *pdecl = dest;
-      *poff = integer_zero_node;
-      if (tree size = DECL_SIZE_UNIT (dest))
-	return TREE_CODE (size) == INTEGER_CST ? size : NULL_TREE;
-    }
-
-  if (TREE_CODE (dest) == COMPONENT_REF)
-    {
-      *pdecl = TREE_OPERAND (dest, 1);
-      *poff = integer_zero_node;
-      /* Only return constant sizes for now while callers depend on it.  */
-      if (tree size = component_ref_size (dest))
-	return TREE_CODE (size) == INTEGER_CST ? size : NULL_TREE;
-    }
+  wide_int wr[2];
+  if (!get_range (x, wr, rvals))
+    return false;
 
-  return NULL_TREE;
+  r[0] = offset_int::from (wr[0], sgn);
+  r[1] = offset_int::from (wr[1], sgn);
+  return true;
 }
 
-/* Helper to compute the size of the object referenced by the DEST
+/* Helper to compute the size of the object referenced by the PTR
    expression which must have pointer type, using Object Size type
    OSTYPE (only the least significant 2 bits are used).
-   Returns an estimate of the size of the object represented as
-   a sizetype constant if successful or NULL when the size cannot
-   be determined.
-   When the referenced object involves a non-constant offset in some
-   range the returned value represents the largest size given the
-   smallest non-negative offset in the range.
-   If nonnull, sets *PDECL to the decl of the referenced subobject
-   if it can be determined, or to null otherwise.  Likewise, when
-   POFF is nonnull *POFF is set to the offset into *PDECL.
+   On success, sets PREF->REF to the DECL of the referenced object
+   if it's unique, otherwise to null, PREF->OFFRNG to the range of
+   offsets into it, and PREF->SIZRNG to the range of sizes of
+   the object(s).
+   VISITED is used to avoid visiting the same PHI operand multiple
+   times, and, when nonnull, RVALS to determine range information.
+   Returns true on success, false when the size cannot be determined.
 
    The function is intended for diagnostics and should not be used
    to influence code generation or optimization.  */
 
-tree
-compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */,
-		 tree *poff /* = NULL */, const vr_values *rvals /* = NULL */)
+static bool
+compute_objsize (tree ptr, int ostype, access_ref *pref,
+		 bitmap *visited, const vr_values *rvals /* = NULL */)
 {
-  tree dummy_decl = NULL_TREE;
-  if (!pdecl)
-    pdecl = &dummy_decl;
+  if (ostype == 0)
+    {
+      /* Use BOS only for raw memory functions like memcpy to get
+	 the size of the largest enclosing object.  */
+      tree off = NULL_TREE;
+      unsigned HOST_WIDE_INT size;
+      if (compute_builtin_object_size (ptr, ostype, &size, &pref->ref, &off))
+	{
+	  if (off)
+	    {
+	      offset_int offset = wi::to_offset (off);
+	      pref->offrng[0] += offset;
+	      pref->offrng[1] += offset;
+
+	      /* compute_builtin_object_size() returns the remaining
+		 size in PTR.  Add the offset to it to get the full
+		 size.  */
+	      pref->sizrng[0] = pref->sizrng[1] = size + offset;
+	    }
+	  else
+	    pref->sizrng[0] = pref->sizrng[1] = size;
+	  return true;
+	}
+    }
+
+  const bool addr = TREE_CODE (ptr) == ADDR_EXPR;
+  if (addr)
+    ptr = TREE_OPERAND (ptr, 0);
 
-  tree dummy_off = NULL_TREE;
-  if (!poff)
-    poff = &dummy_off;
+  if (DECL_P (ptr))
+    {
+      /* Bail if the reference is to the pointer itself (as opposed
+	 to what it points to).  */
+      if (!addr && POINTER_TYPE_P (TREE_TYPE (ptr)))
+	return false;
 
-  /* Only the two least significant bits are meaningful.  */
-  ostype &= 3;
+      tree size = decl_init_size (ptr, false);
+      if (!size || TREE_CODE (size) != INTEGER_CST)
+	return false;
 
-  if (ostype)
-    /* Except for overly permissive calls to memcpy and other raw
-       memory functions with zero OSTYPE, detect the size from simple
-       DECLs first to more reliably than compute_builtin_object_size
-       set *PDECL and *POFF.  */
-    if (tree size = addr_decl_size (dest, pdecl, poff))
-      return size;
+      pref->ref = ptr;
+      pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
+      return true;
+    }
 
-  unsigned HOST_WIDE_INT size;
-  if (compute_builtin_object_size (dest, ostype, &size, pdecl, poff))
-    return build_int_cst (sizetype, size);
+  const tree_code code = TREE_CODE (ptr);
 
-  if (TREE_CODE (dest) == SSA_NAME)
+  if (code == COMPONENT_REF)
     {
-      gimple *stmt = SSA_NAME_DEF_STMT (dest);
-      if (is_gimple_call (stmt))
+      if (ostype == 0)
 	{
-	  /* If STMT is a call to an allocation function get the size
-	     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;
-	      *poff = integer_zero_node;
-	      return size;
-	    }
-	  return NULL_TREE;
+	  /* For raw memory functions like memcpy bail if the size
+	     of the enclosing object cannot be determined.  */
+	  access_ref tmpref;
+	  tree ref = TREE_OPERAND (ptr, 0);
+	  if (!compute_objsize (ref, ostype, &tmpref, visited, rvals)
+	      || !tmpref.ref)
+	    return false;
 	}
 
-      if (!is_gimple_assign (stmt))
-	return NULL_TREE;
+      tree field = TREE_OPERAND (ptr, 1);
+      /* Bail if the reference is to the pointer itself (as opposed
+	 to what it points to).  */
+      if (!addr && POINTER_TYPE_P (TREE_TYPE (field)))
+	return false;
 
-      dest = gimple_assign_rhs1 (stmt);
+      pref->ref = field;
+      /* Only return constant sizes for now while callers depend
+	 on it.  */
+      tree size = component_ref_size (ptr);
+      if (!size || TREE_CODE (size) != INTEGER_CST)
+	return false;
 
-      tree_code code = gimple_assign_rhs_code (stmt);
-      if (code == POINTER_PLUS_EXPR)
-	{
-	  /* compute_builtin_object_size fails for addresses with
-	     non-constant offsets.  Try to determine the range of
-	     such an offset here and use it to adjust the constant
-	     size.  */
-	  tree off = gimple_assign_rhs2 (stmt);
-	  if (TREE_CODE (off) == INTEGER_CST)
-	    {
-	      if (tree size = compute_objsize (dest, ostype, pdecl, poff))
-		{
-		  wide_int wioff = wi::to_wide (off);
-		  wide_int wisiz = wi::to_wide (size);
-
-		  /* Ignore negative offsets for now.  For others,
-		     use the lower bound as the most optimistic
-		     estimate of the (remaining) size.  */
-		  if (wi::neg_p (wioff))
-		    ;
-		  else
-		    {
-		      if (*poff)
-			{
-			  *poff = fold_convert (ptrdiff_type_node, *poff);
-			  off = fold_convert (ptrdiff_type_node, *poff);
-			  *poff = size_binop (PLUS_EXPR, *poff, off);
-			}
-		      else
-			*poff = off;
-		      if (wi::ltu_p (wioff, wisiz))
-			return wide_int_to_tree (TREE_TYPE (size),
-						 wi::sub (wisiz, wioff));
-		      return size_zero_node;
-		    }
-		}
-	    }
-	  else if (TREE_CODE (off) == SSA_NAME
-		   && INTEGRAL_TYPE_P (TREE_TYPE (off)))
-	    {
-	      wide_int min, max;
-	      enum value_range_kind rng = get_range_info (off, &min, &max);
-
-	      if (rng == VR_RANGE)
-		if (tree size = compute_objsize (dest, ostype, pdecl, poff))
-		  {
-		    wide_int wisiz = wi::to_wide (size);
-
-		    /* Ignore negative offsets for now.  For others,
-		       use the lower bound as the most optimistic
-		       estimate of the (remaining)size.  */
-		    if (wi::neg_p (min) || wi::neg_p (max))
-		      ;
-		    else
-		      {
-			/* FIXME: For now, since the offset is non-constant,
-			   clear *POFF to keep it from being "misused."
-			   Eventually *POFF will need to become a range that
-			   can be properly added to the outer offset if it
-			   too is one.  */
-			*poff = NULL_TREE;
-			if (wi::ltu_p (min, wisiz))
-			  return wide_int_to_tree (TREE_TYPE (size),
-						   wi::sub (wisiz, min));
-			return size_zero_node;
-		      }
-		  }
-	    }
-	}
-      else if (code != ADDR_EXPR)
-	return NULL_TREE;
+      pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
+      return true;
     }
 
-  /* Unless computing the largest size (for memcpy and other raw memory
-     functions), try to determine the size of the object from its type.  */
-  if (!ostype)
-    return NULL_TREE;
-
-  if (TREE_CODE (dest) == ARRAY_REF
-      || TREE_CODE (dest) == MEM_REF)
+  if (code == ARRAY_REF || code == MEM_REF)
     {
-      tree ref = TREE_OPERAND (dest, 0);
+      tree ref = TREE_OPERAND (ptr, 0);
       tree reftype = TREE_TYPE (ref);
-      if (TREE_CODE (dest) == MEM_REF && TREE_CODE (reftype) == POINTER_TYPE)
+      if (code == ARRAY_REF
+	  && TREE_CODE (TREE_TYPE (reftype)) == POINTER_TYPE)
+	/* Avoid arrays of pointers.  FIXME: Hande pointers to arrays
+	   of known bound.  */
+	return false;
+
+      if (code == MEM_REF && TREE_CODE (reftype) == POINTER_TYPE)
 	{
 	  /* Give up for MEM_REFs of vector types; those may be synthesized
 	     from multiple assignments to consecutive data members.  See PR
@@ -3974,158 +4051,174 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */,
 	     MEM_REFs at the time they're created.  */
 	  reftype = TREE_TYPE (reftype);
 	  if (TREE_CODE (reftype) == VECTOR_TYPE)
-	    return NULL_TREE;
+	    return false;
 	}
-      tree off = TREE_OPERAND (dest, 1);
-      if (tree size = compute_objsize (ref, ostype, pdecl, poff))
-	{
-	  /* If the declaration of the destination object is known
-	     to have zero size, return zero.  */
-	  if (integer_zerop (size)
-	      && *pdecl && DECL_P (*pdecl)
-	      && *poff && integer_zerop (*poff))
-	    return size_zero_node;
-
-	  /* 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;
 
-	  /* Convert to the same precision to keep wide_int from "helpfully"
-	     crashing whenever it sees other arguments.  */
-	  const unsigned sizprec = TYPE_PRECISION (sizetype);
-	  offrng[0] = wide_int::from (offrng[0], sizprec, SIGNED);
-	  offrng[1] = wide_int::from (offrng[1], sizprec, SIGNED);
+      if (!compute_objsize (ref, ostype, pref, visited, rvals))
+	return false;
 
-	  /* Adjust SIZE either up or down by the sum of *POFF and OFF
-	     above.  */
-	  if (TREE_CODE (dest) == ARRAY_REF)
-	    {
-	      tree lowbnd = array_ref_low_bound (dest);
-	      if (!integer_zerop (lowbnd) && tree_fits_uhwi_p (lowbnd))
-		{
-		  /* Adjust the offset by the low bound of the array
-		     domain (normally zero but 1 in Fortran).  */
-		  unsigned HOST_WIDE_INT lb = tree_to_uhwi (lowbnd);
-		  offrng[0] -= lb;
-		  offrng[1] -= lb;
-		}
+      offset_int orng[2];
+      tree off = TREE_OPERAND (ptr, 1);
+      if (!get_range (off, SIGNED, orng, rvals))
+	return false;
 
-	      /* Convert the array index into a byte offset.  */
-	      tree eltype = TREE_TYPE (dest);
-	      tree tpsize = TYPE_SIZE_UNIT (eltype);
-	      if (tpsize && TREE_CODE (tpsize) == INTEGER_CST)
-		{
-		  wide_int wsz = wi::to_wide (tpsize, offrng->get_precision ());
-		  offrng[0] *= wsz;
-		  offrng[1] *= wsz;
-		}
-	      else
-		return NULL_TREE;
+      if (TREE_CODE (ptr) == ARRAY_REF)
+	{
+	  /* Convert the array index range determined above to a byte
+	     offset.  */
+	  tree lowbnd = array_ref_low_bound (ptr);
+	  if (!integer_zerop (lowbnd) && tree_fits_uhwi_p (lowbnd))
+	    {
+	      /* Adjust the index by the low bound of the array domain
+		 (normally zero but 1 in Fortran).  */
+	      unsigned HOST_WIDE_INT lb = tree_to_uhwi (lowbnd);
+	      orng[0] -= lb;
+	      orng[1] -= lb;
 	    }
 
-	  wide_int wisize = wi::to_wide (size);
+	  tree eltype = TREE_TYPE (ptr);
+	  tree tpsize = TYPE_SIZE_UNIT (eltype);
+	  if (!tpsize || TREE_CODE (tpsize) != INTEGER_CST)
+	    return false;
+
+	  offset_int sz = wi::to_offset (tpsize);
+	  orng[0] *= sz;
+	  orng[1] *= sz;
 
-	  if (!*poff)
+	  if (TREE_CODE (eltype) == ARRAY_TYPE)
 	    {
-	      /* 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.  */
-	      if (wi::neg_p (offrng[0]))
-		return size;
-	      if (wi::ltu_p (offrng[0], wisize))
-		return build_int_cst (sizetype, (wisize - offrng[0]).to_uhwi ());
-	      return size_zero_node;
+	      pref->sizrng[0] = pref->offrng[0] + orng[0] + sz;
+	      pref->sizrng[1] = pref->offrng[1] + orng[1] + sz;
 	    }
+	}
+
+      pref->offrng[0] += orng[0];
+      pref->offrng[1] += orng[1];
 
-	  /* Convert to the same precision to keep wide_int from "helpfuly"
-	     crashing whenever it sees other argumments.  */
-	  offrng[0] = wide_int::from (offrng[0], sizprec, SIGNED);
-	  offrng[1] = wide_int::from (offrng[1], sizprec, SIGNED);
+      return true;
+    }
 
-	  tree dstoff = *poff;
-	  if (integer_zerop (*poff))
-	    *poff = off;
-	  else if (!integer_zerop (off))
+  if (TREE_CODE (ptr) == SSA_NAME)
+    {
+      gimple *stmt = SSA_NAME_DEF_STMT (ptr);
+      if (is_gimple_call (stmt))
+	{
+	  /* If STMT is a call to an allocation function get the size
+	     from its argument(s).  If successful, also set *PDECL to
+	     PTR for the caller to include in diagnostics.  */
+	  wide_int wr[2];
+	  if (gimple_call_alloc_size (stmt, wr, rvals))
 	    {
-	      *poff = fold_convert (ptrdiff_type_node, *poff);
-	      off = fold_convert (ptrdiff_type_node, off);
-	      *poff = size_binop (PLUS_EXPR, *poff, off);
+	      pref->ref = ptr;
+	      pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED);
+	      pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED);
+	      return true;
 	    }
+	  return false;
+	}
 
-	  if (!wi::neg_p (offrng[0]))
-	    {
-	      if (TREE_CODE (size) != INTEGER_CST)
-		return NULL_TREE;
+      /* TODO: Handle PHI.  */
 
-	      /* Return the difference between the size and the offset
-		 or zero if the offset is greater.  */
-	      wide_int wisize = wi::to_wide (size, sizprec);
-	      if (wi::ltu_p (wisize, offrng[0]))
-		return size_zero_node;
+      if (!is_gimple_assign (stmt))
+	return false;
 
-	      return wide_int_to_tree (sizetype, wisize - offrng[0]);
-	    }
+      ptr = gimple_assign_rhs1 (stmt);
+
+      tree_code code = gimple_assign_rhs_code (stmt);
+      if (TREE_CODE (TREE_TYPE (ptr)) != POINTER_TYPE)
+	/* Avoid conversions from non-pointers.  */
+	return false;
 
-	  wide_int dstoffrng[2];
-	  if (TREE_CODE (dstoff) == INTEGER_CST)
-	    dstoffrng[0] = dstoffrng[1] = wi::to_wide (dstoff);
-	  else if (TREE_CODE (dstoff) == SSA_NAME)
+      if (code == POINTER_PLUS_EXPR)
+	{
+	  /* If the the offset in the expression can be determined use
+	     it to adjust the overall offset.  Otherwise, set the overall
+	     offset to the maximum.  */
+	  offset_int orng[2];
+	  tree off = gimple_assign_rhs2 (stmt);
+	  if (!get_range (off, SIGNED, orng, rvals)
+	      || !wi::les_p (orng[0], orng[1]))
 	    {
-	      enum value_range_kind rng
-		= get_range_info (dstoff, dstoffrng, dstoffrng + 1);
-	      if (rng != VR_RANGE)
-		return NULL_TREE;
+	      orng[0] = wi::to_offset (TYPE_MIN_VALUE (ptrdiff_type_node));
+	      orng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
 	    }
-	  else
-	    return NULL_TREE;
-
-	  dstoffrng[0] = wide_int::from (dstoffrng[0], sizprec, SIGNED);
-	  dstoffrng[1] = wide_int::from (dstoffrng[1], sizprec, SIGNED);
 
-	  if (!wi::neg_p (dstoffrng[0]))
-	    wisize += dstoffrng[0];
-
-	  offrng[1] += dstoffrng[1];
-	  if (wi::neg_p (offrng[1]))
-	    return size_zero_node;
-
-	  return wide_int_to_tree (sizetype, wisize);
+	  pref->offrng[0] += orng[0];
+	  pref->offrng[1] += orng[1];
 	}
+      else if (code != ADDR_EXPR)
+	return false;
 
-      return NULL_TREE;
+      return compute_objsize (ptr, ostype, pref, visited, rvals);
     }
 
-  /* Try simple DECLs not handled above.  */
-  if (tree size = addr_decl_size (dest, pdecl, poff))
-    return size;
-
-  tree type = TREE_TYPE (dest);
-  if (TREE_CODE (type) == POINTER_TYPE)
-    type = TREE_TYPE (type);
-
+  tree type = TREE_TYPE (ptr);
   type = TYPE_MAIN_VARIANT (type);
-  if (TREE_CODE (dest) == ADDR_EXPR)
-    dest = TREE_OPERAND (dest, 0);
+  if (TREE_CODE (ptr) == ADDR_EXPR)
+    ptr = TREE_OPERAND (ptr, 0);
 
   if (TREE_CODE (type) == ARRAY_TYPE
-      && !array_at_struct_end_p (dest))
+      && !array_at_struct_end_p (ptr))
     {
       if (tree size = TYPE_SIZE_UNIT (type))
-	return TREE_CODE (size) == INTEGER_CST ? size : NULL_TREE;
+	return get_range (size, UNSIGNED, pref->sizrng, rvals);
     }
 
-  return NULL_TREE;
+  return false;
+}
+
+/* Convenience wrapper around the above.  */
+
+static tree
+compute_objsize (tree ptr, int ostype, access_ref *pref,
+		 const vr_values *rvals = NULL)
+{
+  bitmap visited = NULL;
+
+  bool success
+    = compute_objsize (ptr, ostype, pref, &visited, rvals);
+
+  if (visited)
+    BITMAP_FREE (visited);
+
+  if (!success)
+    return NULL_TREE;
+
+  if (pref->offrng[0] < 0)
+    {
+      if (pref->offrng[1] < 0)
+	return size_zero_node;
+
+      pref->offrng[0] = 0;
+    }
+
+  if (pref->sizrng[1] < pref->offrng[0])
+    return size_zero_node;
+
+  return wide_int_to_tree (sizetype, pref->sizrng[1] - pref->offrng[0]);
+}
+
+/* Transitional wrapper around the above.  The function should be removed
+   once callers transition to one of the two above.  */
+
+tree
+compute_objsize (tree ptr, int ostype, tree *pdecl /* = NULL */,
+		 tree *poff /* = NULL */, const vr_values *rvals /* = NULL */)
+{
+  /* Set the initial offsets to zero and size to negative to indicate
+     none has been computed yet.  */
+  access_ref ref;
+  tree size = compute_objsize (ptr, ostype, &ref, rvals);
+  if (!size)
+    return NULL_TREE;
+
+  if (pdecl)
+    *pdecl = ref.ref;
+
+  if (poff)
+    *poff = wide_int_to_tree (ptrdiff_type_node, ref.offrng[ref.offrng[0] < 0]);
+
+  return size;
 }
 
 /* Helper to determine and check the sizes of the source and the destination
@@ -4142,11 +4235,12 @@ check_memop_access (tree exp, tree dest, tree src, tree size)
      try to determine the size of the largest source and destination
      object using type-0 Object Size regardless of the object size
      type specified by the option.  */
-  tree srcsize = src ? compute_objsize (src, 0) : NULL_TREE;
-  tree dstsize = compute_objsize (dest, 0);
+  access_data data;
+  tree srcsize = src ? compute_objsize (src, 0, &data.src) : NULL_TREE;
+  tree dstsize = compute_objsize (dest, 0, &data.dst);
 
   return check_access (exp, dest, src, size, /*maxread=*/NULL_TREE,
-		       srcsize, dstsize);
+		       srcsize, dstsize, &data);
 }
 
 /* Validate memchr arguments without performing any expansion.
@@ -4166,9 +4260,10 @@ expand_builtin_memchr (tree exp, rtx)
      of the object.  */
   if (warn_stringop_overflow)
     {
-      tree size = compute_objsize (arg1, 0);
+      access_data data;
+      tree size = compute_objsize (arg1, 0, &data.src);
       check_access (exp, /*dst=*/NULL_TREE, /*src=*/NULL_TREE, len,
-		    /*maxread=*/NULL_TREE, size, /*objsize=*/NULL_TREE);
+		    /*maxread=*/NULL_TREE, size, /*objsize=*/NULL_TREE, &data);
     }
 
   return NULL_RTX;
@@ -4443,10 +4538,11 @@ expand_builtin_strcat (tree exp)
      just diagnose cases when the souce string is longer than
      the destination object.  */
 
-  tree destsize = compute_objsize (dest, warn_stringop_overflow - 1);
+  access_data data;
+  tree destsize = compute_objsize (dest, warn_stringop_overflow - 1, &data.dst);
 
   check_access (exp, dest, src, /*size=*/NULL_TREE, /*maxread=*/NULL_TREE, src,
-		destsize);
+		destsize, &data);
 
   return NULL_RTX;
 }
@@ -4467,9 +4563,11 @@ expand_builtin_strcpy (tree exp, rtx target)
 
   if (warn_stringop_overflow)
     {
-      tree destsize = compute_objsize (dest, warn_stringop_overflow - 1);
+      access_data data;
+      tree destsize = compute_objsize (dest, warn_stringop_overflow - 1,
+				       &data.dst);
       check_access (exp, dest, src, /*size=*/NULL_TREE, /*maxread=*/NULL_TREE,
-		    src, destsize);
+		    src, destsize, &data);
     }
 
   if (rtx ret = expand_builtin_strcpy_args (exp, dest, src, target))
@@ -4525,9 +4623,11 @@ expand_builtin_stpcpy_1 (tree exp, rtx target, machine_mode mode)
 
   if (warn_stringop_overflow)
     {
-      tree destsize = compute_objsize (dst, warn_stringop_overflow - 1);
+      access_data data;
+      tree destsize = compute_objsize (dst, warn_stringop_overflow - 1,
+				       &data.dst);
       check_access (exp, dst, src, /*size=*/NULL_TREE, /*maxread=*/NULL_TREE,
-		    src, destsize);
+		    src, destsize, &data);
     }
 
   /* If return value is ignored, transform stpcpy into strcpy.  */
@@ -4636,10 +4736,12 @@ expand_builtin_stpncpy (tree exp, rtx)
   if (!check_nul_terminated_array (exp, src, len))
     return NULL_RTX;
 
+  access_data data;
   /* The size of the destination object.  */
-  tree destsize = compute_objsize (dest, warn_stringop_overflow - 1);
+  tree destsize = compute_objsize (dest, warn_stringop_overflow - 1, &data.dst);
 
-  check_access (exp, dest, src, len, /*maxread=*/NULL_TREE, src, destsize);
+  check_access (exp, dest, src, len, /*maxread=*/NULL_TREE, src, destsize,
+		&data);
 
   return NULL_RTX;
 }
@@ -4679,12 +4781,13 @@ check_strncat_sizes (tree exp, tree objsize)
   /* Try to verify that the destination is big enough for the shortest
      string.  */
 
+  access_data data;
   if (!objsize && warn_stringop_overflow)
     {
       /* If it hasn't been provided by __strncat_chk, try to determine
 	 the size of the destination object into which the source is
 	 being copied.  */
-      objsize = compute_objsize (dest, warn_stringop_overflow - 1);
+      objsize = compute_objsize (dest, warn_stringop_overflow - 1, &data.dst);
     }
 
   /* Add one for the terminating nul.  */
@@ -4715,10 +4818,10 @@ check_strncat_sizes (tree exp, tree objsize)
 	  && tree_int_cst_lt (maxread, srclen)))
     srclen = maxread;
 
-  /* The number of bytes to write is LEN but check_access will also
+  /* The number of bytes to write is LEN but check_access will alsoa
      check SRCLEN if LEN's value isn't known.  */
   return check_access (exp, dest, src, /*size=*/NULL_TREE, maxread, srclen,
-		       objsize);
+		       objsize, &data);
 }
 
 /* Similar to expand_builtin_strcat, do some very basic size validation
@@ -4756,10 +4859,11 @@ expand_builtin_strncat (tree exp, rtx)
       maxlen = lendata.maxbound;
     }
 
+  access_data data;
   /* Try to verify that the destination is big enough for the shortest
      string.  First try to determine the size of the destination object
      into which the source is being copied.  */
-  tree destsize = compute_objsize (dest, warn_stringop_overflow - 1);
+  tree destsize = compute_objsize (dest, warn_stringop_overflow - 1, &data.dst);
 
   /* Add one for the terminating nul.  */
   tree srclen = (maxlen
@@ -4789,8 +4893,7 @@ expand_builtin_strncat (tree exp, rtx)
 	  && tree_int_cst_lt (maxread, srclen)))
     srclen = maxread;
 
-  /* The number of bytes to write is SRCLEN.  */
-  check_access (exp, dest, src, NULL_TREE, maxread, srclen, destsize);
+  check_access (exp, dest, src, NULL_TREE, maxread, srclen, destsize, &data);
 
   return NULL_RTX;
 }
@@ -4819,13 +4922,14 @@ expand_builtin_strncpy (tree exp, rtx target)
 
   if (warn_stringop_overflow)
     {
-      tree destsize = compute_objsize (dest,
-				       warn_stringop_overflow - 1);
+      access_data data;
+      tree destsize = compute_objsize (dest, warn_stringop_overflow - 1,
+				       &data.dst);
 
       /* The number of bytes to write is LEN but check_access will also
 	 check SLEN if LEN's value isn't known.  */
       check_access (exp, dest, src, len, /*maxread=*/NULL_TREE, src,
-		    destsize);
+		    destsize, &data);
     }
 
   /* We must be passed a constant len and src parameter.  */
@@ -5138,16 +5242,18 @@ expand_builtin_memcmp (tree exp, rtx target, bool result_eq)
 
   /* Diagnose calls where the specified length exceeds the size of either
      object.  */
-  tree size = compute_objsize (arg1, 0);
+  access_data data;
+  tree size = compute_objsize (arg1, 0, &data.src);
   no_overflow = check_access (exp, /*dst=*/NULL_TREE, /*src=*/NULL_TREE,
 			      len, /*maxread=*/NULL_TREE, size,
-			      /*objsize=*/NULL_TREE);
+			      /*objsize=*/NULL_TREE, &data);
   if (no_overflow)
     {
-      size = compute_objsize (arg2, 0);
+      access_data data;
+      size = compute_objsize (arg2, 0, &data.src);
       no_overflow = check_access (exp, /*dst=*/NULL_TREE, /*src=*/NULL_TREE,
 				  len,  /*maxread=*/NULL_TREE, size,
-				  /*objsize=*/NULL_TREE);
+				  /*objsize=*/NULL_TREE, &data);
     }
 
   /* If the specified length exceeds the size of either object, 
diff --git a/gcc/builtins.h b/gcc/builtins.h
index 7d8b9cddf73..f085c3fce71 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -152,11 +152,40 @@ extern bool target_char_cst_p (tree t, char *p);
 extern internal_fn associated_internal_fn (tree);
 extern internal_fn replacement_internal_fn (gcall *);
 
-bool check_nul_terminated_array (tree, tree, tree = NULL_TREE);
+extern bool check_nul_terminated_array (tree, tree, tree = NULL_TREE);
 extern void warn_string_no_nul (location_t, const char *, tree, tree);
 extern tree unterminated_array (tree, tree * = NULL, bool * = NULL);
 extern bool builtin_with_linkage_p (tree);
-extern bool check_access (tree, tree, tree, tree, tree, tree, tree);
+
+/* Describes a reference to an object used in an access.  */
+struct access_ref
+{
+  access_ref (): ref ()
+  {
+    /* Set to valid.  */
+    offrng[0] = offrng[1] = 0;
+    /* Invalidate.   */
+    sizrng[0] = sizrng[1] = -1;
+  }
+
+  /* Reference to the object.  */
+  tree ref;
+
+  /* Range of offsets into and sizes of the object(s).  */
+  offset_int offrng[2];
+  offset_int sizrng[2];
+};
+
+/* Describes a pair of references used in an access by built-in
+   functions like memcpy.  */
+struct access_data
+{
+  /* Destination and source of the access.  */
+  access_ref dst, src;
+};
+
+extern bool check_access (tree, tree, tree, tree, tree, tree, tree,
+			  const access_data * = NULL);
 
 
 #endif /* GCC_BUILTINS_H */
diff --git a/gcc/gimple-ssa-warn-restrict.c b/gcc/gimple-ssa-warn-restrict.c
index 19d2ec09aa5..512fc138528 100644
--- a/gcc/gimple-ssa-warn-restrict.c
+++ b/gcc/gimple-ssa-warn-restrict.c
@@ -1274,6 +1274,27 @@ builtin_access::strcpy_overlap ()
   return generic_overlap ();
 }
 
+/* For a BASE of array type, clamp REFOFF to at most [0, BASE_SIZE]
+   if known, or [0, MAXOBJSIZE] otherwise.  */
+
+static void
+clamp_offset (tree base, offset_int refoff[2], offset_int maxobjsize)
+{
+  if (!base || TREE_CODE (TREE_TYPE (base)) != ARRAY_TYPE)
+    return;
+
+  if (refoff[0] < 0 && refoff[1] >= 0)
+    refoff[0] = 0;
+
+  if (refoff[1] < refoff[0])
+    {
+      offset_int maxsize =  maxobjsize;
+      if (tree size = TYPE_SIZE_UNIT (TREE_TYPE (base)))
+	maxsize = wi::to_offset (size);
+
+      refoff[1] = wi::umin (refoff[1], maxsize);
+    }
+}
 
 /* Return true if DSTREF and SRCREF describe accesses that either overlap
    one another or that, in order not to overlap, would imply that the size
@@ -1312,35 +1333,12 @@ builtin_access::overlap ()
 
   /* If the base object is an array adjust the bounds of the offset
      to be non-negative and within the bounds of the array if possible.  */
-  if (dstref->base
-      && TREE_CODE (TREE_TYPE (dstref->base)) == ARRAY_TYPE)
-    {
-      if (acs.dstoff[0] < 0 && acs.dstoff[1] >= 0)
-	acs.dstoff[0] = 0;
-
-      if (acs.dstoff[1] < acs.dstoff[0])
-	{
-	  if (tree size = TYPE_SIZE_UNIT (TREE_TYPE (dstref->base)))
-	    acs.dstoff[1] = wi::umin (acs.dstoff[1], wi::to_offset (size));
-	  else
-	    acs.dstoff[1] = wi::umin (acs.dstoff[1], maxobjsize);
-	}
-    }
+  clamp_offset (dstref->base, acs.dstoff, maxobjsize);
 
   acs.srcoff[0] = srcref->offrange[0];
   acs.srcoff[1] = srcref->offrange[1];
 
-  if (srcref->base
-      && TREE_CODE (TREE_TYPE (srcref->base)) == ARRAY_TYPE)
-    {
-      if (acs.srcoff[0] < 0 && acs.srcoff[1] >= 0)
-	acs.srcoff[0] = 0;
-
-      if (tree size = TYPE_SIZE_UNIT (TREE_TYPE (srcref->base)))
-	acs.srcoff[1] = wi::umin (acs.srcoff[1], wi::to_offset (size));
-      else if (acs.srcoff[1] < acs.srcoff[0])
-	acs.srcoff[1] = wi::umin (acs.srcoff[1], maxobjsize);
-    }
+  clamp_offset (srcref->base, acs.srcoff, maxobjsize);
 
   /* When the upper bound of the offset is less than the lower bound
      the former is the result of a negative offset being represented
diff --git a/gcc/testsuite/c-c++-common/Wstringop-truncation.c b/gcc/testsuite/c-c++-common/Wstringop-truncation.c
index 5e43405fde8..f29eee29e85 100644
--- a/gcc/testsuite/c-c++-common/Wstringop-truncation.c
+++ b/gcc/testsuite/c-c++-common/Wstringop-truncation.c
@@ -269,7 +269,7 @@ void test_strncpy_array (Dest *pd, int i, const char* s)
   CPY (dst7, s, 7);                 /* { dg-warning "specified bound 7 equals destination size" } */
   CPY (dst7, s, sizeof dst7);       /* { dg-warning "specified bound 7 equals destination size" } */
 
-  CPY (dst2_5[0], s, sizeof dst2_5[0]); /* { dg-warning "specified bound 5 equals destination size" "bug 77293" { xfail *-*-* } } */
+  CPY (dst2_5[0], s, sizeof dst2_5[0]); /* { dg-warning "specified bound 5 equals destination size" "bug 77293" } */
   CPY (dst2_5[1], s, sizeof dst2_5[1]); /* { dg-warning "specified bound 5 equals destination size" } */
 
   /* Verify that copies that nul-terminate are not diagnosed.  */
diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-46.c b/gcc/testsuite/gcc.dg/Warray-bounds-46.c
index 9078c6f5998..3f1c6c715ea 100644
--- a/gcc/testsuite/gcc.dg/Warray-bounds-46.c
+++ b/gcc/testsuite/gcc.dg/Warray-bounds-46.c
@@ -66,8 +66,10 @@ void strcpy_global (void)
   T (gma.a17, 17);        // { dg-warning "'strcpy' offset 157 from the object at 'gma' is out of the bounds of referenced subobject 'a17' with type 'char\\\[17]' at offset 140" }
 
   SA (__builtin_offsetof (struct MA17, ax) == 157);
-
-  T (gma.ax, 0);          // { dg-warning "'strcpy' offset 157 from the object at 'gma' is out of the bounds of referenced subobject 'ax' with type 'char[]' at offset 157|'strcpy' offset 157 is out of the bounds \\\[0, 157] of object 'gma' with type 'struct MA17'" }
+  // GCC allows static initialization of flexible array members of
+  // non-local objects.  Verify that writing into one that may be
+  // initialized in another translation unit isn't diagnosed.  */
+  T (gma.ax, 0);          // { dg-bogus "\\\[-Warray-bounds" }
 }
 
 
diff --git a/gcc/testsuite/gcc.dg/Wrestrict-9.c b/gcc/testsuite/gcc.dg/Wrestrict-9.c
index 5ad87401ed9..878fc811bc6 100644
--- a/gcc/testsuite/gcc.dg/Wrestrict-9.c
+++ b/gcc/testsuite/gcc.dg/Wrestrict-9.c
@@ -1,7 +1,7 @@
 /* PR tree-optimization/84095 - false-positive -Wrestrict warnings for
    strcpy within array
    { dg-do compile }
-   { dg-options "-O2 -Wrestrict -ftrack-macro-expansion=0" } */
+   { dg-options "-O2 -Wrestrict -Wno-stringop-overflow -ftrack-macro-expansion=0" } */
 
 typedef __SIZE_TYPE__ size_t;
 
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-12.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-12.c
index 5a05f5cf7cf..1e67b5fd928 100644
--- a/gcc/testsuite/gcc.dg/Wstringop-overflow-12.c
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-12.c
@@ -29,15 +29,15 @@ void test_memcpy_array_cst_range_off (const void *s)
   T (d + UR (1, 2), 6);       /* { dg-warning ".memcpy. writing 6 bytes into a region of size 5 overflows the destination" } */
   T (d + UR (1, 2), 7);       /* { dg-warning "writing 7 bytes into a region of size 5 " } */
 
-  T (d + SR (-3, -2), 1);     /* { dg-warning "writing 1 byte into a region of size 0 " "pr85350" { xfail *-*-* } } */
+  T (d + SR (-3, -2), 1);     /* { dg-warning "writing 1 byte into a region of size 0 " } */
   T (d + SR (-2, -1), 1);
   T (d + SR (-2, -1), 2);     /* { dg-warning "writing 2 bytes into a region of size 7 " "pr89428" { xfail *-*-* } } */
-  T (d + SR (-2, -1), 9);     /* { dg-warning "writing 9 bytes into a region of size 7 " "pr85350" { xfail *-*-* } } */
+  T (d + SR (-2, -1), 9);     /* { dg-warning "writing 9 bytes into a region of size 7 " } */
 
   d = ga7 + 7;
   T (d + SR (-7, -6), 1);
   T (d + SR (-7, -1), 1);
-  T (d + SR (-2, -1), 3);     /* { dg-warning "writing 3 bytes into a region of size 2 " "pr85350" { xfail *-*-* } } */
+  T (d + SR (-2, -1), 3);     /* { dg-warning "writing 3 bytes into a region of size 2 " } */
 
   T (d + UR (1, 2), 1);       /* { dg-warning "writing 1 byte into a region of size 0 " } */
 }
@@ -67,15 +67,15 @@ void test_memset_array_unsigned_off (void)
   T (d + UR (1, 2), 6);       /* { dg-warning ".memset. writing 6 bytes into a region of size 5 overflows the destination" } */
   T (d + UR (1, 2), 7);       /* { dg-warning "writing 7 bytes into a region of size 5 " } */
 
-  T (d + SR (-3, -2), 1);     /* { dg-warning "writing 1 byte into a region of size 0 " "pr85350" { xfail *-*-* } } */
+  T (d + SR (-3, -2), 1);     /* { dg-warning "writing 1 byte into a region of size 0 " } */
   T (d + SR (-2, -1), 1);
   T (d + SR (-2, -1), 2);     /* { dg-warning "writing 2 bytes into a region of size 7 " "pr89428" { xfail *-*-* } } */
-  T (d + SR (-2, -1), 9);     /* { dg-warning "writing 9 bytes into a region of size 7 " "pr85350" { xfail *-*-* } } */
+  T (d + SR (-2, -1), 9);     /* { dg-warning "writing 9 bytes into a region of size 7 " } */
 
   d = ga7 + 7;
   T (d + SR (-7, -6), 1);
   T (d + SR (-7, -1), 1);
-  T (d + SR (-2, -1), 3);     /* { dg-warning "writing 3 bytes into a region of size 2 " "pr85350" { xfail *-*-* } } */
+  T (d + SR (-2, -1), 3);     /* { dg-warning "writing 3 bytes into a region of size 2 " } */
 
   T (d + UR (1, 2), 1);       /* { dg-warning "writing 1 byte into a region of size 0 " } */
 }
@@ -110,8 +110,8 @@ void test_memcpy_array_signed_off (const void *s)
 
   T (d + SR (-7, 7), 7);
   T (d + SR (-1, 1), 7);
-  T (d + SR (-1, 1), 9);      /* { dg-warning "writing 9 bytes into a region of size " "pr89428" { xfail *-*-* } } */
-  T (d + SR (-1, 2), 9);      /* { dg-warning "writing 9 bytes into a region of size " "pr89428" { xfail *-*-* } } */
+  T (d + SR (-1, 1), 9);      /* { dg-warning "writing 9 bytes into a region of size " } */
+  T (d + SR (-1, 2), 9);      /* { dg-warning "writing 9 bytes into a region of size " } */
   T (d + SR (1, 2), 1);
   T (d + SR (1, 2), 5);
 
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-28.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-28.c
index 8844b9f5e71..be7f51ad3a5 100644
--- a/gcc/testsuite/gcc.dg/Wstringop-overflow-28.c
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-28.c
@@ -224,7 +224,7 @@ void ptr_sub_from_end (int n, int i0, int i1, int i2, int i3)
   q += n;      //  N=1     N=2
   q[-1] = 0;   // p[0]    p[1]
   q[-2] = 1;   // p[-1]   p[0]
-  q[-3] = 2;   // p[-2]   p[-1]   // { dg-warning "\\\[-Wstringop-overflow" "pr92939: negative offset from end" { xfail *-*-* } }
+  q[-3] = 2;   // p[-2]   p[-1]   // { dg-warning "\\\[-Wstringop-overflow" "pr92939: negative offset from end" }
 
   /* The following isn't diagnosed because the warning doesn't recognize
      the index below as necessarily having the same value as the size
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-33.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-33.c
new file mode 100644
index 00000000000..75edfef40ea
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-33.c
@@ -0,0 +1,89 @@
+/* { dg-do compile }
+   { dg-options "-O1 -Wall -Wno-array-bounds" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern void* memcpy (void*, const void*, size_t);
+extern void* memset (void*, int, size_t);
+
+
+struct Xp { char *p; } xp;
+struct Xa { char *a[2]; } xa;
+
+void nowarn_copy_read_pointed_obj_plus_cst (void *d)
+{
+  int i = 0;
+  memcpy (d, xp.p + i, 9);
+  memcpy (d, xa.a[i], 9);
+}
+
+void nowarn_copy_read_pointed_obj_plus_var (void *d, int i)
+{
+  memcpy (d, xp.p + i, 9);
+  memcpy (d, xa.a[i], 9);
+}
+
+
+void warn_copy_read_pointer_plus_cst (void *d)
+{
+  int i = 0;
+  memcpy (d, &xp.p + i, 9);   // { dg-warning "reading 9 bytes from a region of size . " }
+}
+
+void warn_copy_read_pointer_plus_var (void *d, int i)
+{
+  memcpy (d, &xp.p + i, 9);   // { dg-warning "reading 9 bytes from a region of size . " }
+}
+
+
+void nowarn_copy_write_pointed_obj_plus_cst (const void *s)
+{
+  int i = 0;
+  memcpy (xp.p + i, s, 9);
+  memcpy (xa.a[i], s, 9);
+}
+
+void nowarn_copy_write_pointed_obj_plus_var (const void *s, int i)
+{
+  memcpy (xp.p + i, s, 9);
+  memcpy (xa.a[i], s, 9);
+}
+
+
+void warn_copy_write_pointer_plus_cst (const void *s)
+{
+  int i = 0;
+  memcpy (&xp.p + i, s, 9);   // { dg-warning "writing 9 bytes into a region of size . " }
+}
+
+void warn_copy_write_pointer_plus_var (const void *s, int i)
+{
+  memcpy (&xp.p + i, s, 9);   // { dg-warning "writing 9 bytes into a region of size . " }
+}
+
+
+void nowarn_set_pointed_obj_plus_cst (void)
+{
+  int i = 0;
+  memset (xp.p + i, 0, 9);
+  memset (xa.a[i], 0, 9);
+}
+
+void nowarn_set_pointed_obj_plus_var (int i)
+{
+  memset (xp.p + i, 0, 9);
+  memset (xa.a[i], 0, 9);
+}
+
+
+void warn_set_pointer_plus_cst (void)
+{
+  int i = 0;
+  memset (&xp.p + i, 0, 9);   // { dg-warning "writing 9 bytes into a region of size . " }
+}
+
+void warn_set_pointer_plus_var (int i)
+{
+  memset (&xp.p + i, 0, 9);   // { dg-warning "writing 9 bytes into a region of size . " }
+  memset (&xa.a[i], 0, 17);   // { dg-warning "writing 17 bytes into a region of size \[0-9\]+ " }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-34.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-34.c
new file mode 100644
index 00000000000..13a28397a7d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-34.c
@@ -0,0 +1,211 @@
+/* PR middle-end/95353 - spurious -Wstringop-overflow writing to a trailing
+   array plus offset
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+struct S0 { char n, a[0]; };
+
+
+void s0_nowarn_cstidx (struct S0 *p)
+{
+  char *q = p->a;
+  q[1] = __LINE__;
+  q[9] = __LINE__;
+}
+
+void s0_nowarn_cstoff_cstidx (struct S0 *p)
+{
+  char *q = p->a + 1;
+  q[1] = __LINE__;
+  q[9] = __LINE__;
+}
+
+void s0_nowarn_varoff_cstdix (struct S0 *p, int i)
+{
+  char *q = p->a + i;
+  q[1] = __LINE__;            // { dg-bogus "\\\[-Wstringop-overflow" }
+  q[9] = __LINE__;            // { dg-bogus "\\\[-Wstringop-overflow" }
+}
+
+void s0_nowarn_cstoff_varidx (struct S0 *p, int i)
+{
+  char *q = p->a + 1;
+  q[i] = __LINE__;
+}
+
+void s0_nowarn_varoff_varidx (struct S0 *p, int i, int j)
+{
+  char *q = p->a + i;
+  q[j] = __LINE__;            // { dg-bogus "\\\[-Wstringop-overflow" }
+}
+
+
+/* Accesses past the end of a trailing array with one element is
+   discouraged but still reluctantly not diagnosed.  This should
+   change.  */
+
+struct S1 { char n, a[1]; };
+
+
+void s1_nowarn_cstidx (struct S1 *p)
+{
+  char *q = p->a;
+  q[1] = __LINE__;
+  q[9] = __LINE__;
+}
+
+void s1_nowarn_cstoff_cstidx (struct S1 *p)
+{
+  char *q = p->a + 1;
+  q[1] = __LINE__;
+  q[9] = __LINE__;
+}
+
+void s1_nowarn_varoff_cstdix (struct S1 *p, int i)
+{
+  char *q = p->a + i;
+  q[1] = __LINE__;            // { dg-bogus "\\\[-Wstringop-overflow" }
+  q[9] = __LINE__;            // { dg-bogus "\\\[-Wstringop-overflow" }
+}
+
+void s1_nowarn_cstoff_varidx (struct S1 *p, int i)
+{
+  char *q = p->a + 1;
+  q[i] = __LINE__;
+}
+
+void s1_nowarn_varoff_varidx (struct S1 *p, int i, int j)
+{
+  char *q = p->a + i;
+  q[j] = __LINE__;
+}
+
+
+/* Accesses past the end of a trailing array with more than one
+   element should be diagnosed but aren't yet because the MEM_REF
+   makes the out-of-bounds accesses indistinguishable from valid
+   ones to subsequent elements of the array pointed by P.  */
+
+struct S2 { char n, a[2]; };
+
+
+void s2_warn_cstidx (struct S2 *p)
+{
+  char *q = p->a;
+
+  /* The following invalid store is represented as
+       MEM[(char *)p_1(D) + 3B] = __LINE__;
+     which is indistinguishable from the valid
+       q = &p[1].n; q[0] = __LINE__;
+  */
+  q[2] = __LINE__;            // { dg-warning "\\\[-Wstringop-overflow" "pr?????" { xfail *-*-* } }
+}
+
+void s2_warn_cstoff_cstidx (struct S2 *p)
+{
+  char *q = p->a + 1;
+  q[1] = __LINE__;            // { dg-warning "\\\[-Wstringop-overflow" "pr?????" { xfail *-*-* }  }
+}
+
+void s2_warn_varoff_cstdix (struct S2 *p, int i)
+{
+  char *q = p->a + i;
+  q[2] = __LINE__;            // { dg-warning "\\\[-Wstringop-overflow" "pr?????" { xfail *-*-* }  }
+}
+
+void s2_warn_cstoff_varidx (struct S2 *p, int i)
+{
+  char *q = p->a + 1;
+  q[2] = __LINE__;            // { dg-warning "\\\[-Wstringop-overflow" "pr?????" { xfail *-*-* }  }
+}
+
+void s2_warn_varoff_varidx (struct S2 *p, int i, int j)
+{
+  char *q = p->a + i;
+  q[j] = __LINE__;            // { dg-warning "\\\[-Wstringop-overflow" "pr?????" { xfail *-*-* }  }
+}
+
+
+/* Verify that none of these triggers a bogus warning (not tested
+   elsewhere but triggered during bootstrap).  */
+
+void s2_nowarn_varidx_int (struct S2 *p, int i)
+{
+  extern struct S2 s2;
+  extern struct S2 s2a[];
+
+  s2.a[i - 1] = __LINE__;
+  s2.a[i] = __LINE__;
+  s2.a[i + 1] = __LINE__;
+
+  s2a[i].a[i - 1] = __LINE__;
+  s2a[i].a[i] = __LINE__;
+  s2a[i].a[i + 1] = __LINE__;
+
+  p[i].a[i - 1] = __LINE__;
+  p[i].a[i] = __LINE__;
+  p[i].a[i + 1] = __LINE__;
+
+  char *q = p->a;
+  q[i - 1] = __LINE__;
+  q[i] = __LINE__;
+  q[i + 1] = __LINE__;
+}
+
+/* Same as above but with a size_t index in range [1, SIZE_MAX].  */
+
+void* s2_nowarn_varidx_size (struct S2 *p, size_t i, size_t j)
+{
+  extern struct S2 s2;
+  extern struct S2 s2a[];
+  struct S2 *ps2 = __builtin_malloc (3 * sizeof *ps2);
+
+  s2.a[i - 1] = __LINE__;
+  s2.a[i] = __LINE__;
+  s2.a[i + 1] = __LINE__;
+
+  s2a[i].a[i - 1] = __LINE__;
+  s2a[i].a[i] = __LINE__;
+  s2a[i].a[i + 1] = __LINE__;
+
+  p[i].a[i - 1] = __LINE__;
+  p[i].a[i] = __LINE__;
+  p[i].a[i + 1] = __LINE__;
+
+  ps2->a[i - 1] = __LINE__;
+  ps2->a[i] = __LINE__;
+  ps2->a[i + 1] = __LINE__;
+
+  char *q = p->a;
+  q[i - 1] = __LINE__;
+  q[i] = __LINE__;
+  q[i + 1] = __LINE__;
+
+  if (j == 0)
+    return ps2;
+
+  s2.a[j - 1] = __LINE__;
+  s2.a[j] = __LINE__;
+  s2.a[j + 1] = __LINE__;
+
+  s2a[j].a[j - 1] = __LINE__;
+  s2a[j].a[j] = __LINE__;
+  s2a[j].a[j + 1] = __LINE__;
+
+  p[j].a[j - 1] = __LINE__;
+  p[j].a[j] = __LINE__;
+  p[j].a[j + 1] = __LINE__;
+
+  ps2->a[j - 1] = __LINE__;
+  ps2->a[j] = __LINE__;
+  ps2->a[j + 1] = __LINE__;
+
+  q = p->a;
+  q[j - 1] = __LINE__;
+  q[j] = __LINE__;
+  q[j + 1] = __LINE__;
+
+  return ps2;
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-35.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-35.c
new file mode 100644
index 00000000000..612137ee469
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-35.c
@@ -0,0 +1,76 @@
+/* Verify that calls to strcpy to write to an element of an array of pointers
+   are not diagnosed (due to mistakenly using the size of the array as that
+   of the destination).
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+typedef char A1[1];
+typedef char A2[2];
+typedef char A3[3];
+typedef char A4[4];
+typedef char A5[5];
+typedef char A6[6];
+typedef char A7[7];
+typedef char A8[8];
+typedef char A9[9];
+typedef char A10[10];
+
+A1* pa1[3];
+A2* pa2[3];
+A3* pa3[3];
+A4* pa4[3];
+A5* pa5[3];
+A6* pa6[3];
+A7* pa7[3];
+A8* pa8[3];
+A9* pa9[3];
+A10* pa10[3];
+
+void nowarn_a1_1 (int i)
+{
+  __builtin_strcpy (*pa1[0], "");
+  __builtin_strcpy (*pa1[1], "");
+  __builtin_strcpy (*pa1[i], "");
+}
+
+void nowarn_a2_2 (int i)
+{
+  __builtin_strcpy (*pa2[0], "1");
+  __builtin_strcpy (*pa2[1], "2");
+  __builtin_strcpy (*pa2[i], "3");
+}
+
+void nowarn_a3_3 (int i)
+{
+  __builtin_strcpy (*pa3[0], "12");
+  __builtin_strcpy (*pa3[1], "23");
+  __builtin_strcpy (*pa3[i], "34");
+}
+
+void nowarn_a4_4 (int i)
+{
+  __builtin_strcpy (*pa4[0], "123");
+  __builtin_strcpy (*pa4[1], "234");
+  __builtin_strcpy (*pa4[i], "345");
+}
+
+void nowarn_a5_5 (int i)
+{
+  __builtin_strcpy (*pa5[0], "1234");
+  __builtin_strcpy (*pa5[1], "2345");
+  __builtin_strcpy (*pa5[i], "3456");
+}
+
+void nowarn_a6_6 (int i)
+{
+  __builtin_strcpy (*pa6[0], "12345");
+  __builtin_strcpy (*pa6[1], "23456");
+  __builtin_strcpy (*pa6[1], "34567");
+}
+
+void nowarn_a10_10 (int i)
+{
+  __builtin_strcpy (*pa10[0], "0123456789");
+  __builtin_strcpy (*pa10[1], "1234567890");
+  __builtin_strcpy (*pa10[i], "2345678909");
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-36.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-36.c
new file mode 100644
index 00000000000..3f0874dc5bb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-36.c
@@ -0,0 +1,24 @@
+/* Verify that casts between pointers and integers don't trigger false
+   positives.  Test derived from Glibc's _dl_allocate_tls_storage() in
+   dl-tls.c.
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wno-array-bounds" } */
+
+typedef __SIZE_TYPE__ size_t;
+typedef __UINTPTR_TYPE__ uintptr_t;
+
+size_t a;
+size_t s;
+
+void* _dl_allocate_tls_storage (void)
+{
+  void *p = __builtin_malloc (s + a + sizeof (void *));
+
+  char *q = (char *)(__builtin_constant_p (a) && (((a - 1) & a) == 0)
+             ? ((((uintptr_t)p) + a - 1) & ~(a - 1))
+             : (((((uintptr_t)p) + (a - 1)) / a) * a));
+
+  char *r = q + s - sizeof (int[4]);
+  __builtin_memset (r, '\0', sizeof (int[4]));
+  return r;
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c
new file mode 100644
index 00000000000..339f904d7a6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c
@@ -0,0 +1,229 @@
+/* Verify that -Wstringop-overflow detects writing past the end of each
+   individual element of a multidimensional array.
+  { dg-do compile }
+  { dg-options "-O2 -Wall -Wno-array-bounds -Wno-stringop-truncation" } */
+
+#define CONCAT(x, y)    x ## y
+#define CAT(name, line) CONCAT (name, line)
+#define UNIQ_NAME(name) CAT (name, __LINE__)
+
+typedef __SIZE_TYPE__ size_t;
+
+extern void* malloc (size_t);
+extern char* strncpy (char*, const char*, size_t);
+
+extern char a2_2_8[2][2][8];
+
+void nowarn_a2_2_8 (const char *s)
+{
+  // The following trigger -Wstringop-truncation.
+  strncpy (a2_2_8[0][0], s, 8);
+  strncpy (a2_2_8[0][1], s, 8);
+  strncpy (a2_2_8[1][0], s, 8);
+  strncpy (a2_2_8[1][1], s, 8);
+}
+
+void warn_a2_2_8 (const char *s)
+{
+  strncpy (a2_2_8[0][0], s, 9);         // { dg-warning "writing 9 bytes into a region of size 8 " }
+  strncpy (a2_2_8[0][1], s, 9);         // { dg-warning "writing 9 bytes into a region of size 8 " }
+  strncpy (a2_2_8[1][0], s, 9);         // { dg-warning "writing 9 bytes into a region of size 8 " }
+  strncpy (a2_2_8[1][1], s, 9);         // { dg-warning "writing 9 bytes into a region of size 8 " }
+}
+
+
+extern char a2_3_5[2][3][5];
+
+void nowarn_a2_3_5 (const char *s)
+{
+  // The following trigger -Wstringop-truncation.
+  strncpy (a2_3_5[0][0], s, 5);
+  strncpy (a2_3_5[0][1], s, 5);
+  strncpy (a2_3_5[0][2], s, 5);
+  strncpy (a2_3_5[1][0], s, 5);
+  strncpy (a2_3_5[1][1], s, 5);
+  strncpy (a2_3_5[1][2], s, 5);
+}
+
+void warn_a2_3_5 (const char *s)
+{
+  strncpy (a2_3_5[0][0], s, 6);         // { dg-warning "writing 6 bytes into a region of size 5 " }
+  strncpy (a2_3_5[0][1], s, 7);         // { dg-warning "writing 7 bytes into a region of size 5 " }
+  strncpy (a2_3_5[1][0], s, 8);         // { dg-warning "writing 8 bytes into a region of size 5 " }
+  strncpy (a2_3_5[1][1], s, 9);         // { dg-warning "writing 9 bytes into a region of size 5 " }
+}
+
+
+void* nowarn_malloc_3_5 (const char *s, unsigned n)
+{
+  if (n < 3 || 5 < n)
+    n = 3;
+  char *p = (char*)malloc (n);
+  strncpy (p + 1, s, 4);
+  return p;
+}
+
+void* warn_malloc_3_5 (const char *s, unsigned n)
+{
+  if (n < 3 || 5 < n)
+    n = 3;
+  char *p = (char*)malloc (n);          // { dg-message "at offset 1 into destination object of size \\\[3, 5] allocated by 'malloc'" }
+  // The size below should be a range like the one above.
+  strncpy (p + 1, s, 5);                // { dg-warning "writing 5 bytes into a region of size 4 " }
+  return p;
+}
+
+
+typedef __attribute__ ((alloc_size (1, 2))) void* UsrAlloc (int, int);
+
+void* nowarn_use_alloc_3_5 (UsrAlloc *usr_alloc, const char *s, unsigned n)
+{
+  if (n < 3 || 5 < n)
+    n = 3;
+  char *p = (char*)usr_alloc (n, 3);
+  strncpy (p + 1, s, 14);
+  return p;
+}
+
+void* warn_usr_alloc_3_5 (UsrAlloc *usr_alloc, const char *s, unsigned n)
+{
+  if (n < 3 || 5 < n)
+    n = 3;
+  char *p = (char*)usr_alloc (n, 3);    // { dg-message "at offset 1 into destination object of size \\\[9, 15] allocated by 'usr_alloc'" }
+  // The size below should be a range like the one above.
+  strncpy (p + 1, s, 15);               // { dg-warning "writing 15 bytes into a region of size 14 " }
+  return p;
+}
+
+struct S
+{
+  char a2_3_4[2][3][4];
+  char a3_4_5[3][4][5];
+};
+
+extern struct S sa[];
+
+void nowarn_sa_cstidx_cstsize (const char* const s[])
+{
+  strncpy (sa[0].a2_3_4[0][0], s[0], 4);
+  strncpy (sa[0].a2_3_4[0][1], s[1], 4);
+  strncpy (sa[0].a2_3_4[0][2], s[2], 4);
+
+  strncpy (sa[0].a2_3_4[1][0], s[3], 4);
+  strncpy (sa[0].a2_3_4[1][1], s[4], 4);
+  strncpy (sa[0].a2_3_4[1][2], s[5], 4);
+
+  strncpy (sa[1].a2_3_4[0][0], s[6], 4);
+  strncpy (sa[1].a2_3_4[0][1], s[7], 4);
+  strncpy (sa[1].a2_3_4[0][2], s[8], 4);
+
+  strncpy (sa[1].a2_3_4[1][0], s[9], 4);
+  strncpy (sa[1].a2_3_4[1][1], s[10], 4);
+  strncpy (sa[1].a2_3_4[1][2], s[11], 4);
+}
+
+void warn_sa_cstidx_cstsize (const char* const s[])
+{
+  strncpy (sa[0].a2_3_4[0][0], s[0], 5);    // { dg-warning "writing 5 bytes into a region of size 4 " }
+  strncpy (sa[0].a2_3_4[0][1], s[1], 6);    // { dg-warning "writing 6 bytes into a region of size 4 " }
+  strncpy (sa[0].a2_3_4[0][2], s[2], 7);    // { dg-warning "writing 7 bytes into a region of size 4 " }
+
+  strncpy (sa[0].a2_3_4[1][0], s[3], 5);    // { dg-warning "writing 5 bytes into a region of size 4 " }
+  strncpy (sa[0].a2_3_4[1][1], s[4], 6);    // { dg-warning "writing 6 bytes into a region of size 4 " }
+  strncpy (sa[0].a2_3_4[1][2], s[5], 7);    // { dg-warning "writing 7 bytes into a region of size 4 " }
+
+  strncpy (sa[1].a2_3_4[0][0], s[6], 5);    // { dg-warning "writing 5 bytes into a region of size 4 " }
+  strncpy (sa[1].a2_3_4[0][1], s[7], 6);    // { dg-warning "writing 6 bytes into a region of size 4 " }
+  strncpy (sa[1].a2_3_4[0][2], s[8], 7);    // { dg-warning "writing 7 bytes into a region of size 4 " }
+
+  strncpy (sa[1].a2_3_4[1][0], s[9], 5);    // { dg-warning "writing 5 bytes into a region of size 4 " }
+  strncpy (sa[1].a2_3_4[1][1], s[10], 6);   // { dg-warning "writing 6 bytes into a region of size 4 " }
+  strncpy (sa[1].a2_3_4[1][2], s[11], 7);   // { dg-warning "writing 7 bytes into a region of size 4 " }
+}
+
+void nowarn_sa_cstidx_varsize (const char* const s[], unsigned n)
+{
+  strncpy (sa[0].a2_3_4[0][0], s[0], n);
+  strncpy (sa[0].a2_3_4[0][1], s[1], n);
+  strncpy (sa[0].a2_3_4[0][2], s[2], n);
+
+  strncpy (sa[0].a2_3_4[1][0], s[3], n);
+  strncpy (sa[0].a2_3_4[1][1], s[4], n);
+  strncpy (sa[0].a2_3_4[1][2], s[5], n);
+
+  strncpy (sa[1].a2_3_4[0][0], s[6], n);
+  strncpy (sa[1].a2_3_4[0][1], s[7], n);
+  strncpy (sa[1].a2_3_4[0][2], s[8], n);
+
+  strncpy (sa[1].a2_3_4[1][0], s[9], n);
+  strncpy (sa[1].a2_3_4[1][1], s[10], n);
+  strncpy (sa[1].a2_3_4[1][2], s[11], n);
+}
+
+void nowarn_sa_loop (const char* const s[], unsigned n)
+{
+  for (unsigned i0 = 0; i0 != 5; ++i0)
+    for (unsigned i1 = 0; i1 != 3; ++i1)
+      for (unsigned i2 = 0; i2 != 2; ++i2)
+	strncpy (sa[i0].a2_3_4[i1][i2], s[i2], n);
+}
+
+
+/* Verify that a note after the warning points to the accessed object
+   and mentions the starting offset of the access.  Another alternative
+   might be for the offset to be the starting offset of the overflow.
+   As it is, it's not clear to which of the two the offset refers.  */
+
+void test_note (const char *s)
+{
+  extern void sink (void*);
+
+  {
+    char a[1][1][2];                    // { dg-message "destination object" }
+    strncpy (a[0][0], s, 3);            // { dg-warning "writing 3 bytes into a region of size 2 " }
+    sink (a);
+  }
+
+  {
+    char a[1][2][2];                    // { dg-message "at offset 2 into " }
+    strncpy (a[0][1], s, 3);            // { dg-warning "writing 3 bytes into a region of size 2 " }
+    sink (a);
+  }
+
+  {
+    char a[1][2][2];                    // { dg-message "at offset 4 into " }
+    strncpy (a[1][0], s, 3);            // { dg-warning "writing 3 bytes into a region of size 2 " }
+    sink (a);
+  }
+
+  {
+    char a[2][1][2];                    // { dg-message "at offset 2 into " }
+    strncpy (a[1][0], s, 3);            // { dg-warning "writing 3 bytes into a region of size 2 " }
+    sink (a);
+  }
+
+  {
+    char a[2][2][3];                    // { dg-message "at offset 9 into " }
+    strncpy (a[1][1], s, 4);            // { dg-warning "writing 4 bytes into a region of size 3 " }
+    sink (a);
+  }
+
+  {
+    char a[2][3][3];                    // { dg-message "at offset 12 into " }
+    strncpy (a[1][1], s, 5);            // { dg-warning "writing 5 bytes into a region of size 3 " }
+    sink (a);
+  }
+
+  {
+    char a[2][3][3];                    // { dg-message "at offset 12 into " }
+    strncpy (a[1][1], s, 6);            // { dg-warning "writing 6 bytes into a region of size 3 " }
+    sink (a);
+  }
+
+  {
+    char a[2][3][3];                    // { dg-message "at offset 15 into " }
+    strncpy (a[1][2], s, 7);            // { dg-warning "writing 7 bytes into a region of size 3 " }
+    sink (a);
+  }
+
+}
diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-4.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-4.c
index 6e836c5d4d3..2409e597aac 100644
--- a/gcc/testsuite/gcc.dg/builtin-stringop-chk-4.c
+++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-4.c
@@ -99,7 +99,7 @@ void test_memcpy_range (void *d, const void *s)
 
   memcpy (buf + 5, s, UR (1, 2));  /* { dg-warning "writing between 1 and 2 bytes into a region of size 0 overflows the destination" } */
 
-  memcpy (buf + size_max, s, UR (1, 2));  /* { dg-warning "writing between 1 and 2 bytes into a region of size 0 overflows the destination" "excessive pointer offset" { xfail *-*-* } } */
+  memcpy (buf + size_max, s, UR (1, 2));  /* { dg-warning "writing between 1 and 2 bytes into a region of size 0 overflows the destination" "excessive pointer offset" } */
 
   memcpy (buf, s, UR (ssize_max, size_max));   /* { dg-warning "writing \[0-9\]+ or more bytes into a region of size 5 overflows the destination" } */
   memcpy (buf, s, UR (ssize_max + 1, size_max));  /* { dg-warning "specified \(bound|size\) between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c
index 87dd6ac4e89..5398e9f1f9c 100644
--- a/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c
+++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c
@@ -1,6 +1,6 @@
 /* Test exercising -Wstringop-overflow warnings.  */
 /* { dg-do compile } */
-/* { dg-options "-O2 -Wstringop-overflow=1" } */
+/* { dg-options "-O2 -Wstringop-overflow=1 -Wno-array-bounds" } */
 
 #define offsetof(type, mem)   __builtin_offsetof (type, mem)
 
@@ -120,7 +120,7 @@ void test_memop_warn_alloc (const void *src)
 
   /* Verify the same as above but by writing into the first mmeber
      of the first element of the array.  */
-  memcpy (&a[0].a, src, n);   /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" "memcpy into allocated" { xfail *-*-*} } */
+  memcpy (&a[0].a, src, n);   /* { dg-warning "writing between 8 and 32 bytes into a region of size " "memcpy into allocated" } */
   escape (a, src);
 
   n = range (12, 32);
@@ -133,33 +133,33 @@ void test_memop_warn_alloc (const void *src)
   /* The following idiom of clearing multiple members of a struct is
      used in a few places in the Linux kernel.  Verify that a warning
      is issued for it when it writes past the end of the array object.  */
-  memset (&b[0].a.b, 0, offsetfrom (struct B, b, a.b) + 1);   /* { dg-warning "writing 8 bytes into a region of size 7" "memcpy into allocated" { xfail *-*-*} } */
+  memset (&b[0].a.b, 0, offsetfrom (struct B, b, a.b) + 1);   /* { dg-warning "writing 8 bytes into a region of size " "memcpy into allocated" } */
   escape (b);
 
-  memset (&b->a.b, 0, offsetfrom (struct B, b, a.b) + 1);   /* { dg-warning "writing 8 bytes into a region of size 7" "memcpy into allocated" { xfail *-*-*} } */
+  memset (&b->a.b, 0, offsetfrom (struct B, b, a.b) + 1);   /* { dg-warning "writing 8 bytes into a region of size " "memcpy into allocated" } */
   escape (b);
 
-  memset (&b[0].c, 0, offsetfrom (struct B, b, c) + 1);   /* { dg-warning "writing 7 bytes into a region of size 6" "memcpy into allocated" { xfail *-*-*} } */
+  memset (&b[0].c, 0, offsetfrom (struct B, b, c) + 1);   /* { dg-warning "writing 7 bytes into a region of size " "memcpy into allocated" } */
   escape (b);
 
-  memset (&b->c, 0, offsetfrom (struct B, b, c) + 1);   /* { dg-warning "writing 7 bytes into a region of size 6" "memcpy into allocated" { xfail *-*-*} } */
+  memset (&b->c, 0, offsetfrom (struct B, b, c) + 1);   /* { dg-warning "writing 7 bytes into a region of size " "memcpy into allocated" } */
   escape (b);
 
-  memset (&b[0].d, 0, offsetfrom (struct B, b, d) + 1);   /* { dg-warning "writing 6 bytes into a region of size 5" "memcpy into allocated" { xfail *-*-*} } */
+  memset (&b[0].d, 0, offsetfrom (struct B, b, d) + 1);   /* { dg-warning "writing 6 bytes into a region of size " "memcpy into allocated" } */
   escape (b);
 
-  memset (&b->d, 0, offsetfrom (struct B, b, d) + 1);   /* { dg-warning "writing 6 bytes into a region of size 5" "memcpy into allocated" { xfail *-*-*} } */
+  memset (&b->d, 0, offsetfrom (struct B, b, d) + 1);   /* { dg-warning "writing 6 bytes into a region of size " "memcpy into allocated" } */
   escape (b);
 
   /* Same as above but clearing just elements of the second element
      of the array.  */
-  memset (&b[1].a.b, 0, offsetfrom (struct B, b[1], a.b) + 1);   /* { dg-warning "writing 4 bytes into a region of size 3" "memcpy into allocated" { xfail *-*-*} } */
+  memset (&b[1].a.b, 0, offsetfrom (struct B, b[1], a.b) + 1);   /* { dg-warning "writing 4 bytes into a region of size " "memcpy into allocated" } */
   escape (b);
 
-  memset (&b[1].c, 0, offsetfrom (struct B, b[1], c) + 1);   /* { dg-warning "writing 3 bytes into a region of size 2" "memcpy into allocated" { xfail *-*-*} } */
+  memset (&b[1].c, 0, offsetfrom (struct B, b[1], c) + 1);   /* { dg-warning "writing 3 bytes into a region of size " "memcpy into allocated" } */
   escape (b);
 
-  memset (&b[1].d, 0, offsetfrom (struct B, b[1], d) + 1);   /* { dg-warning "writing 2 bytes into a region of size 1" "memcpy into allocated" { xfail *-*-*} } */
+  memset (&b[1].d, 0, offsetfrom (struct B, b[1], d) + 1);   /* { dg-warning "writing 2 bytes into a region of size 1" "memcpy into allocated" } */
   escape (b);
 }
 
diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-8.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-8.c
index 741c1f88eaa..12d2491b9f5 100644
--- a/gcc/testsuite/gcc.dg/builtin-stringop-chk-8.c
+++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-8.c
@@ -106,7 +106,7 @@ void test_memop_warn_alloc (void *p)
 
   memcpy (p, &a[0], n);   /* { dg-warning "reading between 8 and 32 bytes from a region of size 4" "memcpy from allocated" } */
 
-  memcpy (p, &a[0].a, n);   /* { dg-warning "reading between 8 and 32 bytes from a region of size 4" "memcpy from allocated" { xfail *-*-*} } */
+  memcpy (p, &a[0].a, n);   /* { dg-warning "reading between 8 and 32 bytes from a region of size " "memcpy from allocated" } */
 
   n = range (12, 32);
 
diff --git a/gcc/testsuite/gcc.dg/strlenopt-74.c b/gcc/testsuite/gcc.dg/strlenopt-74.c
index 0a9ac6c1da2..19e9a608583 100644
--- a/gcc/testsuite/gcc.dg/strlenopt-74.c
+++ b/gcc/testsuite/gcc.dg/strlenopt-74.c
@@ -75,7 +75,7 @@ VERIFY (i0 ? (a8 + 2) : (b8 + 2), 7, 6);
 VERIFY (i0 ? (a8 + 0) : (c4 + 0), 9, 4);
 VERIFY (i0 ? (a8 + 0) : (c4 + 1), 9, 3);
 VERIFY (i0 ? (a8 + 0) : (c4 + 3), 9, 1);
-VERIFY (i0 ? (a8 + 0) : (c4 + 4), 9, 0);
+VERIFY (i0 ? (a8 + 0) : (c4 + 4), 8, 0);
 VERIFY (i0 ? (a8 + 1) : (c4 + 0), 8, 4);
 VERIFY (i0 ? (a8 + 1) : (c4 + 1), 8, 3);
 VERIFY (i0 ? (a8 + 1) : (c4 + 2), 8, 2);
diff --git a/gcc/tree-object-size.c b/gcc/tree-object-size.c
index 8855065f577..b73a8cb7b1b 100644
--- a/gcc/tree-object-size.c
+++ b/gcc/tree-object-size.c
@@ -172,7 +172,7 @@ compute_object_offset (const_tree expr, const_tree var)
    is true, else null.  An object's initializer affects the object's
    size if it's a struct type with a flexible array member.  */
 
-static tree
+tree
 decl_init_size (tree decl, bool min)
 {
   tree size = DECL_SIZE_UNIT (decl);
@@ -189,7 +189,7 @@ decl_init_size (tree decl, bool min)
       || TYPE_SIZE (last_type))
     return size;
 
-  /* Use TYPE_SIZE_UNIT; DECL_SIZE_UNIT sometimes reflects the size
+  /* Use TYPE_SIZE_UNIT; DECL_SIZEgcc.dg/Warray-bounds-46.c_UNIT sometimes reflects the size
      of the initializer and sometimes doesn't.  */
   size = TYPE_SIZE_UNIT (type);
   tree ref = build3 (COMPONENT_REF, type, decl, last, NULL_TREE);
@@ -259,6 +259,11 @@ addr_object_size (struct object_size_info *osi, const_tree ptr,
 	  offset_int mem_offset;
 	  if (mem_ref_offset (pt_var).is_constant (&mem_offset))
 	    {
+	      if (*poff)
+		*poff = wide_int_to_tree (ptrdiff_type_node,
+					  mem_offset + wi::to_offset (*poff));
+	      else
+		*poff = wide_int_to_tree (ptrdiff_type_node, mem_offset);
 	      offset_int dsz = wi::sub (sz, mem_offset);
 	      if (wi::neg_p (dsz))
 		sz = 0;
@@ -413,12 +418,12 @@ addr_object_size (struct object_size_info *osi, const_tree ptr,
       bytes = compute_object_offset (TREE_OPERAND (ptr, 0), var);
       if (bytes != error_mark_node)
 	{
+	  *poff = bytes;
 	  if (TREE_CODE (bytes) == INTEGER_CST
 	      && tree_int_cst_lt (var_size, bytes))
 	    bytes = size_zero_node;
 	  else
 	    bytes = size_binop (MINUS_EXPR, var_size, bytes);
-	  *poff = bytes;
 	}
       if (var != pt_var
 	  && pt_var_size
@@ -441,7 +446,11 @@ addr_object_size (struct object_size_info *osi, const_tree ptr,
   else if (!pt_var_size)
     return false;
   else
-    bytes = pt_var_size;
+    {
+      bytes = pt_var_size;
+      if (!*poff)
+	*poff = size_zero_node;
+    }
 
   if (tree_fits_uhwi_p (bytes))
     {
diff --git a/gcc/tree-object-size.h b/gcc/tree-object-size.h
index 871dcdba06a..89d4c46be72 100644
--- a/gcc/tree-object-size.h
+++ b/gcc/tree-object-size.h
@@ -24,5 +24,6 @@ extern void init_object_sizes (void);
 extern void fini_object_sizes (void);
 extern bool compute_builtin_object_size (tree, int, unsigned HOST_WIDE_INT *,
 					 tree * = NULL, tree * = NULL);
+tree decl_init_size (tree, bool);
 
 #endif  // GCC_TREE_OBJECT_SIZE_H
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
index 93d095e1896..fbaee745f7d 100644
--- a/gcc/tree-ssa-strlen.c
+++ b/gcc/tree-ssa-strlen.c
@@ -5589,7 +5589,6 @@ handle_integral_assign (gimple_stmt_iterator *gsi, bool *cleanup_eh,
 	      set_strinfo (idx, si);
 	      si->writable = true;
 	      si->dont_invalidate = true;
-	      maybe_warn_overflow (stmt, lenrange[2], rvals);
 	    }
 	}
     }

Reply via email to