public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
@ 2020-08-11 16:19 Martin Sebor
  2020-08-19 15:00 ` [PING][PATCH] " Martin Sebor
  2020-09-01 19:22 ` [PATCH] " Jason Merrill
  0 siblings, 2 replies; 28+ messages in thread
From: Martin Sebor @ 2020-08-11 16:19 UTC (permalink / raw)
  To: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 995 bytes --]

-Wplacement-new handles array indices and pointer offsets the same:
by adjusting them by the size of the element.  That's correct for
the latter but wrong for the former, causing false positives when
the element size is greater than one.

In addition, the warning doesn't even attempt to handle arrays of
arrays.  I'm not sure if I forgot or if I simply didn't think of
it.

The attached patch corrects these oversights by replacing most
of the -Wplacement-new code with a call to compute_objsize which
handles all this correctly (plus more), and is also better tested.
But even compute_objsize has bugs: it trips up while converting
wide_int to offset_int for some pointer offset ranges.  Since
handling the C++ IL required changes in this area the patch also
fixes that.

For review purposes, the patch affects just the middle end.
The C++ diff pretty much just removes code from the front end.

Tested on x86_64-linux plus by building the latest Glibc and
confirming no new warnings.

Martin

[-- Attachment #2: gcc-96511.diff --]
[-- Type: text/x-patch, Size: 44051 bytes --]

Correct handling of indices into arrays with elements larger than 1 (PR c++/96511)

Resolves:
PR c++/96511 - Incorrect -Wplacement-new on POINTER_PLUS into an array with 4-byte elements
PR middle-end/96561 - missing warning for buffer overflow with negative offset
PR middle-end/96384 - bogus -Wstringop-overflow= storing into multidimensional array with index in range

gcc/ChangeLog:

	PR c++/96511
	PR middle-end/96384
	* builtins.c (get_range): Return full range of type when neither
	value nor its range is available.  Fail for ranges inverted due
	to the signedness of offsets.
	(compute_objsize): Handle more special array members.  Handle
	POINTER_PLUS_EXPR and VIEW_CONVERT_EXPR that come up in front end
	code.
	(access_ref::offset_bounded): Define new member function.
	* builtins.h (access_ref::eval): New data member.
	(access_ref::offset_bounded): New member function.
	(access_ref::offset_zero): New member function.
	(compute_objsize): Declare a new overload.
	* gimple-array-bounds.cc (array_bounds_checker::check_array_ref): Use
	enum special_array_member.
	* tree-object-size.c (decl_init_size): Return the size of the structure
	type if the decl size is null.
	* tree.c (component_ref_size): Use special_array_member.
	* tree.h (special_array_member): Define a new type.
	(component_ref_size): Change signature/	

gcc/cp/ChangeLog:

	PR c++/96511
	PR middle-end/96384
	* init.c (warn_placement_new_too_small): Call builtin_objsize instead
	of duplicating what it does.

gcc/testsuite/ChangeLog:

	PR c++/96511
	PR middle-end/96384
	* g++.dg/warn/Wplacement-new-size-1.C: Relax warnings.
	* g++.dg/warn/Wplacement-new-size-2.C: Same.
	* g++.dg/warn/Wplacement-new-size-6.C: Same.
	* g++.dg/warn/Wplacement-new-size-7.C: New test.
	* gcc.dg/Wstringop-overflow-40.c: New test.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index beb56e06d8a..4f34a99c2f9 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -3977,13 +3977,32 @@ static bool
 get_range (tree x, signop sgn, offset_int r[2],
 	   const vr_values *rvals /* = NULL */)
 {
+  tree type = TREE_TYPE (x);
+  if (TREE_CODE (x) != INTEGER_CST
+      && TREE_CODE (x) != SSA_NAME)
+    {
+      if (TYPE_UNSIGNED (type))
+	{
+	  if (sgn == SIGNED)
+	    type = signed_type_for (type);
+	}
+      else if (sgn == UNSIGNED)
+	type = unsigned_type_for (type);
+
+      r[0] = wi::to_offset (TYPE_MIN_VALUE (type));
+      r[1] = wi::to_offset (TYPE_MAX_VALUE (type));
+      return x;
+    }
+
   wide_int wr[2];
   if (!get_range (x, wr, rvals))
     return false;
 
   r[0] = offset_int::from (wr[0], sgn);
   r[1] = offset_int::from (wr[1], sgn);
-  return true;
+  /* Succeed only for valid ranges (pointer offsets are represented
+     as unsigned despite taking on "negative" values).  */
+  return r[0] <= r[1];
 }
 
 /* Helper to compute the size of the object referenced by the PTR
@@ -4001,9 +4020,11 @@ get_range (tree x, signop sgn, offset_int r[2],
    to influence code generation or optimization.  */
 
 static bool
-compute_objsize (tree ptr, int ostype, access_ref *pref,
-		 bitmap *visited, const vr_values *rvals /* = NULL */)
+compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
+		 const vr_values *rvals)
 {
+  STRIP_NOPS (ptr);
+
   const bool addr = TREE_CODE (ptr) == ADDR_EXPR;
   if (addr)
     ptr = TREE_OPERAND (ptr, 0);
@@ -4015,12 +4036,15 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
       if (!addr && POINTER_TYPE_P (TREE_TYPE (ptr)))
 	return false;
 
-      tree size = decl_init_size (ptr, false);
-      if (!size || TREE_CODE (size) != INTEGER_CST)
-	return false;
-
       pref->ref = ptr;
-      pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
+      if (tree size = decl_init_size (ptr, false))
+	if (TREE_CODE (size) == INTEGER_CST)
+	  {
+	    pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
+	    return true;
+	  }
+      pref->sizrng[0] = 0;
+      pref->sizrng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
       return true;
     }
 
@@ -4028,13 +4052,13 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
   if (code == COMPONENT_REF)
     {
+      tree ref = TREE_OPERAND (ptr, 0);
       tree field = TREE_OPERAND (ptr, 1);
 
       if (ostype == 0)
 	{
 	  /* For raw memory functions like memcpy bail if the size
 	     of the enclosing object cannot be determined.  */
-	  tree ref = TREE_OPERAND (ptr, 0);
 	  if (!compute_objsize (ref, ostype, pref, visited, rvals)
 	      || !pref->ref)
 	    return false;
@@ -4056,20 +4080,28 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 	return false;
 
       pref->ref = field;
-      /* Only return constant sizes for now while callers depend
-	 on it.  INT0LEN is true for interior zero-length arrays.  */
-      bool int0len = false;
-      tree size = component_ref_size (ptr, &int0len);
-      if (int0len)
+
+      /* SAM is set for array members that might need special treatment.  */
+      special_array_member sam{ };
+      tree size = component_ref_size (ptr, &sam);
+      if (sam == special_array_member::int_0)
+	pref->sizrng[0] = pref->sizrng[1] = 0;
+      else if (!pref->trail1special && sam == special_array_member::trail_1)
+	pref->sizrng[0] = pref->sizrng[1] = 1;
+      else if (size && TREE_CODE (size) == INTEGER_CST)
+	pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
+      else
 	{
-	  pref->sizrng[0] = pref->sizrng[1] = 0;
-	  return true;
+	  /* When the size of the member is unknown it's either a flexible
+	     array member or a trailing special array member (either zero
+	     length or one-element).  Set the size to the maximum minus
+	     the constant size of the type.  */
+	  pref->sizrng[0] = 0;
+	  pref->sizrng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
+	  if (tree recsize = TYPE_SIZE_UNIT (TREE_TYPE (ref)))
+	    if (TREE_CODE (recsize) == INTEGER_CST)
+	      pref->sizrng[1] -= wi::to_offset (recsize);
 	}
-
-      if (!size || TREE_CODE (size) != INTEGER_CST)
-	return false;
-
-      pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
       return true;
     }
 
@@ -4099,7 +4131,7 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 	return false;
 
       offset_int orng[2];
-      tree off = TREE_OPERAND (ptr, 1);
+      tree off = pref->eval (TREE_OPERAND (ptr, 1));
       if (!get_range (off, SIGNED, orng, rvals))
 	/* Fail unless the size of the object is zero.  */
 	return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1];
@@ -4129,11 +4161,20 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
 	  if (ostype && TREE_CODE (eltype) == ARRAY_TYPE)
 	    {
-	      /* Execpt for the permissive raw memory functions which
-		 use the size of the whole object determined above,
-		 use the size of the referenced array.  */
-	      pref->sizrng[0] = pref->offrng[0] + orng[0] + sz;
-	      pref->sizrng[1] = pref->offrng[1] + orng[1] + sz;
+	      /* Except for the permissive raw memory functions which use
+		 the size of the whole object determined above, use the size
+		 of the referenced array.  Because the overall offset is from
+		 the beginning of the complete array object add this overall
+		 offset to the size of array.  */
+	      const offset_int sizrng[2] =
+		{
+		 pref->offrng[0] + orng[0] + sz,
+		 pref->offrng[1] + orng[1] + sz
+		};
+	      if (sizrng[0] >= 0 && sizrng[0] <= pref->sizrng[0])
+		pref->sizrng[0] = sizrng[0];
+	      if (sizrng[1] >= 0 && sizrng[1] <= pref->sizrng[1])
+		pref->sizrng[1] = sizrng[1];
 	    }
 	}
 
@@ -4142,6 +4183,28 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
       return true;
     }
+  else if (code == POINTER_PLUS_EXPR)
+    {
+      tree ref = TREE_OPERAND (ptr, 0);
+      if (!compute_objsize (ref, ostype, pref, visited, rvals))
+	return false;
+
+      offset_int orng[2];
+      tree off = pref->eval (TREE_OPERAND (ptr, 1));
+      if (!get_range (off, SIGNED, orng, rvals))
+	/* Fail unless the size of the object is zero.  */
+	return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1];
+
+      pref->offrng[0] += orng[0];
+      pref->offrng[1] += orng[1];
+
+      return true;
+    }
+  else if (code == VIEW_CONVERT_EXPR)
+    {
+      ptr = TREE_OPERAND (ptr, 0);
+      return compute_objsize (ptr, ostype, pref, visited, rvals);
+    }
 
   if (TREE_CODE (ptr) == SSA_NAME)
     {
@@ -4214,9 +4277,9 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
 /* Convenience wrapper around the above.  */
 
-static tree
+tree
 compute_objsize (tree ptr, int ostype, access_ref *pref,
-		 const vr_values *rvals = NULL)
+		 const vr_values *rvals /* = NULL */)
 {
   bitmap visited = NULL;
 
@@ -12076,3 +12139,14 @@ builtin_with_linkage_p (tree decl)
     }
   return false;
 }
+
+bool
+access_ref::offset_bounded () const
+{
+  if (offrng[0] == offrng[1])
+    return false;
+
+  tree min = TYPE_MIN_VALUE (ptrdiff_type_node);
+  tree max = TYPE_MAX_VALUE (ptrdiff_type_node);
+  return offrng[0] <= wi::to_offset (min) || offrng[1] >= wi::to_offset (max);
+}
diff --git a/gcc/builtins.h b/gcc/builtins.h
index 8b812ceb2c4..a60998d884b 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -133,13 +133,6 @@ 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);
-
-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);
 extern unsigned HOST_WIDE_INT target_newline;
@@ -160,7 +153,8 @@ extern bool builtin_with_linkage_p (tree);
 /* Describes a reference to an object used in an access.  */
 struct access_ref
 {
-  access_ref (): ref ()
+  access_ref ()
+    : ref (), eval ([](tree x){ return x; }), trail1special (true)
   {
     /* Set to valid.  */
     offrng[0] = offrng[1] = 0;
@@ -174,6 +168,22 @@ struct access_ref
   /* Range of offsets into and sizes of the object(s).  */
   offset_int offrng[2];
   offset_int sizrng[2];
+
+  /* Return true if OFFRNG is the constant zero.  */
+  bool offset_zero () const
+  {
+    return offrng[0] == 0 && offrng[1] == 0;
+  }
+
+  /* Return true if OFFRNG is bounded to a subrange of possible offset
+     values.  */
+  bool offset_bounded () const;
+
+  /* Used to fold integer expressions when called from front ends.  */
+  tree (*eval)(tree);
+  /* Set if trailing one-element arrays should be treated as flexible
+     array members.  */
+  bool trail1special;
 };
 
 /* Describes a pair of references used in an access by built-in
@@ -184,6 +194,12 @@ struct access_data
   access_ref dst, src;
 };
 
+class vr_values;
+tree gimple_call_alloc_size (gimple *, wide_int[2] = NULL,
+			     const vr_values * = NULL);
+extern tree compute_objsize (tree, int, access_ref *, const vr_values * = NULL);
+extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL,
+			     const vr_values * = NULL);
 extern bool check_access (tree, tree, tree, tree, tree, tree, tree,
 			  bool = true, const access_data * = NULL);
 
diff --git a/gcc/cp/init.c b/gcc/cp/init.c
index 3f089404cf2..d544e02b1d0 100644
--- a/gcc/cp/init.c
+++ b/gcc/cp/init.c
@@ -34,6 +34,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "attribs.h"
 #include "asan.h"
 #include "stor-layout.h"
+#include "builtins.h"
 
 static bool begin_init_stmts (tree *, tree *);
 static tree finish_init_stmts (bool, tree, tree);
@@ -2551,27 +2552,6 @@ throw_bad_array_new_length (void)
   return build_cxx_call (fn, 0, NULL, tf_warning_or_error);
 }
 
-/* Attempt to find the initializer for flexible array field T in the
-   initializer INIT, when non-null.  Returns the initializer when
-   successful and NULL otherwise.  */
-static tree
-find_flexarray_init (tree t, tree init)
-{
-  if (!init || init == error_mark_node)
-    return NULL_TREE;
-
-  unsigned HOST_WIDE_INT idx;
-  tree field, elt;
-
-  /* Iterate over all top-level initializer elements.  */
-  FOR_EACH_CONSTRUCTOR_ELT (CONSTRUCTOR_ELTS (init), idx, field, elt)
-    /* If the member T is found, return it.  */
-    if (field == t)
-      return elt;
-
-  return NULL_TREE;
-}
-
 /* Attempt to verify that the argument, OPER, of a placement new expression
    refers to an object sufficiently large for an object of TYPE or an array
    of NELTS of such objects when NELTS is non-null, and issue a warning when
@@ -2588,17 +2568,6 @@ warn_placement_new_too_small (tree type, tree nelts, tree size, tree oper)
 {
   location_t loc = cp_expr_loc_or_input_loc (oper);
 
-  /* The number of bytes to add to or subtract from the size of the provided
-     buffer based on an offset into an array or an array element reference.
-     Although intermediate results may be negative (as in a[3] - 2) a valid
-     final result cannot be.  */
-  offset_int adjust = 0;
-  /* True when the size of the entire destination object should be used
-     to compute the possibly optimistic estimate of the available space.  */
-  bool use_obj_size = false;
-  /* True when the reference to the destination buffer is an ADDR_EXPR.  */
-  bool addr_expr = false;
-
   STRIP_NOPS (oper);
 
   /* Using a function argument or a (non-array) variable as an argument
@@ -2612,231 +2581,91 @@ warn_placement_new_too_small (tree type, tree nelts, tree size, tree oper)
   /* Evaluate any constant expressions.  */
   size = fold_non_dependent_expr (size);
 
-  /* Handle the common case of array + offset expression when the offset
-     is a constant.  */
-  if (TREE_CODE (oper) == POINTER_PLUS_EXPR)
-    {
-      /* If the offset is compile-time constant, use it to compute a more
-	 accurate estimate of the size of the buffer.  Since the operand
-	 of POINTER_PLUS_EXPR is represented as an unsigned type, convert
-	 it to signed first.
-	 Otherwise, use the size of the entire array as an optimistic
-	 estimate (this may lead to false negatives).  */
-      tree adj = TREE_OPERAND (oper, 1);
-      adj = fold_for_warn (adj);
-      if (CONSTANT_CLASS_P (adj))
-	adjust += wi::to_offset (convert (ssizetype, adj));
-      else
-	use_obj_size = true;
-
-      oper = TREE_OPERAND (oper, 0);
-
-      STRIP_NOPS (oper);
-    }
-
-  if (TREE_CODE (oper) == TARGET_EXPR)
-    oper = TREE_OPERAND (oper, 1);
-  else if (TREE_CODE (oper) == ADDR_EXPR)
-    {
-      addr_expr = true;
-      oper = TREE_OPERAND (oper, 0);
-    }
-
-  STRIP_NOPS (oper);
+  access_ref ref;
+  ref.eval = [](tree x){ return fold_non_dependent_expr (x); };
+  ref.trail1special = warn_placement_new < 2;
+  tree objsize =  compute_objsize (oper, 1, &ref);
+  if (!objsize)
+    return;
 
-  if (TREE_CODE (oper) == ARRAY_REF
-      && (addr_expr || TREE_CODE (TREE_TYPE (oper)) == ARRAY_TYPE))
-    {
-      /* Similar to the offset computed above, see if the array index
-	 is a compile-time constant.  If so, and unless the offset was
-	 not a compile-time constant, use the index to determine the
-	 size of the buffer.  Otherwise, use the entire array as
-	 an optimistic estimate of the size.  */
-      const_tree adj = fold_non_dependent_expr (TREE_OPERAND (oper, 1));
-      if (!use_obj_size && CONSTANT_CLASS_P (adj))
-	adjust += wi::to_offset (adj);
-      else
-	{
-	  use_obj_size = true;
-	  adjust = 0;
-	}
+  const bool exact_size = ref.offrng[0] == ref.offrng[1];
 
-      oper = TREE_OPERAND (oper, 0);
-    }
+  offset_int bytes_avail = wi::to_offset (objsize);
+  offset_int bytes_need;
 
-  /* Refers to the declared object that constains the subobject referenced
-     by OPER.  When the object is initialized, makes it possible to determine
-     the actual size of a flexible array member used as the buffer passed
-     as OPER to placement new.  */
-  tree var_decl = NULL_TREE;
-  /* True when operand is a COMPONENT_REF, to distinguish flexible array
-     members from arrays of unspecified size.  */
-  bool compref = TREE_CODE (oper) == COMPONENT_REF;
-
-  /* For COMPONENT_REF (i.e., a struct member) the size of the entire
-     enclosing struct.  Used to validate the adjustment (offset) into
-     an array at the end of a struct.  */
-  offset_int compsize = 0;
-
-  /* Descend into a struct or union to find the member whose address
-     is being used as the argument.  */
-  if (TREE_CODE (oper) == COMPONENT_REF)
+  if (CONSTANT_CLASS_P (size))
+    bytes_need = wi::to_offset (size);
+  else if (nelts && CONSTANT_CLASS_P (nelts))
+    bytes_need = (wi::to_offset (nelts)
+		  * wi::to_offset (TYPE_SIZE_UNIT (type)));
+  else if (tree_fits_uhwi_p (TYPE_SIZE_UNIT (type)))
+    bytes_need = wi::to_offset (TYPE_SIZE_UNIT (type));
+  else
     {
-      tree comptype = TREE_TYPE (TREE_OPERAND (oper, 0));
-      compsize = wi::to_offset (TYPE_SIZE_UNIT (comptype));
-
-      tree op0 = oper;
-      while (TREE_CODE (op0 = TREE_OPERAND (op0, 0)) == COMPONENT_REF);
-      STRIP_ANY_LOCATION_WRAPPER (op0);
-      if (VAR_P (op0))
-	var_decl = op0;
-      oper = TREE_OPERAND (oper, 1);
+      /* The type is a VLA.  */
+      return;
     }
 
-  STRIP_ANY_LOCATION_WRAPPER (oper);
-  tree opertype = TREE_TYPE (oper);
-  if ((addr_expr || !INDIRECT_TYPE_P (opertype))
-      && (VAR_P (oper)
-	  || TREE_CODE (oper) == FIELD_DECL
-	  || TREE_CODE (oper) == PARM_DECL))
-    {
-      /* A possibly optimistic estimate of the number of bytes available
-	 in the destination buffer.  */
-      offset_int bytes_avail = 0;
-      /* True when the estimate above is in fact the exact size
-	 of the destination buffer rather than an estimate.  */
-      bool exact_size = true;
-
-      /* Treat members of unions and members of structs uniformly, even
-	 though the size of a member of a union may be viewed as extending
-	 to the end of the union itself (it is by __builtin_object_size).  */
-      if ((VAR_P (oper) || use_obj_size)
-	  && DECL_SIZE_UNIT (oper)
-	  && tree_fits_uhwi_p (DECL_SIZE_UNIT (oper)))
-	{
-	  /* Use the size of the entire array object when the expression
-	     refers to a variable or its size depends on an expression
-	     that's not a compile-time constant.  */
-	  bytes_avail = wi::to_offset (DECL_SIZE_UNIT (oper));
-	  exact_size = !use_obj_size;
-	}
-      else if (tree opersize = TYPE_SIZE_UNIT (opertype))
-	{
-	  /* Use the size of the type of the destination buffer object
-	     as the optimistic estimate of the available space in it.
-	     Use the maximum possible size for zero-size arrays and
-	     flexible array members (except of initialized objects
-	     thereof).  */
-	  if (TREE_CODE (opersize) == INTEGER_CST)
-	    bytes_avail = wi::to_offset (opersize);
-	}
-
-      if (bytes_avail == 0)
-	{
-	  if (var_decl)
-	    {
-	      /* Constructing into a buffer provided by the flexible array
-		 member of a declared object (which is permitted as a G++
-		 extension).  If the array member has been initialized,
-		 determine its size from the initializer.  Otherwise,
-		 the array size is zero.  */
-	      if (tree init = find_flexarray_init (oper,
-						   DECL_INITIAL (var_decl)))
-		bytes_avail = wi::to_offset (TYPE_SIZE_UNIT (TREE_TYPE (init)));
-	    }
-	  else
-	    bytes_avail = (wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node))
-			   - compsize);
-	}
-
-      tree_code oper_code = TREE_CODE (opertype);
-
-      if (compref && oper_code == ARRAY_TYPE)
-	{
-	  tree nelts = array_type_nelts_top (opertype);
-	  tree nelts_cst = maybe_constant_value (nelts);
-	  if (TREE_CODE (nelts_cst) == INTEGER_CST
-	      && integer_onep (nelts_cst)
-	      && !var_decl
-	      && warn_placement_new < 2)
-	    return;
-	}
-
-      /* Reduce the size of the buffer by the adjustment computed above
-	 from the offset and/or the index into the array.  */
-      if (bytes_avail < adjust || adjust < 0)
-	bytes_avail = 0;
-      else
-	{
-	  tree elttype = (TREE_CODE (opertype) == ARRAY_TYPE
-			  ? TREE_TYPE (opertype) : opertype);
-	  if (tree eltsize = TYPE_SIZE_UNIT (elttype))
-	    {
-	      bytes_avail -= adjust * wi::to_offset (eltsize);
-	      if (bytes_avail < 0)
-		bytes_avail = 0;
-	    }
-	}
-
-      /* The minimum amount of space needed for the allocation.  This
-	 is an optimistic estimate that makes it possible to detect
-	 placement new invocation for some undersize buffers but not
-	 others.  */
-      offset_int bytes_need;
+  if (bytes_avail >= bytes_need)
+    return;
 
-      if (nelts)
-	nelts = fold_for_warn (nelts);
-
-      if (CONSTANT_CLASS_P (size))
-	bytes_need = wi::to_offset (size);
-      else if (nelts && CONSTANT_CLASS_P (nelts))
-	bytes_need = (wi::to_offset (nelts)
-		      * wi::to_offset (TYPE_SIZE_UNIT (type)));
-      else if (tree_fits_uhwi_p (TYPE_SIZE_UNIT (type)))
-	bytes_need = wi::to_offset (TYPE_SIZE_UNIT (type));
-      else
-	{
-	  /* The type is a VLA.  */
-	  return;
-	}
+  tree opertype = ref.ref ? TREE_TYPE (ref.ref) : TREE_TYPE (oper);
+  bool warned = false;
+  if (nelts)
+    nelts = fold_for_warn (nelts);
+  if (nelts)
+    if (CONSTANT_CLASS_P (nelts))
+      warned = warning_at (loc, OPT_Wplacement_new_,
+			   (exact_size
+			    ? G_("placement new constructing an object "
+				 "of type %<%T [%wu]%> and size %qwu "
+				 "in a region of type %qT and size %qwi")
+			    : G_("placement new constructing an object "
+				 "of type %<%T [%wu]%> and size %qwu "
+				 "in a region of type %qT and size "
+				 "at most %qwu")),
+			   type, tree_to_uhwi (nelts),
+			   bytes_need.to_uhwi (),
+			   opertype, bytes_avail.to_uhwi ());
+    else
+      warned = warning_at (loc, OPT_Wplacement_new_,
+			   (exact_size
+			    ? G_("placement new constructing an array "
+				 "of objects of type %qT and size %qwu "
+				 "in a region of type %qT and size %qwi")
+			    : G_("placement new constructing an array "
+				 "of objects of type %qT and size %qwu "
+				 "in a region of type %qT and size "
+				 "at most %qwu")),
+			   type, bytes_need.to_uhwi (), opertype,
+			   bytes_avail.to_uhwi ());
+  else
+    warned = warning_at (loc, OPT_Wplacement_new_,
+			 (exact_size
+			  ? G_("placement new constructing an object "
+			       "of type %qT and size %qwu in a region "
+			       "of type %qT and size %qwi")
+			  : G_("placement new constructing an object "
+			       "of type %qT "
+			       "and size %qwu in a region of type %qT "
+			       "and size at most %qwu")),
+			       type, bytes_need.to_uhwi (), opertype,
+			 bytes_avail.to_uhwi ());
+
+  if (!warned || !ref.ref)
+    return;
 
-      if (bytes_avail < bytes_need)
-	{
-	  if (nelts)
-	    if (CONSTANT_CLASS_P (nelts))
-	      warning_at (loc, OPT_Wplacement_new_,
-			  exact_size ?
-			  "placement new constructing an object of type "
-			  "%<%T [%wu]%> and size %qwu in a region of type %qT "
-			  "and size %qwi"
-			  : "placement new constructing an object of type "
-			  "%<%T [%wu]%> and size %qwu in a region of type %qT "
-			  "and size at most %qwu",
-			  type, tree_to_uhwi (nelts), bytes_need.to_uhwi (),
-			  opertype, bytes_avail.to_uhwi ());
-	    else
-	      warning_at (loc, OPT_Wplacement_new_,
-			  exact_size ?
-			  "placement new constructing an array of objects "
-			  "of type %qT and size %qwu in a region of type %qT "
-			  "and size %qwi"
-			  : "placement new constructing an array of objects "
-			  "of type %qT and size %qwu in a region of type %qT "
-			  "and size at most %qwu",
-			  type, bytes_need.to_uhwi (), opertype,
-			  bytes_avail.to_uhwi ());
-	  else
-	    warning_at (loc, OPT_Wplacement_new_,
-			exact_size ?
-			"placement new constructing an object of type %qT "
-			"and size %qwu in a region of type %qT and size %qwi"
-			: "placement new constructing an object of type %qT "
-			"and size %qwu in a region of type %qT and size "
-			"at most %qwu",
-			type, bytes_need.to_uhwi (), opertype,
-			bytes_avail.to_uhwi ());
-	}
-    }
+  if (ref.offset_zero () || !ref.offset_bounded ())
+    inform (DECL_SOURCE_LOCATION (ref.ref),
+	    "%qD declared here", ref.ref);
+  else if (ref.offrng[0] == ref.offrng[1])
+    inform (DECL_SOURCE_LOCATION (ref.ref),
+	    "at offset %wi from %qD declared here",
+	    ref.offrng[0].to_shwi (), ref.ref);
+  else
+    inform (DECL_SOURCE_LOCATION (ref.ref),
+	    "at offset [%wi, %wi] from %qD declared here",
+	    ref.offrng[0].to_shwi (), ref.offrng[1].to_shwi (), ref.ref);
 }
 
 /* True if alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__.  */
diff --git a/gcc/gimple-array-bounds.cc b/gcc/gimple-array-bounds.cc
index c2dd6663c3a..fd4eb443f3e 100644
--- a/gcc/gimple-array-bounds.cc
+++ b/gcc/gimple-array-bounds.cc
@@ -68,7 +68,7 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
   tree decl = NULL_TREE;
 
   /* Set for accesses to interior zero-length arrays.  */
-  bool interior_zero_len = false;
+  special_array_member sam{ };
 
   tree up_bound_p1;
 
@@ -101,7 +101,7 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
 	    {
 	      /* Try to determine the size of the trailing array from
 		 its initializer (if it has one).  */
-	      if (tree refsize = component_ref_size (arg, &interior_zero_len))
+	      if (tree refsize = component_ref_size (arg, &sam))
 		if (TREE_CODE (refsize) == INTEGER_CST)
 		  maxbound = refsize;
 	    }
@@ -199,7 +199,7 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
 			 "array subscript %E is below array bounds of %qT",
 			 low_sub, artype);
 
-  if (!warned && interior_zero_len)
+  if (!warned && sam == special_array_member::int_0)
     warned = warning_at (location, OPT_Wzero_length_bounds,
 			 (TREE_CODE (low_sub) == INTEGER_CST
 			  ? G_("array subscript %E is outside the bounds "
diff --git a/gcc/testsuite/g++.dg/init/strlen.C b/gcc/testsuite/g++.dg/init/strlen.C
index aa8950e2dc0..cc650d65dbe 100644
--- a/gcc/testsuite/g++.dg/init/strlen.C
+++ b/gcc/testsuite/g++.dg/init/strlen.C
@@ -29,7 +29,7 @@ test_dynamic_type (S *p)
   // distinguish invalid cases from ones like it that might be valid.
   // If/when GIMPLE changes to make this possible this test can be
   // removed.
-  char *q = new (p->a) char [16];
+  char *q = new (p->a) char [16];   // { dg-warning "\\\[-Wplacement-new" }
 
   init (q);
 
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C
index d2ec608afd4..cec83163dbe 100644
--- a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C
@@ -66,8 +66,9 @@ struct BA2 { int i; A2 a2; };
 void fBx (BAx *pbx, BAx &rbx)
 {
   BAx bax;
-  new (bax.ax.a) char;     // { dg-warning "placement" }
-  new (bax.ax.a) Int16;    // { dg-warning "placement" }
+  // The uninitialized flexible array takes up the bytes of padding.
+  new (bax.ax.a) char;
+  new (bax.ax.a) Int16;
   new (bax.ax.a) Int32;    // { dg-warning "placement" }
 
   new (pbx->ax.a) char;
@@ -84,9 +85,12 @@ void fBx1 ()
 {
   static BAx bax1 = { 1, /* Ax = */ { 2, /* a[] = */ {} } };
 
-  new (bax1.ax.a) char;	    // { dg-warning "placement" }
-  new (bax1.ax.a) char[2];  // { dg-warning "placement" }
-  new (bax1.ax.a) Int16;    // { dg-warning "placement" }
+  // The empty flexible array takes up the bytes of padding.
+  new (bax1.ax.a) char;
+  new (bax1.ax.a) char[2];
+  new (bax1.ax.a) Int16;
+  new (bax1.ax.a) char[3];
+  new (bax1.ax.a) char[4];  // { dg-warning "placement" }
   new (bax1.ax.a) Int32;    // { dg-warning "placement" }
 }
 
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C
index e00515eeaa9..e5fdfe1f603 100644
--- a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C
@@ -124,9 +124,13 @@ struct BA2 { int i; A2 a2; };
 void fBx (BAx *pbx, BAx &rbx)
 {
   BAx bax;
-  new (bax.ax.a) char;        // { dg-warning "placement" }
-  new (bax.ax.a) Int16;       // { dg-warning "placement" }
+  // The uninitialized flexible array takes up the bytes of padding.
+  new (bax.ax.a) char;
+  new (bax.ax.a) Int16;
+  new (bax.ax.a) char[3];
   new (bax.ax.a) Int32;       // { dg-warning "placement" }
+  new (bax.ax.a) char[4];     // { dg-warning "placement" }
+  new (bax.ax.a) char[5];     // { dg-warning "placement" }
 
   new (pbx->ax.a) char;
   new (rbx.ax.a) char;
@@ -142,10 +146,14 @@ void fBx1 ()
 {
   static BAx bax1 = { 1, /* Ax = */ { 2, /* a[] = */ {} } };
 
-  new (bax1.ax.a) char;	      // { dg-warning "placement" }
-  new (bax1.ax.a) char[2];    // { dg-warning "placement" }
-  new (bax1.ax.a) Int16;      // { dg-warning "placement" }
+  // The empty flexible array takes up the bytes of padding.
+  new (bax1.ax.a) char;
+  new (bax1.ax.a) char[2];
+  new (bax1.ax.a) Int16;
+  new (bax1.ax.a) char[3];
   new (bax1.ax.a) Int32;      // { dg-warning "placement" }
+  new (bax1.ax.a) char[4];    // { dg-warning "placement" }
+  new (bax1.ax.a) char[5];    // { dg-warning "placement" }
 }
 
 void fB0 (BA0 *pb0, BA0 &rb0)
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C
index b6a72b18f6a..5eb63d23b47 100644
--- a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C
@@ -17,9 +17,10 @@ void fBx1 ()
 {
   static BAx bax1 = { 1, /* Ax = */ { 2, /* a[] = */ { 3 } } }; // { dg-error "initialization of flexible array member in a nested context" }
 
-  new (bax1.ax.a) char;     // { dg-warning "placement" }
-  new (bax1.ax.a) char[2];  // { dg-warning "placement" }
-  new (bax1.ax.a) Int16;    // { dg-warning "placement" }
+  // The first three bytes of the flexible array member live in the padding.
+  new (bax1.ax.a) char;
+  new (bax1.ax.a) char[2];
+  new (bax1.ax.a) Int16;
   new (bax1.ax.a) Int32;    // { dg-warning "placement" }
 }
 
@@ -27,10 +28,11 @@ void fBx2 ()
 {
   static BAx bax2 = { 1, /* Ax = */ { 2, /* a[] = */ { 3, 4 } } }; // { dg-error "initialization of flexible array member in a nested context" }
 
-  new (bax2.ax.a) char;       // { dg-warning "placement" }
-  new (bax2.ax.a) char[2];    // { dg-warning "placement" }
-  new (bax2.ax.a) char[3];    // { dg-warning "placement" }
-  new (bax2.ax.a) Int16;      // { dg-warning "placement" }
+  // The first three bytes of the flexible array member live in the padding.
+  new (bax2.ax.a) char;
+  new (bax2.ax.a) char[2];
+  new (bax2.ax.a) char[3];
+  new (bax2.ax.a) Int16;
   new (bax2.ax.a) char[4];    // { dg-warning "placement" }
   new (bax2.ax.a) Int32;      // { dg-warning "placement" }
 }
@@ -39,10 +41,11 @@ void fBx3 ()
 {
   static BAx bax2 = { 1, /* Ax = */ { 3, /* a[] = */ { 4, 5, 6 } } }; // { dg-error "initialization of flexible array member in a nested context" }
 
-  new (bax2.ax.a) char;       // { dg-warning "placement" }
-  new (bax2.ax.a) char[2];    // { dg-warning "placement" }
-  new (bax2.ax.a) Int16;      // { dg-warning "placement" }
-  new (bax2.ax.a) char[3];    // { dg-warning "placement" }
+  // The first three bytes of the flexible array member live in the padding.
+  new (bax2.ax.a) char;
+  new (bax2.ax.a) char[2];
+  new (bax2.ax.a) Int16;
+  new (bax2.ax.a) char[3];
   new (bax2.ax.a) char[4];    // { dg-warning "placement" }
   new (bax2.ax.a) Int32;      // { dg-warning "placement" }
 }
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-7.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-7.C
new file mode 100644
index 00000000000..82f298d8008
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-7.C
@@ -0,0 +1,82 @@
+/* PR c++/96511 - Incorrect -Wplacement-new on POINTER_PLUS into an array
+   with 4-byte elements
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+typedef __INT16_TYPE__ int16_t;
+typedef __INT32_TYPE__ int32_t;
+typedef __SIZE_TYPE__  size_t;
+
+void* operator new (size_t, void *p) { return p; }
+
+void test_a1_int16 ()
+{
+  int16_t a3[3];                    // { dg-message "declared here" }
+
+  new (a3) int16_t;
+  new (a3 + 1) int16_t;
+  new (a3 + 2) int16_t;             // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[1]) int16_t;
+  new (&a3[0] + 1) int16_t;
+  new (&a3[0] + 2) int16_t;         // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[0] + 3) int16_t;         // { dg-warning "\\\[-Wplacement-new" }
+}
+
+void test_a1_int32 ()
+{
+  int16_t a3[3];
+
+  new (a3 + 1) int32_t;             // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[1]) int32_t;
+  new (&a3[0] + 1) int32_t;         // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[0] + 2) int32_t;         // { dg-warning "\\\[-Wplacement-new" }
+}
+
+
+void test_a2 ()
+{
+  int16_t a23[2][3];
+
+  new (a23 + 1) int16_t;            // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a23[1]) int16_t;
+  new (&a23[2]) int16_t;            // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[0][0] + 1) int16_t;
+  new (&a23[0][0] + 2) int16_t;
+  // Deriving a pointer to the next array from one to an element of
+  // the prior array isn't valid even if the resulting pointer points
+  // to an element of the larger array.  Verify it's diagnosed.
+  new (&a23[0][0] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][0] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][0] + 5) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][0] + 6) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[0][1] + 1) int16_t;
+  new (&a23[0][1] + 2) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 5) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 6) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[0][2] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 2) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 5) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 6) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[1][0]) int16_t;
+  new (&a23[1][0] + 1) int16_t;
+  new (&a23[1][0] + 2) int16_t;
+  new (&a23[1][0] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[1][0] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[1][1]) int16_t;
+  new (&a23[1][2]) int16_t;
+  new (&a23[1][2] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[1][3]) int16_t;         // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[1][3] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[2][0]) int16_t;         // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[2][0] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-40.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-40.c
new file mode 100644
index 00000000000..cd8fa3202eb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-40.c
@@ -0,0 +1,116 @@
+/* PR middle-end/96384 - bogus -Wstringop-overflow= storing into
+   multidimensional array with index in range
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+#define SHRT_MAX   __SHRT_MAX__
+#define SHRT_MIN   (-SHRT_MAX - 1)
+#define INT_MAX    __INT_MAX__
+#define INT_MIN    (-INT_MAX - 1)
+#define LONG_MAX   __LONG_MAX__
+#define LONG_MIN   (-LONG_MAX - 1)
+
+#define USHRT_MAX  (SHRT_MAX * 2 + 1)
+#define UINT_MAX   ~0U
+#define ULONG_MAX  ~0LU
+
+char ca3_5_7[3][5][7];
+
+void nowarn_ca_3_5_ssi (short i)
+{
+  if (i > SHRT_MAX - 1)
+    i = SHRT_MAX - 1;
+
+  ca3_5_7[i][0][0] = __LINE__;
+  ca3_5_7[i][0][1] = __LINE__;
+  ca3_5_7[i][0][2] = __LINE__;
+  ca3_5_7[i][0][3] = __LINE__;
+  ca3_5_7[i][0][4] = __LINE__;
+  ca3_5_7[i][0][5] = __LINE__;
+  ca3_5_7[i][0][6] = __LINE__;
+
+  ca3_5_7[i][1][0] = __LINE__;
+  ca3_5_7[i][1][1] = __LINE__;
+  ca3_5_7[i][1][2] = __LINE__;
+  ca3_5_7[i][1][3] = __LINE__;
+  ca3_5_7[i][1][4] = __LINE__;
+  ca3_5_7[i][1][5] = __LINE__;
+  ca3_5_7[i][1][6] = __LINE__;
+
+  ca3_5_7[i][2][0] = __LINE__;
+  ca3_5_7[i][2][1] = __LINE__;
+  ca3_5_7[i][2][2] = __LINE__;
+  ca3_5_7[i][2][3] = __LINE__;
+  ca3_5_7[i][2][4] = __LINE__;
+  ca3_5_7[i][2][5] = __LINE__;
+  ca3_5_7[i][2][6] = __LINE__;
+
+  ca3_5_7[i][3][0] = __LINE__;
+  ca3_5_7[i][3][1] = __LINE__;
+  ca3_5_7[i][3][2] = __LINE__;
+  ca3_5_7[i][3][3] = __LINE__;
+  ca3_5_7[i][3][4] = __LINE__;
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[i][3][6] = __LINE__;
+
+  ca3_5_7[i][4][0] = __LINE__;
+  ca3_5_7[i][4][1] = __LINE__;
+  ca3_5_7[i][4][2] = __LINE__;
+  ca3_5_7[i][4][3] = __LINE__;
+  ca3_5_7[i][4][4] = __LINE__;
+  ca3_5_7[i][4][5] = __LINE__;
+  ca3_5_7[i][4][6] = __LINE__;
+
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_usi (unsigned short i)
+{
+  if (i > USHRT_MAX - 1)
+    i = USHRT_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_si (int i)
+{
+  if (i > INT_MAX - 1)
+    i = INT_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_ui (unsigned i)
+{
+  if (i > UINT_MAX - 1)
+    i = UINT_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_li (long i)
+{
+  if (i > LONG_MAX - 1)
+    i = LONG_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_uli (unsigned long i)
+{
+  if (i > ULONG_MAX - 1)
+    i = ULONG_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
diff --git a/gcc/testsuite/gcc.dg/builtin-object-size-21.c b/gcc/testsuite/gcc.dg/builtin-object-size-21.c
index 1c42374ba89..a232b500a51 100644
--- a/gcc/testsuite/gcc.dg/builtin-object-size-21.c
+++ b/gcc/testsuite/gcc.dg/builtin-object-size-21.c
@@ -1,7 +1,7 @@
 /* PR middle-end/92815 - spurious -Wstringop-overflow writing into
    a flexible array of an extern struct
    { dg-do compile }
-   { dg-options "-Wall -fdump-tree-optimized" } */
+   { dg-options "-Wall -fdump-tree-optimized -o/dev/null" } */
 
 #define PTRDIFF_MAX __PTRDIFF_MAX__
 
diff --git a/gcc/tree-object-size.c b/gcc/tree-object-size.c
index bc55b27cff0..38632a51ac9 100644
--- a/gcc/tree-object-size.c
+++ b/gcc/tree-object-size.c
@@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
   tree last_type = TREE_TYPE (last);
   if (TREE_CODE (last_type) != ARRAY_TYPE
       || TYPE_SIZE (last_type))
-    return size;
+    return size ? size : TYPE_SIZE_UNIT (type);
 
   /* Use TYPE_SIZE_UNIT; DECL_SIZE_UNIT sometimes reflects the size
      of the initializer and sometimes doesn't.  */
diff --git a/gcc/tree.c b/gcc/tree.c
index 6dea32aeb45..af3d12ccaeb 100644
--- a/gcc/tree.c
+++ b/gcc/tree.c
@@ -13623,20 +13623,21 @@ get_initializer_for (tree init, tree decl)
 /* Determines the size of the member referenced by the COMPONENT_REF
    REF, using its initializer expression if necessary in order to
    determine the size of an initialized flexible array member.
-   If non-null, *INTERIOR_ZERO_LENGTH is set when REF refers to
-   an interior zero-length array.
+   If non-null, set *ARK when REF refers to an interior zero-length
+   array or a trailing one-element array.
    Returns the size as sizetype (which might be zero for an object
    with an uninitialized flexible array member) or null if the size
    cannot be determined.  */
 
 tree
-component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
+component_ref_size (tree ref, special_array_member *sam /* = NULL */)
 {
   gcc_assert (TREE_CODE (ref) == COMPONENT_REF);
 
-  bool int_0_len = false;
-  if (!interior_zero_length)
-    interior_zero_length = &int_0_len;
+  special_array_member arkbuf;
+  if (!sam)
+    sam = &arkbuf;
+  *sam = special_array_member::none;
 
   /* The object/argument referenced by the COMPONENT_REF and its type.  */
   tree arg = TREE_OPERAND (ref, 0);
@@ -13658,9 +13659,16 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
 	   more than one element.  */
 	return memsize;
 
-      *interior_zero_length = zero_length && !trailing;
-      if (*interior_zero_length)
-	memsize = NULL_TREE;
+      if (zero_length)
+	{
+	  if (trailing)
+	    *sam = special_array_member::trail_0;
+	  else
+	    {
+	      *sam = special_array_member::int_0;
+	      memsize = NULL_TREE;
+	    }
+	}
 
       if (!zero_length)
 	if (tree dom = TYPE_DOMAIN (memtype))
@@ -13671,9 +13679,13 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
 		{
 		  offset_int minidx = wi::to_offset (min);
 		  offset_int maxidx = wi::to_offset (max);
-		  if (maxidx - minidx > 0)
+		  offset_int neltsm1 = maxidx - minidx;
+		  if (neltsm1 > 0)
 		    /* MEMBER is an array with more than one element.  */
 		    return memsize;
+
+		  if (neltsm1 == 0)
+		    *sam = special_array_member::trail_1;
 		}
 
       /* For a refernce to a zero- or one-element array member of a union
@@ -13691,7 +13703,7 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
   tree base = get_addr_base_and_unit_offset (ref, &baseoff);
   if (!base || !VAR_P (base))
     {
-      if (!*interior_zero_length)
+      if (*sam != special_array_member::int_0)
 	return NULL_TREE;
 
       if (TREE_CODE (arg) != COMPONENT_REF)
@@ -13711,7 +13723,7 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
   /* Determine the base type of the referenced object.  If it's
      the same as ARGTYPE and MEMBER has a known size, return it.  */
   tree bt = basetype;
-  if (!*interior_zero_length)
+  if (*sam != special_array_member::int_0)
     while (TREE_CODE (bt) == ARRAY_TYPE)
       bt = TREE_TYPE (bt);
   bool typematch = useless_type_conversion_p (argtype, bt);
@@ -13751,7 +13763,7 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
 	  if (DECL_P (base)
 	      && DECL_EXTERNAL (base)
 	      && bt == basetype
-	      && !*interior_zero_length)
+	      && *sam != special_array_member::int_0)
 	    /* The size of a flexible array member of an extern struct
 	       with no initializer cannot be determined (it's defined
 	       in another translation unit and can have an initializer
diff --git a/gcc/tree.h b/gcc/tree.h
index 22dd4ac0f3c..7e289c22678 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -5283,12 +5283,22 @@ extern bool array_at_struct_end_p (tree);
    by EXP.  This does not include any offset in DECL_FIELD_BIT_OFFSET.  */
 extern tree component_ref_field_offset (tree);
 
+/* Describes a "special" array member due to which component_ref_size
+   returns null.  */
+enum struct special_array_member
+  {
+   none,      /* Not a special array member.  */
+   int_0,     /* Interior array member with size zero.  */
+   trail_0,   /* Trailing array member with size zero.  */
+   trail_1    /* Trailing array member with one element.  */
+  };
+
 /* Return the size of the member referenced by the COMPONENT_REF, using
    its initializer expression if necessary in order to determine the size
    of an initialized flexible array member.  The size might be zero for
    an object with an uninitialized flexible array member or null if it
    cannot be determined.  */
-extern tree component_ref_size (tree, bool * = NULL);
+extern tree component_ref_size (tree, special_array_member * = NULL);
 
 extern int tree_map_base_eq (const void *, const void *);
 extern unsigned int tree_map_base_hash (const void *);

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-08-11 16:19 [PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511) Martin Sebor
@ 2020-08-19 15:00 ` Martin Sebor
  2020-08-28 15:42   ` [PING 2][PATCH] " Martin Sebor
  2020-09-01 19:22 ` [PATCH] " Jason Merrill
  1 sibling, 1 reply; 28+ messages in thread
From: Martin Sebor @ 2020-08-19 15:00 UTC (permalink / raw)
  To: gcc-patches, Jason Merrill

Ping: https://gcc.gnu.org/pipermail/gcc-patches/2020-August/551783.html

On 8/11/20 10:19 AM, Martin Sebor wrote:
> -Wplacement-new handles array indices and pointer offsets the same:
> by adjusting them by the size of the element.  That's correct for
> the latter but wrong for the former, causing false positives when
> the element size is greater than one.
> 
> In addition, the warning doesn't even attempt to handle arrays of
> arrays.  I'm not sure if I forgot or if I simply didn't think of
> it.
> 
> The attached patch corrects these oversights by replacing most
> of the -Wplacement-new code with a call to compute_objsize which
> handles all this correctly (plus more), and is also better tested.
> But even compute_objsize has bugs: it trips up while converting
> wide_int to offset_int for some pointer offset ranges.  Since
> handling the C++ IL required changes in this area the patch also
> fixes that.
> 
> For review purposes, the patch affects just the middle end.
> The C++ diff pretty much just removes code from the front end.
> 
> Tested on x86_64-linux plus by building the latest Glibc and
> confirming no new warnings.
> 
> Martin


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PING 2][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-08-19 15:00 ` [PING][PATCH] " Martin Sebor
@ 2020-08-28 15:42   ` Martin Sebor
  0 siblings, 0 replies; 28+ messages in thread
From: Martin Sebor @ 2020-08-28 15:42 UTC (permalink / raw)
  To: gcc-patches, Jason Merrill

Ping: https://gcc.gnu.org/pipermail/gcc-patches/2020-August/551783.html

On 8/19/20 9:00 AM, Martin Sebor wrote:
> Ping: https://gcc.gnu.org/pipermail/gcc-patches/2020-August/551783.html
> 
> On 8/11/20 10:19 AM, Martin Sebor wrote:
>> -Wplacement-new handles array indices and pointer offsets the same:
>> by adjusting them by the size of the element.  That's correct for
>> the latter but wrong for the former, causing false positives when
>> the element size is greater than one.
>>
>> In addition, the warning doesn't even attempt to handle arrays of
>> arrays.  I'm not sure if I forgot or if I simply didn't think of
>> it.
>>
>> The attached patch corrects these oversights by replacing most
>> of the -Wplacement-new code with a call to compute_objsize which
>> handles all this correctly (plus more), and is also better tested.
>> But even compute_objsize has bugs: it trips up while converting
>> wide_int to offset_int for some pointer offset ranges.  Since
>> handling the C++ IL required changes in this area the patch also
>> fixes that.
>>
>> For review purposes, the patch affects just the middle end.
>> The C++ diff pretty much just removes code from the front end.
>>
>> Tested on x86_64-linux plus by building the latest Glibc and
>> confirming no new warnings.
>>
>> Martin
> 


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-08-11 16:19 [PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511) Martin Sebor
  2020-08-19 15:00 ` [PING][PATCH] " Martin Sebor
@ 2020-09-01 19:22 ` Jason Merrill
  2020-09-03 18:44   ` Martin Sebor
  1 sibling, 1 reply; 28+ messages in thread
From: Jason Merrill @ 2020-09-01 19:22 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
> -Wplacement-new handles array indices and pointer offsets the same:
> by adjusting them by the size of the element.  That's correct for
> the latter but wrong for the former, causing false positives when
> the element size is greater than one.
> 
> In addition, the warning doesn't even attempt to handle arrays of
> arrays.  I'm not sure if I forgot or if I simply didn't think of
> it.
> 
> The attached patch corrects these oversights by replacing most
> of the -Wplacement-new code with a call to compute_objsize which
> handles all this correctly (plus more), and is also better tested.
> But even compute_objsize has bugs: it trips up while converting
> wide_int to offset_int for some pointer offset ranges.  Since
> handling the C++ IL required changes in this area the patch also
> fixes that.
> 
> For review purposes, the patch affects just the middle end.
> The C++ diff pretty much just removes code from the front end.

The C++ changes are OK.

> -compute_objsize (tree ptr, int ostype, access_ref *pref,
> -                bitmap *visited, const vr_values *rvals /* = NULL */)
> +compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
> +                const vr_values *rvals)

This reformatting seems unnecessary, and I prefer to keep the comment 
about the default argument.

> -      if (!size || TREE_CODE (size) != INTEGER_CST)
> -       return false;
 >...

You change some failure cases in compute_objsize to return success with 
a maximum range, while others continue to return failure.  This needs 
commentary about the design rationale.

> +  special_array_member sam{ };

sam is always set by component_ref_size, so I don't think it's necessary 
to initialize it at the declaration.

> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
>    tree last_type = TREE_TYPE (last);
>    if (TREE_CODE (last_type) != ARRAY_TYPE
>        || TYPE_SIZE (last_type))
> -    return size;
> +    return size ? size : TYPE_SIZE_UNIT (type);

This change seems to violate the comment for the function.

Jason


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-09-01 19:22 ` [PATCH] " Jason Merrill
@ 2020-09-03 18:44   ` Martin Sebor
  2020-09-04 17:14     ` Jason Merrill
  0 siblings, 1 reply; 28+ messages in thread
From: Martin Sebor @ 2020-09-03 18:44 UTC (permalink / raw)
  To: Jason Merrill, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 4581 bytes --]

On 9/1/20 1:22 PM, Jason Merrill wrote:
> On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
>> -Wplacement-new handles array indices and pointer offsets the same:
>> by adjusting them by the size of the element.  That's correct for
>> the latter but wrong for the former, causing false positives when
>> the element size is greater than one.
>>
>> In addition, the warning doesn't even attempt to handle arrays of
>> arrays.  I'm not sure if I forgot or if I simply didn't think of
>> it.
>>
>> The attached patch corrects these oversights by replacing most
>> of the -Wplacement-new code with a call to compute_objsize which
>> handles all this correctly (plus more), and is also better tested.
>> But even compute_objsize has bugs: it trips up while converting
>> wide_int to offset_int for some pointer offset ranges.  Since
>> handling the C++ IL required changes in this area the patch also
>> fixes that.
>>
>> For review purposes, the patch affects just the middle end.
>> The C++ diff pretty much just removes code from the front end.
> 
> The C++ changes are OK.

Thank you for looking at the rest as well.

> 
>> -compute_objsize (tree ptr, int ostype, access_ref *pref,
>> -                bitmap *visited, const vr_values *rvals /* = NULL */)
>> +compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap 
>> *visited,
>> +                const vr_values *rvals)
> 
> This reformatting seems unnecessary, and I prefer to keep the comment 
> about the default argument.

This overload doesn't take a default argument.  (There was a stray
declaration of a similar function at the top of the file that had
one.  I've removed it.)

> 
>> -      if (!size || TREE_CODE (size) != INTEGER_CST)
>> -       return false;
>  >...
> 
> You change some failure cases in compute_objsize to return success with 
> a maximum range, while others continue to return failure.  This needs 
> commentary about the design rationale.

This is too much for a comment in the code but the background is
this: compute_objsize initially returned the object size as a constant.
Recently, I have enhanced it to return a range to improve warnings for
allocated objects.  With that, a failure can be turned into success by
having the function set the range to that of the largest object.  That
should simplify the function's callers and could even improve
the detection of some invalid accesses.  Once this change is made
it might even be possible to change its return type to void.

The change that caught your eye is necessary to make the function
a drop-in replacement for the C++ front end code which makes this
same assumption.  Without it, a number of test cases that exercise
VLAs fail in g++.dg/warn/Wplacement-new-size-5.C.  For example:

   void f (int n)
   {
     char a[n];
     new (a - 1) int ();
   }

Changing any of the other places isn't necessary for existing tests
to pass (and I didn't want to introduce too much churn).  But I do
want to change the rest of the function along the same lines at some
point.

I've tweaked the comment a little bit without going into all this
detail.

> 
>> +  special_array_member sam{ };
> 
> sam is always set by component_ref_size, so I don't think it's necessary 
> to initialize it at the declaration.

I find initializing pass-by-pointer local variables helpful but
I don't insist on it.

> 
>> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
>>    tree last_type = TREE_TYPE (last);
>>    if (TREE_CODE (last_type) != ARRAY_TYPE
>>        || TYPE_SIZE (last_type))
>> -    return size;
>> +    return size ? size : TYPE_SIZE_UNIT (type);
> 
> This change seems to violate the comment for the function.

By my reading (and writing) the change is covered by the first
sentence:

    Returns the size of the object designated by DECL considering
    its initializer if it either has one or if it would not affect
    its size, ...

It handles a number of cases in Wplacement-new-size.C fail that
construct a larger object in an extern declaration of a template,
like this:

   template <class> struct S { char c; };
   extern S<int> s;

   void f ()
   {
     new (&s) int ();
   }

I don't know why DECL_SIZE isn't set here (I don't think it can
be anything but equal to TYPE_SIZE, can it?) and other than struct
objects with a flexible array member where this identity doesn't
hold I can't think of others.  Am I missing something?

Attached is an updated patch rebased on top of the current trunk
and retested on x86_64-linux + by building Glibc.

Martin

[-- Attachment #2: gcc-96511.diff --]
[-- Type: text/x-patch, Size: 48850 bytes --]

Correct handling of indices into arrays with elements larger than 1 (PR c++/96511)

Resolves:
PR c++/96511 - Incorrect -Wplacement-new on POINTER_PLUS into an array with 4-byte elements
PR middle-end/96384 - bogus -Wstringop-overflow= storing into multidimensional array with index in range

gcc/ChangeLog:

	PR c++/96511
	PR middle-end/96384
	* builtins.c (get_range): Return full range of type when neither
	value nor its range is available.  Fail for ranges inverted due
	to the signedness of offsets.
	(compute_objsize): Handle more special array members.  Handle
	POINTER_PLUS_EXPR and VIEW_CONVERT_EXPR that come up in front end
	code.
	(access_ref::offset_bounded): Define new member function.
	* builtins.h (access_ref::eval): New data member.
	(access_ref::offset_bounded): New member function.
	(access_ref::offset_zero): New member function.
	(compute_objsize): Declare a new overload.
	* gimple-array-bounds.cc (array_bounds_checker::check_array_ref): Use
	enum special_array_member.
	* tree-object-size.c (decl_init_size): Return the size of the structure
	type if the decl size is null.
	* tree.c (component_ref_size): Use special_array_member.
	* tree.h (special_array_member): Define a new type.
	(component_ref_size): Change signature/	

gcc/cp/ChangeLog:

	PR c++/96511
	PR middle-end/96384
	* init.c (warn_placement_new_too_small): Call builtin_objsize instead
	of duplicating what it does.

gcc/testsuite/ChangeLog:

	PR c++/96511
	PR middle-end/96384
	* g++.dg/warn/Wplacement-new-size-1.C: Relax warnings.
	* g++.dg/warn/Wplacement-new-size-2.C: Same.
	* g++.dg/warn/Wplacement-new-size-6.C: Same.
	* g++.dg/warn/Wplacement-new-size-7.C: New test.
	* gcc.dg/Wstringop-overflow-40.c: New test.
	* gcc/testsuite/gcc.dg/Warray-bounds-58.c: Adjust
	* gcc/testsuite/gcc.dg/Wstringop-overflow-37.c: Same.
	* gcc/testsuite/gcc.dg/Wstringop-overflow-40.c: Same.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 97f1a184dc6..bcb0c282a7e 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -183,7 +183,6 @@ static void maybe_emit_chk_warning (tree, enum built_in_function);
 static void maybe_emit_sprintf_chk_warning (tree, enum built_in_function);
 static void maybe_emit_free_warning (tree);
 static tree fold_builtin_object_size (tree, tree);
-static tree compute_objsize (tree, int, access_ref *, const vr_values * = NULL);
 static bool get_range (tree, signop, offset_int[2], const vr_values * = NULL);
 static bool check_read_access (tree, tree, tree = NULL_TREE, int = 1);
 
@@ -200,7 +199,7 @@ static void expand_builtin_sync_synchronize (void);
 
 access_ref::access_ref (tree bound /* = NULL_TREE */,
 			bool minaccess /* = false */)
-  : ref ()
+: ref (), eval ([](tree x){ return x; }), trail1special (true)
 {
   /* Set to valid.  */
   offrng[0] = offrng[1] = 0;
@@ -4163,10 +4162,34 @@ static bool
 get_range (tree x, signop sgn, offset_int r[2],
 	   const vr_values *rvals /* = NULL */)
 {
+  tree type = TREE_TYPE (x);
+  if (TREE_CODE (x) != INTEGER_CST
+      && TREE_CODE (x) != SSA_NAME)
+    {
+      if (TYPE_UNSIGNED (type))
+	{
+	  if (sgn == SIGNED)
+	    type = signed_type_for (type);
+	}
+      else if (sgn == UNSIGNED)
+	type = unsigned_type_for (type);
+
+      r[0] = wi::to_offset (TYPE_MIN_VALUE (type));
+      r[1] = wi::to_offset (TYPE_MAX_VALUE (type));
+      return x;
+    }
+
   wide_int wr[2];
   if (!get_range (x, wr, rvals))
     return false;
 
+  /* Only convert signed integers or unsigned sizetype to a signed
+     offset and avoid converting large positive values in narrower
+     types to negative offsets.  */
+  if (TYPE_UNSIGNED (type)
+      && wr[0].get_precision () < TYPE_PRECISION (sizetype))
+    sgn = UNSIGNED;
+
   r[0] = offset_int::from (wr[0], sgn);
   r[1] = offset_int::from (wr[1], sgn);
   return true;
@@ -4187,9 +4210,11 @@ get_range (tree x, signop sgn, offset_int r[2],
    to influence code generation or optimization.  */
 
 static bool
-compute_objsize (tree ptr, int ostype, access_ref *pref,
-		 bitmap *visited, const vr_values *rvals /* = NULL */)
+compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
+		 const vr_values *rvals)
 {
+  STRIP_NOPS (ptr);
+
   const bool addr = TREE_CODE (ptr) == ADDR_EXPR;
   if (addr)
     ptr = TREE_OPERAND (ptr, 0);
@@ -4201,12 +4226,15 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
       if (!addr && POINTER_TYPE_P (TREE_TYPE (ptr)))
 	return false;
 
-      tree size = decl_init_size (ptr, false);
-      if (!size || TREE_CODE (size) != INTEGER_CST)
-	return false;
-
       pref->ref = ptr;
-      pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
+      if (tree size = decl_init_size (ptr, false))
+	if (TREE_CODE (size) == INTEGER_CST)
+	  {
+	    pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
+	    return true;
+	  }
+      pref->sizrng[0] = 0;
+      pref->sizrng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
       return true;
     }
 
@@ -4214,13 +4242,13 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
   if (code == COMPONENT_REF)
     {
+      tree ref = TREE_OPERAND (ptr, 0);
       tree field = TREE_OPERAND (ptr, 1);
 
       if (ostype == 0)
 	{
 	  /* For raw memory functions like memcpy bail if the size
 	     of the enclosing object cannot be determined.  */
-	  tree ref = TREE_OPERAND (ptr, 0);
 	  if (!compute_objsize (ref, ostype, pref, visited, rvals)
 	      || !pref->ref)
 	    return false;
@@ -4242,20 +4270,28 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 	return false;
 
       pref->ref = field;
-      /* Only return constant sizes for now while callers depend
-	 on it.  INT0LEN is true for interior zero-length arrays.  */
-      bool int0len = false;
-      tree size = component_ref_size (ptr, &int0len);
-      if (int0len)
+
+      /* SAM is set for array members that might need special treatment.  */
+      special_array_member sam;
+      tree size = component_ref_size (ptr, &sam);
+      if (sam == special_array_member::int_0)
+	pref->sizrng[0] = pref->sizrng[1] = 0;
+      else if (!pref->trail1special && sam == special_array_member::trail_1)
+	pref->sizrng[0] = pref->sizrng[1] = 1;
+      else if (size && TREE_CODE (size) == INTEGER_CST)
+	pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
+      else
 	{
-	  pref->sizrng[0] = pref->sizrng[1] = 0;
-	  return true;
+	  /* When the size of the member is unknown it's either a flexible
+	     array member or a trailing special array member (either zero
+	     length or one-element).  Set the size to the maximum minus
+	     the constant size of the type.  */
+	  pref->sizrng[0] = 0;
+	  pref->sizrng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
+	  if (tree recsize = TYPE_SIZE_UNIT (TREE_TYPE (ref)))
+	    if (TREE_CODE (recsize) == INTEGER_CST)
+	      pref->sizrng[1] -= wi::to_offset (recsize);
 	}
-
-      if (!size || TREE_CODE (size) != INTEGER_CST)
-	return false;
-
-      pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
       return true;
     }
 
@@ -4285,7 +4321,7 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 	return false;
 
       offset_int orng[2];
-      tree off = TREE_OPERAND (ptr, 1);
+      tree off = pref->eval (TREE_OPERAND (ptr, 1));
       if (!get_range (off, SIGNED, orng, rvals))
 	/* Fail unless the size of the object is zero.  */
 	return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1];
@@ -4315,11 +4351,22 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
 	  if (ostype && TREE_CODE (eltype) == ARRAY_TYPE)
 	    {
-	      /* Execpt for the permissive raw memory functions which
-		 use the size of the whole object determined above,
-		 use the size of the referenced array.  */
-	      pref->sizrng[0] = pref->offrng[0] + orng[0] + sz;
-	      pref->sizrng[1] = pref->offrng[1] + orng[1] + sz;
+	      /* Except for the permissive raw memory functions which use
+		 the size of the whole object determined above, use the size
+		 of the referenced array.  Because the overall offset is from
+		 the beginning of the complete array object add this overall
+		 offset to the size of array.  */
+	      offset_int sizrng[2] =
+		{
+		 pref->offrng[0] + orng[0] + sz,
+		 pref->offrng[1] + orng[1] + sz
+		};
+	      if (sizrng[1] < sizrng[0])
+		std::swap (sizrng[0], sizrng[1]);
+	      if (sizrng[0] >= 0 && sizrng[0] <= pref->sizrng[0])
+		pref->sizrng[0] = sizrng[0];
+	      if (sizrng[1] >= 0 && sizrng[1] <= pref->sizrng[1])
+		pref->sizrng[1] = sizrng[1];
 	    }
 	}
 
@@ -4328,6 +4375,28 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
       return true;
     }
+  else if (code == POINTER_PLUS_EXPR)
+    {
+      tree ref = TREE_OPERAND (ptr, 0);
+      if (!compute_objsize (ref, ostype, pref, visited, rvals))
+	return false;
+
+      offset_int orng[2];
+      tree off = pref->eval (TREE_OPERAND (ptr, 1));
+      if (!get_range (off, SIGNED, orng, rvals))
+	/* Fail unless the size of the object is zero.  */
+	return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1];
+
+      pref->offrng[0] += orng[0];
+      pref->offrng[1] += orng[1];
+
+      return true;
+    }
+  else if (code == VIEW_CONVERT_EXPR)
+    {
+      ptr = TREE_OPERAND (ptr, 0);
+      return compute_objsize (ptr, ostype, pref, visited, rvals);
+    }
 
   if (TREE_CODE (ptr) == SSA_NAME)
     {
@@ -4405,7 +4474,7 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
 /* Convenience wrapper around the above.  */
 
-static tree
+tree
 compute_objsize (tree ptr, int ostype, access_ref *pref,
 		 const vr_values *rvals /* = NULL */)
 {
@@ -12280,3 +12349,14 @@ builtin_with_linkage_p (tree decl)
     }
   return false;
 }
+
+bool
+access_ref::offset_bounded () const
+{
+  if (offrng[0] == offrng[1])
+    return false;
+
+  tree min = TYPE_MIN_VALUE (ptrdiff_type_node);
+  tree max = TYPE_MAX_VALUE (ptrdiff_type_node);
+  return offrng[0] <= wi::to_offset (min) || offrng[1] >= wi::to_offset (max);
+}
diff --git a/gcc/builtins.h b/gcc/builtins.h
index 94ff96b1292..e42c9664724 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -133,13 +133,6 @@ 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);
-
-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);
 extern unsigned HOST_WIDE_INT target_newline;
@@ -179,6 +172,22 @@ struct access_ref
      For string functions the size of the actual access is
      further constrained by the length of the string.  */
   offset_int bndrng[2];
+
+  /* Return true if OFFRNG is the constant zero.  */
+  bool offset_zero () const
+  {
+    return offrng[0] == 0 && offrng[1] == 0;
+  }
+
+  /* Return true if OFFRNG is bounded to a subrange of possible offset
+     values.  */
+  bool offset_bounded () const;
+
+  /* Used to fold integer expressions when called from front ends.  */
+  tree (*eval)(tree);
+  /* Set if trailing one-element arrays should be treated as flexible
+     array members.  */
+  bool trail1special;
 };
 
 /* Describes a pair of references used in an access by built-in
@@ -202,6 +211,12 @@ struct access_data
   access_mode mode;
 };
 
+class vr_values;
+tree gimple_call_alloc_size (gimple *, wide_int[2] = NULL,
+			     const vr_values * = NULL);
+extern tree compute_objsize (tree, int, access_ref *, const vr_values * = NULL);
+extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL,
+			     const vr_values * = NULL);
 extern bool check_access (tree, tree, tree, tree, tree,
 			  access_mode, const access_data * = NULL);
 
diff --git a/gcc/cp/init.c b/gcc/cp/init.c
index d4540db3605..883f0f7e23a 100644
--- a/gcc/cp/init.c
+++ b/gcc/cp/init.c
@@ -34,6 +34,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "attribs.h"
 #include "asan.h"
 #include "stor-layout.h"
+#include "builtins.h"
 
 static bool begin_init_stmts (tree *, tree *);
 static tree finish_init_stmts (bool, tree, tree);
@@ -2552,27 +2553,6 @@ throw_bad_array_new_length (void)
   return build_cxx_call (fn, 0, NULL, tf_warning_or_error);
 }
 
-/* Attempt to find the initializer for flexible array field T in the
-   initializer INIT, when non-null.  Returns the initializer when
-   successful and NULL otherwise.  */
-static tree
-find_flexarray_init (tree t, tree init)
-{
-  if (!init || init == error_mark_node)
-    return NULL_TREE;
-
-  unsigned HOST_WIDE_INT idx;
-  tree field, elt;
-
-  /* Iterate over all top-level initializer elements.  */
-  FOR_EACH_CONSTRUCTOR_ELT (CONSTRUCTOR_ELTS (init), idx, field, elt)
-    /* If the member T is found, return it.  */
-    if (field == t)
-      return elt;
-
-  return NULL_TREE;
-}
-
 /* Attempt to verify that the argument, OPER, of a placement new expression
    refers to an object sufficiently large for an object of TYPE or an array
    of NELTS of such objects when NELTS is non-null, and issue a warning when
@@ -2589,17 +2569,6 @@ warn_placement_new_too_small (tree type, tree nelts, tree size, tree oper)
 {
   location_t loc = cp_expr_loc_or_input_loc (oper);
 
-  /* The number of bytes to add to or subtract from the size of the provided
-     buffer based on an offset into an array or an array element reference.
-     Although intermediate results may be negative (as in a[3] - 2) a valid
-     final result cannot be.  */
-  offset_int adjust = 0;
-  /* True when the size of the entire destination object should be used
-     to compute the possibly optimistic estimate of the available space.  */
-  bool use_obj_size = false;
-  /* True when the reference to the destination buffer is an ADDR_EXPR.  */
-  bool addr_expr = false;
-
   STRIP_NOPS (oper);
 
   /* Using a function argument or a (non-array) variable as an argument
@@ -2613,231 +2582,91 @@ warn_placement_new_too_small (tree type, tree nelts, tree size, tree oper)
   /* Evaluate any constant expressions.  */
   size = fold_non_dependent_expr (size);
 
-  /* Handle the common case of array + offset expression when the offset
-     is a constant.  */
-  if (TREE_CODE (oper) == POINTER_PLUS_EXPR)
-    {
-      /* If the offset is compile-time constant, use it to compute a more
-	 accurate estimate of the size of the buffer.  Since the operand
-	 of POINTER_PLUS_EXPR is represented as an unsigned type, convert
-	 it to signed first.
-	 Otherwise, use the size of the entire array as an optimistic
-	 estimate (this may lead to false negatives).  */
-      tree adj = TREE_OPERAND (oper, 1);
-      adj = fold_for_warn (adj);
-      if (CONSTANT_CLASS_P (adj))
-	adjust += wi::to_offset (convert (ssizetype, adj));
-      else
-	use_obj_size = true;
-
-      oper = TREE_OPERAND (oper, 0);
-
-      STRIP_NOPS (oper);
-    }
-
-  if (TREE_CODE (oper) == TARGET_EXPR)
-    oper = TREE_OPERAND (oper, 1);
-  else if (TREE_CODE (oper) == ADDR_EXPR)
-    {
-      addr_expr = true;
-      oper = TREE_OPERAND (oper, 0);
-    }
-
-  STRIP_NOPS (oper);
+  access_ref ref;
+  ref.eval = [](tree x){ return fold_non_dependent_expr (x); };
+  ref.trail1special = warn_placement_new < 2;
+  tree objsize =  compute_objsize (oper, 1, &ref);
+  if (!objsize)
+    return;
 
-  if (TREE_CODE (oper) == ARRAY_REF
-      && (addr_expr || TREE_CODE (TREE_TYPE (oper)) == ARRAY_TYPE))
-    {
-      /* Similar to the offset computed above, see if the array index
-	 is a compile-time constant.  If so, and unless the offset was
-	 not a compile-time constant, use the index to determine the
-	 size of the buffer.  Otherwise, use the entire array as
-	 an optimistic estimate of the size.  */
-      const_tree adj = fold_non_dependent_expr (TREE_OPERAND (oper, 1));
-      if (!use_obj_size && CONSTANT_CLASS_P (adj))
-	adjust += wi::to_offset (adj);
-      else
-	{
-	  use_obj_size = true;
-	  adjust = 0;
-	}
+  const bool exact_size = ref.offrng[0] == ref.offrng[1];
 
-      oper = TREE_OPERAND (oper, 0);
-    }
+  offset_int bytes_avail = wi::to_offset (objsize);
+  offset_int bytes_need;
 
-  /* Refers to the declared object that constains the subobject referenced
-     by OPER.  When the object is initialized, makes it possible to determine
-     the actual size of a flexible array member used as the buffer passed
-     as OPER to placement new.  */
-  tree var_decl = NULL_TREE;
-  /* True when operand is a COMPONENT_REF, to distinguish flexible array
-     members from arrays of unspecified size.  */
-  bool compref = TREE_CODE (oper) == COMPONENT_REF;
-
-  /* For COMPONENT_REF (i.e., a struct member) the size of the entire
-     enclosing struct.  Used to validate the adjustment (offset) into
-     an array at the end of a struct.  */
-  offset_int compsize = 0;
-
-  /* Descend into a struct or union to find the member whose address
-     is being used as the argument.  */
-  if (TREE_CODE (oper) == COMPONENT_REF)
+  if (CONSTANT_CLASS_P (size))
+    bytes_need = wi::to_offset (size);
+  else if (nelts && CONSTANT_CLASS_P (nelts))
+    bytes_need = (wi::to_offset (nelts)
+		  * wi::to_offset (TYPE_SIZE_UNIT (type)));
+  else if (tree_fits_uhwi_p (TYPE_SIZE_UNIT (type)))
+    bytes_need = wi::to_offset (TYPE_SIZE_UNIT (type));
+  else
     {
-      tree comptype = TREE_TYPE (TREE_OPERAND (oper, 0));
-      compsize = wi::to_offset (TYPE_SIZE_UNIT (comptype));
-
-      tree op0 = oper;
-      while (TREE_CODE (op0 = TREE_OPERAND (op0, 0)) == COMPONENT_REF);
-      STRIP_ANY_LOCATION_WRAPPER (op0);
-      if (VAR_P (op0))
-	var_decl = op0;
-      oper = TREE_OPERAND (oper, 1);
+      /* The type is a VLA.  */
+      return;
     }
 
-  STRIP_ANY_LOCATION_WRAPPER (oper);
-  tree opertype = TREE_TYPE (oper);
-  if ((addr_expr || !INDIRECT_TYPE_P (opertype))
-      && (VAR_P (oper)
-	  || TREE_CODE (oper) == FIELD_DECL
-	  || TREE_CODE (oper) == PARM_DECL))
-    {
-      /* A possibly optimistic estimate of the number of bytes available
-	 in the destination buffer.  */
-      offset_int bytes_avail = 0;
-      /* True when the estimate above is in fact the exact size
-	 of the destination buffer rather than an estimate.  */
-      bool exact_size = true;
-
-      /* Treat members of unions and members of structs uniformly, even
-	 though the size of a member of a union may be viewed as extending
-	 to the end of the union itself (it is by __builtin_object_size).  */
-      if ((VAR_P (oper) || use_obj_size)
-	  && DECL_SIZE_UNIT (oper)
-	  && tree_fits_uhwi_p (DECL_SIZE_UNIT (oper)))
-	{
-	  /* Use the size of the entire array object when the expression
-	     refers to a variable or its size depends on an expression
-	     that's not a compile-time constant.  */
-	  bytes_avail = wi::to_offset (DECL_SIZE_UNIT (oper));
-	  exact_size = !use_obj_size;
-	}
-      else if (tree opersize = TYPE_SIZE_UNIT (opertype))
-	{
-	  /* Use the size of the type of the destination buffer object
-	     as the optimistic estimate of the available space in it.
-	     Use the maximum possible size for zero-size arrays and
-	     flexible array members (except of initialized objects
-	     thereof).  */
-	  if (TREE_CODE (opersize) == INTEGER_CST)
-	    bytes_avail = wi::to_offset (opersize);
-	}
-
-      if (bytes_avail == 0)
-	{
-	  if (var_decl)
-	    {
-	      /* Constructing into a buffer provided by the flexible array
-		 member of a declared object (which is permitted as a G++
-		 extension).  If the array member has been initialized,
-		 determine its size from the initializer.  Otherwise,
-		 the array size is zero.  */
-	      if (tree init = find_flexarray_init (oper,
-						   DECL_INITIAL (var_decl)))
-		bytes_avail = wi::to_offset (TYPE_SIZE_UNIT (TREE_TYPE (init)));
-	    }
-	  else
-	    bytes_avail = (wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node))
-			   - compsize);
-	}
-
-      tree_code oper_code = TREE_CODE (opertype);
-
-      if (compref && oper_code == ARRAY_TYPE)
-	{
-	  tree nelts = array_type_nelts_top (opertype);
-	  tree nelts_cst = maybe_constant_value (nelts);
-	  if (TREE_CODE (nelts_cst) == INTEGER_CST
-	      && integer_onep (nelts_cst)
-	      && !var_decl
-	      && warn_placement_new < 2)
-	    return;
-	}
-
-      /* Reduce the size of the buffer by the adjustment computed above
-	 from the offset and/or the index into the array.  */
-      if (bytes_avail < adjust || adjust < 0)
-	bytes_avail = 0;
-      else
-	{
-	  tree elttype = (TREE_CODE (opertype) == ARRAY_TYPE
-			  ? TREE_TYPE (opertype) : opertype);
-	  if (tree eltsize = TYPE_SIZE_UNIT (elttype))
-	    {
-	      bytes_avail -= adjust * wi::to_offset (eltsize);
-	      if (bytes_avail < 0)
-		bytes_avail = 0;
-	    }
-	}
-
-      /* The minimum amount of space needed for the allocation.  This
-	 is an optimistic estimate that makes it possible to detect
-	 placement new invocation for some undersize buffers but not
-	 others.  */
-      offset_int bytes_need;
+  if (bytes_avail >= bytes_need)
+    return;
 
-      if (nelts)
-	nelts = fold_for_warn (nelts);
-
-      if (CONSTANT_CLASS_P (size))
-	bytes_need = wi::to_offset (size);
-      else if (nelts && CONSTANT_CLASS_P (nelts))
-	bytes_need = (wi::to_offset (nelts)
-		      * wi::to_offset (TYPE_SIZE_UNIT (type)));
-      else if (tree_fits_uhwi_p (TYPE_SIZE_UNIT (type)))
-	bytes_need = wi::to_offset (TYPE_SIZE_UNIT (type));
-      else
-	{
-	  /* The type is a VLA.  */
-	  return;
-	}
+  tree opertype = ref.ref ? TREE_TYPE (ref.ref) : TREE_TYPE (oper);
+  bool warned = false;
+  if (nelts)
+    nelts = fold_for_warn (nelts);
+  if (nelts)
+    if (CONSTANT_CLASS_P (nelts))
+      warned = warning_at (loc, OPT_Wplacement_new_,
+			   (exact_size
+			    ? G_("placement new constructing an object "
+				 "of type %<%T [%wu]%> and size %qwu "
+				 "in a region of type %qT and size %qwi")
+			    : G_("placement new constructing an object "
+				 "of type %<%T [%wu]%> and size %qwu "
+				 "in a region of type %qT and size "
+				 "at most %qwu")),
+			   type, tree_to_uhwi (nelts),
+			   bytes_need.to_uhwi (),
+			   opertype, bytes_avail.to_uhwi ());
+    else
+      warned = warning_at (loc, OPT_Wplacement_new_,
+			   (exact_size
+			    ? G_("placement new constructing an array "
+				 "of objects of type %qT and size %qwu "
+				 "in a region of type %qT and size %qwi")
+			    : G_("placement new constructing an array "
+				 "of objects of type %qT and size %qwu "
+				 "in a region of type %qT and size "
+				 "at most %qwu")),
+			   type, bytes_need.to_uhwi (), opertype,
+			   bytes_avail.to_uhwi ());
+  else
+    warned = warning_at (loc, OPT_Wplacement_new_,
+			 (exact_size
+			  ? G_("placement new constructing an object "
+			       "of type %qT and size %qwu in a region "
+			       "of type %qT and size %qwi")
+			  : G_("placement new constructing an object "
+			       "of type %qT "
+			       "and size %qwu in a region of type %qT "
+			       "and size at most %qwu")),
+			       type, bytes_need.to_uhwi (), opertype,
+			 bytes_avail.to_uhwi ());
+
+  if (!warned || !ref.ref)
+    return;
 
-      if (bytes_avail < bytes_need)
-	{
-	  if (nelts)
-	    if (CONSTANT_CLASS_P (nelts))
-	      warning_at (loc, OPT_Wplacement_new_,
-			  exact_size ?
-			  "placement new constructing an object of type "
-			  "%<%T [%wu]%> and size %qwu in a region of type %qT "
-			  "and size %qwi"
-			  : "placement new constructing an object of type "
-			  "%<%T [%wu]%> and size %qwu in a region of type %qT "
-			  "and size at most %qwu",
-			  type, tree_to_uhwi (nelts), bytes_need.to_uhwi (),
-			  opertype, bytes_avail.to_uhwi ());
-	    else
-	      warning_at (loc, OPT_Wplacement_new_,
-			  exact_size ?
-			  "placement new constructing an array of objects "
-			  "of type %qT and size %qwu in a region of type %qT "
-			  "and size %qwi"
-			  : "placement new constructing an array of objects "
-			  "of type %qT and size %qwu in a region of type %qT "
-			  "and size at most %qwu",
-			  type, bytes_need.to_uhwi (), opertype,
-			  bytes_avail.to_uhwi ());
-	  else
-	    warning_at (loc, OPT_Wplacement_new_,
-			exact_size ?
-			"placement new constructing an object of type %qT "
-			"and size %qwu in a region of type %qT and size %qwi"
-			: "placement new constructing an object of type %qT "
-			"and size %qwu in a region of type %qT and size "
-			"at most %qwu",
-			type, bytes_need.to_uhwi (), opertype,
-			bytes_avail.to_uhwi ());
-	}
-    }
+  if (ref.offset_zero () || !ref.offset_bounded ())
+    inform (DECL_SOURCE_LOCATION (ref.ref),
+	    "%qD declared here", ref.ref);
+  else if (ref.offrng[0] == ref.offrng[1])
+    inform (DECL_SOURCE_LOCATION (ref.ref),
+	    "at offset %wi from %qD declared here",
+	    ref.offrng[0].to_shwi (), ref.ref);
+  else
+    inform (DECL_SOURCE_LOCATION (ref.ref),
+	    "at offset [%wi, %wi] from %qD declared here",
+	    ref.offrng[0].to_shwi (), ref.offrng[1].to_shwi (), ref.ref);
 }
 
 /* True if alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__.  */
diff --git a/gcc/gimple-array-bounds.cc b/gcc/gimple-array-bounds.cc
index c2dd6663c3a..fd4eb443f3e 100644
--- a/gcc/gimple-array-bounds.cc
+++ b/gcc/gimple-array-bounds.cc
@@ -68,7 +68,7 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
   tree decl = NULL_TREE;
 
   /* Set for accesses to interior zero-length arrays.  */
-  bool interior_zero_len = false;
+  special_array_member sam{ };
 
   tree up_bound_p1;
 
@@ -101,7 +101,7 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
 	    {
 	      /* Try to determine the size of the trailing array from
 		 its initializer (if it has one).  */
-	      if (tree refsize = component_ref_size (arg, &interior_zero_len))
+	      if (tree refsize = component_ref_size (arg, &sam))
 		if (TREE_CODE (refsize) == INTEGER_CST)
 		  maxbound = refsize;
 	    }
@@ -199,7 +199,7 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
 			 "array subscript %E is below array bounds of %qT",
 			 low_sub, artype);
 
-  if (!warned && interior_zero_len)
+  if (!warned && sam == special_array_member::int_0)
     warned = warning_at (location, OPT_Wzero_length_bounds,
 			 (TREE_CODE (low_sub) == INTEGER_CST
 			  ? G_("array subscript %E is outside the bounds "
diff --git a/gcc/testsuite/g++.dg/init/strlen.C b/gcc/testsuite/g++.dg/init/strlen.C
index aa8950e2dc0..cc650d65dbe 100644
--- a/gcc/testsuite/g++.dg/init/strlen.C
+++ b/gcc/testsuite/g++.dg/init/strlen.C
@@ -29,7 +29,7 @@ test_dynamic_type (S *p)
   // distinguish invalid cases from ones like it that might be valid.
   // If/when GIMPLE changes to make this possible this test can be
   // removed.
-  char *q = new (p->a) char [16];
+  char *q = new (p->a) char [16];   // { dg-warning "\\\[-Wplacement-new" }
 
   init (q);
 
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C
index d2ec608afd4..cec83163dbe 100644
--- a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C
@@ -66,8 +66,9 @@ struct BA2 { int i; A2 a2; };
 void fBx (BAx *pbx, BAx &rbx)
 {
   BAx bax;
-  new (bax.ax.a) char;     // { dg-warning "placement" }
-  new (bax.ax.a) Int16;    // { dg-warning "placement" }
+  // The uninitialized flexible array takes up the bytes of padding.
+  new (bax.ax.a) char;
+  new (bax.ax.a) Int16;
   new (bax.ax.a) Int32;    // { dg-warning "placement" }
 
   new (pbx->ax.a) char;
@@ -84,9 +85,12 @@ void fBx1 ()
 {
   static BAx bax1 = { 1, /* Ax = */ { 2, /* a[] = */ {} } };
 
-  new (bax1.ax.a) char;	    // { dg-warning "placement" }
-  new (bax1.ax.a) char[2];  // { dg-warning "placement" }
-  new (bax1.ax.a) Int16;    // { dg-warning "placement" }
+  // The empty flexible array takes up the bytes of padding.
+  new (bax1.ax.a) char;
+  new (bax1.ax.a) char[2];
+  new (bax1.ax.a) Int16;
+  new (bax1.ax.a) char[3];
+  new (bax1.ax.a) char[4];  // { dg-warning "placement" }
   new (bax1.ax.a) Int32;    // { dg-warning "placement" }
 }
 
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C
index e00515eeaa9..e5fdfe1f603 100644
--- a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C
@@ -124,9 +124,13 @@ struct BA2 { int i; A2 a2; };
 void fBx (BAx *pbx, BAx &rbx)
 {
   BAx bax;
-  new (bax.ax.a) char;        // { dg-warning "placement" }
-  new (bax.ax.a) Int16;       // { dg-warning "placement" }
+  // The uninitialized flexible array takes up the bytes of padding.
+  new (bax.ax.a) char;
+  new (bax.ax.a) Int16;
+  new (bax.ax.a) char[3];
   new (bax.ax.a) Int32;       // { dg-warning "placement" }
+  new (bax.ax.a) char[4];     // { dg-warning "placement" }
+  new (bax.ax.a) char[5];     // { dg-warning "placement" }
 
   new (pbx->ax.a) char;
   new (rbx.ax.a) char;
@@ -142,10 +146,14 @@ void fBx1 ()
 {
   static BAx bax1 = { 1, /* Ax = */ { 2, /* a[] = */ {} } };
 
-  new (bax1.ax.a) char;	      // { dg-warning "placement" }
-  new (bax1.ax.a) char[2];    // { dg-warning "placement" }
-  new (bax1.ax.a) Int16;      // { dg-warning "placement" }
+  // The empty flexible array takes up the bytes of padding.
+  new (bax1.ax.a) char;
+  new (bax1.ax.a) char[2];
+  new (bax1.ax.a) Int16;
+  new (bax1.ax.a) char[3];
   new (bax1.ax.a) Int32;      // { dg-warning "placement" }
+  new (bax1.ax.a) char[4];    // { dg-warning "placement" }
+  new (bax1.ax.a) char[5];    // { dg-warning "placement" }
 }
 
 void fB0 (BA0 *pb0, BA0 &rb0)
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C
index b6a72b18f6a..5eb63d23b47 100644
--- a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C
@@ -17,9 +17,10 @@ void fBx1 ()
 {
   static BAx bax1 = { 1, /* Ax = */ { 2, /* a[] = */ { 3 } } }; // { dg-error "initialization of flexible array member in a nested context" }
 
-  new (bax1.ax.a) char;     // { dg-warning "placement" }
-  new (bax1.ax.a) char[2];  // { dg-warning "placement" }
-  new (bax1.ax.a) Int16;    // { dg-warning "placement" }
+  // The first three bytes of the flexible array member live in the padding.
+  new (bax1.ax.a) char;
+  new (bax1.ax.a) char[2];
+  new (bax1.ax.a) Int16;
   new (bax1.ax.a) Int32;    // { dg-warning "placement" }
 }
 
@@ -27,10 +28,11 @@ void fBx2 ()
 {
   static BAx bax2 = { 1, /* Ax = */ { 2, /* a[] = */ { 3, 4 } } }; // { dg-error "initialization of flexible array member in a nested context" }
 
-  new (bax2.ax.a) char;       // { dg-warning "placement" }
-  new (bax2.ax.a) char[2];    // { dg-warning "placement" }
-  new (bax2.ax.a) char[3];    // { dg-warning "placement" }
-  new (bax2.ax.a) Int16;      // { dg-warning "placement" }
+  // The first three bytes of the flexible array member live in the padding.
+  new (bax2.ax.a) char;
+  new (bax2.ax.a) char[2];
+  new (bax2.ax.a) char[3];
+  new (bax2.ax.a) Int16;
   new (bax2.ax.a) char[4];    // { dg-warning "placement" }
   new (bax2.ax.a) Int32;      // { dg-warning "placement" }
 }
@@ -39,10 +41,11 @@ void fBx3 ()
 {
   static BAx bax2 = { 1, /* Ax = */ { 3, /* a[] = */ { 4, 5, 6 } } }; // { dg-error "initialization of flexible array member in a nested context" }
 
-  new (bax2.ax.a) char;       // { dg-warning "placement" }
-  new (bax2.ax.a) char[2];    // { dg-warning "placement" }
-  new (bax2.ax.a) Int16;      // { dg-warning "placement" }
-  new (bax2.ax.a) char[3];    // { dg-warning "placement" }
+  // The first three bytes of the flexible array member live in the padding.
+  new (bax2.ax.a) char;
+  new (bax2.ax.a) char[2];
+  new (bax2.ax.a) Int16;
+  new (bax2.ax.a) char[3];
   new (bax2.ax.a) char[4];    // { dg-warning "placement" }
   new (bax2.ax.a) Int32;      // { dg-warning "placement" }
 }
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-7.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-7.C
new file mode 100644
index 00000000000..82f298d8008
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-7.C
@@ -0,0 +1,82 @@
+/* PR c++/96511 - Incorrect -Wplacement-new on POINTER_PLUS into an array
+   with 4-byte elements
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+typedef __INT16_TYPE__ int16_t;
+typedef __INT32_TYPE__ int32_t;
+typedef __SIZE_TYPE__  size_t;
+
+void* operator new (size_t, void *p) { return p; }
+
+void test_a1_int16 ()
+{
+  int16_t a3[3];                    // { dg-message "declared here" }
+
+  new (a3) int16_t;
+  new (a3 + 1) int16_t;
+  new (a3 + 2) int16_t;             // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[1]) int16_t;
+  new (&a3[0] + 1) int16_t;
+  new (&a3[0] + 2) int16_t;         // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[0] + 3) int16_t;         // { dg-warning "\\\[-Wplacement-new" }
+}
+
+void test_a1_int32 ()
+{
+  int16_t a3[3];
+
+  new (a3 + 1) int32_t;             // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[1]) int32_t;
+  new (&a3[0] + 1) int32_t;         // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[0] + 2) int32_t;         // { dg-warning "\\\[-Wplacement-new" }
+}
+
+
+void test_a2 ()
+{
+  int16_t a23[2][3];
+
+  new (a23 + 1) int16_t;            // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a23[1]) int16_t;
+  new (&a23[2]) int16_t;            // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[0][0] + 1) int16_t;
+  new (&a23[0][0] + 2) int16_t;
+  // Deriving a pointer to the next array from one to an element of
+  // the prior array isn't valid even if the resulting pointer points
+  // to an element of the larger array.  Verify it's diagnosed.
+  new (&a23[0][0] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][0] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][0] + 5) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][0] + 6) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[0][1] + 1) int16_t;
+  new (&a23[0][1] + 2) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 5) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 6) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[0][2] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 2) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 5) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 6) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[1][0]) int16_t;
+  new (&a23[1][0] + 1) int16_t;
+  new (&a23[1][0] + 2) int16_t;
+  new (&a23[1][0] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[1][0] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[1][1]) int16_t;
+  new (&a23[1][2]) int16_t;
+  new (&a23[1][2] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[1][3]) int16_t;         // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[1][3] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[2][0]) int16_t;         // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[2][0] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+}
diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-58.c b/gcc/testsuite/gcc.dg/Warray-bounds-58.c
index 7c469e2aefc..849457e559f 100644
--- a/gcc/testsuite/gcc.dg/Warray-bounds-58.c
+++ b/gcc/testsuite/gcc.dg/Warray-bounds-58.c
@@ -1,5 +1,5 @@
 /* { dg-do compile }
-   { dg-options "-O2 -Wall" } */
+   { dg-options "-O2 -Wall -Wno-stringop-overread" } */
 
 typedef __SIZE_TYPE__ size_t;
 
@@ -15,7 +15,7 @@ void fa0_extern (void)
 {
   sink (strlen (ea0.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
   sink (strlen (ea0.a - 1));    // { dg-warning "\\\[-Warray-bounds" "pr93514" { xfail *-*-* } }
-  sink (strlen (ea0.a));        // { dg-warning "\\\[-Wstringop-overread" "pr93514" }
+  sink (strlen (ea0.a));        // valid just-past-the-end offset
   sink (strlen (ea0.a + 1));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 }
 
@@ -25,7 +25,7 @@ void fa0_static (void)
 {
   sink (strlen (sa0.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
   sink (strlen (sa0.a - 1));    // { dg-warning "\\\[-Warray-bounds" "pr93514" { xfail *-*-* } }
-  sink (strlen (sa0.a));        // { dg-warning "\\\[-Wstringop-overread" "pr93514" }
+  sink (strlen (sa0.a));        // valid just-past-the-end offset
   sink (strlen (sa0.a + 1));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 }
 
@@ -52,14 +52,14 @@ void fax_static (void)
   sink (strlen (ax0.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
   sink (strlen (ax0.a - 1));    // { dg-warning "\\\[-Warray-bounds" "pr93514" { xfail *-*-* } }
   sink (strlen (ax0.a));
-  sink (strlen (ax0.a + 1));    // { dg-warning "\\\[-Wstringop-overread" "pr93514" }
+  sink (strlen (ax0.a + 1));    // valid just-past-the-end offset
   sink (strlen (ax0.a + 2));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 
   sink (strlen (ax1.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
   sink (strlen (ax1.a - 1));    // { dg-warning "\\\[-Warray-bounds" "pr93514" { xfail *-*-* } }
   sink (strlen (ax1.a));
   sink (strlen (ax1.a + 1));
-  sink (strlen (ax1.a + 2));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" "pr93514" }
+  sink (strlen (ax1.a + 2));    // valid just-past-the-end offset
   sink (strlen (ax1.a + 3));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 
   sink (strlen (ax2.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
@@ -67,7 +67,7 @@ void fax_static (void)
   sink (strlen (ax2.a));
   sink (strlen (ax2.a + 1));
   sink (strlen (ax2.a + 2));
-  sink (strlen (ax2.a + 3));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" "pr93514" }
+  sink (strlen (ax2.a + 3));    // valid just-past-the-end offset
   sink (strlen (ax2.a + 4));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 
   sink (strlen (ax3.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
@@ -76,6 +76,6 @@ void fax_static (void)
   sink (strlen (ax3.a + 1));
   sink (strlen (ax3.a + 2));
   sink (strlen (ax3.a + 3));
-  sink (strlen (ax3.a + 4));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" "pr93514" }
+  sink (strlen (ax3.a + 4));    // valid just-past-the-end offset
   sink (strlen (ax3.a + 5));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 }
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c
index 339f904d7a6..46f8fed79f3 100644
--- a/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c
@@ -184,6 +184,18 @@ void test_note (const char *s)
     sink (a);
   }
 
+  {
+    char a[1][1][2];                    // { dg-message "at offset 2 into " }
+    strncpy (a[0][1], s, 3);            // { dg-warning "writing 3 bytes into a region of size 0 " }
+    sink (a);
+  }
+
+  {
+    char a[1][2][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 " }
@@ -192,7 +204,13 @@ void test_note (const char *s)
 
   {
     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 " }
+    strncpy (a[1][0], s, 3);            // { dg-warning "writing 3 bytes into a region of size 0 " }
+    sink (a);
+  }
+
+  {
+    char a[2][1][2];                    // { dg-message "at offset 2 into " }
+    strncpy (a[0][1], s, 3);            // { dg-warning "writing 3 bytes into a region of size 0 " }
     sink (a);
   }
 
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-40.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-40.c
new file mode 100644
index 00000000000..cd8fa3202eb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-40.c
@@ -0,0 +1,116 @@
+/* PR middle-end/96384 - bogus -Wstringop-overflow= storing into
+   multidimensional array with index in range
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+#define SHRT_MAX   __SHRT_MAX__
+#define SHRT_MIN   (-SHRT_MAX - 1)
+#define INT_MAX    __INT_MAX__
+#define INT_MIN    (-INT_MAX - 1)
+#define LONG_MAX   __LONG_MAX__
+#define LONG_MIN   (-LONG_MAX - 1)
+
+#define USHRT_MAX  (SHRT_MAX * 2 + 1)
+#define UINT_MAX   ~0U
+#define ULONG_MAX  ~0LU
+
+char ca3_5_7[3][5][7];
+
+void nowarn_ca_3_5_ssi (short i)
+{
+  if (i > SHRT_MAX - 1)
+    i = SHRT_MAX - 1;
+
+  ca3_5_7[i][0][0] = __LINE__;
+  ca3_5_7[i][0][1] = __LINE__;
+  ca3_5_7[i][0][2] = __LINE__;
+  ca3_5_7[i][0][3] = __LINE__;
+  ca3_5_7[i][0][4] = __LINE__;
+  ca3_5_7[i][0][5] = __LINE__;
+  ca3_5_7[i][0][6] = __LINE__;
+
+  ca3_5_7[i][1][0] = __LINE__;
+  ca3_5_7[i][1][1] = __LINE__;
+  ca3_5_7[i][1][2] = __LINE__;
+  ca3_5_7[i][1][3] = __LINE__;
+  ca3_5_7[i][1][4] = __LINE__;
+  ca3_5_7[i][1][5] = __LINE__;
+  ca3_5_7[i][1][6] = __LINE__;
+
+  ca3_5_7[i][2][0] = __LINE__;
+  ca3_5_7[i][2][1] = __LINE__;
+  ca3_5_7[i][2][2] = __LINE__;
+  ca3_5_7[i][2][3] = __LINE__;
+  ca3_5_7[i][2][4] = __LINE__;
+  ca3_5_7[i][2][5] = __LINE__;
+  ca3_5_7[i][2][6] = __LINE__;
+
+  ca3_5_7[i][3][0] = __LINE__;
+  ca3_5_7[i][3][1] = __LINE__;
+  ca3_5_7[i][3][2] = __LINE__;
+  ca3_5_7[i][3][3] = __LINE__;
+  ca3_5_7[i][3][4] = __LINE__;
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[i][3][6] = __LINE__;
+
+  ca3_5_7[i][4][0] = __LINE__;
+  ca3_5_7[i][4][1] = __LINE__;
+  ca3_5_7[i][4][2] = __LINE__;
+  ca3_5_7[i][4][3] = __LINE__;
+  ca3_5_7[i][4][4] = __LINE__;
+  ca3_5_7[i][4][5] = __LINE__;
+  ca3_5_7[i][4][6] = __LINE__;
+
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_usi (unsigned short i)
+{
+  if (i > USHRT_MAX - 1)
+    i = USHRT_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_si (int i)
+{
+  if (i > INT_MAX - 1)
+    i = INT_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_ui (unsigned i)
+{
+  if (i > UINT_MAX - 1)
+    i = UINT_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_li (long i)
+{
+  if (i > LONG_MAX - 1)
+    i = LONG_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_uli (unsigned long i)
+{
+  if (i > ULONG_MAX - 1)
+    i = ULONG_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
diff --git a/gcc/tree-object-size.c b/gcc/tree-object-size.c
index c107cb4a866..53d06875f74 100644
--- a/gcc/tree-object-size.c
+++ b/gcc/tree-object-size.c
@@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
   tree last_type = TREE_TYPE (last);
   if (TREE_CODE (last_type) != ARRAY_TYPE
       || TYPE_SIZE (last_type))
-    return size;
+    return size ? size : TYPE_SIZE_UNIT (type);
 
   /* Use TYPE_SIZE_UNIT; DECL_SIZE_UNIT sometimes reflects the size
      of the initializer and sometimes doesn't.  */
diff --git a/gcc/tree.c b/gcc/tree.c
index 45aacadbe2d..5fb94e6b24f 100644
--- a/gcc/tree.c
+++ b/gcc/tree.c
@@ -13638,20 +13638,21 @@ get_initializer_for (tree init, tree decl)
 /* Determines the size of the member referenced by the COMPONENT_REF
    REF, using its initializer expression if necessary in order to
    determine the size of an initialized flexible array member.
-   If non-null, *INTERIOR_ZERO_LENGTH is set when REF refers to
-   an interior zero-length array.
+   If non-null, set *ARK when REF refers to an interior zero-length
+   array or a trailing one-element array.
    Returns the size as sizetype (which might be zero for an object
    with an uninitialized flexible array member) or null if the size
    cannot be determined.  */
 
 tree
-component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
+component_ref_size (tree ref, special_array_member *sam /* = NULL */)
 {
   gcc_assert (TREE_CODE (ref) == COMPONENT_REF);
 
-  bool int_0_len = false;
-  if (!interior_zero_length)
-    interior_zero_length = &int_0_len;
+  special_array_member arkbuf;
+  if (!sam)
+    sam = &arkbuf;
+  *sam = special_array_member::none;
 
   /* The object/argument referenced by the COMPONENT_REF and its type.  */
   tree arg = TREE_OPERAND (ref, 0);
@@ -13673,9 +13674,16 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
 	   more than one element.  */
 	return memsize;
 
-      *interior_zero_length = zero_length && !trailing;
-      if (*interior_zero_length)
-	memsize = NULL_TREE;
+      if (zero_length)
+	{
+	  if (trailing)
+	    *sam = special_array_member::trail_0;
+	  else
+	    {
+	      *sam = special_array_member::int_0;
+	      memsize = NULL_TREE;
+	    }
+	}
 
       if (!zero_length)
 	if (tree dom = TYPE_DOMAIN (memtype))
@@ -13686,9 +13694,13 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
 		{
 		  offset_int minidx = wi::to_offset (min);
 		  offset_int maxidx = wi::to_offset (max);
-		  if (maxidx - minidx > 0)
+		  offset_int neltsm1 = maxidx - minidx;
+		  if (neltsm1 > 0)
 		    /* MEMBER is an array with more than one element.  */
 		    return memsize;
+
+		  if (neltsm1 == 0)
+		    *sam = special_array_member::trail_1;
 		}
 
       /* For a refernce to a zero- or one-element array member of a union
@@ -13706,7 +13718,7 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
   tree base = get_addr_base_and_unit_offset (ref, &baseoff);
   if (!base || !VAR_P (base))
     {
-      if (!*interior_zero_length)
+      if (*sam != special_array_member::int_0)
 	return NULL_TREE;
 
       if (TREE_CODE (arg) != COMPONENT_REF)
@@ -13726,7 +13738,7 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
   /* Determine the base type of the referenced object.  If it's
      the same as ARGTYPE and MEMBER has a known size, return it.  */
   tree bt = basetype;
-  if (!*interior_zero_length)
+  if (*sam != special_array_member::int_0)
     while (TREE_CODE (bt) == ARRAY_TYPE)
       bt = TREE_TYPE (bt);
   bool typematch = useless_type_conversion_p (argtype, bt);
@@ -13766,7 +13778,7 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
 	  if (DECL_P (base)
 	      && DECL_EXTERNAL (base)
 	      && bt == basetype
-	      && !*interior_zero_length)
+	      && *sam != special_array_member::int_0)
 	    /* The size of a flexible array member of an extern struct
 	       with no initializer cannot be determined (it's defined
 	       in another translation unit and can have an initializer
diff --git a/gcc/tree.h b/gcc/tree.h
index b0ef14b6cd9..be426b99b5a 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -5284,12 +5284,22 @@ extern bool array_at_struct_end_p (tree);
    by EXP.  This does not include any offset in DECL_FIELD_BIT_OFFSET.  */
 extern tree component_ref_field_offset (tree);
 
+/* Describes a "special" array member due to which component_ref_size
+   returns null.  */
+enum struct special_array_member
+  {
+   none,      /* Not a special array member.  */
+   int_0,     /* Interior array member with size zero.  */
+   trail_0,   /* Trailing array member with size zero.  */
+   trail_1    /* Trailing array member with one element.  */
+  };
+
 /* Return the size of the member referenced by the COMPONENT_REF, using
    its initializer expression if necessary in order to determine the size
    of an initialized flexible array member.  The size might be zero for
    an object with an uninitialized flexible array member or null if it
    cannot be determined.  */
-extern tree component_ref_size (tree, bool * = NULL);
+extern tree component_ref_size (tree, special_array_member * = NULL);
 
 extern int tree_map_base_eq (const void *, const void *);
 extern unsigned int tree_map_base_hash (const void *);

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-09-03 18:44   ` Martin Sebor
@ 2020-09-04 17:14     ` Jason Merrill
  2020-09-14 22:01       ` Martin Sebor
  0 siblings, 1 reply; 28+ messages in thread
From: Jason Merrill @ 2020-09-04 17:14 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 4955 bytes --]

On 9/3/20 2:44 PM, Martin Sebor wrote:
> On 9/1/20 1:22 PM, Jason Merrill wrote:
>> On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
>>> -Wplacement-new handles array indices and pointer offsets the same:
>>> by adjusting them by the size of the element.  That's correct for
>>> the latter but wrong for the former, causing false positives when
>>> the element size is greater than one.
>>>
>>> In addition, the warning doesn't even attempt to handle arrays of
>>> arrays.  I'm not sure if I forgot or if I simply didn't think of
>>> it.
>>>
>>> The attached patch corrects these oversights by replacing most
>>> of the -Wplacement-new code with a call to compute_objsize which
>>> handles all this correctly (plus more), and is also better tested.
>>> But even compute_objsize has bugs: it trips up while converting
>>> wide_int to offset_int for some pointer offset ranges.  Since
>>> handling the C++ IL required changes in this area the patch also
>>> fixes that.
>>>
>>> For review purposes, the patch affects just the middle end.
>>> The C++ diff pretty much just removes code from the front end.
>>
>> The C++ changes are OK.
> 
> Thank you for looking at the rest as well.
> 
>>
>>> -compute_objsize (tree ptr, int ostype, access_ref *pref,
>>> -                bitmap *visited, const vr_values *rvals /* = NULL */)
>>> +compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap 
>>> *visited,
>>> +                const vr_values *rvals)
>>
>> This reformatting seems unnecessary, and I prefer to keep the comment 
>> about the default argument.
> 
> This overload doesn't take a default argument.  (There was a stray
> declaration of a similar function at the top of the file that had
> one.  I've removed it.)

Ah, true.

>>> -      if (!size || TREE_CODE (size) != INTEGER_CST)
>>> -       return false;
>>  >...
>>
>> You change some failure cases in compute_objsize to return success 
>> with a maximum range, while others continue to return failure.  This 
>> needs commentary about the design rationale.
> 
> This is too much for a comment in the code but the background is
> this: compute_objsize initially returned the object size as a constant.
> Recently, I have enhanced it to return a range to improve warnings for
> allocated objects.  With that, a failure can be turned into success by
> having the function set the range to that of the largest object.  That
> should simplify the function's callers and could even improve
> the detection of some invalid accesses.  Once this change is made
> it might even be possible to change its return type to void.
> 
> The change that caught your eye is necessary to make the function
> a drop-in replacement for the C++ front end code which makes this
> same assumption.  Without it, a number of test cases that exercise
> VLAs fail in g++.dg/warn/Wplacement-new-size-5.C.  For example:
> 
>    void f (int n)
>    {
>      char a[n];
>      new (a - 1) int ();
>    }
> 
> Changing any of the other places isn't necessary for existing tests
> to pass (and I didn't want to introduce too much churn).  But I do
> want to change the rest of the function along the same lines at some
> point.

Please do change the other places to be consistent; better to have more 
churn than to leave the function half-updated.  That can be a separate 
patch if you prefer, but let's do it now rather than later.

>>> +  special_array_member sam{ };
>>
>> sam is always set by component_ref_size, so I don't think it's 
>> necessary to initialize it at the declaration.
> 
> I find initializing pass-by-pointer local variables helpful but
> I don't insist on it.
> 
>>
>>> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
>>>    tree last_type = TREE_TYPE (last);
>>>    if (TREE_CODE (last_type) != ARRAY_TYPE
>>>        || TYPE_SIZE (last_type))
>>> -    return size;
>>> +    return size ? size : TYPE_SIZE_UNIT (type);
>>
>> This change seems to violate the comment for the function.
> 
> By my reading (and writing) the change is covered by the first
> sentence:
> 
>     Returns the size of the object designated by DECL considering
>     its initializer if it either has one or if it would not affect
>     its size, ...

OK, I see it now.

> It handles a number of cases in Wplacement-new-size.C fail that
> construct a larger object in an extern declaration of a template,
> like this:
> 
>    template <class> struct S { char c; };
>    extern S<int> s;
> 
>    void f ()
>    {
>      new (&s) int ();
>    }
> 
> I don't know why DECL_SIZE isn't set here (I don't think it can
> be anything but equal to TYPE_SIZE, can it?) and other than struct
> objects with a flexible array member where this identity doesn't
> hold I can't think of others.  Am I missing something?

Good question.  The attached patch should fix that, so you shouldn't 
need the change to decl_init_size:


[-- Attachment #2: layout.diff --]
[-- Type: text/x-patch, Size: 745 bytes --]

commit 95c284379d67efb79a30273236fd6769a12f3031
Author: Jason Merrill <jason@redhat.com>
Date:   Fri Sep 4 12:14:19 2020 -0400

    layout

diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index 31d68745844..f6ca51ad8ba 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -17444,10 +17444,10 @@ complete_vars (tree type)
 	      && (TYPE_MAIN_VARIANT (strip_array_types (type))
 		  == iv->incomplete_type))
 	    {
-	      /* Complete the type of the variable.  The VAR_DECL itself
-		 will be laid out in expand_expr.  */
+	      /* Complete the type of the variable.  */
 	      complete_type (type);
 	      cp_apply_type_quals_to_decl (cp_type_quals (type), var);
+	      layout_decl (var, 0);
 	    }
 
 	  /* Remove this entry from the list.  */

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-09-04 17:14     ` Jason Merrill
@ 2020-09-14 22:01       ` Martin Sebor
  2020-09-21 21:17         ` [PING][PATCH] " Martin Sebor
  0 siblings, 1 reply; 28+ messages in thread
From: Martin Sebor @ 2020-09-14 22:01 UTC (permalink / raw)
  To: Jason Merrill, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 5819 bytes --]

On 9/4/20 11:14 AM, Jason Merrill wrote:
> On 9/3/20 2:44 PM, Martin Sebor wrote:
>> On 9/1/20 1:22 PM, Jason Merrill wrote:
>>> On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
>>>> -Wplacement-new handles array indices and pointer offsets the same:
>>>> by adjusting them by the size of the element.  That's correct for
>>>> the latter but wrong for the former, causing false positives when
>>>> the element size is greater than one.
>>>>
>>>> In addition, the warning doesn't even attempt to handle arrays of
>>>> arrays.  I'm not sure if I forgot or if I simply didn't think of
>>>> it.
>>>>
>>>> The attached patch corrects these oversights by replacing most
>>>> of the -Wplacement-new code with a call to compute_objsize which
>>>> handles all this correctly (plus more), and is also better tested.
>>>> But even compute_objsize has bugs: it trips up while converting
>>>> wide_int to offset_int for some pointer offset ranges.  Since
>>>> handling the C++ IL required changes in this area the patch also
>>>> fixes that.
>>>>
>>>> For review purposes, the patch affects just the middle end.
>>>> The C++ diff pretty much just removes code from the front end.
>>>
>>> The C++ changes are OK.
>>
>> Thank you for looking at the rest as well.
>>
>>>
>>>> -compute_objsize (tree ptr, int ostype, access_ref *pref,
>>>> -                bitmap *visited, const vr_values *rvals /* = NULL */)
>>>> +compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap 
>>>> *visited,
>>>> +                const vr_values *rvals)
>>>
>>> This reformatting seems unnecessary, and I prefer to keep the comment 
>>> about the default argument.
>>
>> This overload doesn't take a default argument.  (There was a stray
>> declaration of a similar function at the top of the file that had
>> one.  I've removed it.)
> 
> Ah, true.
> 
>>>> -      if (!size || TREE_CODE (size) != INTEGER_CST)
>>>> -       return false;
>>>  >...
>>>
>>> You change some failure cases in compute_objsize to return success 
>>> with a maximum range, while others continue to return failure.  This 
>>> needs commentary about the design rationale.
>>
>> This is too much for a comment in the code but the background is
>> this: compute_objsize initially returned the object size as a constant.
>> Recently, I have enhanced it to return a range to improve warnings for
>> allocated objects.  With that, a failure can be turned into success by
>> having the function set the range to that of the largest object.  That
>> should simplify the function's callers and could even improve
>> the detection of some invalid accesses.  Once this change is made
>> it might even be possible to change its return type to void.
>>
>> The change that caught your eye is necessary to make the function
>> a drop-in replacement for the C++ front end code which makes this
>> same assumption.  Without it, a number of test cases that exercise
>> VLAs fail in g++.dg/warn/Wplacement-new-size-5.C.  For example:
>>
>>    void f (int n)
>>    {
>>      char a[n];
>>      new (a - 1) int ();
>>    }
>>
>> Changing any of the other places isn't necessary for existing tests
>> to pass (and I didn't want to introduce too much churn).  But I do
>> want to change the rest of the function along the same lines at some
>> point.
> 
> Please do change the other places to be consistent; better to have more 
> churn than to leave the function half-updated.  That can be a separate 
> patch if you prefer, but let's do it now rather than later.

I've made most of these changes in the other patch (also attached).
I'm quite happy with the result but it turned out to be a lot more
work than either of us expected, mostly due to the amount of testing.

I've left a couple of failing cases in place mainly as reminders
to handle them better (which means I also didn't change the caller
to avoid testing for failures).  I've also added TODO notes with
reminders to handle some of the new codes more completely.

> 
>>>> +  special_array_member sam{ };
>>>
>>> sam is always set by component_ref_size, so I don't think it's 
>>> necessary to initialize it at the declaration.
>>
>> I find initializing pass-by-pointer local variables helpful but
>> I don't insist on it.
>>
>>>
>>>> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
>>>>    tree last_type = TREE_TYPE (last);
>>>>    if (TREE_CODE (last_type) != ARRAY_TYPE
>>>>        || TYPE_SIZE (last_type))
>>>> -    return size;
>>>> +    return size ? size : TYPE_SIZE_UNIT (type);
>>>
>>> This change seems to violate the comment for the function.
>>
>> By my reading (and writing) the change is covered by the first
>> sentence:
>>
>>     Returns the size of the object designated by DECL considering
>>     its initializer if it either has one or if it would not affect
>>     its size, ...
> 
> OK, I see it now.
> 
>> It handles a number of cases in Wplacement-new-size.C fail that
>> construct a larger object in an extern declaration of a template,
>> like this:
>>
>>    template <class> struct S { char c; };
>>    extern S<int> s;
>>
>>    void f ()
>>    {
>>      new (&s) int ();
>>    }
>>
>> I don't know why DECL_SIZE isn't set here (I don't think it can
>> be anything but equal to TYPE_SIZE, can it?) and other than struct
>> objects with a flexible array member where this identity doesn't
>> hold I can't think of others.  Am I missing something?
> 
> Good question.  The attached patch should fix that, so you shouldn't 
> need the change to decl_init_size:

I've integrated it into the bug fix.

Besides the usual x86_64-linux bootstrap/regtest I tested both
patches by building a few packages, including Binutils/GDB, Glibc,
and  verifying no new warnings show up.

Martin

[-- Attachment #2: gcc-96511.diff --]
[-- Type: text/x-patch, Size: 49026 bytes --]

Correct handling of indices into arrays with elements larger than 1 (PR c++/96511)

Resolves:
PR c++/96511 - Incorrect -Wplacement-new on POINTER_PLUS into an array with 4-byte elements
PR middle-end/96384 - bogus -Wstringop-overflow= storing into multidimensional array with index in range

gcc/ChangeLog:

	PR c++/96511
	PR middle-end/96384
	* builtins.c (get_range): Return full range of type when neither
	value nor its range is available.  Fail for ranges inverted due
	to the signedness of offsets.
	(compute_objsize): Handle more special array members.  Handle
	POINTER_PLUS_EXPR and VIEW_CONVERT_EXPR that come up in front end
	code.
	(access_ref::offset_bounded): Define new member function.
	* builtins.h (access_ref::eval): New data member.
	(access_ref::offset_bounded): New member function.
	(access_ref::offset_zero): New member function.
	(compute_objsize): Declare a new overload.
	* gimple-array-bounds.cc (array_bounds_checker::check_array_ref): Use
	enum special_array_member.
	* tree-object-size.c (decl_init_size): Return the size of the structure
	type if the decl size is null.
	* tree.c (component_ref_size): Use special_array_member.
	* tree.h (special_array_member): Define a new type.
	(component_ref_size): Change signature/	

gcc/cp/ChangeLog:

	PR c++/96511
	PR middle-end/96384
	* decl.c (complete_vars): Call layout_decl.
	* init.c (warn_placement_new_too_small): Call builtin_objsize instead
	of duplicating what it does.

gcc/testsuite/ChangeLog:

	PR c++/96511
	PR middle-end/96384
	* g++.dg/warn/Wplacement-new-size-1.C: Relax warnings.
	* g++.dg/warn/Wplacement-new-size-2.C: Same.
	* g++.dg/warn/Wplacement-new-size-6.C: Same.
	* g++.dg/warn/Wplacement-new-size-7.C: New test.
	* gcc.dg/Wstringop-overflow-40.c: New test.
	* gcc/testsuite/gcc.dg/Warray-bounds-58.c: Adjust
	* gcc/testsuite/gcc.dg/Wstringop-overflow-37.c: Same.
	* gcc/testsuite/gcc.dg/Wstringop-overflow-40.c: Same.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 8b9a4a4d948..611cf7f1f4b 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -183,7 +183,6 @@ static void maybe_emit_chk_warning (tree, enum built_in_function);
 static void maybe_emit_sprintf_chk_warning (tree, enum built_in_function);
 static void maybe_emit_free_warning (tree);
 static tree fold_builtin_object_size (tree, tree);
-static tree compute_objsize (tree, int, access_ref *, const vr_values * = NULL);
 static bool get_range (tree, signop, offset_int[2], const vr_values * = NULL);
 static bool check_read_access (tree, tree, tree = NULL_TREE, int = 1);
 
@@ -200,7 +199,7 @@ static void expand_builtin_sync_synchronize (void);
 
 access_ref::access_ref (tree bound /* = NULL_TREE */,
 			bool minaccess /* = false */)
-  : ref ()
+: ref (), eval ([](tree x){ return x; }), trail1special (true)
 {
   /* Set to valid.  */
   offrng[0] = offrng[1] = 0;
@@ -4163,10 +4162,34 @@ static bool
 get_range (tree x, signop sgn, offset_int r[2],
 	   const vr_values *rvals /* = NULL */)
 {
+  tree type = TREE_TYPE (x);
+  if (TREE_CODE (x) != INTEGER_CST
+      && TREE_CODE (x) != SSA_NAME)
+    {
+      if (TYPE_UNSIGNED (type))
+	{
+	  if (sgn == SIGNED)
+	    type = signed_type_for (type);
+	}
+      else if (sgn == UNSIGNED)
+	type = unsigned_type_for (type);
+
+      r[0] = wi::to_offset (TYPE_MIN_VALUE (type));
+      r[1] = wi::to_offset (TYPE_MAX_VALUE (type));
+      return x;
+    }
+
   wide_int wr[2];
   if (!get_range (x, wr, rvals))
     return false;
 
+  /* Only convert signed integers or unsigned sizetype to a signed
+     offset and avoid converting large positive values in narrower
+     types to negative offsets.  */
+  if (TYPE_UNSIGNED (type)
+      && wr[0].get_precision () < TYPE_PRECISION (sizetype))
+    sgn = UNSIGNED;
+
   r[0] = offset_int::from (wr[0], sgn);
   r[1] = offset_int::from (wr[1], sgn);
   return true;
@@ -4187,9 +4210,11 @@ get_range (tree x, signop sgn, offset_int r[2],
    to influence code generation or optimization.  */
 
 static bool
-compute_objsize (tree ptr, int ostype, access_ref *pref,
-		 bitmap *visited, const vr_values *rvals /* = NULL */)
+compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
+		 const vr_values *rvals)
 {
+  STRIP_NOPS (ptr);
+
   const bool addr = TREE_CODE (ptr) == ADDR_EXPR;
   if (addr)
     ptr = TREE_OPERAND (ptr, 0);
@@ -4201,12 +4226,15 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
       if (!addr && POINTER_TYPE_P (TREE_TYPE (ptr)))
 	return false;
 
-      tree size = decl_init_size (ptr, false);
-      if (!size || TREE_CODE (size) != INTEGER_CST)
-	return false;
-
       pref->ref = ptr;
-      pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
+      if (tree size = decl_init_size (ptr, false))
+	if (TREE_CODE (size) == INTEGER_CST)
+	  {
+	    pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
+	    return true;
+	  }
+      pref->sizrng[0] = 0;
+      pref->sizrng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
       return true;
     }
 
@@ -4214,13 +4242,13 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
   if (code == COMPONENT_REF)
     {
+      tree ref = TREE_OPERAND (ptr, 0);
       tree field = TREE_OPERAND (ptr, 1);
 
       if (ostype == 0)
 	{
 	  /* For raw memory functions like memcpy bail if the size
 	     of the enclosing object cannot be determined.  */
-	  tree ref = TREE_OPERAND (ptr, 0);
 	  if (!compute_objsize (ref, ostype, pref, visited, rvals)
 	      || !pref->ref)
 	    return false;
@@ -4242,20 +4270,28 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 	return false;
 
       pref->ref = field;
-      /* Only return constant sizes for now while callers depend
-	 on it.  INT0LEN is true for interior zero-length arrays.  */
-      bool int0len = false;
-      tree size = component_ref_size (ptr, &int0len);
-      if (int0len)
+
+      /* SAM is set for array members that might need special treatment.  */
+      special_array_member sam;
+      tree size = component_ref_size (ptr, &sam);
+      if (sam == special_array_member::int_0)
+	pref->sizrng[0] = pref->sizrng[1] = 0;
+      else if (!pref->trail1special && sam == special_array_member::trail_1)
+	pref->sizrng[0] = pref->sizrng[1] = 1;
+      else if (size && TREE_CODE (size) == INTEGER_CST)
+	pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
+      else
 	{
-	  pref->sizrng[0] = pref->sizrng[1] = 0;
-	  return true;
+	  /* When the size of the member is unknown it's either a flexible
+	     array member or a trailing special array member (either zero
+	     length or one-element).  Set the size to the maximum minus
+	     the constant size of the type.  */
+	  pref->sizrng[0] = 0;
+	  pref->sizrng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
+	  if (tree recsize = TYPE_SIZE_UNIT (TREE_TYPE (ref)))
+	    if (TREE_CODE (recsize) == INTEGER_CST)
+	      pref->sizrng[1] -= wi::to_offset (recsize);
 	}
-
-      if (!size || TREE_CODE (size) != INTEGER_CST)
-	return false;
-
-      pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
       return true;
     }
 
@@ -4285,7 +4321,7 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 	return false;
 
       offset_int orng[2];
-      tree off = TREE_OPERAND (ptr, 1);
+      tree off = pref->eval (TREE_OPERAND (ptr, 1));
       if (!get_range (off, SIGNED, orng, rvals))
 	/* Fail unless the size of the object is zero.  */
 	return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1];
@@ -4315,11 +4351,22 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
 	  if (ostype && TREE_CODE (eltype) == ARRAY_TYPE)
 	    {
-	      /* Execpt for the permissive raw memory functions which
-		 use the size of the whole object determined above,
-		 use the size of the referenced array.  */
-	      pref->sizrng[0] = pref->offrng[0] + orng[0] + sz;
-	      pref->sizrng[1] = pref->offrng[1] + orng[1] + sz;
+	      /* Except for the permissive raw memory functions which use
+		 the size of the whole object determined above, use the size
+		 of the referenced array.  Because the overall offset is from
+		 the beginning of the complete array object add this overall
+		 offset to the size of array.  */
+	      offset_int sizrng[2] =
+		{
+		 pref->offrng[0] + orng[0] + sz,
+		 pref->offrng[1] + orng[1] + sz
+		};
+	      if (sizrng[1] < sizrng[0])
+		std::swap (sizrng[0], sizrng[1]);
+	      if (sizrng[0] >= 0 && sizrng[0] <= pref->sizrng[0])
+		pref->sizrng[0] = sizrng[0];
+	      if (sizrng[1] >= 0 && sizrng[1] <= pref->sizrng[1])
+		pref->sizrng[1] = sizrng[1];
 	    }
 	}
 
@@ -4328,6 +4375,28 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
       return true;
     }
+  else if (code == POINTER_PLUS_EXPR)
+    {
+      tree ref = TREE_OPERAND (ptr, 0);
+      if (!compute_objsize (ref, ostype, pref, visited, rvals))
+	return false;
+
+      offset_int orng[2];
+      tree off = pref->eval (TREE_OPERAND (ptr, 1));
+      if (!get_range (off, SIGNED, orng, rvals))
+	/* Fail unless the size of the object is zero.  */
+	return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1];
+
+      pref->offrng[0] += orng[0];
+      pref->offrng[1] += orng[1];
+
+      return true;
+    }
+  else if (code == VIEW_CONVERT_EXPR)
+    {
+      ptr = TREE_OPERAND (ptr, 0);
+      return compute_objsize (ptr, ostype, pref, visited, rvals);
+    }
 
   if (TREE_CODE (ptr) == SSA_NAME)
     {
@@ -4400,7 +4469,7 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 /* A "public" wrapper around the above.  Clients should use this overload
    instead.  */
 
-static tree
+tree
 compute_objsize (tree ptr, int ostype, access_ref *pref,
 		 const vr_values *rvals /* = NULL */)
 {
@@ -12284,3 +12353,14 @@ builtin_with_linkage_p (tree decl)
     }
   return false;
 }
+
+bool
+access_ref::offset_bounded () const
+{
+  if (offrng[0] == offrng[1])
+    return false;
+
+  tree min = TYPE_MIN_VALUE (ptrdiff_type_node);
+  tree max = TYPE_MAX_VALUE (ptrdiff_type_node);
+  return offrng[0] <= wi::to_offset (min) || offrng[1] >= wi::to_offset (max);
+}
diff --git a/gcc/builtins.h b/gcc/builtins.h
index 94ff96b1292..e42c9664724 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -133,13 +133,6 @@ 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);
-
-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);
 extern unsigned HOST_WIDE_INT target_newline;
@@ -179,6 +172,22 @@ struct access_ref
      For string functions the size of the actual access is
      further constrained by the length of the string.  */
   offset_int bndrng[2];
+
+  /* Return true if OFFRNG is the constant zero.  */
+  bool offset_zero () const
+  {
+    return offrng[0] == 0 && offrng[1] == 0;
+  }
+
+  /* Return true if OFFRNG is bounded to a subrange of possible offset
+     values.  */
+  bool offset_bounded () const;
+
+  /* Used to fold integer expressions when called from front ends.  */
+  tree (*eval)(tree);
+  /* Set if trailing one-element arrays should be treated as flexible
+     array members.  */
+  bool trail1special;
 };
 
 /* Describes a pair of references used in an access by built-in
@@ -202,6 +211,12 @@ struct access_data
   access_mode mode;
 };
 
+class vr_values;
+tree gimple_call_alloc_size (gimple *, wide_int[2] = NULL,
+			     const vr_values * = NULL);
+extern tree compute_objsize (tree, int, access_ref *, const vr_values * = NULL);
+extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL,
+			     const vr_values * = NULL);
 extern bool check_access (tree, tree, tree, tree, tree,
 			  access_mode, const access_data * = NULL);
 
diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index ad2a30fcf71..62ac775dc95 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -17460,10 +17460,10 @@ complete_vars (tree type)
 	      && (TYPE_MAIN_VARIANT (strip_array_types (type))
 		  == iv->incomplete_type))
 	    {
-	      /* Complete the type of the variable.  The VAR_DECL itself
-		 will be laid out in expand_expr.  */
+	      /* Complete the type of the variable.  */
 	      complete_type (type);
 	      cp_apply_type_quals_to_decl (cp_type_quals (type), var);
+	      layout_decl (var, 0);
 	    }
 
 	  /* Remove this entry from the list.  */
diff --git a/gcc/cp/init.c b/gcc/cp/init.c
index e84e985492d..7e7dabb7d70 100644
--- a/gcc/cp/init.c
+++ b/gcc/cp/init.c
@@ -34,6 +34,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "attribs.h"
 #include "asan.h"
 #include "stor-layout.h"
+#include "builtins.h"
 
 static bool begin_init_stmts (tree *, tree *);
 static tree finish_init_stmts (bool, tree, tree);
@@ -2564,27 +2565,6 @@ throw_bad_array_new_length (void)
   return build_cxx_call (fn, 0, NULL, tf_warning_or_error);
 }
 
-/* Attempt to find the initializer for flexible array field T in the
-   initializer INIT, when non-null.  Returns the initializer when
-   successful and NULL otherwise.  */
-static tree
-find_flexarray_init (tree t, tree init)
-{
-  if (!init || init == error_mark_node)
-    return NULL_TREE;
-
-  unsigned HOST_WIDE_INT idx;
-  tree field, elt;
-
-  /* Iterate over all top-level initializer elements.  */
-  FOR_EACH_CONSTRUCTOR_ELT (CONSTRUCTOR_ELTS (init), idx, field, elt)
-    /* If the member T is found, return it.  */
-    if (field == t)
-      return elt;
-
-  return NULL_TREE;
-}
-
 /* Attempt to verify that the argument, OPER, of a placement new expression
    refers to an object sufficiently large for an object of TYPE or an array
    of NELTS of such objects when NELTS is non-null, and issue a warning when
@@ -2601,17 +2581,6 @@ warn_placement_new_too_small (tree type, tree nelts, tree size, tree oper)
 {
   location_t loc = cp_expr_loc_or_input_loc (oper);
 
-  /* The number of bytes to add to or subtract from the size of the provided
-     buffer based on an offset into an array or an array element reference.
-     Although intermediate results may be negative (as in a[3] - 2) a valid
-     final result cannot be.  */
-  offset_int adjust = 0;
-  /* True when the size of the entire destination object should be used
-     to compute the possibly optimistic estimate of the available space.  */
-  bool use_obj_size = false;
-  /* True when the reference to the destination buffer is an ADDR_EXPR.  */
-  bool addr_expr = false;
-
   STRIP_NOPS (oper);
 
   /* Using a function argument or a (non-array) variable as an argument
@@ -2625,231 +2594,91 @@ warn_placement_new_too_small (tree type, tree nelts, tree size, tree oper)
   /* Evaluate any constant expressions.  */
   size = fold_non_dependent_expr (size);
 
-  /* Handle the common case of array + offset expression when the offset
-     is a constant.  */
-  if (TREE_CODE (oper) == POINTER_PLUS_EXPR)
-    {
-      /* If the offset is compile-time constant, use it to compute a more
-	 accurate estimate of the size of the buffer.  Since the operand
-	 of POINTER_PLUS_EXPR is represented as an unsigned type, convert
-	 it to signed first.
-	 Otherwise, use the size of the entire array as an optimistic
-	 estimate (this may lead to false negatives).  */
-      tree adj = TREE_OPERAND (oper, 1);
-      adj = fold_for_warn (adj);
-      if (CONSTANT_CLASS_P (adj))
-	adjust += wi::to_offset (convert (ssizetype, adj));
-      else
-	use_obj_size = true;
-
-      oper = TREE_OPERAND (oper, 0);
-
-      STRIP_NOPS (oper);
-    }
-
-  if (TREE_CODE (oper) == TARGET_EXPR)
-    oper = TREE_OPERAND (oper, 1);
-  else if (TREE_CODE (oper) == ADDR_EXPR)
-    {
-      addr_expr = true;
-      oper = TREE_OPERAND (oper, 0);
-    }
-
-  STRIP_NOPS (oper);
+  access_ref ref;
+  ref.eval = [](tree x){ return fold_non_dependent_expr (x); };
+  ref.trail1special = warn_placement_new < 2;
+  tree objsize =  compute_objsize (oper, 1, &ref);
+  if (!objsize)
+    return;
 
-  if (TREE_CODE (oper) == ARRAY_REF
-      && (addr_expr || TREE_CODE (TREE_TYPE (oper)) == ARRAY_TYPE))
-    {
-      /* Similar to the offset computed above, see if the array index
-	 is a compile-time constant.  If so, and unless the offset was
-	 not a compile-time constant, use the index to determine the
-	 size of the buffer.  Otherwise, use the entire array as
-	 an optimistic estimate of the size.  */
-      const_tree adj = fold_non_dependent_expr (TREE_OPERAND (oper, 1));
-      if (!use_obj_size && CONSTANT_CLASS_P (adj))
-	adjust += wi::to_offset (adj);
-      else
-	{
-	  use_obj_size = true;
-	  adjust = 0;
-	}
+  const bool exact_size = ref.offrng[0] == ref.offrng[1];
 
-      oper = TREE_OPERAND (oper, 0);
-    }
+  offset_int bytes_avail = wi::to_offset (objsize);
+  offset_int bytes_need;
 
-  /* Refers to the declared object that constains the subobject referenced
-     by OPER.  When the object is initialized, makes it possible to determine
-     the actual size of a flexible array member used as the buffer passed
-     as OPER to placement new.  */
-  tree var_decl = NULL_TREE;
-  /* True when operand is a COMPONENT_REF, to distinguish flexible array
-     members from arrays of unspecified size.  */
-  bool compref = TREE_CODE (oper) == COMPONENT_REF;
-
-  /* For COMPONENT_REF (i.e., a struct member) the size of the entire
-     enclosing struct.  Used to validate the adjustment (offset) into
-     an array at the end of a struct.  */
-  offset_int compsize = 0;
-
-  /* Descend into a struct or union to find the member whose address
-     is being used as the argument.  */
-  if (TREE_CODE (oper) == COMPONENT_REF)
+  if (CONSTANT_CLASS_P (size))
+    bytes_need = wi::to_offset (size);
+  else if (nelts && CONSTANT_CLASS_P (nelts))
+    bytes_need = (wi::to_offset (nelts)
+		  * wi::to_offset (TYPE_SIZE_UNIT (type)));
+  else if (tree_fits_uhwi_p (TYPE_SIZE_UNIT (type)))
+    bytes_need = wi::to_offset (TYPE_SIZE_UNIT (type));
+  else
     {
-      tree comptype = TREE_TYPE (TREE_OPERAND (oper, 0));
-      compsize = wi::to_offset (TYPE_SIZE_UNIT (comptype));
-
-      tree op0 = oper;
-      while (TREE_CODE (op0 = TREE_OPERAND (op0, 0)) == COMPONENT_REF);
-      STRIP_ANY_LOCATION_WRAPPER (op0);
-      if (VAR_P (op0))
-	var_decl = op0;
-      oper = TREE_OPERAND (oper, 1);
+      /* The type is a VLA.  */
+      return;
     }
 
-  STRIP_ANY_LOCATION_WRAPPER (oper);
-  tree opertype = TREE_TYPE (oper);
-  if ((addr_expr || !INDIRECT_TYPE_P (opertype))
-      && (VAR_P (oper)
-	  || TREE_CODE (oper) == FIELD_DECL
-	  || TREE_CODE (oper) == PARM_DECL))
-    {
-      /* A possibly optimistic estimate of the number of bytes available
-	 in the destination buffer.  */
-      offset_int bytes_avail = 0;
-      /* True when the estimate above is in fact the exact size
-	 of the destination buffer rather than an estimate.  */
-      bool exact_size = true;
-
-      /* Treat members of unions and members of structs uniformly, even
-	 though the size of a member of a union may be viewed as extending
-	 to the end of the union itself (it is by __builtin_object_size).  */
-      if ((VAR_P (oper) || use_obj_size)
-	  && DECL_SIZE_UNIT (oper)
-	  && tree_fits_uhwi_p (DECL_SIZE_UNIT (oper)))
-	{
-	  /* Use the size of the entire array object when the expression
-	     refers to a variable or its size depends on an expression
-	     that's not a compile-time constant.  */
-	  bytes_avail = wi::to_offset (DECL_SIZE_UNIT (oper));
-	  exact_size = !use_obj_size;
-	}
-      else if (tree opersize = TYPE_SIZE_UNIT (opertype))
-	{
-	  /* Use the size of the type of the destination buffer object
-	     as the optimistic estimate of the available space in it.
-	     Use the maximum possible size for zero-size arrays and
-	     flexible array members (except of initialized objects
-	     thereof).  */
-	  if (TREE_CODE (opersize) == INTEGER_CST)
-	    bytes_avail = wi::to_offset (opersize);
-	}
-
-      if (bytes_avail == 0)
-	{
-	  if (var_decl)
-	    {
-	      /* Constructing into a buffer provided by the flexible array
-		 member of a declared object (which is permitted as a G++
-		 extension).  If the array member has been initialized,
-		 determine its size from the initializer.  Otherwise,
-		 the array size is zero.  */
-	      if (tree init = find_flexarray_init (oper,
-						   DECL_INITIAL (var_decl)))
-		bytes_avail = wi::to_offset (TYPE_SIZE_UNIT (TREE_TYPE (init)));
-	    }
-	  else
-	    bytes_avail = (wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node))
-			   - compsize);
-	}
-
-      tree_code oper_code = TREE_CODE (opertype);
-
-      if (compref && oper_code == ARRAY_TYPE)
-	{
-	  tree nelts = array_type_nelts_top (opertype);
-	  tree nelts_cst = maybe_constant_value (nelts);
-	  if (TREE_CODE (nelts_cst) == INTEGER_CST
-	      && integer_onep (nelts_cst)
-	      && !var_decl
-	      && warn_placement_new < 2)
-	    return;
-	}
-
-      /* Reduce the size of the buffer by the adjustment computed above
-	 from the offset and/or the index into the array.  */
-      if (bytes_avail < adjust || adjust < 0)
-	bytes_avail = 0;
-      else
-	{
-	  tree elttype = (TREE_CODE (opertype) == ARRAY_TYPE
-			  ? TREE_TYPE (opertype) : opertype);
-	  if (tree eltsize = TYPE_SIZE_UNIT (elttype))
-	    {
-	      bytes_avail -= adjust * wi::to_offset (eltsize);
-	      if (bytes_avail < 0)
-		bytes_avail = 0;
-	    }
-	}
-
-      /* The minimum amount of space needed for the allocation.  This
-	 is an optimistic estimate that makes it possible to detect
-	 placement new invocation for some undersize buffers but not
-	 others.  */
-      offset_int bytes_need;
+  if (bytes_avail >= bytes_need)
+    return;
 
-      if (nelts)
-	nelts = fold_for_warn (nelts);
-
-      if (CONSTANT_CLASS_P (size))
-	bytes_need = wi::to_offset (size);
-      else if (nelts && CONSTANT_CLASS_P (nelts))
-	bytes_need = (wi::to_offset (nelts)
-		      * wi::to_offset (TYPE_SIZE_UNIT (type)));
-      else if (tree_fits_uhwi_p (TYPE_SIZE_UNIT (type)))
-	bytes_need = wi::to_offset (TYPE_SIZE_UNIT (type));
-      else
-	{
-	  /* The type is a VLA.  */
-	  return;
-	}
+  tree opertype = ref.ref ? TREE_TYPE (ref.ref) : TREE_TYPE (oper);
+  bool warned = false;
+  if (nelts)
+    nelts = fold_for_warn (nelts);
+  if (nelts)
+    if (CONSTANT_CLASS_P (nelts))
+      warned = warning_at (loc, OPT_Wplacement_new_,
+			   (exact_size
+			    ? G_("placement new constructing an object "
+				 "of type %<%T [%wu]%> and size %qwu "
+				 "in a region of type %qT and size %qwi")
+			    : G_("placement new constructing an object "
+				 "of type %<%T [%wu]%> and size %qwu "
+				 "in a region of type %qT and size "
+				 "at most %qwu")),
+			   type, tree_to_uhwi (nelts),
+			   bytes_need.to_uhwi (),
+			   opertype, bytes_avail.to_uhwi ());
+    else
+      warned = warning_at (loc, OPT_Wplacement_new_,
+			   (exact_size
+			    ? G_("placement new constructing an array "
+				 "of objects of type %qT and size %qwu "
+				 "in a region of type %qT and size %qwi")
+			    : G_("placement new constructing an array "
+				 "of objects of type %qT and size %qwu "
+				 "in a region of type %qT and size "
+				 "at most %qwu")),
+			   type, bytes_need.to_uhwi (), opertype,
+			   bytes_avail.to_uhwi ());
+  else
+    warned = warning_at (loc, OPT_Wplacement_new_,
+			 (exact_size
+			  ? G_("placement new constructing an object "
+			       "of type %qT and size %qwu in a region "
+			       "of type %qT and size %qwi")
+			  : G_("placement new constructing an object "
+			       "of type %qT "
+			       "and size %qwu in a region of type %qT "
+			       "and size at most %qwu")),
+			       type, bytes_need.to_uhwi (), opertype,
+			 bytes_avail.to_uhwi ());
+
+  if (!warned || !ref.ref)
+    return;
 
-      if (bytes_avail < bytes_need)
-	{
-	  if (nelts)
-	    if (CONSTANT_CLASS_P (nelts))
-	      warning_at (loc, OPT_Wplacement_new_,
-			  exact_size ?
-			  "placement new constructing an object of type "
-			  "%<%T [%wu]%> and size %qwu in a region of type %qT "
-			  "and size %qwi"
-			  : "placement new constructing an object of type "
-			  "%<%T [%wu]%> and size %qwu in a region of type %qT "
-			  "and size at most %qwu",
-			  type, tree_to_uhwi (nelts), bytes_need.to_uhwi (),
-			  opertype, bytes_avail.to_uhwi ());
-	    else
-	      warning_at (loc, OPT_Wplacement_new_,
-			  exact_size ?
-			  "placement new constructing an array of objects "
-			  "of type %qT and size %qwu in a region of type %qT "
-			  "and size %qwi"
-			  : "placement new constructing an array of objects "
-			  "of type %qT and size %qwu in a region of type %qT "
-			  "and size at most %qwu",
-			  type, bytes_need.to_uhwi (), opertype,
-			  bytes_avail.to_uhwi ());
-	  else
-	    warning_at (loc, OPT_Wplacement_new_,
-			exact_size ?
-			"placement new constructing an object of type %qT "
-			"and size %qwu in a region of type %qT and size %qwi"
-			: "placement new constructing an object of type %qT "
-			"and size %qwu in a region of type %qT and size "
-			"at most %qwu",
-			type, bytes_need.to_uhwi (), opertype,
-			bytes_avail.to_uhwi ());
-	}
-    }
+  if (ref.offset_zero () || !ref.offset_bounded ())
+    inform (DECL_SOURCE_LOCATION (ref.ref),
+	    "%qD declared here", ref.ref);
+  else if (ref.offrng[0] == ref.offrng[1])
+    inform (DECL_SOURCE_LOCATION (ref.ref),
+	    "at offset %wi from %qD declared here",
+	    ref.offrng[0].to_shwi (), ref.ref);
+  else
+    inform (DECL_SOURCE_LOCATION (ref.ref),
+	    "at offset [%wi, %wi] from %qD declared here",
+	    ref.offrng[0].to_shwi (), ref.offrng[1].to_shwi (), ref.ref);
 }
 
 /* True if alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__.  */
diff --git a/gcc/gimple-array-bounds.cc b/gcc/gimple-array-bounds.cc
index c2dd6663c3a..fd4eb443f3e 100644
--- a/gcc/gimple-array-bounds.cc
+++ b/gcc/gimple-array-bounds.cc
@@ -68,7 +68,7 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
   tree decl = NULL_TREE;
 
   /* Set for accesses to interior zero-length arrays.  */
-  bool interior_zero_len = false;
+  special_array_member sam{ };
 
   tree up_bound_p1;
 
@@ -101,7 +101,7 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
 	    {
 	      /* Try to determine the size of the trailing array from
 		 its initializer (if it has one).  */
-	      if (tree refsize = component_ref_size (arg, &interior_zero_len))
+	      if (tree refsize = component_ref_size (arg, &sam))
 		if (TREE_CODE (refsize) == INTEGER_CST)
 		  maxbound = refsize;
 	    }
@@ -199,7 +199,7 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
 			 "array subscript %E is below array bounds of %qT",
 			 low_sub, artype);
 
-  if (!warned && interior_zero_len)
+  if (!warned && sam == special_array_member::int_0)
     warned = warning_at (location, OPT_Wzero_length_bounds,
 			 (TREE_CODE (low_sub) == INTEGER_CST
 			  ? G_("array subscript %E is outside the bounds "
diff --git a/gcc/testsuite/g++.dg/init/strlen.C b/gcc/testsuite/g++.dg/init/strlen.C
index aa8950e2dc0..cc650d65dbe 100644
--- a/gcc/testsuite/g++.dg/init/strlen.C
+++ b/gcc/testsuite/g++.dg/init/strlen.C
@@ -29,7 +29,7 @@ test_dynamic_type (S *p)
   // distinguish invalid cases from ones like it that might be valid.
   // If/when GIMPLE changes to make this possible this test can be
   // removed.
-  char *q = new (p->a) char [16];
+  char *q = new (p->a) char [16];   // { dg-warning "\\\[-Wplacement-new" }
 
   init (q);
 
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C
index d2ec608afd4..cec83163dbe 100644
--- a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C
@@ -66,8 +66,9 @@ struct BA2 { int i; A2 a2; };
 void fBx (BAx *pbx, BAx &rbx)
 {
   BAx bax;
-  new (bax.ax.a) char;     // { dg-warning "placement" }
-  new (bax.ax.a) Int16;    // { dg-warning "placement" }
+  // The uninitialized flexible array takes up the bytes of padding.
+  new (bax.ax.a) char;
+  new (bax.ax.a) Int16;
   new (bax.ax.a) Int32;    // { dg-warning "placement" }
 
   new (pbx->ax.a) char;
@@ -84,9 +85,12 @@ void fBx1 ()
 {
   static BAx bax1 = { 1, /* Ax = */ { 2, /* a[] = */ {} } };
 
-  new (bax1.ax.a) char;	    // { dg-warning "placement" }
-  new (bax1.ax.a) char[2];  // { dg-warning "placement" }
-  new (bax1.ax.a) Int16;    // { dg-warning "placement" }
+  // The empty flexible array takes up the bytes of padding.
+  new (bax1.ax.a) char;
+  new (bax1.ax.a) char[2];
+  new (bax1.ax.a) Int16;
+  new (bax1.ax.a) char[3];
+  new (bax1.ax.a) char[4];  // { dg-warning "placement" }
   new (bax1.ax.a) Int32;    // { dg-warning "placement" }
 }
 
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C
index e00515eeaa9..e5fdfe1f603 100644
--- a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C
@@ -124,9 +124,13 @@ struct BA2 { int i; A2 a2; };
 void fBx (BAx *pbx, BAx &rbx)
 {
   BAx bax;
-  new (bax.ax.a) char;        // { dg-warning "placement" }
-  new (bax.ax.a) Int16;       // { dg-warning "placement" }
+  // The uninitialized flexible array takes up the bytes of padding.
+  new (bax.ax.a) char;
+  new (bax.ax.a) Int16;
+  new (bax.ax.a) char[3];
   new (bax.ax.a) Int32;       // { dg-warning "placement" }
+  new (bax.ax.a) char[4];     // { dg-warning "placement" }
+  new (bax.ax.a) char[5];     // { dg-warning "placement" }
 
   new (pbx->ax.a) char;
   new (rbx.ax.a) char;
@@ -142,10 +146,14 @@ void fBx1 ()
 {
   static BAx bax1 = { 1, /* Ax = */ { 2, /* a[] = */ {} } };
 
-  new (bax1.ax.a) char;	      // { dg-warning "placement" }
-  new (bax1.ax.a) char[2];    // { dg-warning "placement" }
-  new (bax1.ax.a) Int16;      // { dg-warning "placement" }
+  // The empty flexible array takes up the bytes of padding.
+  new (bax1.ax.a) char;
+  new (bax1.ax.a) char[2];
+  new (bax1.ax.a) Int16;
+  new (bax1.ax.a) char[3];
   new (bax1.ax.a) Int32;      // { dg-warning "placement" }
+  new (bax1.ax.a) char[4];    // { dg-warning "placement" }
+  new (bax1.ax.a) char[5];    // { dg-warning "placement" }
 }
 
 void fB0 (BA0 *pb0, BA0 &rb0)
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C
index b6a72b18f6a..5eb63d23b47 100644
--- a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C
@@ -17,9 +17,10 @@ void fBx1 ()
 {
   static BAx bax1 = { 1, /* Ax = */ { 2, /* a[] = */ { 3 } } }; // { dg-error "initialization of flexible array member in a nested context" }
 
-  new (bax1.ax.a) char;     // { dg-warning "placement" }
-  new (bax1.ax.a) char[2];  // { dg-warning "placement" }
-  new (bax1.ax.a) Int16;    // { dg-warning "placement" }
+  // The first three bytes of the flexible array member live in the padding.
+  new (bax1.ax.a) char;
+  new (bax1.ax.a) char[2];
+  new (bax1.ax.a) Int16;
   new (bax1.ax.a) Int32;    // { dg-warning "placement" }
 }
 
@@ -27,10 +28,11 @@ void fBx2 ()
 {
   static BAx bax2 = { 1, /* Ax = */ { 2, /* a[] = */ { 3, 4 } } }; // { dg-error "initialization of flexible array member in a nested context" }
 
-  new (bax2.ax.a) char;       // { dg-warning "placement" }
-  new (bax2.ax.a) char[2];    // { dg-warning "placement" }
-  new (bax2.ax.a) char[3];    // { dg-warning "placement" }
-  new (bax2.ax.a) Int16;      // { dg-warning "placement" }
+  // The first three bytes of the flexible array member live in the padding.
+  new (bax2.ax.a) char;
+  new (bax2.ax.a) char[2];
+  new (bax2.ax.a) char[3];
+  new (bax2.ax.a) Int16;
   new (bax2.ax.a) char[4];    // { dg-warning "placement" }
   new (bax2.ax.a) Int32;      // { dg-warning "placement" }
 }
@@ -39,10 +41,11 @@ void fBx3 ()
 {
   static BAx bax2 = { 1, /* Ax = */ { 3, /* a[] = */ { 4, 5, 6 } } }; // { dg-error "initialization of flexible array member in a nested context" }
 
-  new (bax2.ax.a) char;       // { dg-warning "placement" }
-  new (bax2.ax.a) char[2];    // { dg-warning "placement" }
-  new (bax2.ax.a) Int16;      // { dg-warning "placement" }
-  new (bax2.ax.a) char[3];    // { dg-warning "placement" }
+  // The first three bytes of the flexible array member live in the padding.
+  new (bax2.ax.a) char;
+  new (bax2.ax.a) char[2];
+  new (bax2.ax.a) Int16;
+  new (bax2.ax.a) char[3];
   new (bax2.ax.a) char[4];    // { dg-warning "placement" }
   new (bax2.ax.a) Int32;      // { dg-warning "placement" }
 }
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-7.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-7.C
new file mode 100644
index 00000000000..82f298d8008
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-7.C
@@ -0,0 +1,82 @@
+/* PR c++/96511 - Incorrect -Wplacement-new on POINTER_PLUS into an array
+   with 4-byte elements
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+typedef __INT16_TYPE__ int16_t;
+typedef __INT32_TYPE__ int32_t;
+typedef __SIZE_TYPE__  size_t;
+
+void* operator new (size_t, void *p) { return p; }
+
+void test_a1_int16 ()
+{
+  int16_t a3[3];                    // { dg-message "declared here" }
+
+  new (a3) int16_t;
+  new (a3 + 1) int16_t;
+  new (a3 + 2) int16_t;             // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[1]) int16_t;
+  new (&a3[0] + 1) int16_t;
+  new (&a3[0] + 2) int16_t;         // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[0] + 3) int16_t;         // { dg-warning "\\\[-Wplacement-new" }
+}
+
+void test_a1_int32 ()
+{
+  int16_t a3[3];
+
+  new (a3 + 1) int32_t;             // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[1]) int32_t;
+  new (&a3[0] + 1) int32_t;         // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[0] + 2) int32_t;         // { dg-warning "\\\[-Wplacement-new" }
+}
+
+
+void test_a2 ()
+{
+  int16_t a23[2][3];
+
+  new (a23 + 1) int16_t;            // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a23[1]) int16_t;
+  new (&a23[2]) int16_t;            // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[0][0] + 1) int16_t;
+  new (&a23[0][0] + 2) int16_t;
+  // Deriving a pointer to the next array from one to an element of
+  // the prior array isn't valid even if the resulting pointer points
+  // to an element of the larger array.  Verify it's diagnosed.
+  new (&a23[0][0] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][0] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][0] + 5) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][0] + 6) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[0][1] + 1) int16_t;
+  new (&a23[0][1] + 2) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 5) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 6) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[0][2] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 2) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 5) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 6) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[1][0]) int16_t;
+  new (&a23[1][0] + 1) int16_t;
+  new (&a23[1][0] + 2) int16_t;
+  new (&a23[1][0] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[1][0] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[1][1]) int16_t;
+  new (&a23[1][2]) int16_t;
+  new (&a23[1][2] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[1][3]) int16_t;         // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[1][3] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[2][0]) int16_t;         // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[2][0] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+}
diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-58.c b/gcc/testsuite/gcc.dg/Warray-bounds-58.c
index 7c469e2aefc..849457e559f 100644
--- a/gcc/testsuite/gcc.dg/Warray-bounds-58.c
+++ b/gcc/testsuite/gcc.dg/Warray-bounds-58.c
@@ -1,5 +1,5 @@
 /* { dg-do compile }
-   { dg-options "-O2 -Wall" } */
+   { dg-options "-O2 -Wall -Wno-stringop-overread" } */
 
 typedef __SIZE_TYPE__ size_t;
 
@@ -15,7 +15,7 @@ void fa0_extern (void)
 {
   sink (strlen (ea0.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
   sink (strlen (ea0.a - 1));    // { dg-warning "\\\[-Warray-bounds" "pr93514" { xfail *-*-* } }
-  sink (strlen (ea0.a));        // { dg-warning "\\\[-Wstringop-overread" "pr93514" }
+  sink (strlen (ea0.a));        // valid just-past-the-end offset
   sink (strlen (ea0.a + 1));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 }
 
@@ -25,7 +25,7 @@ void fa0_static (void)
 {
   sink (strlen (sa0.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
   sink (strlen (sa0.a - 1));    // { dg-warning "\\\[-Warray-bounds" "pr93514" { xfail *-*-* } }
-  sink (strlen (sa0.a));        // { dg-warning "\\\[-Wstringop-overread" "pr93514" }
+  sink (strlen (sa0.a));        // valid just-past-the-end offset
   sink (strlen (sa0.a + 1));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 }
 
@@ -52,14 +52,14 @@ void fax_static (void)
   sink (strlen (ax0.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
   sink (strlen (ax0.a - 1));    // { dg-warning "\\\[-Warray-bounds" "pr93514" { xfail *-*-* } }
   sink (strlen (ax0.a));
-  sink (strlen (ax0.a + 1));    // { dg-warning "\\\[-Wstringop-overread" "pr93514" }
+  sink (strlen (ax0.a + 1));    // valid just-past-the-end offset
   sink (strlen (ax0.a + 2));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 
   sink (strlen (ax1.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
   sink (strlen (ax1.a - 1));    // { dg-warning "\\\[-Warray-bounds" "pr93514" { xfail *-*-* } }
   sink (strlen (ax1.a));
   sink (strlen (ax1.a + 1));
-  sink (strlen (ax1.a + 2));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" "pr93514" }
+  sink (strlen (ax1.a + 2));    // valid just-past-the-end offset
   sink (strlen (ax1.a + 3));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 
   sink (strlen (ax2.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
@@ -67,7 +67,7 @@ void fax_static (void)
   sink (strlen (ax2.a));
   sink (strlen (ax2.a + 1));
   sink (strlen (ax2.a + 2));
-  sink (strlen (ax2.a + 3));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" "pr93514" }
+  sink (strlen (ax2.a + 3));    // valid just-past-the-end offset
   sink (strlen (ax2.a + 4));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 
   sink (strlen (ax3.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
@@ -76,6 +76,6 @@ void fax_static (void)
   sink (strlen (ax3.a + 1));
   sink (strlen (ax3.a + 2));
   sink (strlen (ax3.a + 3));
-  sink (strlen (ax3.a + 4));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" "pr93514" }
+  sink (strlen (ax3.a + 4));    // valid just-past-the-end offset
   sink (strlen (ax3.a + 5));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 }
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c
index 339f904d7a6..46f8fed79f3 100644
--- a/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c
@@ -184,6 +184,18 @@ void test_note (const char *s)
     sink (a);
   }
 
+  {
+    char a[1][1][2];                    // { dg-message "at offset 2 into " }
+    strncpy (a[0][1], s, 3);            // { dg-warning "writing 3 bytes into a region of size 0 " }
+    sink (a);
+  }
+
+  {
+    char a[1][2][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 " }
@@ -192,7 +204,13 @@ void test_note (const char *s)
 
   {
     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 " }
+    strncpy (a[1][0], s, 3);            // { dg-warning "writing 3 bytes into a region of size 0 " }
+    sink (a);
+  }
+
+  {
+    char a[2][1][2];                    // { dg-message "at offset 2 into " }
+    strncpy (a[0][1], s, 3);            // { dg-warning "writing 3 bytes into a region of size 0 " }
     sink (a);
   }
 
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-40.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-40.c
new file mode 100644
index 00000000000..cd8fa3202eb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-40.c
@@ -0,0 +1,116 @@
+/* PR middle-end/96384 - bogus -Wstringop-overflow= storing into
+   multidimensional array with index in range
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+#define SHRT_MAX   __SHRT_MAX__
+#define SHRT_MIN   (-SHRT_MAX - 1)
+#define INT_MAX    __INT_MAX__
+#define INT_MIN    (-INT_MAX - 1)
+#define LONG_MAX   __LONG_MAX__
+#define LONG_MIN   (-LONG_MAX - 1)
+
+#define USHRT_MAX  (SHRT_MAX * 2 + 1)
+#define UINT_MAX   ~0U
+#define ULONG_MAX  ~0LU
+
+char ca3_5_7[3][5][7];
+
+void nowarn_ca_3_5_ssi (short i)
+{
+  if (i > SHRT_MAX - 1)
+    i = SHRT_MAX - 1;
+
+  ca3_5_7[i][0][0] = __LINE__;
+  ca3_5_7[i][0][1] = __LINE__;
+  ca3_5_7[i][0][2] = __LINE__;
+  ca3_5_7[i][0][3] = __LINE__;
+  ca3_5_7[i][0][4] = __LINE__;
+  ca3_5_7[i][0][5] = __LINE__;
+  ca3_5_7[i][0][6] = __LINE__;
+
+  ca3_5_7[i][1][0] = __LINE__;
+  ca3_5_7[i][1][1] = __LINE__;
+  ca3_5_7[i][1][2] = __LINE__;
+  ca3_5_7[i][1][3] = __LINE__;
+  ca3_5_7[i][1][4] = __LINE__;
+  ca3_5_7[i][1][5] = __LINE__;
+  ca3_5_7[i][1][6] = __LINE__;
+
+  ca3_5_7[i][2][0] = __LINE__;
+  ca3_5_7[i][2][1] = __LINE__;
+  ca3_5_7[i][2][2] = __LINE__;
+  ca3_5_7[i][2][3] = __LINE__;
+  ca3_5_7[i][2][4] = __LINE__;
+  ca3_5_7[i][2][5] = __LINE__;
+  ca3_5_7[i][2][6] = __LINE__;
+
+  ca3_5_7[i][3][0] = __LINE__;
+  ca3_5_7[i][3][1] = __LINE__;
+  ca3_5_7[i][3][2] = __LINE__;
+  ca3_5_7[i][3][3] = __LINE__;
+  ca3_5_7[i][3][4] = __LINE__;
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[i][3][6] = __LINE__;
+
+  ca3_5_7[i][4][0] = __LINE__;
+  ca3_5_7[i][4][1] = __LINE__;
+  ca3_5_7[i][4][2] = __LINE__;
+  ca3_5_7[i][4][3] = __LINE__;
+  ca3_5_7[i][4][4] = __LINE__;
+  ca3_5_7[i][4][5] = __LINE__;
+  ca3_5_7[i][4][6] = __LINE__;
+
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_usi (unsigned short i)
+{
+  if (i > USHRT_MAX - 1)
+    i = USHRT_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_si (int i)
+{
+  if (i > INT_MAX - 1)
+    i = INT_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_ui (unsigned i)
+{
+  if (i > UINT_MAX - 1)
+    i = UINT_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_li (long i)
+{
+  if (i > LONG_MAX - 1)
+    i = LONG_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_uli (unsigned long i)
+{
+  if (i > ULONG_MAX - 1)
+    i = ULONG_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
diff --git a/gcc/tree.c b/gcc/tree.c
index 45aacadbe2d..5fb94e6b24f 100644
--- a/gcc/tree.c
+++ b/gcc/tree.c
@@ -13638,20 +13638,21 @@ get_initializer_for (tree init, tree decl)
 /* Determines the size of the member referenced by the COMPONENT_REF
    REF, using its initializer expression if necessary in order to
    determine the size of an initialized flexible array member.
-   If non-null, *INTERIOR_ZERO_LENGTH is set when REF refers to
-   an interior zero-length array.
+   If non-null, set *ARK when REF refers to an interior zero-length
+   array or a trailing one-element array.
    Returns the size as sizetype (which might be zero for an object
    with an uninitialized flexible array member) or null if the size
    cannot be determined.  */
 
 tree
-component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
+component_ref_size (tree ref, special_array_member *sam /* = NULL */)
 {
   gcc_assert (TREE_CODE (ref) == COMPONENT_REF);
 
-  bool int_0_len = false;
-  if (!interior_zero_length)
-    interior_zero_length = &int_0_len;
+  special_array_member arkbuf;
+  if (!sam)
+    sam = &arkbuf;
+  *sam = special_array_member::none;
 
   /* The object/argument referenced by the COMPONENT_REF and its type.  */
   tree arg = TREE_OPERAND (ref, 0);
@@ -13673,9 +13674,16 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
 	   more than one element.  */
 	return memsize;
 
-      *interior_zero_length = zero_length && !trailing;
-      if (*interior_zero_length)
-	memsize = NULL_TREE;
+      if (zero_length)
+	{
+	  if (trailing)
+	    *sam = special_array_member::trail_0;
+	  else
+	    {
+	      *sam = special_array_member::int_0;
+	      memsize = NULL_TREE;
+	    }
+	}
 
       if (!zero_length)
 	if (tree dom = TYPE_DOMAIN (memtype))
@@ -13686,9 +13694,13 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
 		{
 		  offset_int minidx = wi::to_offset (min);
 		  offset_int maxidx = wi::to_offset (max);
-		  if (maxidx - minidx > 0)
+		  offset_int neltsm1 = maxidx - minidx;
+		  if (neltsm1 > 0)
 		    /* MEMBER is an array with more than one element.  */
 		    return memsize;
+
+		  if (neltsm1 == 0)
+		    *sam = special_array_member::trail_1;
 		}
 
       /* For a refernce to a zero- or one-element array member of a union
@@ -13706,7 +13718,7 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
   tree base = get_addr_base_and_unit_offset (ref, &baseoff);
   if (!base || !VAR_P (base))
     {
-      if (!*interior_zero_length)
+      if (*sam != special_array_member::int_0)
 	return NULL_TREE;
 
       if (TREE_CODE (arg) != COMPONENT_REF)
@@ -13726,7 +13738,7 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
   /* Determine the base type of the referenced object.  If it's
      the same as ARGTYPE and MEMBER has a known size, return it.  */
   tree bt = basetype;
-  if (!*interior_zero_length)
+  if (*sam != special_array_member::int_0)
     while (TREE_CODE (bt) == ARRAY_TYPE)
       bt = TREE_TYPE (bt);
   bool typematch = useless_type_conversion_p (argtype, bt);
@@ -13766,7 +13778,7 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
 	  if (DECL_P (base)
 	      && DECL_EXTERNAL (base)
 	      && bt == basetype
-	      && !*interior_zero_length)
+	      && *sam != special_array_member::int_0)
 	    /* The size of a flexible array member of an extern struct
 	       with no initializer cannot be determined (it's defined
 	       in another translation unit and can have an initializer
diff --git a/gcc/tree.h b/gcc/tree.h
index 9ec24a3008b..afdb6ea0336 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -5284,12 +5284,22 @@ extern bool array_at_struct_end_p (tree);
    by EXP.  This does not include any offset in DECL_FIELD_BIT_OFFSET.  */
 extern tree component_ref_field_offset (tree);
 
+/* Describes a "special" array member due to which component_ref_size
+   returns null.  */
+enum struct special_array_member
+  {
+   none,      /* Not a special array member.  */
+   int_0,     /* Interior array member with size zero.  */
+   trail_0,   /* Trailing array member with size zero.  */
+   trail_1    /* Trailing array member with one element.  */
+  };
+
 /* Return the size of the member referenced by the COMPONENT_REF, using
    its initializer expression if necessary in order to determine the size
    of an initialized flexible array member.  The size might be zero for
    an object with an uninitialized flexible array member or null if it
    cannot be determined.  */
-extern tree component_ref_size (tree, bool * = NULL);
+extern tree component_ref_size (tree, special_array_member * = NULL);
 
 extern int tree_map_base_eq (const void *, const void *);
 extern unsigned int tree_map_base_hash (const void *);

[-- Attachment #3: gcc-compute_objsize.diff --]
[-- Type: text/x-patch, Size: 57824 bytes --]

Generalize compute_objsize to return maximum size/offset instead of failing.

Also resolves:
PR middle-end/97023 - missing warning on buffer overflow in chained mempcpy

gcc/ChangeLog:

	PR middle-end/97023
	* builtins.c (access_ref::access_ref): Initialize new member.  Use
	new enum.
	(access_ref::size_remaining): Define new member function.
	(inform_access): Handle expressions referencing objects.
	(gimple_call_alloc_size): Call get_size_range instead of get_range.
	(gimple_call_return_array): New function.
	(get_range): Update comment.
	(compute_objsize): Set maximum size or offset instead of failing
	for unknown objects and handle more kinds of expressions.
	(compute_objsize): Call access_ref::size_remaining.
	(compute_objsize): Have transitional wrapper fail for pointers
	into unknown objects.
	(expand_builtin_strncmp): Call access_ref::size_remaining and
	handle new cases.
	* builtins.h (access_ref::size_remaining): Declare new member function.
	(access_ref::set_max_size_range): Define new member function.
	(access_ref::add_ofset, access_ref::add_max_ofset): Same.
	(access_ref::add_base0): New data member.
	* calls.c (get_size_range): Change argument type.  Handle new
	condition.
	* calls.h (get_size_range): Adjust signature.
	(enum size_range_flags): Define new type.

gcc/testsuite/ChangeLog:

	PR middle-end/97023
	* c-c++-common/Wrestrict.c: Add new case, expect warning for existing.
	* gcc.dg/pr51683.c: Prune out expected warning.
	* gcc.dg/Wstringop-overflow-41.c: New test.
	* gcc.dg/Wstringop-overflow-44.c: New test.
	* gcc.dg/Wstringop-overflow-45.c: New test.
	* gcc.dg/Wstringop-overflow-46.c: New test.
	* gcc.dg/Wstringop-overflow-47.c: New test.
	* gcc.dg/Wstringop-overflow-49.c: New test.
	* gcc.dg/Wstringop-overflow-50.c: New test.
	* gcc.dg/Wstringop-overflow-51.c: New test.
	* gcc.dg/Wstringop-overflow-52.c: New test.

commit 7b10cb93b9bf5c38f4805767957506ca94a32fae
Author: Martin Sebor <msebor@redhat.com>
Date:   Fri Sep 11 16:39:05 2020 -0600

    compute_objsize generalization.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 611cf7f1f4b..9e5e6ce10da 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -199,7 +199,7 @@ static void expand_builtin_sync_synchronize (void);
 
 access_ref::access_ref (tree bound /* = NULL_TREE */,
 			bool minaccess /* = false */)
-: ref (), eval ([](tree x){ return x; }), trail1special (true)
+: ref (), eval ([](tree x){ return x; }), trail1special (true), base0 (true)
 {
   /* Set to valid.  */
   offrng[0] = offrng[1] = 0;
@@ -214,7 +214,7 @@ access_ref::access_ref (tree bound /* = NULL_TREE */,
      set the bounds of the access to reflect both it and MINACCESS.
      BNDRNG[0] is the size of the minimum access.  */
   tree rng[2];
-  if (bound && get_size_range (bound, rng, true))
+  if (bound && get_size_range (bound, rng, SR_ALLOW_ZERO))
     {
       bndrng[0] = wi::to_offset (rng[0]);
       bndrng[1] = wi::to_offset (rng[1]);
@@ -222,6 +222,83 @@ access_ref::access_ref (tree bound /* = NULL_TREE */,
     }
 }
 
+offset_int
+access_ref::size_remaining (offset_int *pmin /* = NULL */) const
+{
+  offset_int minbuf;
+  if (!pmin)
+    pmin = &minbuf;
+
+  if (base0)
+    {
+      /* The offset into referenced object is zero-based (i.e., it's
+	 not referenced by a pointer into middle of some unknown object).  */
+      if (offrng[0] < 0 && offrng[1] < 0)
+	{
+	  /* If the offset is negative the remaining size is zero.  */
+	  *pmin = 0;
+	  return 0;
+	}
+
+      if (offrng[1] < offrng[0])
+	{
+	  if (offrng[1] < 0)
+	    {
+	      *pmin = 0;
+	      return offrng[0] < sizrng[1] ? sizrng[1] - offrng[0] : 0;
+	    }
+
+	  *pmin = offrng[0] < sizrng[1] ? sizrng[1] - offrng[0] : 0;
+	  return sizrng[1];
+	}
+
+      if (sizrng[1] <= offrng[0])
+	{
+	  /* If the starting offset is greater than the upper bound on
+	     the size of the object the space remaining is zero.  */
+	  *pmin = 0;
+	  return 0;
+	}
+
+      /* Otherwise return the size minus the lower bound of the offset.  */
+      offset_int or0 = offrng[0] < 0 ? 0 : offrng[0];
+
+      *pmin = sizrng[0] - or0;
+      return sizrng[1] - or0;
+    }
+
+  /* The offset to the referenced object isn't zero-based (i.e., it may
+     refer to a byte other than the first.  The size of such an object
+     is constrained only by the size of the address space (the result
+     of max_object_size()).  */
+  if (offrng[1] < offrng[0])
+    {
+      if (offrng[1] < 0)
+	{
+	  *pmin = sizrng[0];
+	  return sizrng[1];
+	}
+
+      if (offrng[0] < sizrng[1])
+	*pmin = offrng[0] < 0 ? sizrng[1] : sizrng[1] - offrng[0];
+      else
+	*pmin = 0;
+
+      return sizrng[1];
+    }
+
+  if (sizrng[1] <= offrng[0])
+    {
+      *pmin = 0;
+      return 0;
+    }
+
+  offset_int or0 = offrng[0] < 0 ? 0 : offrng[0];
+
+  *pmin = sizrng[0] - or0;
+  return sizrng[1] - or0;
+}
+
 /* Return true if NAME starts with __builtin_ or __sync_.  */
 
 static bool
@@ -3634,23 +3711,27 @@ inform_access (const access_ref &ref, access_mode mode)
 	sprintf (sizestr, "[%llu, %llu]", minsize, maxsize);
 
     }
-  else
+  else if (DECL_P (ref.ref))
     loc = DECL_SOURCE_LOCATION (ref.ref);
+  else if (EXPR_P (ref.ref) && EXPR_HAS_LOCATION (ref.ref))
+    loc = EXPR_LOCATION (ref.ref);
+  else
+    return;
 
   if (mode == access_read_write || mode == access_write_only)
     {
-      if (DECL_P (ref.ref))
+      if (allocfn == NULL_TREE)
 	{
 	  if (minoff == maxoff)
 	    {
 	      if (minoff == 0)
-		inform (loc, "destination object %qD", ref.ref);
+		inform (loc, "destination object %qE", ref.ref);
 	      else
-		inform (loc, "at offset %lli into destination object %qD",
+		inform (loc, "at offset %lli into destination object %qE",
 			minoff, ref.ref);
 	    }
 	  else
-	    inform (loc, "at offset [%lli, %lli] into destination object %qD",
+	    inform (loc, "at offset [%lli, %lli] into destination object %qE",
 		    minoff, maxoff, ref.ref);
 	  return;
 	}
@@ -4121,8 +4202,14 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
   if (!rng1)
     rng1 = rng1_buf;
 
-  if (!get_range (size, rng1, rvals))
-    return NULL_TREE;
+  {
+    tree r[2];
+    /* Determine the largest valid range size, including zero.  */
+    if (!get_size_range (size, r, SR_ALLOW_ZERO | SR_USE_LARGEST))
+      return NULL_TREE;
+    rng1[0] = wi::to_wide (r[0]);
+    rng1[1] = wi::to_wide (r[1]);
+  }
 
   if (argidx2 > nargs && TREE_CODE (size) == INTEGER_CST)
     return fold_convert (sizetype, size);
@@ -4195,6 +4282,82 @@ get_range (tree x, signop sgn, offset_int r[2],
   return true;
 }
 
+/* Return the argument that the call STMT to a built-in function returns
+   or null if it doesn't.  On success, set OFFRNG[] to the range of offsets
+   from the argument reflected in the value returned by the built-in if it
+   can be determined, otherwise to 0 and HWI_M1U respectively.  */
+
+static tree
+gimple_call_return_array (gimple *stmt, offset_int offrng[2])
+{
+  if (!gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)
+      || gimple_call_num_args (stmt) < 1)
+    return NULL_TREE;
+
+  tree fn = gimple_call_fndecl (stmt);
+  switch (DECL_FUNCTION_CODE (fn))
+    {
+    case BUILT_IN_MEMCPY:
+    case BUILT_IN_MEMCPY_CHK:
+    case BUILT_IN_MEMMOVE:
+    case BUILT_IN_MEMMOVE_CHK:
+    case BUILT_IN_MEMSET:
+    case BUILT_IN_STPCPY:
+    case BUILT_IN_STPCPY_CHK:
+    case BUILT_IN_STPNCPY:
+    case BUILT_IN_STPNCPY_CHK:
+    case BUILT_IN_STRCAT:
+    case BUILT_IN_STRCAT_CHK:
+    case BUILT_IN_STRCPY:
+    case BUILT_IN_STRCPY_CHK:
+    case BUILT_IN_STRNCAT:
+    case BUILT_IN_STRNCAT_CHK:
+    case BUILT_IN_STRNCPY:
+    case BUILT_IN_STRNCPY_CHK:
+      offrng[0] = offrng[1] = 0;
+      return gimple_call_arg (stmt, 0);
+
+    case BUILT_IN_MEMPCPY:
+    case BUILT_IN_MEMPCPY_CHK:
+      {
+	tree off = gimple_call_arg (stmt, 2);
+	if (!get_range (off, SIGNED, offrng))
+	  {
+	    offrng[0] = 0;
+	    offrng[1] = HOST_WIDE_INT_M1U;
+	  }
+	return gimple_call_arg (stmt, 0);
+      }
+
+    case BUILT_IN_MEMCHR:
+      {
+	tree off = gimple_call_arg (stmt, 2);
+	if (get_range (off, SIGNED, offrng))
+	  offrng[0] = 0;
+	else
+	  {
+	    offrng[0] = 0;
+	    offrng[1] = HOST_WIDE_INT_M1U;
+	  }
+	return gimple_call_arg (stmt, 0);
+      }
+
+    case BUILT_IN_STRCHR:
+    case BUILT_IN_STRRCHR:
+    case BUILT_IN_STRSTR:
+      {
+	offrng[0] = 0;
+	offrng[1] = HOST_WIDE_INT_M1U;
+      }
+      return gimple_call_arg (stmt, 0);
+
+    default:
+      break;
+    }
+
+  return NULL_TREE;
+}
+
 /* 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).
@@ -4204,7 +4367,8 @@ get_range (tree x, signop sgn, offset_int r[2],
    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.
+   Returns true on success, false when a meaningful size (or range)
+   cannot be determined.
 
    The function is intended for diagnostics and should not be used
    to influence code generation or optimization.  */
@@ -4221,25 +4385,40 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 
   if (DECL_P (ptr))
     {
-      /* Bail if the reference is to the pointer itself (as opposed
-	 to what it points to).  */
+      pref->ref = ptr;
+
       if (!addr && POINTER_TYPE_P (TREE_TYPE (ptr)))
-	return false;
+	{
+	  /* Set the maximum size if the reference is to the pointer
+	     itself (as opposed to what it points to).  */
+	  pref->set_max_size_range ();
+	  return true;
+	}
 
-      pref->ref = ptr;
       if (tree size = decl_init_size (ptr, false))
 	if (TREE_CODE (size) == INTEGER_CST)
 	  {
 	    pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
 	    return true;
 	  }
-      pref->sizrng[0] = 0;
-      pref->sizrng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
+
+      pref->set_max_size_range ();
       return true;
     }
 
   const tree_code code = TREE_CODE (ptr);
 
+  if (code == BIT_FIELD_REF)
+    {
+      tree ref = TREE_OPERAND (ptr, 0);
+      if (!compute_objsize (ref, ostype, pref, visited, rvals))
+	return false;
+
+      offset_int off = wi::to_offset (pref->eval (TREE_OPERAND (ptr, 2)));
+      pref->add_offset (off / BITS_PER_UNIT);
+      return true;
+    }
+
   if (code == COMPONENT_REF)
     {
       tree ref = TREE_OPERAND (ptr, 0);
@@ -4247,27 +4426,29 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 
       if (ostype == 0)
 	{
-	  /* For raw memory functions like memcpy bail if the size
-	     of the enclosing object cannot be determined.  */
-	  if (!compute_objsize (ref, ostype, pref, visited, rvals)
-	      || !pref->ref)
+	  /* In OSTYPE zero (for raw memory functions like memcpy), use
+	     the maximum size instead if the identity of the enclosing
+	     object cannot be determined.  */
+	  if (!compute_objsize (ref, ostype, pref, visited, rvals))
 	    return false;
 
 	  /* Otherwise, use the size of the enclosing object and add
 	     the offset of the member to the offset computed so far.  */
 	  tree offset = byte_position (field);
-	  if (TREE_CODE (offset) != INTEGER_CST)
-	    return false;
-	  offset_int off = wi::to_offset (offset);
-	  pref->offrng[0] += off;
-	  pref->offrng[1] += off;
+	  if (TREE_CODE (offset) == INTEGER_CST)
+	    pref->add_offset (wi::to_offset (offset));
+	  else
+	    pref->add_max_offset ();
 	  return true;
 	}
 
-      /* 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;
+	{
+	  /* Set maximum size if the reference is to the pointer member
+	     itself (as opposed to what it points to).  */
+	  pref->set_max_size_range ();
+	  return true;
+	}
 
       pref->ref = field;
 
@@ -4323,8 +4504,10 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
       offset_int orng[2];
       tree off = pref->eval (TREE_OPERAND (ptr, 1));
       if (!get_range (off, SIGNED, orng, rvals))
-	/* Fail unless the size of the object is zero.  */
-	return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1];
+	{
+	  orng[1] = wi::to_offset (max_object_size ());
+	  orng[0] = -orng[1] - 1;
+	}
 
       if (TREE_CODE (ptr) == ARRAY_REF)
 	{
@@ -4343,7 +4526,10 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 	  tree eltype = TREE_TYPE (ptr);
 	  tree tpsize = TYPE_SIZE_UNIT (eltype);
 	  if (!tpsize || TREE_CODE (tpsize) != INTEGER_CST)
-	    return false;
+	    {
+	      pref->add_max_offset ();
+	      return true;
+	    }
 
 	  offset_int sz = wi::to_offset (tpsize);
 	  orng[0] *= sz;
@@ -4375,24 +4561,56 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 
       return true;
     }
-  else if (code == POINTER_PLUS_EXPR)
+
+  if (code == TARGET_MEM_REF)
     {
       tree ref = TREE_OPERAND (ptr, 0);
       if (!compute_objsize (ref, ostype, pref, visited, rvals))
 	return false;
 
-      offset_int orng[2];
-      tree off = pref->eval (TREE_OPERAND (ptr, 1));
-      if (!get_range (off, SIGNED, orng, rvals))
-	/* Fail unless the size of the object is zero.  */
-	return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1];
+      /* TODO: Handle remaining operands.  Until then, add maximum offset.  */
+      pref->ref = ptr;
+      pref->add_max_offset ();
+      return true;
+    }
 
-      pref->offrng[0] += orng[0];
-      pref->offrng[1] += orng[1];
+  if (code == INTEGER_CST)
+    {
+      /* Pointer constants other than null are most likely the result
+	 of erroneous null pointer addition/subtraction.  Set size to
+	 zero.  For null pointers, set size to the maximum for now
+	 since those may be the result of jump threading.  */
+      if (integer_zerop (ptr))
+	pref->set_max_size_range ();
+      else
+	pref->sizrng[0] = pref->sizrng[1] = 0;
+      pref->ref = ptr;
 
       return true;
     }
-  else if (code == VIEW_CONVERT_EXPR)
+
+  if (code == STRING_CST)
+    {
+      pref->sizrng[0] = pref->sizrng[1] = TREE_STRING_LENGTH (ptr);
+      return true;
+    }
+
+  if (code == POINTER_PLUS_EXPR)
+    {
+      tree ref = TREE_OPERAND (ptr, 0);
+      if (!compute_objsize (ref, ostype, pref, visited, rvals))
+	return false;
+
+      offset_int orng[2];
+      tree off = pref->eval (TREE_OPERAND (ptr, 1));
+      if (get_range (off, SIGNED, orng, rvals))
+	pref->add_offset (orng[0], orng[1]);
+      else
+	pref->add_max_offset ();
+      return true;
+    }
+
+  if (code == VIEW_CONVERT_EXPR)
     {
       ptr = TREE_OPERAND (ptr, 0);
       return compute_objsize (ptr, ostype, pref, visited, rvals);
@@ -4404,32 +4622,77 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
       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.  */
+	     from its argument(s).  If successful, also set *PREF->REF
+	     to PTR for the caller to include in diagnostics.  */
 	  wide_int wr[2];
 	  if (gimple_call_alloc_size (stmt, wr, rvals))
 	    {
 	      pref->ref = ptr;
 	      pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED);
 	      pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED);
-	      return true;
+	      /* Constrain both bounds to a valid size.  */
+	      offset_int maxsize = wi::to_offset (max_object_size ());
+	      if (pref->sizrng[0] > maxsize)
+		pref->sizrng[0] = maxsize;
+	      if (pref->sizrng[1] > maxsize)
+		pref->sizrng[1] = maxsize;
 	    }
-	  return false;
+	  else
+	    {
+	      /* For functions known to return one of their pointer arguments
+		 try to determine what the returned pointer points to, and on
+		 success add OFFRNG which was set to the offset added by
+		 the function (e.g., memchr) to the overall offset.  */
+	      offset_int offrng[2];
+	      if (tree ret = gimple_call_return_array (stmt, offrng))
+		{
+		  if (!compute_objsize (ret, ostype, pref, visited, rvals))
+		    return false;
+
+		  /* Cap OFFRNG[1] to at most the remaining size of
+		     the object.  */
+		  offset_int remrng[2];
+		  remrng[1] = pref->size_remaining (remrng);
+		  if (remrng[1] < offrng[1])
+		    offrng[1] = remrng[1];
+		  pref->add_offset (offrng[0], offrng[1]);
+		}
+	      else
+		{
+		  /* For other calls that might return arbitrary pointers
+		     including into the middle of objects set the size
+		     range to maximum, clear PREF->BASE0, and also set
+		     PREF->REF to include in diagnostics.  */
+		  pref->set_max_size_range ();
+		  pref->base0 = false;
+		  pref->ref = ptr;
+		}
+	    }
+	  return true;
 	}
 
       /* TODO: Handle PHI.  */
 
       if (!is_gimple_assign (stmt))
-	return false;
+	{
+	  /* Clear BASE0 since the assigned pointer might point into
+	     the middle of the object, set the maximum size range and,
+	     if the SSA_NAME refers to a function argumnent, set
+	     PREF->REF to it.  */
+	  pref->base0 = false;
+	  pref->set_max_size_range ();
+	  if (tree var = SSA_NAME_VAR (ptr))
+	    if (TREE_CODE (var) == PARM_DECL)
+	      pref->ref = var;
+	  return true;
+	}
 
       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;
 
-      if (code == POINTER_PLUS_EXPR)
+      if (code == POINTER_PLUS_EXPR
+	  && TREE_CODE (TREE_TYPE (ptr)) == POINTER_TYPE)
 	{
 	  /* If the the offset in the expression can be determined use
 	     it to adjust the overall offset.  Otherwise, set the overall
@@ -4438,32 +4701,28 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 	  tree off = gimple_assign_rhs2 (stmt);
 	  if (!get_range (off, SIGNED, orng, rvals))
 	    {
-	      orng[0] = wi::to_offset (TYPE_MIN_VALUE (ptrdiff_type_node));
-	      orng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
+	      orng[0] = -wi::to_offset (max_object_size ()) - 1;
+	      orng[1] = wi::to_offset (max_object_size ());
 	    }
 
-	  pref->offrng[0] += orng[0];
-	  pref->offrng[1] += orng[1];
+	  pref->add_offset (orng[0], orng[1]);
+	  return compute_objsize (ptr, ostype, pref, visited, rvals);
 	}
-      else if (code != ADDR_EXPR)
-	return false;
-
-      return compute_objsize (ptr, ostype, pref, visited, rvals);
-    }
 
-  tree type = TREE_TYPE (ptr);
-  type = TYPE_MAIN_VARIANT (type);
-  if (TREE_CODE (ptr) == ADDR_EXPR)
-    ptr = TREE_OPERAND (ptr, 0);
+      if (code == ADDR_EXPR)
+	return compute_objsize (ptr, ostype, pref, visited, rvals);
 
-  if (TREE_CODE (type) == ARRAY_TYPE
-      && !array_at_struct_end_p (ptr))
-    {
-      if (tree size = TYPE_SIZE_UNIT (type))
-	return get_range (size, UNSIGNED, pref->sizrng, rvals);
+      /* This could be an assignment from a nonlocal pointer.  Save PTR
+	 to mention in diagnostics but otherwise treat it as a pointer
+	 to an unknown object.  */
+      pref->ref = ptr;
     }
 
-  return false;
+  /* Assume all other expressions point into an unknown object
+     of the maximum valid size.  */
+  pref->base0 = false;
+  pref->set_max_size_range ();
+  return true;
 }
 
 /* A "public" wrapper around the above.  Clients should use this overload
@@ -4484,27 +4743,10 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
   if (!success)
     return NULL_TREE;
 
-  if (pref->offrng[1] < pref->offrng[0])
-    {
-      if (pref->offrng[1] < 0
-	  && pref->sizrng[1] <= pref->offrng[0])
-	return size_zero_node;
-
-      return wide_int_to_tree (sizetype, pref->sizrng[1]);
-    }
-
-  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]);
+  offset_int maxsize = pref->size_remaining ();
+  if (pref->base0 && pref->offrng[0] < 0 && pref->offrng[1] >= 0)
+    pref->offrng[0] = 0;
+  return wide_int_to_tree (sizetype, maxsize);
 }
 
 /* Transitional wrapper around the above.  The function should be removed
@@ -4518,7 +4760,7 @@ compute_objsize (tree ptr, int ostype, tree *pdecl /* = NULL */,
      none has been computed yet.  */
   access_ref ref;
   tree size = compute_objsize (ptr, ostype, &ref, rvals);
-  if (!size)
+  if (!size || !ref.base0)
     return NULL_TREE;
 
   if (pdecl)
@@ -5799,13 +6041,21 @@ expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED rtx target,
 	  tree size2 = compute_objsize (arg2, 1, &ref2);
 	  tree func = get_callee_fndecl (exp);
 
-	  if (size1 && size2)
+	  if (size1 && size2 && bndrng[0] && !integer_zerop (bndrng[0]))
 	    {
-	      tree maxsize = tree_int_cst_le (size1, size2) ? size2 : size1;
-
-	      if (tree_int_cst_lt (maxsize, bndrng[0]))
+	      offset_int rem1 = ref1.size_remaining ();
+	      offset_int rem2 = ref2.size_remaining ();
+	      if (rem1 == 0 || rem2 == 0)
 		maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp, func,
-				      bndrng, maxsize);
+				      bndrng, integer_zero_node);
+	      else
+		{
+		  offset_int maxrem = wi::max (rem1, rem2, UNSIGNED);
+		  if (maxrem < wi::to_offset (bndrng[0]))
+		    maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp,
+					  func, bndrng,
+					  wide_int_to_tree (sizetype, maxrem));
+		}
 	    }
 	  else if (bndrng[0]
 		   && !integer_zerop (bndrng[0])
diff --git a/gcc/builtins.h b/gcc/builtins.h
index e42c9664724..9b06fd5ebee 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -183,11 +183,42 @@ struct access_ref
      values.  */
   bool offset_bounded () const;
 
+  /* Return the maximum amount of space remaining and if non-null, set
+     argument to the minimum.  */
+  offset_int size_remaining (offset_int * = NULL) const;
+
+  void set_max_size_range ()
+  {
+    sizrng[0] = 0;
+    sizrng[1] = wi::to_offset (max_object_size ());
+  }
+
+  void add_offset (const offset_int &off)
+  {
+    offrng[0] += off;
+    offrng[1] += off;
+  }
+
+  void add_offset (const offset_int &min, const offset_int &max)
+  {
+    offrng[0] += min;
+    offrng[1] += max;
+  }
+
+  void add_max_offset ()
+  {
+    offrng[0] -= wi::to_offset (max_object_size ()) + 1;
+    offrng[1] += wi::to_offset (max_object_size ());
+  }
+
   /* Used to fold integer expressions when called from front ends.  */
   tree (*eval)(tree);
   /* Set if trailing one-element arrays should be treated as flexible
      array members.  */
   bool trail1special;
+  /* Set if valid offsets must start at zero (for declared and allocated
+     objects but not for others referenced by pointers).  */
+  bool base0;
 };
 
 /* Describes a pair of references used in an access by built-in
diff --git a/gcc/calls.c b/gcc/calls.c
index 8ac94db6817..dabbb5b442d 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -1241,14 +1241,16 @@ alloc_max_size (void)
    after adjusting it if necessary to make EXP a represents a valid size
    of object, or a valid size argument to an allocation function declared
    with attribute alloc_size (whose argument may be signed), or to a string
-   manipulation function like memset.  When ALLOW_ZERO is true, allow
-   returning a range of [0, 0] for a size in an anti-range [1, N] where
-   N > PTRDIFF_MAX.  A zero range is a (nearly) invalid argument to
-   allocation functions like malloc but it is a valid argument to
-   functions like memset.  */
+   manipulation function like memset.
+   When ALLOW_ZERO is set in FLAGS, allow returning a range of [0, 0] for
+   a size in an anti-range [1, N] where N > PTRDIFF_MAX.  A zero range is
+   a (nearly) invalid argument to allocation functions like malloc but it
+   is a valid argument to functions like memset.
+   When USE_LARGEST is set in FLAGS set RANGE to the largest valid subrange
+   in a multi-range, otherwise to the smallest valid subrange.  */
 
 bool
-get_size_range (tree exp, tree range[2], bool allow_zero /* = false */)
+get_size_range (tree exp, tree range[2], int flags /* = 0 */)
 {
   if (!exp)
     return false;
@@ -1320,25 +1322,42 @@ get_size_range (tree exp, tree range[2], bool allow_zero /* = false */)
 	      min = wi::zero (expprec);
 	    }
 	}
-      else if (wi::eq_p (0, min - 1))
+      else
 	{
-	  /* EXP is unsigned and not in the range [1, MAX].  That means
-	     it's either zero or greater than MAX.  Even though 0 would
-	     normally be detected by -Walloc-zero, unless ALLOW_ZERO
-	     is true, set the range to [MAX, TYPE_MAX] so that when MAX
-	     is greater than the limit the whole range is diagnosed.  */
-	  if (allow_zero)
-	    min = max = wi::zero (expprec);
-	  else
+	  wide_int maxsize = wi::to_wide (max_object_size ());
+	  min = wide_int::from (min, maxsize.get_precision (), UNSIGNED);
+	  max = wide_int::from (max, maxsize.get_precision (), UNSIGNED);
+	  if (wi::eq_p (0, min - 1))
+	    {
+	      /* EXP is unsigned and not in the range [1, MAX].  That means
+		 it's either zero or greater than MAX.  Even though 0 would
+		 normally be detected by -Walloc-zero, unless ALLOW_ZERO
+		 is set, set the range to [MAX, TYPE_MAX] so that when MAX
+		 is greater than the limit the whole range is diagnosed.  */
+	      wide_int maxsize = wi::to_wide (max_object_size ());
+	      if ((flags & SR_ALLOW_ZERO)
+		  && wi::ltu_p (maxsize, max))
+		min = max = wi::zero (expprec);
+	      else
+		{
+		  min = max + 1;
+		  max = wi::to_wide (TYPE_MAX_VALUE (exptype));
+		}
+	    }
+	  else if ((flags & SR_USE_LARGEST)
+		   && wi::ltu_p (max + 1, maxsize))
 	    {
+	      /* When USE_LARGEST is set and the larger of the two subranges
+		 is a valid size, use it...  */
 	      min = max + 1;
-	      max = wi::to_wide (TYPE_MAX_VALUE (exptype));
+	      max = maxsize;
+	    }
+	  else
+	    {
+	      /* ...otherwise use the smaller subrange.  */
+	      max = min - 1;
+	      min = wi::zero (expprec);
 	    }
-	}
-      else
-	{
-	  max = min - 1;
-	  min = wi::zero (expprec);
 	}
     }
 
diff --git a/gcc/calls.h b/gcc/calls.h
index dfb951ca45b..644ec45d92c 100644
--- a/gcc/calls.h
+++ b/gcc/calls.h
@@ -133,7 +133,15 @@ extern bool reference_callee_copied (CUMULATIVE_ARGS *,
 extern void maybe_warn_alloc_args_overflow (tree, tree, tree[2], int[2]);
 extern tree get_attr_nonstring_decl (tree, tree * = NULL);
 extern bool maybe_warn_nonstring_arg (tree, tree);
-extern bool get_size_range (tree, tree[2], bool = false);
+enum size_range_flags
+  {
+   /* Set to consider zero a valid range.  */
+   SR_ALLOW_ZERO = 1,
+   /* Set to use the largest subrange of a set of ranges as opposed
+      to the smallest.  */
+   SR_USE_LARGEST = 2
+  };
+extern bool get_size_range (tree, tree[2], int = 0);
 extern rtx rtx_for_static_chain (const_tree, bool);
 extern bool cxx17_empty_base_field_p (const_tree);
 
diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index ad2a30fcf71..62ac775dc95 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -17460,10 +17460,10 @@ complete_vars (tree type)
 	      && (TYPE_MAIN_VARIANT (strip_array_types (type))
 		  == iv->incomplete_type))
 	    {
-	      /* Complete the type of the variable.  The VAR_DECL itself
-		 will be laid out in expand_expr.  */
+	      /* Complete the type of the variable.  */
 	      complete_type (type);
 	      cp_apply_type_quals_to_decl (cp_type_quals (type), var);
+	      layout_decl (var, 0);
 	    }
 
 	  /* Remove this entry from the list.  */
diff --git a/gcc/testsuite/c-c++-common/Wrestrict.c b/gcc/testsuite/c-c++-common/Wrestrict.c
index 3b019c8a80e..7a536456c47 100644
--- a/gcc/testsuite/c-c++-common/Wrestrict.c
+++ b/gcc/testsuite/c-c++-common/Wrestrict.c
@@ -320,14 +320,16 @@ void test_memcpy_anti_range (char *d, const char *s)
 
   T (d, d + SAR (0, 3), UR (DIFF_MAX - 2, DIFF_MAX));               /* { dg-warning "accessing \[0-9\]+ or more bytes at offsets 0 and \\\[-?\[0-9\]+, -?\[0-9\]+] overlaps \[0-9\]+ bytes at offset 2" "memcpy" } */
 
-  /* Verify that a size in an anti-range ~[0, N] where N >= PTRDIFF_MAX
-     doesn't trigger a warning.  */
-  T (d, s, UAR (1, DIFF_MAX - 1));
+  /* Verify that a size in an anti-range ~[1, N] where N >= PTRDIFF_MAX - 2
+     doesn't trigger a warning.  With ~[1, PTRDIFF_MAX - 1] and assuming
+     a nonzero size, the difference between the just-past-the-end pointer
+     to A and A for char A[PTRDIFF_MAX] wouldn't be representable in
+     ptrdiff_t so such a large object cannot exist.  */
+  T (d, s, UAR (1, DIFF_MAX / 2 - 1));
+  T (d, s, UAR (1, DIFF_MAX - 1));      /* { dg-warning "\\\[-Wrestrict" } */
   T (d, s, UAR (1, DIFF_MAX));
   T (d, s, UAR (1, SIZE_MAX - 1));
-
-  /* This causes the last dg-warning test to fail for some reason.
-     T (d, s, UAR (1, SIZE_MAX)); */
+  T (d, s, UAR (1, SIZE_MAX));
 }
 
 /* Verify calls to memcpy() where the combination of offsets in some
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-41.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-41.c
new file mode 100644
index 00000000000..2647043b1ef
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-41.c
@@ -0,0 +1,118 @@
+/* Verify that writes at excessive offsets into declared or allocated
+   objects of unknown size are diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#define DIFF_MAX __PTRDIFF_MAX__
+
+typedef __SIZE_TYPE__ size_t;
+
+void* malloc (size_t);
+void* memcpy (void*, const void*, size_t);
+void* memset (void*, int, size_t);
+
+void sink (void*);
+
+
+void char_array_cst_off_cst_size (void)
+{
+  extern char caxcc[];                  // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'caxcc'" }
+
+  char *p = caxcc;
+  size_t idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 2" }
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 1" "pr?????" { xfail ilp32 } }
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 0" }
+  sink (p);
+}
+
+
+void char_array_var_off_cst_size (size_t idx)
+{
+  /* The upper bound of the offset would ideally be positive but for now
+     allow it to be negative too.  */
+  extern char caxvc[];                  // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object 'caxvc'" }
+
+  char *p = caxvc;
+
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+  sink (p);
+}
+
+
+void char_array_var_off_var_size (size_t idx, size_t n)
+{
+  extern char caxvv[];                  // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object 'caxvv'" }
+
+  char *p = caxvv;
+
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  if (n < 3 || 7 < n)
+    n = 3;
+
+  memset (p + idx, 0, n);
+  sink (p);
+
+  ++n;
+  memset (p + idx, 0, n);               // { dg-warning "writing between 4 and 8 bytes into a region of size 3" }
+  sink (p);
+}
+
+
+void alloc_array_var_off_cst_size (size_t n, size_t idx)
+{
+  char *p = malloc (n);                 // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object" }
+
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+  sink (p);
+}
+
+
+void int_array_cst_off_cst_size (void)
+{
+  extern int iaxc[];                    // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'iaxc'" }
+
+  int *p = iaxc;
+  size_t idx = DIFF_MAX / sizeof *iaxc;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+  sink (p);
+}
+
+
+void* nowarn_anti_range_1 (char *p, char *q)
+{
+  size_t n = q - p;
+  if (!n) return 0;
+
+  char *d = __builtin_malloc (n + 1);
+  memcpy (d, p, n + 1);                 // { dg-bogus "-Wstringop-overflow" }
+  return d;
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-44.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-44.c
new file mode 100644
index 00000000000..a9cf4071fff
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-44.c
@@ -0,0 +1,103 @@
+/* Verify that writes at excessive offsets into flexible array members
+   of extern or allocated objects of unknow size are diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#define DIFF_MAX __PTRDIFF_MAX__
+
+typedef __PTRDIFF_TYPE__ ptrdiff_t;
+typedef __SIZE_TYPE__    size_t;
+
+void* memset (void*, int, size_t);
+
+void sink (void*);
+
+void char_flexarray_cst_off_cst_size (void)
+{
+  extern struct { char n, a[]; }
+    caxcc;                              // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'caxcc'" }
+
+  char *p = caxcc.a;
+  size_t idx = DIFF_MAX - 4;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 2" }
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 1" }
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 0" }
+}
+
+
+void char_flexarray_var_off_cst_size (ptrdiff_t idx)
+{
+  extern struct { char n, a[]; }
+    caxvc;                              // { dg-message "destination object 'caxvc'" }
+
+  char *p = caxvc.a;
+
+  if (idx < DIFF_MAX - 4)
+    idx = DIFF_MAX - 4;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+}
+
+
+void char_flexarray_var_off_var_size (size_t n, ptrdiff_t idx)
+{
+  extern struct { char n, a[]; }
+    caxvv;                              // { dg-message "destination object 'caxvv'" }
+
+  char *p = caxvv.a;
+
+  if (idx < DIFF_MAX - 4)
+    idx = DIFF_MAX - 4;
+
+  if (n < 3 || 7 < n)
+    n = 3;
+
+  memset (p + idx, 0, n);
+  sink (p);
+
+  ++n;
+  memset (p + idx, 0, n);               // { dg-warning "writing between 4 and 8 bytes into a region of size 3" }
+}
+
+
+void alloc_array_var_off_cst_size (size_t n, ptrdiff_t idx)
+{
+  struct { char n, a[]; }
+    *p = __builtin_malloc (n);          // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object" "" { xfail *-*-* } }
+
+  if (idx < DIFF_MAX - 4)
+    idx = DIFF_MAX - 4;
+
+  memset (p->a + idx, 0, 3);
+  sink (p);
+
+  memset (p->a + idx, 0, 5);            // { dg-warning "writing 5 bytes into a region of size 3" }
+}
+
+
+void int_array_cst_off_cst_size (void)
+{
+  extern struct { int n, a[]; }
+    iaxc;                               // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'iaxc'" }
+
+  int *p = iaxc.a;
+  size_t idx = DIFF_MAX / sizeof *p - 1;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-45.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-45.c
new file mode 100644
index 00000000000..7e9be57ae85
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-45.c
@@ -0,0 +1,255 @@
+/* PR middle-end/97023 - missing warning on buffer overflow in chained mempcpy
+   Verify that out of bounds writes by built-ins to objects through pointers
+   returned by other built-ins are diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#include "range.h"
+
+void* malloc (size_t);
+void* memcpy (void*, const void*, size_t);
+void* memmove (void*, const void*, size_t);
+void* mempcpy (void*, const void*, size_t);
+
+void sink (void*, ...);
+
+
+void nowarn_memcpy (const void *s)
+{
+  extern char cpy_a4[4];
+  unsigned n = sizeof cpy_a4;
+
+  void *p = cpy_a4;
+  p = memcpy (p, s, n);
+  sink (p);
+  memcpy (p, s, n);
+  sink (p);
+
+  p = cpy_a4 + 1;
+  p = memcpy (p, s, n - 1);
+  sink (p);
+  memcpy (p, s, n - 1);
+  sink (p);
+
+  p = cpy_a4 + 2;
+  p = memcpy (p, s, n - 2);
+  sink (p);
+  memcpy (p, s, n - 2);
+  sink (p);
+
+  p = cpy_a4 + 3;
+  p = memcpy (p, s, n - 3);
+  sink (p);
+  memcpy (p, s, n - 3);
+  sink (p);
+
+  p = cpy_a4 + 4;
+  p = memcpy (p, s, n - 4);
+  sink (p);
+  memcpy (p, s, n - 4);
+  sink (p);
+}
+
+
+void nowarn_memcpy_chain (const void *s)
+{
+  extern char cpy_a8[8];
+
+  char *p = cpy_a8;
+
+  p = memcpy (p + 1, s, 7);
+  sink (p);
+
+  p = memcpy (p + 2 , s, 5);
+  sink (p);
+
+  p = memcpy (p + 3 , s, 2);
+  sink (p);
+
+  p = memcpy (p + 1 , s, 1);
+  sink (p);
+
+  p = memcpy (p - 7 , s, 8);
+  sink (p);
+
+  memcpy (p + 1, s, 7);
+}
+
+
+void warn_memcpy (const void *s)
+{
+  extern char cpy_a5[5];                // { dg-message "destination object 'cpy_a5'" "note" }
+
+  unsigned n = sizeof cpy_a5;
+  void *p = cpy_a5;
+
+  p = memcpy (p, s, n);
+  sink (p);
+  memcpy (p, s, n + 1);                 // { dg-warning "writing 6 bytes into a region of size 5" }
+  sink (p);
+
+  p = cpy_a5;
+  p = memcpy (p, s, n);
+  sink (p);
+  memcpy (p, s, n + 1);                 // { dg-warning "writing 6 bytes into a region of size 5" }
+  sink (p);
+
+  p = cpy_a5 + 1;
+  p = memcpy (p, s, n - 1);
+  sink (p);
+  memcpy (p, s, n);                     // { dg-warning "writing 5 bytes into a region of size 4" }
+  sink (p);
+}
+
+
+void warn_memcpy_chain (const void *s)
+{
+  extern char cpy_a8[8];                // { dg-message "destination object 'cpy_a8'" "note" }
+
+  char *p = cpy_a8;
+
+  p = memcpy (p, s, 9);                 // { dg-warning "writing 9 bytes into a region of size 8" }
+  sink (p);
+
+  p = memcpy (p + 2, s, 7);             // { dg-warning "writing 7 bytes into a region of size 6" }
+  sink (p);
+
+  p = memcpy (p + 3, s, 5);             // { dg-warning "writing 5 bytes into a region of size 3" }
+  sink (p);
+
+  p = memcpy (p + 3, s, 3);             // { dg-warning "writing 3 bytes into a region of size 0" }
+  sink (p);
+}
+
+
+void nowarn_mempcpy (const void *s)
+{
+  extern char a4[4];
+  unsigned n = sizeof a4;
+
+  char *p = mempcpy (a4, s, n);
+  sink (p);
+  mempcpy (p - 4, s, n);
+  sink (p);
+
+  p = mempcpy (a4 + 1, s, n - 1);
+  sink (p);
+  mempcpy (p - 4, s, n);
+  sink (p);
+
+  p = mempcpy (a4 + 2, s, n - 2);
+  sink (p);
+  mempcpy (p - 4, s, n);
+  sink (p);
+
+  p = mempcpy (a4 + 3, s, n - 3);
+  sink (p);
+  mempcpy (p - 4, s, n);
+  sink (p);
+
+  p = mempcpy (a4 + 4, s, n - 4);
+  sink (p);
+  mempcpy (p - 4, s, n);
+  sink (p);
+}
+
+
+void nowarn_mempcpy_chain (const void *s)
+{
+  extern char pcpy_a8[8];
+
+  char *p = pcpy_a8;
+
+  p = mempcpy (p + 1, s, 7);
+  sink (p);
+
+  p = mempcpy (p - 7 , s, 7);
+  sink (p);
+
+  p = mempcpy (p - 5 , s, 5);
+  sink (p);
+
+  p = mempcpy (p - 3 , s, 3);
+  sink (p);
+
+  p = mempcpy (p - 2 , s, 2);
+  sink (p);
+
+  mempcpy (p - 1, s, 1);
+  sink (p);
+
+  mempcpy (p - 8, s, 8);
+}
+
+
+void warn_mempcpy (const void *s)
+{
+  extern char pcpy_a5[5];               // { dg-message "destination object 'pcpy_a5'" "note" }
+
+  char *p = pcpy_a5;
+
+  p = mempcpy (p, s, 5);
+  sink (p);
+  mempcpy (p - 5, s, 6);                // { dg-warning "writing 6 bytes into a region of size 5 " }
+  sink (p);
+
+  p = pcpy_a5;
+  p = mempcpy (p, s, 3);
+  sink (p);
+  mempcpy (p, s, 3);                    // { dg-warning "writing 3 bytes into a region of size 2 " }
+  sink (p);
+
+  p = pcpy_a5 + 1;
+  p = mempcpy (p, s, 3);
+  sink (p);
+  mempcpy (p - 1, s, 5);                // { dg-warning "writing 5 bytes into a region of size 2 " }
+  sink (p);
+}
+
+
+void warn_mempcpy_chain_3 (const void *s)
+{
+  char *p = malloc (5);                 // { dg-message "at offset \\\[3, 5] into destination object of size 5" }
+  p = mempcpy (p, s, UR (1, 2));
+  p = mempcpy (p, s, UR (2, 3));
+  p = mempcpy (p, s, UR (3, 4));        // { dg-warning "writing between 3 and 4 bytes into a region of size 2 " }
+
+  sink (p);
+}
+
+void warn_mempcpy_offrng_chain_3 (const void *s)
+{
+  char *p = malloc (11);                 // { dg-message "at offset \\\[9, 14] into destination object of size 11 " }
+  size_t r1_2 = UR (1, 2);
+  size_t r2_3 = r1_2 + 1;
+  size_t r3_4 = r2_3 + 1;
+
+  p = mempcpy (p + r1_2, s, r1_2);
+  p = mempcpy (p + r2_3, s, r2_3);
+  p = mempcpy (p + r3_4, s, r3_4);       // { dg-warning "writing between 3 and 4 bytes into a region of size 2 " }
+
+  sink (p);
+}
+
+void warn_mempcpy_chain_4 (const void *s)
+{
+  char *p = malloc (9);                 // { dg-message "at offset \\\[6, 9] into destination object of size 9 " }
+  p = mempcpy (p, s, UR (1, 2));
+  p = mempcpy (p, s, UR (2, 3));
+  p = mempcpy (p, s, UR (3, 4));
+  p = mempcpy (p, s, UR (4, 5));        // { dg-warning "writing between 4 and 5 bytes into a region of size 3 " }
+
+  sink (p);
+}
+
+void warn_mempcpy_chain_5 (const void *s)
+{
+  char *p = malloc (14);                // { dg-message "at offset \\\[10, 14] into destination object of size 14 " }
+  p = mempcpy (p, s, UR (1, 2));
+  p = mempcpy (p, s, UR (2, 3));
+  p = mempcpy (p, s, UR (3, 4));
+  p = mempcpy (p, s, UR (4, 5));
+  p = mempcpy (p, s, UR (5, 6));        // { dg-warning "writing between 5 and 6 bytes into a region of size 4 " }
+
+  sink (p);
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-46.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-46.c
new file mode 100644
index 00000000000..68c9c866887
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-46.c
@@ -0,0 +1,94 @@
+/* PR middle-end/97023 - missing warning on buffer overflow in chained mempcpy
+   Verify that out of bounds writes by built-ins to objects through pointers
+   returned by memchr() are diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#include "range.h"
+
+void* malloc (size_t);
+void* memchr (void*, int, size_t);
+void* memset (void*, int, size_t);
+
+void sink (void*, ...);
+
+void nowarn_memchr_cst_memset_cst (const void *s)
+{
+  char *p = malloc (4);
+  sink (p);
+
+  p = memchr (p, '1', 4);
+  memset (p, 0, 4);
+}
+
+void nowarn_memchr_uint_memset_cst (const void *s, unsigned n)
+{
+  char *p = malloc (4);
+  sink (p);
+
+  p = memchr (p, '1', n);
+  memset (p, 0, 4);
+}
+
+void nowarn_memchr_sz_memset_cst (const void *s, size_t n)
+{
+  char *p = malloc (4);
+  sink (p);
+
+  p = memchr (p, '1', n);
+  memset (p, 0, 4);
+}
+
+void nowarn_memchr_anti_range_memset_cst (const void *s, size_t n)
+{
+  char *p = malloc (4);
+  sink (p);
+
+  if (n == 0)
+    n = 1;
+
+  p = memchr (p, '1', n);
+  memset (p, 0, 4);
+}
+
+void warn_memchr_cst_memset_cst (const void *s)
+{
+  char *p = malloc (4);                 // { dg-message "at offset \\\[0, 4] into destination object of size 4 " "note" }
+  sink (p);
+
+  p = memchr (p, '1', 4);
+  memset (p, 0, 5);                     // { dg-warning "writing 5 bytes into a region of size 4 " }
+}
+
+void warn_memchr_var_memset_cst (const void *s, unsigned n)
+{
+  char *p = malloc (4);                 // { dg-message "at offset \\\[0, 4] into destination object of size 4 " "note" }
+  sink (p);
+
+  p = memchr (p, '1', n);
+  memset (p, 0, 5);                     // { dg-warning "writing 5 bytes into a region of size 4 " }
+}
+
+void warn_memchr_var_memset_range (const void *s, unsigned n)
+{
+  /* The offsets in the first two notes are bounded by the size of
+     the allocated object.  The upper bound of the offset in last
+     note is bigger because it includes the upper bound of the offset
+     of the pointer returned from the previous memchr() call.  */
+  char *p0 = malloc (UR (5, 7));
+  // { dg-message "at offset \\\[0, 7] into destination object of size \\\[5, 7]" "note" { target *-*-* } .-1 }
+  // { dg-message "at offset \\\[1, 7] into destination object of size \\\[5, 7]" "note"  { target *-*-* } .-2 }
+  // { dg-message "at offset \\\[2, 12] into destination object of size \\\[5, 7]" "note"  { target *-*-* } .-3 }
+
+  sink (p0);
+  char *p1 = memchr (p0, '1', n);
+  memset (p1, 0, UR (8, 9));            // { dg-warning "writing between 8 and 9 bytes into a region of size 7 " }
+
+  sink (p0);
+  p1 = memchr (p0 + 1, '2', n);
+  memset (p1, 0, UR (7, 9));            // { dg-warning "writing between 7 and 9 bytes into a region of size 6 " }
+
+  sink (p0);
+  char *p2 = memchr (p1 + 1, '3', n);
+  memset (p2, 0, UR (6, 9));            // { dg-warning "writing between 6 and 9 bytes into a region of size 5 " }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-47.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-47.c
new file mode 100644
index 00000000000..02b14ee2eda
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-47.c
@@ -0,0 +1,69 @@
+/* Verify that storing a bigger vector into smaller space is diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+typedef __INT16_TYPE__                         int16_t;
+typedef __attribute__ ((__vector_size__ (32))) char C32;
+
+typedef __attribute__ ((__vector_size__ (64))) int16_t I16_64;
+
+void sink (void*);
+
+
+void nowarn_c32 (char c)
+{
+  extern char nowarn_a32[32];
+
+  void *p = nowarn_a32;
+  *(C32*)p = (C32){ c };
+  sink (p);
+
+  char a32[32];
+  p = a32;
+  *(C32*)p = (C32){ c };
+  sink (p);
+}
+
+void warn_c32 (char c)
+{
+  extern char warn_a32[32];   // { dg-message "at offset 32 to object 'warn_a32' with size 32" }
+
+  void *p = warn_a32 + 1;
+  *(C32*)p = (C32){ c };      // { dg-warning "writing 1 byte into a region of size 0" }
+
+  /* Verify a local variable too. */
+  char a32[32];
+  p = a32 + 1;
+  *(C32*)p = (C32){ c };      // { dg-warning "writing 1 byte into a region of size 0" }
+  sink (p);
+}
+
+
+void nowarn_i16_64 (int16_t i)
+{
+  extern char nowarn_a64[64];
+
+  void *p = nowarn_a64;
+  I16_64 *q = (I16_64*)p;
+  *q = (I16_64){ i };
+
+  char a64[64];
+  q = (I16_64*)a64;
+  *q = (I16_64){ i };
+  sink (q);
+}
+
+void warn_i16_64 (int16_t i)
+{
+  extern char warn_a64[64];   // { dg-message "at offset 128 to object 'warn_a64' with size 64" "pr97027" { xfail *-*-* } }
+
+  void *p = warn_a64 + 1;
+  I16_64 *q = (I16_64*)p;
+  *q = (I16_64){ i };         // { dg-warning "writing 1 byte into a region of size 0" "pr97027" { xfail *-*-* } }
+
+  char a64[64];
+  p = a64 + 1;
+  q = (I16_64*)p;
+  *q = (I16_64){ i };         // { dg-warning "writing 1 byte into a region of size 0" "pr97027" { xfail *-*-* } }
+  sink (p);
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-49.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-49.c
new file mode 100644
index 00000000000..84b6c94fa7e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-49.c
@@ -0,0 +1,146 @@
+/* Verify the handling of anti-ranges/multi-ranges by allocation functions
+   and subsequent accesses.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+void* malloc (size_t);
+void  bzero (void*, size_t);
+void* memset (void*, int, size_t);
+
+
+/* Exercise size_t (via malloc and memset) and unsigned/signed int.  */
+
+__attribute__ ((alloc_size (1))) void*
+alloc_int (int);
+
+__attribute__ ((access (write_only, 1, 2))) void
+access_int (void*, int);
+
+__attribute__ ((alloc_size (1))) void*
+alloc_uint (unsigned);
+
+__attribute__ ((access (write_only, 1, 2))) void
+access_uint (void*, unsigned);
+
+
+void* nowarn_malloc_memset_same_anti_range (size_t n)
+{
+  /* Set N to the anti-range ~[3, 3].  */
+  if (n == 3)
+    n = 4;
+  void *p = malloc (n);
+
+  /* Verify there is no warning for an access to N bytes at P.
+     This means the warning has to assume the value of N in the call
+     to alloc() is in the larger subrange [4, UINT_MAX], while in
+     the call to access() in [0, 3].  */
+  return memset (p, 0, n);
+}
+
+/* Same as above but with two valid ranges.  */
+
+void* nowarn_malloc_memset_anti_range (size_t n1, size_t n2)
+{
+  /* Set N1 to the anti-range ~[3, 3].  */
+  if (n1 == 3)
+    n1 = 4;
+  void *p = malloc (n1);
+
+  /* Set N2 to the anti-range ~[7, 7].  */
+  if (n2 == 7)
+    n2 = 8;
+
+  return memset (p, 0, n2);
+}
+
+
+void nowarn_alloc_access_same_anti_range_int (int n)
+{
+  /* Set N to the anti-range ~[3, 3].  */
+  if (n == 3)
+    n = 4;
+  void *p = alloc_int (n);
+
+  /* Verify there is no warning for an access to N bytes at P.
+     This means the warning has to assume the value of N in the call
+     to alloc() is in the larger subrange [4, UINT_MAX], while in
+     the call to access() in [0, 3].  */
+  access_int (p, n);
+}
+
+/* Same as above but with two valid ranges.  */
+
+void nowarn_alloc_access_anti_range_int (int n1, int n2)
+{
+  /* Set N1 to the anti-range ~[3, 3].  */
+  if (n1 == 3)
+    n1 = 4;
+  void *p = alloc_int (n1);
+
+  /* Set N2 to the anti-range ~[7, 7].  */
+  if (n2 == 7)
+    n2 = 8;
+
+  access_int (p, n2);
+}
+
+
+void nowarn_alloc_access_same_anti_range_uint (unsigned n)
+{
+  /* Set N to the anti-range ~[3, 3].  */
+  if (n == 3)
+    n = 4;
+  void *p = alloc_uint (n);
+
+  /* Verify there is no warning for an access to N bytes at P.
+     This means the warning has to assume the value of N in the call
+     to alloc() is in the larger subrange [4, UINT_MAX], while in
+     the call to access() in [0, 3].  */
+  access_uint (p, n);
+}
+
+/* Same as above but with two valid ranges.  */
+
+void nowarn_alloc_access_anti_range_uint (unsigned n1, unsigned n2)
+{
+  /* Set N1 to the anti-range ~[3, 3].  */
+  if (n1 == 3)
+    n1 = 4;
+  void *p = alloc_uint (n1);
+
+  /* Set N2 to the anti-range ~[7, 7].  */
+  if (n2 == 7)
+    n2 = 8;
+
+  access_uint (p, n2);
+}
+
+
+void* nowarn_malloc_anti_range_memset_range (size_t n1, size_t n2)
+{
+  /* Set N1 to the anti-range ~[3, 3].  */
+  if (n1 == 3)
+    n1 = 4;
+  void *p = malloc (n1);
+
+  /* Set N2 to the range [5, MAX].  */
+  if (n2 < 5)
+    n2 = 5;
+  return memset (p, 0, n2);
+}
+
+void* nowarn_malloc_range_bzero_anti_range (size_t n1, size_t n2)
+{
+  /* Set N1 to the anti-range ~[3, 3].  */
+  if (n1 > 4)
+    n1 = 4;
+  void *p = malloc (n1);
+
+  /* Set N2 to the range [5, MAX].  */
+  if (n2 <= 3 || 5 <= n2)
+    n2 = 4;
+  bzero (p, n2);
+  return p;
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-50.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-50.c
new file mode 100644
index 00000000000..ca98286f449
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-50.c
@@ -0,0 +1,121 @@
+/* Verify that writes at excessive offsets into objects of unknown size
+   pointed to by function arguments are diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#define DIFF_MAX __PTRDIFF_MAX__
+
+typedef __PTRDIFF_TYPE__ ptrdiff_t;
+typedef __SIZE_TYPE__    size_t;
+
+void* memset (void*, int, size_t);
+
+void sink (void*);
+
+char* fcall (void);
+
+void char_ptr_cst_off_cst_size (char *p)
+                                        // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'p'" "note" { target *-*-* } .-1 }
+{
+  size_t idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 2" }
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 1" }
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 0" }
+}
+
+
+void char_ptr_var_difoff_cst_size (ptrdiff_t idx)
+{
+  char *p = fcall ();
+  // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object of size \\\[0, \[1-9\]\[0-9\]+] (allocated|returned) by 'fcall'" "note" { target *-*-* } .-1 }
+
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+}
+
+
+void char_ptr_var_szoff_cst_size (size_t idx)
+{
+  extern char* gptr;
+  // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object 'gptr'" "note" { target *-*-* } .-1 }
+
+  char *p = gptr;
+
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" "" { xfail *-*-* } }
+
+  if (idx > DIFF_MAX)
+    idx = DIFF_MAX;
+
+  memset (p + idx, 0, 7);               // { dg-warning "writing 7 bytes into a region of size 3" }
+}
+
+
+void char_ptr_var_difoff_var_size (char *p, ptrdiff_t idx, size_t n)
+                                        // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, \[1-9\]\[0-9\]+] into destination object 'p'" "note" { target *-*-* } .-1 }
+{
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  if (n < 3 || 7 < n)
+    n = 3;
+
+  memset (p + idx, 0, n);
+  sink (p);
+
+  ++n;
+  memset (p + idx, 0, n);               // { dg-warning "writing between 4 and 8 bytes into a region of size 3" }
+}
+
+
+void char_ptr_var_szoff_var_size (char *p, size_t idx, size_t n)
+                                        // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, \[1-9\]\[0-9\]+] into destination object 'p'" "note" { xfail *-*-* } .-1 }
+{
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  if (n < 3 || 7 < n)
+    n = 3;
+
+  memset (p + idx, 0, n);
+  sink (p);
+
+  ++n;
+  /* With an unsigned offset large values are interpreted as negative
+     so the addition (p + idx) is effectively treated as subtraction,
+     making an overflow indistinguishable from a valid (if unlikely)
+     store.  */
+  memset (p + idx, 0, n);               // { dg-warning "writing between 4 and 8 bytes into a region of size 3" "pr?????" { xfail *-*-* } }
+}
+
+
+void int_ptr_cst_off_cst_size (int *p)
+                                        // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'p'" "note" { target *-*-* } .-1 }
+{
+  size_t idx = DIFF_MAX / sizeof *p;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-51.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-51.c
new file mode 100644
index 00000000000..6f36643c8bb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-51.c
@@ -0,0 +1,34 @@
+/* Test case derived from Binutils/GDB's readline/readline/histexpand.c.
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+char *
+get_subst_pattern (char *str, int *iptr, int delimiter, int is_rhs, int *lenptr)
+{
+  int si, i, j, k;
+  char *s;
+
+  s = 0;
+  i = *iptr;
+
+  for (si = i; str[si] && str[si] != delimiter; si++)
+      if (str[si] == '\\' && str[si + 1] == delimiter)
+	si++;
+
+  if (si > i || is_rhs)
+    {
+      s = (char *)__builtin_malloc (si - i + 1);
+      for (j = 0, k = i; k < si; j++, k++)
+	{
+	  /* Remove a backslash quoting the search string delimiter. */
+	  if (str[k] == '\\' && str[k + 1] == delimiter)
+	    k++;
+	  s[j] = str[k];   // { dg-bogus "-Wstringop-overflow" }
+	}
+      s[j] = '\0';
+      if (lenptr)
+	*lenptr = j;
+    }
+
+  return s;
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-52.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-52.c
new file mode 100644
index 00000000000..a28965557c3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-52.c
@@ -0,0 +1,62 @@
+
+/* PR middle-end/97023 - missing warning on buffer overflow in chained mempcpy
+   Verify that writes by built-in functions to objects through pointers
+   returned by ordinary (non-built-int) function are assumed to point to
+   the beginning of objects.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#include "range.h"
+
+void* memcpy (void*, const void*, size_t);
+void* memset (void*, int, size_t);
+
+void sink (void*, ...);
+
+extern char* arrptr[];
+extern char* ptr;
+extern char* retptr (void);
+struct S { char *p; };
+extern struct S retstruct (void);
+
+void nowarn_ptr (void)
+{
+  {
+    void *p = arrptr;
+    memset (p - 1, 0, 12345);           // { dg-warning "\\\[-Wstringop-overflow" }
+    memset (p,0, 12345);
+    memset (p,0, DIFF_MAX - 1);
+  }
+
+  {
+    char *p = arrptr[0];
+    memset (p - 1, 0, 12345);
+    memset (p - 12345, 0, 12345);
+    memset (p - 1234, 0, DIFF_MAX - 1);
+    memset (p - DIFF_MAX + 1, 0, 12345);
+  }
+
+  {
+    char *p = ptr;
+    memset (p - 1, 0, 12345);
+    memset (p - 12345, 0, 12345);
+    memset (p - 1234, 0, DIFF_MAX - 1);
+    memset (p - DIFF_MAX + 1, 0, 12345);
+  }
+
+  {
+    char *p = retptr ();
+    memset (p - 1, 0, 12345);
+    memset (p - 12345, 0, 12345);
+    memset (p - 1234, 0, DIFF_MAX - 1);
+    memset (p - DIFF_MAX + 1, 0, 12345);
+  }
+
+  {
+    char *p = retstruct ().p;
+    memset (p - 1, 0, 12345);
+    memset (p - 12345, 0, 12345);
+    memset (p - 1234, 0, DIFF_MAX - 1);
+    memset (p - DIFF_MAX + 1, 0, 12345);
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/pr51683.c b/gcc/testsuite/gcc.dg/pr51683.c
index c477cd8fdd2..7a624e4055d 100644
--- a/gcc/testsuite/gcc.dg/pr51683.c
+++ b/gcc/testsuite/gcc.dg/pr51683.c
@@ -12,6 +12,9 @@ void *
 foo (void *p)
 {
   return bar ((void *) 0x12345000, p, 256);
+  /* Integers converted to pointers are assumed to be the result of
+     (invalid) arithmetic on null pointers.
+     { dg-prune-output "writing 256 bytes into a region of size 0" } */
 }
 
 /* { dg-final { scan-tree-dump "memcpy" "optimized" } } */

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-09-14 22:01       ` Martin Sebor
@ 2020-09-21 21:17         ` Martin Sebor
  2020-09-22 20:05           ` Martin Sebor
  0 siblings, 1 reply; 28+ messages in thread
From: Martin Sebor @ 2020-09-21 21:17 UTC (permalink / raw)
  To: Jason Merrill, gcc-patches

Ping: https://gcc.gnu.org/pipermail/gcc-patches/2020-September/553906.html

(I'm working on rebasing the patch on top of the latest trunk which
has changed some of the same code but it'd be helpful to get a go-
ahead on substance the changes.  I don't expect the rebase to
require any substantive modifications.)

Martin

On 9/14/20 4:01 PM, Martin Sebor wrote:
> On 9/4/20 11:14 AM, Jason Merrill wrote:
>> On 9/3/20 2:44 PM, Martin Sebor wrote:
>>> On 9/1/20 1:22 PM, Jason Merrill wrote:
>>>> On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
>>>>> -Wplacement-new handles array indices and pointer offsets the same:
>>>>> by adjusting them by the size of the element.  That's correct for
>>>>> the latter but wrong for the former, causing false positives when
>>>>> the element size is greater than one.
>>>>>
>>>>> In addition, the warning doesn't even attempt to handle arrays of
>>>>> arrays.  I'm not sure if I forgot or if I simply didn't think of
>>>>> it.
>>>>>
>>>>> The attached patch corrects these oversights by replacing most
>>>>> of the -Wplacement-new code with a call to compute_objsize which
>>>>> handles all this correctly (plus more), and is also better tested.
>>>>> But even compute_objsize has bugs: it trips up while converting
>>>>> wide_int to offset_int for some pointer offset ranges.  Since
>>>>> handling the C++ IL required changes in this area the patch also
>>>>> fixes that.
>>>>>
>>>>> For review purposes, the patch affects just the middle end.
>>>>> The C++ diff pretty much just removes code from the front end.
>>>>
>>>> The C++ changes are OK.
>>>
>>> Thank you for looking at the rest as well.
>>>
>>>>
>>>>> -compute_objsize (tree ptr, int ostype, access_ref *pref,
>>>>> -                bitmap *visited, const vr_values *rvals /* = NULL */)
>>>>> +compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap 
>>>>> *visited,
>>>>> +                const vr_values *rvals)
>>>>
>>>> This reformatting seems unnecessary, and I prefer to keep the 
>>>> comment about the default argument.
>>>
>>> This overload doesn't take a default argument.  (There was a stray
>>> declaration of a similar function at the top of the file that had
>>> one.  I've removed it.)
>>
>> Ah, true.
>>
>>>>> -      if (!size || TREE_CODE (size) != INTEGER_CST)
>>>>> -       return false;
>>>>  >...
>>>>
>>>> You change some failure cases in compute_objsize to return success 
>>>> with a maximum range, while others continue to return failure.  This 
>>>> needs commentary about the design rationale.
>>>
>>> This is too much for a comment in the code but the background is
>>> this: compute_objsize initially returned the object size as a constant.
>>> Recently, I have enhanced it to return a range to improve warnings for
>>> allocated objects.  With that, a failure can be turned into success by
>>> having the function set the range to that of the largest object.  That
>>> should simplify the function's callers and could even improve
>>> the detection of some invalid accesses.  Once this change is made
>>> it might even be possible to change its return type to void.
>>>
>>> The change that caught your eye is necessary to make the function
>>> a drop-in replacement for the C++ front end code which makes this
>>> same assumption.  Without it, a number of test cases that exercise
>>> VLAs fail in g++.dg/warn/Wplacement-new-size-5.C.  For example:
>>>
>>>    void f (int n)
>>>    {
>>>      char a[n];
>>>      new (a - 1) int ();
>>>    }
>>>
>>> Changing any of the other places isn't necessary for existing tests
>>> to pass (and I didn't want to introduce too much churn).  But I do
>>> want to change the rest of the function along the same lines at some
>>> point.
>>
>> Please do change the other places to be consistent; better to have 
>> more churn than to leave the function half-updated.  That can be a 
>> separate patch if you prefer, but let's do it now rather than later.
> 
> I've made most of these changes in the other patch (also attached).
> I'm quite happy with the result but it turned out to be a lot more
> work than either of us expected, mostly due to the amount of testing.
> 
> I've left a couple of failing cases in place mainly as reminders
> to handle them better (which means I also didn't change the caller
> to avoid testing for failures).  I've also added TODO notes with
> reminders to handle some of the new codes more completely.
> 
>>
>>>>> +  special_array_member sam{ };
>>>>
>>>> sam is always set by component_ref_size, so I don't think it's 
>>>> necessary to initialize it at the declaration.
>>>
>>> I find initializing pass-by-pointer local variables helpful but
>>> I don't insist on it.
>>>
>>>>
>>>>> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
>>>>>    tree last_type = TREE_TYPE (last);
>>>>>    if (TREE_CODE (last_type) != ARRAY_TYPE
>>>>>        || TYPE_SIZE (last_type))
>>>>> -    return size;
>>>>> +    return size ? size : TYPE_SIZE_UNIT (type);
>>>>
>>>> This change seems to violate the comment for the function.
>>>
>>> By my reading (and writing) the change is covered by the first
>>> sentence:
>>>
>>>     Returns the size of the object designated by DECL considering
>>>     its initializer if it either has one or if it would not affect
>>>     its size, ...
>>
>> OK, I see it now.
>>
>>> It handles a number of cases in Wplacement-new-size.C fail that
>>> construct a larger object in an extern declaration of a template,
>>> like this:
>>>
>>>    template <class> struct S { char c; };
>>>    extern S<int> s;
>>>
>>>    void f ()
>>>    {
>>>      new (&s) int ();
>>>    }
>>>
>>> I don't know why DECL_SIZE isn't set here (I don't think it can
>>> be anything but equal to TYPE_SIZE, can it?) and other than struct
>>> objects with a flexible array member where this identity doesn't
>>> hold I can't think of others.  Am I missing something?
>>
>> Good question.  The attached patch should fix that, so you shouldn't 
>> need the change to decl_init_size:
> 
> I've integrated it into the bug fix.
> 
> Besides the usual x86_64-linux bootstrap/regtest I tested both
> patches by building a few packages, including Binutils/GDB, Glibc,
> and  verifying no new warnings show up.
> 
> Martin


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-09-21 21:17         ` [PING][PATCH] " Martin Sebor
@ 2020-09-22 20:05           ` Martin Sebor
  2020-09-26  5:17             ` Jason Merrill
  0 siblings, 1 reply; 28+ messages in thread
From: Martin Sebor @ 2020-09-22 20:05 UTC (permalink / raw)
  To: Jason Merrill, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 6599 bytes --]

The rebased and retested patches are attached.

On 9/21/20 3:17 PM, Martin Sebor wrote:
> Ping: https://gcc.gnu.org/pipermail/gcc-patches/2020-September/553906.html
> 
> (I'm working on rebasing the patch on top of the latest trunk which
> has changed some of the same code but it'd be helpful to get a go-
> ahead on substance the changes.  I don't expect the rebase to
> require any substantive modifications.)
> 
> Martin
> 
> On 9/14/20 4:01 PM, Martin Sebor wrote:
>> On 9/4/20 11:14 AM, Jason Merrill wrote:
>>> On 9/3/20 2:44 PM, Martin Sebor wrote:
>>>> On 9/1/20 1:22 PM, Jason Merrill wrote:
>>>>> On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
>>>>>> -Wplacement-new handles array indices and pointer offsets the same:
>>>>>> by adjusting them by the size of the element.  That's correct for
>>>>>> the latter but wrong for the former, causing false positives when
>>>>>> the element size is greater than one.
>>>>>>
>>>>>> In addition, the warning doesn't even attempt to handle arrays of
>>>>>> arrays.  I'm not sure if I forgot or if I simply didn't think of
>>>>>> it.
>>>>>>
>>>>>> The attached patch corrects these oversights by replacing most
>>>>>> of the -Wplacement-new code with a call to compute_objsize which
>>>>>> handles all this correctly (plus more), and is also better tested.
>>>>>> But even compute_objsize has bugs: it trips up while converting
>>>>>> wide_int to offset_int for some pointer offset ranges.  Since
>>>>>> handling the C++ IL required changes in this area the patch also
>>>>>> fixes that.
>>>>>>
>>>>>> For review purposes, the patch affects just the middle end.
>>>>>> The C++ diff pretty much just removes code from the front end.
>>>>>
>>>>> The C++ changes are OK.
>>>>
>>>> Thank you for looking at the rest as well.
>>>>
>>>>>
>>>>>> -compute_objsize (tree ptr, int ostype, access_ref *pref,
>>>>>> -                bitmap *visited, const vr_values *rvals /* = NULL 
>>>>>> */)
>>>>>> +compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap 
>>>>>> *visited,
>>>>>> +                const vr_values *rvals)
>>>>>
>>>>> This reformatting seems unnecessary, and I prefer to keep the 
>>>>> comment about the default argument.
>>>>
>>>> This overload doesn't take a default argument.  (There was a stray
>>>> declaration of a similar function at the top of the file that had
>>>> one.  I've removed it.)
>>>
>>> Ah, true.
>>>
>>>>>> -      if (!size || TREE_CODE (size) != INTEGER_CST)
>>>>>> -       return false;
>>>>>  >...
>>>>>
>>>>> You change some failure cases in compute_objsize to return success 
>>>>> with a maximum range, while others continue to return failure.  
>>>>> This needs commentary about the design rationale.
>>>>
>>>> This is too much for a comment in the code but the background is
>>>> this: compute_objsize initially returned the object size as a constant.
>>>> Recently, I have enhanced it to return a range to improve warnings for
>>>> allocated objects.  With that, a failure can be turned into success by
>>>> having the function set the range to that of the largest object.  That
>>>> should simplify the function's callers and could even improve
>>>> the detection of some invalid accesses.  Once this change is made
>>>> it might even be possible to change its return type to void.
>>>>
>>>> The change that caught your eye is necessary to make the function
>>>> a drop-in replacement for the C++ front end code which makes this
>>>> same assumption.  Without it, a number of test cases that exercise
>>>> VLAs fail in g++.dg/warn/Wplacement-new-size-5.C.  For example:
>>>>
>>>>    void f (int n)
>>>>    {
>>>>      char a[n];
>>>>      new (a - 1) int ();
>>>>    }
>>>>
>>>> Changing any of the other places isn't necessary for existing tests
>>>> to pass (and I didn't want to introduce too much churn).  But I do
>>>> want to change the rest of the function along the same lines at some
>>>> point.
>>>
>>> Please do change the other places to be consistent; better to have 
>>> more churn than to leave the function half-updated.  That can be a 
>>> separate patch if you prefer, but let's do it now rather than later.
>>
>> I've made most of these changes in the other patch (also attached).
>> I'm quite happy with the result but it turned out to be a lot more
>> work than either of us expected, mostly due to the amount of testing.
>>
>> I've left a couple of failing cases in place mainly as reminders
>> to handle them better (which means I also didn't change the caller
>> to avoid testing for failures).  I've also added TODO notes with
>> reminders to handle some of the new codes more completely.
>>
>>>
>>>>>> +  special_array_member sam{ };
>>>>>
>>>>> sam is always set by component_ref_size, so I don't think it's 
>>>>> necessary to initialize it at the declaration.
>>>>
>>>> I find initializing pass-by-pointer local variables helpful but
>>>> I don't insist on it.
>>>>
>>>>>
>>>>>> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
>>>>>>    tree last_type = TREE_TYPE (last);
>>>>>>    if (TREE_CODE (last_type) != ARRAY_TYPE
>>>>>>        || TYPE_SIZE (last_type))
>>>>>> -    return size;
>>>>>> +    return size ? size : TYPE_SIZE_UNIT (type);
>>>>>
>>>>> This change seems to violate the comment for the function.
>>>>
>>>> By my reading (and writing) the change is covered by the first
>>>> sentence:
>>>>
>>>>     Returns the size of the object designated by DECL considering
>>>>     its initializer if it either has one or if it would not affect
>>>>     its size, ...
>>>
>>> OK, I see it now.
>>>
>>>> It handles a number of cases in Wplacement-new-size.C fail that
>>>> construct a larger object in an extern declaration of a template,
>>>> like this:
>>>>
>>>>    template <class> struct S { char c; };
>>>>    extern S<int> s;
>>>>
>>>>    void f ()
>>>>    {
>>>>      new (&s) int ();
>>>>    }
>>>>
>>>> I don't know why DECL_SIZE isn't set here (I don't think it can
>>>> be anything but equal to TYPE_SIZE, can it?) and other than struct
>>>> objects with a flexible array member where this identity doesn't
>>>> hold I can't think of others.  Am I missing something?
>>>
>>> Good question.  The attached patch should fix that, so you shouldn't 
>>> need the change to decl_init_size:
>>
>> I've integrated it into the bug fix.
>>
>> Besides the usual x86_64-linux bootstrap/regtest I tested both
>> patches by building a few packages, including Binutils/GDB, Glibc,
>> and  verifying no new warnings show up.
>>
>> Martin
> 


[-- Attachment #2: gcc-96511.diff --]
[-- Type: text/x-patch, Size: 44370 bytes --]

Correct handling of indices into arrays with elements larger than 1 (PR c++/96511)

Resolves:
PR c++/96511 - Incorrect -Wplacement-new on POINTER_PLUS into an array with 4-byte elements
PR middle-end/96384 - bogus -Wstringop-overflow= storing into multidimensional array with index in range

gcc/ChangeLog:

	PR c++/96511
	PR middle-end/96384
	* builtins.c (get_range): Return full range of type when neither
	value nor its range is available.  Fail for ranges inverted due
	to the signedness of offsets.
	(compute_objsize): Handle more special array members.  Handle
	POINTER_PLUS_EXPR and VIEW_CONVERT_EXPR that come up in front end
	code.
	(access_ref::offset_bounded): Define new member function.
	* builtins.h (access_ref::eval): New data member.
	(access_ref::offset_bounded): New member function.
	(access_ref::offset_zero): New member function.
	(compute_objsize): Declare a new overload.
	* gimple-array-bounds.cc (array_bounds_checker::check_array_ref): Use
	enum special_array_member.
	* tree-object-size.c (decl_init_size): Return the size of the structure
	type if the decl size is null.
	* tree.c (component_ref_size): Use special_array_member.
	* tree.h (special_array_member): Define a new type.
	(component_ref_size): Change signature/	

gcc/cp/ChangeLog:

	PR c++/96511
	PR middle-end/96384
	* decl.c (complete_vars): Call layout_decl.
	* init.c (warn_placement_new_too_small): Call builtin_objsize instead
	of duplicating what it does.

gcc/testsuite/ChangeLog:

	PR c++/96511
	PR middle-end/96384
	* g++.dg/warn/Wplacement-new-size-1.C: Relax warnings.
	* g++.dg/warn/Wplacement-new-size-2.C: Same.
	* g++.dg/warn/Wplacement-new-size-6.C: Same.
	* gcc.dg/Warray-bounds-58.c: Adjust
	* gcc.dg/Wstringop-overflow-37.c: Same.
	* g++.dg/warn/Wplacement-new-size-7.C: New test.
	* gcc.dg/Wstringop-overflow-40.c: New test.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 45efc1c3992..b5680bf93dd 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -199,7 +199,7 @@ static void expand_builtin_sync_synchronize (void);
 
 access_ref::access_ref (tree bound /* = NULL_TREE */,
 			bool minaccess /* = false */)
-  : ref ()
+: ref (), eval ([](tree x){ return x; }), trail1special (true)
 {
   /* Set to valid.  */
   offrng[0] = offrng[1] = 0;
@@ -4302,10 +4302,34 @@ static bool
 get_range (tree x, signop sgn, offset_int r[2],
 	   const vr_values *rvals /* = NULL */)
 {
+  tree type = TREE_TYPE (x);
+  if (TREE_CODE (x) != INTEGER_CST
+      && TREE_CODE (x) != SSA_NAME)
+    {
+      if (TYPE_UNSIGNED (type))
+	{
+	  if (sgn == SIGNED)
+	    type = signed_type_for (type);
+	}
+      else if (sgn == UNSIGNED)
+	type = unsigned_type_for (type);
+
+      r[0] = wi::to_offset (TYPE_MIN_VALUE (type));
+      r[1] = wi::to_offset (TYPE_MAX_VALUE (type));
+      return x;
+    }
+
   wide_int wr[2];
   if (!get_range (x, wr, rvals))
     return false;
 
+  /* Only convert signed integers or unsigned sizetype to a signed
+     offset and avoid converting large positive values in narrower
+     types to negative offsets.  */
+  if (TYPE_UNSIGNED (type)
+      && wr[0].get_precision () < TYPE_PRECISION (sizetype))
+    sgn = UNSIGNED;
+
   r[0] = offset_int::from (wr[0], sgn);
   r[1] = offset_int::from (wr[1], sgn);
   return true;
@@ -4326,9 +4350,11 @@ get_range (tree x, signop sgn, offset_int r[2],
    to influence code generation or optimization.  */
 
 static bool
-compute_objsize (tree ptr, int ostype, access_ref *pref,
-		 bitmap *visited, const vr_values *rvals /* = NULL */)
+compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
+		 const vr_values *rvals)
 {
+  STRIP_NOPS (ptr);
+
   const bool addr = TREE_CODE (ptr) == ADDR_EXPR;
   if (addr)
     ptr = TREE_OPERAND (ptr, 0);
@@ -4340,12 +4366,15 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
       if (!addr && POINTER_TYPE_P (TREE_TYPE (ptr)))
 	return false;
 
-      tree size = decl_init_size (ptr, false);
-      if (!size || TREE_CODE (size) != INTEGER_CST)
-	return false;
-
       pref->ref = ptr;
-      pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
+      if (tree size = decl_init_size (ptr, false))
+	if (TREE_CODE (size) == INTEGER_CST)
+	  {
+	    pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
+	    return true;
+	  }
+      pref->sizrng[0] = 0;
+      pref->sizrng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
       return true;
     }
 
@@ -4353,13 +4382,13 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
   if (code == COMPONENT_REF)
     {
+      tree ref = TREE_OPERAND (ptr, 0);
       tree field = TREE_OPERAND (ptr, 1);
 
       if (ostype == 0)
 	{
 	  /* For raw memory functions like memcpy bail if the size
 	     of the enclosing object cannot be determined.  */
-	  tree ref = TREE_OPERAND (ptr, 0);
 	  if (!compute_objsize (ref, ostype, pref, visited, rvals)
 	      || !pref->ref)
 	    return false;
@@ -4381,20 +4410,28 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 	return false;
 
       pref->ref = field;
-      /* Only return constant sizes for now while callers depend
-	 on it.  INT0LEN is true for interior zero-length arrays.  */
-      bool int0len = false;
-      tree size = component_ref_size (ptr, &int0len);
-      if (int0len)
+
+      /* SAM is set for array members that might need special treatment.  */
+      special_array_member sam;
+      tree size = component_ref_size (ptr, &sam);
+      if (sam == special_array_member::int_0)
+	pref->sizrng[0] = pref->sizrng[1] = 0;
+      else if (!pref->trail1special && sam == special_array_member::trail_1)
+	pref->sizrng[0] = pref->sizrng[1] = 1;
+      else if (size && TREE_CODE (size) == INTEGER_CST)
+	pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
+      else
 	{
-	  pref->sizrng[0] = pref->sizrng[1] = 0;
-	  return true;
+	  /* When the size of the member is unknown it's either a flexible
+	     array member or a trailing special array member (either zero
+	     length or one-element).  Set the size to the maximum minus
+	     the constant size of the type.  */
+	  pref->sizrng[0] = 0;
+	  pref->sizrng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
+	  if (tree recsize = TYPE_SIZE_UNIT (TREE_TYPE (ref)))
+	    if (TREE_CODE (recsize) == INTEGER_CST)
+	      pref->sizrng[1] -= wi::to_offset (recsize);
 	}
-
-      if (!size || TREE_CODE (size) != INTEGER_CST)
-	return false;
-
-      pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
       return true;
     }
 
@@ -4424,7 +4461,7 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 	return false;
 
       offset_int orng[2];
-      tree off = TREE_OPERAND (ptr, 1);
+      tree off = pref->eval (TREE_OPERAND (ptr, 1));
       if (!get_range (off, SIGNED, orng, rvals))
 	/* Fail unless the size of the object is zero.  */
 	return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1];
@@ -4454,11 +4491,22 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
 	  if (ostype && TREE_CODE (eltype) == ARRAY_TYPE)
 	    {
-	      /* Execpt for the permissive raw memory functions which
-		 use the size of the whole object determined above,
-		 use the size of the referenced array.  */
-	      pref->sizrng[0] = pref->offrng[0] + orng[0] + sz;
-	      pref->sizrng[1] = pref->offrng[1] + orng[1] + sz;
+	      /* Except for the permissive raw memory functions which use
+		 the size of the whole object determined above, use the size
+		 of the referenced array.  Because the overall offset is from
+		 the beginning of the complete array object add this overall
+		 offset to the size of array.  */
+	      offset_int sizrng[2] =
+		{
+		 pref->offrng[0] + orng[0] + sz,
+		 pref->offrng[1] + orng[1] + sz
+		};
+	      if (sizrng[1] < sizrng[0])
+		std::swap (sizrng[0], sizrng[1]);
+	      if (sizrng[0] >= 0 && sizrng[0] <= pref->sizrng[0])
+		pref->sizrng[0] = sizrng[0];
+	      if (sizrng[1] >= 0 && sizrng[1] <= pref->sizrng[1])
+		pref->sizrng[1] = sizrng[1];
 	    }
 	}
 
@@ -4467,6 +4515,28 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
       return true;
     }
+  else if (code == POINTER_PLUS_EXPR)
+    {
+      tree ref = TREE_OPERAND (ptr, 0);
+      if (!compute_objsize (ref, ostype, pref, visited, rvals))
+	return false;
+
+      offset_int orng[2];
+      tree off = pref->eval (TREE_OPERAND (ptr, 1));
+      if (!get_range (off, SIGNED, orng, rvals))
+	/* Fail unless the size of the object is zero.  */
+	return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1];
+
+      pref->offrng[0] += orng[0];
+      pref->offrng[1] += orng[1];
+
+      return true;
+    }
+  else if (code == VIEW_CONVERT_EXPR)
+    {
+      ptr = TREE_OPERAND (ptr, 0);
+      return compute_objsize (ptr, ostype, pref, visited, rvals);
+    }
 
   if (TREE_CODE (ptr) == SSA_NAME)
     {
@@ -12436,3 +12506,14 @@ builtin_with_linkage_p (tree decl)
     }
   return false;
 }
+
+bool
+access_ref::offset_bounded () const
+{
+  if (offrng[0] == offrng[1])
+    return false;
+
+  tree min = TYPE_MIN_VALUE (ptrdiff_type_node);
+  tree max = TYPE_MAX_VALUE (ptrdiff_type_node);
+  return offrng[0] <= wi::to_offset (min) || offrng[1] >= wi::to_offset (max);
+}
diff --git a/gcc/builtins.h b/gcc/builtins.h
index 8136b768750..39415aade05 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -172,6 +172,22 @@ struct access_ref
      For string functions the size of the actual access is
      further constrained by the length of the string.  */
   offset_int bndrng[2];
+
+  /* Return true if OFFRNG is the constant zero.  */
+  bool offset_zero () const
+  {
+    return offrng[0] == 0 && offrng[1] == 0;
+  }
+
+  /* Return true if OFFRNG is bounded to a subrange of possible offset
+     values.  */
+  bool offset_bounded () const;
+
+  /* Used to fold integer expressions when called from front ends.  */
+  tree (*eval)(tree);
+  /* Set if trailing one-element arrays should be treated as flexible
+     array members.  */
+  bool trail1special;
 };
 
 /* Describes a pair of references used in an access by built-in
@@ -197,13 +213,13 @@ struct access_data
 
 class vr_values;
 extern tree gimple_call_alloc_size (gimple *, wide_int[2] = NULL,
+			     const vr_values * = NULL);
+extern tree gimple_parm_array_size (tree, wide_int[2],
 				    const vr_values * = NULL);
-extern tree gimple_parm_array_size (tree, wide_int[2], const vr_values * = NULL);
+extern tree compute_objsize (tree, int, access_ref *, const vr_values * = NULL);
 extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL,
 			     const vr_values * = NULL);
-extern tree compute_objsize (tree, int, access_ref *, const vr_values * = NULL);
-
-extern bool check_access (tree, tree, tree, tree, tree, access_mode,
-			  const access_data * = NULL);
+extern bool check_access (tree, tree, tree, tree, tree,
+			  access_mode, const access_data * = NULL);
 
 #endif /* GCC_BUILTINS_H */
diff --git a/gcc/cp/init.c b/gcc/cp/init.c
index e84e985492d..7e7dabb7d70 100644
--- a/gcc/cp/init.c
+++ b/gcc/cp/init.c
@@ -34,6 +34,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "attribs.h"
 #include "asan.h"
 #include "stor-layout.h"
+#include "builtins.h"
 
 static bool begin_init_stmts (tree *, tree *);
 static tree finish_init_stmts (bool, tree, tree);
@@ -2564,27 +2565,6 @@ throw_bad_array_new_length (void)
   return build_cxx_call (fn, 0, NULL, tf_warning_or_error);
 }
 
-/* Attempt to find the initializer for flexible array field T in the
-   initializer INIT, when non-null.  Returns the initializer when
-   successful and NULL otherwise.  */
-static tree
-find_flexarray_init (tree t, tree init)
-{
-  if (!init || init == error_mark_node)
-    return NULL_TREE;
-
-  unsigned HOST_WIDE_INT idx;
-  tree field, elt;
-
-  /* Iterate over all top-level initializer elements.  */
-  FOR_EACH_CONSTRUCTOR_ELT (CONSTRUCTOR_ELTS (init), idx, field, elt)
-    /* If the member T is found, return it.  */
-    if (field == t)
-      return elt;
-
-  return NULL_TREE;
-}
-
 /* Attempt to verify that the argument, OPER, of a placement new expression
    refers to an object sufficiently large for an object of TYPE or an array
    of NELTS of such objects when NELTS is non-null, and issue a warning when
@@ -2601,17 +2581,6 @@ warn_placement_new_too_small (tree type, tree nelts, tree size, tree oper)
 {
   location_t loc = cp_expr_loc_or_input_loc (oper);
 
-  /* The number of bytes to add to or subtract from the size of the provided
-     buffer based on an offset into an array or an array element reference.
-     Although intermediate results may be negative (as in a[3] - 2) a valid
-     final result cannot be.  */
-  offset_int adjust = 0;
-  /* True when the size of the entire destination object should be used
-     to compute the possibly optimistic estimate of the available space.  */
-  bool use_obj_size = false;
-  /* True when the reference to the destination buffer is an ADDR_EXPR.  */
-  bool addr_expr = false;
-
   STRIP_NOPS (oper);
 
   /* Using a function argument or a (non-array) variable as an argument
@@ -2625,231 +2594,91 @@ warn_placement_new_too_small (tree type, tree nelts, tree size, tree oper)
   /* Evaluate any constant expressions.  */
   size = fold_non_dependent_expr (size);
 
-  /* Handle the common case of array + offset expression when the offset
-     is a constant.  */
-  if (TREE_CODE (oper) == POINTER_PLUS_EXPR)
-    {
-      /* If the offset is compile-time constant, use it to compute a more
-	 accurate estimate of the size of the buffer.  Since the operand
-	 of POINTER_PLUS_EXPR is represented as an unsigned type, convert
-	 it to signed first.
-	 Otherwise, use the size of the entire array as an optimistic
-	 estimate (this may lead to false negatives).  */
-      tree adj = TREE_OPERAND (oper, 1);
-      adj = fold_for_warn (adj);
-      if (CONSTANT_CLASS_P (adj))
-	adjust += wi::to_offset (convert (ssizetype, adj));
-      else
-	use_obj_size = true;
-
-      oper = TREE_OPERAND (oper, 0);
-
-      STRIP_NOPS (oper);
-    }
-
-  if (TREE_CODE (oper) == TARGET_EXPR)
-    oper = TREE_OPERAND (oper, 1);
-  else if (TREE_CODE (oper) == ADDR_EXPR)
-    {
-      addr_expr = true;
-      oper = TREE_OPERAND (oper, 0);
-    }
-
-  STRIP_NOPS (oper);
+  access_ref ref;
+  ref.eval = [](tree x){ return fold_non_dependent_expr (x); };
+  ref.trail1special = warn_placement_new < 2;
+  tree objsize =  compute_objsize (oper, 1, &ref);
+  if (!objsize)
+    return;
 
-  if (TREE_CODE (oper) == ARRAY_REF
-      && (addr_expr || TREE_CODE (TREE_TYPE (oper)) == ARRAY_TYPE))
-    {
-      /* Similar to the offset computed above, see if the array index
-	 is a compile-time constant.  If so, and unless the offset was
-	 not a compile-time constant, use the index to determine the
-	 size of the buffer.  Otherwise, use the entire array as
-	 an optimistic estimate of the size.  */
-      const_tree adj = fold_non_dependent_expr (TREE_OPERAND (oper, 1));
-      if (!use_obj_size && CONSTANT_CLASS_P (adj))
-	adjust += wi::to_offset (adj);
-      else
-	{
-	  use_obj_size = true;
-	  adjust = 0;
-	}
+  const bool exact_size = ref.offrng[0] == ref.offrng[1];
 
-      oper = TREE_OPERAND (oper, 0);
-    }
+  offset_int bytes_avail = wi::to_offset (objsize);
+  offset_int bytes_need;
 
-  /* Refers to the declared object that constains the subobject referenced
-     by OPER.  When the object is initialized, makes it possible to determine
-     the actual size of a flexible array member used as the buffer passed
-     as OPER to placement new.  */
-  tree var_decl = NULL_TREE;
-  /* True when operand is a COMPONENT_REF, to distinguish flexible array
-     members from arrays of unspecified size.  */
-  bool compref = TREE_CODE (oper) == COMPONENT_REF;
-
-  /* For COMPONENT_REF (i.e., a struct member) the size of the entire
-     enclosing struct.  Used to validate the adjustment (offset) into
-     an array at the end of a struct.  */
-  offset_int compsize = 0;
-
-  /* Descend into a struct or union to find the member whose address
-     is being used as the argument.  */
-  if (TREE_CODE (oper) == COMPONENT_REF)
+  if (CONSTANT_CLASS_P (size))
+    bytes_need = wi::to_offset (size);
+  else if (nelts && CONSTANT_CLASS_P (nelts))
+    bytes_need = (wi::to_offset (nelts)
+		  * wi::to_offset (TYPE_SIZE_UNIT (type)));
+  else if (tree_fits_uhwi_p (TYPE_SIZE_UNIT (type)))
+    bytes_need = wi::to_offset (TYPE_SIZE_UNIT (type));
+  else
     {
-      tree comptype = TREE_TYPE (TREE_OPERAND (oper, 0));
-      compsize = wi::to_offset (TYPE_SIZE_UNIT (comptype));
-
-      tree op0 = oper;
-      while (TREE_CODE (op0 = TREE_OPERAND (op0, 0)) == COMPONENT_REF);
-      STRIP_ANY_LOCATION_WRAPPER (op0);
-      if (VAR_P (op0))
-	var_decl = op0;
-      oper = TREE_OPERAND (oper, 1);
+      /* The type is a VLA.  */
+      return;
     }
 
-  STRIP_ANY_LOCATION_WRAPPER (oper);
-  tree opertype = TREE_TYPE (oper);
-  if ((addr_expr || !INDIRECT_TYPE_P (opertype))
-      && (VAR_P (oper)
-	  || TREE_CODE (oper) == FIELD_DECL
-	  || TREE_CODE (oper) == PARM_DECL))
-    {
-      /* A possibly optimistic estimate of the number of bytes available
-	 in the destination buffer.  */
-      offset_int bytes_avail = 0;
-      /* True when the estimate above is in fact the exact size
-	 of the destination buffer rather than an estimate.  */
-      bool exact_size = true;
-
-      /* Treat members of unions and members of structs uniformly, even
-	 though the size of a member of a union may be viewed as extending
-	 to the end of the union itself (it is by __builtin_object_size).  */
-      if ((VAR_P (oper) || use_obj_size)
-	  && DECL_SIZE_UNIT (oper)
-	  && tree_fits_uhwi_p (DECL_SIZE_UNIT (oper)))
-	{
-	  /* Use the size of the entire array object when the expression
-	     refers to a variable or its size depends on an expression
-	     that's not a compile-time constant.  */
-	  bytes_avail = wi::to_offset (DECL_SIZE_UNIT (oper));
-	  exact_size = !use_obj_size;
-	}
-      else if (tree opersize = TYPE_SIZE_UNIT (opertype))
-	{
-	  /* Use the size of the type of the destination buffer object
-	     as the optimistic estimate of the available space in it.
-	     Use the maximum possible size for zero-size arrays and
-	     flexible array members (except of initialized objects
-	     thereof).  */
-	  if (TREE_CODE (opersize) == INTEGER_CST)
-	    bytes_avail = wi::to_offset (opersize);
-	}
-
-      if (bytes_avail == 0)
-	{
-	  if (var_decl)
-	    {
-	      /* Constructing into a buffer provided by the flexible array
-		 member of a declared object (which is permitted as a G++
-		 extension).  If the array member has been initialized,
-		 determine its size from the initializer.  Otherwise,
-		 the array size is zero.  */
-	      if (tree init = find_flexarray_init (oper,
-						   DECL_INITIAL (var_decl)))
-		bytes_avail = wi::to_offset (TYPE_SIZE_UNIT (TREE_TYPE (init)));
-	    }
-	  else
-	    bytes_avail = (wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node))
-			   - compsize);
-	}
-
-      tree_code oper_code = TREE_CODE (opertype);
-
-      if (compref && oper_code == ARRAY_TYPE)
-	{
-	  tree nelts = array_type_nelts_top (opertype);
-	  tree nelts_cst = maybe_constant_value (nelts);
-	  if (TREE_CODE (nelts_cst) == INTEGER_CST
-	      && integer_onep (nelts_cst)
-	      && !var_decl
-	      && warn_placement_new < 2)
-	    return;
-	}
-
-      /* Reduce the size of the buffer by the adjustment computed above
-	 from the offset and/or the index into the array.  */
-      if (bytes_avail < adjust || adjust < 0)
-	bytes_avail = 0;
-      else
-	{
-	  tree elttype = (TREE_CODE (opertype) == ARRAY_TYPE
-			  ? TREE_TYPE (opertype) : opertype);
-	  if (tree eltsize = TYPE_SIZE_UNIT (elttype))
-	    {
-	      bytes_avail -= adjust * wi::to_offset (eltsize);
-	      if (bytes_avail < 0)
-		bytes_avail = 0;
-	    }
-	}
-
-      /* The minimum amount of space needed for the allocation.  This
-	 is an optimistic estimate that makes it possible to detect
-	 placement new invocation for some undersize buffers but not
-	 others.  */
-      offset_int bytes_need;
+  if (bytes_avail >= bytes_need)
+    return;
 
-      if (nelts)
-	nelts = fold_for_warn (nelts);
-
-      if (CONSTANT_CLASS_P (size))
-	bytes_need = wi::to_offset (size);
-      else if (nelts && CONSTANT_CLASS_P (nelts))
-	bytes_need = (wi::to_offset (nelts)
-		      * wi::to_offset (TYPE_SIZE_UNIT (type)));
-      else if (tree_fits_uhwi_p (TYPE_SIZE_UNIT (type)))
-	bytes_need = wi::to_offset (TYPE_SIZE_UNIT (type));
-      else
-	{
-	  /* The type is a VLA.  */
-	  return;
-	}
+  tree opertype = ref.ref ? TREE_TYPE (ref.ref) : TREE_TYPE (oper);
+  bool warned = false;
+  if (nelts)
+    nelts = fold_for_warn (nelts);
+  if (nelts)
+    if (CONSTANT_CLASS_P (nelts))
+      warned = warning_at (loc, OPT_Wplacement_new_,
+			   (exact_size
+			    ? G_("placement new constructing an object "
+				 "of type %<%T [%wu]%> and size %qwu "
+				 "in a region of type %qT and size %qwi")
+			    : G_("placement new constructing an object "
+				 "of type %<%T [%wu]%> and size %qwu "
+				 "in a region of type %qT and size "
+				 "at most %qwu")),
+			   type, tree_to_uhwi (nelts),
+			   bytes_need.to_uhwi (),
+			   opertype, bytes_avail.to_uhwi ());
+    else
+      warned = warning_at (loc, OPT_Wplacement_new_,
+			   (exact_size
+			    ? G_("placement new constructing an array "
+				 "of objects of type %qT and size %qwu "
+				 "in a region of type %qT and size %qwi")
+			    : G_("placement new constructing an array "
+				 "of objects of type %qT and size %qwu "
+				 "in a region of type %qT and size "
+				 "at most %qwu")),
+			   type, bytes_need.to_uhwi (), opertype,
+			   bytes_avail.to_uhwi ());
+  else
+    warned = warning_at (loc, OPT_Wplacement_new_,
+			 (exact_size
+			  ? G_("placement new constructing an object "
+			       "of type %qT and size %qwu in a region "
+			       "of type %qT and size %qwi")
+			  : G_("placement new constructing an object "
+			       "of type %qT "
+			       "and size %qwu in a region of type %qT "
+			       "and size at most %qwu")),
+			       type, bytes_need.to_uhwi (), opertype,
+			 bytes_avail.to_uhwi ());
+
+  if (!warned || !ref.ref)
+    return;
 
-      if (bytes_avail < bytes_need)
-	{
-	  if (nelts)
-	    if (CONSTANT_CLASS_P (nelts))
-	      warning_at (loc, OPT_Wplacement_new_,
-			  exact_size ?
-			  "placement new constructing an object of type "
-			  "%<%T [%wu]%> and size %qwu in a region of type %qT "
-			  "and size %qwi"
-			  : "placement new constructing an object of type "
-			  "%<%T [%wu]%> and size %qwu in a region of type %qT "
-			  "and size at most %qwu",
-			  type, tree_to_uhwi (nelts), bytes_need.to_uhwi (),
-			  opertype, bytes_avail.to_uhwi ());
-	    else
-	      warning_at (loc, OPT_Wplacement_new_,
-			  exact_size ?
-			  "placement new constructing an array of objects "
-			  "of type %qT and size %qwu in a region of type %qT "
-			  "and size %qwi"
-			  : "placement new constructing an array of objects "
-			  "of type %qT and size %qwu in a region of type %qT "
-			  "and size at most %qwu",
-			  type, bytes_need.to_uhwi (), opertype,
-			  bytes_avail.to_uhwi ());
-	  else
-	    warning_at (loc, OPT_Wplacement_new_,
-			exact_size ?
-			"placement new constructing an object of type %qT "
-			"and size %qwu in a region of type %qT and size %qwi"
-			: "placement new constructing an object of type %qT "
-			"and size %qwu in a region of type %qT and size "
-			"at most %qwu",
-			type, bytes_need.to_uhwi (), opertype,
-			bytes_avail.to_uhwi ());
-	}
-    }
+  if (ref.offset_zero () || !ref.offset_bounded ())
+    inform (DECL_SOURCE_LOCATION (ref.ref),
+	    "%qD declared here", ref.ref);
+  else if (ref.offrng[0] == ref.offrng[1])
+    inform (DECL_SOURCE_LOCATION (ref.ref),
+	    "at offset %wi from %qD declared here",
+	    ref.offrng[0].to_shwi (), ref.ref);
+  else
+    inform (DECL_SOURCE_LOCATION (ref.ref),
+	    "at offset [%wi, %wi] from %qD declared here",
+	    ref.offrng[0].to_shwi (), ref.offrng[1].to_shwi (), ref.ref);
 }
 
 /* True if alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__.  */
diff --git a/gcc/gimple-array-bounds.cc b/gcc/gimple-array-bounds.cc
index b93ef7a7b74..ddeca5fb2a3 100644
--- a/gcc/gimple-array-bounds.cc
+++ b/gcc/gimple-array-bounds.cc
@@ -188,7 +188,7 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
   tree decl = NULL_TREE;
 
   /* Set for accesses to interior zero-length arrays.  */
-  bool interior_zero_len = false;
+  special_array_member sam{ };
 
   tree up_bound_p1;
 
@@ -220,7 +220,7 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
 	    {
 	      /* Try to determine the size of the trailing array from
 		 its initializer (if it has one).  */
-	      if (tree refsize = component_ref_size (arg, &interior_zero_len))
+	      if (tree refsize = component_ref_size (arg, &sam))
 		if (TREE_CODE (refsize) == INTEGER_CST)
 		  maxbound = refsize;
 	    }
@@ -325,7 +325,7 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
 			 "array subscript %E is below array bounds of %qT",
 			 low_sub, artype);
 
-  if (!warned && interior_zero_len)
+  if (!warned && sam == special_array_member::int_0)
     warned = warning_at (location, OPT_Wzero_length_bounds,
 			 (TREE_CODE (low_sub) == INTEGER_CST
 			  ? G_("array subscript %E is outside the bounds "
diff --git a/gcc/testsuite/g++.dg/init/strlen.C b/gcc/testsuite/g++.dg/init/strlen.C
index aa8950e2dc0..cc650d65dbe 100644
--- a/gcc/testsuite/g++.dg/init/strlen.C
+++ b/gcc/testsuite/g++.dg/init/strlen.C
@@ -29,7 +29,7 @@ test_dynamic_type (S *p)
   // distinguish invalid cases from ones like it that might be valid.
   // If/when GIMPLE changes to make this possible this test can be
   // removed.
-  char *q = new (p->a) char [16];
+  char *q = new (p->a) char [16];   // { dg-warning "\\\[-Wplacement-new" }
 
   init (q);
 
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C
index d2ec608afd4..cec83163dbe 100644
--- a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C
@@ -66,8 +66,9 @@ struct BA2 { int i; A2 a2; };
 void fBx (BAx *pbx, BAx &rbx)
 {
   BAx bax;
-  new (bax.ax.a) char;     // { dg-warning "placement" }
-  new (bax.ax.a) Int16;    // { dg-warning "placement" }
+  // The uninitialized flexible array takes up the bytes of padding.
+  new (bax.ax.a) char;
+  new (bax.ax.a) Int16;
   new (bax.ax.a) Int32;    // { dg-warning "placement" }
 
   new (pbx->ax.a) char;
@@ -84,9 +85,12 @@ void fBx1 ()
 {
   static BAx bax1 = { 1, /* Ax = */ { 2, /* a[] = */ {} } };
 
-  new (bax1.ax.a) char;	    // { dg-warning "placement" }
-  new (bax1.ax.a) char[2];  // { dg-warning "placement" }
-  new (bax1.ax.a) Int16;    // { dg-warning "placement" }
+  // The empty flexible array takes up the bytes of padding.
+  new (bax1.ax.a) char;
+  new (bax1.ax.a) char[2];
+  new (bax1.ax.a) Int16;
+  new (bax1.ax.a) char[3];
+  new (bax1.ax.a) char[4];  // { dg-warning "placement" }
   new (bax1.ax.a) Int32;    // { dg-warning "placement" }
 }
 
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C
index e00515eeaa9..e5fdfe1f603 100644
--- a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C
@@ -124,9 +124,13 @@ struct BA2 { int i; A2 a2; };
 void fBx (BAx *pbx, BAx &rbx)
 {
   BAx bax;
-  new (bax.ax.a) char;        // { dg-warning "placement" }
-  new (bax.ax.a) Int16;       // { dg-warning "placement" }
+  // The uninitialized flexible array takes up the bytes of padding.
+  new (bax.ax.a) char;
+  new (bax.ax.a) Int16;
+  new (bax.ax.a) char[3];
   new (bax.ax.a) Int32;       // { dg-warning "placement" }
+  new (bax.ax.a) char[4];     // { dg-warning "placement" }
+  new (bax.ax.a) char[5];     // { dg-warning "placement" }
 
   new (pbx->ax.a) char;
   new (rbx.ax.a) char;
@@ -142,10 +146,14 @@ void fBx1 ()
 {
   static BAx bax1 = { 1, /* Ax = */ { 2, /* a[] = */ {} } };
 
-  new (bax1.ax.a) char;	      // { dg-warning "placement" }
-  new (bax1.ax.a) char[2];    // { dg-warning "placement" }
-  new (bax1.ax.a) Int16;      // { dg-warning "placement" }
+  // The empty flexible array takes up the bytes of padding.
+  new (bax1.ax.a) char;
+  new (bax1.ax.a) char[2];
+  new (bax1.ax.a) Int16;
+  new (bax1.ax.a) char[3];
   new (bax1.ax.a) Int32;      // { dg-warning "placement" }
+  new (bax1.ax.a) char[4];    // { dg-warning "placement" }
+  new (bax1.ax.a) char[5];    // { dg-warning "placement" }
 }
 
 void fB0 (BA0 *pb0, BA0 &rb0)
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C
index b6a72b18f6a..5eb63d23b47 100644
--- a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C
@@ -17,9 +17,10 @@ void fBx1 ()
 {
   static BAx bax1 = { 1, /* Ax = */ { 2, /* a[] = */ { 3 } } }; // { dg-error "initialization of flexible array member in a nested context" }
 
-  new (bax1.ax.a) char;     // { dg-warning "placement" }
-  new (bax1.ax.a) char[2];  // { dg-warning "placement" }
-  new (bax1.ax.a) Int16;    // { dg-warning "placement" }
+  // The first three bytes of the flexible array member live in the padding.
+  new (bax1.ax.a) char;
+  new (bax1.ax.a) char[2];
+  new (bax1.ax.a) Int16;
   new (bax1.ax.a) Int32;    // { dg-warning "placement" }
 }
 
@@ -27,10 +28,11 @@ void fBx2 ()
 {
   static BAx bax2 = { 1, /* Ax = */ { 2, /* a[] = */ { 3, 4 } } }; // { dg-error "initialization of flexible array member in a nested context" }
 
-  new (bax2.ax.a) char;       // { dg-warning "placement" }
-  new (bax2.ax.a) char[2];    // { dg-warning "placement" }
-  new (bax2.ax.a) char[3];    // { dg-warning "placement" }
-  new (bax2.ax.a) Int16;      // { dg-warning "placement" }
+  // The first three bytes of the flexible array member live in the padding.
+  new (bax2.ax.a) char;
+  new (bax2.ax.a) char[2];
+  new (bax2.ax.a) char[3];
+  new (bax2.ax.a) Int16;
   new (bax2.ax.a) char[4];    // { dg-warning "placement" }
   new (bax2.ax.a) Int32;      // { dg-warning "placement" }
 }
@@ -39,10 +41,11 @@ void fBx3 ()
 {
   static BAx bax2 = { 1, /* Ax = */ { 3, /* a[] = */ { 4, 5, 6 } } }; // { dg-error "initialization of flexible array member in a nested context" }
 
-  new (bax2.ax.a) char;       // { dg-warning "placement" }
-  new (bax2.ax.a) char[2];    // { dg-warning "placement" }
-  new (bax2.ax.a) Int16;      // { dg-warning "placement" }
-  new (bax2.ax.a) char[3];    // { dg-warning "placement" }
+  // The first three bytes of the flexible array member live in the padding.
+  new (bax2.ax.a) char;
+  new (bax2.ax.a) char[2];
+  new (bax2.ax.a) Int16;
+  new (bax2.ax.a) char[3];
   new (bax2.ax.a) char[4];    // { dg-warning "placement" }
   new (bax2.ax.a) Int32;      // { dg-warning "placement" }
 }
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-7.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-7.C
new file mode 100644
index 00000000000..82f298d8008
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-7.C
@@ -0,0 +1,82 @@
+/* PR c++/96511 - Incorrect -Wplacement-new on POINTER_PLUS into an array
+   with 4-byte elements
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+typedef __INT16_TYPE__ int16_t;
+typedef __INT32_TYPE__ int32_t;
+typedef __SIZE_TYPE__  size_t;
+
+void* operator new (size_t, void *p) { return p; }
+
+void test_a1_int16 ()
+{
+  int16_t a3[3];                    // { dg-message "declared here" }
+
+  new (a3) int16_t;
+  new (a3 + 1) int16_t;
+  new (a3 + 2) int16_t;             // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[1]) int16_t;
+  new (&a3[0] + 1) int16_t;
+  new (&a3[0] + 2) int16_t;         // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[0] + 3) int16_t;         // { dg-warning "\\\[-Wplacement-new" }
+}
+
+void test_a1_int32 ()
+{
+  int16_t a3[3];
+
+  new (a3 + 1) int32_t;             // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[1]) int32_t;
+  new (&a3[0] + 1) int32_t;         // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[0] + 2) int32_t;         // { dg-warning "\\\[-Wplacement-new" }
+}
+
+
+void test_a2 ()
+{
+  int16_t a23[2][3];
+
+  new (a23 + 1) int16_t;            // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a23[1]) int16_t;
+  new (&a23[2]) int16_t;            // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[0][0] + 1) int16_t;
+  new (&a23[0][0] + 2) int16_t;
+  // Deriving a pointer to the next array from one to an element of
+  // the prior array isn't valid even if the resulting pointer points
+  // to an element of the larger array.  Verify it's diagnosed.
+  new (&a23[0][0] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][0] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][0] + 5) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][0] + 6) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[0][1] + 1) int16_t;
+  new (&a23[0][1] + 2) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 5) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 6) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[0][2] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 2) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 5) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 6) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[1][0]) int16_t;
+  new (&a23[1][0] + 1) int16_t;
+  new (&a23[1][0] + 2) int16_t;
+  new (&a23[1][0] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[1][0] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[1][1]) int16_t;
+  new (&a23[1][2]) int16_t;
+  new (&a23[1][2] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[1][3]) int16_t;         // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[1][3] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[2][0]) int16_t;         // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[2][0] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+}
diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-58.c b/gcc/testsuite/gcc.dg/Warray-bounds-58.c
index 7c469e2aefc..849457e559f 100644
--- a/gcc/testsuite/gcc.dg/Warray-bounds-58.c
+++ b/gcc/testsuite/gcc.dg/Warray-bounds-58.c
@@ -1,5 +1,5 @@
 /* { dg-do compile }
-   { dg-options "-O2 -Wall" } */
+   { dg-options "-O2 -Wall -Wno-stringop-overread" } */
 
 typedef __SIZE_TYPE__ size_t;
 
@@ -15,7 +15,7 @@ void fa0_extern (void)
 {
   sink (strlen (ea0.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
   sink (strlen (ea0.a - 1));    // { dg-warning "\\\[-Warray-bounds" "pr93514" { xfail *-*-* } }
-  sink (strlen (ea0.a));        // { dg-warning "\\\[-Wstringop-overread" "pr93514" }
+  sink (strlen (ea0.a));        // valid just-past-the-end offset
   sink (strlen (ea0.a + 1));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 }
 
@@ -25,7 +25,7 @@ void fa0_static (void)
 {
   sink (strlen (sa0.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
   sink (strlen (sa0.a - 1));    // { dg-warning "\\\[-Warray-bounds" "pr93514" { xfail *-*-* } }
-  sink (strlen (sa0.a));        // { dg-warning "\\\[-Wstringop-overread" "pr93514" }
+  sink (strlen (sa0.a));        // valid just-past-the-end offset
   sink (strlen (sa0.a + 1));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 }
 
@@ -52,14 +52,14 @@ void fax_static (void)
   sink (strlen (ax0.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
   sink (strlen (ax0.a - 1));    // { dg-warning "\\\[-Warray-bounds" "pr93514" { xfail *-*-* } }
   sink (strlen (ax0.a));
-  sink (strlen (ax0.a + 1));    // { dg-warning "\\\[-Wstringop-overread" "pr93514" }
+  sink (strlen (ax0.a + 1));    // valid just-past-the-end offset
   sink (strlen (ax0.a + 2));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 
   sink (strlen (ax1.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
   sink (strlen (ax1.a - 1));    // { dg-warning "\\\[-Warray-bounds" "pr93514" { xfail *-*-* } }
   sink (strlen (ax1.a));
   sink (strlen (ax1.a + 1));
-  sink (strlen (ax1.a + 2));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" "pr93514" }
+  sink (strlen (ax1.a + 2));    // valid just-past-the-end offset
   sink (strlen (ax1.a + 3));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 
   sink (strlen (ax2.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
@@ -67,7 +67,7 @@ void fax_static (void)
   sink (strlen (ax2.a));
   sink (strlen (ax2.a + 1));
   sink (strlen (ax2.a + 2));
-  sink (strlen (ax2.a + 3));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" "pr93514" }
+  sink (strlen (ax2.a + 3));    // valid just-past-the-end offset
   sink (strlen (ax2.a + 4));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 
   sink (strlen (ax3.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
@@ -76,6 +76,6 @@ void fax_static (void)
   sink (strlen (ax3.a + 1));
   sink (strlen (ax3.a + 2));
   sink (strlen (ax3.a + 3));
-  sink (strlen (ax3.a + 4));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" "pr93514" }
+  sink (strlen (ax3.a + 4));    // valid just-past-the-end offset
   sink (strlen (ax3.a + 5));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 }
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c
index 339f904d7a6..46f8fed79f3 100644
--- a/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c
@@ -184,6 +184,18 @@ void test_note (const char *s)
     sink (a);
   }
 
+  {
+    char a[1][1][2];                    // { dg-message "at offset 2 into " }
+    strncpy (a[0][1], s, 3);            // { dg-warning "writing 3 bytes into a region of size 0 " }
+    sink (a);
+  }
+
+  {
+    char a[1][2][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 " }
@@ -192,7 +204,13 @@ void test_note (const char *s)
 
   {
     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 " }
+    strncpy (a[1][0], s, 3);            // { dg-warning "writing 3 bytes into a region of size 0 " }
+    sink (a);
+  }
+
+  {
+    char a[2][1][2];                    // { dg-message "at offset 2 into " }
+    strncpy (a[0][1], s, 3);            // { dg-warning "writing 3 bytes into a region of size 0 " }
     sink (a);
   }
 
diff --git a/gcc/tree.c b/gcc/tree.c
index 4046debb72f..2ce46000a05 100644
--- a/gcc/tree.c
+++ b/gcc/tree.c
@@ -13640,20 +13640,21 @@ get_initializer_for (tree init, tree decl)
 /* Determines the size of the member referenced by the COMPONENT_REF
    REF, using its initializer expression if necessary in order to
    determine the size of an initialized flexible array member.
-   If non-null, *INTERIOR_ZERO_LENGTH is set when REF refers to
-   an interior zero-length array.
+   If non-null, set *ARK when REF refers to an interior zero-length
+   array or a trailing one-element array.
    Returns the size as sizetype (which might be zero for an object
    with an uninitialized flexible array member) or null if the size
    cannot be determined.  */
 
 tree
-component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
+component_ref_size (tree ref, special_array_member *sam /* = NULL */)
 {
   gcc_assert (TREE_CODE (ref) == COMPONENT_REF);
 
-  bool int_0_len = false;
-  if (!interior_zero_length)
-    interior_zero_length = &int_0_len;
+  special_array_member arkbuf;
+  if (!sam)
+    sam = &arkbuf;
+  *sam = special_array_member::none;
 
   /* The object/argument referenced by the COMPONENT_REF and its type.  */
   tree arg = TREE_OPERAND (ref, 0);
@@ -13675,9 +13676,16 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
 	   more than one element.  */
 	return memsize;
 
-      *interior_zero_length = zero_length && !trailing;
-      if (*interior_zero_length)
-	memsize = NULL_TREE;
+      if (zero_length)
+	{
+	  if (trailing)
+	    *sam = special_array_member::trail_0;
+	  else
+	    {
+	      *sam = special_array_member::int_0;
+	      memsize = NULL_TREE;
+	    }
+	}
 
       if (!zero_length)
 	if (tree dom = TYPE_DOMAIN (memtype))
@@ -13688,9 +13696,13 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
 		{
 		  offset_int minidx = wi::to_offset (min);
 		  offset_int maxidx = wi::to_offset (max);
-		  if (maxidx - minidx > 0)
+		  offset_int neltsm1 = maxidx - minidx;
+		  if (neltsm1 > 0)
 		    /* MEMBER is an array with more than one element.  */
 		    return memsize;
+
+		  if (neltsm1 == 0)
+		    *sam = special_array_member::trail_1;
 		}
 
       /* For a refernce to a zero- or one-element array member of a union
@@ -13708,7 +13720,7 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
   tree base = get_addr_base_and_unit_offset (ref, &baseoff);
   if (!base || !VAR_P (base))
     {
-      if (!*interior_zero_length)
+      if (*sam != special_array_member::int_0)
 	return NULL_TREE;
 
       if (TREE_CODE (arg) != COMPONENT_REF)
@@ -13728,7 +13740,7 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
   /* Determine the base type of the referenced object.  If it's
      the same as ARGTYPE and MEMBER has a known size, return it.  */
   tree bt = basetype;
-  if (!*interior_zero_length)
+  if (*sam != special_array_member::int_0)
     while (TREE_CODE (bt) == ARRAY_TYPE)
       bt = TREE_TYPE (bt);
   bool typematch = useless_type_conversion_p (argtype, bt);
@@ -13768,7 +13780,7 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
 	  if (DECL_P (base)
 	      && DECL_EXTERNAL (base)
 	      && bt == basetype
-	      && !*interior_zero_length)
+	      && *sam != special_array_member::int_0)
 	    /* The size of a flexible array member of an extern struct
 	       with no initializer cannot be determined (it's defined
 	       in another translation unit and can have an initializer
diff --git a/gcc/tree.h b/gcc/tree.h
index 5bb6e7bc000..8addb024628 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -5288,12 +5288,22 @@ extern bool array_at_struct_end_p (tree);
    by EXP.  This does not include any offset in DECL_FIELD_BIT_OFFSET.  */
 extern tree component_ref_field_offset (tree);
 
+/* Describes a "special" array member due to which component_ref_size
+   returns null.  */
+enum struct special_array_member
+  {
+   none,      /* Not a special array member.  */
+   int_0,     /* Interior array member with size zero.  */
+   trail_0,   /* Trailing array member with size zero.  */
+   trail_1    /* Trailing array member with one element.  */
+  };
+
 /* Return the size of the member referenced by the COMPONENT_REF, using
    its initializer expression if necessary in order to determine the size
    of an initialized flexible array member.  The size might be zero for
    an object with an uninitialized flexible array member or null if it
    cannot be determined.  */
-extern tree component_ref_size (tree, bool * = NULL);
+extern tree component_ref_size (tree, special_array_member * = NULL);
 
 extern int tree_map_base_eq (const void *, const void *);
 extern unsigned int tree_map_base_hash (const void *);

[-- Attachment #3: gcc-compute_objsize.diff --]
[-- Type: text/x-patch, Size: 64386 bytes --]

Generalize compute_objsize to return maximum size/offset instead of failing.

Also resolves:
PR middle-end/97023 - missing warning on buffer overflow in chained mempcpy

gcc/ChangeLog:

	PR middle-end/97023
	* builtins.c (access_ref::access_ref): Initialize new member.  Use
	new enum.
	(access_ref::size_remaining): Define new member function.
	(inform_access): Handle expressions referencing objects.
	(gimple_call_alloc_size): Call get_size_range instead of get_range.
	(gimple_call_return_array): New function.
	(get_range): Update comment.
	(compute_objsize): Set maximum size or offset instead of failing
	for unknown objects and handle more kinds of expressions.
	(compute_objsize): Call access_ref::size_remaining.
	(compute_objsize): Have transitional wrapper fail for pointers
	into unknown objects.
	(expand_builtin_strncmp): Call access_ref::size_remaining and
	handle new cases.
	* builtins.h (access_ref::size_remaining): Declare new member function.
	(access_ref::set_max_size_range): Define new member function.
	(access_ref::add_ofset, access_ref::add_max_ofset): Same.
	(access_ref::add_base0): New data member.
	* calls.c (get_size_range): Change argument type.  Handle new
	condition.
	* calls.h (get_size_range): Adjust signature.
	(enum size_range_flags): Define new type.

gcc/testsuite/ChangeLog:

	PR middle-end/97023
	* c-c++-common/Wrestrict.c: Add new case, expect warning for existing.
	* gcc.dg/pr51683.c: Prune out expected warning.
	* gcc.dg/Wstringop-overflow-41.c: New test.
	* gcc.dg/Wstringop-overflow-44.c: New test.
	* gcc.dg/Wstringop-overflow-45.c: New test.
	* gcc.dg/Wstringop-overflow-46.c: New test.
	* gcc.dg/Wstringop-overflow-47.c: New test.
	* gcc.dg/Wstringop-overflow-49.c: New test.
	* gcc.dg/Wstringop-overflow-50.c: New test.
	* gcc.dg/Wstringop-overflow-51.c: New test.
	* gcc.dg/Wstringop-overflow-52.c: New test.
	* gcc.dg/Wstringop-overflow-53.c: New test.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index b5680bf93dd..83fe7f2df09 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -199,7 +199,7 @@ static void expand_builtin_sync_synchronize (void);
 
 access_ref::access_ref (tree bound /* = NULL_TREE */,
 			bool minaccess /* = false */)
-: ref (), eval ([](tree x){ return x; }), trail1special (true)
+: ref (), eval ([](tree x){ return x; }), trail1special (true), base0 (true)
 {
   /* Set to valid.  */
   offrng[0] = offrng[1] = 0;
@@ -214,7 +214,7 @@ access_ref::access_ref (tree bound /* = NULL_TREE */,
      set the bounds of the access to reflect both it and MINACCESS.
      BNDRNG[0] is the size of the minimum access.  */
   tree rng[2];
-  if (bound && get_size_range (bound, rng, true))
+  if (bound && get_size_range (bound, rng, SR_ALLOW_ZERO))
     {
       bndrng[0] = wi::to_offset (rng[0]);
       bndrng[1] = wi::to_offset (rng[1]);
@@ -222,6 +222,83 @@ access_ref::access_ref (tree bound /* = NULL_TREE */,
     }
 }
 
+offset_int
+access_ref::size_remaining (offset_int *pmin /* = NULL */) const
+{
+  offset_int minbuf;
+  if (!pmin)
+    pmin = &minbuf;
+
+  if (base0)
+    {
+      /* The offset into referenced object is zero-based (i.e., it's
+	 not referenced by a pointer into middle of some unknown object).  */
+      if (offrng[0] < 0 && offrng[1] < 0)
+	{
+	  /* If the offset is negative the remaining size is zero.  */
+	  *pmin = 0;
+	  return 0;
+	}
+
+      if (offrng[1] < offrng[0])
+	{
+	  if (offrng[1] < 0)
+	    {
+	      *pmin = 0;
+	      return offrng[0] < sizrng[1] ? sizrng[1] - offrng[0] : 0;
+	    }
+
+	  *pmin = offrng[0] < sizrng[1] ? sizrng[1] - offrng[0] : 0;
+	  return sizrng[1];
+	}
+
+      if (sizrng[1] <= offrng[0])
+	{
+	  /* If the starting offset is greater than the upper bound on
+	     the size of the object the space remaining is zero.  */
+	  *pmin = 0;
+	  return 0;
+	}
+
+      /* Otherwise return the size minus the lower bound of the offset.  */
+      offset_int or0 = offrng[0] < 0 ? 0 : offrng[0];
+
+      *pmin = sizrng[0] - or0;
+      return sizrng[1] - or0;
+    }
+
+  /* The offset to the referenced object isn't zero-based (i.e., it may
+     refer to a byte other than the first.  The size of such an object
+     is constrained only by the size of the address space (the result
+     of max_object_size()).  */
+  if (offrng[1] < offrng[0])
+    {
+      if (offrng[1] < 0)
+	{
+	  *pmin = sizrng[0];
+	  return sizrng[1];
+	}
+
+      if (offrng[0] < sizrng[1])
+	*pmin = offrng[0] < 0 ? sizrng[1] : sizrng[1] - offrng[0];
+      else
+	*pmin = 0;
+
+      return sizrng[1];
+    }
+
+  if (sizrng[1] <= offrng[0])
+    {
+      *pmin = 0;
+      return 0;
+    }
+
+  offset_int or0 = offrng[0] < 0 ? 0 : offrng[0];
+
+  *pmin = sizrng[0] - or0;
+  return sizrng[1] - or0;
+}
+
 /* Return true if NAME starts with __builtin_ or __sync_.  */
 
 static bool
@@ -3730,23 +3807,27 @@ inform_access (const access_ref &ref, access_mode mode)
 	sprintf (sizestr, "[%llu, %llu]", minsize, maxsize);
 
     }
-  else
+  else if (DECL_P (ref.ref))
     loc = DECL_SOURCE_LOCATION (ref.ref);
+  else if (EXPR_P (ref.ref) && EXPR_HAS_LOCATION (ref.ref))
+    loc = EXPR_LOCATION (ref.ref);
+  else
+    return;
 
   if (mode == access_read_write || mode == access_write_only)
     {
-      if (DECL_P (ref.ref))
+      if (allocfn == NULL_TREE)
 	{
 	  if (minoff == maxoff)
 	    {
 	      if (minoff == 0)
-		inform (loc, "destination object %qD", ref.ref);
+		inform (loc, "destination object %qE", ref.ref);
 	      else
-		inform (loc, "at offset %lli into destination object %qD",
+		inform (loc, "at offset %lli into destination object %qE",
 			minoff, ref.ref);
 	    }
 	  else
-	    inform (loc, "at offset [%lli, %lli] into destination object %qD",
+	    inform (loc, "at offset [%lli, %lli] into destination object %qE",
 		    minoff, maxoff, ref.ref);
 	  return;
 	}
@@ -4146,7 +4227,7 @@ check_read_access (tree exp, tree src, tree bound /* = NULL_TREE */,
 
 tree
 gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
-			const vr_values *rvals /* = NULL */)
+			const vr_values * /* = NULL */)
 {
   if (!stmt)
     return NULL_TREE;
@@ -4198,14 +4279,17 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
   if (!rng1)
     rng1 = rng1_buf;
 
+  /* Use maximum precision to avoid overflow below.  */
   const int prec = ADDR_MAX_PRECISION;
-  const tree size_max = TYPE_MAX_VALUE (sizetype);
-  if (!get_range (size, rng1, rvals))
-    {
-      /* Use the full non-negative range on failure.  */
-      rng1[0] = wi::zero (prec);
-      rng1[1] = wi::to_wide (size_max, prec);
-    }
+
+  {
+    tree r[2];
+    /* Determine the largest valid range size, including zero.  */
+    if (!get_size_range (size, r, SR_ALLOW_ZERO | SR_USE_LARGEST))
+      return NULL_TREE;
+    rng1[0] = wi::to_wide (r[0], prec);
+    rng1[1] = wi::to_wide (r[1], prec);
+  }
 
   if (argidx2 > nargs && TREE_CODE (size) == INTEGER_CST)
     return fold_convert (sizetype, size);
@@ -4214,26 +4298,24 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
      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 (!get_range (n, rng2, rvals))
-    {
+  {
+    tree r[2];
       /* As above, use the full non-negative range on failure.  */
-      rng2[0] = wi::zero (prec);
-      rng2[1] = wi::to_wide (size_max, prec);
-    }
-
-  /* Extend to the maximum precision to avoid overflow.  */
-  rng1[0] = wide_int::from (rng1[0], prec, UNSIGNED);
-  rng1[1] = wide_int::from (rng1[1], prec, UNSIGNED);
-  rng2[0] = wide_int::from (rng2[0], prec, UNSIGNED);
-  rng2[1] = wide_int::from (rng2[1], prec, UNSIGNED);
+    if (!get_size_range (n, r, SR_ALLOW_ZERO | SR_USE_LARGEST))
+      return NULL_TREE;
+    rng2[0] = wi::to_wide (r[0], prec);
+    rng2[1] = wi::to_wide (r[1], prec);
+  }
 
   /* Compute products of both bounds for the caller but return the lesser
      of SIZE_MAX and the product of the upper bounds as a constant.  */
   rng1[0] = rng1[0] * rng2[0];
   rng1[1] = rng1[1] * rng2[1];
+
+  const tree size_max = TYPE_MAX_VALUE (sizetype);
   if (wi::gtu_p (rng1[1], wi::to_wide (size_max, prec)))
     {
-      rng1[1] = wi::to_wide (size_max);
+      rng1[1] = wi::to_wide (size_max, prec);
       return size_max;
     }
 
@@ -4335,6 +4417,82 @@ get_range (tree x, signop sgn, offset_int r[2],
   return true;
 }
 
+/* Return the argument that the call STMT to a built-in function returns
+   or null if it doesn't.  On success, set OFFRNG[] to the range of offsets
+   from the argument reflected in the value returned by the built-in if it
+   can be determined, otherwise to 0 and HWI_M1U respectively.  */
+
+static tree
+gimple_call_return_array (gimple *stmt, offset_int offrng[2])
+{
+  if (!gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)
+      || gimple_call_num_args (stmt) < 1)
+    return NULL_TREE;
+
+  tree fn = gimple_call_fndecl (stmt);
+  switch (DECL_FUNCTION_CODE (fn))
+    {
+    case BUILT_IN_MEMCPY:
+    case BUILT_IN_MEMCPY_CHK:
+    case BUILT_IN_MEMMOVE:
+    case BUILT_IN_MEMMOVE_CHK:
+    case BUILT_IN_MEMSET:
+    case BUILT_IN_STPCPY:
+    case BUILT_IN_STPCPY_CHK:
+    case BUILT_IN_STPNCPY:
+    case BUILT_IN_STPNCPY_CHK:
+    case BUILT_IN_STRCAT:
+    case BUILT_IN_STRCAT_CHK:
+    case BUILT_IN_STRCPY:
+    case BUILT_IN_STRCPY_CHK:
+    case BUILT_IN_STRNCAT:
+    case BUILT_IN_STRNCAT_CHK:
+    case BUILT_IN_STRNCPY:
+    case BUILT_IN_STRNCPY_CHK:
+      offrng[0] = offrng[1] = 0;
+      return gimple_call_arg (stmt, 0);
+
+    case BUILT_IN_MEMPCPY:
+    case BUILT_IN_MEMPCPY_CHK:
+      {
+	tree off = gimple_call_arg (stmt, 2);
+	if (!get_range (off, SIGNED, offrng))
+	  {
+	    offrng[0] = 0;
+	    offrng[1] = HOST_WIDE_INT_M1U;
+	  }
+	return gimple_call_arg (stmt, 0);
+      }
+
+    case BUILT_IN_MEMCHR:
+      {
+	tree off = gimple_call_arg (stmt, 2);
+	if (get_range (off, SIGNED, offrng))
+	  offrng[0] = 0;
+	else
+	  {
+	    offrng[0] = 0;
+	    offrng[1] = HOST_WIDE_INT_M1U;
+	  }
+	return gimple_call_arg (stmt, 0);
+      }
+
+    case BUILT_IN_STRCHR:
+    case BUILT_IN_STRRCHR:
+    case BUILT_IN_STRSTR:
+      {
+	offrng[0] = 0;
+	offrng[1] = HOST_WIDE_INT_M1U;
+      }
+      return gimple_call_arg (stmt, 0);
+
+    default:
+      break;
+    }
+
+  return NULL_TREE;
+}
+
 /* 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).
@@ -4344,7 +4502,8 @@ get_range (tree x, signop sgn, offset_int r[2],
    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.
+   Returns true on success, false when a meaningful size (or range)
+   cannot be determined.
 
    The function is intended for diagnostics and should not be used
    to influence code generation or optimization.  */
@@ -4361,25 +4520,40 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 
   if (DECL_P (ptr))
     {
-      /* Bail if the reference is to the pointer itself (as opposed
-	 to what it points to).  */
+      pref->ref = ptr;
+
       if (!addr && POINTER_TYPE_P (TREE_TYPE (ptr)))
-	return false;
+	{
+	  /* Set the maximum size if the reference is to the pointer
+	     itself (as opposed to what it points to).  */
+	  pref->set_max_size_range ();
+	  return true;
+	}
 
-      pref->ref = ptr;
       if (tree size = decl_init_size (ptr, false))
 	if (TREE_CODE (size) == INTEGER_CST)
 	  {
 	    pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
 	    return true;
 	  }
-      pref->sizrng[0] = 0;
-      pref->sizrng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
+
+      pref->set_max_size_range ();
       return true;
     }
 
   const tree_code code = TREE_CODE (ptr);
 
+  if (code == BIT_FIELD_REF)
+    {
+      tree ref = TREE_OPERAND (ptr, 0);
+      if (!compute_objsize (ref, ostype, pref, visited, rvals))
+	return false;
+
+      offset_int off = wi::to_offset (pref->eval (TREE_OPERAND (ptr, 2)));
+      pref->add_offset (off / BITS_PER_UNIT);
+      return true;
+    }
+
   if (code == COMPONENT_REF)
     {
       tree ref = TREE_OPERAND (ptr, 0);
@@ -4387,27 +4561,29 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 
       if (ostype == 0)
 	{
-	  /* For raw memory functions like memcpy bail if the size
-	     of the enclosing object cannot be determined.  */
-	  if (!compute_objsize (ref, ostype, pref, visited, rvals)
-	      || !pref->ref)
+	  /* In OSTYPE zero (for raw memory functions like memcpy), use
+	     the maximum size instead if the identity of the enclosing
+	     object cannot be determined.  */
+	  if (!compute_objsize (ref, ostype, pref, visited, rvals))
 	    return false;
 
 	  /* Otherwise, use the size of the enclosing object and add
 	     the offset of the member to the offset computed so far.  */
 	  tree offset = byte_position (field);
-	  if (TREE_CODE (offset) != INTEGER_CST)
-	    return false;
-	  offset_int off = wi::to_offset (offset);
-	  pref->offrng[0] += off;
-	  pref->offrng[1] += off;
+	  if (TREE_CODE (offset) == INTEGER_CST)
+	    pref->add_offset (wi::to_offset (offset));
+	  else
+	    pref->add_max_offset ();
 	  return true;
 	}
 
-      /* 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;
+	{
+	  /* Set maximum size if the reference is to the pointer member
+	     itself (as opposed to what it points to).  */
+	  pref->set_max_size_range ();
+	  return true;
+	}
 
       pref->ref = field;
 
@@ -4463,8 +4639,10 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
       offset_int orng[2];
       tree off = pref->eval (TREE_OPERAND (ptr, 1));
       if (!get_range (off, SIGNED, orng, rvals))
-	/* Fail unless the size of the object is zero.  */
-	return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1];
+	{
+	  orng[1] = wi::to_offset (max_object_size ());
+	  orng[0] = -orng[1] - 1;
+	}
 
       if (TREE_CODE (ptr) == ARRAY_REF)
 	{
@@ -4483,7 +4661,10 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 	  tree eltype = TREE_TYPE (ptr);
 	  tree tpsize = TYPE_SIZE_UNIT (eltype);
 	  if (!tpsize || TREE_CODE (tpsize) != INTEGER_CST)
-	    return false;
+	    {
+	      pref->add_max_offset ();
+	      return true;
+	    }
 
 	  offset_int sz = wi::to_offset (tpsize);
 	  orng[0] *= sz;
@@ -4515,24 +4696,56 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 
       return true;
     }
-  else if (code == POINTER_PLUS_EXPR)
+
+  if (code == TARGET_MEM_REF)
     {
       tree ref = TREE_OPERAND (ptr, 0);
       if (!compute_objsize (ref, ostype, pref, visited, rvals))
 	return false;
 
-      offset_int orng[2];
-      tree off = pref->eval (TREE_OPERAND (ptr, 1));
-      if (!get_range (off, SIGNED, orng, rvals))
-	/* Fail unless the size of the object is zero.  */
-	return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1];
+      /* TODO: Handle remaining operands.  Until then, add maximum offset.  */
+      pref->ref = ptr;
+      pref->add_max_offset ();
+      return true;
+    }
 
-      pref->offrng[0] += orng[0];
-      pref->offrng[1] += orng[1];
+  if (code == INTEGER_CST)
+    {
+      /* Pointer constants other than null are most likely the result
+	 of erroneous null pointer addition/subtraction.  Set size to
+	 zero.  For null pointers, set size to the maximum for now
+	 since those may be the result of jump threading.  */
+      if (integer_zerop (ptr))
+	pref->set_max_size_range ();
+      else
+	pref->sizrng[0] = pref->sizrng[1] = 0;
+      pref->ref = ptr;
+
+      return true;
+    }
+
+  if (code == STRING_CST)
+    {
+      pref->sizrng[0] = pref->sizrng[1] = TREE_STRING_LENGTH (ptr);
+      return true;
+    }
+
+  if (code == POINTER_PLUS_EXPR)
+    {
+      tree ref = TREE_OPERAND (ptr, 0);
+      if (!compute_objsize (ref, ostype, pref, visited, rvals))
+	return false;
 
+      offset_int orng[2];
+      tree off = pref->eval (TREE_OPERAND (ptr, 1));
+      if (get_range (off, SIGNED, orng, rvals))
+	pref->add_offset (orng[0], orng[1]);
+      else
+	pref->add_max_offset ();
       return true;
     }
-  else if (code == VIEW_CONVERT_EXPR)
+
+  if (code == VIEW_CONVERT_EXPR)
     {
       ptr = TREE_OPERAND (ptr, 0);
       return compute_objsize (ptr, ostype, pref, visited, rvals);
@@ -4544,17 +4757,53 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
       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.  */
+	     from its argument(s).  If successful, also set *PREF->REF
+	     to PTR for the caller to include in diagnostics.  */
 	  wide_int wr[2];
 	  if (gimple_call_alloc_size (stmt, wr, rvals))
 	    {
 	      pref->ref = ptr;
 	      pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED);
 	      pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED);
-	      return true;
+	      /* Constrain both bounds to a valid size.  */
+	      offset_int maxsize = wi::to_offset (max_object_size ());
+	      if (pref->sizrng[0] > maxsize)
+		pref->sizrng[0] = maxsize;
+	      if (pref->sizrng[1] > maxsize)
+		pref->sizrng[1] = maxsize;
 	    }
-	  return false;
+	  else
+	    {
+	      /* For functions known to return one of their pointer arguments
+		 try to determine what the returned pointer points to, and on
+		 success add OFFRNG which was set to the offset added by
+		 the function (e.g., memchr) to the overall offset.  */
+	      offset_int offrng[2];
+	      if (tree ret = gimple_call_return_array (stmt, offrng))
+		{
+		  if (!compute_objsize (ret, ostype, pref, visited, rvals))
+		    return false;
+
+		  /* Cap OFFRNG[1] to at most the remaining size of
+		     the object.  */
+		  offset_int remrng[2];
+		  remrng[1] = pref->size_remaining (remrng);
+		  if (remrng[1] < offrng[1])
+		    offrng[1] = remrng[1];
+		  pref->add_offset (offrng[0], offrng[1]);
+		}
+	      else
+		{
+		  /* For other calls that might return arbitrary pointers
+		     including into the middle of objects set the size
+		     range to maximum, clear PREF->BASE0, and also set
+		     PREF->REF to include in diagnostics.  */
+		  pref->set_max_size_range ();
+		  pref->base0 = false;
+		  pref->ref = ptr;
+		}
+	    }
+	  return true;
 	}
 
       if (gimple_nop_p (stmt))
@@ -4563,28 +4812,46 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 	     of the array from the current function declaratation
 	     (e.g., attribute access or related).  */
 	  wide_int wr[2];
-	  tree ref = gimple_parm_array_size (ptr, wr, rvals);
-	  if (!ref)
-	    return NULL_TREE;
-	  pref->ref = ref;
-	  pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED);
-	  pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED);
+	  if (tree ref = gimple_parm_array_size (ptr, wr, rvals))
+	    {
+	      pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED);
+	      pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED);
+	      pref->ref = ref;
+	      return true;
+	    }
+
+	  pref->set_max_size_range ();
+	  pref->base0 = false;
+	  pref->ref = ptr;
+	  if (tree var = SSA_NAME_VAR (ptr))
+	    if (TREE_CODE (var) == PARM_DECL)
+	      pref->ref = var;
+
 	  return true;
 	}
 
       /* TODO: Handle PHI.  */
 
       if (!is_gimple_assign (stmt))
-	return false;
+	{
+	  /* Clear BASE0 since the assigned pointer might point into
+	     the middle of the object, set the maximum size range and,
+	     if the SSA_NAME refers to a function argumnent, set
+	     PREF->REF to it.  */
+	  pref->base0 = false;
+	  pref->set_max_size_range ();
+	  if (tree var = SSA_NAME_VAR (ptr))
+	    if (TREE_CODE (var) == PARM_DECL)
+	      pref->ref = var;
+	  return true;
+	}
 
       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;
 
-      if (code == POINTER_PLUS_EXPR)
+      if (code == POINTER_PLUS_EXPR
+	  && TREE_CODE (TREE_TYPE (ptr)) == POINTER_TYPE)
 	{
 	  /* If the the offset in the expression can be determined use
 	     it to adjust the overall offset.  Otherwise, set the overall
@@ -4593,32 +4860,28 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 	  tree off = gimple_assign_rhs2 (stmt);
 	  if (!get_range (off, SIGNED, orng, rvals))
 	    {
-	      orng[0] = wi::to_offset (TYPE_MIN_VALUE (ptrdiff_type_node));
-	      orng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
+	      orng[0] = -wi::to_offset (max_object_size ()) - 1;
+	      orng[1] = wi::to_offset (max_object_size ());
 	    }
 
-	  pref->offrng[0] += orng[0];
-	  pref->offrng[1] += orng[1];
+	  pref->add_offset (orng[0], orng[1]);
+	  return compute_objsize (ptr, ostype, pref, visited, rvals);
 	}
-      else if (code != ADDR_EXPR)
-	return false;
 
-      return compute_objsize (ptr, ostype, pref, visited, rvals);
-    }
+      if (code == ADDR_EXPR)
+	return compute_objsize (ptr, ostype, pref, visited, rvals);
 
-  tree type = TREE_TYPE (ptr);
-  type = TYPE_MAIN_VARIANT (type);
-  if (TREE_CODE (ptr) == ADDR_EXPR)
-    ptr = TREE_OPERAND (ptr, 0);
-
-  if (TREE_CODE (type) == ARRAY_TYPE
-      && !array_at_struct_end_p (ptr))
-    {
-      if (tree size = TYPE_SIZE_UNIT (type))
-	return get_range (size, UNSIGNED, pref->sizrng, rvals);
+      /* This could be an assignment from a nonlocal pointer.  Save PTR
+	 to mention in diagnostics but otherwise treat it as a pointer
+	 to an unknown object.  */
+      pref->ref = ptr;
     }
 
-  return false;
+  /* Assume all other expressions point into an unknown object
+     of the maximum valid size.  */
+  pref->base0 = false;
+  pref->set_max_size_range ();
+  return true;
 }
 
 /* A "public" wrapper around the above.  Clients should use this overload
@@ -4639,27 +4902,10 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
   if (!success)
     return NULL_TREE;
 
-  if (pref->offrng[1] < pref->offrng[0])
-    {
-      if (pref->offrng[1] < 0
-	  && pref->sizrng[1] <= pref->offrng[0])
-	return size_zero_node;
-
-      return wide_int_to_tree (sizetype, pref->sizrng[1]);
-    }
-
-  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]);
+  offset_int maxsize = pref->size_remaining ();
+  if (pref->base0 && pref->offrng[0] < 0 && pref->offrng[1] >= 0)
+    pref->offrng[0] = 0;
+  return wide_int_to_tree (sizetype, maxsize);
 }
 
 /* Transitional wrapper around the above.  The function should be removed
@@ -4673,7 +4919,7 @@ compute_objsize (tree ptr, int ostype, tree *pdecl /* = NULL */,
      none has been computed yet.  */
   access_ref ref;
   tree size = compute_objsize (ptr, ostype, &ref, rvals);
-  if (!size)
+  if (!size || !ref.base0)
     return NULL_TREE;
 
   if (pdecl)
@@ -5952,13 +6198,21 @@ expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED rtx target,
 	  tree size2 = compute_objsize (arg2, 1, &ref2);
 	  tree func = get_callee_fndecl (exp);
 
-	  if (size1 && size2)
+	  if (size1 && size2 && bndrng[0] && !integer_zerop (bndrng[0]))
 	    {
-	      tree maxsize = tree_int_cst_le (size1, size2) ? size2 : size1;
-
-	      if (tree_int_cst_lt (maxsize, bndrng[0]))
+	      offset_int rem1 = ref1.size_remaining ();
+	      offset_int rem2 = ref2.size_remaining ();
+	      if (rem1 == 0 || rem2 == 0)
 		maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp, func,
-				      bndrng, maxsize);
+				      bndrng, integer_zero_node);
+	      else
+		{
+		  offset_int maxrem = wi::max (rem1, rem2, UNSIGNED);
+		  if (maxrem < wi::to_offset (bndrng[0]))
+		    maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp,
+					  func, bndrng,
+					  wide_int_to_tree (sizetype, maxrem));
+		}
 	    }
 	  else if (bndrng[0]
 		   && !integer_zerop (bndrng[0])
diff --git a/gcc/builtins.h b/gcc/builtins.h
index 39415aade05..e155c159629 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -183,11 +183,42 @@ struct access_ref
      values.  */
   bool offset_bounded () const;
 
+  /* Return the maximum amount of space remaining and if non-null, set
+     argument to the minimum.  */
+  offset_int size_remaining (offset_int * = NULL) const;
+
+  void set_max_size_range ()
+  {
+    sizrng[0] = 0;
+    sizrng[1] = wi::to_offset (max_object_size ());
+  }
+
+  void add_offset (const offset_int &off)
+  {
+    offrng[0] += off;
+    offrng[1] += off;
+  }
+
+  void add_offset (const offset_int &min, const offset_int &max)
+  {
+    offrng[0] += min;
+    offrng[1] += max;
+  }
+
+  void add_max_offset ()
+  {
+    offrng[0] -= wi::to_offset (max_object_size ()) + 1;
+    offrng[1] += wi::to_offset (max_object_size ());
+  }
+
   /* Used to fold integer expressions when called from front ends.  */
   tree (*eval)(tree);
   /* Set if trailing one-element arrays should be treated as flexible
      array members.  */
   bool trail1special;
+  /* Set if valid offsets must start at zero (for declared and allocated
+     objects but not for others referenced by pointers).  */
+  bool base0;
 };
 
 /* Describes a pair of references used in an access by built-in
diff --git a/gcc/calls.c b/gcc/calls.c
index 0e5c696c463..8fb8640aa43 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -1243,14 +1243,16 @@ alloc_max_size (void)
    after adjusting it if necessary to make EXP a represents a valid size
    of object, or a valid size argument to an allocation function declared
    with attribute alloc_size (whose argument may be signed), or to a string
-   manipulation function like memset.  When ALLOW_ZERO is true, allow
-   returning a range of [0, 0] for a size in an anti-range [1, N] where
-   N > PTRDIFF_MAX.  A zero range is a (nearly) invalid argument to
-   allocation functions like malloc but it is a valid argument to
-   functions like memset.  */
+   manipulation function like memset.
+   When ALLOW_ZERO is set in FLAGS, allow returning a range of [0, 0] for
+   a size in an anti-range [1, N] where N > PTRDIFF_MAX.  A zero range is
+   a (nearly) invalid argument to allocation functions like malloc but it
+   is a valid argument to functions like memset.
+   When USE_LARGEST is set in FLAGS set RANGE to the largest valid subrange
+   in a multi-range, otherwise to the smallest valid subrange.  */
 
 bool
-get_size_range (tree exp, tree range[2], bool allow_zero /* = false */)
+get_size_range (tree exp, tree range[2], int flags /* = 0 */)
 {
   if (!exp)
     return false;
@@ -1322,25 +1324,42 @@ get_size_range (tree exp, tree range[2], bool allow_zero /* = false */)
 	      min = wi::zero (expprec);
 	    }
 	}
-      else if (wi::eq_p (0, min - 1))
+      else
 	{
-	  /* EXP is unsigned and not in the range [1, MAX].  That means
-	     it's either zero or greater than MAX.  Even though 0 would
-	     normally be detected by -Walloc-zero, unless ALLOW_ZERO
-	     is true, set the range to [MAX, TYPE_MAX] so that when MAX
-	     is greater than the limit the whole range is diagnosed.  */
-	  if (allow_zero)
-	    min = max = wi::zero (expprec);
-	  else
+	  wide_int maxsize = wi::to_wide (max_object_size ());
+	  min = wide_int::from (min, maxsize.get_precision (), UNSIGNED);
+	  max = wide_int::from (max, maxsize.get_precision (), UNSIGNED);
+	  if (wi::eq_p (0, min - 1))
+	    {
+	      /* EXP is unsigned and not in the range [1, MAX].  That means
+		 it's either zero or greater than MAX.  Even though 0 would
+		 normally be detected by -Walloc-zero, unless ALLOW_ZERO
+		 is set, set the range to [MAX, TYPE_MAX] so that when MAX
+		 is greater than the limit the whole range is diagnosed.  */
+	      wide_int maxsize = wi::to_wide (max_object_size ());
+	      if ((flags & SR_ALLOW_ZERO)
+		  && wi::ltu_p (maxsize, max))
+		min = max = wi::zero (expprec);
+	      else
+		{
+		  min = max + 1;
+		  max = wi::to_wide (TYPE_MAX_VALUE (exptype));
+		}
+	    }
+	  else if ((flags & SR_USE_LARGEST)
+		   && wi::ltu_p (max + 1, maxsize))
 	    {
+	      /* When USE_LARGEST is set and the larger of the two subranges
+		 is a valid size, use it...  */
 	      min = max + 1;
-	      max = wi::to_wide (TYPE_MAX_VALUE (exptype));
+	      max = maxsize;
+	    }
+	  else
+	    {
+	      /* ...otherwise use the smaller subrange.  */
+	      max = min - 1;
+	      min = wi::zero (expprec);
 	    }
-	}
-      else
-	{
-	  max = min - 1;
-	  min = wi::zero (expprec);
 	}
     }
 
diff --git a/gcc/calls.h b/gcc/calls.h
index dfb951ca45b..644ec45d92c 100644
--- a/gcc/calls.h
+++ b/gcc/calls.h
@@ -133,7 +133,15 @@ extern bool reference_callee_copied (CUMULATIVE_ARGS *,
 extern void maybe_warn_alloc_args_overflow (tree, tree, tree[2], int[2]);
 extern tree get_attr_nonstring_decl (tree, tree * = NULL);
 extern bool maybe_warn_nonstring_arg (tree, tree);
-extern bool get_size_range (tree, tree[2], bool = false);
+enum size_range_flags
+  {
+   /* Set to consider zero a valid range.  */
+   SR_ALLOW_ZERO = 1,
+   /* Set to use the largest subrange of a set of ranges as opposed
+      to the smallest.  */
+   SR_USE_LARGEST = 2
+  };
+extern bool get_size_range (tree, tree[2], int = 0);
 extern rtx rtx_for_static_chain (const_tree, bool);
 extern bool cxx17_empty_base_field_p (const_tree);
 
diff --git a/gcc/gimple-ssa-warn-restrict.c b/gcc/gimple-ssa-warn-restrict.c
index 512fc138528..3a5d6f24b3b 100644
--- a/gcc/gimple-ssa-warn-restrict.c
+++ b/gcc/gimple-ssa-warn-restrict.c
@@ -761,7 +761,15 @@ builtin_access::builtin_access (gimple *call, builtin_memref &dst,
 	addr = build1 (ADDR_EXPR, (TREE_TYPE (addr)), addr);
 
       if (tree dstsize = compute_objsize (addr, ostype))
-	dst.basesize = wi::to_offset (dstsize);
+	{
+	  offset_int size = wi::to_offset (dstsize);
+	  if (size < maxobjsize)
+	    dst.basesize = wi::to_offset (dstsize);
+	  else if (POINTER_TYPE_P (TREE_TYPE (addr)))
+	    dst.basesize = HOST_WIDE_INT_MIN;
+	  else
+	    dst.basesize = maxobjsize;
+	}
       else if (POINTER_TYPE_P (TREE_TYPE (addr)))
 	dst.basesize = HOST_WIDE_INT_MIN;
       else
@@ -775,7 +783,15 @@ builtin_access::builtin_access (gimple *call, builtin_memref &dst,
 	addr = build1 (ADDR_EXPR, (TREE_TYPE (addr)), addr);
 
       if (tree srcsize = compute_objsize (addr, ostype))
-	src.basesize = wi::to_offset (srcsize);
+	{
+	  offset_int size = wi::to_offset (srcsize);
+	  if (size < maxobjsize)
+	    src.basesize = wi::to_offset (srcsize);
+	  else if (POINTER_TYPE_P (TREE_TYPE (addr)))
+	    src.basesize = HOST_WIDE_INT_MIN;
+	  else
+	    src.basesize = maxobjsize;
+	}
       else if (POINTER_TYPE_P (TREE_TYPE (addr)))
 	src.basesize = HOST_WIDE_INT_MIN;
       else
diff --git a/gcc/testsuite/c-c++-common/Wrestrict.c b/gcc/testsuite/c-c++-common/Wrestrict.c
index 3b019c8a80e..7a536456c47 100644
--- a/gcc/testsuite/c-c++-common/Wrestrict.c
+++ b/gcc/testsuite/c-c++-common/Wrestrict.c
@@ -320,14 +320,16 @@ void test_memcpy_anti_range (char *d, const char *s)
 
   T (d, d + SAR (0, 3), UR (DIFF_MAX - 2, DIFF_MAX));               /* { dg-warning "accessing \[0-9\]+ or more bytes at offsets 0 and \\\[-?\[0-9\]+, -?\[0-9\]+] overlaps \[0-9\]+ bytes at offset 2" "memcpy" } */
 
-  /* Verify that a size in an anti-range ~[0, N] where N >= PTRDIFF_MAX
-     doesn't trigger a warning.  */
-  T (d, s, UAR (1, DIFF_MAX - 1));
+  /* Verify that a size in an anti-range ~[1, N] where N >= PTRDIFF_MAX - 2
+     doesn't trigger a warning.  With ~[1, PTRDIFF_MAX - 1] and assuming
+     a nonzero size, the difference between the just-past-the-end pointer
+     to A and A for char A[PTRDIFF_MAX] wouldn't be representable in
+     ptrdiff_t so such a large object cannot exist.  */
+  T (d, s, UAR (1, DIFF_MAX / 2 - 1));
+  T (d, s, UAR (1, DIFF_MAX - 1));      /* { dg-warning "\\\[-Wrestrict" } */
   T (d, s, UAR (1, DIFF_MAX));
   T (d, s, UAR (1, SIZE_MAX - 1));
-
-  /* This causes the last dg-warning test to fail for some reason.
-     T (d, s, UAR (1, SIZE_MAX)); */
+  T (d, s, UAR (1, SIZE_MAX));
 }
 
 /* Verify calls to memcpy() where the combination of offsets in some
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-41.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-41.c
new file mode 100644
index 00000000000..2647043b1ef
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-41.c
@@ -0,0 +1,118 @@
+/* Verify that writes at excessive offsets into declared or allocated
+   objects of unknown size are diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#define DIFF_MAX __PTRDIFF_MAX__
+
+typedef __SIZE_TYPE__ size_t;
+
+void* malloc (size_t);
+void* memcpy (void*, const void*, size_t);
+void* memset (void*, int, size_t);
+
+void sink (void*);
+
+
+void char_array_cst_off_cst_size (void)
+{
+  extern char caxcc[];                  // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'caxcc'" }
+
+  char *p = caxcc;
+  size_t idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 2" }
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 1" "pr?????" { xfail ilp32 } }
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 0" }
+  sink (p);
+}
+
+
+void char_array_var_off_cst_size (size_t idx)
+{
+  /* The upper bound of the offset would ideally be positive but for now
+     allow it to be negative too.  */
+  extern char caxvc[];                  // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object 'caxvc'" }
+
+  char *p = caxvc;
+
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+  sink (p);
+}
+
+
+void char_array_var_off_var_size (size_t idx, size_t n)
+{
+  extern char caxvv[];                  // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object 'caxvv'" }
+
+  char *p = caxvv;
+
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  if (n < 3 || 7 < n)
+    n = 3;
+
+  memset (p + idx, 0, n);
+  sink (p);
+
+  ++n;
+  memset (p + idx, 0, n);               // { dg-warning "writing between 4 and 8 bytes into a region of size 3" }
+  sink (p);
+}
+
+
+void alloc_array_var_off_cst_size (size_t n, size_t idx)
+{
+  char *p = malloc (n);                 // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object" }
+
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+  sink (p);
+}
+
+
+void int_array_cst_off_cst_size (void)
+{
+  extern int iaxc[];                    // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'iaxc'" }
+
+  int *p = iaxc;
+  size_t idx = DIFF_MAX / sizeof *iaxc;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+  sink (p);
+}
+
+
+void* nowarn_anti_range_1 (char *p, char *q)
+{
+  size_t n = q - p;
+  if (!n) return 0;
+
+  char *d = __builtin_malloc (n + 1);
+  memcpy (d, p, n + 1);                 // { dg-bogus "-Wstringop-overflow" }
+  return d;
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-44.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-44.c
new file mode 100644
index 00000000000..a9cf4071fff
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-44.c
@@ -0,0 +1,103 @@
+/* Verify that writes at excessive offsets into flexible array members
+   of extern or allocated objects of unknow size are diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#define DIFF_MAX __PTRDIFF_MAX__
+
+typedef __PTRDIFF_TYPE__ ptrdiff_t;
+typedef __SIZE_TYPE__    size_t;
+
+void* memset (void*, int, size_t);
+
+void sink (void*);
+
+void char_flexarray_cst_off_cst_size (void)
+{
+  extern struct { char n, a[]; }
+    caxcc;                              // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'caxcc'" }
+
+  char *p = caxcc.a;
+  size_t idx = DIFF_MAX - 4;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 2" }
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 1" }
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 0" }
+}
+
+
+void char_flexarray_var_off_cst_size (ptrdiff_t idx)
+{
+  extern struct { char n, a[]; }
+    caxvc;                              // { dg-message "destination object 'caxvc'" }
+
+  char *p = caxvc.a;
+
+  if (idx < DIFF_MAX - 4)
+    idx = DIFF_MAX - 4;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+}
+
+
+void char_flexarray_var_off_var_size (size_t n, ptrdiff_t idx)
+{
+  extern struct { char n, a[]; }
+    caxvv;                              // { dg-message "destination object 'caxvv'" }
+
+  char *p = caxvv.a;
+
+  if (idx < DIFF_MAX - 4)
+    idx = DIFF_MAX - 4;
+
+  if (n < 3 || 7 < n)
+    n = 3;
+
+  memset (p + idx, 0, n);
+  sink (p);
+
+  ++n;
+  memset (p + idx, 0, n);               // { dg-warning "writing between 4 and 8 bytes into a region of size 3" }
+}
+
+
+void alloc_array_var_off_cst_size (size_t n, ptrdiff_t idx)
+{
+  struct { char n, a[]; }
+    *p = __builtin_malloc (n);          // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object" "" { xfail *-*-* } }
+
+  if (idx < DIFF_MAX - 4)
+    idx = DIFF_MAX - 4;
+
+  memset (p->a + idx, 0, 3);
+  sink (p);
+
+  memset (p->a + idx, 0, 5);            // { dg-warning "writing 5 bytes into a region of size 3" }
+}
+
+
+void int_array_cst_off_cst_size (void)
+{
+  extern struct { int n, a[]; }
+    iaxc;                               // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'iaxc'" }
+
+  int *p = iaxc.a;
+  size_t idx = DIFF_MAX / sizeof *p - 1;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-45.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-45.c
new file mode 100644
index 00000000000..7e9be57ae85
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-45.c
@@ -0,0 +1,255 @@
+/* PR middle-end/97023 - missing warning on buffer overflow in chained mempcpy
+   Verify that out of bounds writes by built-ins to objects through pointers
+   returned by other built-ins are diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#include "range.h"
+
+void* malloc (size_t);
+void* memcpy (void*, const void*, size_t);
+void* memmove (void*, const void*, size_t);
+void* mempcpy (void*, const void*, size_t);
+
+void sink (void*, ...);
+
+
+void nowarn_memcpy (const void *s)
+{
+  extern char cpy_a4[4];
+  unsigned n = sizeof cpy_a4;
+
+  void *p = cpy_a4;
+  p = memcpy (p, s, n);
+  sink (p);
+  memcpy (p, s, n);
+  sink (p);
+
+  p = cpy_a4 + 1;
+  p = memcpy (p, s, n - 1);
+  sink (p);
+  memcpy (p, s, n - 1);
+  sink (p);
+
+  p = cpy_a4 + 2;
+  p = memcpy (p, s, n - 2);
+  sink (p);
+  memcpy (p, s, n - 2);
+  sink (p);
+
+  p = cpy_a4 + 3;
+  p = memcpy (p, s, n - 3);
+  sink (p);
+  memcpy (p, s, n - 3);
+  sink (p);
+
+  p = cpy_a4 + 4;
+  p = memcpy (p, s, n - 4);
+  sink (p);
+  memcpy (p, s, n - 4);
+  sink (p);
+}
+
+
+void nowarn_memcpy_chain (const void *s)
+{
+  extern char cpy_a8[8];
+
+  char *p = cpy_a8;
+
+  p = memcpy (p + 1, s, 7);
+  sink (p);
+
+  p = memcpy (p + 2 , s, 5);
+  sink (p);
+
+  p = memcpy (p + 3 , s, 2);
+  sink (p);
+
+  p = memcpy (p + 1 , s, 1);
+  sink (p);
+
+  p = memcpy (p - 7 , s, 8);
+  sink (p);
+
+  memcpy (p + 1, s, 7);
+}
+
+
+void warn_memcpy (const void *s)
+{
+  extern char cpy_a5[5];                // { dg-message "destination object 'cpy_a5'" "note" }
+
+  unsigned n = sizeof cpy_a5;
+  void *p = cpy_a5;
+
+  p = memcpy (p, s, n);
+  sink (p);
+  memcpy (p, s, n + 1);                 // { dg-warning "writing 6 bytes into a region of size 5" }
+  sink (p);
+
+  p = cpy_a5;
+  p = memcpy (p, s, n);
+  sink (p);
+  memcpy (p, s, n + 1);                 // { dg-warning "writing 6 bytes into a region of size 5" }
+  sink (p);
+
+  p = cpy_a5 + 1;
+  p = memcpy (p, s, n - 1);
+  sink (p);
+  memcpy (p, s, n);                     // { dg-warning "writing 5 bytes into a region of size 4" }
+  sink (p);
+}
+
+
+void warn_memcpy_chain (const void *s)
+{
+  extern char cpy_a8[8];                // { dg-message "destination object 'cpy_a8'" "note" }
+
+  char *p = cpy_a8;
+
+  p = memcpy (p, s, 9);                 // { dg-warning "writing 9 bytes into a region of size 8" }
+  sink (p);
+
+  p = memcpy (p + 2, s, 7);             // { dg-warning "writing 7 bytes into a region of size 6" }
+  sink (p);
+
+  p = memcpy (p + 3, s, 5);             // { dg-warning "writing 5 bytes into a region of size 3" }
+  sink (p);
+
+  p = memcpy (p + 3, s, 3);             // { dg-warning "writing 3 bytes into a region of size 0" }
+  sink (p);
+}
+
+
+void nowarn_mempcpy (const void *s)
+{
+  extern char a4[4];
+  unsigned n = sizeof a4;
+
+  char *p = mempcpy (a4, s, n);
+  sink (p);
+  mempcpy (p - 4, s, n);
+  sink (p);
+
+  p = mempcpy (a4 + 1, s, n - 1);
+  sink (p);
+  mempcpy (p - 4, s, n);
+  sink (p);
+
+  p = mempcpy (a4 + 2, s, n - 2);
+  sink (p);
+  mempcpy (p - 4, s, n);
+  sink (p);
+
+  p = mempcpy (a4 + 3, s, n - 3);
+  sink (p);
+  mempcpy (p - 4, s, n);
+  sink (p);
+
+  p = mempcpy (a4 + 4, s, n - 4);
+  sink (p);
+  mempcpy (p - 4, s, n);
+  sink (p);
+}
+
+
+void nowarn_mempcpy_chain (const void *s)
+{
+  extern char pcpy_a8[8];
+
+  char *p = pcpy_a8;
+
+  p = mempcpy (p + 1, s, 7);
+  sink (p);
+
+  p = mempcpy (p - 7 , s, 7);
+  sink (p);
+
+  p = mempcpy (p - 5 , s, 5);
+  sink (p);
+
+  p = mempcpy (p - 3 , s, 3);
+  sink (p);
+
+  p = mempcpy (p - 2 , s, 2);
+  sink (p);
+
+  mempcpy (p - 1, s, 1);
+  sink (p);
+
+  mempcpy (p - 8, s, 8);
+}
+
+
+void warn_mempcpy (const void *s)
+{
+  extern char pcpy_a5[5];               // { dg-message "destination object 'pcpy_a5'" "note" }
+
+  char *p = pcpy_a5;
+
+  p = mempcpy (p, s, 5);
+  sink (p);
+  mempcpy (p - 5, s, 6);                // { dg-warning "writing 6 bytes into a region of size 5 " }
+  sink (p);
+
+  p = pcpy_a5;
+  p = mempcpy (p, s, 3);
+  sink (p);
+  mempcpy (p, s, 3);                    // { dg-warning "writing 3 bytes into a region of size 2 " }
+  sink (p);
+
+  p = pcpy_a5 + 1;
+  p = mempcpy (p, s, 3);
+  sink (p);
+  mempcpy (p - 1, s, 5);                // { dg-warning "writing 5 bytes into a region of size 2 " }
+  sink (p);
+}
+
+
+void warn_mempcpy_chain_3 (const void *s)
+{
+  char *p = malloc (5);                 // { dg-message "at offset \\\[3, 5] into destination object of size 5" }
+  p = mempcpy (p, s, UR (1, 2));
+  p = mempcpy (p, s, UR (2, 3));
+  p = mempcpy (p, s, UR (3, 4));        // { dg-warning "writing between 3 and 4 bytes into a region of size 2 " }
+
+  sink (p);
+}
+
+void warn_mempcpy_offrng_chain_3 (const void *s)
+{
+  char *p = malloc (11);                 // { dg-message "at offset \\\[9, 14] into destination object of size 11 " }
+  size_t r1_2 = UR (1, 2);
+  size_t r2_3 = r1_2 + 1;
+  size_t r3_4 = r2_3 + 1;
+
+  p = mempcpy (p + r1_2, s, r1_2);
+  p = mempcpy (p + r2_3, s, r2_3);
+  p = mempcpy (p + r3_4, s, r3_4);       // { dg-warning "writing between 3 and 4 bytes into a region of size 2 " }
+
+  sink (p);
+}
+
+void warn_mempcpy_chain_4 (const void *s)
+{
+  char *p = malloc (9);                 // { dg-message "at offset \\\[6, 9] into destination object of size 9 " }
+  p = mempcpy (p, s, UR (1, 2));
+  p = mempcpy (p, s, UR (2, 3));
+  p = mempcpy (p, s, UR (3, 4));
+  p = mempcpy (p, s, UR (4, 5));        // { dg-warning "writing between 4 and 5 bytes into a region of size 3 " }
+
+  sink (p);
+}
+
+void warn_mempcpy_chain_5 (const void *s)
+{
+  char *p = malloc (14);                // { dg-message "at offset \\\[10, 14] into destination object of size 14 " }
+  p = mempcpy (p, s, UR (1, 2));
+  p = mempcpy (p, s, UR (2, 3));
+  p = mempcpy (p, s, UR (3, 4));
+  p = mempcpy (p, s, UR (4, 5));
+  p = mempcpy (p, s, UR (5, 6));        // { dg-warning "writing between 5 and 6 bytes into a region of size 4 " }
+
+  sink (p);
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-46.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-46.c
new file mode 100644
index 00000000000..68c9c866887
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-46.c
@@ -0,0 +1,94 @@
+/* PR middle-end/97023 - missing warning on buffer overflow in chained mempcpy
+   Verify that out of bounds writes by built-ins to objects through pointers
+   returned by memchr() are diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#include "range.h"
+
+void* malloc (size_t);
+void* memchr (void*, int, size_t);
+void* memset (void*, int, size_t);
+
+void sink (void*, ...);
+
+void nowarn_memchr_cst_memset_cst (const void *s)
+{
+  char *p = malloc (4);
+  sink (p);
+
+  p = memchr (p, '1', 4);
+  memset (p, 0, 4);
+}
+
+void nowarn_memchr_uint_memset_cst (const void *s, unsigned n)
+{
+  char *p = malloc (4);
+  sink (p);
+
+  p = memchr (p, '1', n);
+  memset (p, 0, 4);
+}
+
+void nowarn_memchr_sz_memset_cst (const void *s, size_t n)
+{
+  char *p = malloc (4);
+  sink (p);
+
+  p = memchr (p, '1', n);
+  memset (p, 0, 4);
+}
+
+void nowarn_memchr_anti_range_memset_cst (const void *s, size_t n)
+{
+  char *p = malloc (4);
+  sink (p);
+
+  if (n == 0)
+    n = 1;
+
+  p = memchr (p, '1', n);
+  memset (p, 0, 4);
+}
+
+void warn_memchr_cst_memset_cst (const void *s)
+{
+  char *p = malloc (4);                 // { dg-message "at offset \\\[0, 4] into destination object of size 4 " "note" }
+  sink (p);
+
+  p = memchr (p, '1', 4);
+  memset (p, 0, 5);                     // { dg-warning "writing 5 bytes into a region of size 4 " }
+}
+
+void warn_memchr_var_memset_cst (const void *s, unsigned n)
+{
+  char *p = malloc (4);                 // { dg-message "at offset \\\[0, 4] into destination object of size 4 " "note" }
+  sink (p);
+
+  p = memchr (p, '1', n);
+  memset (p, 0, 5);                     // { dg-warning "writing 5 bytes into a region of size 4 " }
+}
+
+void warn_memchr_var_memset_range (const void *s, unsigned n)
+{
+  /* The offsets in the first two notes are bounded by the size of
+     the allocated object.  The upper bound of the offset in last
+     note is bigger because it includes the upper bound of the offset
+     of the pointer returned from the previous memchr() call.  */
+  char *p0 = malloc (UR (5, 7));
+  // { dg-message "at offset \\\[0, 7] into destination object of size \\\[5, 7]" "note" { target *-*-* } .-1 }
+  // { dg-message "at offset \\\[1, 7] into destination object of size \\\[5, 7]" "note"  { target *-*-* } .-2 }
+  // { dg-message "at offset \\\[2, 12] into destination object of size \\\[5, 7]" "note"  { target *-*-* } .-3 }
+
+  sink (p0);
+  char *p1 = memchr (p0, '1', n);
+  memset (p1, 0, UR (8, 9));            // { dg-warning "writing between 8 and 9 bytes into a region of size 7 " }
+
+  sink (p0);
+  p1 = memchr (p0 + 1, '2', n);
+  memset (p1, 0, UR (7, 9));            // { dg-warning "writing between 7 and 9 bytes into a region of size 6 " }
+
+  sink (p0);
+  char *p2 = memchr (p1 + 1, '3', n);
+  memset (p2, 0, UR (6, 9));            // { dg-warning "writing between 6 and 9 bytes into a region of size 5 " }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-47.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-47.c
new file mode 100644
index 00000000000..02b14ee2eda
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-47.c
@@ -0,0 +1,69 @@
+/* Verify that storing a bigger vector into smaller space is diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+typedef __INT16_TYPE__                         int16_t;
+typedef __attribute__ ((__vector_size__ (32))) char C32;
+
+typedef __attribute__ ((__vector_size__ (64))) int16_t I16_64;
+
+void sink (void*);
+
+
+void nowarn_c32 (char c)
+{
+  extern char nowarn_a32[32];
+
+  void *p = nowarn_a32;
+  *(C32*)p = (C32){ c };
+  sink (p);
+
+  char a32[32];
+  p = a32;
+  *(C32*)p = (C32){ c };
+  sink (p);
+}
+
+void warn_c32 (char c)
+{
+  extern char warn_a32[32];   // { dg-message "at offset 32 to object 'warn_a32' with size 32" }
+
+  void *p = warn_a32 + 1;
+  *(C32*)p = (C32){ c };      // { dg-warning "writing 1 byte into a region of size 0" }
+
+  /* Verify a local variable too. */
+  char a32[32];
+  p = a32 + 1;
+  *(C32*)p = (C32){ c };      // { dg-warning "writing 1 byte into a region of size 0" }
+  sink (p);
+}
+
+
+void nowarn_i16_64 (int16_t i)
+{
+  extern char nowarn_a64[64];
+
+  void *p = nowarn_a64;
+  I16_64 *q = (I16_64*)p;
+  *q = (I16_64){ i };
+
+  char a64[64];
+  q = (I16_64*)a64;
+  *q = (I16_64){ i };
+  sink (q);
+}
+
+void warn_i16_64 (int16_t i)
+{
+  extern char warn_a64[64];   // { dg-message "at offset 128 to object 'warn_a64' with size 64" "pr97027" { xfail *-*-* } }
+
+  void *p = warn_a64 + 1;
+  I16_64 *q = (I16_64*)p;
+  *q = (I16_64){ i };         // { dg-warning "writing 1 byte into a region of size 0" "pr97027" { xfail *-*-* } }
+
+  char a64[64];
+  p = a64 + 1;
+  q = (I16_64*)p;
+  *q = (I16_64){ i };         // { dg-warning "writing 1 byte into a region of size 0" "pr97027" { xfail *-*-* } }
+  sink (p);
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-49.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-49.c
new file mode 100644
index 00000000000..84b6c94fa7e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-49.c
@@ -0,0 +1,146 @@
+/* Verify the handling of anti-ranges/multi-ranges by allocation functions
+   and subsequent accesses.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+void* malloc (size_t);
+void  bzero (void*, size_t);
+void* memset (void*, int, size_t);
+
+
+/* Exercise size_t (via malloc and memset) and unsigned/signed int.  */
+
+__attribute__ ((alloc_size (1))) void*
+alloc_int (int);
+
+__attribute__ ((access (write_only, 1, 2))) void
+access_int (void*, int);
+
+__attribute__ ((alloc_size (1))) void*
+alloc_uint (unsigned);
+
+__attribute__ ((access (write_only, 1, 2))) void
+access_uint (void*, unsigned);
+
+
+void* nowarn_malloc_memset_same_anti_range (size_t n)
+{
+  /* Set N to the anti-range ~[3, 3].  */
+  if (n == 3)
+    n = 4;
+  void *p = malloc (n);
+
+  /* Verify there is no warning for an access to N bytes at P.
+     This means the warning has to assume the value of N in the call
+     to alloc() is in the larger subrange [4, UINT_MAX], while in
+     the call to access() in [0, 3].  */
+  return memset (p, 0, n);
+}
+
+/* Same as above but with two valid ranges.  */
+
+void* nowarn_malloc_memset_anti_range (size_t n1, size_t n2)
+{
+  /* Set N1 to the anti-range ~[3, 3].  */
+  if (n1 == 3)
+    n1 = 4;
+  void *p = malloc (n1);
+
+  /* Set N2 to the anti-range ~[7, 7].  */
+  if (n2 == 7)
+    n2 = 8;
+
+  return memset (p, 0, n2);
+}
+
+
+void nowarn_alloc_access_same_anti_range_int (int n)
+{
+  /* Set N to the anti-range ~[3, 3].  */
+  if (n == 3)
+    n = 4;
+  void *p = alloc_int (n);
+
+  /* Verify there is no warning for an access to N bytes at P.
+     This means the warning has to assume the value of N in the call
+     to alloc() is in the larger subrange [4, UINT_MAX], while in
+     the call to access() in [0, 3].  */
+  access_int (p, n);
+}
+
+/* Same as above but with two valid ranges.  */
+
+void nowarn_alloc_access_anti_range_int (int n1, int n2)
+{
+  /* Set N1 to the anti-range ~[3, 3].  */
+  if (n1 == 3)
+    n1 = 4;
+  void *p = alloc_int (n1);
+
+  /* Set N2 to the anti-range ~[7, 7].  */
+  if (n2 == 7)
+    n2 = 8;
+
+  access_int (p, n2);
+}
+
+
+void nowarn_alloc_access_same_anti_range_uint (unsigned n)
+{
+  /* Set N to the anti-range ~[3, 3].  */
+  if (n == 3)
+    n = 4;
+  void *p = alloc_uint (n);
+
+  /* Verify there is no warning for an access to N bytes at P.
+     This means the warning has to assume the value of N in the call
+     to alloc() is in the larger subrange [4, UINT_MAX], while in
+     the call to access() in [0, 3].  */
+  access_uint (p, n);
+}
+
+/* Same as above but with two valid ranges.  */
+
+void nowarn_alloc_access_anti_range_uint (unsigned n1, unsigned n2)
+{
+  /* Set N1 to the anti-range ~[3, 3].  */
+  if (n1 == 3)
+    n1 = 4;
+  void *p = alloc_uint (n1);
+
+  /* Set N2 to the anti-range ~[7, 7].  */
+  if (n2 == 7)
+    n2 = 8;
+
+  access_uint (p, n2);
+}
+
+
+void* nowarn_malloc_anti_range_memset_range (size_t n1, size_t n2)
+{
+  /* Set N1 to the anti-range ~[3, 3].  */
+  if (n1 == 3)
+    n1 = 4;
+  void *p = malloc (n1);
+
+  /* Set N2 to the range [5, MAX].  */
+  if (n2 < 5)
+    n2 = 5;
+  return memset (p, 0, n2);
+}
+
+void* nowarn_malloc_range_bzero_anti_range (size_t n1, size_t n2)
+{
+  /* Set N1 to the anti-range ~[3, 3].  */
+  if (n1 > 4)
+    n1 = 4;
+  void *p = malloc (n1);
+
+  /* Set N2 to the range [5, MAX].  */
+  if (n2 <= 3 || 5 <= n2)
+    n2 = 4;
+  bzero (p, n2);
+  return p;
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-50.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-50.c
new file mode 100644
index 00000000000..ca98286f449
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-50.c
@@ -0,0 +1,121 @@
+/* Verify that writes at excessive offsets into objects of unknown size
+   pointed to by function arguments are diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#define DIFF_MAX __PTRDIFF_MAX__
+
+typedef __PTRDIFF_TYPE__ ptrdiff_t;
+typedef __SIZE_TYPE__    size_t;
+
+void* memset (void*, int, size_t);
+
+void sink (void*);
+
+char* fcall (void);
+
+void char_ptr_cst_off_cst_size (char *p)
+                                        // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'p'" "note" { target *-*-* } .-1 }
+{
+  size_t idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 2" }
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 1" }
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 0" }
+}
+
+
+void char_ptr_var_difoff_cst_size (ptrdiff_t idx)
+{
+  char *p = fcall ();
+  // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object of size \\\[0, \[1-9\]\[0-9\]+] (allocated|returned) by 'fcall'" "note" { target *-*-* } .-1 }
+
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+}
+
+
+void char_ptr_var_szoff_cst_size (size_t idx)
+{
+  extern char* gptr;
+  // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object 'gptr'" "note" { target *-*-* } .-1 }
+
+  char *p = gptr;
+
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" "" { xfail *-*-* } }
+
+  if (idx > DIFF_MAX)
+    idx = DIFF_MAX;
+
+  memset (p + idx, 0, 7);               // { dg-warning "writing 7 bytes into a region of size 3" }
+}
+
+
+void char_ptr_var_difoff_var_size (char *p, ptrdiff_t idx, size_t n)
+                                        // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, \[1-9\]\[0-9\]+] into destination object 'p'" "note" { target *-*-* } .-1 }
+{
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  if (n < 3 || 7 < n)
+    n = 3;
+
+  memset (p + idx, 0, n);
+  sink (p);
+
+  ++n;
+  memset (p + idx, 0, n);               // { dg-warning "writing between 4 and 8 bytes into a region of size 3" }
+}
+
+
+void char_ptr_var_szoff_var_size (char *p, size_t idx, size_t n)
+                                        // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, \[1-9\]\[0-9\]+] into destination object 'p'" "note" { xfail *-*-* } .-1 }
+{
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  if (n < 3 || 7 < n)
+    n = 3;
+
+  memset (p + idx, 0, n);
+  sink (p);
+
+  ++n;
+  /* With an unsigned offset large values are interpreted as negative
+     so the addition (p + idx) is effectively treated as subtraction,
+     making an overflow indistinguishable from a valid (if unlikely)
+     store.  */
+  memset (p + idx, 0, n);               // { dg-warning "writing between 4 and 8 bytes into a region of size 3" "pr?????" { xfail *-*-* } }
+}
+
+
+void int_ptr_cst_off_cst_size (int *p)
+                                        // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'p'" "note" { target *-*-* } .-1 }
+{
+  size_t idx = DIFF_MAX / sizeof *p;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-51.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-51.c
new file mode 100644
index 00000000000..6f36643c8bb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-51.c
@@ -0,0 +1,34 @@
+/* Test case derived from Binutils/GDB's readline/readline/histexpand.c.
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+char *
+get_subst_pattern (char *str, int *iptr, int delimiter, int is_rhs, int *lenptr)
+{
+  int si, i, j, k;
+  char *s;
+
+  s = 0;
+  i = *iptr;
+
+  for (si = i; str[si] && str[si] != delimiter; si++)
+      if (str[si] == '\\' && str[si + 1] == delimiter)
+	si++;
+
+  if (si > i || is_rhs)
+    {
+      s = (char *)__builtin_malloc (si - i + 1);
+      for (j = 0, k = i; k < si; j++, k++)
+	{
+	  /* Remove a backslash quoting the search string delimiter. */
+	  if (str[k] == '\\' && str[k + 1] == delimiter)
+	    k++;
+	  s[j] = str[k];   // { dg-bogus "-Wstringop-overflow" }
+	}
+      s[j] = '\0';
+      if (lenptr)
+	*lenptr = j;
+    }
+
+  return s;
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-52.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-52.c
new file mode 100644
index 00000000000..a28965557c3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-52.c
@@ -0,0 +1,62 @@
+
+/* PR middle-end/97023 - missing warning on buffer overflow in chained mempcpy
+   Verify that writes by built-in functions to objects through pointers
+   returned by ordinary (non-built-int) function are assumed to point to
+   the beginning of objects.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#include "range.h"
+
+void* memcpy (void*, const void*, size_t);
+void* memset (void*, int, size_t);
+
+void sink (void*, ...);
+
+extern char* arrptr[];
+extern char* ptr;
+extern char* retptr (void);
+struct S { char *p; };
+extern struct S retstruct (void);
+
+void nowarn_ptr (void)
+{
+  {
+    void *p = arrptr;
+    memset (p - 1, 0, 12345);           // { dg-warning "\\\[-Wstringop-overflow" }
+    memset (p,0, 12345);
+    memset (p,0, DIFF_MAX - 1);
+  }
+
+  {
+    char *p = arrptr[0];
+    memset (p - 1, 0, 12345);
+    memset (p - 12345, 0, 12345);
+    memset (p - 1234, 0, DIFF_MAX - 1);
+    memset (p - DIFF_MAX + 1, 0, 12345);
+  }
+
+  {
+    char *p = ptr;
+    memset (p - 1, 0, 12345);
+    memset (p - 12345, 0, 12345);
+    memset (p - 1234, 0, DIFF_MAX - 1);
+    memset (p - DIFF_MAX + 1, 0, 12345);
+  }
+
+  {
+    char *p = retptr ();
+    memset (p - 1, 0, 12345);
+    memset (p - 12345, 0, 12345);
+    memset (p - 1234, 0, DIFF_MAX - 1);
+    memset (p - DIFF_MAX + 1, 0, 12345);
+  }
+
+  {
+    char *p = retstruct ().p;
+    memset (p - 1, 0, 12345);
+    memset (p - 12345, 0, 12345);
+    memset (p - 1234, 0, DIFF_MAX - 1);
+    memset (p - DIFF_MAX + 1, 0, 12345);
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-53.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-53.c
new file mode 100644
index 00000000000..cd8fa3202eb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-53.c
@@ -0,0 +1,116 @@
+/* PR middle-end/96384 - bogus -Wstringop-overflow= storing into
+   multidimensional array with index in range
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+#define SHRT_MAX   __SHRT_MAX__
+#define SHRT_MIN   (-SHRT_MAX - 1)
+#define INT_MAX    __INT_MAX__
+#define INT_MIN    (-INT_MAX - 1)
+#define LONG_MAX   __LONG_MAX__
+#define LONG_MIN   (-LONG_MAX - 1)
+
+#define USHRT_MAX  (SHRT_MAX * 2 + 1)
+#define UINT_MAX   ~0U
+#define ULONG_MAX  ~0LU
+
+char ca3_5_7[3][5][7];
+
+void nowarn_ca_3_5_ssi (short i)
+{
+  if (i > SHRT_MAX - 1)
+    i = SHRT_MAX - 1;
+
+  ca3_5_7[i][0][0] = __LINE__;
+  ca3_5_7[i][0][1] = __LINE__;
+  ca3_5_7[i][0][2] = __LINE__;
+  ca3_5_7[i][0][3] = __LINE__;
+  ca3_5_7[i][0][4] = __LINE__;
+  ca3_5_7[i][0][5] = __LINE__;
+  ca3_5_7[i][0][6] = __LINE__;
+
+  ca3_5_7[i][1][0] = __LINE__;
+  ca3_5_7[i][1][1] = __LINE__;
+  ca3_5_7[i][1][2] = __LINE__;
+  ca3_5_7[i][1][3] = __LINE__;
+  ca3_5_7[i][1][4] = __LINE__;
+  ca3_5_7[i][1][5] = __LINE__;
+  ca3_5_7[i][1][6] = __LINE__;
+
+  ca3_5_7[i][2][0] = __LINE__;
+  ca3_5_7[i][2][1] = __LINE__;
+  ca3_5_7[i][2][2] = __LINE__;
+  ca3_5_7[i][2][3] = __LINE__;
+  ca3_5_7[i][2][4] = __LINE__;
+  ca3_5_7[i][2][5] = __LINE__;
+  ca3_5_7[i][2][6] = __LINE__;
+
+  ca3_5_7[i][3][0] = __LINE__;
+  ca3_5_7[i][3][1] = __LINE__;
+  ca3_5_7[i][3][2] = __LINE__;
+  ca3_5_7[i][3][3] = __LINE__;
+  ca3_5_7[i][3][4] = __LINE__;
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[i][3][6] = __LINE__;
+
+  ca3_5_7[i][4][0] = __LINE__;
+  ca3_5_7[i][4][1] = __LINE__;
+  ca3_5_7[i][4][2] = __LINE__;
+  ca3_5_7[i][4][3] = __LINE__;
+  ca3_5_7[i][4][4] = __LINE__;
+  ca3_5_7[i][4][5] = __LINE__;
+  ca3_5_7[i][4][6] = __LINE__;
+
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_usi (unsigned short i)
+{
+  if (i > USHRT_MAX - 1)
+    i = USHRT_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_si (int i)
+{
+  if (i > INT_MAX - 1)
+    i = INT_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_ui (unsigned i)
+{
+  if (i > UINT_MAX - 1)
+    i = UINT_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_li (long i)
+{
+  if (i > LONG_MAX - 1)
+    i = LONG_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_uli (unsigned long i)
+{
+  if (i > ULONG_MAX - 1)
+    i = ULONG_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
diff --git a/gcc/testsuite/gcc.dg/pr51683.c b/gcc/testsuite/gcc.dg/pr51683.c
index c477cd8fdd2..7a624e4055d 100644
--- a/gcc/testsuite/gcc.dg/pr51683.c
+++ b/gcc/testsuite/gcc.dg/pr51683.c
@@ -12,6 +12,9 @@ void *
 foo (void *p)
 {
   return bar ((void *) 0x12345000, p, 256);
+  /* Integers converted to pointers are assumed to be the result of
+     (invalid) arithmetic on null pointers.
+     { dg-prune-output "writing 256 bytes into a region of size 0" } */
 }
 
 /* { dg-final { scan-tree-dump "memcpy" "optimized" } } */

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-09-22 20:05           ` Martin Sebor
@ 2020-09-26  5:17             ` Jason Merrill
  2020-09-28 22:01               ` Martin Sebor
  0 siblings, 1 reply; 28+ messages in thread
From: Jason Merrill @ 2020-09-26  5:17 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On 9/22/20 4:05 PM, Martin Sebor wrote:
> The rebased and retested patches are attached.
> 
> On 9/21/20 3:17 PM, Martin Sebor wrote:
>> Ping: 
>> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/553906.html
>>
>> (I'm working on rebasing the patch on top of the latest trunk which
>> has changed some of the same code but it'd be helpful to get a go-
>> ahead on substance the changes.  I don't expect the rebase to
>> require any substantive modifications.)
>>
>> Martin
>>
>> On 9/14/20 4:01 PM, Martin Sebor wrote:
>>> On 9/4/20 11:14 AM, Jason Merrill wrote:
>>>> On 9/3/20 2:44 PM, Martin Sebor wrote:
>>>>> On 9/1/20 1:22 PM, Jason Merrill wrote:
>>>>>> On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
>>>>>>> -Wplacement-new handles array indices and pointer offsets the same:
>>>>>>> by adjusting them by the size of the element.  That's correct for
>>>>>>> the latter but wrong for the former, causing false positives when
>>>>>>> the element size is greater than one.
>>>>>>>
>>>>>>> In addition, the warning doesn't even attempt to handle arrays of
>>>>>>> arrays.  I'm not sure if I forgot or if I simply didn't think of
>>>>>>> it.
>>>>>>>
>>>>>>> The attached patch corrects these oversights by replacing most
>>>>>>> of the -Wplacement-new code with a call to compute_objsize which
>>>>>>> handles all this correctly (plus more), and is also better tested.
>>>>>>> But even compute_objsize has bugs: it trips up while converting
>>>>>>> wide_int to offset_int for some pointer offset ranges.  Since
>>>>>>> handling the C++ IL required changes in this area the patch also
>>>>>>> fixes that.
>>>>>>>
>>>>>>> For review purposes, the patch affects just the middle end.
>>>>>>> The C++ diff pretty much just removes code from the front end.
>>>>>>
>>>>>> The C++ changes are OK.
>>>>>
>>>>> Thank you for looking at the rest as well.
>>>>>
>>>>>>
>>>>>>> -compute_objsize (tree ptr, int ostype, access_ref *pref,
>>>>>>> -                bitmap *visited, const vr_values *rvals /* = 
>>>>>>> NULL */)
>>>>>>> +compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap 
>>>>>>> *visited,
>>>>>>> +                const vr_values *rvals)
>>>>>>
>>>>>> This reformatting seems unnecessary, and I prefer to keep the 
>>>>>> comment about the default argument.
>>>>>
>>>>> This overload doesn't take a default argument.  (There was a stray
>>>>> declaration of a similar function at the top of the file that had
>>>>> one.  I've removed it.)
>>>>
>>>> Ah, true.
>>>>
>>>>>>> -      if (!size || TREE_CODE (size) != INTEGER_CST)
>>>>>>> -       return false;
>>>>>>  >...
>>>>>>
>>>>>> You change some failure cases in compute_objsize to return success 
>>>>>> with a maximum range, while others continue to return failure. 
>>>>>> This needs commentary about the design rationale.
>>>>>
>>>>> This is too much for a comment in the code but the background is
>>>>> this: compute_objsize initially returned the object size as a 
>>>>> constant.
>>>>> Recently, I have enhanced it to return a range to improve warnings for
>>>>> allocated objects.  With that, a failure can be turned into success by
>>>>> having the function set the range to that of the largest object.  That
>>>>> should simplify the function's callers and could even improve
>>>>> the detection of some invalid accesses.  Once this change is made
>>>>> it might even be possible to change its return type to void.
>>>>>
>>>>> The change that caught your eye is necessary to make the function
>>>>> a drop-in replacement for the C++ front end code which makes this
>>>>> same assumption.  Without it, a number of test cases that exercise
>>>>> VLAs fail in g++.dg/warn/Wplacement-new-size-5.C.  For example:
>>>>>
>>>>>    void f (int n)
>>>>>    {
>>>>>      char a[n];
>>>>>      new (a - 1) int ();
>>>>>    }
>>>>>
>>>>> Changing any of the other places isn't necessary for existing tests
>>>>> to pass (and I didn't want to introduce too much churn).  But I do
>>>>> want to change the rest of the function along the same lines at some
>>>>> point.
>>>>
>>>> Please do change the other places to be consistent; better to have 
>>>> more churn than to leave the function half-updated.  That can be a 
>>>> separate patch if you prefer, but let's do it now rather than later.
>>>
>>> I've made most of these changes in the other patch (also attached).
>>> I'm quite happy with the result but it turned out to be a lot more
>>> work than either of us expected, mostly due to the amount of testing.
>>>
>>> I've left a couple of failing cases in place mainly as reminders
>>> to handle them better (which means I also didn't change the caller
>>> to avoid testing for failures).  I've also added TODO notes with
>>> reminders to handle some of the new codes more completely.
>>>
>>>>
>>>>>>> +  special_array_member sam{ };
>>>>>>
>>>>>> sam is always set by component_ref_size, so I don't think it's 
>>>>>> necessary to initialize it at the declaration.
>>>>>
>>>>> I find initializing pass-by-pointer local variables helpful but
>>>>> I don't insist on it.
>>>>>
>>>>>>
>>>>>>> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
>>>>>>>    tree last_type = TREE_TYPE (last);
>>>>>>>    if (TREE_CODE (last_type) != ARRAY_TYPE
>>>>>>>        || TYPE_SIZE (last_type))
>>>>>>> -    return size;
>>>>>>> +    return size ? size : TYPE_SIZE_UNIT (type);
>>>>>>
>>>>>> This change seems to violate the comment for the function.
>>>>>
>>>>> By my reading (and writing) the change is covered by the first
>>>>> sentence:
>>>>>
>>>>>     Returns the size of the object designated by DECL considering
>>>>>     its initializer if it either has one or if it would not affect
>>>>>     its size, ...
>>>>
>>>> OK, I see it now.
>>>>
>>>>> It handles a number of cases in Wplacement-new-size.C fail that
>>>>> construct a larger object in an extern declaration of a template,
>>>>> like this:
>>>>>
>>>>>    template <class> struct S { char c; };
>>>>>    extern S<int> s;
>>>>>
>>>>>    void f ()
>>>>>    {
>>>>>      new (&s) int ();
>>>>>    }
>>>>>
>>>>> I don't know why DECL_SIZE isn't set here (I don't think it can
>>>>> be anything but equal to TYPE_SIZE, can it?) and other than struct
>>>>> objects with a flexible array member where this identity doesn't
>>>>> hold I can't think of others.  Am I missing something?
>>>>
>>>> Good question.  The attached patch should fix that, so you shouldn't 
>>>> need the change to decl_init_size:
>>>
>>> I've integrated it into the bug fix.
>>>
>>> Besides the usual x86_64-linux bootstrap/regtest I tested both
>>> patches by building a few packages, including Binutils/GDB, Glibc,
>>> and  verifying no new warnings show up.
>>>
>>> Martin

> +offset_int
> +access_ref::size_remaining (offset_int *pmin /* = NULL */) const

For the various member functions, please include the comments with the 
definition as well as the in-class declaration.

> +      if (offrng[1] < offrng[0])

What does it mean for the max offset to be less than the min offset?  I 
wouldn't expect that to ever happen with wide integers.

> +  /* Return true if OFFRNG is bounded to a subrange of possible offset
> +     values.  */
> +  bool offset_bounded () const;

I don't understand how you're using this.  The implementation checks for 
the possible offset values falling outside those representable by 
ptrdiff_t, unless the range is only a single value.  And then the only 
use is

> +  if (ref.offset_zero () || !ref.offset_bounded ())
> +    inform (DECL_SOURCE_LOCATION (ref.ref),
> +	    "%qD declared here", ref.ref);
> +  else if (ref.offrng[0] == ref.offrng[1])
> +    inform (DECL_SOURCE_LOCATION (ref.ref),
> +	    "at offset %wi from %qD declared here",
> +	    ref.offrng[0].to_shwi (), ref.ref);
> +  else
> +    inform (DECL_SOURCE_LOCATION (ref.ref),
> +	    "at offset [%wi, %wi] from %qD declared here",
> +	    ref.offrng[0].to_shwi (), ref.offrng[1].to_shwi (), ref.ref);

So if the possible offsets are all representable by ptrdiff_t, we don't 
print the range?  The middle case also looks unreachable, since 
offset_bounded will return false in that case.

Jason


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-09-26  5:17             ` Jason Merrill
@ 2020-09-28 22:01               ` Martin Sebor
  2020-10-05 16:37                 ` Martin Sebor
                                   ` (2 more replies)
  0 siblings, 3 replies; 28+ messages in thread
From: Martin Sebor @ 2020-09-28 22:01 UTC (permalink / raw)
  To: Jason Merrill, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 9722 bytes --]

On 9/25/20 11:17 PM, Jason Merrill wrote:
> On 9/22/20 4:05 PM, Martin Sebor wrote:
>> The rebased and retested patches are attached.
>>
>> On 9/21/20 3:17 PM, Martin Sebor wrote:
>>> Ping: 
>>> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/553906.html
>>>
>>> (I'm working on rebasing the patch on top of the latest trunk which
>>> has changed some of the same code but it'd be helpful to get a go-
>>> ahead on substance the changes.  I don't expect the rebase to
>>> require any substantive modifications.)
>>>
>>> Martin
>>>
>>> On 9/14/20 4:01 PM, Martin Sebor wrote:
>>>> On 9/4/20 11:14 AM, Jason Merrill wrote:
>>>>> On 9/3/20 2:44 PM, Martin Sebor wrote:
>>>>>> On 9/1/20 1:22 PM, Jason Merrill wrote:
>>>>>>> On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
>>>>>>>> -Wplacement-new handles array indices and pointer offsets the same:
>>>>>>>> by adjusting them by the size of the element.  That's correct for
>>>>>>>> the latter but wrong for the former, causing false positives when
>>>>>>>> the element size is greater than one.
>>>>>>>>
>>>>>>>> In addition, the warning doesn't even attempt to handle arrays of
>>>>>>>> arrays.  I'm not sure if I forgot or if I simply didn't think of
>>>>>>>> it.
>>>>>>>>
>>>>>>>> The attached patch corrects these oversights by replacing most
>>>>>>>> of the -Wplacement-new code with a call to compute_objsize which
>>>>>>>> handles all this correctly (plus more), and is also better tested.
>>>>>>>> But even compute_objsize has bugs: it trips up while converting
>>>>>>>> wide_int to offset_int for some pointer offset ranges.  Since
>>>>>>>> handling the C++ IL required changes in this area the patch also
>>>>>>>> fixes that.
>>>>>>>>
>>>>>>>> For review purposes, the patch affects just the middle end.
>>>>>>>> The C++ diff pretty much just removes code from the front end.
>>>>>>>
>>>>>>> The C++ changes are OK.
>>>>>>
>>>>>> Thank you for looking at the rest as well.
>>>>>>
>>>>>>>
>>>>>>>> -compute_objsize (tree ptr, int ostype, access_ref *pref,
>>>>>>>> -                bitmap *visited, const vr_values *rvals /* = 
>>>>>>>> NULL */)
>>>>>>>> +compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap 
>>>>>>>> *visited,
>>>>>>>> +                const vr_values *rvals)
>>>>>>>
>>>>>>> This reformatting seems unnecessary, and I prefer to keep the 
>>>>>>> comment about the default argument.
>>>>>>
>>>>>> This overload doesn't take a default argument.  (There was a stray
>>>>>> declaration of a similar function at the top of the file that had
>>>>>> one.  I've removed it.)
>>>>>
>>>>> Ah, true.
>>>>>
>>>>>>>> -      if (!size || TREE_CODE (size) != INTEGER_CST)
>>>>>>>> -       return false;
>>>>>>>  >...
>>>>>>>
>>>>>>> You change some failure cases in compute_objsize to return 
>>>>>>> success with a maximum range, while others continue to return 
>>>>>>> failure. This needs commentary about the design rationale.
>>>>>>
>>>>>> This is too much for a comment in the code but the background is
>>>>>> this: compute_objsize initially returned the object size as a 
>>>>>> constant.
>>>>>> Recently, I have enhanced it to return a range to improve warnings 
>>>>>> for
>>>>>> allocated objects.  With that, a failure can be turned into 
>>>>>> success by
>>>>>> having the function set the range to that of the largest object.  
>>>>>> That
>>>>>> should simplify the function's callers and could even improve
>>>>>> the detection of some invalid accesses.  Once this change is made
>>>>>> it might even be possible to change its return type to void.
>>>>>>
>>>>>> The change that caught your eye is necessary to make the function
>>>>>> a drop-in replacement for the C++ front end code which makes this
>>>>>> same assumption.  Without it, a number of test cases that exercise
>>>>>> VLAs fail in g++.dg/warn/Wplacement-new-size-5.C.  For example:
>>>>>>
>>>>>>    void f (int n)
>>>>>>    {
>>>>>>      char a[n];
>>>>>>      new (a - 1) int ();
>>>>>>    }
>>>>>>
>>>>>> Changing any of the other places isn't necessary for existing tests
>>>>>> to pass (and I didn't want to introduce too much churn).  But I do
>>>>>> want to change the rest of the function along the same lines at some
>>>>>> point.
>>>>>
>>>>> Please do change the other places to be consistent; better to have 
>>>>> more churn than to leave the function half-updated.  That can be a 
>>>>> separate patch if you prefer, but let's do it now rather than later.
>>>>
>>>> I've made most of these changes in the other patch (also attached).
>>>> I'm quite happy with the result but it turned out to be a lot more
>>>> work than either of us expected, mostly due to the amount of testing.
>>>>
>>>> I've left a couple of failing cases in place mainly as reminders
>>>> to handle them better (which means I also didn't change the caller
>>>> to avoid testing for failures).  I've also added TODO notes with
>>>> reminders to handle some of the new codes more completely.
>>>>
>>>>>
>>>>>>>> +  special_array_member sam{ };
>>>>>>>
>>>>>>> sam is always set by component_ref_size, so I don't think it's 
>>>>>>> necessary to initialize it at the declaration.
>>>>>>
>>>>>> I find initializing pass-by-pointer local variables helpful but
>>>>>> I don't insist on it.
>>>>>>
>>>>>>>
>>>>>>>> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
>>>>>>>>    tree last_type = TREE_TYPE (last);
>>>>>>>>    if (TREE_CODE (last_type) != ARRAY_TYPE
>>>>>>>>        || TYPE_SIZE (last_type))
>>>>>>>> -    return size;
>>>>>>>> +    return size ? size : TYPE_SIZE_UNIT (type);
>>>>>>>
>>>>>>> This change seems to violate the comment for the function.
>>>>>>
>>>>>> By my reading (and writing) the change is covered by the first
>>>>>> sentence:
>>>>>>
>>>>>>     Returns the size of the object designated by DECL considering
>>>>>>     its initializer if it either has one or if it would not affect
>>>>>>     its size, ...
>>>>>
>>>>> OK, I see it now.
>>>>>
>>>>>> It handles a number of cases in Wplacement-new-size.C fail that
>>>>>> construct a larger object in an extern declaration of a template,
>>>>>> like this:
>>>>>>
>>>>>>    template <class> struct S { char c; };
>>>>>>    extern S<int> s;
>>>>>>
>>>>>>    void f ()
>>>>>>    {
>>>>>>      new (&s) int ();
>>>>>>    }
>>>>>>
>>>>>> I don't know why DECL_SIZE isn't set here (I don't think it can
>>>>>> be anything but equal to TYPE_SIZE, can it?) and other than struct
>>>>>> objects with a flexible array member where this identity doesn't
>>>>>> hold I can't think of others.  Am I missing something?
>>>>>
>>>>> Good question.  The attached patch should fix that, so you 
>>>>> shouldn't need the change to decl_init_size:
>>>>
>>>> I've integrated it into the bug fix.
>>>>
>>>> Besides the usual x86_64-linux bootstrap/regtest I tested both
>>>> patches by building a few packages, including Binutils/GDB, Glibc,
>>>> and  verifying no new warnings show up.
>>>>
>>>> Martin
> 
>> +offset_int
>> +access_ref::size_remaining (offset_int *pmin /* = NULL */) const
> 
> For the various member functions, please include the comments with the 
> definition as well as the in-class declaration.

Only one access_ref member function is defined out-of-line: 
offset_bounded().  I've adjusted the comment and copied it above
the function definition.

> 
>> +      if (offrng[1] < offrng[0])
> 
> What does it mean for the max offset to be less than the min offset?  I 
> wouldn't expect that to ever happen with wide integers.

The offset is represented in sizetype with negative values represented
as large positive values, but has to be converted to ptrdiff_t.  These
cases come up when the unsigned offset is an ordinary range that
corresponds to an anti-range, such as here:

   extern char a[2];

   void f (unsigned long i)
   {
     if (i == 0)
       return;
     a[i] = 0;   // i's range is [1, -1] (i.e., [1, SIZE_MAX]
   }

> 
>> +  /* Return true if OFFRNG is bounded to a subrange of possible offset
>> +     values.  */
>> +  bool offset_bounded () const;
> 
> I don't understand how you're using this.  The implementation checks for 
> the possible offset values falling outside those representable by 
> ptrdiff_t, unless the range is only a single value.  And then the only 
> use is
> 
>> +  if (ref.offset_zero () || !ref.offset_bounded ())
>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>> +        "%qD declared here", ref.ref);
>> +  else if (ref.offrng[0] == ref.offrng[1])
>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>> +        "at offset %wi from %qD declared here",
>> +        ref.offrng[0].to_shwi (), ref.ref);
>> +  else
>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>> +        "at offset [%wi, %wi] from %qD declared here",
>> +        ref.offrng[0].to_shwi (), ref.offrng[1].to_shwi (), ref.ref);
> 
> So if the possible offsets are all representable by ptrdiff_t, we don't 
> print the range?  The middle case also looks unreachable, since 
> offset_bounded will return false in that case.

The function was originally named "offset_unbounded."  I changed
it to "offset_bounded" but looks like I didn't finish the job or
add any tests for it.

The goal of conditionals is to avoid overwhelming the user with
excessive numbers that may not be meaningful or even relevant
to the warning.  I've corrected the function body, tweaked and
renamed the get_range function to get_offset_range to do a better
job of extracting ranges from the types of some nonconstant
expressions the front end passes it, and added a new test for
all this.  Attached is the new revision.

Martin

[-- Attachment #2: gcc-96511.diff --]
[-- Type: text/x-patch, Size: 44623 bytes --]

Correct handling of indices into arrays with elements larger than 1 (PR c++/96511)

Resolves:
PR c++/96511 - Incorrect -Wplacement-new on POINTER_PLUS into an array with 4-byte elements
PR middle-end/96384 - bogus -Wstringop-overflow= storing into multidimensional array with index in range

gcc/ChangeLog:

	PR c++/96511
	PR middle-end/96384
	* builtins.c (get_range): Return full range of type when neither
	value nor its range is available.  Fail for ranges inverted due
	to the signedness of offsets.
	(compute_objsize): Handle more special array members.  Handle
	POINTER_PLUS_EXPR and VIEW_CONVERT_EXPR that come up in front end
	code.
	(access_ref::offset_bounded): Define new member function.
	* builtins.h (access_ref::eval): New data member.
	(access_ref::offset_bounded): New member function.
	(access_ref::offset_zero): New member function.
	(compute_objsize): Declare a new overload.
	* gimple-array-bounds.cc (array_bounds_checker::check_array_ref): Use
	enum special_array_member.
	* tree-object-size.c (decl_init_size): Return the size of the structure
	type if the decl size is null.
	* tree.c (component_ref_size): Use special_array_member.
	* tree.h (special_array_member): Define a new type.
	(component_ref_size): Change signature/	

gcc/cp/ChangeLog:

	PR c++/96511
	PR middle-end/96384
	* decl.c (complete_vars): Call layout_decl.
	* init.c (warn_placement_new_too_small): Call builtin_objsize instead
	of duplicating what it does.

gcc/testsuite/ChangeLog:

	PR c++/96511
	PR middle-end/96384
	* g++.dg/warn/Wplacement-new-size-1.C: Relax warnings.
	* g++.dg/warn/Wplacement-new-size-2.C: Same.
	* g++.dg/warn/Wplacement-new-size-6.C: Same.
	* gcc.dg/Warray-bounds-58.c: Adjust
	* gcc.dg/Wstringop-overflow-37.c: Same.
	* g++.dg/warn/Wplacement-new-size-7.C: New test.
	* gcc.dg/Wstringop-overflow-40.c: New test.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 45efc1c3992..b5680bf93dd 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -199,7 +199,7 @@ static void expand_builtin_sync_synchronize (void);
 
 access_ref::access_ref (tree bound /* = NULL_TREE */,
 			bool minaccess /* = false */)
-  : ref ()
+: ref (), eval ([](tree x){ return x; }), trail1special (true)
 {
   /* Set to valid.  */
   offrng[0] = offrng[1] = 0;
@@ -4302,10 +4302,34 @@ static bool
 get_range (tree x, signop sgn, offset_int r[2],
 	   const vr_values *rvals /* = NULL */)
 {
+  tree type = TREE_TYPE (x);
+  if (TREE_CODE (x) != INTEGER_CST
+      && TREE_CODE (x) != SSA_NAME)
+    {
+      if (TYPE_UNSIGNED (type))
+	{
+	  if (sgn == SIGNED)
+	    type = signed_type_for (type);
+	}
+      else if (sgn == UNSIGNED)
+	type = unsigned_type_for (type);
+
+      r[0] = wi::to_offset (TYPE_MIN_VALUE (type));
+      r[1] = wi::to_offset (TYPE_MAX_VALUE (type));
+      return x;
+    }
+
   wide_int wr[2];
   if (!get_range (x, wr, rvals))
     return false;
 
+  /* Only convert signed integers or unsigned sizetype to a signed
+     offset and avoid converting large positive values in narrower
+     types to negative offsets.  */
+  if (TYPE_UNSIGNED (type)
+      && wr[0].get_precision () < TYPE_PRECISION (sizetype))
+    sgn = UNSIGNED;
+
   r[0] = offset_int::from (wr[0], sgn);
   r[1] = offset_int::from (wr[1], sgn);
   return true;
@@ -4326,9 +4350,11 @@ get_range (tree x, signop sgn, offset_int r[2],
    to influence code generation or optimization.  */
 
 static bool
-compute_objsize (tree ptr, int ostype, access_ref *pref,
-		 bitmap *visited, const vr_values *rvals /* = NULL */)
+compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
+		 const vr_values *rvals)
 {
+  STRIP_NOPS (ptr);
+
   const bool addr = TREE_CODE (ptr) == ADDR_EXPR;
   if (addr)
     ptr = TREE_OPERAND (ptr, 0);
@@ -4340,12 +4366,15 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
       if (!addr && POINTER_TYPE_P (TREE_TYPE (ptr)))
 	return false;
 
-      tree size = decl_init_size (ptr, false);
-      if (!size || TREE_CODE (size) != INTEGER_CST)
-	return false;
-
       pref->ref = ptr;
-      pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
+      if (tree size = decl_init_size (ptr, false))
+	if (TREE_CODE (size) == INTEGER_CST)
+	  {
+	    pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
+	    return true;
+	  }
+      pref->sizrng[0] = 0;
+      pref->sizrng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
       return true;
     }
 
@@ -4353,13 +4382,13 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
   if (code == COMPONENT_REF)
     {
+      tree ref = TREE_OPERAND (ptr, 0);
       tree field = TREE_OPERAND (ptr, 1);
 
       if (ostype == 0)
 	{
 	  /* For raw memory functions like memcpy bail if the size
 	     of the enclosing object cannot be determined.  */
-	  tree ref = TREE_OPERAND (ptr, 0);
 	  if (!compute_objsize (ref, ostype, pref, visited, rvals)
 	      || !pref->ref)
 	    return false;
@@ -4381,20 +4410,28 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 	return false;
 
       pref->ref = field;
-      /* Only return constant sizes for now while callers depend
-	 on it.  INT0LEN is true for interior zero-length arrays.  */
-      bool int0len = false;
-      tree size = component_ref_size (ptr, &int0len);
-      if (int0len)
+
+      /* SAM is set for array members that might need special treatment.  */
+      special_array_member sam;
+      tree size = component_ref_size (ptr, &sam);
+      if (sam == special_array_member::int_0)
+	pref->sizrng[0] = pref->sizrng[1] = 0;
+      else if (!pref->trail1special && sam == special_array_member::trail_1)
+	pref->sizrng[0] = pref->sizrng[1] = 1;
+      else if (size && TREE_CODE (size) == INTEGER_CST)
+	pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
+      else
 	{
-	  pref->sizrng[0] = pref->sizrng[1] = 0;
-	  return true;
+	  /* When the size of the member is unknown it's either a flexible
+	     array member or a trailing special array member (either zero
+	     length or one-element).  Set the size to the maximum minus
+	     the constant size of the type.  */
+	  pref->sizrng[0] = 0;
+	  pref->sizrng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
+	  if (tree recsize = TYPE_SIZE_UNIT (TREE_TYPE (ref)))
+	    if (TREE_CODE (recsize) == INTEGER_CST)
+	      pref->sizrng[1] -= wi::to_offset (recsize);
 	}
-
-      if (!size || TREE_CODE (size) != INTEGER_CST)
-	return false;
-
-      pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
       return true;
     }
 
@@ -4424,7 +4461,7 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 	return false;
 
       offset_int orng[2];
-      tree off = TREE_OPERAND (ptr, 1);
+      tree off = pref->eval (TREE_OPERAND (ptr, 1));
       if (!get_range (off, SIGNED, orng, rvals))
 	/* Fail unless the size of the object is zero.  */
 	return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1];
@@ -4454,11 +4491,22 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
 	  if (ostype && TREE_CODE (eltype) == ARRAY_TYPE)
 	    {
-	      /* Execpt for the permissive raw memory functions which
-		 use the size of the whole object determined above,
-		 use the size of the referenced array.  */
-	      pref->sizrng[0] = pref->offrng[0] + orng[0] + sz;
-	      pref->sizrng[1] = pref->offrng[1] + orng[1] + sz;
+	      /* Except for the permissive raw memory functions which use
+		 the size of the whole object determined above, use the size
+		 of the referenced array.  Because the overall offset is from
+		 the beginning of the complete array object add this overall
+		 offset to the size of array.  */
+	      offset_int sizrng[2] =
+		{
+		 pref->offrng[0] + orng[0] + sz,
+		 pref->offrng[1] + orng[1] + sz
+		};
+	      if (sizrng[1] < sizrng[0])
+		std::swap (sizrng[0], sizrng[1]);
+	      if (sizrng[0] >= 0 && sizrng[0] <= pref->sizrng[0])
+		pref->sizrng[0] = sizrng[0];
+	      if (sizrng[1] >= 0 && sizrng[1] <= pref->sizrng[1])
+		pref->sizrng[1] = sizrng[1];
 	    }
 	}
 
@@ -4467,6 +4515,28 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
       return true;
     }
+  else if (code == POINTER_PLUS_EXPR)
+    {
+      tree ref = TREE_OPERAND (ptr, 0);
+      if (!compute_objsize (ref, ostype, pref, visited, rvals))
+	return false;
+
+      offset_int orng[2];
+      tree off = pref->eval (TREE_OPERAND (ptr, 1));
+      if (!get_range (off, SIGNED, orng, rvals))
+	/* Fail unless the size of the object is zero.  */
+	return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1];
+
+      pref->offrng[0] += orng[0];
+      pref->offrng[1] += orng[1];
+
+      return true;
+    }
+  else if (code == VIEW_CONVERT_EXPR)
+    {
+      ptr = TREE_OPERAND (ptr, 0);
+      return compute_objsize (ptr, ostype, pref, visited, rvals);
+    }
 
   if (TREE_CODE (ptr) == SSA_NAME)
     {
@@ -12436,3 +12506,14 @@ builtin_with_linkage_p (tree decl)
     }
   return false;
 }
+
+bool
+access_ref::offset_bounded () const
+{
+  if (offrng[0] == offrng[1])
+    return false;
+
+  tree min = TYPE_MIN_VALUE (ptrdiff_type_node);
+  tree max = TYPE_MAX_VALUE (ptrdiff_type_node);
+  return offrng[0] <= wi::to_offset (min) || offrng[1] >= wi::to_offset (max);
+}
diff --git a/gcc/builtins.h b/gcc/builtins.h
index 8136b768750..39415aade05 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -172,6 +172,22 @@ struct access_ref
      For string functions the size of the actual access is
      further constrained by the length of the string.  */
   offset_int bndrng[2];
+
+  /* Return true if OFFRNG is the constant zero.  */
+  bool offset_zero () const
+  {
+    return offrng[0] == 0 && offrng[1] == 0;
+  }
+
+  /* Return true if OFFRNG is bounded to a subrange of possible offset
+     values.  */
+  bool offset_bounded () const;
+
+  /* Used to fold integer expressions when called from front ends.  */
+  tree (*eval)(tree);
+  /* Set if trailing one-element arrays should be treated as flexible
+     array members.  */
+  bool trail1special;
 };
 
 /* Describes a pair of references used in an access by built-in
@@ -197,13 +213,13 @@ struct access_data
 
 class vr_values;
 extern tree gimple_call_alloc_size (gimple *, wide_int[2] = NULL,
+			     const vr_values * = NULL);
+extern tree gimple_parm_array_size (tree, wide_int[2],
 				    const vr_values * = NULL);
-extern tree gimple_parm_array_size (tree, wide_int[2], const vr_values * = NULL);
+extern tree compute_objsize (tree, int, access_ref *, const vr_values * = NULL);
 extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL,
 			     const vr_values * = NULL);
-extern tree compute_objsize (tree, int, access_ref *, const vr_values * = NULL);
-
-extern bool check_access (tree, tree, tree, tree, tree, access_mode,
-			  const access_data * = NULL);
+extern bool check_access (tree, tree, tree, tree, tree,
+			  access_mode, const access_data * = NULL);
 
 #endif /* GCC_BUILTINS_H */
diff --git a/gcc/cp/init.c b/gcc/cp/init.c
index e84e985492d..a05ad26ba41 100644
--- a/gcc/cp/init.c
+++ b/gcc/cp/init.c
@@ -34,6 +34,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "attribs.h"
 #include "asan.h"
 #include "stor-layout.h"
+#include "builtins.h"
 
 static bool begin_init_stmts (tree *, tree *);
 static tree finish_init_stmts (bool, tree, tree);
@@ -2564,27 +2565,6 @@ throw_bad_array_new_length (void)
   return build_cxx_call (fn, 0, NULL, tf_warning_or_error);
 }
 
-/* Attempt to find the initializer for flexible array field T in the
-   initializer INIT, when non-null.  Returns the initializer when
-   successful and NULL otherwise.  */
-static tree
-find_flexarray_init (tree t, tree init)
-{
-  if (!init || init == error_mark_node)
-    return NULL_TREE;
-
-  unsigned HOST_WIDE_INT idx;
-  tree field, elt;
-
-  /* Iterate over all top-level initializer elements.  */
-  FOR_EACH_CONSTRUCTOR_ELT (CONSTRUCTOR_ELTS (init), idx, field, elt)
-    /* If the member T is found, return it.  */
-    if (field == t)
-      return elt;
-
-  return NULL_TREE;
-}
-
 /* Attempt to verify that the argument, OPER, of a placement new expression
    refers to an object sufficiently large for an object of TYPE or an array
    of NELTS of such objects when NELTS is non-null, and issue a warning when
@@ -2601,17 +2581,6 @@ warn_placement_new_too_small (tree type, tree nelts, tree size, tree oper)
 {
   location_t loc = cp_expr_loc_or_input_loc (oper);
 
-  /* The number of bytes to add to or subtract from the size of the provided
-     buffer based on an offset into an array or an array element reference.
-     Although intermediate results may be negative (as in a[3] - 2) a valid
-     final result cannot be.  */
-  offset_int adjust = 0;
-  /* True when the size of the entire destination object should be used
-     to compute the possibly optimistic estimate of the available space.  */
-  bool use_obj_size = false;
-  /* True when the reference to the destination buffer is an ADDR_EXPR.  */
-  bool addr_expr = false;
-
   STRIP_NOPS (oper);
 
   /* Using a function argument or a (non-array) variable as an argument
@@ -2625,231 +2594,96 @@ warn_placement_new_too_small (tree type, tree nelts, tree size, tree oper)
   /* Evaluate any constant expressions.  */
   size = fold_non_dependent_expr (size);
 
-  /* Handle the common case of array + offset expression when the offset
-     is a constant.  */
-  if (TREE_CODE (oper) == POINTER_PLUS_EXPR)
-    {
-      /* If the offset is compile-time constant, use it to compute a more
-	 accurate estimate of the size of the buffer.  Since the operand
-	 of POINTER_PLUS_EXPR is represented as an unsigned type, convert
-	 it to signed first.
-	 Otherwise, use the size of the entire array as an optimistic
-	 estimate (this may lead to false negatives).  */
-      tree adj = TREE_OPERAND (oper, 1);
-      adj = fold_for_warn (adj);
-      if (CONSTANT_CLASS_P (adj))
-	adjust += wi::to_offset (convert (ssizetype, adj));
-      else
-	use_obj_size = true;
-
-      oper = TREE_OPERAND (oper, 0);
-
-      STRIP_NOPS (oper);
-    }
-
-  if (TREE_CODE (oper) == TARGET_EXPR)
-    oper = TREE_OPERAND (oper, 1);
-  else if (TREE_CODE (oper) == ADDR_EXPR)
-    {
-      addr_expr = true;
-      oper = TREE_OPERAND (oper, 0);
-    }
-
-  STRIP_NOPS (oper);
-
-  if (TREE_CODE (oper) == ARRAY_REF
-      && (addr_expr || TREE_CODE (TREE_TYPE (oper)) == ARRAY_TYPE))
-    {
-      /* Similar to the offset computed above, see if the array index
-	 is a compile-time constant.  If so, and unless the offset was
-	 not a compile-time constant, use the index to determine the
-	 size of the buffer.  Otherwise, use the entire array as
-	 an optimistic estimate of the size.  */
-      const_tree adj = fold_non_dependent_expr (TREE_OPERAND (oper, 1));
-      if (!use_obj_size && CONSTANT_CLASS_P (adj))
-	adjust += wi::to_offset (adj);
-      else
-	{
-	  use_obj_size = true;
-	  adjust = 0;
-	}
+  access_ref ref;
+  ref.eval = [](tree x){ return fold_non_dependent_expr (x); };
+  ref.trail1special = warn_placement_new < 2;
+  tree objsize =  compute_objsize (oper, 1, &ref);
+  if (!objsize)
+    return;
 
-      oper = TREE_OPERAND (oper, 0);
-    }
+  offset_int bytes_avail = wi::to_offset (objsize);
+  offset_int bytes_need;
 
-  /* Refers to the declared object that constains the subobject referenced
-     by OPER.  When the object is initialized, makes it possible to determine
-     the actual size of a flexible array member used as the buffer passed
-     as OPER to placement new.  */
-  tree var_decl = NULL_TREE;
-  /* True when operand is a COMPONENT_REF, to distinguish flexible array
-     members from arrays of unspecified size.  */
-  bool compref = TREE_CODE (oper) == COMPONENT_REF;
-
-  /* For COMPONENT_REF (i.e., a struct member) the size of the entire
-     enclosing struct.  Used to validate the adjustment (offset) into
-     an array at the end of a struct.  */
-  offset_int compsize = 0;
-
-  /* Descend into a struct or union to find the member whose address
-     is being used as the argument.  */
-  if (TREE_CODE (oper) == COMPONENT_REF)
+  if (CONSTANT_CLASS_P (size))
+    bytes_need = wi::to_offset (size);
+  else if (nelts && CONSTANT_CLASS_P (nelts))
+    bytes_need = (wi::to_offset (nelts)
+		  * wi::to_offset (TYPE_SIZE_UNIT (type)));
+  else if (tree_fits_uhwi_p (TYPE_SIZE_UNIT (type)))
+    bytes_need = wi::to_offset (TYPE_SIZE_UNIT (type));
+  else
     {
-      tree comptype = TREE_TYPE (TREE_OPERAND (oper, 0));
-      compsize = wi::to_offset (TYPE_SIZE_UNIT (comptype));
-
-      tree op0 = oper;
-      while (TREE_CODE (op0 = TREE_OPERAND (op0, 0)) == COMPONENT_REF);
-      STRIP_ANY_LOCATION_WRAPPER (op0);
-      if (VAR_P (op0))
-	var_decl = op0;
-      oper = TREE_OPERAND (oper, 1);
+      /* The type is a VLA.  */
+      return;
     }
 
-  STRIP_ANY_LOCATION_WRAPPER (oper);
-  tree opertype = TREE_TYPE (oper);
-  if ((addr_expr || !INDIRECT_TYPE_P (opertype))
-      && (VAR_P (oper)
-	  || TREE_CODE (oper) == FIELD_DECL
-	  || TREE_CODE (oper) == PARM_DECL))
-    {
-      /* A possibly optimistic estimate of the number of bytes available
-	 in the destination buffer.  */
-      offset_int bytes_avail = 0;
-      /* True when the estimate above is in fact the exact size
-	 of the destination buffer rather than an estimate.  */
-      bool exact_size = true;
-
-      /* Treat members of unions and members of structs uniformly, even
-	 though the size of a member of a union may be viewed as extending
-	 to the end of the union itself (it is by __builtin_object_size).  */
-      if ((VAR_P (oper) || use_obj_size)
-	  && DECL_SIZE_UNIT (oper)
-	  && tree_fits_uhwi_p (DECL_SIZE_UNIT (oper)))
-	{
-	  /* Use the size of the entire array object when the expression
-	     refers to a variable or its size depends on an expression
-	     that's not a compile-time constant.  */
-	  bytes_avail = wi::to_offset (DECL_SIZE_UNIT (oper));
-	  exact_size = !use_obj_size;
-	}
-      else if (tree opersize = TYPE_SIZE_UNIT (opertype))
-	{
-	  /* Use the size of the type of the destination buffer object
-	     as the optimistic estimate of the available space in it.
-	     Use the maximum possible size for zero-size arrays and
-	     flexible array members (except of initialized objects
-	     thereof).  */
-	  if (TREE_CODE (opersize) == INTEGER_CST)
-	    bytes_avail = wi::to_offset (opersize);
-	}
-
-      if (bytes_avail == 0)
-	{
-	  if (var_decl)
-	    {
-	      /* Constructing into a buffer provided by the flexible array
-		 member of a declared object (which is permitted as a G++
-		 extension).  If the array member has been initialized,
-		 determine its size from the initializer.  Otherwise,
-		 the array size is zero.  */
-	      if (tree init = find_flexarray_init (oper,
-						   DECL_INITIAL (var_decl)))
-		bytes_avail = wi::to_offset (TYPE_SIZE_UNIT (TREE_TYPE (init)));
-	    }
-	  else
-	    bytes_avail = (wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node))
-			   - compsize);
-	}
-
-      tree_code oper_code = TREE_CODE (opertype);
-
-      if (compref && oper_code == ARRAY_TYPE)
-	{
-	  tree nelts = array_type_nelts_top (opertype);
-	  tree nelts_cst = maybe_constant_value (nelts);
-	  if (TREE_CODE (nelts_cst) == INTEGER_CST
-	      && integer_onep (nelts_cst)
-	      && !var_decl
-	      && warn_placement_new < 2)
-	    return;
-	}
-
-      /* Reduce the size of the buffer by the adjustment computed above
-	 from the offset and/or the index into the array.  */
-      if (bytes_avail < adjust || adjust < 0)
-	bytes_avail = 0;
-      else
-	{
-	  tree elttype = (TREE_CODE (opertype) == ARRAY_TYPE
-			  ? TREE_TYPE (opertype) : opertype);
-	  if (tree eltsize = TYPE_SIZE_UNIT (elttype))
-	    {
-	      bytes_avail -= adjust * wi::to_offset (eltsize);
-	      if (bytes_avail < 0)
-		bytes_avail = 0;
-	    }
-	}
+  if (bytes_avail >= bytes_need)
+    return;
 
-      /* The minimum amount of space needed for the allocation.  This
-	 is an optimistic estimate that makes it possible to detect
-	 placement new invocation for some undersize buffers but not
-	 others.  */
-      offset_int bytes_need;
+  /* True when the size to mention in the warning is exact as opposed
+     to "at least N".  */
+  const bool exact_size = (ref.offrng[0] == ref.offrng[1]
+			   || ref.sizrng[1] - ref.offrng[0] == 0);
 
-      if (nelts)
-	nelts = fold_for_warn (nelts);
-
-      if (CONSTANT_CLASS_P (size))
-	bytes_need = wi::to_offset (size);
-      else if (nelts && CONSTANT_CLASS_P (nelts))
-	bytes_need = (wi::to_offset (nelts)
-		      * wi::to_offset (TYPE_SIZE_UNIT (type)));
-      else if (tree_fits_uhwi_p (TYPE_SIZE_UNIT (type)))
-	bytes_need = wi::to_offset (TYPE_SIZE_UNIT (type));
-      else
-	{
-	  /* The type is a VLA.  */
-	  return;
-	}
+  tree opertype = ref.ref ? TREE_TYPE (ref.ref) : TREE_TYPE (oper);
+  bool warned = false;
+  if (nelts)
+    nelts = fold_for_warn (nelts);
+  if (nelts)
+    if (CONSTANT_CLASS_P (nelts))
+      warned = warning_at (loc, OPT_Wplacement_new_,
+			   (exact_size
+			    ? G_("placement new constructing an object "
+				 "of type %<%T [%wu]%> and size %qwu "
+				 "in a region of type %qT and size %qwi")
+			    : G_("placement new constructing an object "
+				 "of type %<%T [%wu]%> and size %qwu "
+				 "in a region of type %qT and size "
+				 "at most %qwu")),
+			   type, tree_to_uhwi (nelts),
+			   bytes_need.to_uhwi (),
+			   opertype, bytes_avail.to_uhwi ());
+    else
+      warned = warning_at (loc, OPT_Wplacement_new_,
+			   (exact_size
+			    ? G_("placement new constructing an array "
+				 "of objects of type %qT and size %qwu "
+				 "in a region of type %qT and size %qwi")
+			    : G_("placement new constructing an array "
+				 "of objects of type %qT and size %qwu "
+				 "in a region of type %qT and size "
+				 "at most %qwu")),
+			   type, bytes_need.to_uhwi (), opertype,
+			   bytes_avail.to_uhwi ());
+  else
+    warned = warning_at (loc, OPT_Wplacement_new_,
+			 (exact_size
+			  ? G_("placement new constructing an object "
+			       "of type %qT and size %qwu in a region "
+			       "of type %qT and size %qwi")
+			  : G_("placement new constructing an object "
+			       "of type %qT "
+			       "and size %qwu in a region of type %qT "
+			       "and size at most %qwu")),
+			       type, bytes_need.to_uhwi (), opertype,
+			 bytes_avail.to_uhwi ());
+
+  if (!warned || !ref.ref)
+    return;
 
-      if (bytes_avail < bytes_need)
-	{
-	  if (nelts)
-	    if (CONSTANT_CLASS_P (nelts))
-	      warning_at (loc, OPT_Wplacement_new_,
-			  exact_size ?
-			  "placement new constructing an object of type "
-			  "%<%T [%wu]%> and size %qwu in a region of type %qT "
-			  "and size %qwi"
-			  : "placement new constructing an object of type "
-			  "%<%T [%wu]%> and size %qwu in a region of type %qT "
-			  "and size at most %qwu",
-			  type, tree_to_uhwi (nelts), bytes_need.to_uhwi (),
-			  opertype, bytes_avail.to_uhwi ());
-	    else
-	      warning_at (loc, OPT_Wplacement_new_,
-			  exact_size ?
-			  "placement new constructing an array of objects "
-			  "of type %qT and size %qwu in a region of type %qT "
-			  "and size %qwi"
-			  : "placement new constructing an array of objects "
-			  "of type %qT and size %qwu in a region of type %qT "
-			  "and size at most %qwu",
-			  type, bytes_need.to_uhwi (), opertype,
-			  bytes_avail.to_uhwi ());
-	  else
-	    warning_at (loc, OPT_Wplacement_new_,
-			exact_size ?
-			"placement new constructing an object of type %qT "
-			"and size %qwu in a region of type %qT and size %qwi"
-			: "placement new constructing an object of type %qT "
-			"and size %qwu in a region of type %qT and size "
-			"at most %qwu",
-			type, bytes_need.to_uhwi (), opertype,
-			bytes_avail.to_uhwi ());
-	}
-    }
+  if (ref.offrng[0] == 0 || !ref.offset_bounded ())
+    /* Avoid mentioning the offset when its lower bound is zero
+       or when it's impossibly large.  */
+    inform (DECL_SOURCE_LOCATION (ref.ref),
+	    "%qD declared here", ref.ref);
+  else if (ref.offrng[0] == ref.offrng[1])
+    inform (DECL_SOURCE_LOCATION (ref.ref),
+	    "at offset %wi from %qD declared here",
+	    ref.offrng[0].to_shwi (), ref.ref);
+  else
+    inform (DECL_SOURCE_LOCATION (ref.ref),
+	    "at offset [%wi, %wi] from %qD declared here",
+	    ref.offrng[0].to_shwi (), ref.offrng[1].to_shwi (), ref.ref);
 }
 
 /* True if alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__.  */
diff --git a/gcc/gimple-array-bounds.cc b/gcc/gimple-array-bounds.cc
index b93ef7a7b74..ddeca5fb2a3 100644
--- a/gcc/gimple-array-bounds.cc
+++ b/gcc/gimple-array-bounds.cc
@@ -188,7 +188,7 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
   tree decl = NULL_TREE;
 
   /* Set for accesses to interior zero-length arrays.  */
-  bool interior_zero_len = false;
+  special_array_member sam{ };
 
   tree up_bound_p1;
 
@@ -220,7 +220,7 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
 	    {
 	      /* Try to determine the size of the trailing array from
 		 its initializer (if it has one).  */
-	      if (tree refsize = component_ref_size (arg, &interior_zero_len))
+	      if (tree refsize = component_ref_size (arg, &sam))
 		if (TREE_CODE (refsize) == INTEGER_CST)
 		  maxbound = refsize;
 	    }
@@ -325,7 +325,7 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
 			 "array subscript %E is below array bounds of %qT",
 			 low_sub, artype);
 
-  if (!warned && interior_zero_len)
+  if (!warned && sam == special_array_member::int_0)
     warned = warning_at (location, OPT_Wzero_length_bounds,
 			 (TREE_CODE (low_sub) == INTEGER_CST
 			  ? G_("array subscript %E is outside the bounds "
diff --git a/gcc/testsuite/g++.dg/init/strlen.C b/gcc/testsuite/g++.dg/init/strlen.C
index aa8950e2dc0..cc650d65dbe 100644
--- a/gcc/testsuite/g++.dg/init/strlen.C
+++ b/gcc/testsuite/g++.dg/init/strlen.C
@@ -29,7 +29,7 @@ test_dynamic_type (S *p)
   // distinguish invalid cases from ones like it that might be valid.
   // If/when GIMPLE changes to make this possible this test can be
   // removed.
-  char *q = new (p->a) char [16];
+  char *q = new (p->a) char [16];   // { dg-warning "\\\[-Wplacement-new" }
 
   init (q);
 
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C
index d2ec608afd4..cec83163dbe 100644
--- a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-1.C
@@ -66,8 +66,9 @@ struct BA2 { int i; A2 a2; };
 void fBx (BAx *pbx, BAx &rbx)
 {
   BAx bax;
-  new (bax.ax.a) char;     // { dg-warning "placement" }
-  new (bax.ax.a) Int16;    // { dg-warning "placement" }
+  // The uninitialized flexible array takes up the bytes of padding.
+  new (bax.ax.a) char;
+  new (bax.ax.a) Int16;
   new (bax.ax.a) Int32;    // { dg-warning "placement" }
 
   new (pbx->ax.a) char;
@@ -84,9 +85,12 @@ void fBx1 ()
 {
   static BAx bax1 = { 1, /* Ax = */ { 2, /* a[] = */ {} } };
 
-  new (bax1.ax.a) char;	    // { dg-warning "placement" }
-  new (bax1.ax.a) char[2];  // { dg-warning "placement" }
-  new (bax1.ax.a) Int16;    // { dg-warning "placement" }
+  // The empty flexible array takes up the bytes of padding.
+  new (bax1.ax.a) char;
+  new (bax1.ax.a) char[2];
+  new (bax1.ax.a) Int16;
+  new (bax1.ax.a) char[3];
+  new (bax1.ax.a) char[4];  // { dg-warning "placement" }
   new (bax1.ax.a) Int32;    // { dg-warning "placement" }
 }
 
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C
index e00515eeaa9..e5fdfe1f603 100644
--- a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-2.C
@@ -124,9 +124,13 @@ struct BA2 { int i; A2 a2; };
 void fBx (BAx *pbx, BAx &rbx)
 {
   BAx bax;
-  new (bax.ax.a) char;        // { dg-warning "placement" }
-  new (bax.ax.a) Int16;       // { dg-warning "placement" }
+  // The uninitialized flexible array takes up the bytes of padding.
+  new (bax.ax.a) char;
+  new (bax.ax.a) Int16;
+  new (bax.ax.a) char[3];
   new (bax.ax.a) Int32;       // { dg-warning "placement" }
+  new (bax.ax.a) char[4];     // { dg-warning "placement" }
+  new (bax.ax.a) char[5];     // { dg-warning "placement" }
 
   new (pbx->ax.a) char;
   new (rbx.ax.a) char;
@@ -142,10 +146,14 @@ void fBx1 ()
 {
   static BAx bax1 = { 1, /* Ax = */ { 2, /* a[] = */ {} } };
 
-  new (bax1.ax.a) char;	      // { dg-warning "placement" }
-  new (bax1.ax.a) char[2];    // { dg-warning "placement" }
-  new (bax1.ax.a) Int16;      // { dg-warning "placement" }
+  // The empty flexible array takes up the bytes of padding.
+  new (bax1.ax.a) char;
+  new (bax1.ax.a) char[2];
+  new (bax1.ax.a) Int16;
+  new (bax1.ax.a) char[3];
   new (bax1.ax.a) Int32;      // { dg-warning "placement" }
+  new (bax1.ax.a) char[4];    // { dg-warning "placement" }
+  new (bax1.ax.a) char[5];    // { dg-warning "placement" }
 }
 
 void fB0 (BA0 *pb0, BA0 &rb0)
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C
index b6a72b18f6a..5eb63d23b47 100644
--- a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-6.C
@@ -17,9 +17,10 @@ void fBx1 ()
 {
   static BAx bax1 = { 1, /* Ax = */ { 2, /* a[] = */ { 3 } } }; // { dg-error "initialization of flexible array member in a nested context" }
 
-  new (bax1.ax.a) char;     // { dg-warning "placement" }
-  new (bax1.ax.a) char[2];  // { dg-warning "placement" }
-  new (bax1.ax.a) Int16;    // { dg-warning "placement" }
+  // The first three bytes of the flexible array member live in the padding.
+  new (bax1.ax.a) char;
+  new (bax1.ax.a) char[2];
+  new (bax1.ax.a) Int16;
   new (bax1.ax.a) Int32;    // { dg-warning "placement" }
 }
 
@@ -27,10 +28,11 @@ void fBx2 ()
 {
   static BAx bax2 = { 1, /* Ax = */ { 2, /* a[] = */ { 3, 4 } } }; // { dg-error "initialization of flexible array member in a nested context" }
 
-  new (bax2.ax.a) char;       // { dg-warning "placement" }
-  new (bax2.ax.a) char[2];    // { dg-warning "placement" }
-  new (bax2.ax.a) char[3];    // { dg-warning "placement" }
-  new (bax2.ax.a) Int16;      // { dg-warning "placement" }
+  // The first three bytes of the flexible array member live in the padding.
+  new (bax2.ax.a) char;
+  new (bax2.ax.a) char[2];
+  new (bax2.ax.a) char[3];
+  new (bax2.ax.a) Int16;
   new (bax2.ax.a) char[4];    // { dg-warning "placement" }
   new (bax2.ax.a) Int32;      // { dg-warning "placement" }
 }
@@ -39,10 +41,11 @@ void fBx3 ()
 {
   static BAx bax2 = { 1, /* Ax = */ { 3, /* a[] = */ { 4, 5, 6 } } }; // { dg-error "initialization of flexible array member in a nested context" }
 
-  new (bax2.ax.a) char;       // { dg-warning "placement" }
-  new (bax2.ax.a) char[2];    // { dg-warning "placement" }
-  new (bax2.ax.a) Int16;      // { dg-warning "placement" }
-  new (bax2.ax.a) char[3];    // { dg-warning "placement" }
+  // The first three bytes of the flexible array member live in the padding.
+  new (bax2.ax.a) char;
+  new (bax2.ax.a) char[2];
+  new (bax2.ax.a) Int16;
+  new (bax2.ax.a) char[3];
   new (bax2.ax.a) char[4];    // { dg-warning "placement" }
   new (bax2.ax.a) Int32;      // { dg-warning "placement" }
 }
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-7.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-7.C
new file mode 100644
index 00000000000..82f298d8008
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-7.C
@@ -0,0 +1,82 @@
+/* PR c++/96511 - Incorrect -Wplacement-new on POINTER_PLUS into an array
+   with 4-byte elements
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+typedef __INT16_TYPE__ int16_t;
+typedef __INT32_TYPE__ int32_t;
+typedef __SIZE_TYPE__  size_t;
+
+void* operator new (size_t, void *p) { return p; }
+
+void test_a1_int16 ()
+{
+  int16_t a3[3];                    // { dg-message "declared here" }
+
+  new (a3) int16_t;
+  new (a3 + 1) int16_t;
+  new (a3 + 2) int16_t;             // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[1]) int16_t;
+  new (&a3[0] + 1) int16_t;
+  new (&a3[0] + 2) int16_t;         // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[0] + 3) int16_t;         // { dg-warning "\\\[-Wplacement-new" }
+}
+
+void test_a1_int32 ()
+{
+  int16_t a3[3];
+
+  new (a3 + 1) int32_t;             // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[1]) int32_t;
+  new (&a3[0] + 1) int32_t;         // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a3[0] + 2) int32_t;         // { dg-warning "\\\[-Wplacement-new" }
+}
+
+
+void test_a2 ()
+{
+  int16_t a23[2][3];
+
+  new (a23 + 1) int16_t;            // { dg-bogus "\\\[-Wplacement-new" }
+  new (&a23[1]) int16_t;
+  new (&a23[2]) int16_t;            // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[0][0] + 1) int16_t;
+  new (&a23[0][0] + 2) int16_t;
+  // Deriving a pointer to the next array from one to an element of
+  // the prior array isn't valid even if the resulting pointer points
+  // to an element of the larger array.  Verify it's diagnosed.
+  new (&a23[0][0] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][0] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][0] + 5) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][0] + 6) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[0][1] + 1) int16_t;
+  new (&a23[0][1] + 2) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 5) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][1] + 6) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[0][2] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 2) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 5) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[0][2] + 6) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[1][0]) int16_t;
+  new (&a23[1][0] + 1) int16_t;
+  new (&a23[1][0] + 2) int16_t;
+  new (&a23[1][0] + 3) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[1][0] + 4) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[1][1]) int16_t;
+  new (&a23[1][2]) int16_t;
+  new (&a23[1][2] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[1][3]) int16_t;         // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[1][3] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+
+  new (&a23[2][0]) int16_t;         // { dg-warning "\\\[-Wplacement-new" }
+  new (&a23[2][0] + 1) int16_t;     // { dg-warning "\\\[-Wplacement-new" }
+}
diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-58.c b/gcc/testsuite/gcc.dg/Warray-bounds-58.c
index 7c469e2aefc..849457e559f 100644
--- a/gcc/testsuite/gcc.dg/Warray-bounds-58.c
+++ b/gcc/testsuite/gcc.dg/Warray-bounds-58.c
@@ -1,5 +1,5 @@
 /* { dg-do compile }
-   { dg-options "-O2 -Wall" } */
+   { dg-options "-O2 -Wall -Wno-stringop-overread" } */
 
 typedef __SIZE_TYPE__ size_t;
 
@@ -15,7 +15,7 @@ void fa0_extern (void)
 {
   sink (strlen (ea0.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
   sink (strlen (ea0.a - 1));    // { dg-warning "\\\[-Warray-bounds" "pr93514" { xfail *-*-* } }
-  sink (strlen (ea0.a));        // { dg-warning "\\\[-Wstringop-overread" "pr93514" }
+  sink (strlen (ea0.a));        // valid just-past-the-end offset
   sink (strlen (ea0.a + 1));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 }
 
@@ -25,7 +25,7 @@ void fa0_static (void)
 {
   sink (strlen (sa0.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
   sink (strlen (sa0.a - 1));    // { dg-warning "\\\[-Warray-bounds" "pr93514" { xfail *-*-* } }
-  sink (strlen (sa0.a));        // { dg-warning "\\\[-Wstringop-overread" "pr93514" }
+  sink (strlen (sa0.a));        // valid just-past-the-end offset
   sink (strlen (sa0.a + 1));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 }
 
@@ -52,14 +52,14 @@ void fax_static (void)
   sink (strlen (ax0.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
   sink (strlen (ax0.a - 1));    // { dg-warning "\\\[-Warray-bounds" "pr93514" { xfail *-*-* } }
   sink (strlen (ax0.a));
-  sink (strlen (ax0.a + 1));    // { dg-warning "\\\[-Wstringop-overread" "pr93514" }
+  sink (strlen (ax0.a + 1));    // valid just-past-the-end offset
   sink (strlen (ax0.a + 2));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 
   sink (strlen (ax1.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
   sink (strlen (ax1.a - 1));    // { dg-warning "\\\[-Warray-bounds" "pr93514" { xfail *-*-* } }
   sink (strlen (ax1.a));
   sink (strlen (ax1.a + 1));
-  sink (strlen (ax1.a + 2));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" "pr93514" }
+  sink (strlen (ax1.a + 2));    // valid just-past-the-end offset
   sink (strlen (ax1.a + 3));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 
   sink (strlen (ax2.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
@@ -67,7 +67,7 @@ void fax_static (void)
   sink (strlen (ax2.a));
   sink (strlen (ax2.a + 1));
   sink (strlen (ax2.a + 2));
-  sink (strlen (ax2.a + 3));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" "pr93514" }
+  sink (strlen (ax2.a + 3));    // valid just-past-the-end offset
   sink (strlen (ax2.a + 4));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 
   sink (strlen (ax3.a - 2));    // { dg-warning "\\\[-Warray-bounds" }
@@ -76,6 +76,6 @@ void fax_static (void)
   sink (strlen (ax3.a + 1));
   sink (strlen (ax3.a + 2));
   sink (strlen (ax3.a + 3));
-  sink (strlen (ax3.a + 4));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" "pr93514" }
+  sink (strlen (ax3.a + 4));    // valid just-past-the-end offset
   sink (strlen (ax3.a + 5));    // { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" }
 }
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c
index 339f904d7a6..46f8fed79f3 100644
--- a/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-37.c
@@ -184,6 +184,18 @@ void test_note (const char *s)
     sink (a);
   }
 
+  {
+    char a[1][1][2];                    // { dg-message "at offset 2 into " }
+    strncpy (a[0][1], s, 3);            // { dg-warning "writing 3 bytes into a region of size 0 " }
+    sink (a);
+  }
+
+  {
+    char a[1][2][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 " }
@@ -192,7 +204,13 @@ void test_note (const char *s)
 
   {
     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 " }
+    strncpy (a[1][0], s, 3);            // { dg-warning "writing 3 bytes into a region of size 0 " }
+    sink (a);
+  }
+
+  {
+    char a[2][1][2];                    // { dg-message "at offset 2 into " }
+    strncpy (a[0][1], s, 3);            // { dg-warning "writing 3 bytes into a region of size 0 " }
     sink (a);
   }
 
diff --git a/gcc/tree.c b/gcc/tree.c
index 4046debb72f..2ce46000a05 100644
--- a/gcc/tree.c
+++ b/gcc/tree.c
@@ -13640,20 +13640,21 @@ get_initializer_for (tree init, tree decl)
 /* Determines the size of the member referenced by the COMPONENT_REF
    REF, using its initializer expression if necessary in order to
    determine the size of an initialized flexible array member.
-   If non-null, *INTERIOR_ZERO_LENGTH is set when REF refers to
-   an interior zero-length array.
+   If non-null, set *ARK when REF refers to an interior zero-length
+   array or a trailing one-element array.
    Returns the size as sizetype (which might be zero for an object
    with an uninitialized flexible array member) or null if the size
    cannot be determined.  */
 
 tree
-component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
+component_ref_size (tree ref, special_array_member *sam /* = NULL */)
 {
   gcc_assert (TREE_CODE (ref) == COMPONENT_REF);
 
-  bool int_0_len = false;
-  if (!interior_zero_length)
-    interior_zero_length = &int_0_len;
+  special_array_member arkbuf;
+  if (!sam)
+    sam = &arkbuf;
+  *sam = special_array_member::none;
 
   /* The object/argument referenced by the COMPONENT_REF and its type.  */
   tree arg = TREE_OPERAND (ref, 0);
@@ -13675,9 +13676,16 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
 	   more than one element.  */
 	return memsize;
 
-      *interior_zero_length = zero_length && !trailing;
-      if (*interior_zero_length)
-	memsize = NULL_TREE;
+      if (zero_length)
+	{
+	  if (trailing)
+	    *sam = special_array_member::trail_0;
+	  else
+	    {
+	      *sam = special_array_member::int_0;
+	      memsize = NULL_TREE;
+	    }
+	}
 
       if (!zero_length)
 	if (tree dom = TYPE_DOMAIN (memtype))
@@ -13688,9 +13696,13 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
 		{
 		  offset_int minidx = wi::to_offset (min);
 		  offset_int maxidx = wi::to_offset (max);
-		  if (maxidx - minidx > 0)
+		  offset_int neltsm1 = maxidx - minidx;
+		  if (neltsm1 > 0)
 		    /* MEMBER is an array with more than one element.  */
 		    return memsize;
+
+		  if (neltsm1 == 0)
+		    *sam = special_array_member::trail_1;
 		}
 
       /* For a refernce to a zero- or one-element array member of a union
@@ -13708,7 +13720,7 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
   tree base = get_addr_base_and_unit_offset (ref, &baseoff);
   if (!base || !VAR_P (base))
     {
-      if (!*interior_zero_length)
+      if (*sam != special_array_member::int_0)
 	return NULL_TREE;
 
       if (TREE_CODE (arg) != COMPONENT_REF)
@@ -13728,7 +13740,7 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
   /* Determine the base type of the referenced object.  If it's
      the same as ARGTYPE and MEMBER has a known size, return it.  */
   tree bt = basetype;
-  if (!*interior_zero_length)
+  if (*sam != special_array_member::int_0)
     while (TREE_CODE (bt) == ARRAY_TYPE)
       bt = TREE_TYPE (bt);
   bool typematch = useless_type_conversion_p (argtype, bt);
@@ -13768,7 +13780,7 @@ component_ref_size (tree ref, bool *interior_zero_length /* = NULL */)
 	  if (DECL_P (base)
 	      && DECL_EXTERNAL (base)
 	      && bt == basetype
-	      && !*interior_zero_length)
+	      && *sam != special_array_member::int_0)
 	    /* The size of a flexible array member of an extern struct
 	       with no initializer cannot be determined (it's defined
 	       in another translation unit and can have an initializer
diff --git a/gcc/tree.h b/gcc/tree.h
index 5bb6e7bc000..8addb024628 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -5288,12 +5288,22 @@ extern bool array_at_struct_end_p (tree);
    by EXP.  This does not include any offset in DECL_FIELD_BIT_OFFSET.  */
 extern tree component_ref_field_offset (tree);
 
+/* Describes a "special" array member due to which component_ref_size
+   returns null.  */
+enum struct special_array_member
+  {
+   none,      /* Not a special array member.  */
+   int_0,     /* Interior array member with size zero.  */
+   trail_0,   /* Trailing array member with size zero.  */
+   trail_1    /* Trailing array member with one element.  */
+  };
+
 /* Return the size of the member referenced by the COMPONENT_REF, using
    its initializer expression if necessary in order to determine the size
    of an initialized flexible array member.  The size might be zero for
    an object with an uninitialized flexible array member or null if it
    cannot be determined.  */
-extern tree component_ref_size (tree, bool * = NULL);
+extern tree component_ref_size (tree, special_array_member * = NULL);
 
 extern int tree_map_base_eq (const void *, const void *);
 extern unsigned int tree_map_base_hash (const void *);

[-- Attachment #3: gcc-compute_objsize.diff --]
[-- Type: text/x-patch, Size: 74534 bytes --]

Generalize compute_objsize to return maximum size/offset instead of failing.

Also resolves:
PR middle-end/97023 - missing warning on buffer overflow in chained mempcpy

gcc/ChangeLog:

	PR middle-end/97023
	* builtins.c (access_ref::access_ref): Initialize new member.  Use
	new enum.
	(access_ref::size_remaining): Define new member function.
	(inform_access): Handle expressions referencing objects.
	(gimple_call_alloc_size): Call get_size_range instead of get_range.
	(gimple_call_return_array): New function.
	(get_range): Rename...
	(get_offset_range): ...to this.  Improve detection of ranges from
	types of expressions.
	(gimple_call_return_array): Adjust calls to get_range per above.
	(compute_objsize): Same.  Set maximum size or offset instead of
	failing for unknown objects and handle more kinds of expressions.
	(compute_objsize): Call access_ref::size_remaining.
	(compute_objsize): Have transitional wrapper fail for pointers
	into unknown objects.
	(expand_builtin_strncmp): Call access_ref::size_remaining and
	handle new cases.
	* builtins.h (access_ref::size_remaining): Declare new member function.
	(access_ref::set_max_size_range): Define new member function.
	(access_ref::add_ofset, access_ref::add_max_ofset): Same.
	(access_ref::add_base0): New data member.
	* calls.c (get_size_range): Change argument type.  Handle new
	condition.
	* calls.h (get_size_range): Adjust signature.
	(enum size_range_flags): Define new type.

gcc/testsuite/ChangeLog:

	PR middle-end/97023
	* c-c++-common/Wrestrict.c: Add new case, expect warning for existing.
	* g++.dg/warn/Wplacement-new-size-8.C: New test.
	* gcc.dg/pr51683.c: Prune out expected warning.
	* gcc.dg/Wstringop-overflow-41.c: New test.
	* gcc.dg/Wstringop-overflow-44.c: New test.
	* gcc.dg/Wstringop-overflow-45.c: New test.
	* gcc.dg/Wstringop-overflow-46.c: New test.
	* gcc.dg/Wstringop-overflow-47.c: New test.
	* gcc.dg/Wstringop-overflow-49.c: New test.
	* gcc.dg/Wstringop-overflow-50.c: New test.
	* gcc.dg/Wstringop-overflow-51.c: New test.
	* gcc.dg/Wstringop-overflow-52.c: New test.
	* gcc.dg/Wstringop-overflow-53.c: New test.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index b5680bf93dd..0462eaaac06 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -183,7 +183,7 @@ static void maybe_emit_chk_warning (tree, enum built_in_function);
 static void maybe_emit_sprintf_chk_warning (tree, enum built_in_function);
 static void maybe_emit_free_warning (tree);
 static tree fold_builtin_object_size (tree, tree);
-static bool get_range (tree, signop, offset_int[2], const vr_values * = NULL);
+static bool get_offset_range (tree, offset_int[2], const vr_values *);
 static bool check_read_access (tree, tree, tree = NULL_TREE, int = 1);
 
 unsigned HOST_WIDE_INT target_newline;
@@ -199,7 +199,7 @@ static void expand_builtin_sync_synchronize (void);
 
 access_ref::access_ref (tree bound /* = NULL_TREE */,
 			bool minaccess /* = false */)
-: ref (), eval ([](tree x){ return x; }), trail1special (true)
+: ref (), eval ([](tree x){ return x; }), trail1special (true), base0 (true)
 {
   /* Set to valid.  */
   offrng[0] = offrng[1] = 0;
@@ -214,7 +214,7 @@ access_ref::access_ref (tree bound /* = NULL_TREE */,
      set the bounds of the access to reflect both it and MINACCESS.
      BNDRNG[0] is the size of the minimum access.  */
   tree rng[2];
-  if (bound && get_size_range (bound, rng, true))
+  if (bound && get_size_range (bound, rng, SR_ALLOW_ZERO))
     {
       bndrng[0] = wi::to_offset (rng[0]);
       bndrng[1] = wi::to_offset (rng[1]);
@@ -222,6 +222,83 @@ access_ref::access_ref (tree bound /* = NULL_TREE */,
     }
 }
 
+offset_int
+access_ref::size_remaining (offset_int *pmin /* = NULL */) const
+{
+  offset_int minbuf;
+  if (!pmin)
+    pmin = &minbuf;
+
+  if (base0)
+    {
+      /* The offset into referenced object is zero-based (i.e., it's
+	 not referenced by a pointer into middle of some unknown object).  */
+      if (offrng[0] < 0 && offrng[1] < 0)
+	{
+	  /* If the offset is negative the remaining size is zero.  */
+	  *pmin = 0;
+	  return 0;
+	}
+
+      if (offrng[1] < offrng[0])
+	{
+	  if (offrng[1] < 0)
+	    {
+	      *pmin = 0;
+	      return offrng[0] < sizrng[1] ? sizrng[1] - offrng[0] : 0;
+	    }
+
+	  *pmin = offrng[0] < sizrng[1] ? sizrng[1] - offrng[0] : 0;
+	  return sizrng[1];
+	}
+
+      if (sizrng[1] <= offrng[0])
+	{
+	  /* If the starting offset is greater than the upper bound on
+	     the size of the object the space remaining is zero.  */
+	  *pmin = 0;
+	  return 0;
+	}
+
+      /* Otherwise return the size minus the lower bound of the offset.  */
+      offset_int or0 = offrng[0] < 0 ? 0 : offrng[0];
+
+      *pmin = sizrng[0] - or0;
+      return sizrng[1] - or0;
+    }
+
+  /* The offset to the referenced object isn't zero-based (i.e., it may
+     refer to a byte other than the first.  The size of such an object
+     is constrained only by the size of the address space (the result
+     of max_object_size()).  */
+  if (offrng[1] < offrng[0])
+    {
+      if (offrng[1] < 0)
+	{
+	  *pmin = sizrng[0];
+	  return sizrng[1];
+	}
+
+      if (offrng[0] < sizrng[1])
+	*pmin = offrng[0] < 0 ? sizrng[1] : sizrng[1] - offrng[0];
+      else
+	*pmin = 0;
+
+      return sizrng[1];
+    }
+
+  if (sizrng[1] <= offrng[0])
+    {
+      *pmin = 0;
+      return 0;
+    }
+
+  offset_int or0 = offrng[0] < 0 ? 0 : offrng[0];
+
+  *pmin = sizrng[0] - or0;
+  return sizrng[1] - or0;
+}
+
 /* Return true if NAME starts with __builtin_ or __sync_.  */
 
 static bool
@@ -3730,23 +3807,27 @@ inform_access (const access_ref &ref, access_mode mode)
 	sprintf (sizestr, "[%llu, %llu]", minsize, maxsize);
 
     }
-  else
+  else if (DECL_P (ref.ref))
     loc = DECL_SOURCE_LOCATION (ref.ref);
+  else if (EXPR_P (ref.ref) && EXPR_HAS_LOCATION (ref.ref))
+    loc = EXPR_LOCATION (ref.ref);
+  else
+    return;
 
   if (mode == access_read_write || mode == access_write_only)
     {
-      if (DECL_P (ref.ref))
+      if (allocfn == NULL_TREE)
 	{
 	  if (minoff == maxoff)
 	    {
 	      if (minoff == 0)
-		inform (loc, "destination object %qD", ref.ref);
+		inform (loc, "destination object %qE", ref.ref);
 	      else
-		inform (loc, "at offset %lli into destination object %qD",
+		inform (loc, "at offset %lli into destination object %qE",
 			minoff, ref.ref);
 	    }
 	  else
-	    inform (loc, "at offset [%lli, %lli] into destination object %qD",
+	    inform (loc, "at offset [%lli, %lli] into destination object %qE",
 		    minoff, maxoff, ref.ref);
 	  return;
 	}
@@ -4146,7 +4227,7 @@ check_read_access (tree exp, tree src, tree bound /* = NULL_TREE */,
 
 tree
 gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
-			const vr_values *rvals /* = NULL */)
+			const vr_values * /* = NULL */)
 {
   if (!stmt)
     return NULL_TREE;
@@ -4198,14 +4279,17 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
   if (!rng1)
     rng1 = rng1_buf;
 
+  /* Use maximum precision to avoid overflow below.  */
   const int prec = ADDR_MAX_PRECISION;
-  const tree size_max = TYPE_MAX_VALUE (sizetype);
-  if (!get_range (size, rng1, rvals))
-    {
-      /* Use the full non-negative range on failure.  */
-      rng1[0] = wi::zero (prec);
-      rng1[1] = wi::to_wide (size_max, prec);
-    }
+
+  {
+    tree r[2];
+    /* Determine the largest valid range size, including zero.  */
+    if (!get_size_range (size, r, SR_ALLOW_ZERO | SR_USE_LARGEST))
+      return NULL_TREE;
+    rng1[0] = wi::to_wide (r[0], prec);
+    rng1[1] = wi::to_wide (r[1], prec);
+  }
 
   if (argidx2 > nargs && TREE_CODE (size) == INTEGER_CST)
     return fold_convert (sizetype, size);
@@ -4214,26 +4298,24 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
      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 (!get_range (n, rng2, rvals))
-    {
+  {
+    tree r[2];
       /* As above, use the full non-negative range on failure.  */
-      rng2[0] = wi::zero (prec);
-      rng2[1] = wi::to_wide (size_max, prec);
-    }
-
-  /* Extend to the maximum precision to avoid overflow.  */
-  rng1[0] = wide_int::from (rng1[0], prec, UNSIGNED);
-  rng1[1] = wide_int::from (rng1[1], prec, UNSIGNED);
-  rng2[0] = wide_int::from (rng2[0], prec, UNSIGNED);
-  rng2[1] = wide_int::from (rng2[1], prec, UNSIGNED);
+    if (!get_size_range (n, r, SR_ALLOW_ZERO | SR_USE_LARGEST))
+      return NULL_TREE;
+    rng2[0] = wi::to_wide (r[0], prec);
+    rng2[1] = wi::to_wide (r[1], prec);
+  }
 
   /* Compute products of both bounds for the caller but return the lesser
      of SIZE_MAX and the product of the upper bounds as a constant.  */
   rng1[0] = rng1[0] * rng2[0];
   rng1[1] = rng1[1] * rng2[1];
+
+  const tree size_max = TYPE_MAX_VALUE (sizetype);
   if (wi::gtu_p (rng1[1], wi::to_wide (size_max, prec)))
     {
-      rng1[1] = wi::to_wide (size_max);
+      rng1[1] = wi::to_wide (size_max, prec);
       return size_max;
     }
 
@@ -4295,27 +4377,43 @@ gimple_parm_array_size (tree ptr, wide_int rng[2],
   return var;
 }
 
-/* Wrapper around the wide_int overload of get_range.  Returns the same
-   result but accepts offset_int instead.  */
+/* Wrapper around the wide_int overload of get_range that accepts
+   offset_int instead.  For middle end expressions returns the same
+   result.  For a subset of nonconstamt expressions emitted by the front
+   end determines a more precise range than would be possible otherwise.  */
 
 static bool
-get_range (tree x, signop sgn, offset_int r[2],
-	   const vr_values *rvals /* = NULL */)
+get_offset_range (tree x, offset_int r[2], const vr_values *rvals)
 {
-  tree type = TREE_TYPE (x);
-  if (TREE_CODE (x) != INTEGER_CST
-      && TREE_CODE (x) != SSA_NAME)
+  offset_int add = 0;
+  if (TREE_CODE (x) == PLUS_EXPR)
     {
-      if (TYPE_UNSIGNED (type))
+      /* Handle constant offsets in pointer addition expressions seen
+	 n the front end IL.  */
+      tree op = TREE_OPERAND (x, 1);
+      if (TREE_CODE (op) == INTEGER_CST)
 	{
-	  if (sgn == SIGNED)
-	    type = signed_type_for (type);
+	  op = fold_convert (signed_type_for (TREE_TYPE (op)), op);
+	  add = wi::to_offset (op);
+	  x = TREE_OPERAND (x, 0);
 	}
-      else if (sgn == UNSIGNED)
-	type = unsigned_type_for (type);
+    }
+
+  if (TREE_CODE (x) == NOP_EXPR)
+    /* Also handle conversions to sizetype seen in the front end IL.  */
+    x = TREE_OPERAND (x, 0);
+
+  tree type = TREE_TYPE (x);
+
+   if (TREE_CODE (x) != INTEGER_CST
+      && TREE_CODE (x) != SSA_NAME)
+    {
+      if (TYPE_UNSIGNED (type)
+	  && TYPE_PRECISION (type) == TYPE_PRECISION (sizetype))
+	type = signed_type_for (type);
 
-      r[0] = wi::to_offset (TYPE_MIN_VALUE (type));
-      r[1] = wi::to_offset (TYPE_MAX_VALUE (type));
+      r[0] = wi::to_offset (TYPE_MIN_VALUE (type)) + add;
+      r[1] = wi::to_offset (TYPE_MAX_VALUE (type)) + add;
       return x;
     }
 
@@ -4323,6 +4421,7 @@ get_range (tree x, signop sgn, offset_int r[2],
   if (!get_range (x, wr, rvals))
     return false;
 
+  signop sgn = SIGNED;
   /* Only convert signed integers or unsigned sizetype to a signed
      offset and avoid converting large positive values in narrower
      types to negative offsets.  */
@@ -4335,6 +4434,83 @@ get_range (tree x, signop sgn, offset_int r[2],
   return true;
 }
 
+/* Return the argument that the call STMT to a built-in function returns
+   or null if it doesn't.  On success, set OFFRNG[] to the range of offsets
+   from the argument reflected in the value returned by the built-in if it
+   can be determined, otherwise to 0 and HWI_M1U respectively.  */
+
+static tree
+gimple_call_return_array (gimple *stmt, offset_int offrng[2],
+			  const vr_values *rvals)
+{
+  if (!gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)
+      || gimple_call_num_args (stmt) < 1)
+    return NULL_TREE;
+
+  tree fn = gimple_call_fndecl (stmt);
+  switch (DECL_FUNCTION_CODE (fn))
+    {
+    case BUILT_IN_MEMCPY:
+    case BUILT_IN_MEMCPY_CHK:
+    case BUILT_IN_MEMMOVE:
+    case BUILT_IN_MEMMOVE_CHK:
+    case BUILT_IN_MEMSET:
+    case BUILT_IN_STPCPY:
+    case BUILT_IN_STPCPY_CHK:
+    case BUILT_IN_STPNCPY:
+    case BUILT_IN_STPNCPY_CHK:
+    case BUILT_IN_STRCAT:
+    case BUILT_IN_STRCAT_CHK:
+    case BUILT_IN_STRCPY:
+    case BUILT_IN_STRCPY_CHK:
+    case BUILT_IN_STRNCAT:
+    case BUILT_IN_STRNCAT_CHK:
+    case BUILT_IN_STRNCPY:
+    case BUILT_IN_STRNCPY_CHK:
+      offrng[0] = offrng[1] = 0;
+      return gimple_call_arg (stmt, 0);
+
+    case BUILT_IN_MEMPCPY:
+    case BUILT_IN_MEMPCPY_CHK:
+      {
+	tree off = gimple_call_arg (stmt, 2);
+	if (!get_offset_range (off, offrng, rvals))
+	  {
+	    offrng[0] = 0;
+	    offrng[1] = HOST_WIDE_INT_M1U;
+	  }
+	return gimple_call_arg (stmt, 0);
+      }
+
+    case BUILT_IN_MEMCHR:
+      {
+	tree off = gimple_call_arg (stmt, 2);
+	if (get_offset_range (off, offrng, rvals))
+	  offrng[0] = 0;
+	else
+	  {
+	    offrng[0] = 0;
+	    offrng[1] = HOST_WIDE_INT_M1U;
+	  }
+	return gimple_call_arg (stmt, 0);
+      }
+
+    case BUILT_IN_STRCHR:
+    case BUILT_IN_STRRCHR:
+    case BUILT_IN_STRSTR:
+      {
+	offrng[0] = 0;
+	offrng[1] = HOST_WIDE_INT_M1U;
+      }
+      return gimple_call_arg (stmt, 0);
+
+    default:
+      break;
+    }
+
+  return NULL_TREE;
+}
+
 /* 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).
@@ -4344,7 +4520,8 @@ get_range (tree x, signop sgn, offset_int r[2],
    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.
+   Returns true on success, false when a meaningful size (or range)
+   cannot be determined.
 
    The function is intended for diagnostics and should not be used
    to influence code generation or optimization.  */
@@ -4361,25 +4538,40 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 
   if (DECL_P (ptr))
     {
-      /* Bail if the reference is to the pointer itself (as opposed
-	 to what it points to).  */
+      pref->ref = ptr;
+
       if (!addr && POINTER_TYPE_P (TREE_TYPE (ptr)))
-	return false;
+	{
+	  /* Set the maximum size if the reference is to the pointer
+	     itself (as opposed to what it points to).  */
+	  pref->set_max_size_range ();
+	  return true;
+	}
 
-      pref->ref = ptr;
       if (tree size = decl_init_size (ptr, false))
 	if (TREE_CODE (size) == INTEGER_CST)
 	  {
 	    pref->sizrng[0] = pref->sizrng[1] = wi::to_offset (size);
 	    return true;
 	  }
-      pref->sizrng[0] = 0;
-      pref->sizrng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
+
+      pref->set_max_size_range ();
       return true;
     }
 
   const tree_code code = TREE_CODE (ptr);
 
+  if (code == BIT_FIELD_REF)
+    {
+      tree ref = TREE_OPERAND (ptr, 0);
+      if (!compute_objsize (ref, ostype, pref, visited, rvals))
+	return false;
+
+      offset_int off = wi::to_offset (pref->eval (TREE_OPERAND (ptr, 2)));
+      pref->add_offset (off / BITS_PER_UNIT);
+      return true;
+    }
+
   if (code == COMPONENT_REF)
     {
       tree ref = TREE_OPERAND (ptr, 0);
@@ -4387,27 +4579,29 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 
       if (ostype == 0)
 	{
-	  /* For raw memory functions like memcpy bail if the size
-	     of the enclosing object cannot be determined.  */
-	  if (!compute_objsize (ref, ostype, pref, visited, rvals)
-	      || !pref->ref)
+	  /* In OSTYPE zero (for raw memory functions like memcpy), use
+	     the maximum size instead if the identity of the enclosing
+	     object cannot be determined.  */
+	  if (!compute_objsize (ref, ostype, pref, visited, rvals))
 	    return false;
 
 	  /* Otherwise, use the size of the enclosing object and add
 	     the offset of the member to the offset computed so far.  */
 	  tree offset = byte_position (field);
-	  if (TREE_CODE (offset) != INTEGER_CST)
-	    return false;
-	  offset_int off = wi::to_offset (offset);
-	  pref->offrng[0] += off;
-	  pref->offrng[1] += off;
+	  if (TREE_CODE (offset) == INTEGER_CST)
+	    pref->add_offset (wi::to_offset (offset));
+	  else
+	    pref->add_max_offset ();
 	  return true;
 	}
 
-      /* 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;
+	{
+	  /* Set maximum size if the reference is to the pointer member
+	     itself (as opposed to what it points to).  */
+	  pref->set_max_size_range ();
+	  return true;
+	}
 
       pref->ref = field;
 
@@ -4462,9 +4656,11 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 
       offset_int orng[2];
       tree off = pref->eval (TREE_OPERAND (ptr, 1));
-      if (!get_range (off, SIGNED, orng, rvals))
-	/* Fail unless the size of the object is zero.  */
-	return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1];
+      if (!get_offset_range (off, orng, rvals))
+	{
+	  orng[1] = wi::to_offset (max_object_size ());
+	  orng[0] = -orng[1] - 1;
+	}
 
       if (TREE_CODE (ptr) == ARRAY_REF)
 	{
@@ -4483,7 +4679,10 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 	  tree eltype = TREE_TYPE (ptr);
 	  tree tpsize = TYPE_SIZE_UNIT (eltype);
 	  if (!tpsize || TREE_CODE (tpsize) != INTEGER_CST)
-	    return false;
+	    {
+	      pref->add_max_offset ();
+	      return true;
+	    }
 
 	  offset_int sz = wi::to_offset (tpsize);
 	  orng[0] *= sz;
@@ -4515,24 +4714,56 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 
       return true;
     }
-  else if (code == POINTER_PLUS_EXPR)
+
+  if (code == TARGET_MEM_REF)
     {
       tree ref = TREE_OPERAND (ptr, 0);
       if (!compute_objsize (ref, ostype, pref, visited, rvals))
 	return false;
 
-      offset_int orng[2];
-      tree off = pref->eval (TREE_OPERAND (ptr, 1));
-      if (!get_range (off, SIGNED, orng, rvals))
-	/* Fail unless the size of the object is zero.  */
-	return pref->sizrng[0] == 0 && pref->sizrng[0] == pref->sizrng[1];
+      /* TODO: Handle remaining operands.  Until then, add maximum offset.  */
+      pref->ref = ptr;
+      pref->add_max_offset ();
+      return true;
+    }
 
-      pref->offrng[0] += orng[0];
-      pref->offrng[1] += orng[1];
+  if (code == INTEGER_CST)
+    {
+      /* Pointer constants other than null are most likely the result
+	 of erroneous null pointer addition/subtraction.  Set size to
+	 zero.  For null pointers, set size to the maximum for now
+	 since those may be the result of jump threading.  */
+      if (integer_zerop (ptr))
+	pref->set_max_size_range ();
+      else
+	pref->sizrng[0] = pref->sizrng[1] = 0;
+      pref->ref = ptr;
+
+      return true;
+    }
+
+  if (code == STRING_CST)
+    {
+      pref->sizrng[0] = pref->sizrng[1] = TREE_STRING_LENGTH (ptr);
+      return true;
+    }
+
+  if (code == POINTER_PLUS_EXPR)
+    {
+      tree ref = TREE_OPERAND (ptr, 0);
+      if (!compute_objsize (ref, ostype, pref, visited, rvals))
+	return false;
 
+      offset_int orng[2];
+      tree off = pref->eval (TREE_OPERAND (ptr, 1));
+      if (get_offset_range (off, orng, rvals))
+	pref->add_offset (orng[0], orng[1]);
+      else
+	pref->add_max_offset ();
       return true;
     }
-  else if (code == VIEW_CONVERT_EXPR)
+
+  if (code == VIEW_CONVERT_EXPR)
     {
       ptr = TREE_OPERAND (ptr, 0);
       return compute_objsize (ptr, ostype, pref, visited, rvals);
@@ -4544,17 +4775,53 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
       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.  */
+	     from its argument(s).  If successful, also set *PREF->REF
+	     to PTR for the caller to include in diagnostics.  */
 	  wide_int wr[2];
 	  if (gimple_call_alloc_size (stmt, wr, rvals))
 	    {
 	      pref->ref = ptr;
 	      pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED);
 	      pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED);
-	      return true;
+	      /* Constrain both bounds to a valid size.  */
+	      offset_int maxsize = wi::to_offset (max_object_size ());
+	      if (pref->sizrng[0] > maxsize)
+		pref->sizrng[0] = maxsize;
+	      if (pref->sizrng[1] > maxsize)
+		pref->sizrng[1] = maxsize;
 	    }
-	  return false;
+	  else
+	    {
+	      /* For functions known to return one of their pointer arguments
+		 try to determine what the returned pointer points to, and on
+		 success add OFFRNG which was set to the offset added by
+		 the function (e.g., memchr) to the overall offset.  */
+	      offset_int offrng[2];
+	      if (tree ret = gimple_call_return_array (stmt, offrng, rvals))
+		{
+		  if (!compute_objsize (ret, ostype, pref, visited, rvals))
+		    return false;
+
+		  /* Cap OFFRNG[1] to at most the remaining size of
+		     the object.  */
+		  offset_int remrng[2];
+		  remrng[1] = pref->size_remaining (remrng);
+		  if (remrng[1] < offrng[1])
+		    offrng[1] = remrng[1];
+		  pref->add_offset (offrng[0], offrng[1]);
+		}
+	      else
+		{
+		  /* For other calls that might return arbitrary pointers
+		     including into the middle of objects set the size
+		     range to maximum, clear PREF->BASE0, and also set
+		     PREF->REF to include in diagnostics.  */
+		  pref->set_max_size_range ();
+		  pref->base0 = false;
+		  pref->ref = ptr;
+		}
+	    }
+	  return true;
 	}
 
       if (gimple_nop_p (stmt))
@@ -4563,62 +4830,76 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 	     of the array from the current function declaratation
 	     (e.g., attribute access or related).  */
 	  wide_int wr[2];
-	  tree ref = gimple_parm_array_size (ptr, wr, rvals);
-	  if (!ref)
-	    return NULL_TREE;
-	  pref->ref = ref;
-	  pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED);
-	  pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED);
+	  if (tree ref = gimple_parm_array_size (ptr, wr, rvals))
+	    {
+	      pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED);
+	      pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED);
+	      pref->ref = ref;
+	      return true;
+	    }
+
+	  pref->set_max_size_range ();
+	  pref->base0 = false;
+	  pref->ref = ptr;
+	  if (tree var = SSA_NAME_VAR (ptr))
+	    if (TREE_CODE (var) == PARM_DECL)
+	      pref->ref = var;
+
 	  return true;
 	}
 
       /* TODO: Handle PHI.  */
 
       if (!is_gimple_assign (stmt))
-	return false;
+	{
+	  /* Clear BASE0 since the assigned pointer might point into
+	     the middle of the object, set the maximum size range and,
+	     if the SSA_NAME refers to a function argumnent, set
+	     PREF->REF to it.  */
+	  pref->base0 = false;
+	  pref->set_max_size_range ();
+	  if (tree var = SSA_NAME_VAR (ptr))
+	    if (TREE_CODE (var) == PARM_DECL)
+	      pref->ref = var;
+	  return true;
+	}
 
       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;
 
-      if (code == POINTER_PLUS_EXPR)
+      if (code == POINTER_PLUS_EXPR
+	  && TREE_CODE (TREE_TYPE (ptr)) == POINTER_TYPE)
 	{
 	  /* 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))
+	  if (!get_offset_range (off, orng, rvals))
 	    {
-	      orng[0] = wi::to_offset (TYPE_MIN_VALUE (ptrdiff_type_node));
-	      orng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
+	      orng[0] = -wi::to_offset (max_object_size ()) - 1;
+	      orng[1] = wi::to_offset (max_object_size ());
 	    }
 
-	  pref->offrng[0] += orng[0];
-	  pref->offrng[1] += orng[1];
+	  pref->add_offset (orng[0], orng[1]);
+	  return compute_objsize (ptr, ostype, pref, visited, rvals);
 	}
-      else if (code != ADDR_EXPR)
-	return false;
 
-      return compute_objsize (ptr, ostype, pref, visited, rvals);
-    }
-
-  tree type = TREE_TYPE (ptr);
-  type = TYPE_MAIN_VARIANT (type);
-  if (TREE_CODE (ptr) == ADDR_EXPR)
-    ptr = TREE_OPERAND (ptr, 0);
+      if (code == ADDR_EXPR)
+	return compute_objsize (ptr, ostype, pref, visited, rvals);
 
-  if (TREE_CODE (type) == ARRAY_TYPE
-      && !array_at_struct_end_p (ptr))
-    {
-      if (tree size = TYPE_SIZE_UNIT (type))
-	return get_range (size, UNSIGNED, pref->sizrng, rvals);
+      /* This could be an assignment from a nonlocal pointer.  Save PTR
+	 to mention in diagnostics but otherwise treat it as a pointer
+	 to an unknown object.  */
+      pref->ref = ptr;
     }
 
-  return false;
+  /* Assume all other expressions point into an unknown object
+     of the maximum valid size.  */
+  pref->base0 = false;
+  pref->set_max_size_range ();
+  return true;
 }
 
 /* A "public" wrapper around the above.  Clients should use this overload
@@ -4639,27 +4920,10 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
   if (!success)
     return NULL_TREE;
 
-  if (pref->offrng[1] < pref->offrng[0])
-    {
-      if (pref->offrng[1] < 0
-	  && pref->sizrng[1] <= pref->offrng[0])
-	return size_zero_node;
-
-      return wide_int_to_tree (sizetype, pref->sizrng[1]);
-    }
-
-  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]);
+  offset_int maxsize = pref->size_remaining ();
+  if (pref->base0 && pref->offrng[0] < 0 && pref->offrng[1] >= 0)
+    pref->offrng[0] = 0;
+  return wide_int_to_tree (sizetype, maxsize);
 }
 
 /* Transitional wrapper around the above.  The function should be removed
@@ -4673,7 +4937,7 @@ compute_objsize (tree ptr, int ostype, tree *pdecl /* = NULL */,
      none has been computed yet.  */
   access_ref ref;
   tree size = compute_objsize (ptr, ostype, &ref, rvals);
-  if (!size)
+  if (!size || !ref.base0)
     return NULL_TREE;
 
   if (pdecl)
@@ -5952,13 +6216,21 @@ expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED rtx target,
 	  tree size2 = compute_objsize (arg2, 1, &ref2);
 	  tree func = get_callee_fndecl (exp);
 
-	  if (size1 && size2)
+	  if (size1 && size2 && bndrng[0] && !integer_zerop (bndrng[0]))
 	    {
-	      tree maxsize = tree_int_cst_le (size1, size2) ? size2 : size1;
-
-	      if (tree_int_cst_lt (maxsize, bndrng[0]))
+	      offset_int rem1 = ref1.size_remaining ();
+	      offset_int rem2 = ref2.size_remaining ();
+	      if (rem1 == 0 || rem2 == 0)
 		maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp, func,
-				      bndrng, maxsize);
+				      bndrng, integer_zero_node);
+	      else
+		{
+		  offset_int maxrem = wi::max (rem1, rem2, UNSIGNED);
+		  if (maxrem < wi::to_offset (bndrng[0]))
+		    maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp,
+					  func, bndrng,
+					  wide_int_to_tree (sizetype, maxrem));
+		}
 	    }
 	  else if (bndrng[0]
 		   && !integer_zerop (bndrng[0])
@@ -12507,13 +12779,13 @@ builtin_with_linkage_p (tree decl)
   return false;
 }
 
+/* Return true if OFFRNG is bounded to a subrange of offset values
+   valid for the largest possible object.  */
+
 bool
 access_ref::offset_bounded () const
 {
-  if (offrng[0] == offrng[1])
-    return false;
-
   tree min = TYPE_MIN_VALUE (ptrdiff_type_node);
   tree max = TYPE_MAX_VALUE (ptrdiff_type_node);
-  return offrng[0] <= wi::to_offset (min) || offrng[1] >= wi::to_offset (max);
+  return wi::to_offset (min) <= offrng[0] && offrng[1] <= wi::to_offset (max);
 }
diff --git a/gcc/builtins.h b/gcc/builtins.h
index 39415aade05..c4788518e73 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -179,15 +179,50 @@ struct access_ref
     return offrng[0] == 0 && offrng[1] == 0;
   }
 
-  /* Return true if OFFRNG is bounded to a subrange of possible offset
-     values.  */
+  /* Return true if OFFRNG is bounded to a subrange of offset values
+     valid for the largest possible object.  */
   bool offset_bounded () const;
 
+  /* Return the maximum amount of space remaining and if non-null, set
+     argument to the minimum.  */
+  offset_int size_remaining (offset_int * = NULL) const;
+
+  /* Set the size range to the maximum.  */
+  void set_max_size_range ()
+  {
+    sizrng[0] = 0;
+    sizrng[1] = wi::to_offset (max_object_size ());
+  }
+
+  /* Add OFF to the offset range.  */
+  void add_offset (const offset_int &off)
+  {
+    offrng[0] += off;
+    offrng[1] += off;
+  }
+
+  /* Add the range [MIN, MAX] to the offset range.  */
+  void add_offset (const offset_int &min, const offset_int &max)
+  {
+    offrng[0] += min;
+    offrng[1] += max;
+  }
+
+  /* Add the maximum valid offset to the offset range.  */
+  void add_max_offset ()
+  {
+    offrng[0] -= wi::to_offset (max_object_size ()) + 1;
+    offrng[1] += wi::to_offset (max_object_size ());
+  }
+
   /* Used to fold integer expressions when called from front ends.  */
   tree (*eval)(tree);
   /* Set if trailing one-element arrays should be treated as flexible
      array members.  */
   bool trail1special;
+  /* Set if valid offsets must start at zero (for declared and allocated
+     objects but not for others referenced by pointers).  */
+  bool base0;
 };
 
 /* Describes a pair of references used in an access by built-in
diff --git a/gcc/calls.c b/gcc/calls.c
index 0e5c696c463..8fb8640aa43 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -1243,14 +1243,16 @@ alloc_max_size (void)
    after adjusting it if necessary to make EXP a represents a valid size
    of object, or a valid size argument to an allocation function declared
    with attribute alloc_size (whose argument may be signed), or to a string
-   manipulation function like memset.  When ALLOW_ZERO is true, allow
-   returning a range of [0, 0] for a size in an anti-range [1, N] where
-   N > PTRDIFF_MAX.  A zero range is a (nearly) invalid argument to
-   allocation functions like malloc but it is a valid argument to
-   functions like memset.  */
+   manipulation function like memset.
+   When ALLOW_ZERO is set in FLAGS, allow returning a range of [0, 0] for
+   a size in an anti-range [1, N] where N > PTRDIFF_MAX.  A zero range is
+   a (nearly) invalid argument to allocation functions like malloc but it
+   is a valid argument to functions like memset.
+   When USE_LARGEST is set in FLAGS set RANGE to the largest valid subrange
+   in a multi-range, otherwise to the smallest valid subrange.  */
 
 bool
-get_size_range (tree exp, tree range[2], bool allow_zero /* = false */)
+get_size_range (tree exp, tree range[2], int flags /* = 0 */)
 {
   if (!exp)
     return false;
@@ -1322,25 +1324,42 @@ get_size_range (tree exp, tree range[2], bool allow_zero /* = false */)
 	      min = wi::zero (expprec);
 	    }
 	}
-      else if (wi::eq_p (0, min - 1))
+      else
 	{
-	  /* EXP is unsigned and not in the range [1, MAX].  That means
-	     it's either zero or greater than MAX.  Even though 0 would
-	     normally be detected by -Walloc-zero, unless ALLOW_ZERO
-	     is true, set the range to [MAX, TYPE_MAX] so that when MAX
-	     is greater than the limit the whole range is diagnosed.  */
-	  if (allow_zero)
-	    min = max = wi::zero (expprec);
-	  else
+	  wide_int maxsize = wi::to_wide (max_object_size ());
+	  min = wide_int::from (min, maxsize.get_precision (), UNSIGNED);
+	  max = wide_int::from (max, maxsize.get_precision (), UNSIGNED);
+	  if (wi::eq_p (0, min - 1))
+	    {
+	      /* EXP is unsigned and not in the range [1, MAX].  That means
+		 it's either zero or greater than MAX.  Even though 0 would
+		 normally be detected by -Walloc-zero, unless ALLOW_ZERO
+		 is set, set the range to [MAX, TYPE_MAX] so that when MAX
+		 is greater than the limit the whole range is diagnosed.  */
+	      wide_int maxsize = wi::to_wide (max_object_size ());
+	      if ((flags & SR_ALLOW_ZERO)
+		  && wi::ltu_p (maxsize, max))
+		min = max = wi::zero (expprec);
+	      else
+		{
+		  min = max + 1;
+		  max = wi::to_wide (TYPE_MAX_VALUE (exptype));
+		}
+	    }
+	  else if ((flags & SR_USE_LARGEST)
+		   && wi::ltu_p (max + 1, maxsize))
 	    {
+	      /* When USE_LARGEST is set and the larger of the two subranges
+		 is a valid size, use it...  */
 	      min = max + 1;
-	      max = wi::to_wide (TYPE_MAX_VALUE (exptype));
+	      max = maxsize;
+	    }
+	  else
+	    {
+	      /* ...otherwise use the smaller subrange.  */
+	      max = min - 1;
+	      min = wi::zero (expprec);
 	    }
-	}
-      else
-	{
-	  max = min - 1;
-	  min = wi::zero (expprec);
 	}
     }
 
diff --git a/gcc/calls.h b/gcc/calls.h
index dfb951ca45b..644ec45d92c 100644
--- a/gcc/calls.h
+++ b/gcc/calls.h
@@ -133,7 +133,15 @@ extern bool reference_callee_copied (CUMULATIVE_ARGS *,
 extern void maybe_warn_alloc_args_overflow (tree, tree, tree[2], int[2]);
 extern tree get_attr_nonstring_decl (tree, tree * = NULL);
 extern bool maybe_warn_nonstring_arg (tree, tree);
-extern bool get_size_range (tree, tree[2], bool = false);
+enum size_range_flags
+  {
+   /* Set to consider zero a valid range.  */
+   SR_ALLOW_ZERO = 1,
+   /* Set to use the largest subrange of a set of ranges as opposed
+      to the smallest.  */
+   SR_USE_LARGEST = 2
+  };
+extern bool get_size_range (tree, tree[2], int = 0);
 extern rtx rtx_for_static_chain (const_tree, bool);
 extern bool cxx17_empty_base_field_p (const_tree);
 
diff --git a/gcc/gimple-ssa-warn-restrict.c b/gcc/gimple-ssa-warn-restrict.c
index 512fc138528..3a5d6f24b3b 100644
--- a/gcc/gimple-ssa-warn-restrict.c
+++ b/gcc/gimple-ssa-warn-restrict.c
@@ -761,7 +761,15 @@ builtin_access::builtin_access (gimple *call, builtin_memref &dst,
 	addr = build1 (ADDR_EXPR, (TREE_TYPE (addr)), addr);
 
       if (tree dstsize = compute_objsize (addr, ostype))
-	dst.basesize = wi::to_offset (dstsize);
+	{
+	  offset_int size = wi::to_offset (dstsize);
+	  if (size < maxobjsize)
+	    dst.basesize = wi::to_offset (dstsize);
+	  else if (POINTER_TYPE_P (TREE_TYPE (addr)))
+	    dst.basesize = HOST_WIDE_INT_MIN;
+	  else
+	    dst.basesize = maxobjsize;
+	}
       else if (POINTER_TYPE_P (TREE_TYPE (addr)))
 	dst.basesize = HOST_WIDE_INT_MIN;
       else
@@ -775,7 +783,15 @@ builtin_access::builtin_access (gimple *call, builtin_memref &dst,
 	addr = build1 (ADDR_EXPR, (TREE_TYPE (addr)), addr);
 
       if (tree srcsize = compute_objsize (addr, ostype))
-	src.basesize = wi::to_offset (srcsize);
+	{
+	  offset_int size = wi::to_offset (srcsize);
+	  if (size < maxobjsize)
+	    src.basesize = wi::to_offset (srcsize);
+	  else if (POINTER_TYPE_P (TREE_TYPE (addr)))
+	    src.basesize = HOST_WIDE_INT_MIN;
+	  else
+	    src.basesize = maxobjsize;
+	}
       else if (POINTER_TYPE_P (TREE_TYPE (addr)))
 	src.basesize = HOST_WIDE_INT_MIN;
       else
diff --git a/gcc/testsuite/c-c++-common/Wrestrict.c b/gcc/testsuite/c-c++-common/Wrestrict.c
index 3b019c8a80e..7a536456c47 100644
--- a/gcc/testsuite/c-c++-common/Wrestrict.c
+++ b/gcc/testsuite/c-c++-common/Wrestrict.c
@@ -320,14 +320,16 @@ void test_memcpy_anti_range (char *d, const char *s)
 
   T (d, d + SAR (0, 3), UR (DIFF_MAX - 2, DIFF_MAX));               /* { dg-warning "accessing \[0-9\]+ or more bytes at offsets 0 and \\\[-?\[0-9\]+, -?\[0-9\]+] overlaps \[0-9\]+ bytes at offset 2" "memcpy" } */
 
-  /* Verify that a size in an anti-range ~[0, N] where N >= PTRDIFF_MAX
-     doesn't trigger a warning.  */
-  T (d, s, UAR (1, DIFF_MAX - 1));
+  /* Verify that a size in an anti-range ~[1, N] where N >= PTRDIFF_MAX - 2
+     doesn't trigger a warning.  With ~[1, PTRDIFF_MAX - 1] and assuming
+     a nonzero size, the difference between the just-past-the-end pointer
+     to A and A for char A[PTRDIFF_MAX] wouldn't be representable in
+     ptrdiff_t so such a large object cannot exist.  */
+  T (d, s, UAR (1, DIFF_MAX / 2 - 1));
+  T (d, s, UAR (1, DIFF_MAX - 1));      /* { dg-warning "\\\[-Wrestrict" } */
   T (d, s, UAR (1, DIFF_MAX));
   T (d, s, UAR (1, SIZE_MAX - 1));
-
-  /* This causes the last dg-warning test to fail for some reason.
-     T (d, s, UAR (1, SIZE_MAX)); */
+  T (d, s, UAR (1, SIZE_MAX));
 }
 
 /* Verify calls to memcpy() where the combination of offsets in some
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size-8.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-8.C
new file mode 100644
index 00000000000..46326c85e7e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size-8.C
@@ -0,0 +1,147 @@
+/* Verify informational notes following the warning.
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+#define DISS_MAX  __PTRDIFF_MAX__
+#define SIZE_MAX  __SIZE_MAX__
+
+typedef __SIZE_TYPE__  size_t;
+
+template <int N> struct S { char a[N]; };
+
+void* operator new (size_t, void *p) { return p; }
+void* operator new[] (size_t, void *p) { return p; }
+
+
+void test_cst_off ()
+{
+  {
+    char ca0[0];                // { dg-message "'ca0' declared here" "note" }
+    new (ca0 + 0) S<1>;         // { dg-warning "constructing an object of type 'S<1>' and size '1' in a region of type 'char \\\[0]' and size '0'" }
+  }
+  {
+    char ca1[1];
+    new (ca1 + 0) S<1>;
+  }
+  {
+    char ca1[1];                // { dg-message "'ca1' declared here" "note" }
+    new (ca1 + 0) S<2>;         // { dg-warning "constructing an object of type 'S<2>' and size '2' in a region of type 'char \\\[1]' and size '1'" }
+  }
+  {
+    char ca1[1];                // { dg-message "at offset 1 from 'ca1' declared here" "note" }
+    new (ca1 + 1) S<1>;         // { dg-warning "constructing an object of type 'S<1>' and size '1' in a region of type 'char \\\[1]' and size '0'" }
+  }
+  {
+    char ca1[1];                // { dg-message "at offset 2 from 'ca1' declared here" "note" }
+    new (ca1 + 2) S<1>;         // { dg-warning "constructing an object of type 'S<1>' and size '1' in a region of type 'char \\\[1]' and size '0'" }
+  }
+  {
+    char ca1[1];                // { dg-message "at offset -1 from 'ca1' declared here" "note" }
+    new (ca1 - 1) S<1>;         // { dg-warning "constructing an object of type 'S<1>' and size '1' in a region of type 'char \\\[1]' and size '0'" }
+  }
+  {
+    /* Offsets are treated as signed so SIZE_MAX is indistinguishable
+       from -1.  */
+    char ca1[1];                // { dg-message "at offset \\d+ from 'ca1' declared here" "note" { xfail *-*-* } }
+                                // { dg-message "at offset -1 from 'ca1' declared here" "note" { target *-*-* } .-1 }
+    new (ca1 + SIZE_MAX) S<1>;  // { dg-warning "constructing an object of type 'S<1>' and size '1' in a region of type 'char \\\[1]' and size '0'" }
+  }
+}
+
+
+/* Verify that the range of the offset included in the note corresponds
+   to the range of its type (plus the optional constant).  */
+
+void test_var_off_uchar (unsigned char i)
+{
+  {
+    // Verify that the nore doesn't mention an offset.
+    char ca0[0];                // { dg-message ": 'ca0' declared here" "note" }
+    new (ca0 + i) S<1>;         // { dg-warning "constructing an object of type 'S<1>' and size '1' in a region of type 'char \\\[0]' and size '0'" }
+  }
+  {
+    char ca1[1];
+    new (ca1 + i) S<1>;
+  }
+  {
+    // Verify that the nore doesn't mention an offset.
+    char ca1[1];                // { dg-message ": 'ca1' declared here" "note" }
+    new (ca1 + i) S<2>;         // { dg-warning "constructing an object of type 'S<2>' and size '2' in a region of type 'char \\\[1]' and size at most '1'" }
+  }
+  {
+    char ca2[2];
+    new (ca2 + i) S<2>;
+    new (ca2 + 1 - i) S<2>;
+    new (ca2 - i + 1) S<2>;
+    new (ca2 - 2 + i) S<2>;
+    new (ca2 - i + 2) S<2>;
+    new (ca2 - i + i) S<2>;
+    new (ca2 + i + i) S<2>;
+  }
+  {
+    char ca2[2];                // { dg-message "at offset \\\[1, 256] from 'ca2' declared here" }
+    new (ca2 + i + 1) S<2>;     // { dg-warning "constructing an object of type 'S<2>' and size '2' in a region of type 'char \\\[2]' and size at most '1'" }
+  }
+
+  {
+    char a[65281];
+    new (a + i + 65280) S<1>;
+  }
+  {
+    char a[65281];              // { dg-message "at offset \\\[65281, 65536] from 'a' declared here" }
+    new (a + i + 65281) S<1>;   // { dg-warning "constructing an object of type 'S<1>' and size '1' in a region of type 'char \\\[65281]' and size '0'" }
+  }
+  {
+    char a[65281];              // { dg-message "at offset \\\[65154, 65409] from 'a' declared here" }
+    new (a + i + 65154) S<128>; // { dg-warning "constructing an object of type 'S<128>' and size '128' in a region of type 'char \\\[65281]' and size at most '127'" }
+  }
+}
+
+
+/* Same as above but also verify that the signedness of the offset is
+   considered in the issuing the warning.  */
+
+void test_var_off_schar (signed char i)
+{
+  {
+    // Verify that the nore doesn't mention an offset.
+    char ca0[0];                // { dg-message ": 'ca0' declared here" "note" }
+    new (ca0 + i) S<1>;         // { dg-warning "constructing an object of type 'S<1>' and size '1' in a region of type 'char \\\[0]' and size '0'" }
+  }
+  {
+    char ca1[1];
+    new (ca1 + i) S<1>;
+    new (ca1 - i) S<1>;
+    new (ca1 + i + 1) S<1>;
+    new (ca1 - i + 1) S<1>;
+    new (ca1 + i + i) S<1>;
+    new (ca1 - i - i) S<1>;
+  }
+  {
+    // Verify that the nore doesn't mention an offset.
+    char ca1[1];                // { dg-message ": 'ca1' declared here" "note" }
+    new (ca1 + i) S<2>;         // { dg-warning "constructing an object of type 'S<2>' and size '2' in a region of type 'char \\\[1]' and size at most '1'" }
+  }
+  {
+    char ca2[2];
+    new (ca2 + i) S<2>;
+    new (ca2 + 1 - i) S<2>;
+    new (ca2 - i + 1) S<2>;
+    new (ca2 - 2 + i) S<2>;
+    new (ca2 - i + 2) S<2>;
+    new (ca2 - i + i) S<2>;
+    new (ca2 + i + i) S<2>;
+  }
+  {
+    char ca2[2];
+    new (ca2 + i + 1) S<2>;
+  }
+
+  {
+    char a[65281];              // { dg-message "at offset \\\[65153, 65408] from 'a'" }
+    new (a + i + 65280) S<1>;
+    new (a + i + 65281) S<1>;
+    new (a + i + 65281) S<128>;
+    new (a + i + 65281) S<129>; // { dg-warning "constructing an object of type 'S<129>' and size '129' in a region of type 'char \\\[65281]' and size at most '128'" }
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-41.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-41.c
new file mode 100644
index 00000000000..2647043b1ef
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-41.c
@@ -0,0 +1,118 @@
+/* Verify that writes at excessive offsets into declared or allocated
+   objects of unknown size are diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#define DIFF_MAX __PTRDIFF_MAX__
+
+typedef __SIZE_TYPE__ size_t;
+
+void* malloc (size_t);
+void* memcpy (void*, const void*, size_t);
+void* memset (void*, int, size_t);
+
+void sink (void*);
+
+
+void char_array_cst_off_cst_size (void)
+{
+  extern char caxcc[];                  // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'caxcc'" }
+
+  char *p = caxcc;
+  size_t idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 2" }
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 1" "pr?????" { xfail ilp32 } }
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 0" }
+  sink (p);
+}
+
+
+void char_array_var_off_cst_size (size_t idx)
+{
+  /* The upper bound of the offset would ideally be positive but for now
+     allow it to be negative too.  */
+  extern char caxvc[];                  // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object 'caxvc'" }
+
+  char *p = caxvc;
+
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+  sink (p);
+}
+
+
+void char_array_var_off_var_size (size_t idx, size_t n)
+{
+  extern char caxvv[];                  // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object 'caxvv'" }
+
+  char *p = caxvv;
+
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  if (n < 3 || 7 < n)
+    n = 3;
+
+  memset (p + idx, 0, n);
+  sink (p);
+
+  ++n;
+  memset (p + idx, 0, n);               // { dg-warning "writing between 4 and 8 bytes into a region of size 3" }
+  sink (p);
+}
+
+
+void alloc_array_var_off_cst_size (size_t n, size_t idx)
+{
+  char *p = malloc (n);                 // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object" }
+
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+  sink (p);
+}
+
+
+void int_array_cst_off_cst_size (void)
+{
+  extern int iaxc[];                    // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'iaxc'" }
+
+  int *p = iaxc;
+  size_t idx = DIFF_MAX / sizeof *iaxc;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+  sink (p);
+}
+
+
+void* nowarn_anti_range_1 (char *p, char *q)
+{
+  size_t n = q - p;
+  if (!n) return 0;
+
+  char *d = __builtin_malloc (n + 1);
+  memcpy (d, p, n + 1);                 // { dg-bogus "-Wstringop-overflow" }
+  return d;
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-44.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-44.c
new file mode 100644
index 00000000000..a9cf4071fff
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-44.c
@@ -0,0 +1,103 @@
+/* Verify that writes at excessive offsets into flexible array members
+   of extern or allocated objects of unknow size are diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#define DIFF_MAX __PTRDIFF_MAX__
+
+typedef __PTRDIFF_TYPE__ ptrdiff_t;
+typedef __SIZE_TYPE__    size_t;
+
+void* memset (void*, int, size_t);
+
+void sink (void*);
+
+void char_flexarray_cst_off_cst_size (void)
+{
+  extern struct { char n, a[]; }
+    caxcc;                              // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'caxcc'" }
+
+  char *p = caxcc.a;
+  size_t idx = DIFF_MAX - 4;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 2" }
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 1" }
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 0" }
+}
+
+
+void char_flexarray_var_off_cst_size (ptrdiff_t idx)
+{
+  extern struct { char n, a[]; }
+    caxvc;                              // { dg-message "destination object 'caxvc'" }
+
+  char *p = caxvc.a;
+
+  if (idx < DIFF_MAX - 4)
+    idx = DIFF_MAX - 4;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+}
+
+
+void char_flexarray_var_off_var_size (size_t n, ptrdiff_t idx)
+{
+  extern struct { char n, a[]; }
+    caxvv;                              // { dg-message "destination object 'caxvv'" }
+
+  char *p = caxvv.a;
+
+  if (idx < DIFF_MAX - 4)
+    idx = DIFF_MAX - 4;
+
+  if (n < 3 || 7 < n)
+    n = 3;
+
+  memset (p + idx, 0, n);
+  sink (p);
+
+  ++n;
+  memset (p + idx, 0, n);               // { dg-warning "writing between 4 and 8 bytes into a region of size 3" }
+}
+
+
+void alloc_array_var_off_cst_size (size_t n, ptrdiff_t idx)
+{
+  struct { char n, a[]; }
+    *p = __builtin_malloc (n);          // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object" "" { xfail *-*-* } }
+
+  if (idx < DIFF_MAX - 4)
+    idx = DIFF_MAX - 4;
+
+  memset (p->a + idx, 0, 3);
+  sink (p);
+
+  memset (p->a + idx, 0, 5);            // { dg-warning "writing 5 bytes into a region of size 3" }
+}
+
+
+void int_array_cst_off_cst_size (void)
+{
+  extern struct { int n, a[]; }
+    iaxc;                               // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'iaxc'" }
+
+  int *p = iaxc.a;
+  size_t idx = DIFF_MAX / sizeof *p - 1;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-45.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-45.c
new file mode 100644
index 00000000000..7e9be57ae85
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-45.c
@@ -0,0 +1,255 @@
+/* PR middle-end/97023 - missing warning on buffer overflow in chained mempcpy
+   Verify that out of bounds writes by built-ins to objects through pointers
+   returned by other built-ins are diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#include "range.h"
+
+void* malloc (size_t);
+void* memcpy (void*, const void*, size_t);
+void* memmove (void*, const void*, size_t);
+void* mempcpy (void*, const void*, size_t);
+
+void sink (void*, ...);
+
+
+void nowarn_memcpy (const void *s)
+{
+  extern char cpy_a4[4];
+  unsigned n = sizeof cpy_a4;
+
+  void *p = cpy_a4;
+  p = memcpy (p, s, n);
+  sink (p);
+  memcpy (p, s, n);
+  sink (p);
+
+  p = cpy_a4 + 1;
+  p = memcpy (p, s, n - 1);
+  sink (p);
+  memcpy (p, s, n - 1);
+  sink (p);
+
+  p = cpy_a4 + 2;
+  p = memcpy (p, s, n - 2);
+  sink (p);
+  memcpy (p, s, n - 2);
+  sink (p);
+
+  p = cpy_a4 + 3;
+  p = memcpy (p, s, n - 3);
+  sink (p);
+  memcpy (p, s, n - 3);
+  sink (p);
+
+  p = cpy_a4 + 4;
+  p = memcpy (p, s, n - 4);
+  sink (p);
+  memcpy (p, s, n - 4);
+  sink (p);
+}
+
+
+void nowarn_memcpy_chain (const void *s)
+{
+  extern char cpy_a8[8];
+
+  char *p = cpy_a8;
+
+  p = memcpy (p + 1, s, 7);
+  sink (p);
+
+  p = memcpy (p + 2 , s, 5);
+  sink (p);
+
+  p = memcpy (p + 3 , s, 2);
+  sink (p);
+
+  p = memcpy (p + 1 , s, 1);
+  sink (p);
+
+  p = memcpy (p - 7 , s, 8);
+  sink (p);
+
+  memcpy (p + 1, s, 7);
+}
+
+
+void warn_memcpy (const void *s)
+{
+  extern char cpy_a5[5];                // { dg-message "destination object 'cpy_a5'" "note" }
+
+  unsigned n = sizeof cpy_a5;
+  void *p = cpy_a5;
+
+  p = memcpy (p, s, n);
+  sink (p);
+  memcpy (p, s, n + 1);                 // { dg-warning "writing 6 bytes into a region of size 5" }
+  sink (p);
+
+  p = cpy_a5;
+  p = memcpy (p, s, n);
+  sink (p);
+  memcpy (p, s, n + 1);                 // { dg-warning "writing 6 bytes into a region of size 5" }
+  sink (p);
+
+  p = cpy_a5 + 1;
+  p = memcpy (p, s, n - 1);
+  sink (p);
+  memcpy (p, s, n);                     // { dg-warning "writing 5 bytes into a region of size 4" }
+  sink (p);
+}
+
+
+void warn_memcpy_chain (const void *s)
+{
+  extern char cpy_a8[8];                // { dg-message "destination object 'cpy_a8'" "note" }
+
+  char *p = cpy_a8;
+
+  p = memcpy (p, s, 9);                 // { dg-warning "writing 9 bytes into a region of size 8" }
+  sink (p);
+
+  p = memcpy (p + 2, s, 7);             // { dg-warning "writing 7 bytes into a region of size 6" }
+  sink (p);
+
+  p = memcpy (p + 3, s, 5);             // { dg-warning "writing 5 bytes into a region of size 3" }
+  sink (p);
+
+  p = memcpy (p + 3, s, 3);             // { dg-warning "writing 3 bytes into a region of size 0" }
+  sink (p);
+}
+
+
+void nowarn_mempcpy (const void *s)
+{
+  extern char a4[4];
+  unsigned n = sizeof a4;
+
+  char *p = mempcpy (a4, s, n);
+  sink (p);
+  mempcpy (p - 4, s, n);
+  sink (p);
+
+  p = mempcpy (a4 + 1, s, n - 1);
+  sink (p);
+  mempcpy (p - 4, s, n);
+  sink (p);
+
+  p = mempcpy (a4 + 2, s, n - 2);
+  sink (p);
+  mempcpy (p - 4, s, n);
+  sink (p);
+
+  p = mempcpy (a4 + 3, s, n - 3);
+  sink (p);
+  mempcpy (p - 4, s, n);
+  sink (p);
+
+  p = mempcpy (a4 + 4, s, n - 4);
+  sink (p);
+  mempcpy (p - 4, s, n);
+  sink (p);
+}
+
+
+void nowarn_mempcpy_chain (const void *s)
+{
+  extern char pcpy_a8[8];
+
+  char *p = pcpy_a8;
+
+  p = mempcpy (p + 1, s, 7);
+  sink (p);
+
+  p = mempcpy (p - 7 , s, 7);
+  sink (p);
+
+  p = mempcpy (p - 5 , s, 5);
+  sink (p);
+
+  p = mempcpy (p - 3 , s, 3);
+  sink (p);
+
+  p = mempcpy (p - 2 , s, 2);
+  sink (p);
+
+  mempcpy (p - 1, s, 1);
+  sink (p);
+
+  mempcpy (p - 8, s, 8);
+}
+
+
+void warn_mempcpy (const void *s)
+{
+  extern char pcpy_a5[5];               // { dg-message "destination object 'pcpy_a5'" "note" }
+
+  char *p = pcpy_a5;
+
+  p = mempcpy (p, s, 5);
+  sink (p);
+  mempcpy (p - 5, s, 6);                // { dg-warning "writing 6 bytes into a region of size 5 " }
+  sink (p);
+
+  p = pcpy_a5;
+  p = mempcpy (p, s, 3);
+  sink (p);
+  mempcpy (p, s, 3);                    // { dg-warning "writing 3 bytes into a region of size 2 " }
+  sink (p);
+
+  p = pcpy_a5 + 1;
+  p = mempcpy (p, s, 3);
+  sink (p);
+  mempcpy (p - 1, s, 5);                // { dg-warning "writing 5 bytes into a region of size 2 " }
+  sink (p);
+}
+
+
+void warn_mempcpy_chain_3 (const void *s)
+{
+  char *p = malloc (5);                 // { dg-message "at offset \\\[3, 5] into destination object of size 5" }
+  p = mempcpy (p, s, UR (1, 2));
+  p = mempcpy (p, s, UR (2, 3));
+  p = mempcpy (p, s, UR (3, 4));        // { dg-warning "writing between 3 and 4 bytes into a region of size 2 " }
+
+  sink (p);
+}
+
+void warn_mempcpy_offrng_chain_3 (const void *s)
+{
+  char *p = malloc (11);                 // { dg-message "at offset \\\[9, 14] into destination object of size 11 " }
+  size_t r1_2 = UR (1, 2);
+  size_t r2_3 = r1_2 + 1;
+  size_t r3_4 = r2_3 + 1;
+
+  p = mempcpy (p + r1_2, s, r1_2);
+  p = mempcpy (p + r2_3, s, r2_3);
+  p = mempcpy (p + r3_4, s, r3_4);       // { dg-warning "writing between 3 and 4 bytes into a region of size 2 " }
+
+  sink (p);
+}
+
+void warn_mempcpy_chain_4 (const void *s)
+{
+  char *p = malloc (9);                 // { dg-message "at offset \\\[6, 9] into destination object of size 9 " }
+  p = mempcpy (p, s, UR (1, 2));
+  p = mempcpy (p, s, UR (2, 3));
+  p = mempcpy (p, s, UR (3, 4));
+  p = mempcpy (p, s, UR (4, 5));        // { dg-warning "writing between 4 and 5 bytes into a region of size 3 " }
+
+  sink (p);
+}
+
+void warn_mempcpy_chain_5 (const void *s)
+{
+  char *p = malloc (14);                // { dg-message "at offset \\\[10, 14] into destination object of size 14 " }
+  p = mempcpy (p, s, UR (1, 2));
+  p = mempcpy (p, s, UR (2, 3));
+  p = mempcpy (p, s, UR (3, 4));
+  p = mempcpy (p, s, UR (4, 5));
+  p = mempcpy (p, s, UR (5, 6));        // { dg-warning "writing between 5 and 6 bytes into a region of size 4 " }
+
+  sink (p);
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-46.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-46.c
new file mode 100644
index 00000000000..68c9c866887
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-46.c
@@ -0,0 +1,94 @@
+/* PR middle-end/97023 - missing warning on buffer overflow in chained mempcpy
+   Verify that out of bounds writes by built-ins to objects through pointers
+   returned by memchr() are diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#include "range.h"
+
+void* malloc (size_t);
+void* memchr (void*, int, size_t);
+void* memset (void*, int, size_t);
+
+void sink (void*, ...);
+
+void nowarn_memchr_cst_memset_cst (const void *s)
+{
+  char *p = malloc (4);
+  sink (p);
+
+  p = memchr (p, '1', 4);
+  memset (p, 0, 4);
+}
+
+void nowarn_memchr_uint_memset_cst (const void *s, unsigned n)
+{
+  char *p = malloc (4);
+  sink (p);
+
+  p = memchr (p, '1', n);
+  memset (p, 0, 4);
+}
+
+void nowarn_memchr_sz_memset_cst (const void *s, size_t n)
+{
+  char *p = malloc (4);
+  sink (p);
+
+  p = memchr (p, '1', n);
+  memset (p, 0, 4);
+}
+
+void nowarn_memchr_anti_range_memset_cst (const void *s, size_t n)
+{
+  char *p = malloc (4);
+  sink (p);
+
+  if (n == 0)
+    n = 1;
+
+  p = memchr (p, '1', n);
+  memset (p, 0, 4);
+}
+
+void warn_memchr_cst_memset_cst (const void *s)
+{
+  char *p = malloc (4);                 // { dg-message "at offset \\\[0, 4] into destination object of size 4 " "note" }
+  sink (p);
+
+  p = memchr (p, '1', 4);
+  memset (p, 0, 5);                     // { dg-warning "writing 5 bytes into a region of size 4 " }
+}
+
+void warn_memchr_var_memset_cst (const void *s, unsigned n)
+{
+  char *p = malloc (4);                 // { dg-message "at offset \\\[0, 4] into destination object of size 4 " "note" }
+  sink (p);
+
+  p = memchr (p, '1', n);
+  memset (p, 0, 5);                     // { dg-warning "writing 5 bytes into a region of size 4 " }
+}
+
+void warn_memchr_var_memset_range (const void *s, unsigned n)
+{
+  /* The offsets in the first two notes are bounded by the size of
+     the allocated object.  The upper bound of the offset in last
+     note is bigger because it includes the upper bound of the offset
+     of the pointer returned from the previous memchr() call.  */
+  char *p0 = malloc (UR (5, 7));
+  // { dg-message "at offset \\\[0, 7] into destination object of size \\\[5, 7]" "note" { target *-*-* } .-1 }
+  // { dg-message "at offset \\\[1, 7] into destination object of size \\\[5, 7]" "note"  { target *-*-* } .-2 }
+  // { dg-message "at offset \\\[2, 12] into destination object of size \\\[5, 7]" "note"  { target *-*-* } .-3 }
+
+  sink (p0);
+  char *p1 = memchr (p0, '1', n);
+  memset (p1, 0, UR (8, 9));            // { dg-warning "writing between 8 and 9 bytes into a region of size 7 " }
+
+  sink (p0);
+  p1 = memchr (p0 + 1, '2', n);
+  memset (p1, 0, UR (7, 9));            // { dg-warning "writing between 7 and 9 bytes into a region of size 6 " }
+
+  sink (p0);
+  char *p2 = memchr (p1 + 1, '3', n);
+  memset (p2, 0, UR (6, 9));            // { dg-warning "writing between 6 and 9 bytes into a region of size 5 " }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-47.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-47.c
new file mode 100644
index 00000000000..02b14ee2eda
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-47.c
@@ -0,0 +1,69 @@
+/* Verify that storing a bigger vector into smaller space is diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+typedef __INT16_TYPE__                         int16_t;
+typedef __attribute__ ((__vector_size__ (32))) char C32;
+
+typedef __attribute__ ((__vector_size__ (64))) int16_t I16_64;
+
+void sink (void*);
+
+
+void nowarn_c32 (char c)
+{
+  extern char nowarn_a32[32];
+
+  void *p = nowarn_a32;
+  *(C32*)p = (C32){ c };
+  sink (p);
+
+  char a32[32];
+  p = a32;
+  *(C32*)p = (C32){ c };
+  sink (p);
+}
+
+void warn_c32 (char c)
+{
+  extern char warn_a32[32];   // { dg-message "at offset 32 to object 'warn_a32' with size 32" }
+
+  void *p = warn_a32 + 1;
+  *(C32*)p = (C32){ c };      // { dg-warning "writing 1 byte into a region of size 0" }
+
+  /* Verify a local variable too. */
+  char a32[32];
+  p = a32 + 1;
+  *(C32*)p = (C32){ c };      // { dg-warning "writing 1 byte into a region of size 0" }
+  sink (p);
+}
+
+
+void nowarn_i16_64 (int16_t i)
+{
+  extern char nowarn_a64[64];
+
+  void *p = nowarn_a64;
+  I16_64 *q = (I16_64*)p;
+  *q = (I16_64){ i };
+
+  char a64[64];
+  q = (I16_64*)a64;
+  *q = (I16_64){ i };
+  sink (q);
+}
+
+void warn_i16_64 (int16_t i)
+{
+  extern char warn_a64[64];   // { dg-message "at offset 128 to object 'warn_a64' with size 64" "pr97027" { xfail *-*-* } }
+
+  void *p = warn_a64 + 1;
+  I16_64 *q = (I16_64*)p;
+  *q = (I16_64){ i };         // { dg-warning "writing 1 byte into a region of size 0" "pr97027" { xfail *-*-* } }
+
+  char a64[64];
+  p = a64 + 1;
+  q = (I16_64*)p;
+  *q = (I16_64){ i };         // { dg-warning "writing 1 byte into a region of size 0" "pr97027" { xfail *-*-* } }
+  sink (p);
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-49.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-49.c
new file mode 100644
index 00000000000..84b6c94fa7e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-49.c
@@ -0,0 +1,146 @@
+/* Verify the handling of anti-ranges/multi-ranges by allocation functions
+   and subsequent accesses.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+void* malloc (size_t);
+void  bzero (void*, size_t);
+void* memset (void*, int, size_t);
+
+
+/* Exercise size_t (via malloc and memset) and unsigned/signed int.  */
+
+__attribute__ ((alloc_size (1))) void*
+alloc_int (int);
+
+__attribute__ ((access (write_only, 1, 2))) void
+access_int (void*, int);
+
+__attribute__ ((alloc_size (1))) void*
+alloc_uint (unsigned);
+
+__attribute__ ((access (write_only, 1, 2))) void
+access_uint (void*, unsigned);
+
+
+void* nowarn_malloc_memset_same_anti_range (size_t n)
+{
+  /* Set N to the anti-range ~[3, 3].  */
+  if (n == 3)
+    n = 4;
+  void *p = malloc (n);
+
+  /* Verify there is no warning for an access to N bytes at P.
+     This means the warning has to assume the value of N in the call
+     to alloc() is in the larger subrange [4, UINT_MAX], while in
+     the call to access() in [0, 3].  */
+  return memset (p, 0, n);
+}
+
+/* Same as above but with two valid ranges.  */
+
+void* nowarn_malloc_memset_anti_range (size_t n1, size_t n2)
+{
+  /* Set N1 to the anti-range ~[3, 3].  */
+  if (n1 == 3)
+    n1 = 4;
+  void *p = malloc (n1);
+
+  /* Set N2 to the anti-range ~[7, 7].  */
+  if (n2 == 7)
+    n2 = 8;
+
+  return memset (p, 0, n2);
+}
+
+
+void nowarn_alloc_access_same_anti_range_int (int n)
+{
+  /* Set N to the anti-range ~[3, 3].  */
+  if (n == 3)
+    n = 4;
+  void *p = alloc_int (n);
+
+  /* Verify there is no warning for an access to N bytes at P.
+     This means the warning has to assume the value of N in the call
+     to alloc() is in the larger subrange [4, UINT_MAX], while in
+     the call to access() in [0, 3].  */
+  access_int (p, n);
+}
+
+/* Same as above but with two valid ranges.  */
+
+void nowarn_alloc_access_anti_range_int (int n1, int n2)
+{
+  /* Set N1 to the anti-range ~[3, 3].  */
+  if (n1 == 3)
+    n1 = 4;
+  void *p = alloc_int (n1);
+
+  /* Set N2 to the anti-range ~[7, 7].  */
+  if (n2 == 7)
+    n2 = 8;
+
+  access_int (p, n2);
+}
+
+
+void nowarn_alloc_access_same_anti_range_uint (unsigned n)
+{
+  /* Set N to the anti-range ~[3, 3].  */
+  if (n == 3)
+    n = 4;
+  void *p = alloc_uint (n);
+
+  /* Verify there is no warning for an access to N bytes at P.
+     This means the warning has to assume the value of N in the call
+     to alloc() is in the larger subrange [4, UINT_MAX], while in
+     the call to access() in [0, 3].  */
+  access_uint (p, n);
+}
+
+/* Same as above but with two valid ranges.  */
+
+void nowarn_alloc_access_anti_range_uint (unsigned n1, unsigned n2)
+{
+  /* Set N1 to the anti-range ~[3, 3].  */
+  if (n1 == 3)
+    n1 = 4;
+  void *p = alloc_uint (n1);
+
+  /* Set N2 to the anti-range ~[7, 7].  */
+  if (n2 == 7)
+    n2 = 8;
+
+  access_uint (p, n2);
+}
+
+
+void* nowarn_malloc_anti_range_memset_range (size_t n1, size_t n2)
+{
+  /* Set N1 to the anti-range ~[3, 3].  */
+  if (n1 == 3)
+    n1 = 4;
+  void *p = malloc (n1);
+
+  /* Set N2 to the range [5, MAX].  */
+  if (n2 < 5)
+    n2 = 5;
+  return memset (p, 0, n2);
+}
+
+void* nowarn_malloc_range_bzero_anti_range (size_t n1, size_t n2)
+{
+  /* Set N1 to the anti-range ~[3, 3].  */
+  if (n1 > 4)
+    n1 = 4;
+  void *p = malloc (n1);
+
+  /* Set N2 to the range [5, MAX].  */
+  if (n2 <= 3 || 5 <= n2)
+    n2 = 4;
+  bzero (p, n2);
+  return p;
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-50.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-50.c
new file mode 100644
index 00000000000..ca98286f449
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-50.c
@@ -0,0 +1,121 @@
+/* Verify that writes at excessive offsets into objects of unknown size
+   pointed to by function arguments are diagnosed.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#define DIFF_MAX __PTRDIFF_MAX__
+
+typedef __PTRDIFF_TYPE__ ptrdiff_t;
+typedef __SIZE_TYPE__    size_t;
+
+void* memset (void*, int, size_t);
+
+void sink (void*);
+
+char* fcall (void);
+
+void char_ptr_cst_off_cst_size (char *p)
+                                        // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'p'" "note" { target *-*-* } .-1 }
+{
+  size_t idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 2" }
+  sink (p);
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 1" }
+
+  ++idx;
+  memset (p + idx, 0, 3);               // { dg-warning "writing 3 bytes into a region of size 0" }
+}
+
+
+void char_ptr_var_difoff_cst_size (ptrdiff_t idx)
+{
+  char *p = fcall ();
+  // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object of size \\\[0, \[1-9\]\[0-9\]+] (allocated|returned) by 'fcall'" "note" { target *-*-* } .-1 }
+
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+}
+
+
+void char_ptr_var_szoff_cst_size (size_t idx)
+{
+  extern char* gptr;
+  // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, -*\[1-9\]\[0-9\]*] into destination object 'gptr'" "note" { target *-*-* } .-1 }
+
+  char *p = gptr;
+
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" "" { xfail *-*-* } }
+
+  if (idx > DIFF_MAX)
+    idx = DIFF_MAX;
+
+  memset (p + idx, 0, 7);               // { dg-warning "writing 7 bytes into a region of size 3" }
+}
+
+
+void char_ptr_var_difoff_var_size (char *p, ptrdiff_t idx, size_t n)
+                                        // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, \[1-9\]\[0-9\]+] into destination object 'p'" "note" { target *-*-* } .-1 }
+{
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  if (n < 3 || 7 < n)
+    n = 3;
+
+  memset (p + idx, 0, n);
+  sink (p);
+
+  ++n;
+  memset (p + idx, 0, n);               // { dg-warning "writing between 4 and 8 bytes into a region of size 3" }
+}
+
+
+void char_ptr_var_szoff_var_size (char *p, size_t idx, size_t n)
+                                        // { dg-message "at offset \\\[\[1-9\]\[0-9\]+, \[1-9\]\[0-9\]+] into destination object 'p'" "note" { xfail *-*-* } .-1 }
+{
+  if (idx < DIFF_MAX - 3)
+    idx = DIFF_MAX - 3;
+
+  if (n < 3 || 7 < n)
+    n = 3;
+
+  memset (p + idx, 0, n);
+  sink (p);
+
+  ++n;
+  /* With an unsigned offset large values are interpreted as negative
+     so the addition (p + idx) is effectively treated as subtraction,
+     making an overflow indistinguishable from a valid (if unlikely)
+     store.  */
+  memset (p + idx, 0, n);               // { dg-warning "writing between 4 and 8 bytes into a region of size 3" "pr?????" { xfail *-*-* } }
+}
+
+
+void int_ptr_cst_off_cst_size (int *p)
+                                        // { dg-message "at offset \[1-9\]\[0-9\]+ into destination object 'p'" "note" { target *-*-* } .-1 }
+{
+  size_t idx = DIFF_MAX / sizeof *p;
+
+  memset (p + idx, 0, 3);
+  sink (p);
+
+  memset (p + idx, 0, 5);               // { dg-warning "writing 5 bytes into a region of size 3" }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-51.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-51.c
new file mode 100644
index 00000000000..6f36643c8bb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-51.c
@@ -0,0 +1,34 @@
+/* Test case derived from Binutils/GDB's readline/readline/histexpand.c.
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+char *
+get_subst_pattern (char *str, int *iptr, int delimiter, int is_rhs, int *lenptr)
+{
+  int si, i, j, k;
+  char *s;
+
+  s = 0;
+  i = *iptr;
+
+  for (si = i; str[si] && str[si] != delimiter; si++)
+      if (str[si] == '\\' && str[si + 1] == delimiter)
+	si++;
+
+  if (si > i || is_rhs)
+    {
+      s = (char *)__builtin_malloc (si - i + 1);
+      for (j = 0, k = i; k < si; j++, k++)
+	{
+	  /* Remove a backslash quoting the search string delimiter. */
+	  if (str[k] == '\\' && str[k + 1] == delimiter)
+	    k++;
+	  s[j] = str[k];   // { dg-bogus "-Wstringop-overflow" }
+	}
+      s[j] = '\0';
+      if (lenptr)
+	*lenptr = j;
+    }
+
+  return s;
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-52.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-52.c
new file mode 100644
index 00000000000..a28965557c3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-52.c
@@ -0,0 +1,62 @@
+
+/* PR middle-end/97023 - missing warning on buffer overflow in chained mempcpy
+   Verify that writes by built-in functions to objects through pointers
+   returned by ordinary (non-built-int) function are assumed to point to
+   the beginning of objects.
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+#include "range.h"
+
+void* memcpy (void*, const void*, size_t);
+void* memset (void*, int, size_t);
+
+void sink (void*, ...);
+
+extern char* arrptr[];
+extern char* ptr;
+extern char* retptr (void);
+struct S { char *p; };
+extern struct S retstruct (void);
+
+void nowarn_ptr (void)
+{
+  {
+    void *p = arrptr;
+    memset (p - 1, 0, 12345);           // { dg-warning "\\\[-Wstringop-overflow" }
+    memset (p,0, 12345);
+    memset (p,0, DIFF_MAX - 1);
+  }
+
+  {
+    char *p = arrptr[0];
+    memset (p - 1, 0, 12345);
+    memset (p - 12345, 0, 12345);
+    memset (p - 1234, 0, DIFF_MAX - 1);
+    memset (p - DIFF_MAX + 1, 0, 12345);
+  }
+
+  {
+    char *p = ptr;
+    memset (p - 1, 0, 12345);
+    memset (p - 12345, 0, 12345);
+    memset (p - 1234, 0, DIFF_MAX - 1);
+    memset (p - DIFF_MAX + 1, 0, 12345);
+  }
+
+  {
+    char *p = retptr ();
+    memset (p - 1, 0, 12345);
+    memset (p - 12345, 0, 12345);
+    memset (p - 1234, 0, DIFF_MAX - 1);
+    memset (p - DIFF_MAX + 1, 0, 12345);
+  }
+
+  {
+    char *p = retstruct ().p;
+    memset (p - 1, 0, 12345);
+    memset (p - 12345, 0, 12345);
+    memset (p - 1234, 0, DIFF_MAX - 1);
+    memset (p - DIFF_MAX + 1, 0, 12345);
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-53.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-53.c
new file mode 100644
index 00000000000..cd8fa3202eb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-53.c
@@ -0,0 +1,116 @@
+/* PR middle-end/96384 - bogus -Wstringop-overflow= storing into
+   multidimensional array with index in range
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+#define SHRT_MAX   __SHRT_MAX__
+#define SHRT_MIN   (-SHRT_MAX - 1)
+#define INT_MAX    __INT_MAX__
+#define INT_MIN    (-INT_MAX - 1)
+#define LONG_MAX   __LONG_MAX__
+#define LONG_MIN   (-LONG_MAX - 1)
+
+#define USHRT_MAX  (SHRT_MAX * 2 + 1)
+#define UINT_MAX   ~0U
+#define ULONG_MAX  ~0LU
+
+char ca3_5_7[3][5][7];
+
+void nowarn_ca_3_5_ssi (short i)
+{
+  if (i > SHRT_MAX - 1)
+    i = SHRT_MAX - 1;
+
+  ca3_5_7[i][0][0] = __LINE__;
+  ca3_5_7[i][0][1] = __LINE__;
+  ca3_5_7[i][0][2] = __LINE__;
+  ca3_5_7[i][0][3] = __LINE__;
+  ca3_5_7[i][0][4] = __LINE__;
+  ca3_5_7[i][0][5] = __LINE__;
+  ca3_5_7[i][0][6] = __LINE__;
+
+  ca3_5_7[i][1][0] = __LINE__;
+  ca3_5_7[i][1][1] = __LINE__;
+  ca3_5_7[i][1][2] = __LINE__;
+  ca3_5_7[i][1][3] = __LINE__;
+  ca3_5_7[i][1][4] = __LINE__;
+  ca3_5_7[i][1][5] = __LINE__;
+  ca3_5_7[i][1][6] = __LINE__;
+
+  ca3_5_7[i][2][0] = __LINE__;
+  ca3_5_7[i][2][1] = __LINE__;
+  ca3_5_7[i][2][2] = __LINE__;
+  ca3_5_7[i][2][3] = __LINE__;
+  ca3_5_7[i][2][4] = __LINE__;
+  ca3_5_7[i][2][5] = __LINE__;
+  ca3_5_7[i][2][6] = __LINE__;
+
+  ca3_5_7[i][3][0] = __LINE__;
+  ca3_5_7[i][3][1] = __LINE__;
+  ca3_5_7[i][3][2] = __LINE__;
+  ca3_5_7[i][3][3] = __LINE__;
+  ca3_5_7[i][3][4] = __LINE__;
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[i][3][6] = __LINE__;
+
+  ca3_5_7[i][4][0] = __LINE__;
+  ca3_5_7[i][4][1] = __LINE__;
+  ca3_5_7[i][4][2] = __LINE__;
+  ca3_5_7[i][4][3] = __LINE__;
+  ca3_5_7[i][4][4] = __LINE__;
+  ca3_5_7[i][4][5] = __LINE__;
+  ca3_5_7[i][4][6] = __LINE__;
+
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_usi (unsigned short i)
+{
+  if (i > USHRT_MAX - 1)
+    i = USHRT_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_si (int i)
+{
+  if (i > INT_MAX - 1)
+    i = INT_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_ui (unsigned i)
+{
+  if (i > UINT_MAX - 1)
+    i = UINT_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_li (long i)
+{
+  if (i > LONG_MAX - 1)
+    i = LONG_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
+
+void nowarn_ca_3_5_uli (unsigned long i)
+{
+  if (i > ULONG_MAX - 1)
+    i = ULONG_MAX - 1;
+
+  ca3_5_7[i][3][5] = __LINE__;
+  ca3_5_7[1][i][5] = __LINE__;
+  ca3_5_7[2][3][i] = __LINE__;
+}
diff --git a/gcc/testsuite/gcc.dg/pr51683.c b/gcc/testsuite/gcc.dg/pr51683.c
index c477cd8fdd2..7a624e4055d 100644
--- a/gcc/testsuite/gcc.dg/pr51683.c
+++ b/gcc/testsuite/gcc.dg/pr51683.c
@@ -12,6 +12,9 @@ void *
 foo (void *p)
 {
   return bar ((void *) 0x12345000, p, 256);
+  /* Integers converted to pointers are assumed to be the result of
+     (invalid) arithmetic on null pointers.
+     { dg-prune-output "writing 256 bytes into a region of size 0" } */
 }
 
 /* { dg-final { scan-tree-dump "memcpy" "optimized" } } */

^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-09-28 22:01               ` Martin Sebor
@ 2020-10-05 16:37                 ` Martin Sebor
  2020-10-07 14:26                 ` Jason Merrill
  2020-10-13  9:46                 ` Christophe Lyon
  2 siblings, 0 replies; 28+ messages in thread
From: Martin Sebor @ 2020-10-05 16:37 UTC (permalink / raw)
  To: Jason Merrill, gcc-patches

Ping: https://gcc.gnu.org/pipermail/gcc-patches/2020-September/555019.html

On 9/28/20 4:01 PM, Martin Sebor wrote:
> On 9/25/20 11:17 PM, Jason Merrill wrote:
>> On 9/22/20 4:05 PM, Martin Sebor wrote:
>>> The rebased and retested patches are attached.
>>>
>>> On 9/21/20 3:17 PM, Martin Sebor wrote:
>>>> Ping: 
>>>> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/553906.html
>>>>
>>>> (I'm working on rebasing the patch on top of the latest trunk which
>>>> has changed some of the same code but it'd be helpful to get a go-
>>>> ahead on substance the changes.  I don't expect the rebase to
>>>> require any substantive modifications.)
>>>>
>>>> Martin
>>>>
>>>> On 9/14/20 4:01 PM, Martin Sebor wrote:
>>>>> On 9/4/20 11:14 AM, Jason Merrill wrote:
>>>>>> On 9/3/20 2:44 PM, Martin Sebor wrote:
>>>>>>> On 9/1/20 1:22 PM, Jason Merrill wrote:
>>>>>>>> On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
>>>>>>>>> -Wplacement-new handles array indices and pointer offsets the 
>>>>>>>>> same:
>>>>>>>>> by adjusting them by the size of the element.  That's correct for
>>>>>>>>> the latter but wrong for the former, causing false positives when
>>>>>>>>> the element size is greater than one.
>>>>>>>>>
>>>>>>>>> In addition, the warning doesn't even attempt to handle arrays of
>>>>>>>>> arrays.  I'm not sure if I forgot or if I simply didn't think of
>>>>>>>>> it.
>>>>>>>>>
>>>>>>>>> The attached patch corrects these oversights by replacing most
>>>>>>>>> of the -Wplacement-new code with a call to compute_objsize which
>>>>>>>>> handles all this correctly (plus more), and is also better tested.
>>>>>>>>> But even compute_objsize has bugs: it trips up while converting
>>>>>>>>> wide_int to offset_int for some pointer offset ranges.  Since
>>>>>>>>> handling the C++ IL required changes in this area the patch also
>>>>>>>>> fixes that.
>>>>>>>>>
>>>>>>>>> For review purposes, the patch affects just the middle end.
>>>>>>>>> The C++ diff pretty much just removes code from the front end.
>>>>>>>>
>>>>>>>> The C++ changes are OK.
>>>>>>>
>>>>>>> Thank you for looking at the rest as well.
>>>>>>>
>>>>>>>>
>>>>>>>>> -compute_objsize (tree ptr, int ostype, access_ref *pref,
>>>>>>>>> -                bitmap *visited, const vr_values *rvals /* = 
>>>>>>>>> NULL */)
>>>>>>>>> +compute_objsize (tree ptr, int ostype, access_ref *pref, 
>>>>>>>>> bitmap *visited,
>>>>>>>>> +                const vr_values *rvals)
>>>>>>>>
>>>>>>>> This reformatting seems unnecessary, and I prefer to keep the 
>>>>>>>> comment about the default argument.
>>>>>>>
>>>>>>> This overload doesn't take a default argument.  (There was a stray
>>>>>>> declaration of a similar function at the top of the file that had
>>>>>>> one.  I've removed it.)
>>>>>>
>>>>>> Ah, true.
>>>>>>
>>>>>>>>> -      if (!size || TREE_CODE (size) != INTEGER_CST)
>>>>>>>>> -       return false;
>>>>>>>>  >...
>>>>>>>>
>>>>>>>> You change some failure cases in compute_objsize to return 
>>>>>>>> success with a maximum range, while others continue to return 
>>>>>>>> failure. This needs commentary about the design rationale.
>>>>>>>
>>>>>>> This is too much for a comment in the code but the background is
>>>>>>> this: compute_objsize initially returned the object size as a 
>>>>>>> constant.
>>>>>>> Recently, I have enhanced it to return a range to improve 
>>>>>>> warnings for
>>>>>>> allocated objects.  With that, a failure can be turned into 
>>>>>>> success by
>>>>>>> having the function set the range to that of the largest object. 
>>>>>>> That
>>>>>>> should simplify the function's callers and could even improve
>>>>>>> the detection of some invalid accesses.  Once this change is made
>>>>>>> it might even be possible to change its return type to void.
>>>>>>>
>>>>>>> The change that caught your eye is necessary to make the function
>>>>>>> a drop-in replacement for the C++ front end code which makes this
>>>>>>> same assumption.  Without it, a number of test cases that exercise
>>>>>>> VLAs fail in g++.dg/warn/Wplacement-new-size-5.C.  For example:
>>>>>>>
>>>>>>>    void f (int n)
>>>>>>>    {
>>>>>>>      char a[n];
>>>>>>>      new (a - 1) int ();
>>>>>>>    }
>>>>>>>
>>>>>>> Changing any of the other places isn't necessary for existing tests
>>>>>>> to pass (and I didn't want to introduce too much churn).  But I do
>>>>>>> want to change the rest of the function along the same lines at some
>>>>>>> point.
>>>>>>
>>>>>> Please do change the other places to be consistent; better to have 
>>>>>> more churn than to leave the function half-updated.  That can be a 
>>>>>> separate patch if you prefer, but let's do it now rather than later.
>>>>>
>>>>> I've made most of these changes in the other patch (also attached).
>>>>> I'm quite happy with the result but it turned out to be a lot more
>>>>> work than either of us expected, mostly due to the amount of testing.
>>>>>
>>>>> I've left a couple of failing cases in place mainly as reminders
>>>>> to handle them better (which means I also didn't change the caller
>>>>> to avoid testing for failures).  I've also added TODO notes with
>>>>> reminders to handle some of the new codes more completely.
>>>>>
>>>>>>
>>>>>>>>> +  special_array_member sam{ };
>>>>>>>>
>>>>>>>> sam is always set by component_ref_size, so I don't think it's 
>>>>>>>> necessary to initialize it at the declaration.
>>>>>>>
>>>>>>> I find initializing pass-by-pointer local variables helpful but
>>>>>>> I don't insist on it.
>>>>>>>
>>>>>>>>
>>>>>>>>> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
>>>>>>>>>    tree last_type = TREE_TYPE (last);
>>>>>>>>>    if (TREE_CODE (last_type) != ARRAY_TYPE
>>>>>>>>>        || TYPE_SIZE (last_type))
>>>>>>>>> -    return size;
>>>>>>>>> +    return size ? size : TYPE_SIZE_UNIT (type);
>>>>>>>>
>>>>>>>> This change seems to violate the comment for the function.
>>>>>>>
>>>>>>> By my reading (and writing) the change is covered by the first
>>>>>>> sentence:
>>>>>>>
>>>>>>>     Returns the size of the object designated by DECL considering
>>>>>>>     its initializer if it either has one or if it would not affect
>>>>>>>     its size, ...
>>>>>>
>>>>>> OK, I see it now.
>>>>>>
>>>>>>> It handles a number of cases in Wplacement-new-size.C fail that
>>>>>>> construct a larger object in an extern declaration of a template,
>>>>>>> like this:
>>>>>>>
>>>>>>>    template <class> struct S { char c; };
>>>>>>>    extern S<int> s;
>>>>>>>
>>>>>>>    void f ()
>>>>>>>    {
>>>>>>>      new (&s) int ();
>>>>>>>    }
>>>>>>>
>>>>>>> I don't know why DECL_SIZE isn't set here (I don't think it can
>>>>>>> be anything but equal to TYPE_SIZE, can it?) and other than struct
>>>>>>> objects with a flexible array member where this identity doesn't
>>>>>>> hold I can't think of others.  Am I missing something?
>>>>>>
>>>>>> Good question.  The attached patch should fix that, so you 
>>>>>> shouldn't need the change to decl_init_size:
>>>>>
>>>>> I've integrated it into the bug fix.
>>>>>
>>>>> Besides the usual x86_64-linux bootstrap/regtest I tested both
>>>>> patches by building a few packages, including Binutils/GDB, Glibc,
>>>>> and  verifying no new warnings show up.
>>>>>
>>>>> Martin
>>
>>> +offset_int
>>> +access_ref::size_remaining (offset_int *pmin /* = NULL */) const
>>
>> For the various member functions, please include the comments with the 
>> definition as well as the in-class declaration.
> 
> Only one access_ref member function is defined out-of-line: 
> offset_bounded().  I've adjusted the comment and copied it above
> the function definition.
> 
>>
>>> +      if (offrng[1] < offrng[0])
>>
>> What does it mean for the max offset to be less than the min offset?  
>> I wouldn't expect that to ever happen with wide integers.
> 
> The offset is represented in sizetype with negative values represented
> as large positive values, but has to be converted to ptrdiff_t.  These
> cases come up when the unsigned offset is an ordinary range that
> corresponds to an anti-range, such as here:
> 
>    extern char a[2];
> 
>    void f (unsigned long i)
>    {
>      if (i == 0)
>        return;
>      a[i] = 0;   // i's range is [1, -1] (i.e., [1, SIZE_MAX]
>    }
> 
>>
>>> +  /* Return true if OFFRNG is bounded to a subrange of possible offset
>>> +     values.  */
>>> +  bool offset_bounded () const;
>>
>> I don't understand how you're using this.  The implementation checks 
>> for the possible offset values falling outside those representable by 
>> ptrdiff_t, unless the range is only a single value.  And then the only 
>> use is
>>
>>> +  if (ref.offset_zero () || !ref.offset_bounded ())
>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>> +        "%qD declared here", ref.ref);
>>> +  else if (ref.offrng[0] == ref.offrng[1])
>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>> +        "at offset %wi from %qD declared here",
>>> +        ref.offrng[0].to_shwi (), ref.ref);
>>> +  else
>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>> +        "at offset [%wi, %wi] from %qD declared here",
>>> +        ref.offrng[0].to_shwi (), ref.offrng[1].to_shwi (), ref.ref);
>>
>> So if the possible offsets are all representable by ptrdiff_t, we 
>> don't print the range?  The middle case also looks unreachable, since 
>> offset_bounded will return false in that case.
> 
> The function was originally named "offset_unbounded."  I changed
> it to "offset_bounded" but looks like I didn't finish the job or
> add any tests for it.
> 
> The goal of conditionals is to avoid overwhelming the user with
> excessive numbers that may not be meaningful or even relevant
> to the warning.  I've corrected the function body, tweaked and
> renamed the get_range function to get_offset_range to do a better
> job of extracting ranges from the types of some nonconstant
> expressions the front end passes it, and added a new test for
> all this.  Attached is the new revision.
> 
> Martin


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-09-28 22:01               ` Martin Sebor
  2020-10-05 16:37                 ` Martin Sebor
@ 2020-10-07 14:26                 ` Jason Merrill
  2020-10-07 14:42                   ` Martin Sebor
  2020-10-13  9:46                 ` Christophe Lyon
  2 siblings, 1 reply; 28+ messages in thread
From: Jason Merrill @ 2020-10-07 14:26 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On 9/28/20 6:01 PM, Martin Sebor wrote:
> On 9/25/20 11:17 PM, Jason Merrill wrote:
>> On 9/22/20 4:05 PM, Martin Sebor wrote:
>>> The rebased and retested patches are attached.
>>>
>>> On 9/21/20 3:17 PM, Martin Sebor wrote:
>>>> Ping: 
>>>> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/553906.html
>>>>
>>>> (I'm working on rebasing the patch on top of the latest trunk which
>>>> has changed some of the same code but it'd be helpful to get a go-
>>>> ahead on substance the changes.  I don't expect the rebase to
>>>> require any substantive modifications.)
>>>>
>>>> Martin
>>>>
>>>> On 9/14/20 4:01 PM, Martin Sebor wrote:
>>>>> On 9/4/20 11:14 AM, Jason Merrill wrote:
>>>>>> On 9/3/20 2:44 PM, Martin Sebor wrote:
>>>>>>> On 9/1/20 1:22 PM, Jason Merrill wrote:
>>>>>>>> On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
>>>>>>>>> -Wplacement-new handles array indices and pointer offsets the 
>>>>>>>>> same:
>>>>>>>>> by adjusting them by the size of the element.  That's correct for
>>>>>>>>> the latter but wrong for the former, causing false positives when
>>>>>>>>> the element size is greater than one.
>>>>>>>>>
>>>>>>>>> In addition, the warning doesn't even attempt to handle arrays of
>>>>>>>>> arrays.  I'm not sure if I forgot or if I simply didn't think of
>>>>>>>>> it.
>>>>>>>>>
>>>>>>>>> The attached patch corrects these oversights by replacing most
>>>>>>>>> of the -Wplacement-new code with a call to compute_objsize which
>>>>>>>>> handles all this correctly (plus more), and is also better tested.
>>>>>>>>> But even compute_objsize has bugs: it trips up while converting
>>>>>>>>> wide_int to offset_int for some pointer offset ranges.  Since
>>>>>>>>> handling the C++ IL required changes in this area the patch also
>>>>>>>>> fixes that.
>>>>>>>>>
>>>>>>>>> For review purposes, the patch affects just the middle end.
>>>>>>>>> The C++ diff pretty much just removes code from the front end.
>>>>>>>>
>>>>>>>> The C++ changes are OK.
>>>>>>>
>>>>>>> Thank you for looking at the rest as well.
>>>>>>>
>>>>>>>>
>>>>>>>>> -compute_objsize (tree ptr, int ostype, access_ref *pref,
>>>>>>>>> -                bitmap *visited, const vr_values *rvals /* = 
>>>>>>>>> NULL */)
>>>>>>>>> +compute_objsize (tree ptr, int ostype, access_ref *pref, 
>>>>>>>>> bitmap *visited,
>>>>>>>>> +                const vr_values *rvals)
>>>>>>>>
>>>>>>>> This reformatting seems unnecessary, and I prefer to keep the 
>>>>>>>> comment about the default argument.
>>>>>>>
>>>>>>> This overload doesn't take a default argument.  (There was a stray
>>>>>>> declaration of a similar function at the top of the file that had
>>>>>>> one.  I've removed it.)
>>>>>>
>>>>>> Ah, true.
>>>>>>
>>>>>>>>> -      if (!size || TREE_CODE (size) != INTEGER_CST)
>>>>>>>>> -       return false;
>>>>>>>>  >...
>>>>>>>>
>>>>>>>> You change some failure cases in compute_objsize to return 
>>>>>>>> success with a maximum range, while others continue to return 
>>>>>>>> failure. This needs commentary about the design rationale.
>>>>>>>
>>>>>>> This is too much for a comment in the code but the background is
>>>>>>> this: compute_objsize initially returned the object size as a 
>>>>>>> constant.
>>>>>>> Recently, I have enhanced it to return a range to improve 
>>>>>>> warnings for
>>>>>>> allocated objects.  With that, a failure can be turned into 
>>>>>>> success by
>>>>>>> having the function set the range to that of the largest object. 
>>>>>>> That
>>>>>>> should simplify the function's callers and could even improve
>>>>>>> the detection of some invalid accesses.  Once this change is made
>>>>>>> it might even be possible to change its return type to void.
>>>>>>>
>>>>>>> The change that caught your eye is necessary to make the function
>>>>>>> a drop-in replacement for the C++ front end code which makes this
>>>>>>> same assumption.  Without it, a number of test cases that exercise
>>>>>>> VLAs fail in g++.dg/warn/Wplacement-new-size-5.C.  For example:
>>>>>>>
>>>>>>>    void f (int n)
>>>>>>>    {
>>>>>>>      char a[n];
>>>>>>>      new (a - 1) int ();
>>>>>>>    }
>>>>>>>
>>>>>>> Changing any of the other places isn't necessary for existing tests
>>>>>>> to pass (and I didn't want to introduce too much churn).  But I do
>>>>>>> want to change the rest of the function along the same lines at some
>>>>>>> point.
>>>>>>
>>>>>> Please do change the other places to be consistent; better to have 
>>>>>> more churn than to leave the function half-updated.  That can be a 
>>>>>> separate patch if you prefer, but let's do it now rather than later.
>>>>>
>>>>> I've made most of these changes in the other patch (also attached).
>>>>> I'm quite happy with the result but it turned out to be a lot more
>>>>> work than either of us expected, mostly due to the amount of testing.
>>>>>
>>>>> I've left a couple of failing cases in place mainly as reminders
>>>>> to handle them better (which means I also didn't change the caller
>>>>> to avoid testing for failures).  I've also added TODO notes with
>>>>> reminders to handle some of the new codes more completely.
>>>>>
>>>>>>
>>>>>>>>> +  special_array_member sam{ };
>>>>>>>>
>>>>>>>> sam is always set by component_ref_size, so I don't think it's 
>>>>>>>> necessary to initialize it at the declaration.
>>>>>>>
>>>>>>> I find initializing pass-by-pointer local variables helpful but
>>>>>>> I don't insist on it.
>>>>>>>
>>>>>>>>
>>>>>>>>> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
>>>>>>>>>    tree last_type = TREE_TYPE (last);
>>>>>>>>>    if (TREE_CODE (last_type) != ARRAY_TYPE
>>>>>>>>>        || TYPE_SIZE (last_type))
>>>>>>>>> -    return size;
>>>>>>>>> +    return size ? size : TYPE_SIZE_UNIT (type);
>>>>>>>>
>>>>>>>> This change seems to violate the comment for the function.
>>>>>>>
>>>>>>> By my reading (and writing) the change is covered by the first
>>>>>>> sentence:
>>>>>>>
>>>>>>>     Returns the size of the object designated by DECL considering
>>>>>>>     its initializer if it either has one or if it would not affect
>>>>>>>     its size, ...
>>>>>>
>>>>>> OK, I see it now.
>>>>>>
>>>>>>> It handles a number of cases in Wplacement-new-size.C fail that
>>>>>>> construct a larger object in an extern declaration of a template,
>>>>>>> like this:
>>>>>>>
>>>>>>>    template <class> struct S { char c; };
>>>>>>>    extern S<int> s;
>>>>>>>
>>>>>>>    void f ()
>>>>>>>    {
>>>>>>>      new (&s) int ();
>>>>>>>    }
>>>>>>>
>>>>>>> I don't know why DECL_SIZE isn't set here (I don't think it can
>>>>>>> be anything but equal to TYPE_SIZE, can it?) and other than struct
>>>>>>> objects with a flexible array member where this identity doesn't
>>>>>>> hold I can't think of others.  Am I missing something?
>>>>>>
>>>>>> Good question.  The attached patch should fix that, so you 
>>>>>> shouldn't need the change to decl_init_size:
>>>>>
>>>>> I've integrated it into the bug fix.
>>>>>
>>>>> Besides the usual x86_64-linux bootstrap/regtest I tested both
>>>>> patches by building a few packages, including Binutils/GDB, Glibc,
>>>>> and  verifying no new warnings show up.
>>>>>
>>>>> Martin
>>
>>> +offset_int
>>> +access_ref::size_remaining (offset_int *pmin /* = NULL */) const
>>
>> For the various member functions, please include the comments with the 
>> definition as well as the in-class declaration.
> 
> Only one access_ref member function is defined out-of-line: 
> offset_bounded().  I've adjusted the comment and copied it above
> the function definition.
> 
>>
>>> +      if (offrng[1] < offrng[0])
>>
>> What does it mean for the max offset to be less than the min offset?  
>> I wouldn't expect that to ever happen with wide integers.
> 
> The offset is represented in sizetype with negative values represented
> as large positive values, but has to be converted to ptrdiff_t.

It looks to me like the offset is offset_int, which is both signed and 
big enough to hold all values of sizetype without turning large positive 
values into negative values.  Where are these sign-switching conversions 
happening?

> These
> cases come up when the unsigned offset is an ordinary range that
> corresponds to an anti-range, such as here:
> 
>    extern char a[2];
> 
>    void f (unsigned long i)
>    {
>      if (i == 0)
>        return;
>      a[i] = 0;   // i's range is [1, -1] (i.e., [1, SIZE_MAX]
>    }
> 
>>
>>> +  /* Return true if OFFRNG is bounded to a subrange of possible offset
>>> +     values.  */
>>> +  bool offset_bounded () const;
>>
>> I don't understand how you're using this.  The implementation checks 
>> for the possible offset values falling outside those representable by 
>> ptrdiff_t, unless the range is only a single value.  And then the only 
>> use is
>>
>>> +  if (ref.offset_zero () || !ref.offset_bounded ())
>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>> +        "%qD declared here", ref.ref);
>>> +  else if (ref.offrng[0] == ref.offrng[1])
>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>> +        "at offset %wi from %qD declared here",
>>> +        ref.offrng[0].to_shwi (), ref.ref);
>>> +  else
>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>> +        "at offset [%wi, %wi] from %qD declared here",
>>> +        ref.offrng[0].to_shwi (), ref.offrng[1].to_shwi (), ref.ref);
>>
>> So if the possible offsets are all representable by ptrdiff_t, we 
>> don't print the range?  The middle case also looks unreachable, since 
>> offset_bounded will return false in that case.
> 
> The function was originally named "offset_unbounded."  I changed
> it to "offset_bounded" but looks like I didn't finish the job or
> add any tests for it.
> 
> The goal of conditionals is to avoid overwhelming the user with
> excessive numbers that may not be meaningful or even relevant
> to the warning.  I've corrected the function body, tweaked and
> renamed the get_range function to get_offset_range to do a better
> job of extracting ranges from the types of some nonconstant
> expressions the front end passes it, and added a new test for
> all this.  Attached is the new revision.
> 
> Martin


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-10-07 14:26                 ` Jason Merrill
@ 2020-10-07 14:42                   ` Martin Sebor
  2020-10-07 15:07                     ` Jason Merrill
  0 siblings, 1 reply; 28+ messages in thread
From: Martin Sebor @ 2020-10-07 14:42 UTC (permalink / raw)
  To: Jason Merrill, gcc-patches

On 10/7/20 8:26 AM, Jason Merrill wrote:
> On 9/28/20 6:01 PM, Martin Sebor wrote:
>> On 9/25/20 11:17 PM, Jason Merrill wrote:
>>> On 9/22/20 4:05 PM, Martin Sebor wrote:
>>>> The rebased and retested patches are attached.
>>>>
>>>> On 9/21/20 3:17 PM, Martin Sebor wrote:
>>>>> Ping: 
>>>>> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/553906.html
>>>>>
>>>>> (I'm working on rebasing the patch on top of the latest trunk which
>>>>> has changed some of the same code but it'd be helpful to get a go-
>>>>> ahead on substance the changes.  I don't expect the rebase to
>>>>> require any substantive modifications.)
>>>>>
>>>>> Martin
>>>>>
>>>>> On 9/14/20 4:01 PM, Martin Sebor wrote:
>>>>>> On 9/4/20 11:14 AM, Jason Merrill wrote:
>>>>>>> On 9/3/20 2:44 PM, Martin Sebor wrote:
>>>>>>>> On 9/1/20 1:22 PM, Jason Merrill wrote:
>>>>>>>>> On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
>>>>>>>>>> -Wplacement-new handles array indices and pointer offsets the 
>>>>>>>>>> same:
>>>>>>>>>> by adjusting them by the size of the element.  That's correct for
>>>>>>>>>> the latter but wrong for the former, causing false positives when
>>>>>>>>>> the element size is greater than one.
>>>>>>>>>>
>>>>>>>>>> In addition, the warning doesn't even attempt to handle arrays of
>>>>>>>>>> arrays.  I'm not sure if I forgot or if I simply didn't think of
>>>>>>>>>> it.
>>>>>>>>>>
>>>>>>>>>> The attached patch corrects these oversights by replacing most
>>>>>>>>>> of the -Wplacement-new code with a call to compute_objsize which
>>>>>>>>>> handles all this correctly (plus more), and is also better 
>>>>>>>>>> tested.
>>>>>>>>>> But even compute_objsize has bugs: it trips up while converting
>>>>>>>>>> wide_int to offset_int for some pointer offset ranges.  Since
>>>>>>>>>> handling the C++ IL required changes in this area the patch also
>>>>>>>>>> fixes that.
>>>>>>>>>>
>>>>>>>>>> For review purposes, the patch affects just the middle end.
>>>>>>>>>> The C++ diff pretty much just removes code from the front end.
>>>>>>>>>
>>>>>>>>> The C++ changes are OK.
>>>>>>>>
>>>>>>>> Thank you for looking at the rest as well.
>>>>>>>>
>>>>>>>>>
>>>>>>>>>> -compute_objsize (tree ptr, int ostype, access_ref *pref,
>>>>>>>>>> -                bitmap *visited, const vr_values *rvals /* = 
>>>>>>>>>> NULL */)
>>>>>>>>>> +compute_objsize (tree ptr, int ostype, access_ref *pref, 
>>>>>>>>>> bitmap *visited,
>>>>>>>>>> +                const vr_values *rvals)
>>>>>>>>>
>>>>>>>>> This reformatting seems unnecessary, and I prefer to keep the 
>>>>>>>>> comment about the default argument.
>>>>>>>>
>>>>>>>> This overload doesn't take a default argument.  (There was a stray
>>>>>>>> declaration of a similar function at the top of the file that had
>>>>>>>> one.  I've removed it.)
>>>>>>>
>>>>>>> Ah, true.
>>>>>>>
>>>>>>>>>> -      if (!size || TREE_CODE (size) != INTEGER_CST)
>>>>>>>>>> -       return false;
>>>>>>>>>  >...
>>>>>>>>>
>>>>>>>>> You change some failure cases in compute_objsize to return 
>>>>>>>>> success with a maximum range, while others continue to return 
>>>>>>>>> failure. This needs commentary about the design rationale.
>>>>>>>>
>>>>>>>> This is too much for a comment in the code but the background is
>>>>>>>> this: compute_objsize initially returned the object size as a 
>>>>>>>> constant.
>>>>>>>> Recently, I have enhanced it to return a range to improve 
>>>>>>>> warnings for
>>>>>>>> allocated objects.  With that, a failure can be turned into 
>>>>>>>> success by
>>>>>>>> having the function set the range to that of the largest object. 
>>>>>>>> That
>>>>>>>> should simplify the function's callers and could even improve
>>>>>>>> the detection of some invalid accesses.  Once this change is made
>>>>>>>> it might even be possible to change its return type to void.
>>>>>>>>
>>>>>>>> The change that caught your eye is necessary to make the function
>>>>>>>> a drop-in replacement for the C++ front end code which makes this
>>>>>>>> same assumption.  Without it, a number of test cases that exercise
>>>>>>>> VLAs fail in g++.dg/warn/Wplacement-new-size-5.C.  For example:
>>>>>>>>
>>>>>>>>    void f (int n)
>>>>>>>>    {
>>>>>>>>      char a[n];
>>>>>>>>      new (a - 1) int ();
>>>>>>>>    }
>>>>>>>>
>>>>>>>> Changing any of the other places isn't necessary for existing tests
>>>>>>>> to pass (and I didn't want to introduce too much churn).  But I do
>>>>>>>> want to change the rest of the function along the same lines at 
>>>>>>>> some
>>>>>>>> point.
>>>>>>>
>>>>>>> Please do change the other places to be consistent; better to 
>>>>>>> have more churn than to leave the function half-updated.  That 
>>>>>>> can be a separate patch if you prefer, but let's do it now rather 
>>>>>>> than later.
>>>>>>
>>>>>> I've made most of these changes in the other patch (also attached).
>>>>>> I'm quite happy with the result but it turned out to be a lot more
>>>>>> work than either of us expected, mostly due to the amount of testing.
>>>>>>
>>>>>> I've left a couple of failing cases in place mainly as reminders
>>>>>> to handle them better (which means I also didn't change the caller
>>>>>> to avoid testing for failures).  I've also added TODO notes with
>>>>>> reminders to handle some of the new codes more completely.
>>>>>>
>>>>>>>
>>>>>>>>>> +  special_array_member sam{ };
>>>>>>>>>
>>>>>>>>> sam is always set by component_ref_size, so I don't think it's 
>>>>>>>>> necessary to initialize it at the declaration.
>>>>>>>>
>>>>>>>> I find initializing pass-by-pointer local variables helpful but
>>>>>>>> I don't insist on it.
>>>>>>>>
>>>>>>>>>
>>>>>>>>>> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
>>>>>>>>>>    tree last_type = TREE_TYPE (last);
>>>>>>>>>>    if (TREE_CODE (last_type) != ARRAY_TYPE
>>>>>>>>>>        || TYPE_SIZE (last_type))
>>>>>>>>>> -    return size;
>>>>>>>>>> +    return size ? size : TYPE_SIZE_UNIT (type);
>>>>>>>>>
>>>>>>>>> This change seems to violate the comment for the function.
>>>>>>>>
>>>>>>>> By my reading (and writing) the change is covered by the first
>>>>>>>> sentence:
>>>>>>>>
>>>>>>>>     Returns the size of the object designated by DECL considering
>>>>>>>>     its initializer if it either has one or if it would not affect
>>>>>>>>     its size, ...
>>>>>>>
>>>>>>> OK, I see it now.
>>>>>>>
>>>>>>>> It handles a number of cases in Wplacement-new-size.C fail that
>>>>>>>> construct a larger object in an extern declaration of a template,
>>>>>>>> like this:
>>>>>>>>
>>>>>>>>    template <class> struct S { char c; };
>>>>>>>>    extern S<int> s;
>>>>>>>>
>>>>>>>>    void f ()
>>>>>>>>    {
>>>>>>>>      new (&s) int ();
>>>>>>>>    }
>>>>>>>>
>>>>>>>> I don't know why DECL_SIZE isn't set here (I don't think it can
>>>>>>>> be anything but equal to TYPE_SIZE, can it?) and other than struct
>>>>>>>> objects with a flexible array member where this identity doesn't
>>>>>>>> hold I can't think of others.  Am I missing something?
>>>>>>>
>>>>>>> Good question.  The attached patch should fix that, so you 
>>>>>>> shouldn't need the change to decl_init_size:
>>>>>>
>>>>>> I've integrated it into the bug fix.
>>>>>>
>>>>>> Besides the usual x86_64-linux bootstrap/regtest I tested both
>>>>>> patches by building a few packages, including Binutils/GDB, Glibc,
>>>>>> and  verifying no new warnings show up.
>>>>>>
>>>>>> Martin
>>>
>>>> +offset_int
>>>> +access_ref::size_remaining (offset_int *pmin /* = NULL */) const
>>>
>>> For the various member functions, please include the comments with 
>>> the definition as well as the in-class declaration.
>>
>> Only one access_ref member function is defined out-of-line: 
>> offset_bounded().  I've adjusted the comment and copied it above
>> the function definition.
>>
>>>
>>>> +      if (offrng[1] < offrng[0])
>>>
>>> What does it mean for the max offset to be less than the min offset? 
>>> I wouldn't expect that to ever happen with wide integers.
>>
>> The offset is represented in sizetype with negative values represented
>> as large positive values, but has to be converted to ptrdiff_t.
> 
> It looks to me like the offset is offset_int, which is both signed and 
> big enough to hold all values of sizetype without turning large positive 
> values into negative values.  Where are these sign-switching conversions 
> happening?

In get_offset_range in builtins.c.

Martin

> 
>> These
>> cases come up when the unsigned offset is an ordinary range that
>> corresponds to an anti-range, such as here:
>>
>>    extern char a[2];
>>
>>    void f (unsigned long i)
>>    {
>>      if (i == 0)
>>        return;
>>      a[i] = 0;   // i's range is [1, -1] (i.e., [1, SIZE_MAX]
>>    }
>>
>>>
>>>> +  /* Return true if OFFRNG is bounded to a subrange of possible offset
>>>> +     values.  */
>>>> +  bool offset_bounded () const;
>>>
>>> I don't understand how you're using this.  The implementation checks 
>>> for the possible offset values falling outside those representable by 
>>> ptrdiff_t, unless the range is only a single value.  And then the 
>>> only use is
>>>
>>>> +  if (ref.offset_zero () || !ref.offset_bounded ())
>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>> +        "%qD declared here", ref.ref);
>>>> +  else if (ref.offrng[0] == ref.offrng[1])
>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>> +        "at offset %wi from %qD declared here",
>>>> +        ref.offrng[0].to_shwi (), ref.ref);
>>>> +  else
>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>> +        "at offset [%wi, %wi] from %qD declared here",
>>>> +        ref.offrng[0].to_shwi (), ref.offrng[1].to_shwi (), ref.ref);
>>>
>>> So if the possible offsets are all representable by ptrdiff_t, we 
>>> don't print the range?  The middle case also looks unreachable, since 
>>> offset_bounded will return false in that case.
>>
>> The function was originally named "offset_unbounded."  I changed
>> it to "offset_bounded" but looks like I didn't finish the job or
>> add any tests for it.
>>
>> The goal of conditionals is to avoid overwhelming the user with
>> excessive numbers that may not be meaningful or even relevant
>> to the warning.  I've corrected the function body, tweaked and
>> renamed the get_range function to get_offset_range to do a better
>> job of extracting ranges from the types of some nonconstant
>> expressions the front end passes it, and added a new test for
>> all this.  Attached is the new revision.
>>
>> Martin
> 


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-10-07 14:42                   ` Martin Sebor
@ 2020-10-07 15:07                     ` Jason Merrill
  2020-10-07 15:19                       ` Martin Sebor
  0 siblings, 1 reply; 28+ messages in thread
From: Jason Merrill @ 2020-10-07 15:07 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On 10/7/20 10:42 AM, Martin Sebor wrote:
> On 10/7/20 8:26 AM, Jason Merrill wrote:
>> On 9/28/20 6:01 PM, Martin Sebor wrote:
>>> On 9/25/20 11:17 PM, Jason Merrill wrote:
>>>> On 9/22/20 4:05 PM, Martin Sebor wrote:
>>>>> The rebased and retested patches are attached.
>>>>>
>>>>> On 9/21/20 3:17 PM, Martin Sebor wrote:
>>>>>> Ping: 
>>>>>> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/553906.html
>>>>>>
>>>>>> (I'm working on rebasing the patch on top of the latest trunk which
>>>>>> has changed some of the same code but it'd be helpful to get a go-
>>>>>> ahead on substance the changes.  I don't expect the rebase to
>>>>>> require any substantive modifications.)
>>>>>>
>>>>>> Martin
>>>>>>
>>>>>> On 9/14/20 4:01 PM, Martin Sebor wrote:
>>>>>>> On 9/4/20 11:14 AM, Jason Merrill wrote:
>>>>>>>> On 9/3/20 2:44 PM, Martin Sebor wrote:
>>>>>>>>> On 9/1/20 1:22 PM, Jason Merrill wrote:
>>>>>>>>>> On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
>>>>>>>>>>> -Wplacement-new handles array indices and pointer offsets the 
>>>>>>>>>>> same:
>>>>>>>>>>> by adjusting them by the size of the element.  That's correct 
>>>>>>>>>>> for
>>>>>>>>>>> the latter but wrong for the former, causing false positives 
>>>>>>>>>>> when
>>>>>>>>>>> the element size is greater than one.
>>>>>>>>>>>
>>>>>>>>>>> In addition, the warning doesn't even attempt to handle 
>>>>>>>>>>> arrays of
>>>>>>>>>>> arrays.  I'm not sure if I forgot or if I simply didn't think of
>>>>>>>>>>> it.
>>>>>>>>>>>
>>>>>>>>>>> The attached patch corrects these oversights by replacing most
>>>>>>>>>>> of the -Wplacement-new code with a call to compute_objsize which
>>>>>>>>>>> handles all this correctly (plus more), and is also better 
>>>>>>>>>>> tested.
>>>>>>>>>>> But even compute_objsize has bugs: it trips up while converting
>>>>>>>>>>> wide_int to offset_int for some pointer offset ranges.  Since
>>>>>>>>>>> handling the C++ IL required changes in this area the patch also
>>>>>>>>>>> fixes that.
>>>>>>>>>>>
>>>>>>>>>>> For review purposes, the patch affects just the middle end.
>>>>>>>>>>> The C++ diff pretty much just removes code from the front end.
>>>>>>>>>>
>>>>>>>>>> The C++ changes are OK.
>>>>>>>>>
>>>>>>>>> Thank you for looking at the rest as well.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>> -compute_objsize (tree ptr, int ostype, access_ref *pref,
>>>>>>>>>>> -                bitmap *visited, const vr_values *rvals /* = 
>>>>>>>>>>> NULL */)
>>>>>>>>>>> +compute_objsize (tree ptr, int ostype, access_ref *pref, 
>>>>>>>>>>> bitmap *visited,
>>>>>>>>>>> +                const vr_values *rvals)
>>>>>>>>>>
>>>>>>>>>> This reformatting seems unnecessary, and I prefer to keep the 
>>>>>>>>>> comment about the default argument.
>>>>>>>>>
>>>>>>>>> This overload doesn't take a default argument.  (There was a stray
>>>>>>>>> declaration of a similar function at the top of the file that had
>>>>>>>>> one.  I've removed it.)
>>>>>>>>
>>>>>>>> Ah, true.
>>>>>>>>
>>>>>>>>>>> -      if (!size || TREE_CODE (size) != INTEGER_CST)
>>>>>>>>>>> -       return false;
>>>>>>>>>>  >...
>>>>>>>>>>
>>>>>>>>>> You change some failure cases in compute_objsize to return 
>>>>>>>>>> success with a maximum range, while others continue to return 
>>>>>>>>>> failure. This needs commentary about the design rationale.
>>>>>>>>>
>>>>>>>>> This is too much for a comment in the code but the background is
>>>>>>>>> this: compute_objsize initially returned the object size as a 
>>>>>>>>> constant.
>>>>>>>>> Recently, I have enhanced it to return a range to improve 
>>>>>>>>> warnings for
>>>>>>>>> allocated objects.  With that, a failure can be turned into 
>>>>>>>>> success by
>>>>>>>>> having the function set the range to that of the largest 
>>>>>>>>> object. That
>>>>>>>>> should simplify the function's callers and could even improve
>>>>>>>>> the detection of some invalid accesses.  Once this change is made
>>>>>>>>> it might even be possible to change its return type to void.
>>>>>>>>>
>>>>>>>>> The change that caught your eye is necessary to make the function
>>>>>>>>> a drop-in replacement for the C++ front end code which makes this
>>>>>>>>> same assumption.  Without it, a number of test cases that exercise
>>>>>>>>> VLAs fail in g++.dg/warn/Wplacement-new-size-5.C.  For example:
>>>>>>>>>
>>>>>>>>>    void f (int n)
>>>>>>>>>    {
>>>>>>>>>      char a[n];
>>>>>>>>>      new (a - 1) int ();
>>>>>>>>>    }
>>>>>>>>>
>>>>>>>>> Changing any of the other places isn't necessary for existing 
>>>>>>>>> tests
>>>>>>>>> to pass (and I didn't want to introduce too much churn).  But I do
>>>>>>>>> want to change the rest of the function along the same lines at 
>>>>>>>>> some
>>>>>>>>> point.
>>>>>>>>
>>>>>>>> Please do change the other places to be consistent; better to 
>>>>>>>> have more churn than to leave the function half-updated.  That 
>>>>>>>> can be a separate patch if you prefer, but let's do it now 
>>>>>>>> rather than later.
>>>>>>>
>>>>>>> I've made most of these changes in the other patch (also attached).
>>>>>>> I'm quite happy with the result but it turned out to be a lot more
>>>>>>> work than either of us expected, mostly due to the amount of 
>>>>>>> testing.
>>>>>>>
>>>>>>> I've left a couple of failing cases in place mainly as reminders
>>>>>>> to handle them better (which means I also didn't change the caller
>>>>>>> to avoid testing for failures).  I've also added TODO notes with
>>>>>>> reminders to handle some of the new codes more completely.
>>>>>>>
>>>>>>>>
>>>>>>>>>>> +  special_array_member sam{ };
>>>>>>>>>>
>>>>>>>>>> sam is always set by component_ref_size, so I don't think it's 
>>>>>>>>>> necessary to initialize it at the declaration.
>>>>>>>>>
>>>>>>>>> I find initializing pass-by-pointer local variables helpful but
>>>>>>>>> I don't insist on it.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
>>>>>>>>>>>    tree last_type = TREE_TYPE (last);
>>>>>>>>>>>    if (TREE_CODE (last_type) != ARRAY_TYPE
>>>>>>>>>>>        || TYPE_SIZE (last_type))
>>>>>>>>>>> -    return size;
>>>>>>>>>>> +    return size ? size : TYPE_SIZE_UNIT (type);
>>>>>>>>>>
>>>>>>>>>> This change seems to violate the comment for the function.
>>>>>>>>>
>>>>>>>>> By my reading (and writing) the change is covered by the first
>>>>>>>>> sentence:
>>>>>>>>>
>>>>>>>>>     Returns the size of the object designated by DECL considering
>>>>>>>>>     its initializer if it either has one or if it would not affect
>>>>>>>>>     its size, ...
>>>>>>>>
>>>>>>>> OK, I see it now.
>>>>>>>>
>>>>>>>>> It handles a number of cases in Wplacement-new-size.C fail that
>>>>>>>>> construct a larger object in an extern declaration of a template,
>>>>>>>>> like this:
>>>>>>>>>
>>>>>>>>>    template <class> struct S { char c; };
>>>>>>>>>    extern S<int> s;
>>>>>>>>>
>>>>>>>>>    void f ()
>>>>>>>>>    {
>>>>>>>>>      new (&s) int ();
>>>>>>>>>    }
>>>>>>>>>
>>>>>>>>> I don't know why DECL_SIZE isn't set here (I don't think it can
>>>>>>>>> be anything but equal to TYPE_SIZE, can it?) and other than struct
>>>>>>>>> objects with a flexible array member where this identity doesn't
>>>>>>>>> hold I can't think of others.  Am I missing something?
>>>>>>>>
>>>>>>>> Good question.  The attached patch should fix that, so you 
>>>>>>>> shouldn't need the change to decl_init_size:
>>>>>>>
>>>>>>> I've integrated it into the bug fix.
>>>>>>>
>>>>>>> Besides the usual x86_64-linux bootstrap/regtest I tested both
>>>>>>> patches by building a few packages, including Binutils/GDB, Glibc,
>>>>>>> and  verifying no new warnings show up.
>>>>>>>
>>>>>>> Martin
>>>>
>>>>> +offset_int
>>>>> +access_ref::size_remaining (offset_int *pmin /* = NULL */) const
>>>>
>>>> For the various member functions, please include the comments with 
>>>> the definition as well as the in-class declaration.
>>>
>>> Only one access_ref member function is defined out-of-line: 
>>> offset_bounded().  I've adjusted the comment and copied it above
>>> the function definition.
>>>
>>>>
>>>>> +      if (offrng[1] < offrng[0])
>>>>
>>>> What does it mean for the max offset to be less than the min offset? 
>>>> I wouldn't expect that to ever happen with wide integers.
>>>
>>> The offset is represented in sizetype with negative values represented
>>> as large positive values, but has to be converted to ptrdiff_t.
>>
>> It looks to me like the offset is offset_int, which is both signed and 
>> big enough to hold all values of sizetype without turning large 
>> positive values into negative values.  Where are these sign-switching 
>> conversions happening?
> 
> In get_offset_range in builtins.c.

Since we're converting to offset_int there, why not give the offset_int 
the real value rather than a bogus negative value?

>>> These
>>> cases come up when the unsigned offset is an ordinary range that
>>> corresponds to an anti-range, such as here:
>>>
>>>    extern char a[2];
>>>
>>>    void f (unsigned long i)
>>>    {
>>>      if (i == 0)
>>>        return;
>>>      a[i] = 0;   // i's range is [1, -1] (i.e., [1, SIZE_MAX]
>>>    }
>>>
>>>>
>>>>> +  /* Return true if OFFRNG is bounded to a subrange of possible 
>>>>> offset
>>>>> +     values.  */
>>>>> +  bool offset_bounded () const;
>>>>
>>>> I don't understand how you're using this.  The implementation checks 
>>>> for the possible offset values falling outside those representable 
>>>> by ptrdiff_t, unless the range is only a single value.  And then the 
>>>> only use is
>>>>
>>>>> +  if (ref.offset_zero () || !ref.offset_bounded ())
>>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>>> +        "%qD declared here", ref.ref);
>>>>> +  else if (ref.offrng[0] == ref.offrng[1])
>>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>>> +        "at offset %wi from %qD declared here",
>>>>> +        ref.offrng[0].to_shwi (), ref.ref);
>>>>> +  else
>>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>>> +        "at offset [%wi, %wi] from %qD declared here",
>>>>> +        ref.offrng[0].to_shwi (), ref.offrng[1].to_shwi (), ref.ref);
>>>>
>>>> So if the possible offsets are all representable by ptrdiff_t, we 
>>>> don't print the range?  The middle case also looks unreachable, 
>>>> since offset_bounded will return false in that case.
>>>
>>> The function was originally named "offset_unbounded."  I changed
>>> it to "offset_bounded" but looks like I didn't finish the job or
>>> add any tests for it.
>>>
>>> The goal of conditionals is to avoid overwhelming the user with
>>> excessive numbers that may not be meaningful or even relevant
>>> to the warning.  I've corrected the function body, tweaked and
>>> renamed the get_range function to get_offset_range to do a better
>>> job of extracting ranges from the types of some nonconstant
>>> expressions the front end passes it, and added a new test for
>>> all this.  Attached is the new revision.
>>>
>>> Martin
>>
> 


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-10-07 15:07                     ` Jason Merrill
@ 2020-10-07 15:19                       ` Martin Sebor
  2020-10-07 19:28                         ` Jason Merrill
  0 siblings, 1 reply; 28+ messages in thread
From: Martin Sebor @ 2020-10-07 15:19 UTC (permalink / raw)
  To: Jason Merrill, gcc-patches

On 10/7/20 9:07 AM, Jason Merrill wrote:
> On 10/7/20 10:42 AM, Martin Sebor wrote:
>> On 10/7/20 8:26 AM, Jason Merrill wrote:
>>> On 9/28/20 6:01 PM, Martin Sebor wrote:
>>>> On 9/25/20 11:17 PM, Jason Merrill wrote:
>>>>> On 9/22/20 4:05 PM, Martin Sebor wrote:
>>>>>> The rebased and retested patches are attached.
>>>>>>
>>>>>> On 9/21/20 3:17 PM, Martin Sebor wrote:
>>>>>>> Ping: 
>>>>>>> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/553906.html
>>>>>>>
>>>>>>> (I'm working on rebasing the patch on top of the latest trunk which
>>>>>>> has changed some of the same code but it'd be helpful to get a go-
>>>>>>> ahead on substance the changes.  I don't expect the rebase to
>>>>>>> require any substantive modifications.)
>>>>>>>
>>>>>>> Martin
>>>>>>>
>>>>>>> On 9/14/20 4:01 PM, Martin Sebor wrote:
>>>>>>>> On 9/4/20 11:14 AM, Jason Merrill wrote:
>>>>>>>>> On 9/3/20 2:44 PM, Martin Sebor wrote:
>>>>>>>>>> On 9/1/20 1:22 PM, Jason Merrill wrote:
>>>>>>>>>>> On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
>>>>>>>>>>>> -Wplacement-new handles array indices and pointer offsets 
>>>>>>>>>>>> the same:
>>>>>>>>>>>> by adjusting them by the size of the element.  That's 
>>>>>>>>>>>> correct for
>>>>>>>>>>>> the latter but wrong for the former, causing false positives 
>>>>>>>>>>>> when
>>>>>>>>>>>> the element size is greater than one.
>>>>>>>>>>>>
>>>>>>>>>>>> In addition, the warning doesn't even attempt to handle 
>>>>>>>>>>>> arrays of
>>>>>>>>>>>> arrays.  I'm not sure if I forgot or if I simply didn't 
>>>>>>>>>>>> think of
>>>>>>>>>>>> it.
>>>>>>>>>>>>
>>>>>>>>>>>> The attached patch corrects these oversights by replacing most
>>>>>>>>>>>> of the -Wplacement-new code with a call to compute_objsize 
>>>>>>>>>>>> which
>>>>>>>>>>>> handles all this correctly (plus more), and is also better 
>>>>>>>>>>>> tested.
>>>>>>>>>>>> But even compute_objsize has bugs: it trips up while converting
>>>>>>>>>>>> wide_int to offset_int for some pointer offset ranges.  Since
>>>>>>>>>>>> handling the C++ IL required changes in this area the patch 
>>>>>>>>>>>> also
>>>>>>>>>>>> fixes that.
>>>>>>>>>>>>
>>>>>>>>>>>> For review purposes, the patch affects just the middle end.
>>>>>>>>>>>> The C++ diff pretty much just removes code from the front end.
>>>>>>>>>>>
>>>>>>>>>>> The C++ changes are OK.
>>>>>>>>>>
>>>>>>>>>> Thank you for looking at the rest as well.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> -compute_objsize (tree ptr, int ostype, access_ref *pref,
>>>>>>>>>>>> -                bitmap *visited, const vr_values *rvals /* 
>>>>>>>>>>>> = NULL */)
>>>>>>>>>>>> +compute_objsize (tree ptr, int ostype, access_ref *pref, 
>>>>>>>>>>>> bitmap *visited,
>>>>>>>>>>>> +                const vr_values *rvals)
>>>>>>>>>>>
>>>>>>>>>>> This reformatting seems unnecessary, and I prefer to keep the 
>>>>>>>>>>> comment about the default argument.
>>>>>>>>>>
>>>>>>>>>> This overload doesn't take a default argument.  (There was a 
>>>>>>>>>> stray
>>>>>>>>>> declaration of a similar function at the top of the file that had
>>>>>>>>>> one.  I've removed it.)
>>>>>>>>>
>>>>>>>>> Ah, true.
>>>>>>>>>
>>>>>>>>>>>> -      if (!size || TREE_CODE (size) != INTEGER_CST)
>>>>>>>>>>>> -       return false;
>>>>>>>>>>>  >...
>>>>>>>>>>>
>>>>>>>>>>> You change some failure cases in compute_objsize to return 
>>>>>>>>>>> success with a maximum range, while others continue to return 
>>>>>>>>>>> failure. This needs commentary about the design rationale.
>>>>>>>>>>
>>>>>>>>>> This is too much for a comment in the code but the background is
>>>>>>>>>> this: compute_objsize initially returned the object size as a 
>>>>>>>>>> constant.
>>>>>>>>>> Recently, I have enhanced it to return a range to improve 
>>>>>>>>>> warnings for
>>>>>>>>>> allocated objects.  With that, a failure can be turned into 
>>>>>>>>>> success by
>>>>>>>>>> having the function set the range to that of the largest 
>>>>>>>>>> object. That
>>>>>>>>>> should simplify the function's callers and could even improve
>>>>>>>>>> the detection of some invalid accesses.  Once this change is made
>>>>>>>>>> it might even be possible to change its return type to void.
>>>>>>>>>>
>>>>>>>>>> The change that caught your eye is necessary to make the function
>>>>>>>>>> a drop-in replacement for the C++ front end code which makes this
>>>>>>>>>> same assumption.  Without it, a number of test cases that 
>>>>>>>>>> exercise
>>>>>>>>>> VLAs fail in g++.dg/warn/Wplacement-new-size-5.C.  For example:
>>>>>>>>>>
>>>>>>>>>>    void f (int n)
>>>>>>>>>>    {
>>>>>>>>>>      char a[n];
>>>>>>>>>>      new (a - 1) int ();
>>>>>>>>>>    }
>>>>>>>>>>
>>>>>>>>>> Changing any of the other places isn't necessary for existing 
>>>>>>>>>> tests
>>>>>>>>>> to pass (and I didn't want to introduce too much churn).  But 
>>>>>>>>>> I do
>>>>>>>>>> want to change the rest of the function along the same lines 
>>>>>>>>>> at some
>>>>>>>>>> point.
>>>>>>>>>
>>>>>>>>> Please do change the other places to be consistent; better to 
>>>>>>>>> have more churn than to leave the function half-updated.  That 
>>>>>>>>> can be a separate patch if you prefer, but let's do it now 
>>>>>>>>> rather than later.
>>>>>>>>
>>>>>>>> I've made most of these changes in the other patch (also attached).
>>>>>>>> I'm quite happy with the result but it turned out to be a lot more
>>>>>>>> work than either of us expected, mostly due to the amount of 
>>>>>>>> testing.
>>>>>>>>
>>>>>>>> I've left a couple of failing cases in place mainly as reminders
>>>>>>>> to handle them better (which means I also didn't change the caller
>>>>>>>> to avoid testing for failures).  I've also added TODO notes with
>>>>>>>> reminders to handle some of the new codes more completely.
>>>>>>>>
>>>>>>>>>
>>>>>>>>>>>> +  special_array_member sam{ };
>>>>>>>>>>>
>>>>>>>>>>> sam is always set by component_ref_size, so I don't think 
>>>>>>>>>>> it's necessary to initialize it at the declaration.
>>>>>>>>>>
>>>>>>>>>> I find initializing pass-by-pointer local variables helpful but
>>>>>>>>>> I don't insist on it.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
>>>>>>>>>>>>    tree last_type = TREE_TYPE (last);
>>>>>>>>>>>>    if (TREE_CODE (last_type) != ARRAY_TYPE
>>>>>>>>>>>>        || TYPE_SIZE (last_type))
>>>>>>>>>>>> -    return size;
>>>>>>>>>>>> +    return size ? size : TYPE_SIZE_UNIT (type);
>>>>>>>>>>>
>>>>>>>>>>> This change seems to violate the comment for the function.
>>>>>>>>>>
>>>>>>>>>> By my reading (and writing) the change is covered by the first
>>>>>>>>>> sentence:
>>>>>>>>>>
>>>>>>>>>>     Returns the size of the object designated by DECL considering
>>>>>>>>>>     its initializer if it either has one or if it would not 
>>>>>>>>>> affect
>>>>>>>>>>     its size, ...
>>>>>>>>>
>>>>>>>>> OK, I see it now.
>>>>>>>>>
>>>>>>>>>> It handles a number of cases in Wplacement-new-size.C fail that
>>>>>>>>>> construct a larger object in an extern declaration of a template,
>>>>>>>>>> like this:
>>>>>>>>>>
>>>>>>>>>>    template <class> struct S { char c; };
>>>>>>>>>>    extern S<int> s;
>>>>>>>>>>
>>>>>>>>>>    void f ()
>>>>>>>>>>    {
>>>>>>>>>>      new (&s) int ();
>>>>>>>>>>    }
>>>>>>>>>>
>>>>>>>>>> I don't know why DECL_SIZE isn't set here (I don't think it can
>>>>>>>>>> be anything but equal to TYPE_SIZE, can it?) and other than 
>>>>>>>>>> struct
>>>>>>>>>> objects with a flexible array member where this identity doesn't
>>>>>>>>>> hold I can't think of others.  Am I missing something?
>>>>>>>>>
>>>>>>>>> Good question.  The attached patch should fix that, so you 
>>>>>>>>> shouldn't need the change to decl_init_size:
>>>>>>>>
>>>>>>>> I've integrated it into the bug fix.
>>>>>>>>
>>>>>>>> Besides the usual x86_64-linux bootstrap/regtest I tested both
>>>>>>>> patches by building a few packages, including Binutils/GDB, Glibc,
>>>>>>>> and  verifying no new warnings show up.
>>>>>>>>
>>>>>>>> Martin
>>>>>
>>>>>> +offset_int
>>>>>> +access_ref::size_remaining (offset_int *pmin /* = NULL */) const
>>>>>
>>>>> For the various member functions, please include the comments with 
>>>>> the definition as well as the in-class declaration.
>>>>
>>>> Only one access_ref member function is defined out-of-line: 
>>>> offset_bounded().  I've adjusted the comment and copied it above
>>>> the function definition.
>>>>
>>>>>
>>>>>> +      if (offrng[1] < offrng[0])
>>>>>
>>>>> What does it mean for the max offset to be less than the min 
>>>>> offset? I wouldn't expect that to ever happen with wide integers.
>>>>
>>>> The offset is represented in sizetype with negative values represented
>>>> as large positive values, but has to be converted to ptrdiff_t.
>>>
>>> It looks to me like the offset is offset_int, which is both signed 
>>> and big enough to hold all values of sizetype without turning large 
>>> positive values into negative values.  Where are these sign-switching 
>>> conversions happening?
>>
>> In get_offset_range in builtins.c.
> 
> Since we're converting to offset_int there, why not give the offset_int 
> the real value rather than a bogus negative value?

I don't understand the question: the real offset (in the program)
is negative when its sizetype representation is greater than
PTRDIFF_MAX.  It's worked this way for years.

Martin

>>>> These
>>>> cases come up when the unsigned offset is an ordinary range that
>>>> corresponds to an anti-range, such as here:
>>>>
>>>>    extern char a[2];
>>>>
>>>>    void f (unsigned long i)
>>>>    {
>>>>      if (i == 0)
>>>>        return;
>>>>      a[i] = 0;   // i's range is [1, -1] (i.e., [1, SIZE_MAX]
>>>>    }
>>>>
>>>>>
>>>>>> +  /* Return true if OFFRNG is bounded to a subrange of possible 
>>>>>> offset
>>>>>> +     values.  */
>>>>>> +  bool offset_bounded () const;
>>>>>
>>>>> I don't understand how you're using this.  The implementation 
>>>>> checks for the possible offset values falling outside those 
>>>>> representable by ptrdiff_t, unless the range is only a single 
>>>>> value.  And then the only use is
>>>>>
>>>>>> +  if (ref.offset_zero () || !ref.offset_bounded ())
>>>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>>>> +        "%qD declared here", ref.ref);
>>>>>> +  else if (ref.offrng[0] == ref.offrng[1])
>>>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>>>> +        "at offset %wi from %qD declared here",
>>>>>> +        ref.offrng[0].to_shwi (), ref.ref);
>>>>>> +  else
>>>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>>>> +        "at offset [%wi, %wi] from %qD declared here",
>>>>>> +        ref.offrng[0].to_shwi (), ref.offrng[1].to_shwi (), 
>>>>>> ref.ref);
>>>>>
>>>>> So if the possible offsets are all representable by ptrdiff_t, we 
>>>>> don't print the range?  The middle case also looks unreachable, 
>>>>> since offset_bounded will return false in that case.
>>>>
>>>> The function was originally named "offset_unbounded."  I changed
>>>> it to "offset_bounded" but looks like I didn't finish the job or
>>>> add any tests for it.
>>>>
>>>> The goal of conditionals is to avoid overwhelming the user with
>>>> excessive numbers that may not be meaningful or even relevant
>>>> to the warning.  I've corrected the function body, tweaked and
>>>> renamed the get_range function to get_offset_range to do a better
>>>> job of extracting ranges from the types of some nonconstant
>>>> expressions the front end passes it, and added a new test for
>>>> all this.  Attached is the new revision.
>>>>
>>>> Martin
>>>
>>
> 


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-10-07 15:19                       ` Martin Sebor
@ 2020-10-07 19:28                         ` Jason Merrill
  2020-10-07 20:11                           ` Martin Sebor
  0 siblings, 1 reply; 28+ messages in thread
From: Jason Merrill @ 2020-10-07 19:28 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On 10/7/20 11:19 AM, Martin Sebor wrote:
> On 10/7/20 9:07 AM, Jason Merrill wrote:
>> On 10/7/20 10:42 AM, Martin Sebor wrote:
>>> On 10/7/20 8:26 AM, Jason Merrill wrote:
>>>> On 9/28/20 6:01 PM, Martin Sebor wrote:
>>>>> On 9/25/20 11:17 PM, Jason Merrill wrote:
>>>>>> On 9/22/20 4:05 PM, Martin Sebor wrote:
>>>>>>> The rebased and retested patches are attached.
>>>>>>>
>>>>>>> On 9/21/20 3:17 PM, Martin Sebor wrote:
>>>>>>>> Ping: 
>>>>>>>> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/553906.html 
>>>>>>>>
>>>>>>>>
>>>>>>>> (I'm working on rebasing the patch on top of the latest trunk which
>>>>>>>> has changed some of the same code but it'd be helpful to get a go-
>>>>>>>> ahead on substance the changes.  I don't expect the rebase to
>>>>>>>> require any substantive modifications.)
>>>>>>>>
>>>>>>>> Martin
>>>>>>>>
>>>>>>>> On 9/14/20 4:01 PM, Martin Sebor wrote:
>>>>>>>>> On 9/4/20 11:14 AM, Jason Merrill wrote:
>>>>>>>>>> On 9/3/20 2:44 PM, Martin Sebor wrote:
>>>>>>>>>>> On 9/1/20 1:22 PM, Jason Merrill wrote:
>>>>>>>>>>>> On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
>>>>>>>>>>>>> -Wplacement-new handles array indices and pointer offsets 
>>>>>>>>>>>>> the same:
>>>>>>>>>>>>> by adjusting them by the size of the element.  That's 
>>>>>>>>>>>>> correct for
>>>>>>>>>>>>> the latter but wrong for the former, causing false 
>>>>>>>>>>>>> positives when
>>>>>>>>>>>>> the element size is greater than one.
>>>>>>>>>>>>>
>>>>>>>>>>>>> In addition, the warning doesn't even attempt to handle 
>>>>>>>>>>>>> arrays of
>>>>>>>>>>>>> arrays.  I'm not sure if I forgot or if I simply didn't 
>>>>>>>>>>>>> think of
>>>>>>>>>>>>> it.
>>>>>>>>>>>>>
>>>>>>>>>>>>> The attached patch corrects these oversights by replacing most
>>>>>>>>>>>>> of the -Wplacement-new code with a call to compute_objsize 
>>>>>>>>>>>>> which
>>>>>>>>>>>>> handles all this correctly (plus more), and is also better 
>>>>>>>>>>>>> tested.
>>>>>>>>>>>>> But even compute_objsize has bugs: it trips up while 
>>>>>>>>>>>>> converting
>>>>>>>>>>>>> wide_int to offset_int for some pointer offset ranges.  Since
>>>>>>>>>>>>> handling the C++ IL required changes in this area the patch 
>>>>>>>>>>>>> also
>>>>>>>>>>>>> fixes that.
>>>>>>>>>>>>>
>>>>>>>>>>>>> For review purposes, the patch affects just the middle end.
>>>>>>>>>>>>> The C++ diff pretty much just removes code from the front end.
>>>>>>>>>>>>
>>>>>>>>>>>> The C++ changes are OK.
>>>>>>>>>>>
>>>>>>>>>>> Thank you for looking at the rest as well.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>> -compute_objsize (tree ptr, int ostype, access_ref *pref,
>>>>>>>>>>>>> -                bitmap *visited, const vr_values *rvals /* 
>>>>>>>>>>>>> = NULL */)
>>>>>>>>>>>>> +compute_objsize (tree ptr, int ostype, access_ref *pref, 
>>>>>>>>>>>>> bitmap *visited,
>>>>>>>>>>>>> +                const vr_values *rvals)
>>>>>>>>>>>>
>>>>>>>>>>>> This reformatting seems unnecessary, and I prefer to keep 
>>>>>>>>>>>> the comment about the default argument.
>>>>>>>>>>>
>>>>>>>>>>> This overload doesn't take a default argument.  (There was a 
>>>>>>>>>>> stray
>>>>>>>>>>> declaration of a similar function at the top of the file that 
>>>>>>>>>>> had
>>>>>>>>>>> one.  I've removed it.)
>>>>>>>>>>
>>>>>>>>>> Ah, true.
>>>>>>>>>>
>>>>>>>>>>>>> -      if (!size || TREE_CODE (size) != INTEGER_CST)
>>>>>>>>>>>>> -       return false;
>>>>>>>>>>>>  >...
>>>>>>>>>>>>
>>>>>>>>>>>> You change some failure cases in compute_objsize to return 
>>>>>>>>>>>> success with a maximum range, while others continue to 
>>>>>>>>>>>> return failure. This needs commentary about the design 
>>>>>>>>>>>> rationale.
>>>>>>>>>>>
>>>>>>>>>>> This is too much for a comment in the code but the background is
>>>>>>>>>>> this: compute_objsize initially returned the object size as a 
>>>>>>>>>>> constant.
>>>>>>>>>>> Recently, I have enhanced it to return a range to improve 
>>>>>>>>>>> warnings for
>>>>>>>>>>> allocated objects.  With that, a failure can be turned into 
>>>>>>>>>>> success by
>>>>>>>>>>> having the function set the range to that of the largest 
>>>>>>>>>>> object. That
>>>>>>>>>>> should simplify the function's callers and could even improve
>>>>>>>>>>> the detection of some invalid accesses.  Once this change is 
>>>>>>>>>>> made
>>>>>>>>>>> it might even be possible to change its return type to void.
>>>>>>>>>>>
>>>>>>>>>>> The change that caught your eye is necessary to make the 
>>>>>>>>>>> function
>>>>>>>>>>> a drop-in replacement for the C++ front end code which makes 
>>>>>>>>>>> this
>>>>>>>>>>> same assumption.  Without it, a number of test cases that 
>>>>>>>>>>> exercise
>>>>>>>>>>> VLAs fail in g++.dg/warn/Wplacement-new-size-5.C.  For example:
>>>>>>>>>>>
>>>>>>>>>>>    void f (int n)
>>>>>>>>>>>    {
>>>>>>>>>>>      char a[n];
>>>>>>>>>>>      new (a - 1) int ();
>>>>>>>>>>>    }
>>>>>>>>>>>
>>>>>>>>>>> Changing any of the other places isn't necessary for existing 
>>>>>>>>>>> tests
>>>>>>>>>>> to pass (and I didn't want to introduce too much churn).  But 
>>>>>>>>>>> I do
>>>>>>>>>>> want to change the rest of the function along the same lines 
>>>>>>>>>>> at some
>>>>>>>>>>> point.
>>>>>>>>>>
>>>>>>>>>> Please do change the other places to be consistent; better to 
>>>>>>>>>> have more churn than to leave the function half-updated.  That 
>>>>>>>>>> can be a separate patch if you prefer, but let's do it now 
>>>>>>>>>> rather than later.
>>>>>>>>>
>>>>>>>>> I've made most of these changes in the other patch (also 
>>>>>>>>> attached).
>>>>>>>>> I'm quite happy with the result but it turned out to be a lot more
>>>>>>>>> work than either of us expected, mostly due to the amount of 
>>>>>>>>> testing.
>>>>>>>>>
>>>>>>>>> I've left a couple of failing cases in place mainly as reminders
>>>>>>>>> to handle them better (which means I also didn't change the caller
>>>>>>>>> to avoid testing for failures).  I've also added TODO notes with
>>>>>>>>> reminders to handle some of the new codes more completely.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>>>> +  special_array_member sam{ };
>>>>>>>>>>>>
>>>>>>>>>>>> sam is always set by component_ref_size, so I don't think 
>>>>>>>>>>>> it's necessary to initialize it at the declaration.
>>>>>>>>>>>
>>>>>>>>>>> I find initializing pass-by-pointer local variables helpful but
>>>>>>>>>>> I don't insist on it.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
>>>>>>>>>>>>>    tree last_type = TREE_TYPE (last);
>>>>>>>>>>>>>    if (TREE_CODE (last_type) != ARRAY_TYPE
>>>>>>>>>>>>>        || TYPE_SIZE (last_type))
>>>>>>>>>>>>> -    return size;
>>>>>>>>>>>>> +    return size ? size : TYPE_SIZE_UNIT (type);
>>>>>>>>>>>>
>>>>>>>>>>>> This change seems to violate the comment for the function.
>>>>>>>>>>>
>>>>>>>>>>> By my reading (and writing) the change is covered by the first
>>>>>>>>>>> sentence:
>>>>>>>>>>>
>>>>>>>>>>>     Returns the size of the object designated by DECL 
>>>>>>>>>>> considering
>>>>>>>>>>>     its initializer if it either has one or if it would not 
>>>>>>>>>>> affect
>>>>>>>>>>>     its size, ...
>>>>>>>>>>
>>>>>>>>>> OK, I see it now.
>>>>>>>>>>
>>>>>>>>>>> It handles a number of cases in Wplacement-new-size.C fail that
>>>>>>>>>>> construct a larger object in an extern declaration of a 
>>>>>>>>>>> template,
>>>>>>>>>>> like this:
>>>>>>>>>>>
>>>>>>>>>>>    template <class> struct S { char c; };
>>>>>>>>>>>    extern S<int> s;
>>>>>>>>>>>
>>>>>>>>>>>    void f ()
>>>>>>>>>>>    {
>>>>>>>>>>>      new (&s) int ();
>>>>>>>>>>>    }
>>>>>>>>>>>
>>>>>>>>>>> I don't know why DECL_SIZE isn't set here (I don't think it can
>>>>>>>>>>> be anything but equal to TYPE_SIZE, can it?) and other than 
>>>>>>>>>>> struct
>>>>>>>>>>> objects with a flexible array member where this identity doesn't
>>>>>>>>>>> hold I can't think of others.  Am I missing something?
>>>>>>>>>>
>>>>>>>>>> Good question.  The attached patch should fix that, so you 
>>>>>>>>>> shouldn't need the change to decl_init_size:
>>>>>>>>>
>>>>>>>>> I've integrated it into the bug fix.
>>>>>>>>>
>>>>>>>>> Besides the usual x86_64-linux bootstrap/regtest I tested both
>>>>>>>>> patches by building a few packages, including Binutils/GDB, Glibc,
>>>>>>>>> and  verifying no new warnings show up.
>>>>>>>>>
>>>>>>>>> Martin
>>>>>>
>>>>>>> +offset_int
>>>>>>> +access_ref::size_remaining (offset_int *pmin /* = NULL */) const
>>>>>>
>>>>>> For the various member functions, please include the comments with 
>>>>>> the definition as well as the in-class declaration.
>>>>>
>>>>> Only one access_ref member function is defined out-of-line: 
>>>>> offset_bounded().  I've adjusted the comment and copied it above
>>>>> the function definition.
>>>>>
>>>>>>
>>>>>>> +      if (offrng[1] < offrng[0])
>>>>>>
>>>>>> What does it mean for the max offset to be less than the min 
>>>>>> offset? I wouldn't expect that to ever happen with wide integers.
>>>>>
>>>>> The offset is represented in sizetype with negative values represented
>>>>> as large positive values, but has to be converted to ptrdiff_t.
>>>>
>>>> It looks to me like the offset is offset_int, which is both signed 
>>>> and big enough to hold all values of sizetype without turning large 
>>>> positive values into negative values.  Where are these 
>>>> sign-switching conversions happening?
>>>
>>> In get_offset_range in builtins.c.
>>
>> Since we're converting to offset_int there, why not give the 
>> offset_int the real value rather than a bogus negative value?
> 
> I don't understand the question: the real offset (in the program)
> is negative when its sizetype representation is greater than
> PTRDIFF_MAX.  It's worked this way for years.

OK, then we're back to my original question: why is the max offset less 
than the min offset?  If the range includes negative values, why isn't 
the more-negative one the minimum?

>>>>> These
>>>>> cases come up when the unsigned offset is an ordinary range that
>>>>> corresponds to an anti-range, such as here:
>>>>>
>>>>>    extern char a[2];
>>>>>
>>>>>    void f (unsigned long i)
>>>>>    {
>>>>>      if (i == 0)
>>>>>        return;
>>>>>      a[i] = 0;   // i's range is [1, -1] (i.e., [1, SIZE_MAX]
>>>>>    }
>>>>>
>>>>>>
>>>>>>> +  /* Return true if OFFRNG is bounded to a subrange of possible 
>>>>>>> offset
>>>>>>> +     values.  */
>>>>>>> +  bool offset_bounded () const;
>>>>>>
>>>>>> I don't understand how you're using this.  The implementation 
>>>>>> checks for the possible offset values falling outside those 
>>>>>> representable by ptrdiff_t, unless the range is only a single 
>>>>>> value.  And then the only use is
>>>>>>
>>>>>>> +  if (ref.offset_zero () || !ref.offset_bounded ())
>>>>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>>>>> +        "%qD declared here", ref.ref);
>>>>>>> +  else if (ref.offrng[0] == ref.offrng[1])
>>>>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>>>>> +        "at offset %wi from %qD declared here",
>>>>>>> +        ref.offrng[0].to_shwi (), ref.ref);
>>>>>>> +  else
>>>>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>>>>> +        "at offset [%wi, %wi] from %qD declared here",
>>>>>>> +        ref.offrng[0].to_shwi (), ref.offrng[1].to_shwi (), 
>>>>>>> ref.ref);
>>>>>>
>>>>>> So if the possible offsets are all representable by ptrdiff_t, we 
>>>>>> don't print the range?  The middle case also looks unreachable, 
>>>>>> since offset_bounded will return false in that case.
>>>>>
>>>>> The function was originally named "offset_unbounded."  I changed
>>>>> it to "offset_bounded" but looks like I didn't finish the job or
>>>>> add any tests for it.
>>>>>
>>>>> The goal of conditionals is to avoid overwhelming the user with
>>>>> excessive numbers that may not be meaningful or even relevant
>>>>> to the warning.  I've corrected the function body, tweaked and
>>>>> renamed the get_range function to get_offset_range to do a better
>>>>> job of extracting ranges from the types of some nonconstant
>>>>> expressions the front end passes it, and added a new test for
>>>>> all this.  Attached is the new revision.
>>>>>
>>>>> Martin
>>>>
>>>
>>
> 


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-10-07 19:28                         ` Jason Merrill
@ 2020-10-07 20:11                           ` Martin Sebor
  2020-10-07 21:01                             ` Jason Merrill
  0 siblings, 1 reply; 28+ messages in thread
From: Martin Sebor @ 2020-10-07 20:11 UTC (permalink / raw)
  To: Jason Merrill, gcc-patches

On 10/7/20 1:28 PM, Jason Merrill wrote:
> On 10/7/20 11:19 AM, Martin Sebor wrote:
>> On 10/7/20 9:07 AM, Jason Merrill wrote:
>>> On 10/7/20 10:42 AM, Martin Sebor wrote:
>>>> On 10/7/20 8:26 AM, Jason Merrill wrote:
>>>>> On 9/28/20 6:01 PM, Martin Sebor wrote:
>>>>>> On 9/25/20 11:17 PM, Jason Merrill wrote:
>>>>>>> On 9/22/20 4:05 PM, Martin Sebor wrote:
>>>>>>>> The rebased and retested patches are attached.
>>>>>>>>
>>>>>>>> On 9/21/20 3:17 PM, Martin Sebor wrote:
>>>>>>>>> Ping: 
>>>>>>>>> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/553906.html 
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> (I'm working on rebasing the patch on top of the latest trunk 
>>>>>>>>> which
>>>>>>>>> has changed some of the same code but it'd be helpful to get a go-
>>>>>>>>> ahead on substance the changes.  I don't expect the rebase to
>>>>>>>>> require any substantive modifications.)
>>>>>>>>>
>>>>>>>>> Martin
>>>>>>>>>
>>>>>>>>> On 9/14/20 4:01 PM, Martin Sebor wrote:
>>>>>>>>>> On 9/4/20 11:14 AM, Jason Merrill wrote:
>>>>>>>>>>> On 9/3/20 2:44 PM, Martin Sebor wrote:
>>>>>>>>>>>> On 9/1/20 1:22 PM, Jason Merrill wrote:
>>>>>>>>>>>>> On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
>>>>>>>>>>>>>> -Wplacement-new handles array indices and pointer offsets 
>>>>>>>>>>>>>> the same:
>>>>>>>>>>>>>> by adjusting them by the size of the element.  That's 
>>>>>>>>>>>>>> correct for
>>>>>>>>>>>>>> the latter but wrong for the former, causing false 
>>>>>>>>>>>>>> positives when
>>>>>>>>>>>>>> the element size is greater than one.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> In addition, the warning doesn't even attempt to handle 
>>>>>>>>>>>>>> arrays of
>>>>>>>>>>>>>> arrays.  I'm not sure if I forgot or if I simply didn't 
>>>>>>>>>>>>>> think of
>>>>>>>>>>>>>> it.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> The attached patch corrects these oversights by replacing 
>>>>>>>>>>>>>> most
>>>>>>>>>>>>>> of the -Wplacement-new code with a call to compute_objsize 
>>>>>>>>>>>>>> which
>>>>>>>>>>>>>> handles all this correctly (plus more), and is also better 
>>>>>>>>>>>>>> tested.
>>>>>>>>>>>>>> But even compute_objsize has bugs: it trips up while 
>>>>>>>>>>>>>> converting
>>>>>>>>>>>>>> wide_int to offset_int for some pointer offset ranges.  Since
>>>>>>>>>>>>>> handling the C++ IL required changes in this area the 
>>>>>>>>>>>>>> patch also
>>>>>>>>>>>>>> fixes that.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> For review purposes, the patch affects just the middle end.
>>>>>>>>>>>>>> The C++ diff pretty much just removes code from the front 
>>>>>>>>>>>>>> end.
>>>>>>>>>>>>>
>>>>>>>>>>>>> The C++ changes are OK.
>>>>>>>>>>>>
>>>>>>>>>>>> Thank you for looking at the rest as well.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> -compute_objsize (tree ptr, int ostype, access_ref *pref,
>>>>>>>>>>>>>> -                bitmap *visited, const vr_values *rvals 
>>>>>>>>>>>>>> /* = NULL */)
>>>>>>>>>>>>>> +compute_objsize (tree ptr, int ostype, access_ref *pref, 
>>>>>>>>>>>>>> bitmap *visited,
>>>>>>>>>>>>>> +                const vr_values *rvals)
>>>>>>>>>>>>>
>>>>>>>>>>>>> This reformatting seems unnecessary, and I prefer to keep 
>>>>>>>>>>>>> the comment about the default argument.
>>>>>>>>>>>>
>>>>>>>>>>>> This overload doesn't take a default argument.  (There was a 
>>>>>>>>>>>> stray
>>>>>>>>>>>> declaration of a similar function at the top of the file 
>>>>>>>>>>>> that had
>>>>>>>>>>>> one.  I've removed it.)
>>>>>>>>>>>
>>>>>>>>>>> Ah, true.
>>>>>>>>>>>
>>>>>>>>>>>>>> -      if (!size || TREE_CODE (size) != INTEGER_CST)
>>>>>>>>>>>>>> -       return false;
>>>>>>>>>>>>>  >...
>>>>>>>>>>>>>
>>>>>>>>>>>>> You change some failure cases in compute_objsize to return 
>>>>>>>>>>>>> success with a maximum range, while others continue to 
>>>>>>>>>>>>> return failure. This needs commentary about the design 
>>>>>>>>>>>>> rationale.
>>>>>>>>>>>>
>>>>>>>>>>>> This is too much for a comment in the code but the 
>>>>>>>>>>>> background is
>>>>>>>>>>>> this: compute_objsize initially returned the object size as 
>>>>>>>>>>>> a constant.
>>>>>>>>>>>> Recently, I have enhanced it to return a range to improve 
>>>>>>>>>>>> warnings for
>>>>>>>>>>>> allocated objects.  With that, a failure can be turned into 
>>>>>>>>>>>> success by
>>>>>>>>>>>> having the function set the range to that of the largest 
>>>>>>>>>>>> object. That
>>>>>>>>>>>> should simplify the function's callers and could even improve
>>>>>>>>>>>> the detection of some invalid accesses.  Once this change is 
>>>>>>>>>>>> made
>>>>>>>>>>>> it might even be possible to change its return type to void.
>>>>>>>>>>>>
>>>>>>>>>>>> The change that caught your eye is necessary to make the 
>>>>>>>>>>>> function
>>>>>>>>>>>> a drop-in replacement for the C++ front end code which makes 
>>>>>>>>>>>> this
>>>>>>>>>>>> same assumption.  Without it, a number of test cases that 
>>>>>>>>>>>> exercise
>>>>>>>>>>>> VLAs fail in g++.dg/warn/Wplacement-new-size-5.C.  For example:
>>>>>>>>>>>>
>>>>>>>>>>>>    void f (int n)
>>>>>>>>>>>>    {
>>>>>>>>>>>>      char a[n];
>>>>>>>>>>>>      new (a - 1) int ();
>>>>>>>>>>>>    }
>>>>>>>>>>>>
>>>>>>>>>>>> Changing any of the other places isn't necessary for 
>>>>>>>>>>>> existing tests
>>>>>>>>>>>> to pass (and I didn't want to introduce too much churn).  
>>>>>>>>>>>> But I do
>>>>>>>>>>>> want to change the rest of the function along the same lines 
>>>>>>>>>>>> at some
>>>>>>>>>>>> point.
>>>>>>>>>>>
>>>>>>>>>>> Please do change the other places to be consistent; better to 
>>>>>>>>>>> have more churn than to leave the function half-updated.  
>>>>>>>>>>> That can be a separate patch if you prefer, but let's do it 
>>>>>>>>>>> now rather than later.
>>>>>>>>>>
>>>>>>>>>> I've made most of these changes in the other patch (also 
>>>>>>>>>> attached).
>>>>>>>>>> I'm quite happy with the result but it turned out to be a lot 
>>>>>>>>>> more
>>>>>>>>>> work than either of us expected, mostly due to the amount of 
>>>>>>>>>> testing.
>>>>>>>>>>
>>>>>>>>>> I've left a couple of failing cases in place mainly as reminders
>>>>>>>>>> to handle them better (which means I also didn't change the 
>>>>>>>>>> caller
>>>>>>>>>> to avoid testing for failures).  I've also added TODO notes with
>>>>>>>>>> reminders to handle some of the new codes more completely.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>>>> +  special_array_member sam{ };
>>>>>>>>>>>>>
>>>>>>>>>>>>> sam is always set by component_ref_size, so I don't think 
>>>>>>>>>>>>> it's necessary to initialize it at the declaration.
>>>>>>>>>>>>
>>>>>>>>>>>> I find initializing pass-by-pointer local variables helpful but
>>>>>>>>>>>> I don't insist on it.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
>>>>>>>>>>>>>>    tree last_type = TREE_TYPE (last);
>>>>>>>>>>>>>>    if (TREE_CODE (last_type) != ARRAY_TYPE
>>>>>>>>>>>>>>        || TYPE_SIZE (last_type))
>>>>>>>>>>>>>> -    return size;
>>>>>>>>>>>>>> +    return size ? size : TYPE_SIZE_UNIT (type);
>>>>>>>>>>>>>
>>>>>>>>>>>>> This change seems to violate the comment for the function.
>>>>>>>>>>>>
>>>>>>>>>>>> By my reading (and writing) the change is covered by the first
>>>>>>>>>>>> sentence:
>>>>>>>>>>>>
>>>>>>>>>>>>     Returns the size of the object designated by DECL 
>>>>>>>>>>>> considering
>>>>>>>>>>>>     its initializer if it either has one or if it would not 
>>>>>>>>>>>> affect
>>>>>>>>>>>>     its size, ...
>>>>>>>>>>>
>>>>>>>>>>> OK, I see it now.
>>>>>>>>>>>
>>>>>>>>>>>> It handles a number of cases in Wplacement-new-size.C fail that
>>>>>>>>>>>> construct a larger object in an extern declaration of a 
>>>>>>>>>>>> template,
>>>>>>>>>>>> like this:
>>>>>>>>>>>>
>>>>>>>>>>>>    template <class> struct S { char c; };
>>>>>>>>>>>>    extern S<int> s;
>>>>>>>>>>>>
>>>>>>>>>>>>    void f ()
>>>>>>>>>>>>    {
>>>>>>>>>>>>      new (&s) int ();
>>>>>>>>>>>>    }
>>>>>>>>>>>>
>>>>>>>>>>>> I don't know why DECL_SIZE isn't set here (I don't think it can
>>>>>>>>>>>> be anything but equal to TYPE_SIZE, can it?) and other than 
>>>>>>>>>>>> struct
>>>>>>>>>>>> objects with a flexible array member where this identity 
>>>>>>>>>>>> doesn't
>>>>>>>>>>>> hold I can't think of others.  Am I missing something?
>>>>>>>>>>>
>>>>>>>>>>> Good question.  The attached patch should fix that, so you 
>>>>>>>>>>> shouldn't need the change to decl_init_size:
>>>>>>>>>>
>>>>>>>>>> I've integrated it into the bug fix.
>>>>>>>>>>
>>>>>>>>>> Besides the usual x86_64-linux bootstrap/regtest I tested both
>>>>>>>>>> patches by building a few packages, including Binutils/GDB, 
>>>>>>>>>> Glibc,
>>>>>>>>>> and  verifying no new warnings show up.
>>>>>>>>>>
>>>>>>>>>> Martin
>>>>>>>
>>>>>>>> +offset_int
>>>>>>>> +access_ref::size_remaining (offset_int *pmin /* = NULL */) const
>>>>>>>
>>>>>>> For the various member functions, please include the comments 
>>>>>>> with the definition as well as the in-class declaration.
>>>>>>
>>>>>> Only one access_ref member function is defined out-of-line: 
>>>>>> offset_bounded().  I've adjusted the comment and copied it above
>>>>>> the function definition.
>>>>>>
>>>>>>>
>>>>>>>> +      if (offrng[1] < offrng[0])
>>>>>>>
>>>>>>> What does it mean for the max offset to be less than the min 
>>>>>>> offset? I wouldn't expect that to ever happen with wide integers.
>>>>>>
>>>>>> The offset is represented in sizetype with negative values 
>>>>>> represented
>>>>>> as large positive values, but has to be converted to ptrdiff_t.
>>>>>
>>>>> It looks to me like the offset is offset_int, which is both signed 
>>>>> and big enough to hold all values of sizetype without turning large 
>>>>> positive values into negative values.  Where are these 
>>>>> sign-switching conversions happening?
>>>>
>>>> In get_offset_range in builtins.c.
>>>
>>> Since we're converting to offset_int there, why not give the 
>>> offset_int the real value rather than a bogus negative value?
>>
>> I don't understand the question: the real offset (in the program)
>> is negative when its sizetype representation is greater than
>> PTRDIFF_MAX.  It's worked this way for years.
> 
> OK, then we're back to my original question: why is the max offset less 
> than the min offset?  If the range includes negative values, why isn't 
> the more-negative one the minimum?

I showed a simple example where it happens:

    extern char a[2];

    void f (unsigned long i)
    {
      if (i == 0)
        return;
      a[i] = 0;   // i's range is [1, -1] (i.e., [1, SIZE_MAX])
    }

The "negative" offset can't be the minimum because it would include
zero which is the one value the range excludes.  It's effectively
an anti-range represented as an inverted range.

What is your concern with this?  That something isn't right?  Do
you want me to do something differently?  The code will change
again as soon as we introduce the new Ranger API into it.  I'd
like to make progress on it (and other things that depend on it)
but I've been holding off until this is approved.  I believe it's
a good improvement already but it's far from the last word.

Martin

>>>>>> These
>>>>>> cases come up when the unsigned offset is an ordinary range that
>>>>>> corresponds to an anti-range, such as here:
>>>>>>
>>>>>>    extern char a[2];
>>>>>>
>>>>>>    void f (unsigned long i)
>>>>>>    {
>>>>>>      if (i == 0)
>>>>>>        return;
>>>>>>      a[i] = 0;   // i's range is [1, -1] (i.e., [1, SIZE_MAX]
>>>>>>    }
>>>>>>
>>>>>>>
>>>>>>>> +  /* Return true if OFFRNG is bounded to a subrange of possible 
>>>>>>>> offset
>>>>>>>> +     values.  */
>>>>>>>> +  bool offset_bounded () const;
>>>>>>>
>>>>>>> I don't understand how you're using this.  The implementation 
>>>>>>> checks for the possible offset values falling outside those 
>>>>>>> representable by ptrdiff_t, unless the range is only a single 
>>>>>>> value.  And then the only use is
>>>>>>>
>>>>>>>> +  if (ref.offset_zero () || !ref.offset_bounded ())
>>>>>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>>>>>> +        "%qD declared here", ref.ref);
>>>>>>>> +  else if (ref.offrng[0] == ref.offrng[1])
>>>>>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>>>>>> +        "at offset %wi from %qD declared here",
>>>>>>>> +        ref.offrng[0].to_shwi (), ref.ref);
>>>>>>>> +  else
>>>>>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>>>>>> +        "at offset [%wi, %wi] from %qD declared here",
>>>>>>>> +        ref.offrng[0].to_shwi (), ref.offrng[1].to_shwi (), 
>>>>>>>> ref.ref);
>>>>>>>
>>>>>>> So if the possible offsets are all representable by ptrdiff_t, we 
>>>>>>> don't print the range?  The middle case also looks unreachable, 
>>>>>>> since offset_bounded will return false in that case.
>>>>>>
>>>>>> The function was originally named "offset_unbounded."  I changed
>>>>>> it to "offset_bounded" but looks like I didn't finish the job or
>>>>>> add any tests for it.
>>>>>>
>>>>>> The goal of conditionals is to avoid overwhelming the user with
>>>>>> excessive numbers that may not be meaningful or even relevant
>>>>>> to the warning.  I've corrected the function body, tweaked and
>>>>>> renamed the get_range function to get_offset_range to do a better
>>>>>> job of extracting ranges from the types of some nonconstant
>>>>>> expressions the front end passes it, and added a new test for
>>>>>> all this.  Attached is the new revision.
>>>>>>
>>>>>> Martin
>>>>>
>>>>
>>>
>>
> 


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-10-07 20:11                           ` Martin Sebor
@ 2020-10-07 21:01                             ` Jason Merrill
  2020-10-08 19:18                               ` Martin Sebor
  0 siblings, 1 reply; 28+ messages in thread
From: Jason Merrill @ 2020-10-07 21:01 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On 10/7/20 4:11 PM, Martin Sebor wrote:
> On 10/7/20 1:28 PM, Jason Merrill wrote:
>> On 10/7/20 11:19 AM, Martin Sebor wrote:
>>> On 10/7/20 9:07 AM, Jason Merrill wrote:
>>>> On 10/7/20 10:42 AM, Martin Sebor wrote:
>>>>> On 10/7/20 8:26 AM, Jason Merrill wrote:
>>>>>> On 9/28/20 6:01 PM, Martin Sebor wrote:
>>>>>>> On 9/25/20 11:17 PM, Jason Merrill wrote:
>>>>>>>> On 9/22/20 4:05 PM, Martin Sebor wrote:
>>>>>>>>> The rebased and retested patches are attached.
>>>>>>>>>
>>>>>>>>> On 9/21/20 3:17 PM, Martin Sebor wrote:
>>>>>>>>>> Ping: 
>>>>>>>>>> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/553906.html 
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> (I'm working on rebasing the patch on top of the latest trunk 
>>>>>>>>>> which
>>>>>>>>>> has changed some of the same code but it'd be helpful to get a 
>>>>>>>>>> go-
>>>>>>>>>> ahead on substance the changes.  I don't expect the rebase to
>>>>>>>>>> require any substantive modifications.)
>>>>>>>>>>
>>>>>>>>>> Martin
>>>>>>>>>>
>>>>>>>>>> On 9/14/20 4:01 PM, Martin Sebor wrote:
>>>>>>>>>>> On 9/4/20 11:14 AM, Jason Merrill wrote:
>>>>>>>>>>>> On 9/3/20 2:44 PM, Martin Sebor wrote:
>>>>>>>>>>>>> On 9/1/20 1:22 PM, Jason Merrill wrote:
>>>>>>>>>>>>>> On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
>>>>>>>>>>>>>>> -Wplacement-new handles array indices and pointer offsets 
>>>>>>>>>>>>>>> the same:
>>>>>>>>>>>>>>> by adjusting them by the size of the element.  That's 
>>>>>>>>>>>>>>> correct for
>>>>>>>>>>>>>>> the latter but wrong for the former, causing false 
>>>>>>>>>>>>>>> positives when
>>>>>>>>>>>>>>> the element size is greater than one.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> In addition, the warning doesn't even attempt to handle 
>>>>>>>>>>>>>>> arrays of
>>>>>>>>>>>>>>> arrays.  I'm not sure if I forgot or if I simply didn't 
>>>>>>>>>>>>>>> think of
>>>>>>>>>>>>>>> it.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> The attached patch corrects these oversights by replacing 
>>>>>>>>>>>>>>> most
>>>>>>>>>>>>>>> of the -Wplacement-new code with a call to 
>>>>>>>>>>>>>>> compute_objsize which
>>>>>>>>>>>>>>> handles all this correctly (plus more), and is also 
>>>>>>>>>>>>>>> better tested.
>>>>>>>>>>>>>>> But even compute_objsize has bugs: it trips up while 
>>>>>>>>>>>>>>> converting
>>>>>>>>>>>>>>> wide_int to offset_int for some pointer offset ranges.  
>>>>>>>>>>>>>>> Since
>>>>>>>>>>>>>>> handling the C++ IL required changes in this area the 
>>>>>>>>>>>>>>> patch also
>>>>>>>>>>>>>>> fixes that.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> For review purposes, the patch affects just the middle end.
>>>>>>>>>>>>>>> The C++ diff pretty much just removes code from the front 
>>>>>>>>>>>>>>> end.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> The C++ changes are OK.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Thank you for looking at the rest as well.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> -compute_objsize (tree ptr, int ostype, access_ref *pref,
>>>>>>>>>>>>>>> -                bitmap *visited, const vr_values *rvals 
>>>>>>>>>>>>>>> /* = NULL */)
>>>>>>>>>>>>>>> +compute_objsize (tree ptr, int ostype, access_ref *pref, 
>>>>>>>>>>>>>>> bitmap *visited,
>>>>>>>>>>>>>>> +                const vr_values *rvals)
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> This reformatting seems unnecessary, and I prefer to keep 
>>>>>>>>>>>>>> the comment about the default argument.
>>>>>>>>>>>>>
>>>>>>>>>>>>> This overload doesn't take a default argument.  (There was 
>>>>>>>>>>>>> a stray
>>>>>>>>>>>>> declaration of a similar function at the top of the file 
>>>>>>>>>>>>> that had
>>>>>>>>>>>>> one.  I've removed it.)
>>>>>>>>>>>>
>>>>>>>>>>>> Ah, true.
>>>>>>>>>>>>
>>>>>>>>>>>>>>> -      if (!size || TREE_CODE (size) != INTEGER_CST)
>>>>>>>>>>>>>>> -       return false;
>>>>>>>>>>>>>>  >...
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> You change some failure cases in compute_objsize to return 
>>>>>>>>>>>>>> success with a maximum range, while others continue to 
>>>>>>>>>>>>>> return failure. This needs commentary about the design 
>>>>>>>>>>>>>> rationale.
>>>>>>>>>>>>>
>>>>>>>>>>>>> This is too much for a comment in the code but the 
>>>>>>>>>>>>> background is
>>>>>>>>>>>>> this: compute_objsize initially returned the object size as 
>>>>>>>>>>>>> a constant.
>>>>>>>>>>>>> Recently, I have enhanced it to return a range to improve 
>>>>>>>>>>>>> warnings for
>>>>>>>>>>>>> allocated objects.  With that, a failure can be turned into 
>>>>>>>>>>>>> success by
>>>>>>>>>>>>> having the function set the range to that of the largest 
>>>>>>>>>>>>> object. That
>>>>>>>>>>>>> should simplify the function's callers and could even improve
>>>>>>>>>>>>> the detection of some invalid accesses.  Once this change 
>>>>>>>>>>>>> is made
>>>>>>>>>>>>> it might even be possible to change its return type to void.
>>>>>>>>>>>>>
>>>>>>>>>>>>> The change that caught your eye is necessary to make the 
>>>>>>>>>>>>> function
>>>>>>>>>>>>> a drop-in replacement for the C++ front end code which 
>>>>>>>>>>>>> makes this
>>>>>>>>>>>>> same assumption.  Without it, a number of test cases that 
>>>>>>>>>>>>> exercise
>>>>>>>>>>>>> VLAs fail in g++.dg/warn/Wplacement-new-size-5.C.  For 
>>>>>>>>>>>>> example:
>>>>>>>>>>>>>
>>>>>>>>>>>>>    void f (int n)
>>>>>>>>>>>>>    {
>>>>>>>>>>>>>      char a[n];
>>>>>>>>>>>>>      new (a - 1) int ();
>>>>>>>>>>>>>    }
>>>>>>>>>>>>>
>>>>>>>>>>>>> Changing any of the other places isn't necessary for 
>>>>>>>>>>>>> existing tests
>>>>>>>>>>>>> to pass (and I didn't want to introduce too much churn). 
>>>>>>>>>>>>> But I do
>>>>>>>>>>>>> want to change the rest of the function along the same 
>>>>>>>>>>>>> lines at some
>>>>>>>>>>>>> point.
>>>>>>>>>>>>
>>>>>>>>>>>> Please do change the other places to be consistent; better 
>>>>>>>>>>>> to have more churn than to leave the function half-updated. 
>>>>>>>>>>>> That can be a separate patch if you prefer, but let's do it 
>>>>>>>>>>>> now rather than later.
>>>>>>>>>>>
>>>>>>>>>>> I've made most of these changes in the other patch (also 
>>>>>>>>>>> attached).
>>>>>>>>>>> I'm quite happy with the result but it turned out to be a lot 
>>>>>>>>>>> more
>>>>>>>>>>> work than either of us expected, mostly due to the amount of 
>>>>>>>>>>> testing.
>>>>>>>>>>>
>>>>>>>>>>> I've left a couple of failing cases in place mainly as reminders
>>>>>>>>>>> to handle them better (which means I also didn't change the 
>>>>>>>>>>> caller
>>>>>>>>>>> to avoid testing for failures).  I've also added TODO notes with
>>>>>>>>>>> reminders to handle some of the new codes more completely.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>>>> +  special_array_member sam{ };
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> sam is always set by component_ref_size, so I don't think 
>>>>>>>>>>>>>> it's necessary to initialize it at the declaration.
>>>>>>>>>>>>>
>>>>>>>>>>>>> I find initializing pass-by-pointer local variables helpful 
>>>>>>>>>>>>> but
>>>>>>>>>>>>> I don't insist on it.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
>>>>>>>>>>>>>>>    tree last_type = TREE_TYPE (last);
>>>>>>>>>>>>>>>    if (TREE_CODE (last_type) != ARRAY_TYPE
>>>>>>>>>>>>>>>        || TYPE_SIZE (last_type))
>>>>>>>>>>>>>>> -    return size;
>>>>>>>>>>>>>>> +    return size ? size : TYPE_SIZE_UNIT (type);
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> This change seems to violate the comment for the function.
>>>>>>>>>>>>>
>>>>>>>>>>>>> By my reading (and writing) the change is covered by the first
>>>>>>>>>>>>> sentence:
>>>>>>>>>>>>>
>>>>>>>>>>>>>     Returns the size of the object designated by DECL 
>>>>>>>>>>>>> considering
>>>>>>>>>>>>>     its initializer if it either has one or if it would not 
>>>>>>>>>>>>> affect
>>>>>>>>>>>>>     its size, ...
>>>>>>>>>>>>
>>>>>>>>>>>> OK, I see it now.
>>>>>>>>>>>>
>>>>>>>>>>>>> It handles a number of cases in Wplacement-new-size.C fail 
>>>>>>>>>>>>> that
>>>>>>>>>>>>> construct a larger object in an extern declaration of a 
>>>>>>>>>>>>> template,
>>>>>>>>>>>>> like this:
>>>>>>>>>>>>>
>>>>>>>>>>>>>    template <class> struct S { char c; };
>>>>>>>>>>>>>    extern S<int> s;
>>>>>>>>>>>>>
>>>>>>>>>>>>>    void f ()
>>>>>>>>>>>>>    {
>>>>>>>>>>>>>      new (&s) int ();
>>>>>>>>>>>>>    }
>>>>>>>>>>>>>
>>>>>>>>>>>>> I don't know why DECL_SIZE isn't set here (I don't think it 
>>>>>>>>>>>>> can
>>>>>>>>>>>>> be anything but equal to TYPE_SIZE, can it?) and other than 
>>>>>>>>>>>>> struct
>>>>>>>>>>>>> objects with a flexible array member where this identity 
>>>>>>>>>>>>> doesn't
>>>>>>>>>>>>> hold I can't think of others.  Am I missing something?
>>>>>>>>>>>>
>>>>>>>>>>>> Good question.  The attached patch should fix that, so you 
>>>>>>>>>>>> shouldn't need the change to decl_init_size:
>>>>>>>>>>>
>>>>>>>>>>> I've integrated it into the bug fix.
>>>>>>>>>>>
>>>>>>>>>>> Besides the usual x86_64-linux bootstrap/regtest I tested both
>>>>>>>>>>> patches by building a few packages, including Binutils/GDB, 
>>>>>>>>>>> Glibc,
>>>>>>>>>>> and  verifying no new warnings show up.
>>>>>>>>>>>
>>>>>>>>>>> Martin
>>>>>>>>
>>>>>>>>> +offset_int
>>>>>>>>> +access_ref::size_remaining (offset_int *pmin /* = NULL */) const
>>>>>>>>
>>>>>>>> For the various member functions, please include the comments 
>>>>>>>> with the definition as well as the in-class declaration.
>>>>>>>
>>>>>>> Only one access_ref member function is defined out-of-line: 
>>>>>>> offset_bounded().  I've adjusted the comment and copied it above
>>>>>>> the function definition.

And size_remaining, as quoted above?

I also don't see a comment above the definition of offset_bounded in the 
new patch?

>>>>>>>>> +      if (offrng[1] < offrng[0])
>>>>>>>>
>>>>>>>> What does it mean for the max offset to be less than the min 
>>>>>>>> offset? I wouldn't expect that to ever happen with wide integers.
>>>>>>>
>>>>>>> The offset is represented in sizetype with negative values 
>>>>>>> represented
>>>>>>> as large positive values, but has to be converted to ptrdiff_t.
>>>>>>
>>>>>> It looks to me like the offset is offset_int, which is both signed 
>>>>>> and big enough to hold all values of sizetype without turning 
>>>>>> large positive values into negative values.  Where are these 
>>>>>> sign-switching conversions happening?
>>>>>
>>>>> In get_offset_range in builtins.c.
>>>>
>>>> Since we're converting to offset_int there, why not give the 
>>>> offset_int the real value rather than a bogus negative value?
>>>
>>> I don't understand the question: the real offset (in the program)
>>> is negative when its sizetype representation is greater than
>>> PTRDIFF_MAX.  It's worked this way for years.
>>
>> OK, then we're back to my original question: why is the max offset 
>> less than the min offset?  If the range includes negative values, why 
>> isn't the more-negative one the minimum?
> 
> I showed a simple example where it happens:
> 
>     extern char a[2];
> 
>     void f (unsigned long i)
>     {
>       if (i == 0)
>         return;
>       a[i] = 0;   // i's range is [1, -1] (i.e., [1, SIZE_MAX])
>     }

> The "negative" offset can't be the minimum because it would include
> zero which is the one value the range excludes.  It's effectively
> an anti-range represented as an inverted range.

So if the excluded value was 1, the range would be [2, 0]?  OK, then the 
answer to my question is "it indicates an anti-range" and the sign 
wrapping was just a distraction.

>>>>>>> These
>>>>>>> cases come up when the unsigned offset is an ordinary range that
>>>>>>> corresponds to an anti-range, such as here:
>>>>>>>
>>>>>>>    extern char a[2];
>>>>>>>
>>>>>>>    void f (unsigned long i)
>>>>>>>    {
>>>>>>>      if (i == 0)
>>>>>>>        return;
>>>>>>>      a[i] = 0;   // i's range is [1, -1] (i.e., [1, SIZE_MAX]
>>>>>>>    }
>>>>>>>
>>>>>>>>
>>>>>>>>> +  /* Return true if OFFRNG is bounded to a subrange of 
>>>>>>>>> possible offset
>>>>>>>>> +     values.  */
>>>>>>>>> +  bool offset_bounded () const;
>>>>>>>>
>>>>>>>> I don't understand how you're using this.  The implementation 
>>>>>>>> checks for the possible offset values falling outside those 
>>>>>>>> representable by ptrdiff_t, unless the range is only a single 
>>>>>>>> value.  And then the only use is
>>>>>>>>
>>>>>>>>> +  if (ref.offset_zero () || !ref.offset_bounded ())
>>>>>>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>>>>>>> +        "%qD declared here", ref.ref);
>>>>>>>>> +  else if (ref.offrng[0] == ref.offrng[1])
>>>>>>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>>>>>>> +        "at offset %wi from %qD declared here",
>>>>>>>>> +        ref.offrng[0].to_shwi (), ref.ref);
>>>>>>>>> +  else
>>>>>>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>>>>>>> +        "at offset [%wi, %wi] from %qD declared here",
>>>>>>>>> +        ref.offrng[0].to_shwi (), ref.offrng[1].to_shwi (), 
>>>>>>>>> ref.ref);
>>>>>>>>
>>>>>>>> So if the possible offsets are all representable by ptrdiff_t, 
>>>>>>>> we don't print the range?  The middle case also looks 
>>>>>>>> unreachable, since offset_bounded will return false in that case.
>>>>>>>
>>>>>>> The function was originally named "offset_unbounded."  I changed
>>>>>>> it to "offset_bounded" but looks like I didn't finish the job or
>>>>>>> add any tests for it.
>>>>>>>
>>>>>>> The goal of conditionals is to avoid overwhelming the user with
>>>>>>> excessive numbers that may not be meaningful or even relevant
>>>>>>> to the warning.  I've corrected the function body, tweaked and
>>>>>>> renamed the get_range function to get_offset_range to do a better
>>>>>>> job of extracting ranges from the types of some nonconstant
>>>>>>> expressions the front end passes it, and added a new test for
>>>>>>> all this.  Attached is the new revision.

offset_bounded looks unchanged in the new patch.  It still returns true 
iff either the range is a single value or one of the bounds are 
unrepresentable in ptrdiff_t.  I'm still unclear how this corresponds to 
"Return true if OFFRNG is bounded to a subrange of possible offset values."

It still looks like the middle case is unreachable: if offrng[0] == 
offrng[1], offset_bounded returns false, so we don't get as far as 
testing it for the middle case.

Jason


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-10-07 21:01                             ` Jason Merrill
@ 2020-10-08 19:18                               ` Martin Sebor
  2020-10-08 19:40                                 ` Jason Merrill
  0 siblings, 1 reply; 28+ messages in thread
From: Martin Sebor @ 2020-10-08 19:18 UTC (permalink / raw)
  To: Jason Merrill, gcc-patches

On 10/7/20 3:01 PM, Jason Merrill wrote:
> On 10/7/20 4:11 PM, Martin Sebor wrote:
...

>>>>>>>>> For the various member functions, please include the comments 
>>>>>>>>> with the definition as well as the in-class declaration.
>>>>>>>>
>>>>>>>> Only one access_ref member function is defined out-of-line: 
>>>>>>>> offset_bounded().  I've adjusted the comment and copied it above
>>>>>>>> the function definition.
> 
> And size_remaining, as quoted above?

I have this in my tree:

/* Return the maximum amount of space remaining and if non-null, set
    argument to the minimum.  */

I'll add it when I commit the patch.

> 
> I also don't see a comment above the definition of offset_bounded in the 
> new patch?

There is a comment in the latest patch.

...
>>>>>>>> The goal of conditionals is to avoid overwhelming the user with
>>>>>>>> excessive numbers that may not be meaningful or even relevant
>>>>>>>> to the warning.  I've corrected the function body, tweaked and
>>>>>>>> renamed the get_range function to get_offset_range to do a better
>>>>>>>> job of extracting ranges from the types of some nonconstant
>>>>>>>> expressions the front end passes it, and added a new test for
>>>>>>>> all this.  Attached is the new revision.
> 
> offset_bounded looks unchanged in the new patch.  It still returns true 
> iff either the range is a single value or one of the bounds are 
> unrepresentable in ptrdiff_t.  I'm still unclear how this corresponds to 
> "Return true if OFFRNG is bounded to a subrange of possible offset values."

I don't think you're looking at the latest patch.  It has this:

+/* Return true if OFFRNG is bounded to a subrange of offset values
+   valid for the largest possible object.  */
+
  bool
  access_ref::offset_bounded () const
  {
-  if (offrng[0] == offrng[1])
-    return false;
-
    tree min = TYPE_MIN_VALUE (ptrdiff_type_node);
    tree max = TYPE_MAX_VALUE (ptrdiff_type_node);
-  return offrng[0] <= wi::to_offset (min) || offrng[1] >= wi::to_offset 
(max);
+  return wi::to_offset (min) <= offrng[0] && offrng[1] <= wi::to_offset 
(max);
  }

Here's a link to it in the archive:

https://gcc.gnu.org/pipermail/gcc-patches/2020-September/555019.html
https://gcc.gnu.org/pipermail/gcc-patches/attachments/20200928/9026783a/attachment-0003.bin

Is this version okay to commit?

Martin

> 
> It still looks like the middle case is unreachable: if offrng[0] == 
> offrng[1], offset_bounded returns false, so we don't get as far as 
> testing it for the middle case.
> 
> Jason
> 


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-10-08 19:18                               ` Martin Sebor
@ 2020-10-08 19:40                                 ` Jason Merrill
  2020-10-09 14:51                                   ` Martin Sebor
  0 siblings, 1 reply; 28+ messages in thread
From: Jason Merrill @ 2020-10-08 19:40 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On 10/8/20 3:18 PM, Martin Sebor wrote:
> On 10/7/20 3:01 PM, Jason Merrill wrote:
>> On 10/7/20 4:11 PM, Martin Sebor wrote:
> ...
> 
>>>>>>>>>> For the various member functions, please include the comments 
>>>>>>>>>> with the definition as well as the in-class declaration.
>>>>>>>>>
>>>>>>>>> Only one access_ref member function is defined out-of-line: 
>>>>>>>>> offset_bounded().  I've adjusted the comment and copied it above
>>>>>>>>> the function definition.
>>
>> And size_remaining, as quoted above?
> 
> I have this in my tree:
> 
> /* Return the maximum amount of space remaining and if non-null, set
>     argument to the minimum.  */
> 
> I'll add it when I commit the patch.
> 
>>
>> I also don't see a comment above the definition of offset_bounded in 
>> the new patch?
> 
> There is a comment in the latest patch.
> 
> ...
>>>>>>>>> The goal of conditionals is to avoid overwhelming the user with
>>>>>>>>> excessive numbers that may not be meaningful or even relevant
>>>>>>>>> to the warning.  I've corrected the function body, tweaked and
>>>>>>>>> renamed the get_range function to get_offset_range to do a better
>>>>>>>>> job of extracting ranges from the types of some nonconstant
>>>>>>>>> expressions the front end passes it, and added a new test for
>>>>>>>>> all this.  Attached is the new revision.
>>
>> offset_bounded looks unchanged in the new patch.  It still returns 
>> true iff either the range is a single value or one of the bounds are 
>> unrepresentable in ptrdiff_t.  I'm still unclear how this corresponds 
>> to "Return true if OFFRNG is bounded to a subrange of possible offset 
>> values."
> 
> I don't think you're looking at the latest patch.  It has this:
> 
> +/* Return true if OFFRNG is bounded to a subrange of offset values
> +   valid for the largest possible object.  */
> +
>   bool
>   access_ref::offset_bounded () const
>   {
> -  if (offrng[0] == offrng[1])
> -    return false;
> -
>     tree min = TYPE_MIN_VALUE (ptrdiff_type_node);
>     tree max = TYPE_MAX_VALUE (ptrdiff_type_node);
> -  return offrng[0] <= wi::to_offset (min) || offrng[1] >= wi::to_offset 
> (max);
> +  return wi::to_offset (min) <= offrng[0] && offrng[1] <= wi::to_offset 
> (max);
>   }
> 
> Here's a link to it in the archive:
> 
> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/555019.html
> https://gcc.gnu.org/pipermail/gcc-patches/attachments/20200928/9026783a/attachment-0003.bin 

Ah, yes, there are two patches in that email; the first introduces the 
broken offset_bounded, and the second one fixes it without mentioning 
that in the ChangeLog.  How about moving the fix to the first patch?

Jason


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-10-08 19:40                                 ` Jason Merrill
@ 2020-10-09 14:51                                   ` Martin Sebor
  2020-10-09 15:13                                     ` Jason Merrill
  0 siblings, 1 reply; 28+ messages in thread
From: Martin Sebor @ 2020-10-09 14:51 UTC (permalink / raw)
  To: Jason Merrill, gcc-patches

On 10/8/20 1:40 PM, Jason Merrill wrote:
> On 10/8/20 3:18 PM, Martin Sebor wrote:
>> On 10/7/20 3:01 PM, Jason Merrill wrote:
>>> On 10/7/20 4:11 PM, Martin Sebor wrote:
>> ...
>>
>>>>>>>>>>> For the various member functions, please include the comments 
>>>>>>>>>>> with the definition as well as the in-class declaration.
>>>>>>>>>>
>>>>>>>>>> Only one access_ref member function is defined out-of-line: 
>>>>>>>>>> offset_bounded().  I've adjusted the comment and copied it above
>>>>>>>>>> the function definition.
>>>
>>> And size_remaining, as quoted above?
>>
>> I have this in my tree:
>>
>> /* Return the maximum amount of space remaining and if non-null, set
>>     argument to the minimum.  */
>>
>> I'll add it when I commit the patch.
>>
>>>
>>> I also don't see a comment above the definition of offset_bounded in 
>>> the new patch?
>>
>> There is a comment in the latest patch.
>>
>> ...
>>>>>>>>>> The goal of conditionals is to avoid overwhelming the user with
>>>>>>>>>> excessive numbers that may not be meaningful or even relevant
>>>>>>>>>> to the warning.  I've corrected the function body, tweaked and
>>>>>>>>>> renamed the get_range function to get_offset_range to do a better
>>>>>>>>>> job of extracting ranges from the types of some nonconstant
>>>>>>>>>> expressions the front end passes it, and added a new test for
>>>>>>>>>> all this.  Attached is the new revision.
>>>
>>> offset_bounded looks unchanged in the new patch.  It still returns 
>>> true iff either the range is a single value or one of the bounds are 
>>> unrepresentable in ptrdiff_t.  I'm still unclear how this corresponds 
>>> to "Return true if OFFRNG is bounded to a subrange of possible offset 
>>> values."
>>
>> I don't think you're looking at the latest patch.  It has this:
>>
>> +/* Return true if OFFRNG is bounded to a subrange of offset values
>> +   valid for the largest possible object.  */
>> +
>>   bool
>>   access_ref::offset_bounded () const
>>   {
>> -  if (offrng[0] == offrng[1])
>> -    return false;
>> -
>>     tree min = TYPE_MIN_VALUE (ptrdiff_type_node);
>>     tree max = TYPE_MAX_VALUE (ptrdiff_type_node);
>> -  return offrng[0] <= wi::to_offset (min) || offrng[1] >= 
>> wi::to_offset (max);
>> +  return wi::to_offset (min) <= offrng[0] && offrng[1] <= 
>> wi::to_offset (max);
>>   }
>>
>> Here's a link to it in the archive:
>>
>> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/555019.html
>> https://gcc.gnu.org/pipermail/gcc-patches/attachments/20200928/9026783a/attachment-0003.bin 
> 
> 
> Ah, yes, there are two patches in that email; the first introduces the 
> broken offset_bounded, and the second one fixes it without mentioning 
> that in the ChangeLog.  How about moving the fix to the first patch?

Sure, I can do that.  Anything else or is the final version okay
to commit with this adjustment?

Martin


> 
> Jason
> 


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-10-09 14:51                                   ` Martin Sebor
@ 2020-10-09 15:13                                     ` Jason Merrill
  2020-10-11 22:45                                       ` Martin Sebor
  0 siblings, 1 reply; 28+ messages in thread
From: Jason Merrill @ 2020-10-09 15:13 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On 10/9/20 10:51 AM, Martin Sebor wrote:
> On 10/8/20 1:40 PM, Jason Merrill wrote:
>> On 10/8/20 3:18 PM, Martin Sebor wrote:
>>> On 10/7/20 3:01 PM, Jason Merrill wrote:
>>>> On 10/7/20 4:11 PM, Martin Sebor wrote:
>>> ...
>>>
>>>>>>>>>>>> For the various member functions, please include the 
>>>>>>>>>>>> comments with the definition as well as the in-class 
>>>>>>>>>>>> declaration.
>>>>>>>>>>>
>>>>>>>>>>> Only one access_ref member function is defined out-of-line: 
>>>>>>>>>>> offset_bounded().  I've adjusted the comment and copied it above
>>>>>>>>>>> the function definition.
>>>>
>>>> And size_remaining, as quoted above?
>>>
>>> I have this in my tree:
>>>
>>> /* Return the maximum amount of space remaining and if non-null, set
>>>     argument to the minimum.  */
>>>
>>> I'll add it when I commit the patch.
>>>
>>>>
>>>> I also don't see a comment above the definition of offset_bounded in 
>>>> the new patch?
>>>
>>> There is a comment in the latest patch.
>>>
>>> ...
>>>>>>>>>>> The goal of conditionals is to avoid overwhelming the user with
>>>>>>>>>>> excessive numbers that may not be meaningful or even relevant
>>>>>>>>>>> to the warning.  I've corrected the function body, tweaked and
>>>>>>>>>>> renamed the get_range function to get_offset_range to do a 
>>>>>>>>>>> better
>>>>>>>>>>> job of extracting ranges from the types of some nonconstant
>>>>>>>>>>> expressions the front end passes it, and added a new test for
>>>>>>>>>>> all this.  Attached is the new revision.
>>>>
>>>> offset_bounded looks unchanged in the new patch.  It still returns 
>>>> true iff either the range is a single value or one of the bounds are 
>>>> unrepresentable in ptrdiff_t.  I'm still unclear how this 
>>>> corresponds to "Return true if OFFRNG is bounded to a subrange of 
>>>> possible offset values."
>>>
>>> I don't think you're looking at the latest patch.  It has this:
>>>
>>> +/* Return true if OFFRNG is bounded to a subrange of offset values
>>> +   valid for the largest possible object.  */
>>> +
>>>   bool
>>>   access_ref::offset_bounded () const
>>>   {
>>> -  if (offrng[0] == offrng[1])
>>> -    return false;
>>> -
>>>     tree min = TYPE_MIN_VALUE (ptrdiff_type_node);
>>>     tree max = TYPE_MAX_VALUE (ptrdiff_type_node);
>>> -  return offrng[0] <= wi::to_offset (min) || offrng[1] >= 
>>> wi::to_offset (max);
>>> +  return wi::to_offset (min) <= offrng[0] && offrng[1] <= 
>>> wi::to_offset (max);
>>>   }
>>>
>>> Here's a link to it in the archive:
>>>
>>> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/555019.html
>>> https://gcc.gnu.org/pipermail/gcc-patches/attachments/20200928/9026783a/attachment-0003.bin 
>>
>>
>>
>> Ah, yes, there are two patches in that email; the first introduces the 
>> broken offset_bounded, and the second one fixes it without mentioning 
>> that in the ChangeLog.  How about moving the fix to the first patch?
> 
> Sure, I can do that.  Anything else or is the final version okay
> to commit with this adjustment?

OK with that adjustment.

Jason


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-10-09 15:13                                     ` Jason Merrill
@ 2020-10-11 22:45                                       ` Martin Sebor
  2020-10-12  3:44                                         ` Jason Merrill
  0 siblings, 1 reply; 28+ messages in thread
From: Martin Sebor @ 2020-10-11 22:45 UTC (permalink / raw)
  To: Jason Merrill, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 6265 bytes --]

On 10/9/20 9:13 AM, Jason Merrill wrote:
> On 10/9/20 10:51 AM, Martin Sebor wrote:
>> On 10/8/20 1:40 PM, Jason Merrill wrote:
>>> On 10/8/20 3:18 PM, Martin Sebor wrote:
>>>> On 10/7/20 3:01 PM, Jason Merrill wrote:
>>>>> On 10/7/20 4:11 PM, Martin Sebor wrote:
>>>> ...
>>>>
>>>>>>>>>>>>> For the various member functions, please include the 
>>>>>>>>>>>>> comments with the definition as well as the in-class 
>>>>>>>>>>>>> declaration.
>>>>>>>>>>>>
>>>>>>>>>>>> Only one access_ref member function is defined out-of-line: 
>>>>>>>>>>>> offset_bounded().  I've adjusted the comment and copied it 
>>>>>>>>>>>> above
>>>>>>>>>>>> the function definition.
>>>>>
>>>>> And size_remaining, as quoted above?
>>>>
>>>> I have this in my tree:
>>>>
>>>> /* Return the maximum amount of space remaining and if non-null, set
>>>>     argument to the minimum.  */
>>>>
>>>> I'll add it when I commit the patch.
>>>>
>>>>>
>>>>> I also don't see a comment above the definition of offset_bounded 
>>>>> in the new patch?
>>>>
>>>> There is a comment in the latest patch.
>>>>
>>>> ...
>>>>>>>>>>>> The goal of conditionals is to avoid overwhelming the user with
>>>>>>>>>>>> excessive numbers that may not be meaningful or even relevant
>>>>>>>>>>>> to the warning.  I've corrected the function body, tweaked and
>>>>>>>>>>>> renamed the get_range function to get_offset_range to do a 
>>>>>>>>>>>> better
>>>>>>>>>>>> job of extracting ranges from the types of some nonconstant
>>>>>>>>>>>> expressions the front end passes it, and added a new test for
>>>>>>>>>>>> all this.  Attached is the new revision.
>>>>>
>>>>> offset_bounded looks unchanged in the new patch.  It still returns 
>>>>> true iff either the range is a single value or one of the bounds 
>>>>> are unrepresentable in ptrdiff_t.  I'm still unclear how this 
>>>>> corresponds to "Return true if OFFRNG is bounded to a subrange of 
>>>>> possible offset values."
>>>>
>>>> I don't think you're looking at the latest patch.  It has this:
>>>>
>>>> +/* Return true if OFFRNG is bounded to a subrange of offset values
>>>> +   valid for the largest possible object.  */
>>>> +
>>>>   bool
>>>>   access_ref::offset_bounded () const
>>>>   {
>>>> -  if (offrng[0] == offrng[1])
>>>> -    return false;
>>>> -
>>>>     tree min = TYPE_MIN_VALUE (ptrdiff_type_node);
>>>>     tree max = TYPE_MAX_VALUE (ptrdiff_type_node);
>>>> -  return offrng[0] <= wi::to_offset (min) || offrng[1] >= 
>>>> wi::to_offset (max);
>>>> +  return wi::to_offset (min) <= offrng[0] && offrng[1] <= 
>>>> wi::to_offset (max);
>>>>   }
>>>>
>>>> Here's a link to it in the archive:
>>>>
>>>> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/555019.html
>>>> https://gcc.gnu.org/pipermail/gcc-patches/attachments/20200928/9026783a/attachment-0003.bin 
>>>
>>>
>>>
>>>
>>> Ah, yes, there are two patches in that email; the first introduces 
>>> the broken offset_bounded, and the second one fixes it without 
>>> mentioning that in the ChangeLog.  How about moving the fix to the 
>>> first patch?
>>
>> Sure, I can do that.  Anything else or is the final version okay
>> to commit with this adjustment?
> 
> OK with that adjustment.

I've done more testing and found a bug in the second patch: adding
an offset in an inverted range to an existing offset range isn't as
simple as adding up the bounds because they mean different things:
like an anti-range, an inverted range is a union of two subranges.
Instead, the upper bound needs to be extended to PTRDIFF_MAX because
that is the maximum being added, and the lower bound either reset to
zero if the absolute value of the maximum being added is less than
it, or incremented by the absolute value otherwise.

For example, given:

   char a[8];
   char *pa = a;
   char *p1 = pa + i;   // i's range is [3, 5]
   char *p2 = p1 + j;   // j's range is [1, -4]

the range of p2's offset isn't [4, 1] but [4, PTRDIFF_MAX] (or more
precisely [4, 8] if we assume it's valid).  But the range of p3's
valid offset in this last pointer

   char *p3 = p2 + k;   // k's range is [5, -4]

is all of [0, PTRDIFF_MAX] (or, more accurately, [0, 8]).

This may seem obvious but it took me a while at first to wrap my head
around.

I've tweaked access_ref::add_offset in the patch to handle this
correctly.  The function now ensures that every offset is in
a regular range (and not an inverted one).  That in turn simplifies
access_ref::size_remaining.  Since an inverted range is the same as
an anti-range, there's no reason to exclude the latter anymore(*).
The diff on top of the approved patch is attached.

I've retested this new revision of the patch with Glibc and GDB/
Binutils, (the latter fails due to PR 97360), and the Linux kernel.

Please let me know if you have any questions or concerns with
this change.  If not, I'd like to commit it sometime tomorrow.

Martin

[*] I was curious how often these inverted ranges/anti-ranges come
up in pointer arithmetic to see if handling them is worthwhile.  I
instrumented GCC to print them in get_range() on master where they
are only looked at in calls to built-in functions, and in another
patch I'm working on where they are looked at for every pointer
addition.  They account for 16% to 23% (GCC and Glibc, respectively)
and 22% to 32% (Glibc and GCC).  The detailed results are below.

GCC
   Builtin pointer arithmetic:
     kind         ordinary      inverted
     RANGE        636 (38%)     150 ( 9%)
     ANTI_RANGE    28 ( 1%)      99 ( 6%)
     VARYING      733 (44%)
     total       1397 (84%)     249 (15%)

   All pointer arithmetic:
     kind         ordinary      inverted
     RANGE        4663 (45%)   2945 (28%)
     ANTI_RANGE    134 ( 1%)    119 ( 1%)
     VARYING      2344 (22%)
     total        7141 (69%)   3064 (30%)

Glibc
   Builtin pointer arithmetic:
     kind         ordinary      inverted
     RANGE        488 (37%)     191 (14%)
     ANTI_RANGE    18 ( 1%)     112 ( 8%)
     VARYING      509 (38%)
     total       1015 (77%)     303 (22%)

   All pointer arithmetic:
     kind         ordinary      inverted
     RANGE       1941 (51%)     636 (16%)
     ANTI_RANGE    41 ( 1%)     202 ( 5%)
     VARYING      952 (25%)
     total       2934 (77%)     838 (22%)

[-- Attachment #2: gcc-compute_objsize-tweak.diff --]
[-- Type: text/x-patch, Size: 8337 bytes --]

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 5c8cdc3add7..e6649ddc76f 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -231,20 +231,11 @@ access_ref::size_remaining (offset_int *pmin /* = NULL */) const
   if (!pmin)
     pmin = &minbuf;
 
+  /* add_offset() ensures the offset range isn't inverted.  */
+  gcc_checking_assert (offrng[0] <= offrng[1]);
+
   if (base0)
     {
-      if (offrng[1] < offrng[0])
-	{
-	  if (offrng[1] < 0)
-	    {
-	      *pmin = 0;
-	      return offrng[0] < sizrng[1] ? sizrng[1] - offrng[0] : 0;
-	    }
-
-	  *pmin = offrng[0] < sizrng[1] ? sizrng[1] - offrng[0] : 0;
-	  return sizrng[1];
-	}
-
       /* The offset into referenced object is zero-based (i.e., it's
 	 not referenced by a pointer into middle of some unknown object).  */
       if (offrng[0] < 0 && offrng[1] < 0)
@@ -256,9 +247,11 @@ access_ref::size_remaining (offset_int *pmin /* = NULL */) const
 
       if (sizrng[1] <= offrng[0])
 	{
-	  /* If the starting offset is greater than the upper bound on
-	     the size of the object the space remaining is zero.  */
-	  *pmin = 0;
+	  /* If the starting offset is greater than or equal to the upper
+	     bound on the size of the object, the space remaining is zero.
+	     As a special case, if it's equal, set *PMIN to -1 to let
+	     the caller know the offset is valid and just past the end.  */
+	  *pmin = sizrng[1] == offrng[0] ? -1 : 0;
 	  return 0;
 	}
 
@@ -273,22 +266,6 @@ access_ref::size_remaining (offset_int *pmin /* = NULL */) const
      refer to a byte other than the first.  The size of such an object
      is constrained only by the size of the address space (the result
      of max_object_size()).  */
-  if (offrng[1] < offrng[0])
-    {
-      if (offrng[1] < 0)
-	{
-	  *pmin = sizrng[0];
-	  return sizrng[1];
-	}
-
-      if (offrng[0] < sizrng[1])
-	*pmin = offrng[0] < 0 ? sizrng[1] : sizrng[1] - offrng[0];
-      else
-	*pmin = 0;
-
-      return sizrng[1];
-    }
-
   if (sizrng[1] <= offrng[0])
     {
       *pmin = 0;
@@ -301,6 +278,71 @@ access_ref::size_remaining (offset_int *pmin /* = NULL */) const
   return sizrng[1] - or0;
 }
 
+/* Add the range [MIN, MAX] to the offset range.  For known objects (with
+   zero-based offsets) at least one of whose offset's bounds is in range,
+   constrain the other (or both) to the bounds of the object (i.e., zero
+   and the upper bound of its size).  This improves the quality of
+   diagnostics.  */
+
+void access_ref::add_offset (const offset_int &min, const offset_int &max)
+{
+  if (min <= max)
+    {
+      /* To add an ordinary range just add it to the bounds.  */
+      offrng[0] += min;
+      offrng[1] += max;
+    }
+  else if (!base0)
+    {
+      /* To add an inverted range to an offset to an unknown object
+	 expand it to the maximum.  */
+      add_max_offset ();
+      return;
+    }
+  else
+    {
+      /* To add an inverted range to an offset to an known object set
+	 the upper bound to the maximum representable offset value
+	 (which may be greater than MAX_OBJECT_SIZE).
+	 The lower bound is either the sum of the current offset and
+	 MIN when abs(MAX) is greater than the former, or zero otherwise.
+	 Zero because then then inverted range includes the negative of
+	 the lower bound.  */
+      offset_int maxoff = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
+      offrng[1] = maxoff;
+
+      if (max >= 0)
+	{
+	  offrng[0] = 0;
+	  return;
+	}
+
+      offrng[1] = maxoff;
+      offset_int absmax = wi::abs (max);
+      if (offrng[0] < absmax)
+	offrng[0] += min;
+      else
+	offrng[0] = 0;
+    }
+
+  if (!base0)
+    return;
+
+  /* When referencing a known object check to see if the offset computed
+     so far is in bounds... */
+  offset_int remrng[2];
+  remrng[1] = size_remaining (remrng);
+  if (remrng[1] > 0 || remrng[0] < 0)
+    {
+      /* ...if so, constrain it so that neither bound exceeds the size of
+	 the object.  */
+      if (offrng[0] < 0)
+	offrng[0] = 0;
+      if (offrng[1] > sizrng[1])
+	offrng[1] = sizrng[1];
+    }
+}
+
 /* Return true if NAME starts with __builtin_ or __sync_.  */
 
 static bool
@@ -4728,7 +4785,8 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
       tree off = pref->eval (TREE_OPERAND (ptr, 1));
       if (!get_offset_range (off, NULL, orng, rvals))
 	{
-	  orng[1] = wi::to_offset (max_object_size ());
+	  /* Set ORNG to the maximum offset representable in ptrdiff_t.  */
+	  orng[1] = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
 	  orng[0] = -orng[1] - 1;
 	}
 
@@ -4779,9 +4837,7 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
 	    }
 	}
 
-      pref->offrng[0] += orng[0];
-      pref->offrng[1] += orng[1];
-
+      pref->add_offset (orng[0], orng[1]);
       return true;
     }
 
@@ -4941,19 +4997,17 @@ compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap *visited,
       if (code == POINTER_PLUS_EXPR
 	  && TREE_CODE (TREE_TYPE (ptr)) == POINTER_TYPE)
 	{
-	  /* 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.  */
+	  /* Compute the size of the object first. */
+	  if (!compute_objsize (ptr, ostype, pref, visited, rvals))
+	    return false;
+
 	  offset_int orng[2];
 	  tree off = gimple_assign_rhs2 (stmt);
-	  if (!get_offset_range (off, stmt, orng, rvals))
-	    {
-	      orng[0] = -wi::to_offset (max_object_size ()) - 1;
-	      orng[1] = wi::to_offset (max_object_size ());
-	    }
-
-	  pref->add_offset (orng[0], orng[1]);
-	  return compute_objsize (ptr, ostype, pref, visited, rvals);
+	  if (get_offset_range (off, stmt, orng, rvals))
+	    pref->add_offset (orng[0], orng[1]);
+	  else
+	    pref->add_max_offset ();
+	  return true;
 	}
 
       if (code == ADDR_EXPR)
diff --git a/gcc/builtins.h b/gcc/builtins.h
index d07d79ebd1a..c09f36da02b 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -198,22 +198,17 @@ struct access_ref
   /* Add OFF to the offset range.  */
   void add_offset (const offset_int &off)
   {
-    offrng[0] += off;
-    offrng[1] += off;
+    add_offset (off, off);
   }
 
   /* Add the range [MIN, MAX] to the offset range.  */
-  void add_offset (const offset_int &min, const offset_int &max)
-  {
-    offrng[0] += min;
-    offrng[1] += max;
-  }
+  void add_offset (const offset_int &, const offset_int &);
 
-  /* Add the maximum valid offset to the offset range.  */
+  /* Add the maximum representable offset to the offset range.  */
   void add_max_offset ()
   {
-    offrng[0] -= wi::to_offset (max_object_size ()) + 1;
-    offrng[1] += wi::to_offset (max_object_size ());
+    offset_int maxoff = wi::to_offset (TYPE_MAX_VALUE (ptrdiff_type_node));
+    add_offset (-maxoff - 1, maxoff);
   }
 
   /* Used to fold integer expressions when called from front ends.  */
@@ -249,9 +244,8 @@ struct access_data
 
 class range_query;
 extern tree gimple_call_alloc_size (gimple *, wide_int[2] = NULL,
-			     range_query * = NULL);
-extern tree gimple_parm_array_size (tree, wide_int[2],
 				    range_query * = NULL);
+extern tree gimple_parm_array_size (tree, wide_int[2], range_query * = NULL);
 extern tree compute_objsize (tree, int, access_ref *, range_query * = NULL);
 extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL,
 			     range_query * = NULL);
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
index f4d1c5ca256..ebb17cd852c 100644
--- a/gcc/tree-ssa-strlen.c
+++ b/gcc/tree-ssa-strlen.c
@@ -228,8 +228,20 @@ get_range (tree val, gimple *stmt, wide_int minmax[2],
 
   value_range_kind rng = get_range_info (val, minmax, minmax + 1);
   if (rng == VR_RANGE)
+    /* This may be an inverted range whose MINMAX[1] < MINMAX[0].  */
     return val;
 
+  if (rng == VR_ANTI_RANGE)
+    {
+      /* An anti-range is the same as an ordinary range with inverted
+	 bounds (where MINMAX[1] < MINMAX[0] is true) that may result
+	 from the conversion of a signed anti-range to unsigned.  */
+      wide_int tmp = minmax[0];
+      minmax[0] = minmax[1] + 1;
+      minmax[1] = wi::sub (tmp, 1);
+      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 NULL_TREE;

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-10-11 22:45                                       ` Martin Sebor
@ 2020-10-12  3:44                                         ` Jason Merrill
  2020-10-12 15:21                                           ` Martin Sebor
  0 siblings, 1 reply; 28+ messages in thread
From: Jason Merrill @ 2020-10-12  3:44 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On 10/11/20 6:45 PM, Martin Sebor wrote:
> On 10/9/20 9:13 AM, Jason Merrill wrote:
>> On 10/9/20 10:51 AM, Martin Sebor wrote:
>>> On 10/8/20 1:40 PM, Jason Merrill wrote:
>>>> On 10/8/20 3:18 PM, Martin Sebor wrote:
>>>>> On 10/7/20 3:01 PM, Jason Merrill wrote:
>>>>>> On 10/7/20 4:11 PM, Martin Sebor wrote:
>>>>> ...
>>>>>
>>>>>>>>>>>>>> For the various member functions, please include the 
>>>>>>>>>>>>>> comments with the definition as well as the in-class 
>>>>>>>>>>>>>> declaration.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Only one access_ref member function is defined out-of-line: 
>>>>>>>>>>>>> offset_bounded().  I've adjusted the comment and copied it 
>>>>>>>>>>>>> above
>>>>>>>>>>>>> the function definition.
>>>>>>
>>>>>> And size_remaining, as quoted above?
>>>>>
>>>>> I have this in my tree:
>>>>>
>>>>> /* Return the maximum amount of space remaining and if non-null, set
>>>>>     argument to the minimum.  */
>>>>>
>>>>> I'll add it when I commit the patch.
>>>>>
>>>>>>
>>>>>> I also don't see a comment above the definition of offset_bounded 
>>>>>> in the new patch?
>>>>>
>>>>> There is a comment in the latest patch.
>>>>>
>>>>> ...
>>>>>>>>>>>>> The goal of conditionals is to avoid overwhelming the user 
>>>>>>>>>>>>> with
>>>>>>>>>>>>> excessive numbers that may not be meaningful or even relevant
>>>>>>>>>>>>> to the warning.  I've corrected the function body, tweaked and
>>>>>>>>>>>>> renamed the get_range function to get_offset_range to do a 
>>>>>>>>>>>>> better
>>>>>>>>>>>>> job of extracting ranges from the types of some nonconstant
>>>>>>>>>>>>> expressions the front end passes it, and added a new test for
>>>>>>>>>>>>> all this.  Attached is the new revision.
>>>>>>
>>>>>> offset_bounded looks unchanged in the new patch.  It still returns 
>>>>>> true iff either the range is a single value or one of the bounds 
>>>>>> are unrepresentable in ptrdiff_t.  I'm still unclear how this 
>>>>>> corresponds to "Return true if OFFRNG is bounded to a subrange of 
>>>>>> possible offset values."
>>>>>
>>>>> I don't think you're looking at the latest patch.  It has this:
>>>>>
>>>>> +/* Return true if OFFRNG is bounded to a subrange of offset values
>>>>> +   valid for the largest possible object.  */
>>>>> +
>>>>>   bool
>>>>>   access_ref::offset_bounded () const
>>>>>   {
>>>>> -  if (offrng[0] == offrng[1])
>>>>> -    return false;
>>>>> -
>>>>>     tree min = TYPE_MIN_VALUE (ptrdiff_type_node);
>>>>>     tree max = TYPE_MAX_VALUE (ptrdiff_type_node);
>>>>> -  return offrng[0] <= wi::to_offset (min) || offrng[1] >= 
>>>>> wi::to_offset (max);
>>>>> +  return wi::to_offset (min) <= offrng[0] && offrng[1] <= 
>>>>> wi::to_offset (max);
>>>>>   }
>>>>>
>>>>> Here's a link to it in the archive:
>>>>>
>>>>> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/555019.html
>>>>> https://gcc.gnu.org/pipermail/gcc-patches/attachments/20200928/9026783a/attachment-0003.bin 
>>>>
>>>>
>>>>
>>>>
>>>>
>>>> Ah, yes, there are two patches in that email; the first introduces 
>>>> the broken offset_bounded, and the second one fixes it without 
>>>> mentioning that in the ChangeLog.  How about moving the fix to the 
>>>> first patch?
>>>
>>> Sure, I can do that.  Anything else or is the final version okay
>>> to commit with this adjustment?
>>
>> OK with that adjustment.
> 
> I've done more testing and found a bug in the second patch: adding
> an offset in an inverted range to an existing offset range isn't as
> simple as adding up the bounds because they mean different things:
> like an anti-range, an inverted range is a union of two subranges.
> Instead, the upper bound needs to be extended to PTRDIFF_MAX because
> that is the maximum being added, and the lower bound either reset to
> zero if the absolute value of the maximum being added is less than
> it, or incremented by the absolute value otherwise.
> 
> For example, given:
> 
>    char a[8];
>    char *pa = a;
>    char *p1 = pa + i;   // i's range is [3, 5]
>    char *p2 = p1 + j;   // j's range is [1, -4]
> 
> the range of p2's offset isn't [4, 1] but [4, PTRDIFF_MAX] (or more
> precisely [4, 8] if we assume it's valid).  But the range of p3's
> valid offset in this last pointer
> 
>    char *p3 = p2 + k;   // k's range is [5, -4]
> 
> is all of [0, PTRDIFF_MAX] (or, more accurately, [0, 8]).
> 
> This may seem obvious but it took me a while at first to wrap my head
> around.

It makes sense, but doesn't seem obvious; a bit more comment might be nice.

> I've tweaked access_ref::add_offset in the patch to handle this
> correctly.  The function now ensures that every offset is in
> a regular range (and not an inverted one).  That in turn simplifies
> access_ref::size_remaining.  Since an inverted range is the same as
> an anti-range, there's no reason to exclude the latter anymore(*).
> The diff on top of the approved patch is attached.
> 
> I've retested this new revision of the patch with Glibc and GDB/
> Binutils, (the latter fails due to PR 97360), and the Linux kernel.
> 
> Please let me know if you have any questions or concerns with
> this change.  If not, I'd like to commit it sometime tomorrow.
> 
> Martin
> 
> [*] I was curious how often these inverted ranges/anti-ranges come
> up in pointer arithmetic to see if handling them is worthwhile.  I
> instrumented GCC to print them in get_range() on master where they
> are only looked at in calls to built-in functions, and in another
> patch I'm working on where they are looked at for every pointer
> addition.  They account for 16% to 23% (GCC and Glibc, respectively)
> and 22% to 32% (Glibc and GCC).  The detailed results are below.
> 
> GCC
>    Builtin pointer arithmetic:
>      kind         ordinary      inverted
>      RANGE        636 (38%)     150 ( 9%)
>      ANTI_RANGE    28 ( 1%)      99 ( 6%)
>      VARYING      733 (44%)
>      total       1397 (84%)     249 (15%)
> 
>    All pointer arithmetic:
>      kind         ordinary      inverted
>      RANGE        4663 (45%)   2945 (28%)
>      ANTI_RANGE    134 ( 1%)    119 ( 1%)
>      VARYING      2344 (22%)
>      total        7141 (69%)   3064 (30%)
> 
> Glibc
>    Builtin pointer arithmetic:
>      kind         ordinary      inverted
>      RANGE        488 (37%)     191 (14%)
>      ANTI_RANGE    18 ( 1%)     112 ( 8%)
>      VARYING      509 (38%)
>      total       1015 (77%)     303 (22%)
> 
>    All pointer arithmetic:
>      kind         ordinary      inverted
>      RANGE       1941 (51%)     636 (16%)
>      ANTI_RANGE    41 ( 1%)     202 ( 5%)
>      VARYING      952 (25%)
>      total       2934 (77%)     838 (22%)

> +  /* When referencing a known object check to see if the offset computed
> +     so far is in bounds... */

...but you don't do anything if it isn't in bounds?  That could use a 
comment, at least.

Jason


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-10-12  3:44                                         ` Jason Merrill
@ 2020-10-12 15:21                                           ` Martin Sebor
  0 siblings, 0 replies; 28+ messages in thread
From: Martin Sebor @ 2020-10-12 15:21 UTC (permalink / raw)
  To: Jason Merrill, gcc-patches

On 10/11/20 9:44 PM, Jason Merrill wrote:
> On 10/11/20 6:45 PM, Martin Sebor wrote:
>> On 10/9/20 9:13 AM, Jason Merrill wrote:
>>> On 10/9/20 10:51 AM, Martin Sebor wrote:
>>>> On 10/8/20 1:40 PM, Jason Merrill wrote:
>>>>> On 10/8/20 3:18 PM, Martin Sebor wrote:
>>>>>> On 10/7/20 3:01 PM, Jason Merrill wrote:
>>>>>>> On 10/7/20 4:11 PM, Martin Sebor wrote:
>>>>>> ...
>>>>>>
>>>>>>>>>>>>>>> For the various member functions, please include the 
>>>>>>>>>>>>>>> comments with the definition as well as the in-class 
>>>>>>>>>>>>>>> declaration.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Only one access_ref member function is defined 
>>>>>>>>>>>>>> out-of-line: offset_bounded().  I've adjusted the comment 
>>>>>>>>>>>>>> and copied it above
>>>>>>>>>>>>>> the function definition.
>>>>>>>
>>>>>>> And size_remaining, as quoted above?
>>>>>>
>>>>>> I have this in my tree:
>>>>>>
>>>>>> /* Return the maximum amount of space remaining and if non-null, set
>>>>>>     argument to the minimum.  */
>>>>>>
>>>>>> I'll add it when I commit the patch.
>>>>>>
>>>>>>>
>>>>>>> I also don't see a comment above the definition of offset_bounded 
>>>>>>> in the new patch?
>>>>>>
>>>>>> There is a comment in the latest patch.
>>>>>>
>>>>>> ...
>>>>>>>>>>>>>> The goal of conditionals is to avoid overwhelming the user 
>>>>>>>>>>>>>> with
>>>>>>>>>>>>>> excessive numbers that may not be meaningful or even relevant
>>>>>>>>>>>>>> to the warning.  I've corrected the function body, tweaked 
>>>>>>>>>>>>>> and
>>>>>>>>>>>>>> renamed the get_range function to get_offset_range to do a 
>>>>>>>>>>>>>> better
>>>>>>>>>>>>>> job of extracting ranges from the types of some nonconstant
>>>>>>>>>>>>>> expressions the front end passes it, and added a new test for
>>>>>>>>>>>>>> all this.  Attached is the new revision.
>>>>>>>
>>>>>>> offset_bounded looks unchanged in the new patch.  It still 
>>>>>>> returns true iff either the range is a single value or one of the 
>>>>>>> bounds are unrepresentable in ptrdiff_t.  I'm still unclear how 
>>>>>>> this corresponds to "Return true if OFFRNG is bounded to a 
>>>>>>> subrange of possible offset values."
>>>>>>
>>>>>> I don't think you're looking at the latest patch.  It has this:
>>>>>>
>>>>>> +/* Return true if OFFRNG is bounded to a subrange of offset values
>>>>>> +   valid for the largest possible object.  */
>>>>>> +
>>>>>>   bool
>>>>>>   access_ref::offset_bounded () const
>>>>>>   {
>>>>>> -  if (offrng[0] == offrng[1])
>>>>>> -    return false;
>>>>>> -
>>>>>>     tree min = TYPE_MIN_VALUE (ptrdiff_type_node);
>>>>>>     tree max = TYPE_MAX_VALUE (ptrdiff_type_node);
>>>>>> -  return offrng[0] <= wi::to_offset (min) || offrng[1] >= 
>>>>>> wi::to_offset (max);
>>>>>> +  return wi::to_offset (min) <= offrng[0] && offrng[1] <= 
>>>>>> wi::to_offset (max);
>>>>>>   }
>>>>>>
>>>>>> Here's a link to it in the archive:
>>>>>>
>>>>>> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/555019.html
>>>>>> https://gcc.gnu.org/pipermail/gcc-patches/attachments/20200928/9026783a/attachment-0003.bin 
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> Ah, yes, there are two patches in that email; the first introduces 
>>>>> the broken offset_bounded, and the second one fixes it without 
>>>>> mentioning that in the ChangeLog.  How about moving the fix to the 
>>>>> first patch?
>>>>
>>>> Sure, I can do that.  Anything else or is the final version okay
>>>> to commit with this adjustment?
>>>
>>> OK with that adjustment.
>>
>> I've done more testing and found a bug in the second patch: adding
>> an offset in an inverted range to an existing offset range isn't as
>> simple as adding up the bounds because they mean different things:
>> like an anti-range, an inverted range is a union of two subranges.
>> Instead, the upper bound needs to be extended to PTRDIFF_MAX because
>> that is the maximum being added, and the lower bound either reset to
>> zero if the absolute value of the maximum being added is less than
>> it, or incremented by the absolute value otherwise.
>>
>> For example, given:
>>
>>    char a[8];
>>    char *pa = a;
>>    char *p1 = pa + i;   // i's range is [3, 5]
>>    char *p2 = p1 + j;   // j's range is [1, -4]
>>
>> the range of p2's offset isn't [4, 1] but [4, PTRDIFF_MAX] (or more
>> precisely [4, 8] if we assume it's valid).  But the range of p3's
>> valid offset in this last pointer
>>
>>    char *p3 = p2 + k;   // k's range is [5, -4]
>>
>> is all of [0, PTRDIFF_MAX] (or, more accurately, [0, 8]).
>>
>> This may seem obvious but it took me a while at first to wrap my head
>> around.
> 
> It makes sense, but doesn't seem obvious; a bit more comment might be nice.

I just now noticed this suggestion, after pushing both patches.
I'll keep it in mind and add something later.

> 
>> I've tweaked access_ref::add_offset in the patch to handle this
>> correctly.  The function now ensures that every offset is in
>> a regular range (and not an inverted one).  That in turn simplifies
>> access_ref::size_remaining.  Since an inverted range is the same as
>> an anti-range, there's no reason to exclude the latter anymore(*).
>> The diff on top of the approved patch is attached.
>>
>> I've retested this new revision of the patch with Glibc and GDB/
>> Binutils, (the latter fails due to PR 97360), and the Linux kernel.
>>
>> Please let me know if you have any questions or concerns with
>> this change.  If not, I'd like to commit it sometime tomorrow.
>>
>> Martin
>>
>> [*] I was curious how often these inverted ranges/anti-ranges come
>> up in pointer arithmetic to see if handling them is worthwhile.  I
>> instrumented GCC to print them in get_range() on master where they
>> are only looked at in calls to built-in functions, and in another
>> patch I'm working on where they are looked at for every pointer
>> addition.  They account for 16% to 23% (GCC and Glibc, respectively)
>> and 22% to 32% (Glibc and GCC).  The detailed results are below.
>>
>> GCC
>>    Builtin pointer arithmetic:
>>      kind         ordinary      inverted
>>      RANGE        636 (38%)     150 ( 9%)
>>      ANTI_RANGE    28 ( 1%)      99 ( 6%)
>>      VARYING      733 (44%)
>>      total       1397 (84%)     249 (15%)
>>
>>    All pointer arithmetic:
>>      kind         ordinary      inverted
>>      RANGE        4663 (45%)   2945 (28%)
>>      ANTI_RANGE    134 ( 1%)    119 ( 1%)
>>      VARYING      2344 (22%)
>>      total        7141 (69%)   3064 (30%)
>>
>> Glibc
>>    Builtin pointer arithmetic:
>>      kind         ordinary      inverted
>>      RANGE        488 (37%)     191 (14%)
>>      ANTI_RANGE    18 ( 1%)     112 ( 8%)
>>      VARYING      509 (38%)
>>      total       1015 (77%)     303 (22%)
>>
>>    All pointer arithmetic:
>>      kind         ordinary      inverted
>>      RANGE       1941 (51%)     636 (16%)
>>      ANTI_RANGE    41 ( 1%)     202 ( 5%)
>>      VARYING      952 (25%)
>>      total       2934 (77%)     838 (22%)
> 
>> +  /* When referencing a known object check to see if the offset computed
>> +     so far is in bounds... */
> 
> ...but you don't do anything if it isn't in bounds?  That could use a 
> comment, at least.

When the offset is out of bounds it has to stay unchanged so that
the access to the object can be diagnosed and the invalid offset
mentioned in the note after the warning.  This also means that
subsequent pointer addition can make an invalid offset valid
again, preventing the access warning.  A different warning will
have to detect that at the point of the pointer addition (I'm
about to submit a change that implements that).

I added a brief comment mentioning this.

Martin

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-09-28 22:01               ` Martin Sebor
  2020-10-05 16:37                 ` Martin Sebor
  2020-10-07 14:26                 ` Jason Merrill
@ 2020-10-13  9:46                 ` Christophe Lyon
  2020-10-13 16:59                   ` Martin Sebor
  2 siblings, 1 reply; 28+ messages in thread
From: Christophe Lyon @ 2020-10-13  9:46 UTC (permalink / raw)
  To: Martin Sebor; +Cc: Jason Merrill, gcc-patches

On Tue, 29 Sep 2020 at 00:02, Martin Sebor via Gcc-patches
<gcc-patches@gcc.gnu.org> wrote:
>
> On 9/25/20 11:17 PM, Jason Merrill wrote:
> > On 9/22/20 4:05 PM, Martin Sebor wrote:
> >> The rebased and retested patches are attached.
> >>
> >> On 9/21/20 3:17 PM, Martin Sebor wrote:
> >>> Ping:
> >>> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/553906.html
> >>>
> >>> (I'm working on rebasing the patch on top of the latest trunk which
> >>> has changed some of the same code but it'd be helpful to get a go-
> >>> ahead on substance the changes.  I don't expect the rebase to
> >>> require any substantive modifications.)
> >>>
> >>> Martin
> >>>
> >>> On 9/14/20 4:01 PM, Martin Sebor wrote:
> >>>> On 9/4/20 11:14 AM, Jason Merrill wrote:
> >>>>> On 9/3/20 2:44 PM, Martin Sebor wrote:
> >>>>>> On 9/1/20 1:22 PM, Jason Merrill wrote:
> >>>>>>> On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
> >>>>>>>> -Wplacement-new handles array indices and pointer offsets the same:
> >>>>>>>> by adjusting them by the size of the element.  That's correct for
> >>>>>>>> the latter but wrong for the former, causing false positives when
> >>>>>>>> the element size is greater than one.
> >>>>>>>>
> >>>>>>>> In addition, the warning doesn't even attempt to handle arrays of
> >>>>>>>> arrays.  I'm not sure if I forgot or if I simply didn't think of
> >>>>>>>> it.
> >>>>>>>>
> >>>>>>>> The attached patch corrects these oversights by replacing most
> >>>>>>>> of the -Wplacement-new code with a call to compute_objsize which
> >>>>>>>> handles all this correctly (plus more), and is also better tested.
> >>>>>>>> But even compute_objsize has bugs: it trips up while converting
> >>>>>>>> wide_int to offset_int for some pointer offset ranges.  Since
> >>>>>>>> handling the C++ IL required changes in this area the patch also
> >>>>>>>> fixes that.
> >>>>>>>>
> >>>>>>>> For review purposes, the patch affects just the middle end.
> >>>>>>>> The C++ diff pretty much just removes code from the front end.
> >>>>>>>
> >>>>>>> The C++ changes are OK.
> >>>>>>
> >>>>>> Thank you for looking at the rest as well.
> >>>>>>
> >>>>>>>
> >>>>>>>> -compute_objsize (tree ptr, int ostype, access_ref *pref,
> >>>>>>>> -                bitmap *visited, const vr_values *rvals /* =
> >>>>>>>> NULL */)
> >>>>>>>> +compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap
> >>>>>>>> *visited,
> >>>>>>>> +                const vr_values *rvals)
> >>>>>>>
> >>>>>>> This reformatting seems unnecessary, and I prefer to keep the
> >>>>>>> comment about the default argument.
> >>>>>>
> >>>>>> This overload doesn't take a default argument.  (There was a stray
> >>>>>> declaration of a similar function at the top of the file that had
> >>>>>> one.  I've removed it.)
> >>>>>
> >>>>> Ah, true.
> >>>>>
> >>>>>>>> -      if (!size || TREE_CODE (size) != INTEGER_CST)
> >>>>>>>> -       return false;
> >>>>>>>  >...
> >>>>>>>
> >>>>>>> You change some failure cases in compute_objsize to return
> >>>>>>> success with a maximum range, while others continue to return
> >>>>>>> failure. This needs commentary about the design rationale.
> >>>>>>
> >>>>>> This is too much for a comment in the code but the background is
> >>>>>> this: compute_objsize initially returned the object size as a
> >>>>>> constant.
> >>>>>> Recently, I have enhanced it to return a range to improve warnings
> >>>>>> for
> >>>>>> allocated objects.  With that, a failure can be turned into
> >>>>>> success by
> >>>>>> having the function set the range to that of the largest object.
> >>>>>> That
> >>>>>> should simplify the function's callers and could even improve
> >>>>>> the detection of some invalid accesses.  Once this change is made
> >>>>>> it might even be possible to change its return type to void.
> >>>>>>
> >>>>>> The change that caught your eye is necessary to make the function
> >>>>>> a drop-in replacement for the C++ front end code which makes this
> >>>>>> same assumption.  Without it, a number of test cases that exercise
> >>>>>> VLAs fail in g++.dg/warn/Wplacement-new-size-5.C.  For example:
> >>>>>>
> >>>>>>    void f (int n)
> >>>>>>    {
> >>>>>>      char a[n];
> >>>>>>      new (a - 1) int ();
> >>>>>>    }
> >>>>>>
> >>>>>> Changing any of the other places isn't necessary for existing tests
> >>>>>> to pass (and I didn't want to introduce too much churn).  But I do
> >>>>>> want to change the rest of the function along the same lines at some
> >>>>>> point.
> >>>>>
> >>>>> Please do change the other places to be consistent; better to have
> >>>>> more churn than to leave the function half-updated.  That can be a
> >>>>> separate patch if you prefer, but let's do it now rather than later.
> >>>>
> >>>> I've made most of these changes in the other patch (also attached).
> >>>> I'm quite happy with the result but it turned out to be a lot more
> >>>> work than either of us expected, mostly due to the amount of testing.
> >>>>
> >>>> I've left a couple of failing cases in place mainly as reminders
> >>>> to handle them better (which means I also didn't change the caller
> >>>> to avoid testing for failures).  I've also added TODO notes with
> >>>> reminders to handle some of the new codes more completely.
> >>>>
> >>>>>
> >>>>>>>> +  special_array_member sam{ };
> >>>>>>>
> >>>>>>> sam is always set by component_ref_size, so I don't think it's
> >>>>>>> necessary to initialize it at the declaration.
> >>>>>>
> >>>>>> I find initializing pass-by-pointer local variables helpful but
> >>>>>> I don't insist on it.
> >>>>>>
> >>>>>>>
> >>>>>>>> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
> >>>>>>>>    tree last_type = TREE_TYPE (last);
> >>>>>>>>    if (TREE_CODE (last_type) != ARRAY_TYPE
> >>>>>>>>        || TYPE_SIZE (last_type))
> >>>>>>>> -    return size;
> >>>>>>>> +    return size ? size : TYPE_SIZE_UNIT (type);
> >>>>>>>
> >>>>>>> This change seems to violate the comment for the function.
> >>>>>>
> >>>>>> By my reading (and writing) the change is covered by the first
> >>>>>> sentence:
> >>>>>>
> >>>>>>     Returns the size of the object designated by DECL considering
> >>>>>>     its initializer if it either has one or if it would not affect
> >>>>>>     its size, ...
> >>>>>
> >>>>> OK, I see it now.
> >>>>>
> >>>>>> It handles a number of cases in Wplacement-new-size.C fail that
> >>>>>> construct a larger object in an extern declaration of a template,
> >>>>>> like this:
> >>>>>>
> >>>>>>    template <class> struct S { char c; };
> >>>>>>    extern S<int> s;
> >>>>>>
> >>>>>>    void f ()
> >>>>>>    {
> >>>>>>      new (&s) int ();
> >>>>>>    }
> >>>>>>
> >>>>>> I don't know why DECL_SIZE isn't set here (I don't think it can
> >>>>>> be anything but equal to TYPE_SIZE, can it?) and other than struct
> >>>>>> objects with a flexible array member where this identity doesn't
> >>>>>> hold I can't think of others.  Am I missing something?
> >>>>>
> >>>>> Good question.  The attached patch should fix that, so you
> >>>>> shouldn't need the change to decl_init_size:
> >>>>
> >>>> I've integrated it into the bug fix.
> >>>>
> >>>> Besides the usual x86_64-linux bootstrap/regtest I tested both
> >>>> patches by building a few packages, including Binutils/GDB, Glibc,
> >>>> and  verifying no new warnings show up.
> >>>>
> >>>> Martin
> >
> >> +offset_int
> >> +access_ref::size_remaining (offset_int *pmin /* = NULL */) const
> >
> > For the various member functions, please include the comments with the
> > definition as well as the in-class declaration.
>
> Only one access_ref member function is defined out-of-line:
> offset_bounded().  I've adjusted the comment and copied it above
> the function definition.
>
> >
> >> +      if (offrng[1] < offrng[0])
> >
> > What does it mean for the max offset to be less than the min offset?  I
> > wouldn't expect that to ever happen with wide integers.
>
> The offset is represented in sizetype with negative values represented
> as large positive values, but has to be converted to ptrdiff_t.  These
> cases come up when the unsigned offset is an ordinary range that
> corresponds to an anti-range, such as here:
>
>    extern char a[2];
>
>    void f (unsigned long i)
>    {
>      if (i == 0)
>        return;
>      a[i] = 0;   // i's range is [1, -1] (i.e., [1, SIZE_MAX]
>    }
>
> >
> >> +  /* Return true if OFFRNG is bounded to a subrange of possible offset
> >> +     values.  */
> >> +  bool offset_bounded () const;
> >
> > I don't understand how you're using this.  The implementation checks for
> > the possible offset values falling outside those representable by
> > ptrdiff_t, unless the range is only a single value.  And then the only
> > use is
> >
> >> +  if (ref.offset_zero () || !ref.offset_bounded ())
> >> +    inform (DECL_SOURCE_LOCATION (ref.ref),
> >> +        "%qD declared here", ref.ref);
> >> +  else if (ref.offrng[0] == ref.offrng[1])
> >> +    inform (DECL_SOURCE_LOCATION (ref.ref),
> >> +        "at offset %wi from %qD declared here",
> >> +        ref.offrng[0].to_shwi (), ref.ref);
> >> +  else
> >> +    inform (DECL_SOURCE_LOCATION (ref.ref),
> >> +        "at offset [%wi, %wi] from %qD declared here",
> >> +        ref.offrng[0].to_shwi (), ref.offrng[1].to_shwi (), ref.ref);
> >
> > So if the possible offsets are all representable by ptrdiff_t, we don't
> > print the range?  The middle case also looks unreachable, since
> > offset_bounded will return false in that case.
>
> The function was originally named "offset_unbounded."  I changed
> it to "offset_bounded" but looks like I didn't finish the job or
> add any tests for it.
>
> The goal of conditionals is to avoid overwhelming the user with
> excessive numbers that may not be meaningful or even relevant
> to the warning.  I've corrected the function body, tweaked and
> renamed the get_range function to get_offset_range to do a better
> job of extracting ranges from the types of some nonconstant
> expressions the front end passes it, and added a new test for
> all this.  Attached is the new revision.
>
> Martin

Hi Martin,

One of t new tests fails on many arm configurations and on aarch64
FAIL: gcc.dg/Wstringop-overflow-47.c  (test for warnings, line 29)
FAIL: gcc.dg/Wstringop-overflow-47.c  (test for warnings, line 32)
FAIL: gcc.dg/Wstringop-overflow-47.c  (test for warnings, line 37)

It looks like it is also failing on x86, s390 and ia64 according to
gcc-testresults.

Can you check?

Thanks

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PING][PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
  2020-10-13  9:46                 ` Christophe Lyon
@ 2020-10-13 16:59                   ` Martin Sebor
  0 siblings, 0 replies; 28+ messages in thread
From: Martin Sebor @ 2020-10-13 16:59 UTC (permalink / raw)
  To: Christophe Lyon; +Cc: Jason Merrill, gcc-patches

On 10/13/20 3:46 AM, Christophe Lyon wrote:
> On Tue, 29 Sep 2020 at 00:02, Martin Sebor via Gcc-patches
> <gcc-patches@gcc.gnu.org> wrote:
>>
>> On 9/25/20 11:17 PM, Jason Merrill wrote:
>>> On 9/22/20 4:05 PM, Martin Sebor wrote:
>>>> The rebased and retested patches are attached.
>>>>
>>>> On 9/21/20 3:17 PM, Martin Sebor wrote:
>>>>> Ping:
>>>>> https://gcc.gnu.org/pipermail/gcc-patches/2020-September/553906.html
>>>>>
>>>>> (I'm working on rebasing the patch on top of the latest trunk which
>>>>> has changed some of the same code but it'd be helpful to get a go-
>>>>> ahead on substance the changes.  I don't expect the rebase to
>>>>> require any substantive modifications.)
>>>>>
>>>>> Martin
>>>>>
>>>>> On 9/14/20 4:01 PM, Martin Sebor wrote:
>>>>>> On 9/4/20 11:14 AM, Jason Merrill wrote:
>>>>>>> On 9/3/20 2:44 PM, Martin Sebor wrote:
>>>>>>>> On 9/1/20 1:22 PM, Jason Merrill wrote:
>>>>>>>>> On 8/11/20 12:19 PM, Martin Sebor via Gcc-patches wrote:
>>>>>>>>>> -Wplacement-new handles array indices and pointer offsets the same:
>>>>>>>>>> by adjusting them by the size of the element.  That's correct for
>>>>>>>>>> the latter but wrong for the former, causing false positives when
>>>>>>>>>> the element size is greater than one.
>>>>>>>>>>
>>>>>>>>>> In addition, the warning doesn't even attempt to handle arrays of
>>>>>>>>>> arrays.  I'm not sure if I forgot or if I simply didn't think of
>>>>>>>>>> it.
>>>>>>>>>>
>>>>>>>>>> The attached patch corrects these oversights by replacing most
>>>>>>>>>> of the -Wplacement-new code with a call to compute_objsize which
>>>>>>>>>> handles all this correctly (plus more), and is also better tested.
>>>>>>>>>> But even compute_objsize has bugs: it trips up while converting
>>>>>>>>>> wide_int to offset_int for some pointer offset ranges.  Since
>>>>>>>>>> handling the C++ IL required changes in this area the patch also
>>>>>>>>>> fixes that.
>>>>>>>>>>
>>>>>>>>>> For review purposes, the patch affects just the middle end.
>>>>>>>>>> The C++ diff pretty much just removes code from the front end.
>>>>>>>>>
>>>>>>>>> The C++ changes are OK.
>>>>>>>>
>>>>>>>> Thank you for looking at the rest as well.
>>>>>>>>
>>>>>>>>>
>>>>>>>>>> -compute_objsize (tree ptr, int ostype, access_ref *pref,
>>>>>>>>>> -                bitmap *visited, const vr_values *rvals /* =
>>>>>>>>>> NULL */)
>>>>>>>>>> +compute_objsize (tree ptr, int ostype, access_ref *pref, bitmap
>>>>>>>>>> *visited,
>>>>>>>>>> +                const vr_values *rvals)
>>>>>>>>>
>>>>>>>>> This reformatting seems unnecessary, and I prefer to keep the
>>>>>>>>> comment about the default argument.
>>>>>>>>
>>>>>>>> This overload doesn't take a default argument.  (There was a stray
>>>>>>>> declaration of a similar function at the top of the file that had
>>>>>>>> one.  I've removed it.)
>>>>>>>
>>>>>>> Ah, true.
>>>>>>>
>>>>>>>>>> -      if (!size || TREE_CODE (size) != INTEGER_CST)
>>>>>>>>>> -       return false;
>>>>>>>>>   >...
>>>>>>>>>
>>>>>>>>> You change some failure cases in compute_objsize to return
>>>>>>>>> success with a maximum range, while others continue to return
>>>>>>>>> failure. This needs commentary about the design rationale.
>>>>>>>>
>>>>>>>> This is too much for a comment in the code but the background is
>>>>>>>> this: compute_objsize initially returned the object size as a
>>>>>>>> constant.
>>>>>>>> Recently, I have enhanced it to return a range to improve warnings
>>>>>>>> for
>>>>>>>> allocated objects.  With that, a failure can be turned into
>>>>>>>> success by
>>>>>>>> having the function set the range to that of the largest object.
>>>>>>>> That
>>>>>>>> should simplify the function's callers and could even improve
>>>>>>>> the detection of some invalid accesses.  Once this change is made
>>>>>>>> it might even be possible to change its return type to void.
>>>>>>>>
>>>>>>>> The change that caught your eye is necessary to make the function
>>>>>>>> a drop-in replacement for the C++ front end code which makes this
>>>>>>>> same assumption.  Without it, a number of test cases that exercise
>>>>>>>> VLAs fail in g++.dg/warn/Wplacement-new-size-5.C.  For example:
>>>>>>>>
>>>>>>>>     void f (int n)
>>>>>>>>     {
>>>>>>>>       char a[n];
>>>>>>>>       new (a - 1) int ();
>>>>>>>>     }
>>>>>>>>
>>>>>>>> Changing any of the other places isn't necessary for existing tests
>>>>>>>> to pass (and I didn't want to introduce too much churn).  But I do
>>>>>>>> want to change the rest of the function along the same lines at some
>>>>>>>> point.
>>>>>>>
>>>>>>> Please do change the other places to be consistent; better to have
>>>>>>> more churn than to leave the function half-updated.  That can be a
>>>>>>> separate patch if you prefer, but let's do it now rather than later.
>>>>>>
>>>>>> I've made most of these changes in the other patch (also attached).
>>>>>> I'm quite happy with the result but it turned out to be a lot more
>>>>>> work than either of us expected, mostly due to the amount of testing.
>>>>>>
>>>>>> I've left a couple of failing cases in place mainly as reminders
>>>>>> to handle them better (which means I also didn't change the caller
>>>>>> to avoid testing for failures).  I've also added TODO notes with
>>>>>> reminders to handle some of the new codes more completely.
>>>>>>
>>>>>>>
>>>>>>>>>> +  special_array_member sam{ };
>>>>>>>>>
>>>>>>>>> sam is always set by component_ref_size, so I don't think it's
>>>>>>>>> necessary to initialize it at the declaration.
>>>>>>>>
>>>>>>>> I find initializing pass-by-pointer local variables helpful but
>>>>>>>> I don't insist on it.
>>>>>>>>
>>>>>>>>>
>>>>>>>>>> @@ -187,7 +187,7 @@ decl_init_size (tree decl, bool min)
>>>>>>>>>>     tree last_type = TREE_TYPE (last);
>>>>>>>>>>     if (TREE_CODE (last_type) != ARRAY_TYPE
>>>>>>>>>>         || TYPE_SIZE (last_type))
>>>>>>>>>> -    return size;
>>>>>>>>>> +    return size ? size : TYPE_SIZE_UNIT (type);
>>>>>>>>>
>>>>>>>>> This change seems to violate the comment for the function.
>>>>>>>>
>>>>>>>> By my reading (and writing) the change is covered by the first
>>>>>>>> sentence:
>>>>>>>>
>>>>>>>>      Returns the size of the object designated by DECL considering
>>>>>>>>      its initializer if it either has one or if it would not affect
>>>>>>>>      its size, ...
>>>>>>>
>>>>>>> OK, I see it now.
>>>>>>>
>>>>>>>> It handles a number of cases in Wplacement-new-size.C fail that
>>>>>>>> construct a larger object in an extern declaration of a template,
>>>>>>>> like this:
>>>>>>>>
>>>>>>>>     template <class> struct S { char c; };
>>>>>>>>     extern S<int> s;
>>>>>>>>
>>>>>>>>     void f ()
>>>>>>>>     {
>>>>>>>>       new (&s) int ();
>>>>>>>>     }
>>>>>>>>
>>>>>>>> I don't know why DECL_SIZE isn't set here (I don't think it can
>>>>>>>> be anything but equal to TYPE_SIZE, can it?) and other than struct
>>>>>>>> objects with a flexible array member where this identity doesn't
>>>>>>>> hold I can't think of others.  Am I missing something?
>>>>>>>
>>>>>>> Good question.  The attached patch should fix that, so you
>>>>>>> shouldn't need the change to decl_init_size:
>>>>>>
>>>>>> I've integrated it into the bug fix.
>>>>>>
>>>>>> Besides the usual x86_64-linux bootstrap/regtest I tested both
>>>>>> patches by building a few packages, including Binutils/GDB, Glibc,
>>>>>> and  verifying no new warnings show up.
>>>>>>
>>>>>> Martin
>>>
>>>> +offset_int
>>>> +access_ref::size_remaining (offset_int *pmin /* = NULL */) const
>>>
>>> For the various member functions, please include the comments with the
>>> definition as well as the in-class declaration.
>>
>> Only one access_ref member function is defined out-of-line:
>> offset_bounded().  I've adjusted the comment and copied it above
>> the function definition.
>>
>>>
>>>> +      if (offrng[1] < offrng[0])
>>>
>>> What does it mean for the max offset to be less than the min offset?  I
>>> wouldn't expect that to ever happen with wide integers.
>>
>> The offset is represented in sizetype with negative values represented
>> as large positive values, but has to be converted to ptrdiff_t.  These
>> cases come up when the unsigned offset is an ordinary range that
>> corresponds to an anti-range, such as here:
>>
>>     extern char a[2];
>>
>>     void f (unsigned long i)
>>     {
>>       if (i == 0)
>>         return;
>>       a[i] = 0;   // i's range is [1, -1] (i.e., [1, SIZE_MAX]
>>     }
>>
>>>
>>>> +  /* Return true if OFFRNG is bounded to a subrange of possible offset
>>>> +     values.  */
>>>> +  bool offset_bounded () const;
>>>
>>> I don't understand how you're using this.  The implementation checks for
>>> the possible offset values falling outside those representable by
>>> ptrdiff_t, unless the range is only a single value.  And then the only
>>> use is
>>>
>>>> +  if (ref.offset_zero () || !ref.offset_bounded ())
>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>> +        "%qD declared here", ref.ref);
>>>> +  else if (ref.offrng[0] == ref.offrng[1])
>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>> +        "at offset %wi from %qD declared here",
>>>> +        ref.offrng[0].to_shwi (), ref.ref);
>>>> +  else
>>>> +    inform (DECL_SOURCE_LOCATION (ref.ref),
>>>> +        "at offset [%wi, %wi] from %qD declared here",
>>>> +        ref.offrng[0].to_shwi (), ref.offrng[1].to_shwi (), ref.ref);
>>>
>>> So if the possible offsets are all representable by ptrdiff_t, we don't
>>> print the range?  The middle case also looks unreachable, since
>>> offset_bounded will return false in that case.
>>
>> The function was originally named "offset_unbounded."  I changed
>> it to "offset_bounded" but looks like I didn't finish the job or
>> add any tests for it.
>>
>> The goal of conditionals is to avoid overwhelming the user with
>> excessive numbers that may not be meaningful or even relevant
>> to the warning.  I've corrected the function body, tweaked and
>> renamed the get_range function to get_offset_range to do a better
>> job of extracting ranges from the types of some nonconstant
>> expressions the front end passes it, and added a new test for
>> all this.  Attached is the new revision.
>>
>> Martin
> 
> Hi Martin,
> 
> One of t new tests fails on many arm configurations and on aarch64
> FAIL: gcc.dg/Wstringop-overflow-47.c  (test for warnings, line 29)
> FAIL: gcc.dg/Wstringop-overflow-47.c  (test for warnings, line 32)
> FAIL: gcc.dg/Wstringop-overflow-47.c  (test for warnings, line 37)
> 
> It looks like it is also failing on x86, s390 and ia64 according to
> gcc-testresults.
> 
> Can you check?

Yes, I'm tracking the problem in pr97027, but I didn't realize
the test was susceptible to this dependency when I wrote it.  It
could be adjusted to avoid the missing warnings but the right fix
is to improve the detection.  Let me see if I can quickly handle
the latter.  In not, I'll constrain the test to just the targets
where I know is passes.

Thanks
Martin

^ permalink raw reply	[flat|nested] 28+ messages in thread

end of thread, other threads:[~2020-10-13 16:59 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-08-11 16:19 [PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511) Martin Sebor
2020-08-19 15:00 ` [PING][PATCH] " Martin Sebor
2020-08-28 15:42   ` [PING 2][PATCH] " Martin Sebor
2020-09-01 19:22 ` [PATCH] " Jason Merrill
2020-09-03 18:44   ` Martin Sebor
2020-09-04 17:14     ` Jason Merrill
2020-09-14 22:01       ` Martin Sebor
2020-09-21 21:17         ` [PING][PATCH] " Martin Sebor
2020-09-22 20:05           ` Martin Sebor
2020-09-26  5:17             ` Jason Merrill
2020-09-28 22:01               ` Martin Sebor
2020-10-05 16:37                 ` Martin Sebor
2020-10-07 14:26                 ` Jason Merrill
2020-10-07 14:42                   ` Martin Sebor
2020-10-07 15:07                     ` Jason Merrill
2020-10-07 15:19                       ` Martin Sebor
2020-10-07 19:28                         ` Jason Merrill
2020-10-07 20:11                           ` Martin Sebor
2020-10-07 21:01                             ` Jason Merrill
2020-10-08 19:18                               ` Martin Sebor
2020-10-08 19:40                                 ` Jason Merrill
2020-10-09 14:51                                   ` Martin Sebor
2020-10-09 15:13                                     ` Jason Merrill
2020-10-11 22:45                                       ` Martin Sebor
2020-10-12  3:44                                         ` Jason Merrill
2020-10-12 15:21                                           ` Martin Sebor
2020-10-13  9:46                 ` Christophe Lyon
2020-10-13 16:59                   ` Martin Sebor

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).