import core.stdc.stdio : fprintf, stderr;

class MyException : Exception
{
    this() { super(typeof(this).stringof); }
}

void throw_catch()
{
    try
    {
        throw new MyException;
    }
    catch (MyException)
    {
    }
    catch (Exception)
    {
        assert(false);
    }
}

// Test that exceptions that are entirely thrown and caught in finally blocks don't affect exception handling.
void test1()
{
    try
    {
        try
        {
            throw new Exception("p");
        }
        finally
        {
            throw_catch();
        }
    }
    catch (Exception e)
    {
        assert(e.msg == "p");
    }
}

// Test that exceptions that are entirely thrown and caught in finally blocks don't interfere with chaining.
void test2()
{
    try
    {
        try
        {
            try
            {
                throw new Exception("p");
            }
            finally
            {
                throw new Exception("q");
            }
        }
        finally
        {
            throw_catch();
        }
    }
    catch(Exception e)
    {
        assert(e.msg == "p");
        assert(e.next.msg == "q");
        assert(!e.next.next);
    }
}

void test3()
{
    try
    {
        try
        {
            try
            {
                throw new Exception("p");
            }
            finally
            {
                throw_catch();
            }
        }
        finally
        {
            throw new Exception("q");
        }
    }
    catch(Exception e)
    {
        assert(e.msg == "p");
        assert(e.next.msg == "q");
        assert(!e.next.next);
    }
}

// Test order of exception handler operations.
void test4()
{
    string result;
    void throw_catch()
    {
        pragma(inline, false);
        try
        {
            result ~= "b";
            throw new MyException;
        }
        catch (MyException)
        {
            result ~= "c";
        }
        catch (Exception)
        {
            assert(false);
        }
    }
    try
    {
        try
        {
            result ~= "a";
            throw new Exception("");
        }
        finally
        {
            throw_catch();
        }
    }
    catch(Exception e)
    {
        result ~= "d";
    }
    assert(result == "abcd");
}

void test5()
{
    string result;
    void fail()
    {
        result ~= "b";
        throw new Exception("a");
    }

    void throw_catch()
    {
        pragma(inline, false);
        try
        {
            fail();
        }
        catch(Exception e)
        {
            assert(e.msg == "a");
            assert(!e.next);
            result ~= "c";
        }
    }
    try
    {
        try
        {
            result ~= "a";
            throw new Exception("x");
        }
        finally
        {
            throw_catch();
        }
    }
    catch (Exception e)
    {
        assert(e.msg == "x");
        assert(!e.next);
        result ~= "d";
    }
    assert(result == "abcd");
}

void main() {
    test1();
    test2();
    test3();
    test4();
    test5();
    fprintf(stderr, "success.\n");
}