public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
From: Martin Sebor <msebor@gmail.com>
To: Jason Merrill <jason@redhat.com>, gcc-patches <gcc-patches@gcc.gnu.org>
Subject: Re: [PATCH] correct handling of indices into arrays with elements larger than 1 (PR c++/96511)
Date: Thu, 3 Sep 2020 12:44:24 -0600	[thread overview]
Message-ID: <38c7996c-2c9a-c2c3-e18e-db19a2a805a0@gmail.com> (raw)
In-Reply-To: <dd4f23e5-7fc7-1c90-43bd-8b57ac1ecf36@redhat.com>

[-- 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 *);

  reply	other threads:[~2020-09-03 18:44 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-08-11 16:19 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 [this message]
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=38c7996c-2c9a-c2c3-e18e-db19a2a805a0@gmail.com \
    --to=msebor@gmail.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=jason@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).