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