Thread overview
chain of exceptions, next method
Aug 13, 2022
kdevel
Aug 14, 2022
Ali Çehreli
Aug 14, 2022
Paul Backus
Sep 10, 2022
Andrej Mitrovic
Mar 06
kdevel
August 13, 2022

Quote from src/druntime/src:

    /**
     * Returns:
     * A reference to the _next error in the list. This is used when a new
     * $(D Throwable) is thrown from inside a $(D catch) block. The originally
     * caught $(D Exception) will be chained to the new $(D Throwable) via this
     * field.
     */
    @property inout(Throwable) next() @safe inout return scope pure nothrow @nogc { return nextInChain; }

Testcode:

import std.stdio;
import std.exception;

private:

class E1 : Exception { mixin basicExceptionCtors; }
class E2 : Exception { mixin basicExceptionCtors; }

void foo ()
{
   try throw new E1 ("e1");
   catch (Exception e)
      throw new E2 ("e2");
}

void bar ()
{
   auto e = new E1 ("e1");
   e.next = new E2 ("e2");
   throw e;
}

void dumpall (Exception e)
{
   Throwable t = e;
   stderr.writeln ("dumpall");
   do
      stderr.writeln (t.msg);
   while ((t = t.next) !is null); // XXX: Why does !!(t = t.next) not compile?
}

public void main ()
{
   try foo ();
   catch (Exception e)
      dumpall (e);

   try bar ();
   catch (Exception e)
      dumpall (e);
}

$ dmd cetest
$ ./cetest
dumpall
e2
dumpall
e1
e2

Would have expected two chained Exceptions in both cases. Is it permitted to append exceptions from user code?

August 13, 2022
On 8/13/22 15:59, kdevel wrote:
> Quote from `src/druntime/src`:
>
> ```
>      /**
>       * Returns:
>       * A reference to the _next error in the list. This is used when a new
>       * $(D Throwable) is thrown from inside a $(D catch) block. The
> originally
>       * caught $(D Exception) will be chained to the new $(D Throwable)
> via this
>       * field.
>       */
>      @property inout(Throwable) next() @safe inout return scope pure
> nothrow @nogc { return nextInChain; }
>
> ```

This automatic "combining" of exceptions happens for cleanup code like scope(exit). (I remember bug(s) for scope(failure).):

import std.stdio;

void foo() {
  // Bug? Should work for scope(failure) as well.
  scope (exit) {
    bar();
  }
  throw new Exception("from foo");
}

void bar() {
  throw new Exception("from bar");
}

void main() {
  try {
    foo();

  } catch (Exception exc) {
    for (Throwable e = exc; e; e = e.next) {
      writeln(e.msg);
    }
  }
}

The output:

from foo
from bar

You can do the same by calling chainTogether(). Here is an excerpt from an old experiment of mine:

      try {
        foo();

      } catch (Throwable exc) {
        Throwable.chainTogether(exc, new Exception(" ... "));
        throw exc;
      }

Ali

August 14, 2022
On Sunday, 14 August 2022 at 02:07:05 UTC, Ali Çehreli wrote:
>
> This automatic "combining" of exceptions happens for cleanup code like scope(exit). (I remember bug(s) for scope(failure).):

To be precise, an exception thrown inside a 'finally' block gets chained onto the previous exception, but an exception thrown inside a 'catch' block does not. scope(exit) and scope(failure) are just syntax sugar for 'finally' and 'catch', respectively.

Relevant spec paragraph:

> If an exception is raised in the FinallyStatement and is not caught before
> the original exception is caught, it is chained to the previous exception via
> the next member of Throwable. Note that, in contrast to most other
> programming languages, the new exception does not replace the original
> exception. Instead, later exceptions are regarded as 'collateral damage'
> caused by the first exception. The original exception must be caught, and
> this results in the capture of the entire chain.

Source: https://dlang.org/spec/statement.html#try-statement

So, once an exception is caught, the chain ends, and any exception thrown after that begins a new chain.
September 10, 2022
On Sunday, 14 August 2022 at 02:30:43 UTC, Paul Backus wrote:
> On Sunday, 14 August 2022 at 02:07:05 UTC, Ali Çehreli wrote:
>>
>> This automatic "combining" of exceptions happens for cleanup code like scope(exit). (I remember bug(s) for scope(failure).):
>
> To be precise, an exception thrown inside a 'finally' block gets chained onto the previous exception, but an exception thrown inside a 'catch' block does not. scope(exit) and scope(failure) are just syntax sugar for 'finally' and 'catch', respectively.

I wish the compiler would rewrite scope(failure) to use chained exceptions. Otherwise any exceptions thrown within scope(failure) can end up losing information about what was the original exception that was thrown.
March 06
On Saturday, 10 September 2022 at 08:48:39 UTC, Andrej Mitrovic wrote:
> [...]
> I wish the compiler would rewrite scope(failure) to use chained exceptions. Otherwise any exceptions thrown within scope(failure) can end up losing information about what was the original exception that was thrown.

Ran into this issue with the following ordering bug:

   auto tmpfilename = fn.dup ~ ".XXXXXX\0";
   int fd = mkstemp (tmpfilename.ptr);
   scope (failure) remove (tmpfilename); // bug:
   if (fd == -1)
      throw new Exception (strerror(errno).to!string);

The error thrown was

   Failed to remove file ...

Is there any work in progress to chain the exceptions in scope(failure)?
March 06
On Wednesday, March 6, 2024 6:06:34 AM MST kdevel via Digitalmars-d-learn wrote:
> On Saturday, 10 September 2022 at 08:48:39 UTC, Andrej Mitrovic
>
> wrote:
> > [...]
> > I wish the compiler would rewrite scope(failure) to use chained
> > exceptions. Otherwise any exceptions thrown within
> > scope(failure) can end up losing information about what was the
> > original exception that was thrown.
>
> Ran into this issue with the following ordering bug:
>
>     auto tmpfilename = fn.dup ~ ".XXXXXX\0";
>     int fd = mkstemp (tmpfilename.ptr);
>     scope (failure) remove (tmpfilename); // bug:
>     if (fd == -1)
>        throw new Exception (strerror(errno).to!string);
>
> The error thrown was
>
>     Failed to remove file ...
>
> Is there any work in progress to chain the exceptions in
> scope(failure)?

If anything, I think that it's been decided that chained exceptions were a mistake. So, if things go in any direction with them, it's likely to be towards removing them, not doing more to support them.

- Jonathan M Davis