On 05/06/12 17:44, Jonathan M Davis wrote:
On Tuesday, June 05, 2012 13:57:14 Don Clugston wrote:
On 05/06/12 09:07, Jonathan M Davis wrote:
On Tuesday, June 05, 2012 08:53:16 Don Clugston wrote:
On 04/06/12 21:29, Steven Schveighoffer wrote:
On Mon, 04 Jun 2012 06:20:56 -0400, Don Clugston<d...@nospam.com>   wrote:
1. There exist cases where you cannot know why the assert failed.
2. Therefore you never know why an assert failed.
3. Therefore it is not safe to unwind the stack from a nothrow
function.

Spot the fallacies.

The fallacy in moving from 2 to 3 is more serious than the one from 1
to 2: this argument is not in any way dependent on the assert occuring
in a nothrow function. Rather, it's an argument for not having
AssertError at all.

I'm not sure that is the issue here at all. What I see is that the
unwinding of the stack is optional, based on the assumption that there's
no "right" answer.

However, there is an underlying driver for not unwinding the stack --
nothrow. If nothrow results in the compiler optimizing out whatever
hooks a function needs to properly unwind itself (my limited
understanding is that this helps performance), then there *is no
choice*, you can't properly unwind the stack.

-Steve

No, this whole issue started because the compiler currently does do
unwinding whenever it can. And Walter claimed that's a bug, and it
should be explicitly disabled.

It is, in my view, an absurd position. AFAIK not a single argument has
been presented in favour of it. All arguments have been about "you
should never unwind Errors".

It's quite clear that we cannot completely, correctly unwind the stack in
the face of Errors.

Well that's a motherhood statement. Obviously in the face of extreme
memory corruption you can't guarantee *any* code is valid.
The *main* reason why stack unwinding would not be possible is if
nothrow intentionally omits stack unwinding code.

It's not possible precisely because of nothrow.


nothrow only means 'does not throw Exceptions'. It doesn't mean 'does not throw Errors'.
Therefore, given:

int foo() nothrow { ...}


try
{
   foo();
} catch (Error e)
{
  ...
}

even though there are no throw statements inside foo(), the compiler is NOT permitted to remove the catch(Error), whereas it could remove catch(Exception).

The problem is 'finally' clauses. Are they called only on Exception, or on Exception and Error?

Regardless, I think that there are a number of people in this thread who
are mistaken in how recoverable they think Errors and/or segfaults are,
and they seem to be the ones pushing the hardest for full stack unwinding
on the theory that they could somehow ensure safe recovery and a clean
shutdown when an Error occurs, which is almost never possible, and
certainly isn't possible in the general case.

- Jonathan M Davis

Well I'm pushing it because I implemented it (on Windows).

I'm less knowledgeable about what happens on other systems, but know
that on Windows, the whole system is far, far more robust than most
people on this thread seem to think.

I can't see *any* problem with executing catch(Error) clauses. I cannot
envisage a situation where that can cause a problem. I really cannot.

In many cases, it's probably fine, but if the program is in a bad enough state
that an Error is thrown, then you can't know for sure that any particular such
block will execute properly (memory corruption being the extreme case), and if
it doesn't run correctly, then it could make things worse (e.g. writing
invalid data to a file, corrupting that file). Also, if the stack is not unwound
perfectly (as nothrow prevents), then the program's state will become
increasingly invalid the farther that the program gets from the throw point,
which will increase the chances of cleanup code functioning incorrectly, as
any assumptions that they've made about the program state are increasingly
likely to be wrong (as well as it being increasingly likely that the variables
that they operate on no longer being valid).

A lot of it comes down to worst case vs typical case. In the typical case, the
code causing the Error is isolated enough and the code doing the cleanup is
self-contained enough that trying to unwind the stack as much as possible will
result in more correct behavior than skipping it all. But in the worst case,
you can't rely on running any code being safe, because the state of the
program is very much invalid, in which case, it's better to kill the program
ASAP. Walter seems to subscribe to the approach that it's best to assume the
worst case (e.g. that an assertion failure indicates horrible memory
corruption), and always have Errors function that way, whereas others
subscribe to the approach that things are almost never that bad, so we should
just assume that they aren't, since skipping all of that cleanup causes other
problems.

I believe I now understand the root issue behind this dispute.

Consider:

if (x) throw new FileError;

if (x) throw new FileException;

What is the difference between these two, from the point of view of the compiler? Practically nothing. Only the name is different. There is absolutely no difference in the validity of the machine state when executing the first, rather than the second.

In both cases it is possible that something has gone horribly wrong; it's also possible that it's a superficial problem.

The difference between Error and Exception is a matter of *convention*.

Now, what people have been pointing out is that *even with things like null pointer exceptions* there are still cases where the machine state has remained valid.

Now, we can say that when an Error is thrown, the machine is in an invalid state *by definition*, regardless of whether it really is, or not. If we do this, then Walters statements about catching AssertErrors become valid, but for a different reason.

When you have thrown an Error, you've told the compiler that the machine is in an invalid state. Catching it and continuing is wrong not because the machine is unstable (which might be true, or might not); rather it's wrong because it's logically inconsistent: by throwing an Error you've told the compiler that it is not recoverable, but by catching it, you've also told it that it is recoverable!

If we chose to say that Error means that the machine is in an invalid state, there are still a couple of issues:

(1) How to deal with cases where the compiler generates an Error, but you know that the machine state is still valid, and you want to supress the Error and continue.

I think Steven's point near the start of the thread was excellent: in the cases where recovery is possible, it is almost always extremely close to the point where the Error was generated.

(2) Does it make sense to run finally clauses on Error, if we are saying that the machine state is invalid?

Ie, at present they are finally_Throwable clauses, should they instead be finally_Exception clauses?

I cannot see any way in which it makes sense to run them if they're in a throwable function, but not if they are in a nothrow function.

If we take the view that Error by definition implies an invalid machine state, then I don't think they should run at all.

But noting that in a significant fraction of cases, the machine state isn't actually invalid, I think it can be reasonable to provide a mechanism to make them be run.

BTW its worth noting that cleanup is not necessarily performed even in the Exception case. If an exception is thrown while processing a finally clause (eg, inside a destructor) then the destructor didn't completely run. C++ just aborts the program if this happens. We've got exception chaining so that case is well defined, and can be detected, nonetheless it's got a lot of similarities to the Error case.

Reply via email to