Thread overview
Can anyone explain this?
Jun 05, 2018
Shachar Shemesh
Jun 05, 2018
Nicholas Wilson
Jun 05, 2018
Shachar Shemesh
Jun 05, 2018
Simen Kjærås
Jun 05, 2018
Nicholas Wilson
Jun 05, 2018
Nicholas Wilson
Jun 05, 2018
Andrea Fontana
June 05, 2018
I set up to find out what happens if the assert string throws. I have to admit I did not expect the result:

$ cat test.d
import std.stdio;
import core.exception;

void main() {
    scope(failure) {
        writeln("Not gonna happen");
    }

    try {
        static string throwingFunc() {
            throw new Exception("An exception");
        }
        assert(0==1, throwingFunc());
    } catch(Exception ex) {
        writeln("Exception");
    } catch(AssertError ex) {
        writeln("Assert");
    }
}

$ ldc2 --version
LDC - the LLVM D compiler (1.8.0):
  based on DMD v2.078.3 and LLVM 5.0.1
...

$ ./test
Not gonna happen
object.Exception@test.d(11): An exception
----------------
??:? [0x3728941e]
??:? [0x372903aa]
??:? [0x3727b15c]
??:? [0x3724991d]
??:? [0x372496c9]
??:? [0x3727aecf]
??:? [0x3727addb]
??:? [0x3724a124]
??:? __libc_start_main [0xed8b01c0]
??:? [0x372495c9]

$ dmd --version
DMD64 D Compiler v2.080.0
Copyright (C) 1999-2018 by The D Language Foundation, All Rights Reserved written by Walter Bright

$ ./test
Not gonna happen
object.Exception@test.d(11): An exception
----------------
??:? pure @safe immutable(char)[] test.main().throwingFunc() [0xe9b1c2b3]
??:? _Dmain [0xe9b1c1ad]
June 05, 2018
On Tuesday, 5 June 2018 at 08:12:54 UTC, Shachar Shemesh wrote:
> I set up to find out what happens if the assert string throws. I have to admit I did not expect the result:
>
> $ cat test.d
> import std.stdio;
> import core.exception;
>
> void main() {
>     scope(failure) {
>         writeln("Not gonna happen");
>     }
>
>     try {
>         static string throwingFunc() {
>             throw new Exception("An exception");
>         }
>         assert(0==1, throwingFunc());
>     } catch(Exception ex) {
>         writeln("Exception");
>     } catch(AssertError ex) {
>         writeln("Assert");
>     }
> }
>
> $ ldc2 --version
> LDC - the LLVM D compiler (1.8.0):
>   based on DMD v2.078.3 and LLVM 5.0.1
> ...
>
> $ ./test
> Not gonna happen
> object.Exception@test.d(11): An exception

applying a bit of lowering

int Dmain (int arc, const char** argv)
{
    try return main();
    catch (...) dumpThrowable(); // 3 dumps exception
}
void main() {
    try {
        try {
             static string throwingFunc() {
                throw new Exception("An exception");
            }
             __assert_fail(throwingFunc(),...); // 1 throws before assert throws
            } catch(Exception ex) {
               writeln("Exception"); // Why is this not caught? I've no idea
           } catch(AssertError ex) {
              writeln("Assert"); // this is not caught because the thrown throwable is not an exception
          }
     }
     catch(...)
     {
        writeln("Not gonna happen"); // 2 scope failure is run
          throw;
     }

}
June 05, 2018
On Tuesday, 5 June 2018 at 08:12:54 UTC, Shachar Shemesh wrote:
> I set up to find out what happens if the assert string throws.

From here: https://dlang.org/spec/expression.html#assert_expressions

[...]
8. Undefined Behavior: Once in an Invalid State the behavior of the continuing execution of the program is undefined.
[...]

[...]
Best Practices:
- Do not have side effects in either AssignExpression that subsequent code depends on.
- AssertExpressions are intended to detect bugs in the program, do not use for detecting input or environmental errors.
- Do not attempt to resume normal execution after an Assert Failure.
[...]

Anyway you can try to catch "Throwable" to understand what is going to happen (but it's not a good idea at all to keep it in a program)

Andrea
June 05, 2018
On 05/06/18 11:26, Nicholas Wilson wrote:
>                 writeln("Exception"); // Why is this not caught? I've no idea

That's the part I was referring to.
June 05, 2018
On Tuesday, 5 June 2018 at 08:26:22 UTC, Nicholas Wilson wrote:
> writeln("Assert"); // this is not caught because the thrown throwable is not an exception

Now that's plain false - If you replace the call to throwingFunc() with a string literal, you'll see it's properly caught. Also, the catch clause explicitly specifies AssertError, which as the name implies, is an Error, not an Exception.


This might just be a case of undefined behavior. According to https://dlang.org/spec/contracts.html:

"As a contract, an assert represents a guarantee that the code must uphold. Any failure of this expression represents a logic error in the code that must be fixed in the source code. A program for which the assert contract is false is, by definition, invalid, and therefore has undefined behaviour."

In short, what you're doing is UB, and the nasal demons are printing 'not gonna happen' to your console.

That also makes some sense with Andrea Fontana's comments:
> - Do not have side effects in either AssignExpression that subsequent code depends on.
> - Do not attempt to resume normal execution after an Assert Failure.


However, if we add catch (Throwable ex) to the list of catch statements, that *does* catch a regular object.Exception. Moving the catch (Exception) statement down the list does nothing. Adding another layer of try-catch also catches it (even with just catch (Exception ex)):

import std.stdio;
import core.exception;

unittest {
    scope(failure) {
        writeln("Not gonna happen");
    }

    try {
        try {
            static string throwingFunc() {
                throw new Exception("An exception");
            }
            assert(0==1, throwingFunc());
        } catch(AssertError ex) {
            writeln("Assert");
        } catch(Exception ex) {
            writeln("Exception A");
        }
    } catch(Exception ex) {
        writeln("Exception B");
    }
}

So I'm stumped. It seems the exception handling is simply not setup correctly. This could be because of UB I guess, but I'd hope the AssertError would be thrown first in such a case.

--
  Simen
June 05, 2018
On Tuesday, 5 June 2018 at 09:03:03 UTC, Simen Kjærås wrote:
> On Tuesday, 5 June 2018 at 08:26:22 UTC, Nicholas Wilson wrote:
>> writeln("Assert"); // this is not caught because the thrown throwable is not an exception
>
> Now that's plain false - If you replace the call to throwingFunc() with a string literal, you'll see it's properly caught. Also, the catch clause explicitly specifies AssertError, which as the name implies, is an Error, not an Exception.
>

Whoops I meant _is_.

> However, if we add catch (Throwable ex) to the list of catch statements, that *does* catch a regular object.Exception. Moving the catch (Exception) statement down the list does nothing. Adding another layer of try-catch also catches it (even with just catch (Exception ex)):

That does seem odd but is consistent with the implicit "try" surrounding Dmain inserted by the runtime.

> import std.stdio;
> import core.exception;
>
> unittest {
>     scope(failure) {
>         writeln("Not gonna happen");
>     }
>
>     try {
>         try {
>             static string throwingFunc() {
>                 throw new Exception("An exception");
>             }
>             assert(0==1, throwingFunc());
>         } catch(AssertError ex) {
>             writeln("Assert");
>         } catch(Exception ex) {
>             writeln("Exception A");
>         }
>     } catch(Exception ex) {
>         writeln("Exception B");
>     }
> }
>
> So I'm stumped. It seems the exception handling is simply not setup correctly. This could be because of UB I guess, but I'd hope the AssertError would be thrown first in such a case.

The assert error will never be thrown because one of the parameters to the __assert_fail throws. Its like as if you had written.

if (0==1)
{
    string tmp = throwingFunc(); // throws
    __assert_fail(tmp,__LINE__,...); // dead code.
}

Actually it may be the compiler doing something funky with assert(0, msg); being special (assert(0,...) since the spec says for assert any compile time expression == 0  has the effect of assert(0). i.e. assert(0==1); is the same as assert(0);).

Proof


import std.stdio;
import core.exception;
bool foo() { return false;} // mess with the compiler's reasoning about the truthiness of the assert
void main() {
    scope(failure) {
        writeln("Not gonna happen");
    }

    try {
        static string throwingFunc() {
            throw new Exception("An exception");
        }
        assert(foo(), throwingFunc());
    } catch(Exception ex) {
        writeln("Exception");
    } catch(AssertError ex) {
        writeln("Assert");
    }
}

prints

Exception

June 05, 2018
On Tuesday, 5 June 2018 at 09:58:43 UTC, Nicholas Wilson wrote:
> prints
>
> Exception

https://issues.dlang.org/show_bug.cgi?id=18946