Boost logo

Boost :

Subject: Re: [boost] [contract] oldof failure not in N1962?
From: Lorenzo Caminiti (lorcaminiti_at_[hidden])
Date: 2011-07-19 20:49:35


On Tue, Jul 19, 2011 at 5:41 PM, Dave Abrahams <dave_at_[hidden]> wrote:
>
> on Tue Jul 19 2011, lcaminiti <lorcaminiti-AT-gmail.com> wrote:
>
>> Dave Abrahams wrote:
>>>
>>> on Tue Jul 19 2011, Lorenzo Caminiti <lorcaminiti-AT-gmail.com> wrote:
>>>
>>>>
>>>> I'm sorry I managed to confuse everyone with this post
>>>> because I meant to ask something different...
>>>>
>>>> My question instead, is: What shall I do if I fail to copy an oldof
>>>> value?
>>>
>>> I don't think I misunderstood your question, although I don't happen to
>>> know what "oldof" means here...
>>>
>>>> Specifically, what shall I do if an oldof copy throws an
>>>> exception? This is not specified by N1962 (as far as I can see). I
>>>> decided that if I fail to copy and olfof value then I call the
>>>> postcondition_broken handler but only after executing the body (even
>>>> if oldof values are internally copied before the body is executed).
>>>
>>> As I said, that doesn't make any sense to me, for the reasons I already
>>> gave, and because failure to allocate memory in precondition checking
>>> does not amount to a broken postcondition.
>>>
>>
>> But in this case the failure is in allocating memory to copy an old value
>> and NOT in checking preconditions.
>>
>> Failing to copy an old value (e.g., for an alloc failure) will indeed not
>> allow to check postconditions because the postconditions use the old value.
>> Therefore, the postconditions should be considered failed because they
>> cannot be checked and postcondition_broken should be called.
>
> No (IMO).  postcondition_broken indicates a program bug.  As I mentioned
> in my guidelines, anticipated failure to satisfy a postcondition is a
> recoverable condition and should normally result in an exception.
>
> Of course, if that would change the contract of the function, it's
> unacceptable ;-)

I see your point... what to do if contract checking (including old-of
copies) throws an exception is a debatable point (also mentioned in a
previous rev of N1962).

However, if you decide to throw the contract exception outside the
called function there are a couple of important issues to keep in
mind:
1) The caller might not except such an exception because is not among
the ones the body throws. You already mentioned the extreme case of
throwing an alloc error because of an old-of copy failure for a body
that does not throw (throw() exception spec)-- the caller will handle
no exception because of the throw() spec of the body.
2) The exception might be thrown when checking class invariants at
destructor entry so the destructor execution will throw violating STL
exception safety rules.

To address 2), N1962 finally decided to:

``
It is always called as if surrounded by a try-catch block:

try
{
    class_invariant();
}
catch( ... )
{
    std::class_invariant_broken();
}
rationale: this ensures that exceptions thrown in the invariant cannot
escape from functions and in particular not from the destructor. [6]
``

In other words, an exception thrown by class invariant checking will
call the class invariant broken handler. I think the best and most
consistent why to handle the more general issue of exceptions thrown
while checking a contract is to always call the broken handlers (so
not just for class invariants but also for preconditions,
postcondition, static class invariants, block invariants, and loop
variants). Note that this neither terminates nor throws, it simply
calls the handlers that only by _default_ terminate.

Note that in Boost.Contract you can redefine the broken handlers to
re-throw exceptions not thrown by a contract condition failure (e.g.,
a runtime_error) but to still terminate otherwise therefore achieving
the behavior you described (you will need to pay special attention to
destructors). For example, set the broken handlers to:

void david_handler(from const& context) {
    try {
        throw; // re-throw to handle active exception
    } catch(contract::broken& condition_failure) {
        // a contract condition was evaluate false, it's a bug terminate
        std::terminate();
    } catch(...) {
        // some other hopefully recoverable error (alloc, etc),
rethrow unless from destructor entry
        if(context == FROM_DESTRUCTOR) std::terminate();
        else throw; // exception will fly out to the caller
    }
}

set_precondition_broken(&david_handler);
set_postcondition_broken(&david_handler);
set_class_invariant_broken(&david_handler);
set_block_invariant_broken(&david_handler);
set_loop_variant_broken(&david_handler);

Here, 2) is explicitly addressed by terminating for destructors all
the times. While 1) should no longer be a concern because if
programmers redefine the handlers to throw it makes sense that they
also program the callers to handle exceptions thrown by the contracts
even if the body is known to not throw them (based on its exception
specifications, documentation, or similar).

In summary, while I have seen this issue debated both ways, I think
the concept "the contract broken handler is called unless the contract
condition is evaluated to be true" is clear and simple (this includes
the case of a contract condition evaluated to be false, an
unercoverable? bug, but also a contract condition that cannot be
evaluated because it throws, a recoverable? error). At the end,
programmers can configure the broken handlers to take the actions they
need (in fact, in some applications even a bug might be better handled
by starting a "return home" procedure instead of terminating the
program).

I will add this example to the docs.

Thanks,
--Lorenzo


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk