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