From: kanze on
Niklas Matthies wrote:
> On 2005-07-03 10:19, Maxim Yegorushkin wrote:
> > On Sat, 02 Jul 2005 14:23:03 +0400, Alf P. Steinbach wrote:

> >>> If the assertion fails when there is no debugger, how do
> >>> you expect the program to recover?

> >> That's actually a good _C++_ question... ;-)

> >> First, the reason why one would like to 'throw' in this
> >> case, which is usually not to recover in the sense of
> >> continuing normal execution, but to recover enough to do
> >> useful logging, reporting and graceful exit on an end-user
> >> system with no debugger and other programmer's tools
> >> (including, no programmer's level understanding of what
> >> goes on).

> > Why would one want a graceful exit when code is broken,
> > rather than dying as loud as possible leaving a core dump
> > with all state preserved, rather than unwound?

> Because the customer expects and demands it. Actually, more
> often than not the customer even demands graceful resumption.

I guess it depends on the customer. None of my customers would
ever have accepted anything but "aborting" anytime we were
unsure of the data. Most of the time, trying to continue the
program after an assertion failed would have been qualified as
"grobe Fahrlýssigkeit" -- I think the correct translation is
"criminal negligence".

But I'm not sure that that was the question. My impression
wasn't that people were saying, continue, even if you don't know
what you are doing. My impression was that we were discussing
the best way to shut the program down; basically: with or
without stack walkback. Which can be summarized by something
along the lines of: trying to clean up, but risk doing something
bad, or get out as quickly as possible, with as little risk as
possible, and leave the mess.

My experience (for the most part, in systems which are more or
less critical in some way, and under Unix) is that the operating
system will clean up most of the mess anyway, and that any
attempts should be carefully targetted, to minimize the risk.
Throwing an exception means walking back the stack, which in
turn means executing a lot of unnecessary and potentially
dangerous destructors. I don't think that the risk is that
great, typically, but it is very, very difficult, if not
impossible, to really evaluate. For example, I usually have
transaction objects on the stack. Calling the destructor
without having called commit should normally provoke a roll
back. But if I'm unsure of the global invariants of the
process, it's a risk I'd rather not take; maybe the destructor
will misinterpret some data, and cause a commit, although the
transaction didn't finish correctly. Where as if I abort, the
connection to the data base is broken (by the OS), and the data
base automatically does its roll back in this case. Why take
the risk (admittedly very small), when a solution with zero risk
exists?

But this is based on my personal experience. I can imagine that
in the case of a light weight graphical client, for example, the
analysis might be different. About all that can go wrong is
that the display is all messed up, and in this case, the user
will kill the process and restart it manually. And of course,
you might luck out, the user might not even notice, and you can
pull one over on him.

Still, if what the program is doing is important, and not just
cosmetic, you must take into account that if the program
invariants don't hold, it may do something wrong. If doing
something wrong can have bad consequences (and not just cosmetic
effects), then you really should limit what you try to do to a
minimum. Regardless of what some naive user might think. In
such cases, walking back the stack calling destructors
represents a significantly greater risk than explicitly
executing a very limited set of targetted clean-up operations.

--
James Kanze GABI Software
Conseils en informatique orientýe objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sýmard, 78210 St.-Cyr-l'ýcole, France, +33 (0)1 30 23 00 34


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

From: David Abrahams on
alfps(a)start.no (Alf P. Steinbach) writes:

> * Peter Dimov:
>> > >
>> > > No recovery is possible after a failed assert.
>>
>> [The above] means that performing stack unwinding after a failed
>> assert is usually a bad idea.
>
> I didn't think of that interpretation, but OK.
>
> The interpretation, or rather, what you _meant_ to say in the first
> place,

AFAICT that was a *conclusion* based on what Peter had said before.

> is an opinion, which makes it more difficult to discuss.


> After a failed assert it's known that something, which could be anything
> (e.g. full corruption of memory), is wrong. Attempting to execute even
> one teeny tiny little instruction might do unimaginable damage. Yet you
> think it's all right to not only terminate the process but also to log
> things, which involves file handling, as long as one doesn't do a stack
> rewind up from the point of the failed assert.

There are two problems with stack unwinding at that point:

1. It executes more potentially damaging instructions than necessary,
since none of the destructors or catch blocks involved have
anything to do with process termination or logging.

2. The flow of execution proceeds into logic that is usually involved
with the resumption of normal execution, and it's easy to end up
back in code that executes as though everything is still fine.

> This leads me to suspect that you're confusing a failed assert with
> a corrupted stack, or that you think that a failure to clean up 100%
> might be somehow devastating. Anyway, an explanation of your
> opinion would be great, and this time, please write what you mean,
> not something entirely different.

Ouch.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

From: Peter Dimov on
David Abrahams wrote:
> "Peter Dimov" <pdimov(a)gmail.com> writes:
>
> > Either
> >
> > (a) you go the "correct program" way and use assertions to verify that your
> > expectations match the observed behavior of the program, or
> >
> > (b) you go the "resilient program" way and use exceptions in an attempt to
> > recover from certain situations that may be caused by bugs.
> >
> > (a) implies that whenever an assert fails, the program no longer behaves as
> > expected, so everything you do from this point on is based on _hope_ that
> > things aren't as bad.
> >
> > (b) implies that whenever stack unwinding might occur, you must assume that
> > the conditions that you would've ordinarily tested with an assert do not
> > hold.
>
> And while it is possible to do (b) in a principled way, it's much more
> difficult than (a), because once you unwind and return to "normal"
> code with the usual assumptions about program integrity broken, you
> have to either:
>
> 1. Test every bit of data obsessively to make sure it's still
> reasonable, or
>
> 2. Come up with a principled way to decide which kinds of
> brokenness you're going to look for and try to circumvent, and
> which invariants you're going to assume still hold.
>
> In practice, I think doing a complete job of (1) is really impossible,
> so you effectively have to do (2).

It's possible to do (b) when you know that the stack unwinding will
completely destroy the potentially corrupted state, and it seems
possible - in theory - to write programs this way.

That is, instead of:

void menu_item_4()
{
frobnicate( document );
}

one might write

void menu_item_4()
{
Document tmp( document );

try
{
frobnicate( tmp );
document.swap( tmp );
}
catch( exception const & x )
{
// maybe attempt autosave( document ) here
report_error( x );
}
}

Even if there are bugs in frobnicate, if it doesn't leave tmp in an
undestroyable state, it's possible to continue.

I still prefer (a), of course. :-)


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

From: David Abrahams on
alfps(a)start.no (Alf P. Steinbach) writes:

>> And while it is possible to do (b) in a principled way, it's much more
>> difficult than (a), because once you unwind and return to "normal"
>> code with the usual assumptions about program integrity broken, you
>> have to either:
>>
>> 1. Test every bit of data obsessively to make sure it's still
>> reasonable, or
>>
>> 2. Come up with a principled way to decide which kinds of
>> brokenness you're going to look for and try to circumvent, and
>> which invariants you're going to assume still hold.
>>
>> In practice, I think doing a complete job of (1) is really impossible,
>> so you effectively have to do (2).
>
> [Here responding to David Abraham's statement:]
>
> I think your points (1) and (2) summarizes approach (b) well, and show
> that it's not a technique one would choose if there was a choice.

I think that's what Peter meant when he wrote "performing stack
unwinding after a failed assert is usually a bad idea."
^^^^^^^

> But as mentioned above, it's an extreme
^^^^
What is an extreme?

> although in some other languages (e.g. Java) you get null-pointer
> exceptions & the like.

IMO null-pointer exceptions are a joke; it's a way of claiming that
the language is typesafe and making that sound bulletproof: all you do
is turn programming errors into exceptions with well-defined behavior!
Fantastic! Now my program goes on doing... something... even though
its state might be completely garbled.

>> Note also that once you unwind to "normal" code, information about
>> the particular integrity check that failed tends to get lost: all
>> the different throw points unwind into the same instruction stream,
>> so there really is a vast jungle of potential problems to consider.
>
> I agree, for the C++ exceptions we do have.
>
> If we did have some kind of "hard" exception supported by the
> language, or even just standardized and supported by convention,
> then the vast jungle of potential problems that stands in the way of
> further normal execution wouldn't matter so much: catching that hard
> exception at some uppermost control level you know that the process
> has to terminate, not continue with normal execution (which was the
> problem), and you know what actions you've designated for that case
> (also known by throwers, or at least known to be irrelevant to
> them), so that's what the code has to attempt to do.

And what happens if the corrupted programming state causes a crash
during unwinding (e.g. from a destructor)?

What makes executing the unwinding actions the right thing to do? How
do you know which unwinding actions will get executed at the point
where you detect that your program state is broken?

>> [snip]
>> ....and make it someone else's problem. Code higher up the call stack
>> mike know how to deal with it, right? ;-)
>
> In the case of a "hard" exception it's not "might", it's a certainty.

?? I don't care how you flavor the exception; the appropriate recovery
action cannot always be known by the outermost layer of the program.
Furthermore, what the outermost layer of the program knows how to do
becomes irrelevant if there is a crash during unwinding.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

From: David Abrahams on
"Peter Dimov" <pdimov(a)gmail.com> writes:

> David Abrahams wrote:
>> "Peter Dimov" <pdimov(a)gmail.com> writes:
>>
>> > Either
>> >
>> > (a) you go the "correct program" way and use assertions to verify that your
>> > expectations match the observed behavior of the program, or
>> >
>> > (b) you go the "resilient program" way and use exceptions in an attempt to
>> > recover from certain situations that may be caused by bugs.
>> >
>> > (a) implies that whenever an assert fails, the program no longer behaves as
>> > expected, so everything you do from this point on is based on _hope_ that
>> > things aren't as bad.
>> >
>> > (b) implies that whenever stack unwinding might occur, you must assume that
>> > the conditions that you would've ordinarily tested with an assert do not
>> > hold.
>>
>> And while it is possible to do (b) in a principled way, it's much more
>> difficult than (a), because once you unwind and return to "normal"
>> code with the usual assumptions about program integrity broken, you
>> have to either:
>>
>> 1. Test every bit of data obsessively to make sure it's still
>> reasonable, or
>>
>> 2. Come up with a principled way to decide which kinds of
>> brokenness you're going to look for and try to circumvent, and
>> which invariants you're going to assume still hold.
>>
>> In practice, I think doing a complete job of (1) is really impossible,
>> so you effectively have to do (2).
>
> It's possible to do (b) when you know that the stack unwinding will
> completely destroy the potentially corrupted state, and it seems
> possible - in theory - to write programs this way.

<snip example that copies program state, modifies, and swaps>

What you've just done -- implicitly -- is to decide which kinds of
brokenness you're going to look for and try to circumvent, and which
invariants you're going to assume still hold. For example, your
strategy assumes that whatever broke invariants in the copy of your
document didn't also stomp on the memory in the original document.
Part of what your strategy does is to increase the likelihood that
your assumptions will be correct, but if you're going to go down the
(b)(2) road in a principled way, you have to recognize where the
limits of your program's resilience are.

--
Dave Abrahams
Boost Consulting
www.boost-consulting.com

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

First  |  Prev  |  Next  |  Last
Pages: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Next: C++/CLI limitations?