https://gcc.gnu.org/g:e8207be46a6ebbbc079adc72c763b648de5c2da5
commit r17-4499-ge8207be46a6ebbbc079adc72c763b648de5c2da5 Author: Joshua Berne <[email protected]> Date: Mon Sep 21 00:09:58 2026 +0100 c++: fix return object cleanup for functions with contracts [PR127281] PR c++/127281 - [c++26][contracts] a contract emits the return-value cleanup twice: returned object destroyed twice, and an ICE in gimple_add_tmp_var In maybe_apply_function_contracts we create an artificial block that will contain the contract assertion evaluations, much like a function try block. This, however, runs after the function body has already been processed, a declaration for the return value sentinel has already been added (if needed), and cleanup code for the return value is already in place. Running maybe_splice_retval_cleanup again when the artificial block completes will crash due to multiple declarations of the sentinel, and if that is fixed it will double-destroy a non-trivial return value. Tests that hit this bug were added to an existing test case that handles non-trivial return types, and that test was generally enhanced to also execute at runtime (with no violations of any contract assertions, just code generation for them) in order to identify the cases where objects are double-destroyed (or leaked). To fix this, override current_retval_sentinel for the contracts artificial block. Note that there is still a lingering issue (PR c++/127414) involving destruction of the return value when the postcondition evaluations exit via an exception, but that fix is more involved and depends on this one rather than overlapping it. PR c++/127281 gcc/cp/ChangeLog: * contracts.cc (maybe_apply_function_contracts): Override current_retval_sentinel for the artificial block containing contracts. gcc/testsuite/ChangeLog: * g++.dg/contracts/cpp26/dcl.contract.res.p1-NT.C: Added runtime testing, along with tests of NRVO and exceptions from local variables. Signed-off-by: Joshua Berne <[email protected]> Diff: --- gcc/cp/contracts.cc | 13 ++- .../contracts/cpp26/dcl.contract.res.p1-NT.C | 128 +++++++++++++++------ 2 files changed, 105 insertions(+), 36 deletions(-) diff --git a/gcc/cp/contracts.cc b/gcc/cp/contracts.cc index f509424fd7c0..cd5f0bdbb16a 100644 --- a/gcc/cp/contracts.cc +++ b/gcc/cp/contracts.cc @@ -1390,7 +1390,18 @@ maybe_apply_function_contracts (tree fndecl) } /* Now add the pre and post conditions to the existing function body. - This copies the approach used for function try blocks. */ + This approach mostly copies that of function try blocks, but also stops + maybe_splice_retval_cleanup from acting on sk_function_parms again and + introducing another DECL_EXPR for the same sentinel, or a second cleanup + statement, both of which would be broken. + + FIXME: This means no cleanup statement will be emitted to clean up the + return object if the postcondition evaluations themselves throw an + exception (from the contract-violation handler), and this suppression + might need to be removed when that is addressed (PR c++/127414). */ + auto retval_sentinel_ovr = make_temp_override (current_retval_sentinel, + NULL_TREE); + tree compound_stmt = begin_compound_stmt (0); current_binding_level->artificial = true; diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/dcl.contract.res.p1-NT.C b/gcc/testsuite/g++.dg/contracts/cpp26/dcl.contract.res.p1-NT.C index 5cbfb6117584..af7304b77b03 100644 --- a/gcc/testsuite/g++.dg/contracts/cpp26/dcl.contract.res.p1-NT.C +++ b/gcc/testsuite/g++.dg/contracts/cpp26/dcl.contract.res.p1-NT.C @@ -6,40 +6,63 @@ // // Various tests with non trivial return value identifier // -// { dg-do compile { target c++23 } } -// { dg-additional-options "-fcontracts" } +// { dg-do compile { target { c++23 && { ! hostedlib } } } } +// { dg-do run { target { c++23 && hostedlib } } } +// { dg-additional-options "-fcontracts -fcontract-evaluation-semantic=enforce" } +int live = 0; struct NonTrivial{ - NonTrivial(){}; - NonTrivial(const NonTrivial&){} - ~NonTrivial(){}; + NonTrivial(){ ++live; }; + NonTrivial(const NonTrivial&){ ++live;} + NonTrivial(NonTrivial&&){ ++live;} + ~NonTrivial(){ --live; }; int x = 0; }; +struct ThrowOnDestroy { + bool armed = true; + ~ThrowOnDestroy() noexcept(false) { if (armed) throw 17; } +}; + template<typename T> bool check(T t){ return true; } struct S{ - NonTrivial f1(const NonTrivial i) post(r: i.x > 0 ) { return NonTrivial{};} + NonTrivial f1(const NonTrivial i) post(r: check(i.x > 0) ) { return NonTrivial{};} template <typename T> - NonTrivial f2(const T i) post(r: i.x > 0 ) { return NonTrivial{};} + NonTrivial f2(const T i) post(r: check(i.x > 0) ) { return NonTrivial{};} - auto f3(const NonTrivial i) post(r: i.x > 0 ) { return NonTrivial{};} + auto f3(const NonTrivial i) post(r: check(i.x > 0) ) { return NonTrivial{};} template <typename T> - T f4(const T i) post(r: i.x > 0 ) { return NonTrivial{};} + T f4(const T i) post(r: check(i.x > 0) ) { return NonTrivial{};} template <typename T> - auto f5(const T i) post(r: i.x > 0 ) { return i;} + auto f5(const T i) post(r: check(i.x > 0) ) { return i;} template <typename T> auto f6(const T i) post(r: check(i) ) { return i;} auto f7(const NonTrivial i) post(r: check(r) ) { return i;} + NonTrivial f8(bool flag) pre(check(flag)) + { NonTrivial result; + if (flag) { + return result; + } + int other; // force a new block + return result; + } + + NonTrivial f9(bool arm) pre(check(arm)) + { ThrowOnDestroy guard{arm}; + NonTrivial result; + return result; + } + }; template <typename U> @@ -49,42 +72,42 @@ struct S1 struct S2 { NonTrivial - f1 (const NonTrivial i) post(r: i.x > 0 ) + f1 (const NonTrivial i) post(r: check(i.x > 0) ) { return NonTrivial {}; } template <typename T> NonTrivial - f2 (const T i) post(r: i.x > 0 ) + f2 (const T i) post(r: check(i.x > 0) ) { return NonTrivial {}; } auto f3 (const NonTrivial i) - post(r: i.x > 0 ) + post(r: check(i.x > 0) ) { return NonTrivial {};} template <typename T> T f4 (const T i) - post(r: i.x > 0 ) + post(r: check(i.x > 0) ) { return NonTrivial {};} template <typename T> auto f5 (const T i) - post(r: i.x > 0 ) + post(r: check(i.x > 0) ) { return i;} }; NonTrivial f1 (const NonTrivial i) - post(r: i.x > 0 ) + post(r: check(i.x > 0) ) { S2 s; return s.f1(i); } @@ -92,14 +115,14 @@ struct S1 template <typename T> NonTrivial f2 (const T i) - post(r: i.x > 0 ) + post(r: check(i.x > 0) ) { S2 s; return s.f2(i); } auto f3 (const NonTrivial i) - post(r: i.x > 0 ) + post(r: check(i.x > 0) ) { S2 s; return s.f3(i); } @@ -107,7 +130,7 @@ struct S1 template <typename T> T f4 (const T i) - post(r: i.x > 0 ) + post(r: check(i.x > 0) ) { S2 s; return s.f4(i); } @@ -115,32 +138,67 @@ struct S1 template <typename T> auto f5 (const T i) - post(r: i.x > 0 ) + post(r: check(i.x > 0) ) { S2 s; return s.f5(i); } + template <typename T> + auto f6(T flag) pre(check(flag)) + { NonTrivial result; + if (flag) { + return result; + } + int other; // force a new block + return result; + } + + template <typename T> + auto f7(T arm) pre(check(arm)) + { ThrowOnDestroy guard{arm}; + NonTrivial result; + return result; + } + + }; int main() { - S s; - NonTrivial n; - s.f1(NonTrivial{}); + { + S s; + NonTrivial n; + s.f1(NonTrivial{}); - s.f2(n); - s.f3(n); - s.f4(n); - s.f5(n); - s.f6(n); - s.f7(n); + s.f2(n); + s.f3(n); + s.f4(n); + s.f5(n); + s.f6(n); + s.f7(n); + s.f8(true); + s.f8(false); + try { + s.f9(true); + } catch (int) { } + } - S1<NonTrivial> s1; - s1.f1(n); - s1.f2(n); - s1.f3(n); - s1.f4(n); - s1.f5(n); + { + S1<NonTrivial> s1; + NonTrivial n; + s1.f1(n); + s1.f2(n); + s1.f3(n); + s1.f4(n); + s1.f5(n); + s1.f6(true); + s1.f6(false); + try { + s1.f7(true); + } catch (int) { } + } + + if (live != 0) __builtin_abort(); }
