Implement P0722R3, destroying operator delete.
authorJason Merrill <jason@redhat.com>
Tue, 13 Nov 2018 04:40:01 +0000 (23:40 -0500)
committerJason Merrill <jason@gcc.gnu.org>
Tue, 13 Nov 2018 04:40:01 +0000 (23:40 -0500)
A destroying operator delete takes responsibility for calling the destructor
for the object it is deleting; this is intended to be useful for sized
delete of a class allocated with a trailing buffer, where the compiler can't
know the size of the allocation, and so would pass the wrong size to the
non-destroying sized operator delete.

gcc/c-family/
* c-cppbuiltin.c (c_cpp_builtins): Define
__cpp_impl_destroying_delete.
gcc/cp/
* call.c (std_destroying_delete_t_p, destroying_delete_p): New.
(aligned_deallocation_fn_p, usual_deallocation_fn_p): Use
destroying_delete_p.
(build_op_delete_call): Handle destroying delete.
* decl2.c (coerce_delete_type): Handle destroying delete.
* init.c (build_delete): Don't call dtor with destroying delete.
* optimize.c (build_delete_destructor_body): Likewise.
libstdc++-v3/
* libsupc++/new (std::destroying_delete_t): New.

From-SVN: r266053

13 files changed:
gcc/c-family/ChangeLog
gcc/c-family/c-cppbuiltin.c
gcc/cp/ChangeLog
gcc/cp/call.c
gcc/cp/cp-tree.h
gcc/cp/decl.c
gcc/cp/decl2.c
gcc/cp/init.c
gcc/cp/optimize.c
gcc/testsuite/g++.dg/cpp2a/destroying-delete1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/feat-cxx2a.C
libstdc++-v3/ChangeLog
libstdc++-v3/libsupc++/new

index 42bb5ca450b18de3860fe2739cef00c9dba12c0b..165f9b7efcc4f170be3c06202dd35b96fdd716eb 100644 (file)
@@ -1,5 +1,8 @@
 2018-11-12  Jason Merrill  <jason@redhat.com>
 
+       * c-cppbuiltin.c (c_cpp_builtins): Define
+       __cpp_impl_destroying_delete.
+
        * c-cppbuiltin.c (c_cpp_builtins): Change __cpp_explicit_bool to
        __cpp_conditional_explicit.
 
index 8dd62158b62de69f2d8d75033b42088ffb89e8fb..7daa3e33990bfc524a9f84a468a6eb85afe9ed19 100644 (file)
@@ -980,6 +980,7 @@ c_cpp_builtins (cpp_reader *pfile)
          /* Set feature test macros for C++2a.  */
          cpp_define (pfile, "__cpp_conditional_explicit=201806");
          cpp_define (pfile, "__cpp_nontype_template_parameter_class=201806");
+         cpp_define (pfile, "__cpp_impl_destroying_delete=201806");
        }
       if (flag_concepts)
        cpp_define (pfile, "__cpp_concepts=201507");
index 2f15c08b3e4fe390ce34c922c46207ea3451ae49..5497a0829e3c5c1938c09392c289e29d116fb2f2 100644 (file)
@@ -1,5 +1,14 @@
 2018-11-12  Jason Merrill  <jason@redhat.com>
 
+       Implement P0722R3, destroying operator delete.
+       * call.c (std_destroying_delete_t_p, destroying_delete_p): New.
+       (aligned_deallocation_fn_p, usual_deallocation_fn_p): Use
+       destroying_delete_p.
+       (build_op_delete_call): Handle destroying delete.
+       * decl2.c (coerce_delete_type): Handle destroying delete.
+       * init.c (build_delete): Don't call dtor with destroying delete.
+       * optimize.c (build_delete_destructor_body): Likewise.
+
        Implement P0780R2, pack expansion in lambda init-capture.
        * parser.c (cp_parser_lambda_introducer): Parse pack init-capture.
        * pt.c (tsubst_pack_expansion): Handle init-capture packs.
index 6f401567c2e198674204e0d265ed67a57f3e45f2..b668e031d3c5b66154d7b9648a15ea0a2391b7b5 100644 (file)
@@ -6190,6 +6190,31 @@ aligned_allocation_fn_p (tree t)
   return (a && same_type_p (TREE_VALUE (a), align_type_node));
 }
 
+/* True if T is std::destroying_delete_t.  */
+
+static bool
+std_destroying_delete_t_p (tree t)
+{
+  return (TYPE_CONTEXT (t) == std_node
+         && id_equal (TYPE_IDENTIFIER (t), "destroying_delete_t"));
+}
+
+/* A deallocation function with at least two parameters whose second parameter
+   type is of type std::destroying_delete_t is a destroying operator delete. A
+   destroying operator delete shall be a class member function named operator
+   delete. [ Note: Array deletion cannot use a destroying operator
+   delete. --end note ] */
+
+tree
+destroying_delete_p (tree t)
+{
+  tree a = TYPE_ARG_TYPES (TREE_TYPE (t));
+  if (!a || !TREE_CHAIN (a))
+    return NULL_TREE;
+  tree type = TREE_VALUE (TREE_CHAIN (a));
+  return std_destroying_delete_t_p (type) ? type : NULL_TREE;
+}
+
 /* Returns true iff T, an element of an OVERLOAD chain, is a usual deallocation
    function (3.7.4.2 [basic.stc.dynamic.deallocation]) with a parameter of
    std::align_val_t.  */
@@ -6207,6 +6232,8 @@ aligned_deallocation_fn_p (tree t)
     return false;
 
   tree a = FUNCTION_ARG_CHAIN (t);
+  if (destroying_delete_p (t))
+    a = TREE_CHAIN (a);
   if (same_type_p (TREE_VALUE (a), align_type_node)
       && TREE_CHAIN (a) == void_list_node)
     return true;
@@ -6242,6 +6269,8 @@ usual_deallocation_fn_p (tree t)
   tree chain = FUNCTION_ARG_CHAIN (t);
   if (!chain)
     return false;
+  if (destroying_delete_p (t))
+    chain = TREE_CHAIN (chain);
   if (chain == void_list_node
       || ((!global || flag_sized_deallocation)
          && second_parm_is_size_t (t)))
@@ -6307,6 +6336,7 @@ build_op_delete_call (enum tree_code code, tree addr, tree size,
     fns = lookup_name_nonclass (fnname);
 
   /* Strip const and volatile from addr.  */
+  tree oaddr = addr;
   addr = cp_convert (ptr_type_node, addr, complain);
 
   if (placement)
@@ -6484,9 +6514,24 @@ build_op_delete_call (enum tree_code code, tree addr, tree size,
        }
       else
        {
+         tree destroying = destroying_delete_p (fn);
+         if (destroying)
+           {
+             /* Strip const and volatile from addr but retain the type of the
+                object.  */
+             tree rtype = TREE_TYPE (TREE_TYPE (oaddr));
+             rtype = cv_unqualified (rtype);
+             rtype = TYPE_POINTER_TO (rtype);
+             addr = cp_convert (rtype, oaddr, complain);
+             destroying = build_functional_cast (destroying, NULL_TREE,
+                                                 complain);
+           }
+
          tree ret;
          vec<tree, va_gc> *args = make_tree_vector ();
          args->quick_push (addr);
+         if (destroying)
+           args->quick_push (destroying);
          if (second_parm_is_size_t (fn))
            args->quick_push (size);
          if (aligned_deallocation_fn_p (fn))
index 9c4664c3aa70721aeb40af344eccc785320efdfd..c4d79c0cf7fe35112544a6a07866e5f9346e7a7b 100644 (file)
@@ -6127,6 +6127,7 @@ extern tree build_new_op                  (location_t, enum tree_code,
 extern tree build_op_call                      (tree, vec<tree, va_gc> **,
                                                 tsubst_flags_t);
 extern bool aligned_allocation_fn_p            (tree);
+extern tree destroying_delete_p                        (tree);
 extern bool usual_deallocation_fn_p            (tree);
 extern tree build_op_delete_call               (enum tree_code, tree, tree,
                                                 bool, tree, tree,
@@ -6456,7 +6457,7 @@ extern void cplus_decl_attributes         (tree *, tree, int);
 extern void finish_anon_union                  (tree);
 extern void cxx_post_compilation_parsing_cleanups (void);
 extern tree coerce_new_type                    (tree, location_t);
-extern tree coerce_delete_type                 (tree, location_t);
+extern void coerce_delete_type                 (tree, location_t);
 extern void comdat_linkage                     (tree);
 extern void determine_visibility               (tree);
 extern void constrain_class_visibility         (tree);
index cf934159395c9f69c641868f0484d5bef0931a96..42994055d5fd0128e00d7796e726a58b0ec204c6 100644 (file)
@@ -13401,7 +13401,7 @@ grok_op_properties (tree decl, bool complain)
        }
 
       if (op_flags & OVL_OP_FLAG_DELETE)
-       TREE_TYPE (decl) = coerce_delete_type (TREE_TYPE (decl), loc);
+       coerce_delete_type (decl, loc);
       else
        {
          DECL_IS_OPERATOR_NEW (decl) = 1;
index a163558af54710b148e49818a4c8feeaf05e86ae..13c156b947d0136c70d17464af8515abb3d3686a 100644 (file)
@@ -1739,10 +1739,11 @@ coerce_new_type (tree type, location_t loc)
   return type;
 }
 
-tree
-coerce_delete_type (tree type, location_t loc)
+void
+coerce_delete_type (tree decl, location_t loc)
 {
   int e = 0;
+  tree type = TREE_TYPE (decl);
   tree args = TYPE_ARG_TYPES (type);
 
   gcc_assert (TREE_CODE (type) == FUNCTION_TYPE);
@@ -1754,19 +1755,38 @@ coerce_delete_type (tree type, location_t loc)
                void_type_node);
     }
 
+  tree ptrtype = ptr_type_node;
+  if (destroying_delete_p (decl))
+    {
+      if (DECL_CLASS_SCOPE_P (decl))
+       /* If the function is a destroying operator delete declared in class type
+          C, the type of its first parameter shall be C*.  */
+       ptrtype = TYPE_POINTER_TO (DECL_CONTEXT (decl));
+      else
+       /* A destroying operator delete shall be a class member function named
+          operator delete.  */
+       error_at (loc, "destroying operator delete must be a member function");
+      const ovl_op_info_t *op = IDENTIFIER_OVL_OP_INFO (DECL_NAME (decl));
+      if (op->flags & OVL_OP_FLAG_VEC)
+       error_at (loc, "operator delete[] cannot be a destroying delete");
+      if (!usual_deallocation_fn_p (decl))
+       error_at (loc, "destroying operator delete must be a usual "
+                 "deallocation function");
+    }
+
   if (!args || args == void_list_node
-      || !same_type_p (TREE_VALUE (args), ptr_type_node))
+      || !same_type_p (TREE_VALUE (args), ptrtype))
     {
       e = 2;
       if (args && args != void_list_node)
        args = TREE_CHAIN (args);
       error_at (loc, "%<operator delete%> takes type %qT as first parameter",
-               ptr_type_node);
+               ptrtype);
     }
   switch (e)
   {
     case 2:
-      args = tree_cons (NULL_TREE, ptr_type_node, args);
+      args = tree_cons (NULL_TREE, ptrtype, args);
       /* Fall through.  */
     case 1:
       type = (cxx_copy_lang_qualifiers
@@ -1776,7 +1796,7 @@ coerce_delete_type (tree type, location_t loc)
     default:;
   }
 
-  return type;
+  TREE_TYPE (decl) = type;
 }
 \f
 /* DECL is a VAR_DECL for a vtable: walk through the entries in the vtable
index a17e1608c80a2cf9613fc49afd55411f3b292413..5a314862c805e353cce74e1c3036626478f9401f 100644 (file)
@@ -4782,6 +4782,7 @@ build_delete (tree otype, tree addr, special_function_kind auto_delete,
 
   tree head = NULL_TREE;
   tree do_delete = NULL_TREE;
+  bool destroying_delete = false;
 
   if (!deleting)
     {
@@ -4820,6 +4821,11 @@ build_delete (tree otype, tree addr, special_function_kind auto_delete,
                                        complain);
       /* Call the complete object destructor.  */
       auto_delete = sfk_complete_destructor;
+      if (do_delete != error_mark_node)
+       {
+         tree fn = get_callee_fndecl (do_delete);
+         destroying_delete = destroying_delete_p (fn);
+       }
     }
   else if (TYPE_GETS_REG_DELETE (type))
     {
@@ -4832,7 +4838,7 @@ build_delete (tree otype, tree addr, special_function_kind auto_delete,
                            complain);
     }
 
-  if (type_build_dtor_call (type))
+  if (!destroying_delete && type_build_dtor_call (type))
     expr = build_dtor_call (cp_build_fold_indirect_ref (addr),
                            auto_delete, flags, complain);
   else
index 3923a5fc6c4e6950a4f4adef1a1b89d01aebba0d..da068b5931a6476fe74374d789a0638f4a0fa8f5 100644 (file)
@@ -117,11 +117,6 @@ build_delete_destructor_body (tree delete_dtor, tree complete_dtor)
   tree parm = DECL_ARGUMENTS (delete_dtor);
   tree virtual_size = cxx_sizeof (current_class_type);
 
-  /* Call the corresponding complete destructor.  */
-  gcc_assert (complete_dtor);
-  tree call_dtor = build_cxx_call (complete_dtor, 1, &parm,
-                                  tf_warning_or_error);
-
   /* Call the delete function.  */
   tree call_delete = build_op_delete_call (DELETE_EXPR, current_class_ptr,
                                           virtual_size,
@@ -130,10 +125,26 @@ build_delete_destructor_body (tree delete_dtor, tree complete_dtor)
                                           /*alloc_fn=*/NULL_TREE,
                                           tf_warning_or_error);
 
-  /* Operator delete must be called, whether or not the dtor throws.  */
-  add_stmt (build2 (TRY_FINALLY_EXPR, void_type_node, call_dtor, call_delete));
+  tree op = get_callee_fndecl (call_delete);
+  if (op && DECL_P (op) && destroying_delete_p (op))
+    {
+      /* The destroying delete will handle calling complete_dtor.  */
+      add_stmt (call_delete);
+    }
+  else
+    {
+      /* Call the corresponding complete destructor.  */
+      gcc_assert (complete_dtor);
+      tree call_dtor = build_cxx_call (complete_dtor, 1, &parm,
+                                      tf_warning_or_error);
+
+      /* Operator delete must be called, whether or not the dtor throws.  */
+      add_stmt (build2 (TRY_FINALLY_EXPR, void_type_node,
+                       call_dtor, call_delete));
+    }
 
-  /* Return the address of the object.  */
+  /* Return the address of the object.
+     ??? How is it useful to return an invalid address?  */
   if (targetm.cxx.cdtor_returns_this ())
     {
       tree val = DECL_ARGUMENTS (delete_dtor);
diff --git a/gcc/testsuite/g++.dg/cpp2a/destroying-delete1.C b/gcc/testsuite/g++.dg/cpp2a/destroying-delete1.C
new file mode 100644 (file)
index 0000000..329588b
--- /dev/null
@@ -0,0 +1,41 @@
+// { dg-do run { target c++2a } }
+
+#include <new>
+
+int adt, adl;
+struct A {
+  ~A() { ++adt; }
+  void operator delete (A *p, std::destroying_delete_t) {
+    ++adl;
+    if (adt) __builtin_abort();
+    p->~A();
+    ::operator delete (p);
+  }
+};
+
+struct B {
+  virtual ~B() {}
+  void operator delete(void*, std::size_t) { __builtin_abort(); }
+};
+
+int edel, edtor;
+struct E : B {
+  ~E() { ++edtor; }
+  void operator delete(E *p, std::destroying_delete_t) {
+    ++edel;
+    if (edtor) __builtin_abort();
+    p->~E();
+    ::operator delete(p);
+  }
+};
+int main() {
+  A* ap = new A;
+  delete ap;
+  if (adl != 1 || adt != 1)
+    __builtin_abort();
+
+  B* bp = new E;
+  delete bp; // 2: uses E::operator delete(E*, std::destroying_delete_t)
+  if (edel != 1 || edtor != 1)
+    __builtin_abort();
+}
index 4289bfcfa525dfa40dc4d6c1b7109debf572a27f..dba77179b82499e7516a1e062e3121501bfc610e 100644 (file)
 # error "__cpp_nontype_template_parameter_class != 201806"
 #endif
 
+#if __cpp_impl_destroying_delete != 201806
+# error "__cpp_impl_destroying_delete != 201806"
+#endif
+
 #ifdef __has_cpp_attribute
 
 #  if ! __has_cpp_attribute(maybe_unused)
index eafdd927e9cbaa35ad8aa19c0ef3bc34da533d83..d904b45f7dfcf4fb82e75dc4845f1ea48947710a 100644 (file)
@@ -1,3 +1,7 @@
+2018-11-12  Jason Merrill  <jason@redhat.com>
+
+       * libsupc++/new (std::destroying_delete_t): New.
+
 2018-11-12  Jonathan Wakely  <jwakely@redhat.com>
 
        PR libstdc++/87963
index 19bc1832541c0210141a5dc88b03333e11c5f0f7..91ebf3c2cd716c94bd2bdcd5ed91fccb8ee34232 100644 (file)
@@ -208,6 +208,18 @@ namespace std
 #endif // _GLIBCXX_HAVE_BUILTIN_LAUNDER
 #endif // C++17
 
+#if __cpp_impl_destroying_delete
+#define __cpp_lib_destroying_delete 201806L
+namespace std
+{
+  struct destroying_delete_t
+  {
+    explicit destroying_delete_t() = default;
+  };
+  inline constexpr destroying_delete_t destroying_delete{};
+}
+#endif // destroying delete
+
 #pragma GCC visibility pop
 
 #endif