Thread overview
The Proper Use of Exception Chaining: Caught between two conflicting usecases?
Apr 09, 2019
FeepingCreature
Apr 26, 2019
FeepingCreature
Apr 27, 2019
Meta
May 02, 2019
FeepingCreature
May 02, 2019
Meta
May 03, 2019
FeepingCreature
May 03, 2019
FeepingCreature
May 03, 2019
Meta
April 09, 2019
There's a feature where, when creating an exception, you can pass another exception as a second parameter. I always thought that the point of this feature was that you could get a backtrace like:

void foo() {
  throw new FooException("foo failure");
}

void bar() {
  try foo;
  catch (FooException fooException) {
    throw new BarException("bar failure", fooException);
  }
}

-----------
  FooException: foo failure
main.d: foo()
  BarException: bar failure
main.d: bar()
main.d: main()

But it turns out that according to the runtime it's related to throwing exceptions as a side effect of unwinding? Ie. if you

scope(exit) throw new BarException("bar");
throw new FooException("foo");

then you get a BarException chained to the FooException? Or the other way around?

That seems a **very** different usecase from the original one.

In the second case, while processing an error we coincidentally encountered a second error.

In the first case, it's still the same error, just being represented now by a different class.

Furthermore, the runtime seems to think case 2 is the correct use for chained exceptions, whereas the documentation ( https://dlang.org/library/object/throwable.next.html ), admittedly a bit hidden away, claims that case 1 is the proper usecase.

In any case, Throwable.toString doesn't care and just omits the chained exception outright.

This seems a very unsatisfying state of affairs! If the use of exception chaining on throwing an exception during unwind cannot be changed, then there is still a case for creating a whole separate implementation of chaining ("exception wrapping"?) for case 2, and reinterpreting the rarely used two-parameter syntax (new Exception("bla", wrapped)) to use this idiom, as well as prominently displaying the wrapped exception in toString output.

I'm very willing to work out a PR for this, but I want to avoid the effort if it's unlikely to be accepted.

What do you think?
April 26, 2019
On Tuesday, 9 April 2019 at 14:27:37 UTC, FeepingCreature wrote:
> What do you think?

Ping! This is a severe issue making debugging pointlessly hard.
April 27, 2019
On Friday, 26 April 2019 at 08:32:58 UTC, FeepingCreature wrote:
> On Tuesday, 9 April 2019 at 14:27:37 UTC, FeepingCreature wrote:
>> What do you think?
>
> Ping! This is a severe issue making debugging pointlessly hard.

Case 2 is the intended use for chained exceptions. It's an answer to the question of "what happens if you throw an exception while another one is already in flight?" In the case of C++, the program is immediately aborted. In D's case, the second exception will be chained onto the first one.
May 02, 2019
On Saturday, 27 April 2019 at 07:08:50 UTC, Meta wrote:
> On Friday, 26 April 2019 at 08:32:58 UTC, FeepingCreature wrote:
>> Ping! This is a severe issue making debugging pointlessly hard.
>
> Case 2 is the intended use for chained exceptions. It's an answer to the question of "what happens if you throw an exception while another one is already in flight?" In the case of C++, the program is immediately aborted. In D's case, the second exception will be chained onto the first one.

Okay, but then that first of all contradicts the documentation at https://dlang.org/library/object/throwable.next.html which indicates that this feature is certainly confusing enough to be misunderstandable, and second of all there's still very much a call for being able to react to an exception by throwing an exception of another type, without losing the first exception's stack trace. Currently if we catch three types of exceptions that happen somewhere in Phobos and rethrow them as some generic or domain exception, the error will appear to come from the exception handler. Does the error come from the exception handler? No! The actual source of the error has been completely obfuscated. This is bad!
May 02, 2019
On Thursday, 2 May 2019 at 07:10:45 UTC, FeepingCreature wrote:
>> Case 2 is the intended use for chained exceptions. It's an answer to the question of "what happens if you throw an exception while another one is already in flight?" In the case of C++, the program is immediately aborted. In D's case, the second exception will be chained onto the first one.
>
> Okay, but then that first of all contradicts the documentation at https://dlang.org/library/object/throwable.next.html

I don't understand how what I said contradicts what's on that page. It seems to agree with what I said, from what I can tell:

A reference to the next error in the list. This is used when a new Throwable is thrown from inside a catch block. The originally caught Exception will be chained to the new Throwable via this field

> ...and second of all there's still very much a call for being able to react to an exception by throwing an exception of another type, without losing the first exception's stack trace.

I completely agree; this feature is invaluable in figuring out what went wrong when looking at a stack trace.

> Currently if we catch three types of exceptions that happen somewhere in Phobos and rethrow them as some generic or domain exception, the error will appear to come from the exception handler. Does the error come from the exception handler? No! The actual source of the error has been completely obfuscated. This is bad!

Again, agreed. My only disagreement is over what Throwable.next is intended to be used for.

May 03, 2019
On Thursday, 2 May 2019 at 18:34:39 UTC, Meta wrote:
> I don't understand how what I said contradicts what's on that page. It seems to agree with what I said, from what I can tell:
>
> A reference to the next error in the list. This is used when a new Throwable is thrown from inside a catch block. The originally caught Exception will be chained to the new Throwable via this field
>

Subtle difference: It says "thrown from inside a *catch* block", but current exception chaining is for exceptions thrown from inside a *finally* block.

Btw, I want to take this opportunity to highlight this wonderful bug I found a few days ago, coincidentally also with exception chaining: https://issues.dlang.org/show_bug.cgi?id=19831 , when you throw *and catch* an exception inside a finally block the runtime gets very confused, and calls the handler for the *chained* exception - but with the *thrown* exception.
May 03, 2019
On Thursday, 2 May 2019 at 18:34:39 UTC, Meta wrote:
> I don't understand how what I said contradicts what's on that page. It seems to agree with what I said, from what I can tell:
>
> A reference to the next error in the list. This is used when a new Throwable is thrown from inside a catch block. The originally caught Exception will be chained to the new Throwable via this field
>

Subtle difference: It says "thrown from inside a *catch* block", but current exception chaining is for exceptions thrown from inside a *finally* block.

Btw, I want to take this opportunity to highlight this wonderful bug I found a few days ago, coincidentally also with exception chaining: https://issues.dlang.org/show_bug.cgi?id=19831 , when you throw *and catch* an exception inside a finally block the runtime gets very confused, and calls the handler for the *chained* exception - but with the *thrown* exception. Fun!
May 03, 2019
On Friday, 3 May 2019 at 07:12:15 UTC, FeepingCreature wrote:
> On Thursday, 2 May 2019 at 18:34:39 UTC, Meta wrote:
>> I don't understand how what I said contradicts what's on that page. It seems to agree with what I said, from what I can tell:
>>
>> A reference to the next error in the list. This is used when a new Throwable is thrown from inside a catch block. The originally caught Exception will be chained to the new Throwable via this field
>>
>
> Subtle difference: It says "thrown from inside a *catch* block", but current exception chaining is for exceptions thrown from inside a *finally* block.

Ah, I see.

> Btw, I want to take this opportunity to highlight this wonderful bug I found a few days ago, coincidentally also with exception chaining: https://issues.dlang.org/show_bug.cgi?id=19831 , when you throw *and catch* an exception inside a finally block the runtime gets very confused, and calls the handler for the *chained* exception - but with the *thrown* exception. Fun!

That's a nasty one.