Hi!
The following testcase r12-6328, because the elements of the array
are destructed twice, once when the callee encounters delete[] p;
and then second time when the exception is thrown.
The array elts should be only destructed if exception is thrown from
one of the constructors during the build_vec_init emitted code in case of
new expressions, but when the new expression completes, it is IMO
responsibility of user code to delete[] it when it is no longer needed.
So, the following patch arranges for build_vec_init emitted code to clear
the rval variable used to guard the eh cleanup of it at the end, but does so
only if we emit a cleanup like that and only if it is from build_new_1. For
other uses of build_vec_init the elements should be IMO destructed by the
compiler (and the g++.dg/{eh/{aggregate1,ref-temp2},init/aggr7-eh3}.C tests
verify that behavior).
Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
2025-01-23 Jakub Jelinek <[email protected]>
PR c++/117827
* cp-tree.h (build_vec_init): Add another argument defaulted to false.
* init.cc (build_new_1): Pass NULL, true as extra arguments to
build_vec_init.
(build_vec_init): Add NO_CLEANUPS_WHEN_COMPLETE_P argument. If
build_vec_delete_1 cleanup is registered and
NO_CLEANUPS_WHEN_COMPLETE_P is true, copy rval to base, clear rval and
return from stmt expression base instead of rval.
* g++.dg/init/array66.C: New test.
--- gcc/cp/cp-tree.h.jj 2025-01-21 16:25:41.452994411 +0100
+++ gcc/cp/cp-tree.h 2025-01-23 10:54:18.363769919 +0100
@@ -7391,7 +7391,8 @@ extern tree build_new
(location_t,
extern tree get_temp_regvar (tree, tree);
extern tree build_vec_init (tree, tree, tree, bool, int,
tsubst_flags_t,
- vec<tree, va_gc> ** = nullptr);
+ vec<tree, va_gc> ** = nullptr,
+ bool = false);
extern tree build_delete (location_t, tree, tree,
special_function_kind,
int, int, tsubst_flags_t);
--- gcc/cp/init.cc.jj 2025-01-22 19:34:41.470604917 +0100
+++ gcc/cp/init.cc 2025-01-23 10:58:51.619891446 +0100
@@ -3718,7 +3718,9 @@ build_new_1 (vec<tree, va_gc> **placemen
vecinit,
explicit_value_init_p,
/*from_array=*/0,
- complain);
+ complain,
+ NULL,
+ /*no_cleanups_when_complete_p=*/true);
}
else
{
@@ -4500,14 +4502,22 @@ combine_allocator_temps (tree &init, tre
FROM_ARRAY is 1 if we should index into INIT in parallel
with initialization of DECL.
FROM_ARRAY is 2 if we should index into INIT in parallel,
- but use assignment instead of initialization. */
+ but use assignment instead of initialization.
+
+ NO_CLEANUPS_WHEN_COMPLETE_P should be true if destruction
+ of the elements should be disabled after the build_vec_init
+ emitted code finishes. This is useful for new expressions,
+ where the elements should be destructed only when something
+ throws during the construction, or when something later
+ encounters delete expression for it. */
tree
build_vec_init (tree base, tree maxindex, tree init,
bool explicit_value_init_p,
int from_array,
tsubst_flags_t complain,
- vec<tree, va_gc>** cleanup_flags /* = nullptr */)
+ vec<tree, va_gc>** cleanup_flags /* = nullptr */,
+ bool no_cleanups_when_complete_p /* = false */)
{
tree rval;
tree base2 = NULL_TREE;
@@ -4530,6 +4540,7 @@ build_vec_init (tree base, tree maxindex
tree obase = base;
bool xvalue = false;
bool errors = false;
+ bool clear_rval = false;
location_t loc = (init ? cp_expr_loc_or_input_loc (init)
: location_of (base));
@@ -4720,6 +4731,11 @@ build_vec_init (tree base, tree maxindex
errors = true;
TARGET_EXPR_CLEANUP (iterator_targ) = e;
CLEANUP_EH_ONLY (iterator_targ) = true;
+ /* Signal that we want to clear rval near the end of the statement
+ expression so that the the build_vec_delete_1 cleanup does nothing
+ after the whole construction succeeded. */
+ if (no_cleanups_when_complete_p)
+ clear_rval = true;
/* Since we push this cleanup before doing any initialization, cleanups
for any temporaries in the initialization are naturally within our
@@ -5096,7 +5112,23 @@ build_vec_init (tree base, tree maxindex
/* The value of the array initialization is the array itself, RVAL
is a pointer to the first element. */
- finish_stmt_expr_expr (rval, stmt_expr);
+ if (clear_rval)
+ {
+ /* If there is a build_vec_delete_1 cleanup on rval, make sure
+ to return the value of rval but clear the rval variable so that
+ the cleanup does nothing when reaching this. So, emit
+ ({ ... base = rval; rval = nullptr; base; }) */
+ finish_expr_stmt (cp_build_modify_expr (input_location, base,
+ NOP_EXPR, rval,
+ tf_warning_or_error));
+ finish_expr_stmt (cp_build_modify_expr (input_location, rval,
+ NOP_EXPR,
+ build_zero_cst (ptype),
+ tf_warning_or_error));
+ finish_stmt_expr_expr (base, stmt_expr);
+ }
+ else
+ finish_stmt_expr_expr (rval, stmt_expr);
stmt_expr = finish_init_stmts (is_global, stmt_expr, compound_stmt);
--- gcc/testsuite/g++.dg/init/array66.C.jj 2025-01-23 00:41:16.615072941
+0100
+++ gcc/testsuite/g++.dg/init/array66.C 2025-01-23 00:41:16.615072941 +0100
@@ -0,0 +1,33 @@
+// PR c++/117827
+// { dg-do run { target c++11 } }
+
+struct C {
+ int c;
+ static int d, e;
+ C () : c (0) { ++d; }
+ C (const C &) = delete;
+ C &operator= (const C &) = delete;
+ ~C () { ++e; }
+};
+int C::d, C::e;
+
+C *
+foo (C *p)
+{
+ delete[] p;
+ throw 1;
+}
+
+int
+main ()
+{
+ try
+ {
+ foo (new C[1] {});
+ }
+ catch (...)
+ {
+ }
+ if (C::d != C::e)
+ __builtin_abort ();
+}
Jakub