On 1/24/25 1:55 PM, Jakub Jelinek wrote:
On Fri, Jan 24, 2025 at 12:45:54PM -0500, Jason Merrill wrote:
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?
build_vec_init already has the cleanup_flags mechanism for suppressing
cleanups. Instead of a new mechanism, how about in build_new_1 passing a
flags vec and using it to disable cleanups like split_nonconstant_init does?
You're right, thanks for the suggestion, I didn't really know what the
cleanup_flags stuff was about and how it exactly worked.
Or implementing the new flag using the existing mechanism rather than a new
strategy of changing rval?
Seems it works just fine in quick testing, ok for trunk if it passes full
bootstrap/regtest on x86_64/i686?
OK.
Though, I guess the clearing of rval instead of setting iterator to maxindex
could result in tiny bit better code generation, on many targets setting
something to zero is less expensive than setting it to small non-zero
constant (and for larger maxindex (albeit that is less common) could be
even more expensive).
So maybe incrementally
--- gcc/cp/init.cc.jj 2025-01-24 19:26:31.980193087 +0100
+++ gcc/cp/init.cc 2025-01-24 19:45:03.324702001 +0100
@@ -4749,7 +4749,8 @@ build_vec_init (tree base, tree maxindex
itself. But that breaks when gimplify_target_expr adds a clobber
cleanup that runs before the build_vec_init cleanup. */
if (cleanup_flags)
- vec_safe_push (*cleanup_flags, build_tree_list (iterator, maxindex));
+ vec_safe_push (*cleanup_flags,
+ build_tree_list (rval, build_zero_cst (ptype)));
}
/* Should we try to create a constant initializer? */
as build_vec_delete_1 wraps the whole body with base != nullptr and base is
rval (and maybe tweak the comment and maybe the build_disable_temp_cleanup
one as well)? Though that can wait for GCC 16.
Sounds good for GCC 16.
2025-01-24 Jakub Jelinek <ja...@redhat.com>
PR c++/117827
* init.cc (build_new_1): Pass address of a make_tree_vector ()
initialized gc tree vector to build_vec_init and append
build_disable_temp_cleanup to init_expr from it.
* g++.dg/init/array66.C: New test.
--- gcc/cp/init.cc.jj 2025-01-23 11:10:25.791042810 +0100
+++ gcc/cp/init.cc 2025-01-24 19:26:31.980193087 +0100
@@ -3709,6 +3709,11 @@ build_new_1 (vec<tree, va_gc> **placemen
error ("parenthesized initializer in array new");
return error_mark_node;
}
+
+ /* Collect flags for disabling subobject cleanups once the complete
+ object is fully constructed. */
+ vec<tree, va_gc> *flags = make_tree_vector ();
+
init_expr
= build_vec_init (data_addr,
cp_build_binary_op (input_location,
@@ -3718,7 +3723,17 @@ build_new_1 (vec<tree, va_gc> **placemen
vecinit,
explicit_value_init_p,
/*from_array=*/0,
- complain);
+ complain,
+ &flags);
+
+ for (tree f : flags)
+ {
+ tree cl = build_disable_temp_cleanup (f);
+ cl = convert_to_void (cl, ICV_STATEMENT, complain);
+ init_expr = build2 (COMPOUND_EXPR, void_type_node,
+ init_expr, cl);
+ }
+ release_tree_vector (flags);
}
else
{
--- gcc/testsuite/g++.dg/init/array66.C.jj 2025-01-24 19:10:40.851467711
+0100
+++ gcc/testsuite/g++.dg/init/array66.C 2025-01-24 19:10:40.851467711 +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