www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Corrupt Unwinding on Errors

reply Eyal Lotem <eyal weka.io> writes:
Hi,

I've hit a serious bug due to skipped dtors when errors are being 
thrown.

Whether dtors will run or be skipped is quite unpredictable, as 
it depends on `nothrow` inference[1].

dtors are often absolutely crucial for program correctness, and 
skipping them is very dangerous.

Thus, unwinding while skipping dtors may easily create 
corruption, so I'll term this "corrupt unwinding".

During corrupt unwinding, running scope() code is dangerous - as 
it runs in a potentially corrupt state, and indeed a PR exists to 
not run scope() code in this state [2].

The question is, why have corrupt unwinding at all?

If errors thrown should not properly unwind - why not call a 
tls-installed handler before terminating the program (with no 
unwinding)?

If errors do unwind - why not run the dtors and allow actual 
recovery? The performance cost for predictable, sane semantics is 
well worth it.

Side-note: resuming execution after corrupt unwinding is rarely 
practically possible in non-trivial programs. Knowing the effects 
of all potentially skipped dtors in all program and library code 
- especially as the program is maintained over time - is 
impractical.

[1] https://issues.dlang.org/show_bug.cgi?id=19602

[2] https://github.com/dlang/dmd/pull/6896
Jan 22 2019
parent reply Iain Buclaw <ibuclaw gdcproject.org> writes:
On Tue, 22 Jan 2019 at 09:15, Eyal Lotem via Digitalmars-d
<digitalmars-d puremagic.com> wrote:
 Hi,

 I've hit a serious bug due to skipped dtors when errors are being
 thrown.

 Whether dtors will run or be skipped is quite unpredictable, as
 it depends on `nothrow` inference[1].

 dtors are often absolutely crucial for program correctness, and
 skipping them is very dangerous.

 Thus, unwinding while skipping dtors may easily create
 corruption, so I'll term this "corrupt unwinding".

 During corrupt unwinding, running scope() code is dangerous - as
 it runs in a potentially corrupt state, and indeed a PR exists to
 not run scope() code in this state [2].

 The question is, why have corrupt unwinding at all?

 If errors thrown should not properly unwind - why not call a
 tls-installed handler before terminating the program (with no
 unwinding)?

 If errors do unwind - why not run the dtors and allow actual
 recovery? The performance cost for predictable, sane semantics is
 well worth it.
Throwing an Error inside a nothrow function is allowed, unlike an Exception. As for what happens if that were to ever happen is undefined, because you're not supposed to recover from it. -- Iain
Jan 22 2019
parent reply Eyal Lotem <eyal weka.io> writes:
On Tuesday, 22 January 2019 at 09:14:30 UTC, Iain Buclaw wrote:
 On Tue, 22 Jan 2019 at 09:15, Eyal Lotem via Digitalmars-d 
 <digitalmars-d puremagic.com> wrote:

 Throwing an Error inside a nothrow function is allowed, unlike 
 an Exception.  As for what happens if that were to ever happen 
 is undefined, because you're not supposed to recover from it.
If you're not supposed to recover, then what's the point of corrupt unwinding of the stack? It would be much safer to terminate the program right there, allowing a user-installed handler to do its thing before termination. Not unwinding is far better than *corrupt* unwinding.
Jan 22 2019
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/22/19 4:48 AM, Eyal Lotem wrote:
 On Tuesday, 22 January 2019 at 09:14:30 UTC, Iain Buclaw wrote:
 On Tue, 22 Jan 2019 at 09:15, Eyal Lotem via Digitalmars-d 
 <digitalmars-d puremagic.com> wrote:

 Throwing an Error inside a nothrow function is allowed, unlike an 
 Exception.  As for what happens if that were to ever happen is 
 undefined, because you're not supposed to recover from it.
If you're not supposed to recover, then what's the point of corrupt unwinding of the stack? It would be much safer to terminate the program right there, allowing a user-installed handler to do its thing before termination. Not unwinding is far better than *corrupt* unwinding.
So a couple things here: 1. It's not possible to correctly recover from Errors because of the corrupt unwinding, as you say. This is because D compilers take advantage of nothrow attribution to avoid building costly unwinding handlers in functions. It's in fact unwinding the stack, but the compiler *has omitted* the unwinding code that would normally be executed. 2. If you don't unwind, this means that throwing an error isn't exactly throwing. It's basically aborting. That is, the advantage of catching the error in an outer scope is lost. I agree with you that corrupt unwinding can cause more problems than no unwinding. I think it should be made possible to at least *opt-in* to aborting instead of unwinding. Another possibility is not to unwind the stack, but simply pop the stack without executing any unwinding (even proper unwinding code), until you get to the handler, which must assume nothing has been unwound in the case of an Error. This would be similar to the abort mechanism, but gives the control to the appropriate catch statement. A problem with druntime today is that Throwable is caught in the thread startup and main routines. No recovery is attempted, the system is aborted, but there's still an attempt to print out the stack trace. See here: https://github.com/dlang/druntime/blob/master/src/rt/dmain2.d#L480-L484 This assumes some things are still available, e.g. C malloc and stderr, when really, an Error could actually mean that they aren't valid to be used. I'd also like to see an option to have nothrow still generate unwinding code, especially in debug/non-release mode. -Steve
Jan 24 2019