Thread overview
Error and Exception chaining
Dec 12, 2016
Yuxuan Shui
Dec 12, 2016
Ali Çehreli
Dec 12, 2016
Yuxuan Shui
Dec 12, 2016
Ali Çehreli
Dec 12, 2016
Yuxuan Shui
Dec 13, 2016
Yuxuan Shui
Dec 13, 2016
Yuxuan Shui
December 12, 2016
I read https://dlang.org/spec/statement.html, which told me that Error is different in the way it's chained. But that is pretty vague, and I'm still confused.

Can someone explain that using examples?

Thanks.
December 12, 2016
(Note: Looks like there is a bug regarding Error.bypassedException member. Would others please confirm.)

On 12/12/2016 01:15 PM, Yuxuan Shui wrote:
> I read https://dlang.org/spec/statement.html, which told me that Error
> is different in the way it's chained. But that is pretty vague, and I'm
> still confused.
>
> Can someone explain that using examples?
>
> Thanks.

You're referring to "[Errors] bypass the normal chaining mechanism, such that the chain can only be caught by catching the first Error." What it means is that an Error cannot be a collateral of an Exception, hiding in its chain. So, when there is an Error that would otherwise be a collateral of an Exception, you cannot catch the original Exception. What is more importantly in-flight at that time is the Error.

The following program causes a chain of exceptions:

import std.stdio;
import std.string;
import std.range;

class TestException : Exception {
    this(string msg) {
        const fullMmsg = format("%s: %s",typeof(this).stringof, msg);
        writefln("throwing '%s'", fullMmsg);
        super(fullMmsg);
    }
}

class TestError : Error {
    this(string msg) {
        const fullMmsg = format("%s: %s",typeof(this).stringof, msg);
        writefln("throwing '%s'", fullMmsg);
        super(fullMmsg);
    }
}

// Causes an exception chain where the node at index errorIndex is an
// Error (others are all Exceptions).
void causeExceptionChain(size_t chainLength, size_t errorIndex) {
    void throws(size_t n) {
        scope (exit) {
            const msg = format("%s", n);
            if (n == errorIndex) {
                throw new TestError(msg);
            }
            else {
                throw new TestException(msg);
            }
        }

        if (n != 0) {
            // Redundant 'return' keyword due to
            // https://issues.dlang.org/show_bug.cgi?id=16960
            return throws(n - 1);
        }
    }

    throws(chainLength - 1);
}

void main() {
    try {
        // -1 would mean "no Error in the chain". Change this to a
        // number between 0 and 4 (inclusive) then you will realize
        // that the Exception below will not be caught.
        size_t errorIndex = -1;
        causeExceptionChain(5, errorIndex);
    }
    catch (Exception original) {
        writefln("Caught");
        // Unrelated: If you're not familiar with the curly braces in
        // the for loop, see the Note under "The sections of the for
        // loop" section at http://ddili.org/ders/d.en/for.html
        for ({ size_t i; Throwable ex = original; } ex; ex = ex.next, ++i) {
            writeln(" ".replicate(i), ex.msg);
        }
    }
}

throwing 'TestException: 0'
throwing 'TestException: 1'
throwing 'TestException: 2'
throwing 'TestException: 3'
throwing 'TestException: 4'
Caught
TestException: 0
 TestException: 1
  TestException: 2
   TestException: 3
    TestException: 4

Make errorIndex something other than -1 e.g. 3 and you will see that the Exception cannot be caught.

        size_t errorIndex = 3;

You get the usual stack trace of an uncaught exception.

throwing 'TestException: 0'
throwing 'TestException: 1'
throwing 'TestException: 2'
throwing 'TestError: 3'
throwing 'TestException: 4'
deneme.TestError@(0): TestError: 3
----------------
[...]

Then, replace Exception with Error in the catch clause, and you will see that Error is again caught. (Side note: Errors are not supposed to be caught by programs because the whole state of the program is in question.)

    catch (Error original) {    // <-- Now Error
        // ...
    }

You will see that Error is in its own chain. It will contain just Error 3 and Error 4:

throwing 'TestException: 0'
throwing 'TestException: 1'
throwing 'TestException: 2'
throwing 'TestError: 3'
throwing 'TestException: 4'
Caught
TestError: 3
 TestException: 4

Now you would hope to get the original bypassed Exception chain with the following code

    writefln("The bypassed exception was '%s'", original.bypassedException);

But bypassedException member of Error is always null. Bug?

Thank you,
Ali

December 12, 2016
Thanks a lot for the explanation!

On Monday, 12 December 2016 at 22:01:54 UTC, Ali Çehreli wrote:
> (Note: Looks like there is a bug regarding Error.bypassedException member. Would others please confirm.)
>
> On 12/12/2016 01:15 PM, Yuxuan Shui wrote:
> > [...]
> that Error
> > [...]
> vague, and I'm
> > [...]
>
> You're referring to "[Errors] bypass the normal chaining mechanism, such that the chain can only be caught by catching the first Error." What it means is that an Error cannot be a collateral of an Exception, hiding in its chain. So, when there is an Error that would otherwise be a collateral of an Exception, you cannot catch the original Exception. What is more importantly in-flight at that time is the Error.
>

But chaining Error to Error works just like chaining Exception to Exception?

>
> But bypassedException member of Error is always null. Bug?

Did I just randomly found a bug?

>
> Thank you,
> Ali

December 12, 2016
On 12/12/2016 02:08 PM, Yuxuan Shui wrote:
> Thanks a lot for the explanation!
>
> On Monday, 12 December 2016 at 22:01:54 UTC, Ali Çehreli wrote:
>> (Note: Looks like there is a bug regarding Error.bypassedException
>> member. Would others please confirm.)
>>
>> On 12/12/2016 01:15 PM, Yuxuan Shui wrote:
>> > [...]
>> that Error
>> > [...]
>> vague, and I'm
>> > [...]
>>
>> You're referring to "[Errors] bypass the normal chaining mechanism,
>> such that the chain can only be caught by catching the first Error."
>> What it means is that an Error cannot be a collateral of an Exception,
>> hiding in its chain. So, when there is an Error that would otherwise
>> be a collateral of an Exception, you cannot catch the original
>> Exception. What is more importantly in-flight at that time is the Error.
>>
>
> But chaining Error to Error works just like chaining Exception to
> Exception?

Currently yes. Now this line in my versatile :o) program:

            if (n >= errorIndex) {

Two Errors are chained:

Caught
TestError: 3
 TestError: 4

However, there is the following ongoing thread claiming that it was a wrong decision:

  http://forum.dlang.org/post/o2n347$2i1g$1@digitalmars.com

>> But bypassedException member of Error is always null. Bug?
>
> Did I just randomly found a bug?

From what I could graps from that much of documentation, yes, it seems to be a bug.

Ali

December 12, 2016
On Monday, 12 December 2016 at 22:13:59 UTC, Ali Çehreli wrote:
> On 12/12/2016 02:08 PM, Yuxuan Shui wrote:
> > [...]
> wrote:
> >> [...]
> Error.bypassedException
> >> [...]
> mechanism,
> >> [...]
> Error."
> >> [...]
> Exception,
> >> [...]
> otherwise
> >> [...]
> original
> >> [...]
> is the Error.
> > [...]
> Exception to
> > [...]
>
> Currently yes. Now this line in my versatile :o) program:
>
>             if (n >= errorIndex) {
>
> Two Errors are chained:
>
> Caught
> TestError: 3
>  TestError: 4
>
> However, there is the following ongoing thread claiming that it was a wrong decision:
>
>   http://forum.dlang.org/post/o2n347$2i1g$1@digitalmars.com
>
> > [...]
>
> From what I could graps from that much of documentation, yes, it seems to be a bug.

I did some testing and bypassedException is null in 2.072.1, but is not null in 2.070.2

>
> Ali

December 13, 2016
On Monday, 12 December 2016 at 22:35:22 UTC, Yuxuan Shui wrote:
> On Monday, 12 December 2016 at 22:13:59 UTC, Ali Çehreli wrote:
>> On 12/12/2016 02:08 PM, Yuxuan Shui wrote:
>> > [...]
>> wrote:
>> >> [...]
>> Error.bypassedException
>> >> [...]
>> mechanism,
>> >> [...]
>> Error."
>> >> [...]
>> Exception,
>> >> [...]
>> otherwise
>> >> [...]
>> original
>> >> [...]
>> is the Error.
>> > [...]
>> Exception to
>> > [...]
>>
>> Currently yes. Now this line in my versatile :o) program:
>>
>>             if (n >= errorIndex) {
>>
>> Two Errors are chained:
>>
>> Caught
>> TestError: 3
>>  TestError: 4
>>
>> However, there is the following ongoing thread claiming that it was a wrong decision:
>>
>>   http://forum.dlang.org/post/o2n347$2i1g$1@digitalmars.com
>>
>> > [...]
>>
>> From what I could graps from that much of documentation, yes, it seems to be a bug.
>
> I did some testing and bypassedException is null in 2.072.1, but is not null in 2.070.2
>
>>
>> Ali

The unwind process seem to stop one level too early, causing language_specific_data to be different, causing __dmd_personality_v0 to not chain exception into .bypassException.
December 13, 2016
On Tuesday, 13 December 2016 at 00:33:58 UTC, Yuxuan Shui wrote:
> On Monday, 12 December 2016 at 22:35:22 UTC, Yuxuan Shui wrote:
>> On Monday, 12 December 2016 at 22:13:59 UTC, Ali Çehreli wrote:
>>> On 12/12/2016 02:08 PM, Yuxuan Shui wrote:
>>> > [...]
>>> wrote:
>>> >> [...]
>>> Error.bypassedException
>>> >> [...]
>>> mechanism,
>>> >> [...]
>>> Error."
>>> >> [...]
>>> Exception,
>>> >> [...]
>>> otherwise
>>> >> [...]
>>> original
>>> >> [...]
>>> is the Error.
>>> > [...]
>>> Exception to
>>> > [...]
>>>
>>> Currently yes. Now this line in my versatile :o) program:
>>>
>>>             if (n >= errorIndex) {
>>>
>>> Two Errors are chained:
>>>
>>> Caught
>>> TestError: 3
>>>  TestError: 4
>>>
>>> However, there is the following ongoing thread claiming that it was a wrong decision:
>>>
>>>   http://forum.dlang.org/post/o2n347$2i1g$1@digitalmars.com
>>>
>>> > [...]
>>>
>>> From what I could graps from that much of documentation, yes, it seems to be a bug.
>>
>> I did some testing and bypassedException is null in 2.072.1, but is not null in 2.070.2
>>
>>>
>>> Ali
>
> The unwind process seem to stop one level too early, causing language_specific_data to be different, causing __dmd_personality_v0 to not chain exception into .bypassException.

OK. I think I figured it out. catch(Error x) won't catch TestException, so TestException 0,1,2 is not handled at main(). However TestError 3 (and TestExcepotion 4 which is chained to it) is handled at main(). And dmd won't put Throwables that are handled at different places together.

This behavior seems reasonable. So maybe this is actually a bug is LDC?