Re: [C++ PATCH] P0784R7 constexpr new fixes (PR c++/91369)

2019-10-31 Thread Jonathan Wakely

On 30/10/19 23:05 +0100, Jakub Jelinek wrote:

On Wed, Oct 30, 2019 at 04:26:17PM -0400, Jason Merrill wrote:

OK.


Thanks, committed.


> Now, for the accepts invalid issues.
>  From what I understood, in constant evaluation
> ::operator new{,[]} or ::operator delete{,[]}
> can't be called from anywhere, only from new/delete expressions or
> std::allocator::{,de}allocate, is that correct?
> If so, perhaps we need some CALL_EXPR flag that we'd set on the call
> coming from new/delete expressions and disallow calls to
> replaceable global allocator functions in constexpr evaluation unless
> that flag is set or it is in std::allocator::{,de}allocate.

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.


> Another thing is that even with that change,
>std::allocator 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 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:


Yes, I agree. EDG rejects that case and Daveed corrected one of my
tests which looked like the code above, so that it would be valid and
accepted by EDG.


 struct S { constexpr S () : s (0) {}; int s; };
 std::allocator 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.

Jakub




Re: [C++ PATCH] P0784R7 constexpr new fixes (PR c++/91369)

2019-10-30 Thread Jakub Jelinek
On Wed, Oct 30, 2019 at 04:26:17PM -0400, Jason Merrill wrote:
> OK.   
>  

Thanks, committed.

> > Now, for the accepts invalid issues.
> >  From what I understood, in constant evaluation
> > ::operator new{,[]} or ::operator delete{,[]}
> > can't be called from anywhere, only from new/delete expressions or
> > std::allocator::{,de}allocate, is that correct?
> > If so, perhaps we need some CALL_EXPR flag that we'd set on the call
> > coming from new/delete expressions and disallow calls to
> > replaceable global allocator functions in constexpr evaluation unless
> > that flag is set or it is in std::allocator::{,de}allocate.
> 
> 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.

> > Another thing is that even with that change,
> >std::allocator 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 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 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.

Jakub



Re: [C++ PATCH] P0784R7 constexpr new fixes (PR c++/91369)

2019-10-30 Thread Jason Merrill

On 10/24/19 5:11 AM, Jakub Jelinek wrote:

Hi!

Jonathan has showed me a testcase with std::allocator::{,de}allocate
and std::construct_at which FAILs with the current constexpr new
implementation.

There are two problems that make the testcase rejected, and further issues
(not addressed by this patch) where supposedly invalid C++20 code is
accepted.

The first problem was that cxx_replaceable_global_alloc_fn was actually
treating placement new as replaceable global allocation function, when it is
not.
The second problem is that std::construct_at uses placement new under the
hood.  From what I understood, placement new is not allowed in C++20 in
constexpr context and it is unspecified whatever magic std::construct_at
uses to get similar effect.

The fix for the first problem is easy, just add the
DECL_IS_REPLACEABLE_OPERATOR_NEW_P || DECL_IS_OPERATOR_DELETE_P
checks to cxx_replaceable_global_alloc_fn, placement new nor some random
user added ::operator new (size_t, float, double) etc. should not have that
set.
For the second one, the patch is allowing placement new in constexpr
evaluation only in std::construct_at and not in other functions.

With this, Jonathan's testcase works fine.  Ok for trunk if it passes
bootstrap/regtest?


OK.


Now, for the accepts invalid issues.
 From what I understood, in constant evaluation
::operator new{,[]} or ::operator delete{,[]}
can't be called from anywhere, only from new/delete expressions or
std::allocator::{,de}allocate, is that correct?
If so, perhaps we need some CALL_EXPR flag that we'd set on the call
coming from new/delete expressions and disallow calls to
replaceable global allocator functions in constexpr evaluation unless
that flag is set or it is in std::allocator::{,de}allocate.


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.


Or we could check whether the call is wrapped as expected by 
maybe_wrap_new_for_constexpr, but that doesn't help delete.


Or we could build NEW_EXPR and DELETE_EXPR in non-dependent context and 
lower them later.



Another thing is that even with that change,
   std::allocator 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?


Any thoughts on what to do with that?  Even if the magic cast (perhaps
with some flag on it) is moved from whatever the first cast to non-void*
is to the placement new or start of corresponding constructor, would we
need to unmark it somehow if we say std::destroy_at it but allow
next std::construct_at to construct it again?

2019-10-24  Jakub Jelinek  

PR c++/91369 - Implement P0784R7: constexpr new
* constexpr.c (cxx_replaceable_global_alloc_fn): Don't return true
for placement new.
(cxx_placement_new_fn, is_std_construct_at): New functions.
(cxx_eval_call_expression): Allow placement new in std::construct_at.
(potential_constant_expression_1): Likewise.

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

--- gcc/cp/constexpr.c.jj   2019-10-23 20:37:59.981872274 +0200
+++ gcc/cp/constexpr.c  2019-10-24 10:20:57.127193138 +0200
@@ -1601,7 +1601,41 @@ cxx_replaceable_global_alloc_fn (tree fn
  {
return (cxx_dialect >= cxx2a
  && IDENTIFIER_NEWDEL_OP_P (DECL_NAME (fndecl))
- && CP_DECL_CONTEXT (fndecl) == global_namespace);
+ && CP_DECL_CONTEXT (fndecl) == global_namespace
+ && (DECL_IS_REPLACEABLE_OPERATOR_NEW_P (fndecl)
+ || DECL_IS_OPERATOR_DELETE_P (fndecl)));
+}
+
+/* Return true if FNDECL is a placement new function that should be
+   useable during constant expression evaluation of std::construct_at.  */
+
+static inline bool
+cxx_placement_new_fn (tree fndecl)
+{
+  if (cxx_dialect >= cxx2a
+  && IDENTIFIER_NEW_OP_P (DECL_NAME (fndecl))
+  && CP_DECL_CONTEXT (fndecl) == global_namespace
+  && !DECL_IS_REPLACEABLE_OPERATOR_NEW_P (fndecl)
+  && TREE_CODE (TREE_TYPE (fndecl)) == FUNCTION_TYPE)
+{
+  tree first_arg = TREE_CHAIN (TYPE_ARG_TYPES (TREE_TYPE (fndecl)));
+  if (TREE_VALUE (first_arg) == ptr_type_node
+ && TREE_CHAIN (first_arg) == void_list_node)
+   return true;
+}
+  return false;
+}
+
+/* Return true if FNDECL is std::construct_at.  */
+
+static inline bool
+is_std_construct_at (tree fndecl)
+{
+  if (!decl_in_std_namespace_p (fndecl))
+return false;
+
+  tree name = DECL_NAME (fndecl);
+  return name && id_equal (name, "construct_at");
  }
  
  /* Subroutine of cxx_eval_constant_expression.

@@ -1738,6 +1772,27 @@ cxx_eval_call_expression (const constexp
  return t;
}
}
+  /* Allow placement new in std::construct_at, just return the second
+argument.  */
+  if 

[C++ PATCH] P0784R7 constexpr new fixes (PR c++/91369)

2019-10-24 Thread Jakub Jelinek
Hi!

Jonathan has showed me a testcase with std::allocator::{,de}allocate
and std::construct_at which FAILs with the current constexpr new
implementation.

There are two problems that make the testcase rejected, and further issues
(not addressed by this patch) where supposedly invalid C++20 code is
accepted.

The first problem was that cxx_replaceable_global_alloc_fn was actually
treating placement new as replaceable global allocation function, when it is
not.
The second problem is that std::construct_at uses placement new under the
hood.  From what I understood, placement new is not allowed in C++20 in
constexpr context and it is unspecified whatever magic std::construct_at
uses to get similar effect.

The fix for the first problem is easy, just add the
DECL_IS_REPLACEABLE_OPERATOR_NEW_P || DECL_IS_OPERATOR_DELETE_P
checks to cxx_replaceable_global_alloc_fn, placement new nor some random
user added ::operator new (size_t, float, double) etc. should not have that
set.
For the second one, the patch is allowing placement new in constexpr
evaluation only in std::construct_at and not in other functions.

With this, Jonathan's testcase works fine.  Ok for trunk if it passes
bootstrap/regtest?

Now, for the accepts invalid issues.
From what I understood, in constant evaluation
::operator new{,[]} or ::operator delete{,[]}
can't be called from anywhere, only from new/delete expressions or
std::allocator::{,de}allocate, is that correct?
If so, perhaps we need some CALL_EXPR flag that we'd set on the call
coming from new/delete expressions and disallow calls to
replaceable global allocator functions in constexpr evaluation unless
that flag is set or it is in std::allocator::{,de}allocate.

Another thing is that even with that change,
  std::allocator 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.
Any thoughts on what to do with that?  Even if the magic cast (perhaps
with some flag on it) is moved from whatever the first cast to non-void*
is to the placement new or start of corresponding constructor, would we
need to unmark it somehow if we say std::destroy_at it but allow
next std::construct_at to construct it again?

2019-10-24  Jakub Jelinek  

PR c++/91369 - Implement P0784R7: constexpr new
* constexpr.c (cxx_replaceable_global_alloc_fn): Don't return true
for placement new.
(cxx_placement_new_fn, is_std_construct_at): New functions.
(cxx_eval_call_expression): Allow placement new in std::construct_at.
(potential_constant_expression_1): Likewise.

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

--- gcc/cp/constexpr.c.jj   2019-10-23 20:37:59.981872274 +0200
+++ gcc/cp/constexpr.c  2019-10-24 10:20:57.127193138 +0200
@@ -1601,7 +1601,41 @@ cxx_replaceable_global_alloc_fn (tree fn
 {
   return (cxx_dialect >= cxx2a
  && IDENTIFIER_NEWDEL_OP_P (DECL_NAME (fndecl))
- && CP_DECL_CONTEXT (fndecl) == global_namespace);
+ && CP_DECL_CONTEXT (fndecl) == global_namespace
+ && (DECL_IS_REPLACEABLE_OPERATOR_NEW_P (fndecl)
+ || DECL_IS_OPERATOR_DELETE_P (fndecl)));
+}
+
+/* Return true if FNDECL is a placement new function that should be
+   useable during constant expression evaluation of std::construct_at.  */
+
+static inline bool
+cxx_placement_new_fn (tree fndecl)
+{
+  if (cxx_dialect >= cxx2a
+  && IDENTIFIER_NEW_OP_P (DECL_NAME (fndecl))
+  && CP_DECL_CONTEXT (fndecl) == global_namespace
+  && !DECL_IS_REPLACEABLE_OPERATOR_NEW_P (fndecl)
+  && TREE_CODE (TREE_TYPE (fndecl)) == FUNCTION_TYPE)
+{
+  tree first_arg = TREE_CHAIN (TYPE_ARG_TYPES (TREE_TYPE (fndecl)));
+  if (TREE_VALUE (first_arg) == ptr_type_node
+ && TREE_CHAIN (first_arg) == void_list_node)
+   return true;
+}
+  return false;
+}
+
+/* Return true if FNDECL is std::construct_at.  */
+
+static inline bool
+is_std_construct_at (tree fndecl)
+{
+  if (!decl_in_std_namespace_p (fndecl))
+return false;
+
+  tree name = DECL_NAME (fndecl);
+  return name && id_equal (name, "construct_at");
 }
 
 /* Subroutine of cxx_eval_constant_expression.
@@ -1738,6 +1772,27 @@ cxx_eval_call_expression (const constexp
  return t;
}
}
+  /* Allow placement new in std::construct_at, just return the second
+argument.  */
+  if (cxx_placement_new_fn (fun)
+ && ctx->call
+ && ctx->call->fundef
+ && is_std_construct_at (ctx->call->fundef->decl))
+   {
+ const int nargs = call_expr_nargs (t);
+ tree arg1 = NULL_TREE;
+ for (int i = 0; i < nargs; ++i)
+   {
+ tree arg = CALL_EXPR_ARG (t, i);
+ arg = cxx_eval_constant_expression (ctx, arg, false,
+