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  <ja...@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

Reply via email to