Implement P0722R3, destroying operator delete.

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
This commit is contained in:
Jason Merrill 2018-11-12 23:40:01 -05:00 committed by Jason Merrill
parent 7de37c97b4
commit a6bb6b07f7
13 changed files with 174 additions and 17 deletions

View 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.

View 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");

View 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.

View 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))

View 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);

View 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;

View 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;
}
/* DECL is a VAR_DECL for a vtable: walk through the entries in the vtable

View 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

View 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);
/* Return the address of the object. */
/* 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.
??? How is it useful to return an invalid address? */
if (targetm.cxx.cdtor_returns_this ())
{
tree val = DECL_ARGUMENTS (delete_dtor);

View File

@ -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();
}

View File

@ -426,6 +426,10 @@
# 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)

View 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

View 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