diff mbox

[PING,2] c++/67942 - diagnose placement new buffer overflow

Message ID 563A9FAD.2050807@gmail.com
State New
Headers show

Commit Message

Martin Sebor Nov. 5, 2015, 12:15 a.m. UTC
> There was a lot of discussion of C++ aliasing rules at the recent
> meeting; we really seem to be moving in the direction of being stricter
> about which union member is active.  So I think we do want to diagnose
> the new-expression above; the user should write new (&u) if that's what
> they mean.

Okay. I changed that in the latest patch.

>> Adjust is negative when the offset to a buffer of known size is
>> negative. For example:
>>
>>      char buf [sizeof (int)];
>>      new (&buf [1] - 1) int;
>
> OK, so because we're looking at the expression from the outside in, we
> first see the subtraction and adjust becomes -1, then we see the
> array_ref and adjust returns to 0.  We still don't have a negative
> adjust by the time we get to the quoted if/else.

I think I see what you mean. I've changed the type of the variables
and the computation to unsigned. That made it possible to eliminate
the final else and do some other cleanup. Attached is an updated
patch.

Tested on x86_64 by botstrapping C and C++ and running make check.

Martin

Comments

Jason Merrill Nov. 5, 2015, 4:27 a.m. UTC | #1
On 11/04/2015 07:15 PM, Martin Sebor wrote:
>> There was a lot of discussion of C++ aliasing rules at the recent
>> meeting; we really seem to be moving in the direction of being stricter
>> about which union member is active.  So I think we do want to diagnose
>> the new-expression above; the user should write new (&u) if that's what
>> they mean.
>
> Okay. I changed that in the latest patch.
>
>>> Adjust is negative when the offset to a buffer of known size is
>>> negative. For example:
>>>
>>>      char buf [sizeof (int)];
>>>      new (&buf [1] - 1) int;
>>
>> OK, so because we're looking at the expression from the outside in, we
>> first see the subtraction and adjust becomes -1, then we see the
>> array_ref and adjust returns to 0.  We still don't have a negative
>> adjust by the time we get to the quoted if/else.
>
> I think I see what you mean. I've changed the type of the variables
> and the computation to unsigned. That made it possible to eliminate
> the final else and do some other cleanup. Attached is an updated
> patch.

Hmm, I was suggesting that bytes_avail change to unsigned, but I don't 
think adjust should change; I believe that 0u - 1u is undefined due to 
overflow even though (-1u) and (unsigned)-1 are well defined.  Sorry for 
the muddled messages.  I think let's leave adjust signed and assert that 
it ends up non-negative.

Jason
Martin Sebor Nov. 5, 2015, 3:12 p.m. UTC | #2
On 11/04/2015 09:27 PM, Jason Merrill wrote:
> On 11/04/2015 07:15 PM, Martin Sebor wrote:
>>> There was a lot of discussion of C++ aliasing rules at the recent
>>> meeting; we really seem to be moving in the direction of being stricter
>>> about which union member is active.  So I think we do want to diagnose
>>> the new-expression above; the user should write new (&u) if that's what
>>> they mean.
>>
>> Okay. I changed that in the latest patch.
>>
>>>> Adjust is negative when the offset to a buffer of known size is
>>>> negative. For example:
>>>>
>>>>      char buf [sizeof (int)];
>>>>      new (&buf [1] - 1) int;
>>>
>>> OK, so because we're looking at the expression from the outside in, we
>>> first see the subtraction and adjust becomes -1, then we see the
>>> array_ref and adjust returns to 0.  We still don't have a negative
>>> adjust by the time we get to the quoted if/else.
>>
>> I think I see what you mean. I've changed the type of the variables
>> and the computation to unsigned. That made it possible to eliminate
>> the final else and do some other cleanup. Attached is an updated
>> patch.
>
> Hmm, I was suggesting that bytes_avail change to unsigned, but I don't
> think adjust should change; I believe that 0u - 1u is undefined due to
> overflow even though (-1u) and (unsigned)-1 are well defined.  Sorry for
> the muddled messages.  I think let's leave adjust signed and assert that
> it ends up non-negative.

No problem.

Unsigned ints wrap around and don't overflow so the subtraction
is well defined (0u - 1u is equal UINT_MAX).

FWIW, I had the assert there for sanity testing when you first
mentioned it to convince myself there really was no way for it
become negative. A bootstrap went fine with it but it still made
me just a teeny bit uneasy. I would hate for the code to change
in the future and for the assert to then fire after it's released.

In any case, I defer to your better judgment. Please let me know
if you would still like to go with signed + assert.

Thanks
Martin
Jason Merrill Nov. 5, 2015, 3:26 p.m. UTC | #3
On 11/05/2015 10:12 AM, Martin Sebor wrote:
> On 11/04/2015 09:27 PM, Jason Merrill wrote:
>> On 11/04/2015 07:15 PM, Martin Sebor wrote:
>>>> There was a lot of discussion of C++ aliasing rules at the recent
>>>> meeting; we really seem to be moving in the direction of being stricter
>>>> about which union member is active.  So I think we do want to diagnose
>>>> the new-expression above; the user should write new (&u) if that's what
>>>> they mean.
>>>
>>> Okay. I changed that in the latest patch.
>>>
>>>>> Adjust is negative when the offset to a buffer of known size is
>>>>> negative. For example:
>>>>>
>>>>>      char buf [sizeof (int)];
>>>>>      new (&buf [1] - 1) int;
>>>>
>>>> OK, so because we're looking at the expression from the outside in, we
>>>> first see the subtraction and adjust becomes -1, then we see the
>>>> array_ref and adjust returns to 0.  We still don't have a negative
>>>> adjust by the time we get to the quoted if/else.
>>>
>>> I think I see what you mean. I've changed the type of the variables
>>> and the computation to unsigned. That made it possible to eliminate
>>> the final else and do some other cleanup. Attached is an updated
>>> patch.
>>
>> Hmm, I was suggesting that bytes_avail change to unsigned, but I don't
>> think adjust should change; I believe that 0u - 1u is undefined due to
>> overflow even though (-1u) and (unsigned)-1 are well defined.  Sorry for
>> the muddled messages.  I think let's leave adjust signed and assert that
>> it ends up non-negative.
>
> No problem.
>
> Unsigned ints wrap around and don't overflow so the subtraction
> is well defined (0u - 1u is equal UINT_MAX).

I thought I had remembered that, but couldn't find anything in the 
standard to back it up.  Now I see that it's in 3.9.1 rather than clause 5.

> FWIW, I had the assert there for sanity testing when you first
> mentioned it to convince myself there really was no way for it
> become negative. A bootstrap went fine with it but it still made
> me just a teeny bit uneasy. I would hate for the code to change
> in the future and for the assert to then fire after it's released.

> In any case, I defer to your better judgment. Please let me know
> if you would still like to go with signed + assert.

If we use gcc_checking_assert it won't fire in release builds; let's go 
with that.

Jason
diff mbox

Patch

gcc ChangeLog
2015-11-04  Martin Sebor  <msebor@redhat.com>

	PR c++/67942
        * invoke.texi (-Wplacement-new): Document new option.
	* gcc/testsuite/g++.dg/warn/Wplacement-new-size.C: New test.

gcc/c-family ChangeLog
2015-11-04  Martin Sebor  <msebor@redhat.com>

	PR c++/67942
        * c.opt (-Wplacement-new): New option.

gcc/cp ChangeLog
2015-11-04  Martin Sebor  <msebor@redhat.com>

	PR c++/67942
	* cp/init.c (warn_placement_new_too_small): New function.
	(build_new_1): Call it.

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 47ba070..5e9d7a3 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -760,6 +760,10 @@  Wprotocol
 ObjC ObjC++ Var(warn_protocol) Init(1) Warning
 Warn if inherited methods are unimplemented

+Wplacement-new
+C++ Var(warn_placement_new) Init(1) Warning
+Warn for placement new expressions with undefined behavior
+
 Wredundant-decls
 C ObjC C++ ObjC++ Var(warn_redundant_decls) Warning
 Warn about multiple declarations of the same object
diff --git a/gcc/cp/init.c b/gcc/cp/init.c
index 1ed8f6c..e2285ec 100644
--- a/gcc/cp/init.c
+++ b/gcc/cp/init.c
@@ -2269,6 +2269,199 @@  throw_bad_array_new_length (void)
   return build_cxx_call (fn, 0, NULL, tf_warning_or_error);
 }

+/* 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
+   it does not.  SIZE specifies the size needed to construct the object or
+   array and captures the result of NELTS * sizeof (TYPE). (SIZE could be
+   greater when the array under construction requires a cookie to store
+   NELTS.  GCC's placement new expression stores the cookie when invoking
+   a user-defined placement new operator function but not the default one.
+   Placement new expressions with user-defined placement new operator are
+   not diagnosed since we don't know how they use the buffer (this could
+   be a future extension).  */
+static void
+warn_placement_new_too_small (tree type, tree nelts, tree size, tree oper)
+{
+  location_t loc = EXPR_LOC_OR_LOC (oper, input_location);
+
+  /* The number of bytes to 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),
+     the ultimate result cannot be and so the computation is done in
+     unsigned HOST_WIDE_INT.  */
+  unsigned HOST_WIDE_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
+     to placement new is not checked since it's unknown what it might
+     point to.  */
+  if (TREE_CODE (oper) == PARM_DECL
+      || TREE_CODE (oper) == VAR_DECL
+      || TREE_CODE (oper) == COMPONENT_REF)
+    return;
+
+  /* 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 comple-time constant, use it to compute a more
+	 accurate estimate of the size of the buffer.  Otherwise, use
+	 the size of the entire array as an optimistic estimate (this
+	 may lead to false negatives).  */
+      const_tree adj = TREE_OPERAND (oper, 1);
+      if (CONSTANT_CLASS_P (adj))
+	adjust += tree_to_uhwi (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)
+    {
+      /* 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 = TREE_OPERAND (oper, 1);
+      if (!use_obj_size && CONSTANT_CLASS_P (adj))
+	adjust += tree_to_uhwi (adj);
+      else
+	{
+	  use_obj_size = true;
+	  adjust = 0;
+	}
+
+      oper = TREE_OPERAND (oper, 0);
+    }
+
+  /* Descend into a struct or union to find the member whose address
+     is being used as the agument.  */
+  while (TREE_CODE (oper) == COMPONENT_REF)
+    oper = TREE_OPERAND (oper, 1);
+
+  if ((addr_expr || !POINTER_TYPE_P (TREE_TYPE (oper)))
+      && (TREE_CODE (oper) == VAR_DECL
+	  || TREE_CODE (oper) == FIELD_DECL
+	  || TREE_CODE (oper) == PARM_DECL))
+    {
+      /* A possibly optimistic estimate of the number of bytes available
+	 in the destination buffer.  */
+      unsigned HOST_WIDE_INT bytes_avail;
+      /* 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 (TREE_CODE (oper) == VAR_DECL || use_obj_size)
+	{
+	  /* 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 = tree_to_shwi (DECL_SIZE_UNIT (oper));
+	  exact_size = !use_obj_size;
+	}
+      else
+	{
+	  /* Use the size of the type of the destination buffer object
+	     as the optimistic estimate of the available space in it.  */
+	  bytes_avail = tree_to_uhwi (TYPE_SIZE_UNIT (TREE_TYPE (oper)));
+	}
+
+      /* Avoid diagnosing flexible array members (accepted as an extension
+	 and diagnosed with -Wpedantic).
+	 Constructing objects that appear to overflow the C99 equivalent of
+	 flexible array members (i.e., array members of size zero or one)
+	 are diagnosed in C++ since their declaration cannot be diagnosed.  */
+      if (bytes_avail == 0 && TREE_CODE (TREE_TYPE (oper)) == ARRAY_TYPE)
+	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)
+	bytes_avail = 0;
+      else
+	bytes_avail -= adjust;
+
+      /* 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.  */
+      unsigned HOST_WIDE_INT bytes_need;
+
+      if (CONSTANT_CLASS_P (size))
+	bytes_need = tree_to_uhwi (size);
+      else if (nelts && CONSTANT_CLASS_P (nelts))
+	  bytes_need = tree_to_uhwi (nelts)
+	    * tree_to_uhwi (TYPE_SIZE_UNIT (type));
+      else
+	bytes_need = tree_to_uhwi (TYPE_SIZE_UNIT (type));
+
+      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 [%lu]%> and size %qwu in a region of type %qT "
+			  "and size at most %qwu",
+			  type, tree_to_uhwi (nelts), bytes_need,
+			  TREE_TYPE (oper),
+			  bytes_avail);
+	    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, TREE_TYPE (oper),
+			  bytes_avail);
+	  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, TREE_TYPE (oper),
+			bytes_avail);
+	}
+    }
+}
+
 /* Generate code for a new-expression, including calling the "operator
    new" function, initializing the object, and, if an exception occurs
    during construction, cleaning up.  The arguments are as for
@@ -2518,6 +2711,8 @@  build_new_1 (vec<tree, va_gc> **placement, tree type, tree nelts,
       && (TYPE_PTR_P (TREE_TYPE ((**placement)[0]))))
     placement_first = (**placement)[0];

+  bool member_new_p = false;
+
   /* Allocate the object.  */
   if (vec_safe_is_empty (*placement) && TYPE_FOR_JAVA (elt_type))
     {
@@ -2566,11 +2761,13 @@  build_new_1 (vec<tree, va_gc> **placement, tree type, tree nelts,

       fnname = ansi_opname (array_p ? VEC_NEW_EXPR : NEW_EXPR);

-      if (!globally_qualified_p
+      member_new_p = !globally_qualified_p
 	&& CLASS_TYPE_P (elt_type)
 	&& (array_p
 	    ? TYPE_HAS_ARRAY_NEW_OPERATOR (elt_type)
-	      : TYPE_HAS_NEW_OPERATOR (elt_type)))
+	    : TYPE_HAS_NEW_OPERATOR (elt_type));
+
+      if (member_new_p)
 	{
 	  /* Use a class-specific operator new.  */
 	  /* If a cookie is required, add some extra space.  */
@@ -2645,20 +2842,30 @@  build_new_1 (vec<tree, va_gc> **placement, tree type, tree nelts,
   /* If we found a simple case of PLACEMENT_EXPR above, then copy it
      into a temporary variable.  */
   if (!processing_template_decl
-      && placement_first != NULL_TREE
       && TREE_CODE (alloc_call) == CALL_EXPR
       && call_expr_nargs (alloc_call) == 2
       && TREE_CODE (TREE_TYPE (CALL_EXPR_ARG (alloc_call, 0))) == INTEGER_TYPE
       && TYPE_PTR_P (TREE_TYPE (CALL_EXPR_ARG (alloc_call, 1))))
     {
-      tree placement_arg = CALL_EXPR_ARG (alloc_call, 1);
+      tree placement = CALL_EXPR_ARG (alloc_call, 1);

-      if (INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (TREE_TYPE (placement_arg)))
-	  || VOID_TYPE_P (TREE_TYPE (TREE_TYPE (placement_arg))))
+      if (placement_first != NULL_TREE
+	  && (INTEGRAL_OR_ENUMERATION_TYPE_P (TREE_TYPE (TREE_TYPE (placement)))
+	      || VOID_TYPE_P (TREE_TYPE (TREE_TYPE (placement)))))
 	{
 	  placement_expr = get_target_expr (placement_first);
 	  CALL_EXPR_ARG (alloc_call, 1)
-	    = convert (TREE_TYPE (placement_arg), placement_expr);
+	    = convert (TREE_TYPE (placement), placement_expr);
+	}
+
+      if (!member_new_p
+	  && VOID_TYPE_P (TREE_TYPE (TREE_TYPE (CALL_EXPR_ARG (alloc_call, 1)))))
+	{
+	  /* Attempt to make the warning point at the operator new argument.  */
+	  if (placement_first)
+	    placement = placement_first;
+
+	  warn_placement_new_too_small (orig_type, nelts, size, placement);
 	}
     }

diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 547ee2d..337639e 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -271,7 +271,7 @@  Objective-C and Objective-C++ Dialects}.
 -Woverride-init-side-effects @gol
 -Woverlength-strings  -Wpacked  -Wpacked-bitfield-compat  -Wpadded @gol
 -Wparentheses  -Wpedantic-ms-format -Wno-pedantic-ms-format @gol
--Wpointer-arith  -Wno-pointer-to-int-cast @gol
+-Wplacement-new -Wpointer-arith  -Wno-pointer-to-int-cast @gol
 -Wredundant-decls  -Wno-return-local-addr @gol
 -Wreturn-type  -Wsequence-point  -Wshadow  -Wno-shadow-ivar @gol
 -Wshift-overflow -Wshift-overflow=@var{n} @gol
@@ -4789,6 +4799,13 @@  disables the warnings about non-ISO @code{printf} / @code{scanf} format
 width specifiers @code{I32}, @code{I64}, and @code{I} used on Windows targets,
 which depend on the MS runtime.

+@item -Wplacement-new
+@opindex Wplacement-new
+@opindex Wno-placement-new
+Warn about placement new expressions with undefined behavior, such as
+constructing an object in a buffer that is smaller than the type of
+the object.
+
 @item -Wpointer-arith
 @opindex Wpointer-arith
 @opindex Wno-pointer-arith
diff --git a/gcc/testsuite/g++.dg/warn/Wplacement-new-size.C b/gcc/testsuite/g++.dg/warn/Wplacement-new-size.C
new file mode 100644
index 0000000..a8a2a68
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wplacement-new-size.C
@@ -0,0 +1,410 @@ 
+/* { dg-do compile } */
+/* { dg-options "-Wplacement-new -fpermissive" } */
+
+typedef __typeof__ (sizeof 0) size_t;
+
+void* operator new (size_t, void *p) { return p; }
+void* operator new[] (size_t, void *p) { return p; }
+
+static __attribute__ ((used))char c;
+static __attribute__ ((used))char ac1 [1];
+static __attribute__ ((used))char ac2 [2];
+static __attribute__ ((used))char ac3 [3];
+static __attribute__ ((used))char ac4 [4];
+static __attribute__ ((used))char ac5 [5];
+static __attribute__ ((used))char ac6 [6];
+static __attribute__ ((used))char ac7 [7];
+static __attribute__ ((used))char ac8 [8];
+
+static __attribute__ ((used))char ac1_1 [1][1];
+static __attribute__ ((used))char ac1_2 [1][2];
+static __attribute__ ((used))char ac2_1 [2][1];
+static __attribute__ ((used))char ac2_2 [2][2];
+
+static __attribute__ ((used))short s;
+static __attribute__ ((used))short as1 [1];
+static __attribute__ ((used))short as2 [2];
+
+static __attribute__ ((used))struct SC { char c; char *pc; void *pv; } sc;
+static __attribute__ ((used))struct SAC1 { char ac [1]; } sac1;
+static __attribute__ ((used))struct SAC2 { char ac [2]; } sac2;
+static __attribute__ ((used))struct SAC3 { char ac [3]; } sac3;
+static __attribute__ ((used))struct SAC4 { char ac [4]; } sac4;
+
+static __attribute__ ((used))struct SSC { SC sc; int x; } ssc;
+static __attribute__ ((used))struct SSAC1 { SAC1 sac; } ssac1;
+static __attribute__ ((used))struct SSAC2 { SAC2 sac; } ssac2;
+static __attribute__ ((used))struct SSAC3 { SAC3 sac; } ssac3;
+static __attribute__ ((used))struct SSAC4 { SAC4 sac; } ssac4;
+
+static __attribute__ ((used))struct SSAC4_2 { SSAC4 ssac4_2 [2]; } sssac4_2;
+
+static __attribute__ ((used))union UAC1 { char c; char ac [1]; } uac1;
+static __attribute__ ((used))union UAC2 { char c; char ac [2]; } uac2;
+static __attribute__ ((used))union UAC3 { char c; char ac [3]; } uac3;
+static __attribute__ ((used))union UAC4 { char c; char ac [4]; } uac4;
+
+static __attribute__ ((used))SC fsc ()  { return SC (); }
+static __attribute__ ((used))SAC1 fasc1 () { return SAC1 (); }
+static __attribute__ ((used))SAC2 fasc2 () { return SAC2 (); }
+static __attribute__ ((used))SAC3 fasc3 () { return SAC3 (); }
+static __attribute__ ((used))SAC4 fasc4 () { return SAC4 (); }
+
+static __attribute__ ((used))void *r;
+
+static __attribute__ ((used))void* ptr () { return 0; }
+
+static __attribute__ ((used))
+void test (void *p, int n)
+{
+    {
+        void *q = p;
+        struct { void *p; } s = { p };
+
+        // Verify that none of function arguments, local or global
+        // variables, or function return values trigger the warning.
+        new (p) char;
+        new (q) char;
+        new (r) char;
+        new (s.p) char;
+        new (ptr ()) char;
+
+        new (p) char [32];
+        new (q) char [32];
+        new (r) char [32];
+        new (s.p) char [32];
+        new (ptr ()) char [32];
+
+        new (&p) char;
+        new (&q) char;
+        new (&r) char;
+
+        // Using address of objects, however, must trigger the warning.
+        new (&p) char [32];                 // { dg-warning "placement" }
+        new (&q) char [32];                 // { dg-warning "placement" }
+        new (&r) char [32];                 // { dg-warning "placement" }
+    }
+
+    enum { N0, N1, N2, N3 };
+
+    new (&c) char;
+
+    // Warn for the common case when constructing at an offset from
+    // the beginning of an array that doesn't leave enough space for
+    // the object.
+    new (&c + 0) char;                  // okay
+    new (&c + n) char;                  // okay (n is unknown)
+    new (&c + 1) char;                  // { dg-warning "placement" }
+    new (&c + N0) char;
+    new (&c + N1) char;                 // { dg-warning "placement" }
+
+    // No warning is issued when constructing an array in space exactly
+    // its size even though strictly speaking a compiler is allowed to
+    // add a cookie to the array (gcc does not).
+    new (&c) char [1];
+    new (&c) char [sizeof c];
+    new (&c) char [n];
+    new (&c) char [1][1];
+    new (&c) char [1][1][1];
+    new (&c + N1) char [1][1][1];       // { dg-warning "placement" }
+
+    new (&c) char [2];                  // { dg-warning "placement" }
+    new (&c) char [sizeof c + 1];       // { dg-warning "placement" }
+    new (&c) char [1][2];               // { dg-warning "placement" }
+    new (&c) char [2][1];               // { dg-warning "placement" }
+    new (&c) char [n][1];
+    new (&c) char [n][2];               // { dg-warning "placement" }
+    new (&c) char [3];                  // { dg-warning "placement" }
+    new (&c) char [3][1];               // { dg-warning "placement" }
+    new (&c) char [1][3];               // { dg-warning "placement" }
+    new (&c) char [4][1];               // { dg-warning "placement" }
+    new (&c) char [1][4];               // { dg-warning "placement" }
+
+    // Casts must not suppress it.
+    new ((void*)&c) char [2];           // { dg-warning "placement" }
+    new ((char*)&c) char [3];           // { dg-warning "placement" }
+
+    new (static_cast<void*>(&c)) char [4];        // { dg-warning "placement" }
+    new (reinterpret_cast<char*>(&c)) char [5];   // { dg-warning "placement" }
+
+    new (&c + 0) char [2];              // { dg-warning "placement" }
+    new (&c + 0) char [3];              // { dg-warning "placement" }
+    new (&c + 0) char [4];              // { dg-warning "placement" }
+
+    new (&c + 1) char [2];              // { dg-warning "placement" }
+    new (&c + 1) char [3];              // { dg-warning "placement" }
+    new (&c + 1) char [4];              // { dg-warning "placement" }
+
+    new (&c + N0) char [1];
+    new (&c + N1) char [2];             // { dg-warning "placement" }
+
+    // Warn even though n is unknown since c is too small for char[2]
+    // regardless of the value of n.
+    new (&c + n) char [2];              // { dg-warning "placement" }
+
+    new (ac2) char [1];
+    new (ac2) char [1][1];
+    new (ac2) char [1][2];
+    new (ac2) char [2][1];
+    new (ac2) char [1][3];              // { dg-warning "placement" }
+    new (ac2) char [2][2];              // { dg-warning "placement" }
+    new (ac2) char [3][1];              // { dg-warning "placement" }
+
+    new (ac2 + N0) char [1][1];
+    new (ac2 + N0) char [1][2];
+    new (ac2 + N1) char [1][2];         // { dg-warning "placement" }
+    new (ac2 + N1) char [2][1];         // { dg-warning "placement" }
+    new (ac2 + N2) char [1][1];         // { dg-warning "placement" }
+    new (ac2 + N2) char [1][2];         // { dg-warning "placement" }
+    new (ac2 + N2) char [2][1];         // { dg-warning "placement" }
+    new (ac2 + N2) char [2][2];         // { dg-warning "placement" }
+
+    new (ac8) char [1];
+    new (ac8) char [2][2];
+    new (ac8) char [2][3];
+    new (ac8) char [2][4];
+    new (ac8) char [2][5];              // { dg-warning "placement" }
+    new (ac8) char [2][2][2];
+    new (ac8) char [2][2][3];           // { dg-warning "placement" }
+
+    new (&c) int;                       // { dg-warning "placement" }
+
+    new (&ac1) int;                     // { dg-warning "placement" }
+    new (&ac2) int;                     // { dg-warning "placement" }
+    new (&ac3) int;                     // { dg-warning "placement" }
+    new (&ac4) int;
+
+    // Constructing at an address of an array element.
+    new (&ac1 [0]) int;                 // { dg-warning "placement" }
+    new (&ac2 [0]) int;                 // { dg-warning "placement" }
+    new (&ac3 [0]) int;                 // { dg-warning "placement" }
+    new (&ac4 [0]) int;
+
+    // ...plus or minus a constant offset.
+    new (&ac1 [0] + 0) int;             // { dg-warning "placement" }
+    new (&ac2 [0] + 0) int;             // { dg-warning "placement" }
+    new (&ac3 [0] + 0) int;             // { dg-warning "placement" }
+    new (&ac4 [0] + 0) int;
+    new (&ac4 [1] + 0) int;             // { dg-warning "placement" }
+    new (&ac4 [1] - 1) int;
+    new (&ac4 [2] - 1) int;             // { dg-warning "placement" }
+    new (&ac4 [2] - 2) int;
+    new (&ac4 [3] - 1) int;             // { dg-warning "placement" }
+    new (&ac4 [3] - 2) int;             // { dg-warning "placement" }
+    new (&ac4 [3] - 3) int;
+    new (&ac4 [4] - 1) int;             // { dg-warning "placement" }
+    new (&ac4 [4] - 2) int;             // { dg-warning "placement" }
+    new (&ac4 [4] - 3) int;             // { dg-warning "placement" }
+    new (&ac4 [4] - 4) int;
+
+    new (&ac1 [0] + 1) int;             // { dg-warning "placement" }
+    new (&ac2 [0] + 1) int;             // { dg-warning "placement" }
+    new (&ac3 [0] + 1) int;             // { dg-warning "placement" }
+    new (&ac4 [0] + 1) int;             // { dg-warning "placement" }
+
+    new (&ac3 [0] + n) int;             // { dg-warning "placement" }
+    new (&ac4 [0] + n) int;             // no warning (n could be zero)
+    new (&ac4 [1] + n) int;             // no warning (n could be negative)
+    new (&ac4 [2] + n) int;             // ditto
+    new (&ac4 [3] + n) int;             // ditto
+    new (&ac4 [4] + n) int;             // ditto
+    new (&ac4 [4] - n) int;             // (or positive)
+
+    new (&c + 0) int;                   // { dg-warning "placement" }
+    new (&c + 1) int;                   // { dg-warning "placement" }
+
+    // Constructing at an offset into the address of an array.
+    new (&ac1 + 0) int;                 // { dg-warning "placement" }
+    new (&ac1 + 1) int;                 // { dg-warning "placement" }
+    new (&ac1 + n) int;                 // { dg-warning "placement" }
+    new (&ac2 + 0) int;                 // { dg-warning "placement" }
+    new (&ac2 + 1) int;                 // { dg-warning "placement" }
+    new (&ac2 + n) int;                 // { dg-warning "placement" }
+    new (&ac3 + 0) int;                 // { dg-warning "placement" }
+    new (&ac3 + 1) int;                 // { dg-warning "placement" }
+
+    // Even though n below is uknown an array of 3 bytes isn't large
+    // enugh for an int.
+    new (&ac3 + n) int;                 // { dg-warning "placement" }
+
+    new (&ac4 + 0) int;
+    new (&ac4 + 1) int;                 // { dg-warning "placement" }
+    new (&ac4 + n) int;                 // no warning (n could be zero)
+
+    // Constructing in an array object.
+    new (ac1) int;                      // { dg-warning "placement" }
+    new (ac2) int;                      // { dg-warning "placement" }
+    new (ac3) int;                      // { dg-warning "placement" }
+    new (ac4) int;
+    new (ac5) int;
+    new (ac5 + 0) int;
+    new (ac5 + 1) int;
+    new (ac5 + n) int;                  // no warning (n could be zero)
+    new (ac5 + 2) int;                  // { dg-warning "placement" }
+    new (ac5 + 3) int;                  // { dg-warning "placement" }
+    new (ac5 + 4) int;                  // { dg-warning "placement" }
+    new (ac5 + 5) int;                  // { dg-warning "placement" }
+
+    new (ac1_1) char;
+    new (ac1_1) char[1];
+    new (ac1_1) char[n];                // no warning (n is unknown)
+    new (ac1_1) char[2];                // { dg-warning "placement" }
+    new (ac1_1) char[3];                // { dg-warning "placement" }
+
+    new (ac1_2) char;
+    new (ac1_2) char[1];
+    new (ac1_2) char[2];
+    new (ac1_2) char[3];                // { dg-warning "placement" }
+
+    new (ac2_1) char;
+    new (ac2_1) char[1];
+    new (ac2_1) char[2];
+    new (ac2_1) char[3];                // { dg-warning "placement" }
+
+    new (ac2_2) char;
+    new (ac2_2) char[1];
+    new (ac2_2) char[2];
+    new (ac2_2) char[2][2];
+
+    // Even though n below is uknown it can't meaningfully be zero
+    // (even if zero-size arrays are allowed as an extension, the size
+    // they are allocated in by placement new is zero).
+    new (ac1_1) char[n][2];             // { dg-warning "placement" }
+    new (ac2_2) char[3];
+    new (ac2_2) char[3][1];
+    new (ac2_2) char[3][2];             // { dg-warning "placement" }
+    new (ac2_2) char[4];
+    new (ac2_2) char[4][1];
+    new (ac2_2) char[4][2];             // { dg-warning "placement" }
+    new (ac2_2) char[5];                // { dg-warning "placement" }
+
+    new (&s) int;                       // { dg-warning "placement" }
+    new (&as1) int;                     // { dg-warning "placement" }
+    new (&as2) int;
+
+    new (as1) int;                      // { dg-warning "placement" }
+    new (as2) int;
+
+    new (&sc.c) int;                    // { dg-warning "placement" }
+    new (&sac1.ac) int;                 // { dg-warning "placement" }
+    new (&sac2.ac) int;                 // { dg-warning "placement" }
+    new (&sac3.ac) int;                 // { dg-warning "placement" }
+    new (&sac4.ac) int;
+
+    new (sc.pc) char;
+    new (sc.pc) int;
+    new (sc.pc) int[1024];
+    new (sc.pc + 0) int;
+    new (sc.pc + 0) int[2048];
+    new (sc.pv) int;
+    new (sc.pv) char[1024];
+
+    new (sac1.ac) int;                  // { dg-warning "placement" }
+    new (sac2.ac) int;                  // { dg-warning "placement" }
+    new (sac3.ac) int;                  // { dg-warning "placement" }
+    new (sac4.ac) int;
+
+    new (&ssc.sc) SSC;                  // { dg-warning "placement" }
+    new (&ssac1.sac) int;               // { dg-warning "placement" }
+    new (&ssac2.sac) int;               // { dg-warning "placement" }
+    new (&ssac3.sac) int;               // { dg-warning "placement" }
+    new (&ssac4.sac) int;
+
+    new (&sssac4_2) char[sizeof sssac4_2];
+    new (&sssac4_2) char[sizeof sssac4_2 + 1];   // { dg-warning "placement" }
+
+    // taking the address of a temporary is allowed with -fpermissive
+    new (&fsc ().c) int;                // { dg-warning "address|placement" }
+    new (&fasc1 ().ac) int;             // { dg-warning "address|placement" }
+    new (&fasc2 ().ac) int;             // { dg-warning "address|placement" }
+    new (&fasc3 ().ac) int;             // { dg-warning "address|placement" }
+    new (&fasc4 ().ac) int;             // { dg-warning "address|placement" }
+
+    new (&uac1) int;                    // { dg-warning "placement" }
+    new (&uac2) int;                    // { dg-warning "placement" }
+    new (&uac3) int;                    // { dg-warning "placement" }
+    new (&uac4) int;
+    new (&uac4 + 1) int;                // { dg-warning "placement" }
+
+    new (&uac1.c) int;                  // { dg-warning "placement" }
+    new (&uac2.c) int;                  // { dg-warning "placement" }
+    new (&uac3.c) int;                  // { dg-warning "placement" }
+
+    // Diagnose the following even though the size of uac4.c could be
+    // expected to extend to the end of the union (as it is by Built-in
+    // Object Size and so isn't diagnosed in calls to functions like
+    // memset(&uac4.c, 0, sizeof(int)) when _FORTIFY_SOURCE is non-zero.  */
+    new (&uac4.c) int;                  // { dg-warning "placement" }
+
+    new (&uac4.c + 1) int;              // { dg-warning "placement" }
+}
+
+
+struct S { char c [2]; };
+
+// Verify the full text of the warning message.
+static  __attribute__ ((used))
+void test_message (int i)
+{
+    char a [2];
+
+    // The exact sizes of both the buffer and the type are known.
+    new (a + 1) S;         // { dg-warning "placement new constructing an object of type .S. and size .2. in a region of type .char \\\[2\\\]. and size .1." }
+
+    // The buffer size is known but only the size of the type whose
+    // objects are being constructed is known, not their number.  While
+    // in theory it could be zero, it practice likely never will be so
+    // the potential false positive is acceptable.
+    new (a + 1) S [i];  // { dg-warning "placement new constructing an array of objects of type .S. and size .2. in a region of type .char \\\[2\\\]. and size .1." }
+
+    // The exact amount of space in the buffer isn't known, only its
+    // maximum is.  The exact size of the array being created is known.
+    new (a + i) S [2];  // { dg-warning "placement new constructing an object of type .S \\\[2\\\]. and size .4. in a region of type .char \\\[2\\\]. and size at most .2." }
+}
+
+
+struct ClassWithMemberNew {
+    struct Object { int i; } *pobj;
+    unsigned nobj;
+
+    ClassWithMemberNew ();
+    void foo ();
+
+    void* operator new (size_t, void*);
+    void* operator new[] (size_t, void*);
+};
+
+void ClassWithMemberNew::foo()
+{
+    for (unsigned i = 0; i != nobj; ++i)
+        new (pobj + i) Object ();
+}
+
+
+struct ClassWithGlobalNew {
+    int a [4];
+    ClassWithGlobalNew ();
+};
+
+void* operator new (size_t, ClassWithGlobalNew*);
+void* operator new[] (size_t, ClassWithGlobalNew*);
+
+void test_user_defined_placement_new ()
+{
+    {
+        ClassWithMemberNew x;
+
+        // Expect no diagnostics for placement new expressions with types
+        // with their own placement operator new since the semantics of
+        // the operator aren't known.
+        new (&c) ClassWithMemberNew;
+        new (&x) ClassWithMemberNew[2];
+    }
+
+    {
+        ClassWithGlobalNew x;
+
+        new (&c) ClassWithGlobalNew;    // { dg-warning "placement" }
+        new (&x) ClassWithGlobalNew[2];
+    }
+}