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

Reply via email to