[C++] P0784R7 constexpr new further fix (PR c++/91369)
diff mbox series

Message ID 20191031171019.GM4650@tucnak
State New
Headers show
Series
  • [C++] P0784R7 constexpr new further fix (PR c++/91369)
Related show

Commit Message

Jakub Jelinek Oct. 31, 2019, 5:10 p.m. UTC
On Wed, Oct 30, 2019 at 11:05:29PM +0100, Jakub Jelinek wrote:
> > Looks like there used to be a TREE_CALLS_NEW flag in TREE_LANG_FLAG_1, but
> > that flag is now free for CALL_EXPR.
> 
> I'll try a CALL_EXPR flag first.

TREE_LANG_FLAG_1 is also STMT_IS_FULL_EXPR_P, and while it is mostly guarded
with STATEMENT_CODE_P checks, in add_stmt it is not.  Are we sure that we
never add_stmt a CALL_EXPR?

I've picked up TREE_LANG_FLAG_2 instead which looked unused on CALL_EXPR.

Below is the patch that rejects ::operator new and ::operator delete
calls used directly by users (with the STL exteption), but doesn't try to
diagnose what is discussed below.
Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

> > > Another thing is that even with that change,
> > >    std::allocator<int> a;
> > >    auto p = a.allocate (1);
> > >    *p = 1;
> > >    a.deallocate (p, 1);
> > > would be accepted during constexpr evaluation, because allocate already
> > > has the cast which turns "heap uninit" variable into "heap " and assigns
> > > it a type, so there is nothing that will prevent the store from succeeding.
> > 
> > What's wrong with the store?
> 
> If it is fine, the better.  I'd still think that
>   struct S { constexpr S () : s (0) {}; int s; };
>   std::allocator<S> a;
>   auto p = a.allocate (1);
>   p->s = 1;
>   a.deallocate (p, 1);
> would still be invalid though, because that type needs constructing and
> the construction didn't happen in that case.  Or:
>   struct S { constexpr S () : s (0) {}; int s; };
>   std::allocator<S> a;
>   auto p = a.allocate (1);
>   std::construct_at (p);
>   p->s++; // ok
>   std::destruct_at (p);
>   p->s = 1; // shouldn't this be an error?
>   a.deallocate (p, 1);
> but I admit I haven't tried to back that up by some standard wording, just a
> general principle that objects with TYPE_ADDRESSABLE types need to be
> constructed first and destructed last and accesses to it are only valid in
> between those in normal code and constexpr should flag any UB as errors.

So, are objects in the heap with vacuous initialization ok and objects with
non-trivial constructors not ok without std::construct_at to begin the
lifetime?
What about objects with trivial constructor but non-trivial constexpr destructor, is
it ok to set them after std::destruct_at or direct p->~S (); invocation
without an intervening std::construct_at?
Also, shouldn't it be invalid if objects with non-trivial destructor are
constructed using std::construct_at but not destructed before deallocate?

2019-10-31  Jakub Jelinek  <jakub@redhat.com>

	PR c++/91369 - Implement P0784R7: constexpr new
	* cp-tree.h (CALL_FROM_NEW_OR_DELETE_P): Define.
	* init.c (build_new_1, build_vec_delete_1, build_delete): Set
	CALL_FROM_NEW_OR_DELETE_P on the CALL_EXPR to allocator functions.
	* constexpr.c (is_std_allocator_allocate): Only allow
	global replaceable allocator functions if CALL_FROM_NEW_OR_DELETE_P
	or in std::allocate<T>::{,de}allocate.
	(potential_constant_expression_1): Likewise.

	* g++.dg/cpp2a/constexpr-new6.C: New test.
	* g++.dg/cpp2a/constexpr-new7.C: New test.



	Jakub

Comments

Jason Merrill Nov. 1, 2019, 7:20 p.m. UTC | #1
On 10/31/19 1:10 PM, Jakub Jelinek wrote:
> On Wed, Oct 30, 2019 at 11:05:29PM +0100, Jakub Jelinek wrote:
>>> Looks like there used to be a TREE_CALLS_NEW flag in TREE_LANG_FLAG_1, but
>>> that flag is now free for CALL_EXPR.
>>
>> I'll try a CALL_EXPR flag first.
> 
> TREE_LANG_FLAG_1 is also STMT_IS_FULL_EXPR_P, and while it is mostly guarded
> with STATEMENT_CODE_P checks, in add_stmt it is not.  Are we sure that we
> never add_stmt a CALL_EXPR?

I wouldn't think so, but we might add the check there too.

> I've picked up TREE_LANG_FLAG_2 instead which looked unused on CALL_EXPR.

OK.

> Below is the patch that rejects ::operator new and ::operator delete
> calls used directly by users (with the STL exteption), but doesn't try to
> diagnose what is discussed below.
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

OK.

>>>> Another thing is that even with that change,
>>>>     std::allocator<int> a;
>>>>     auto p = a.allocate (1);
>>>>     *p = 1;
>>>>     a.deallocate (p, 1);
>>>> would be accepted during constexpr evaluation, because allocate already
>>>> has the cast which turns "heap uninit" variable into "heap " and assigns
>>>> it a type, so there is nothing that will prevent the store from succeeding.
>>>
>>> What's wrong with the store?
>>
>> If it is fine, the better.  I'd still think that
>>    struct S { constexpr S () : s (0) {}; int s; };
>>    std::allocator<S> a;
>>    auto p = a.allocate (1);
>>    p->s = 1;
>>    a.deallocate (p, 1);
>> would still be invalid though, because that type needs constructing and
>> the construction didn't happen in that case.  Or:
>>    struct S { constexpr S () : s (0) {}; int s; };
>>    std::allocator<S> a;
>>    auto p = a.allocate (1);
>>    std::construct_at (p);
>>    p->s++; // ok
>>    std::destruct_at (p);
>>    p->s = 1; // shouldn't this be an error?
>>    a.deallocate (p, 1);
>> but I admit I haven't tried to back that up by some standard wording, just a
>> general principle that objects with TYPE_ADDRESSABLE types need to be
>> constructed first and destructed last and accesses to it are only valid in
>> between those in normal code and constexpr should flag any UB as errors.
> 
> So, are objects in the heap with vacuous initialization ok and objects with
> non-trivial constructors not ok without std::construct_at to begin the
> lifetime?

Correct.

> What about objects with trivial constructor but non-trivial constexpr destructor, is
> it ok to set them after std::destruct_at or direct p->~S (); invocation
> without an intervening std::construct_at?

That would seem to follow.

> Also, shouldn't it be invalid if objects with non-trivial destructor are
> constructed using std::construct_at but not destructed before deallocate?

It doesn't seem so: "For an object of a class type, the program is not 
required to call the destructor explicitly before the storage which the 
object occupies is reused or released; however, if there is no explicit 
call to the destructor or if a delete-expression is not used to release 
the storage, the destructor is not implicitly called and any program 
that depends on the side effects produced by the destructor has 
undefined behavior."

> 2019-10-31  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR c++/91369 - Implement P0784R7: constexpr new
> 	* cp-tree.h (CALL_FROM_NEW_OR_DELETE_P): Define.
> 	* init.c (build_new_1, build_vec_delete_1, build_delete): Set
> 	CALL_FROM_NEW_OR_DELETE_P on the CALL_EXPR to allocator functions.
> 	* constexpr.c (is_std_allocator_allocate): Only allow
> 	global replaceable allocator functions if CALL_FROM_NEW_OR_DELETE_P
> 	or in std::allocate<T>::{,de}allocate.
> 	(potential_constant_expression_1): Likewise.
> 
> 	* g++.dg/cpp2a/constexpr-new6.C: New test.
> 	* g++.dg/cpp2a/constexpr-new7.C: New test.
> 
> --- gcc/cp/cp-tree.h.jj	2019-10-31 08:10:14.515368058 +0100
> +++ gcc/cp/cp-tree.h	2019-10-31 09:00:45.798418129 +0100
> @@ -448,6 +448,7 @@ extern GTY(()) tree cp_global_trees[CPTI
>         LAMBDA_EXPR_CAPTURE_OPTIMIZED (in LAMBDA_EXPR)
>         IMPLICIT_CONV_EXPR_BRACED_INIT (in IMPLICIT_CONV_EXPR)
>         TINFO_VAR_DECLARED_CONSTINIT (in TEMPLATE_INFO)
> +      CALL_FROM_NEW_OR_DELETE_P (in CALL_EXPR)
>      3: (TREE_REFERENCE_EXPR) (in NON_LVALUE_EXPR) (commented-out).
>         ICS_BAD_FLAG (in _CONV)
>         FN_TRY_BLOCK_P (in TRY_BLOCK)
> @@ -3791,6 +3792,11 @@ struct GTY(()) lang_decl {
>      should be performed at instantiation time.  */
>   #define KOENIG_LOOKUP_P(NODE) TREE_LANG_FLAG_0 (CALL_EXPR_CHECK (NODE))
>   
> +/* In a CALL_EXPR, true for allocator calls from new or delete
> +   expressions.  */
> +#define CALL_FROM_NEW_OR_DELETE_P(NODE) \
> +  TREE_LANG_FLAG_2 (CALL_EXPR_CHECK (NODE))
> +
>   /* True if the arguments to NODE should be evaluated in left-to-right
>      order regardless of PUSH_ARGS_REVERSED.  */
>   #define CALL_EXPR_ORDERED_ARGS(NODE) \
> --- gcc/cp/init.c.jj	2019-10-05 09:36:39.249674512 +0200
> +++ gcc/cp/init.c	2019-10-31 09:25:11.870698471 +0100
> @@ -3404,6 +3404,10 @@ build_new_1 (vec<tree, va_gc> **placemen
>   	}
>       }
>   
> +  tree alloc_call_expr = extract_call_expr (alloc_call);
> +  if (TREE_CODE (alloc_call_expr) == CALL_EXPR)
> +    CALL_FROM_NEW_OR_DELETE_P (alloc_call_expr) = 1;
> +
>     if (cookie_size)
>       alloc_call = maybe_wrap_new_for_constexpr (alloc_call, elt_type,
>   					       cookie_size);
> @@ -4046,6 +4050,10 @@ build_vec_delete_1 (tree base, tree maxi
>   					      /*placement=*/NULL_TREE,
>   					      /*alloc_fn=*/NULL_TREE,
>   					      complain);
> +
> +      tree deallocate_call_expr = extract_call_expr (deallocate_expr);
> +      if (TREE_CODE (deallocate_call_expr) == CALL_EXPR)
> +	CALL_FROM_NEW_OR_DELETE_P (deallocate_call_expr) = 1;
>       }
>   
>     body = loop;
> @@ -4955,6 +4963,13 @@ build_delete (tree otype, tree addr, spe
>     if (!deleting)
>       return expr;
>   
> +  if (do_delete)
> +    {
> +      tree do_delete_call_expr = extract_call_expr (do_delete);
> +      if (TREE_CODE (do_delete_call_expr) == CALL_EXPR)
> +	CALL_FROM_NEW_OR_DELETE_P (do_delete_call_expr) = 1;
> +    }
> +
>     if (do_delete && !TREE_SIDE_EFFECTS (expr))
>       expr = do_delete;
>     else if (do_delete)
> --- gcc/cp/constexpr.c.jj	2019-10-30 22:54:29.574952119 +0100
> +++ gcc/cp/constexpr.c	2019-10-31 10:12:27.074761211 +0100
> @@ -1638,6 +1638,28 @@ is_std_construct_at (tree fndecl)
>     return name && id_equal (name, "construct_at");
>   }
>   
> +/* Return true if FNDECL is std::allocator<T>::{,de}allocate.  */
> +
> +static inline bool
> +is_std_allocator_allocate (tree fndecl)
> +{
> +  tree name = DECL_NAME (fndecl);
> +  if (name == NULL_TREE
> +      || !(id_equal (name, "allocate") || id_equal (name, "deallocate")))
> +    return false;
> +
> +  tree ctx = DECL_CONTEXT (fndecl);
> +  if (ctx == NULL_TREE || !CLASS_TYPE_P (ctx) || !TYPE_MAIN_DECL (ctx))
> +    return false;
> +
> +  tree decl = TYPE_MAIN_DECL (ctx);
> +  name = DECL_NAME (decl);
> +  if (name == NULL_TREE || !id_equal (name, "allocator"))
> +    return false;
> +
> +  return decl_in_std_namespace_p (decl);
> +}
> +
>   /* Subroutine of cxx_eval_constant_expression.
>      Evaluate the call expression tree T in the context of OLD_CALL expression
>      evaluation.  */
> @@ -1716,7 +1738,12 @@ cxx_eval_call_expression (const constexp
>   					   lval, non_constant_p, overflow_p);
>     if (!DECL_DECLARED_CONSTEXPR_P (fun))
>       {
> -      if (cxx_replaceable_global_alloc_fn (fun))
> +      if (TREE_CODE (t) == CALL_EXPR
> +	  && cxx_replaceable_global_alloc_fn (fun)
> +	  && (CALL_FROM_NEW_OR_DELETE_P (t)
> +	      || (ctx->call
> +		  && ctx->call->fundef
> +		  && is_std_allocator_allocate (ctx->call->fundef->decl))))
>   	{
>   	  const int nargs = call_expr_nargs (t);
>   	  tree arg0 = NULL_TREE;
> @@ -1774,7 +1801,8 @@ cxx_eval_call_expression (const constexp
>   	}
>         /* Allow placement new in std::construct_at, just return the second
>   	 argument.  */
> -      if (cxx_placement_new_fn (fun)
> +      if (TREE_CODE (t) == CALL_EXPR
> +	  && cxx_placement_new_fn (fun)
>   	  && ctx->call
>   	  && ctx->call->fundef
>   	  && is_std_construct_at (ctx->call->fundef->decl))
> @@ -6508,9 +6536,15 @@ potential_constant_expression_1 (tree t,
>   		    && !fndecl_built_in_p (fun)
>   		    /* In C++2a, replaceable global allocation functions
>   		       are constant expressions.  */
> -		    && !cxx_replaceable_global_alloc_fn (fun)
> +		    && (!cxx_replaceable_global_alloc_fn (fun)
> +			|| TREE_CODE (t) != CALL_EXPR
> +			|| (!CALL_FROM_NEW_OR_DELETE_P (t)
> +			    && (current_function_decl == NULL_TREE
> +				|| !is_std_allocator_allocate
> +						(current_function_decl))))
>   		    /* Allow placement new in std::construct_at.  */
>   		    && (!cxx_placement_new_fn (fun)
> +			|| TREE_CODE (t) != CALL_EXPR
>   			|| current_function_decl == NULL_TREE
>   			|| !is_std_construct_at (current_function_decl)))
>   		  {
> --- gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C.jj	2019-10-31 09:53:48.606090420 +0100
> +++ gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C	2019-10-31 10:59:59.168577427 +0100
> @@ -0,0 +1,83 @@
> +// P0784R7
> +// { dg-do compile { target c++2a } }
> +
> +namespace std
> +{
> +  inline namespace _8 { }
> +  namespace _8 {
> +
> +    typedef __SIZE_TYPE__ size_t;
> +
> +    template <typename T>
> +    struct allocator
> +    {
> +      constexpr allocator () noexcept {}
> +
> +      constexpr T *allocate (size_t n)
> +      { return static_cast<T *> (::operator new (n * sizeof(T))); }
> +
> +      constexpr void
> +      deallocate (T *p, size_t n)
> +      { ::operator delete (p); }
> +    };
> +
> +    template <typename T, typename U = T &&>
> +    U __declval (int);
> +    template <typename T>
> +    T __declval (long);
> +    template <typename T>
> +    auto declval () noexcept -> decltype (__declval<T> (0));
> +
> +    template <typename T>
> +    struct remove_reference
> +    { typedef T type; };
> +    template <typename T>
> +    struct remove_reference<T &>
> +    { typedef T type; };
> +    template <typename T>
> +    struct remove_reference<T &&>
> +    { typedef T type; };
> +
> +    template <typename T>
> +    constexpr T &&
> +    forward (typename std::remove_reference<T>::type &t) noexcept
> +    { return static_cast<T&&> (t); }
> +
> +    template<typename T>
> +    constexpr T &&
> +    forward (typename std::remove_reference<T>::type &&t) noexcept
> +    { return static_cast<T&&> (t); }
> +
> +    template <typename T, typename... A>
> +    constexpr auto
> +    construct_at (T *l, A &&... a)
> +    noexcept (noexcept (::new ((void *) 0) T (std::declval<A> ()...)))
> +    -> decltype (::new ((void *) 0) T (std::declval<A> ()...))
> +    { return ::new ((void *) l) T (std::forward<A> (a)...); }
> +
> +    template <typename T>
> +    constexpr inline void
> +    destroy_at (T *l)
> +    { l->~T (); }
> +  }
> +}
> +
> +inline void *operator new (std::size_t, void *p) noexcept
> +{ return p; }
> +
> +constexpr bool
> +foo ()
> +{
> +  std::allocator<int> a;
> +  auto p = a.allocate (2);
> +  std::construct_at (p, 1);
> +  std::construct_at (p + 1, 2);
> +  if (p[0] != 1 || p[1] != 2)
> +    throw 1;
> +  std::destroy_at (p);
> +  std::destroy_at (p + 1);
> +  a.deallocate (p, 2);
> +  return true;
> +}
> +
> +static_assert (foo ());
> --- gcc/testsuite/g++.dg/cpp2a/constexpr-new7.C.jj	2019-10-31 10:58:33.227908692 +0100
> +++ gcc/testsuite/g++.dg/cpp2a/constexpr-new7.C	2019-10-31 10:14:10.702155762 +0100
> @@ -0,0 +1,33 @@
> +// P0784R7
> +// { dg-do compile { target c++2a } }
> +
> +namespace std
> +{
> +  typedef __SIZE_TYPE__ size_t;
> +}
> +
> +inline void *operator new (std::size_t, void *p) noexcept
> +{ return p; }
> +void *operator new (std::size_t) noexcept;
> +
> +constexpr bool
> +foo ()
> +{
> +  auto p = static_cast<int *> (::operator new (sizeof (int)));	// { dg-error "call to non-'constexpr' function" }
> +  *p = 1;
> +  ::operator delete (p);
> +  return false;
> +}
> +
> +struct S { constexpr S () : s (0) {} int s; };
> +
> +constexpr bool
> +bar ()
> +{
> +  auto p = static_cast<S *> (::operator new (sizeof (S)));	// { dg-error "call to non-'constexpr' function" }
> +  auto q = new (p) S ();
> +  q->s++;
> +  q->~S ();
> +  ::operator delete (p);
> +  return false;
> +}
> 
> 
> 	Jakub
>

Patch
diff mbox series

--- gcc/cp/cp-tree.h.jj	2019-10-31 08:10:14.515368058 +0100
+++ gcc/cp/cp-tree.h	2019-10-31 09:00:45.798418129 +0100
@@ -448,6 +448,7 @@  extern GTY(()) tree cp_global_trees[CPTI
       LAMBDA_EXPR_CAPTURE_OPTIMIZED (in LAMBDA_EXPR)
       IMPLICIT_CONV_EXPR_BRACED_INIT (in IMPLICIT_CONV_EXPR)
       TINFO_VAR_DECLARED_CONSTINIT (in TEMPLATE_INFO)
+      CALL_FROM_NEW_OR_DELETE_P (in CALL_EXPR)
    3: (TREE_REFERENCE_EXPR) (in NON_LVALUE_EXPR) (commented-out).
       ICS_BAD_FLAG (in _CONV)
       FN_TRY_BLOCK_P (in TRY_BLOCK)
@@ -3791,6 +3792,11 @@  struct GTY(()) lang_decl {
    should be performed at instantiation time.  */
 #define KOENIG_LOOKUP_P(NODE) TREE_LANG_FLAG_0 (CALL_EXPR_CHECK (NODE))
 
+/* In a CALL_EXPR, true for allocator calls from new or delete
+   expressions.  */
+#define CALL_FROM_NEW_OR_DELETE_P(NODE) \
+  TREE_LANG_FLAG_2 (CALL_EXPR_CHECK (NODE))
+
 /* True if the arguments to NODE should be evaluated in left-to-right
    order regardless of PUSH_ARGS_REVERSED.  */
 #define CALL_EXPR_ORDERED_ARGS(NODE) \
--- gcc/cp/init.c.jj	2019-10-05 09:36:39.249674512 +0200
+++ gcc/cp/init.c	2019-10-31 09:25:11.870698471 +0100
@@ -3404,6 +3404,10 @@  build_new_1 (vec<tree, va_gc> **placemen
 	}
     }
 
+  tree alloc_call_expr = extract_call_expr (alloc_call);
+  if (TREE_CODE (alloc_call_expr) == CALL_EXPR)
+    CALL_FROM_NEW_OR_DELETE_P (alloc_call_expr) = 1;
+
   if (cookie_size)
     alloc_call = maybe_wrap_new_for_constexpr (alloc_call, elt_type,
 					       cookie_size);
@@ -4046,6 +4050,10 @@  build_vec_delete_1 (tree base, tree maxi
 					      /*placement=*/NULL_TREE,
 					      /*alloc_fn=*/NULL_TREE,
 					      complain);
+
+      tree deallocate_call_expr = extract_call_expr (deallocate_expr);
+      if (TREE_CODE (deallocate_call_expr) == CALL_EXPR)
+	CALL_FROM_NEW_OR_DELETE_P (deallocate_call_expr) = 1;
     }
 
   body = loop;
@@ -4955,6 +4963,13 @@  build_delete (tree otype, tree addr, spe
   if (!deleting)
     return expr;
 
+  if (do_delete)
+    {
+      tree do_delete_call_expr = extract_call_expr (do_delete);
+      if (TREE_CODE (do_delete_call_expr) == CALL_EXPR)
+	CALL_FROM_NEW_OR_DELETE_P (do_delete_call_expr) = 1;
+    }
+
   if (do_delete && !TREE_SIDE_EFFECTS (expr))
     expr = do_delete;
   else if (do_delete)
--- gcc/cp/constexpr.c.jj	2019-10-30 22:54:29.574952119 +0100
+++ gcc/cp/constexpr.c	2019-10-31 10:12:27.074761211 +0100
@@ -1638,6 +1638,28 @@  is_std_construct_at (tree fndecl)
   return name && id_equal (name, "construct_at");
 }
 
+/* Return true if FNDECL is std::allocator<T>::{,de}allocate.  */
+
+static inline bool
+is_std_allocator_allocate (tree fndecl)
+{
+  tree name = DECL_NAME (fndecl);
+  if (name == NULL_TREE
+      || !(id_equal (name, "allocate") || id_equal (name, "deallocate")))
+    return false;
+
+  tree ctx = DECL_CONTEXT (fndecl);
+  if (ctx == NULL_TREE || !CLASS_TYPE_P (ctx) || !TYPE_MAIN_DECL (ctx))
+    return false;
+
+  tree decl = TYPE_MAIN_DECL (ctx);
+  name = DECL_NAME (decl);
+  if (name == NULL_TREE || !id_equal (name, "allocator"))
+    return false;
+
+  return decl_in_std_namespace_p (decl);
+}
+
 /* Subroutine of cxx_eval_constant_expression.
    Evaluate the call expression tree T in the context of OLD_CALL expression
    evaluation.  */
@@ -1716,7 +1738,12 @@  cxx_eval_call_expression (const constexp
 					   lval, non_constant_p, overflow_p);
   if (!DECL_DECLARED_CONSTEXPR_P (fun))
     {
-      if (cxx_replaceable_global_alloc_fn (fun))
+      if (TREE_CODE (t) == CALL_EXPR
+	  && cxx_replaceable_global_alloc_fn (fun)
+	  && (CALL_FROM_NEW_OR_DELETE_P (t)
+	      || (ctx->call
+		  && ctx->call->fundef
+		  && is_std_allocator_allocate (ctx->call->fundef->decl))))
 	{
 	  const int nargs = call_expr_nargs (t);
 	  tree arg0 = NULL_TREE;
@@ -1774,7 +1801,8 @@  cxx_eval_call_expression (const constexp
 	}
       /* Allow placement new in std::construct_at, just return the second
 	 argument.  */
-      if (cxx_placement_new_fn (fun)
+      if (TREE_CODE (t) == CALL_EXPR
+	  && cxx_placement_new_fn (fun)
 	  && ctx->call
 	  && ctx->call->fundef
 	  && is_std_construct_at (ctx->call->fundef->decl))
@@ -6508,9 +6536,15 @@  potential_constant_expression_1 (tree t,
 		    && !fndecl_built_in_p (fun)
 		    /* In C++2a, replaceable global allocation functions
 		       are constant expressions.  */
-		    && !cxx_replaceable_global_alloc_fn (fun)
+		    && (!cxx_replaceable_global_alloc_fn (fun)
+			|| TREE_CODE (t) != CALL_EXPR
+			|| (!CALL_FROM_NEW_OR_DELETE_P (t)
+			    && (current_function_decl == NULL_TREE
+				|| !is_std_allocator_allocate
+						(current_function_decl))))
 		    /* Allow placement new in std::construct_at.  */
 		    && (!cxx_placement_new_fn (fun)
+			|| TREE_CODE (t) != CALL_EXPR
 			|| current_function_decl == NULL_TREE
 			|| !is_std_construct_at (current_function_decl)))
 		  {
--- gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C.jj	2019-10-31 09:53:48.606090420 +0100
+++ gcc/testsuite/g++.dg/cpp2a/constexpr-new6.C	2019-10-31 10:59:59.168577427 +0100
@@ -0,0 +1,83 @@ 
+// P0784R7
+// { dg-do compile { target c++2a } }
+
+namespace std
+{
+  inline namespace _8 { }
+  namespace _8 {
+
+    typedef __SIZE_TYPE__ size_t;
+
+    template <typename T>
+    struct allocator
+    {
+      constexpr allocator () noexcept {}
+
+      constexpr T *allocate (size_t n)
+      { return static_cast<T *> (::operator new (n * sizeof(T))); }
+
+      constexpr void
+      deallocate (T *p, size_t n)
+      { ::operator delete (p); }
+    };
+
+    template <typename T, typename U = T &&>
+    U __declval (int);
+    template <typename T>
+    T __declval (long);
+    template <typename T>
+    auto declval () noexcept -> decltype (__declval<T> (0));
+
+    template <typename T>
+    struct remove_reference
+    { typedef T type; };
+    template <typename T>
+    struct remove_reference<T &>
+    { typedef T type; };
+    template <typename T>
+    struct remove_reference<T &&>
+    { typedef T type; };
+
+    template <typename T>
+    constexpr T &&
+    forward (typename std::remove_reference<T>::type &t) noexcept
+    { return static_cast<T&&> (t); }
+
+    template<typename T>
+    constexpr T &&
+    forward (typename std::remove_reference<T>::type &&t) noexcept
+    { return static_cast<T&&> (t); }
+
+    template <typename T, typename... A>
+    constexpr auto
+    construct_at (T *l, A &&... a)
+    noexcept (noexcept (::new ((void *) 0) T (std::declval<A> ()...)))
+    -> decltype (::new ((void *) 0) T (std::declval<A> ()...))
+    { return ::new ((void *) l) T (std::forward<A> (a)...); }
+
+    template <typename T>
+    constexpr inline void
+    destroy_at (T *l)
+    { l->~T (); }
+  }
+}
+
+inline void *operator new (std::size_t, void *p) noexcept
+{ return p; }
+
+constexpr bool
+foo ()
+{
+  std::allocator<int> a;
+  auto p = a.allocate (2);
+  std::construct_at (p, 1);
+  std::construct_at (p + 1, 2);
+  if (p[0] != 1 || p[1] != 2)
+    throw 1;
+  std::destroy_at (p);
+  std::destroy_at (p + 1);
+  a.deallocate (p, 2);
+  return true;
+}
+
+static_assert (foo ());
--- gcc/testsuite/g++.dg/cpp2a/constexpr-new7.C.jj	2019-10-31 10:58:33.227908692 +0100
+++ gcc/testsuite/g++.dg/cpp2a/constexpr-new7.C	2019-10-31 10:14:10.702155762 +0100
@@ -0,0 +1,33 @@ 
+// P0784R7
+// { dg-do compile { target c++2a } }
+
+namespace std
+{
+  typedef __SIZE_TYPE__ size_t;
+}
+
+inline void *operator new (std::size_t, void *p) noexcept
+{ return p; }
+void *operator new (std::size_t) noexcept;
+
+constexpr bool
+foo ()
+{
+  auto p = static_cast<int *> (::operator new (sizeof (int)));	// { dg-error "call to non-'constexpr' function" }
+  *p = 1;
+  ::operator delete (p);
+  return false;
+}
+
+struct S { constexpr S () : s (0) {} int s; };
+
+constexpr bool
+bar ()
+{
+  auto p = static_cast<S *> (::operator new (sizeof (S)));	// { dg-error "call to non-'constexpr' function" }
+  auto q = new (p) S ();
+  q->s++;
+  q->~S ();
+  ::operator delete (p);
+  return false;
+}