https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127414

            Bug ID: 127414
           Summary: [c++26][contracts] a violation handler throwing out of
                    a postcondition leaks the returned object
           Product: gcc
           Version: 16.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: berne at notadragon dot com
  Target Milestone: ---

Created attachment 65599
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65599&action=edit
Returned object leaked: exits 2, and 0 with the postconditions deleted

A contract-violation handler that exits by throwing out of a postcondition
leaks the returned object.  The object has been initialized by the time the
postcondition is evaluated, and after unwinding the program can no longer
reach it, so nothing ever runs its destructor.

```
#include <contracts>

int live = 0;

struct Counted {
    Counted () { ++live; }
    Counted (const Counted &) { ++live; }
    ~Counted () { --live; }
};

struct E { };

void handle_contract_violation (const std::contracts::contract_violation &)
{
    throw E { };
}

Counted f (const int n) post (r : n > 100)
{
    // NRVO
    Counted result;
    return result;
}

Counted g (const int n) post (r : n > 100)
{
    // normal return (RVO)
    return Counted {};
}

int main ()
{
    try { f (1); } catch (E &) { }
    try { g (1); } catch (E &) { }
    return live;                 // 0 expected; 2 as it stands
}
```

```
$ g++ -std=c++26 -fcontracts -fcontract-evaluation-semantic=enforce \
      postcondition-throw-leaks-retval.cpp -lstdc++exp && ./a.out; echo $?
2
```

Without the postconditions, or with a throw in the body before we reach
postconditions, the counted objects are all destroyed properly.

The wording in [except.ctor]/2 that should demand that the return object's
destructor be run during unwinding is questionable, so there is a CWG issue
here (that has been submitted).  Even without that CWG issue having been
approved, it's unquestionable that we don't want to leak resources in this
situation and the return value must be destroyed when we unwind out of the
violation handler.


DISCOVERY

Found while migrating uses of BSLS_ASSERT to pre/post in the BDE libraries,
in the same sweep that turned up the double-destroy defect in PR127281.  Both
concern the return object's cleanup around the artificial block that carries
the contract checks, from opposite directions: there the block splices a
second cleanup, here nothing covers the checks at all.


ANALYSIS

maybe_apply_function_contracts (gcc/cp/contracts.cc) builds

  TRY_FINALLY_EXPR
    op 0: the user's body
    op 1: EH_ELSE_EXPR
            op 0: apply_postconditions ()   <- normal-completion arm
            op 1: void_node                 <- exceptional arm, checks skipped

so the postcondition checks live in the finally, not in the body.  The only
cleanup that destroys DECL_RESULT is the one maybe_splice_retval_cleanup
(gcc/cp/except.cc) splices around the function body, guarded by
current_retval_sentinel.  That cleanup covers op 0 and stops there; nothing
covers op 1.  An exception leaving the checks -- which is exactly what a
violation handler that throws produces -- therefore unwinds past a result
object with no cleanup attached, and the object is never destroyed.

VERSIONS -- all on x86_64-linux-gnu

  source              version                       leaks
  compiler-explorer   16.1.0                        yes
  compiler-explorer   16.2.0                        yes
  compiler-explorer   17.0.0 20260914, b76fde4b175  yes
  local build -g      17.0.0 20260909, 7dab38c9d71  yes

Reply via email to