Bug 41174 - uncaught_exception always returns true
Summary: uncaught_exception always returns true
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: libstdc++ (show other bugs)
Version: 4.3.2
: P3 normal
Target Milestone: 4.8.3
Assignee: Jason Merrill
URL:
Keywords:
Depends on:
Blocks: 59224
  Show dependency treegraph
 
Reported: 2009-08-26 06:19 UTC by Bill Ashby
Modified: 2014-04-01 17:29 UTC (History)
6 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail: 4.3.4, 4.4.1, 4.5.0
Last reconfirmed: 2009-08-27 10:37:03


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Bill Ashby 2009-08-26 06:19:58 UTC
C:\...>gcc -v
Using built-in specs.
Target: djgpp
Configured with: /v203/gcc-4.32/configure msdosdjgpp
Thread model: single
gcc version 4.3.2 (GCC)

Build command line:  gpp -oexcpt -DTESTBADE excpt.C

Execute command line:  excpt

The following source code is a pared down version of a short program I wrote to try the boundaries of the functions in <exception>.  An exception object's constructor itself throws and catches an exception.  After that, uncaught_exception always returns true.  In the "Output:" comment at the end of the source file, places where uncaught_exception should return false are marked with "!!!".

// excpt.C uses exceptions in strange and unusual ways
//
// build command line:  gpp -oexcpt -DTESTBADE excpt.C
// execute command line:  excpt

#include <iostream>
#include <exception>

void uncaught(int i = 0)
{
    std::cerr << "[" << i <<"] std::uncaught_exception() = " << std::boolalpha
        << std::uncaught_exception() << std::endl;
}

class E {
    const char* descr;

public:
    E(const char* str): descr(str)
    {
        std::cerr << "E::E(\"" << descr << "\")\n";
        uncaught();    }
    E(const E& e): descr(e.descr)
    {
        std::cerr << "E::E(const E&)\n";
        uncaught();
    }
    ~E()
    {
        std::cerr << "E::~E()\n";
        uncaught();
    }
    const char* describe() { return descr; }
};

/*****************************************************/
#ifdef TESTBADE
class BadE {
    const char* str;

public:
    BadE(const char* s)
    try
        : str(s)
    {
        std::cerr << "BadE::BadE(const char*) throwing \"thrown by Bade::BadE(const char*)\"\n";
        throw "thrown by BadE::BadE(const char*)";
    }
    catch (const char* str) {
        std::cerr << "BadE::BadE(const char*) caught \"" << str << "\"\n";
    }

    ~BadE()
    {
        std::cerr << "BadE::~BadE()\n";
    }

    const char* describe() { return str; }
};

void throwBadE()
{
    std::cerr << "throwBadE():  throw BadE(\"thrown by throwBadE()\")\n";
    throw BadE("thrown by throwBadE()");
}
#endif
/*****************************************************/

void x(int i)
{
    try {
        std::cerr
            << "[" << i <<"] x(int) throwing E(\"E from x(int) try block\")\n";
        throw E("E from x(int) try block");
    }
    catch (E e) {
        std::cerr << "[" << i <<"] x(int) entering catch (E) block\n";
        uncaught(i);
        std::cerr << "[" << i <<"] x(int) caught E(\"" << e.describe() << "\") by value\n";
        uncaught(i);
        std::cerr << "[" << i <<"] x(int) exiting catch (E) block\n";
    }
    std::cerr << "[" << i << "] x(int) tries uncaught before exiting\n";
    uncaught(i);
}

int main()
{
    x(1);
#ifdef TESTBADE
/*****************************************************/
    std::cerr << "*****************************************************\n";
    try {
        std::cerr << "main() calling void throwBadE();\n";
        try {
            throwBadE();
        }
        catch (BadE& be) {
            std::cerr << "catch (BadE&) block => \"" << be.describe() << "\"\n";
        }
        catch (const char* str) {
            std::cerr << "catch (const char*) block:  " << str << "\n";
        }
        catch (...) {
            std::cerr << "catch (...) block after calling void throwBadE();\n";
        }
    }
    catch (...) {
        std::cerr << "catch (...) looking for leftover exceptions ???\n";
    }
/*****************************************************/
#endif
    std::cerr << "*****************************************************\n";
    x(2);
    std::cerr << "*****************************************************\n";
    std::cerr << "before returning from main() check uncaught\n";
    uncaught(3);
}

/*
Output:

[1] x(int) throwing E("E from x(int) try block")
E::E("E from x(int) try block")
[0] std::uncaught_exception() = true
E::E(const E&)
[0] std::uncaught_exception() = true
[1] x(int) entering catch (E) block
[1] std::uncaught_exception() = false
[1] x(int) caught E("E from x(int) try block") by value
[1] std::uncaught_exception() = false
[1] x(int) exiting catch (E) block
E::~E()
[0] std::uncaught_exception() = false
E::~E()
[0] std::uncaught_exception() = false
[1] x(int) tries uncaught before exiting
[1] std::uncaught_exception() = false
*****************************************************
main() calling void throwBadE();
throwBadE():  throw BadE("thrown by throwBadE()")
BadE::BadE(const char*) throwing "thrown by Bade::BadE(const char*)"
BadE::BadE(const char*) caught "thrown by BadE::BadE(const char*)"
catch (const char*) block:  thrown by BadE::BadE(const char*)
*****************************************************
[2] x(int) throwing E("E from x(int) try block")
E::E("E from x(int) try block")
[0] std::uncaught_exception() = true
E::E(const E&)
[0] std::uncaught_exception() = true
[2] x(int) entering catch (E) block
[2] std::uncaught_exception() = true                      !!!
[2] x(int) caught E("E from x(int) try block") by value
[2] std::uncaught_exception() = true                      !!!
[2] x(int) exiting catch (E) block
E::~E()
[0] std::uncaught_exception() = true                      !!!
E::~E()
[0] std::uncaught_exception() = true                      !!!
[2] x(int) tries uncaught before exiting
[2] std::uncaught_exception() = true                      !!!
*****************************************************
before returning from main() check uncaught
[3] std::uncaught_exception() = true                      !!!
*/
Comment 1 Jonathan Wakely 2009-08-26 15:31:17 UTC
Reduced:

#include <cassert>
#include <exception>

struct GoodE {
    GoodE()
    {
        try {
            throw 1;
        } catch (...) {
        }
    }
};

struct BadE {
    BadE()
        try {
            throw 1;
        } catch (...) {
        }
};

int main()
{
    try {
        throw GoodE();
    } catch (...) {
        assert( !std::uncaught_exception() );
    }

    try {
        throw BadE();
    } catch (...) {
        assert( !std::uncaught_exception() );
    }
}

Note that GoodE doesn't cause the problem. The difference is that BadE has a function-try-block
Comment 2 Jonathan Wakely 2009-08-26 15:52:00 UTC
It looks as though uncaught_exception() does not always become false when entering the handler of a function-try-block, and this causes it to stay true.

Maybe the count of uncaught exceptions is not decremented in the function-try-block's handler, but is incremented when the exception is rethrown at the end of the handler, causing it to be one more than it should not be.

Comment 3 Jonathan Wakely 2009-08-26 16:01:01 UTC
(In reply to comment #2)
> at the end of the handler, causing it to be one more than it should not be.

Oops, obviously I meant "one more than it should be"

Comment 4 Jonathan Wakely 2009-08-26 16:17:59 UTC
(In reply to comment #1)
> Note that GoodE doesn't cause the problem. The difference is that BadE has a
> function-try-block

And, of course, that the exception is rethrown by BadE at the end of the handler. Changing GoodE to rethrow makes it have the same behaviour, so it's not specific to function-try-blocks.

I give up for now, I'll just link to bug 10606 and http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#475 for reference




Comment 5 Jonathan Wakely 2009-08-26 16:50:44 UTC
I think the problem is that the uncaught_exception() is true as soon as the memory for the exception has been allocated, but if the exception's copy constructor is elided then happens before entering the exception's constructor.  If the exception constructor throws another exception then uncaughtExceptions goes to 2, and never goes back to zero.

uncaught_exception() should return true after evaluating the operand of throw. If the operand cannot be constructed (because it throws) then that evaluation never completes.

My understanding is that this should run to completeion, but all four assertions fail:

#include <cassert>
#include <exception>

struct e {
    e()
    {
        assert( !std::uncaught_exception() );
        try {
            throw 1;
        } catch (int i) {
            assert( !std::uncaught_exception() );
            throw;
        }
    }
};

int main()
{
    try {
        throw e();
    } catch (int i) {
        assert( !std::uncaught_exception() );
    }
    assert( !std::uncaught_exception() );
}


Comment 6 Paolo Carlini 2009-08-27 10:51:23 UTC
Is this related to PR 37477?
Comment 7 Jonathan Wakely 2009-08-27 11:37:15 UTC
(In reply to comment #6)
> Is this related to PR 37477?

It looks like a slightly different issue.  PR 37477 relates to when uncaught_exception() stops being true and fixing it might need to wait for the resolution to core issue 475.

This bug relates to when uncaught_exception() starts being true, and I don't think it depends on issue 475 (although it might make sense to wait for a resolution anyway.)
The table in issue 475 would show today's GCC as "1 1 1 1 ABRT" and I believe that first "1" is wrong and is the root cause of this bug.

Comment 8 Paolo Carlini 2009-08-27 11:59:02 UTC
As a general rule, if we are sure about the dependency on a DR, we suspend the PR and remember the DR # in the Summary.
Comment 9 Jonathan Wakely 2009-08-27 12:18:14 UTC
(In reply to comment #5)
> I think the problem is that the uncaught_exception() is true as soon as the
> memory for the exception has been allocated, but if the exception's copy
> constructor is elided then happens before entering the exception's constructor.
>  If the exception constructor throws another exception then uncaughtExceptions
> goes to 2, and never goes back to zero.

After re-reading [except.throw] and [except.terminate] I think it is OK to elide the copy of the thrown object into the exception object here:
   throw e();
This means that uncaught_exception() can be true in the call e::e()

*But* if the copy is elided and e::e() throws then std::terminate() should be called. This means the OP's testcase and my one in comment #5 should terminate, rather than exiting normally with std::uncaught_exception()==true

This is because of the following in [except.terminate]/1

"when the exception handling mechanism, after completing evaluation of the expression to be thrown but before the exception is caught (15.1), calls a function that exits via an uncaught exception, 141"
"141) For example, if the object being thrown is of a class with a copy constructor, std::terminate() will be called if that copy constructor exits with an exception during a throw."

Now, either

(A) e::e() happens before completing evaluation of the expression to be thrown and uncaught_exception() should be false during e::e()

or

(B) the copy is elided and e::e() happens after evaluating completing evaluation. In this case, uncaught_exception() should be true during e::e() but if it exits with an exception then std::terminate() should be called.

GCC elides the copy, but does not call std::terminate() if e::e() exits with an exception.  I don't think this interpretation will be changed by DR 475
Comment 10 Bill Ashby 2009-08-27 14:27:25 UTC
(In reply to comment #9)
> ...
> "when the exception handling mechanism, after completing evaluation of the
> expression to be thrown but before the exception is caught (15.1), calls a
> function that exits via an uncaught exception, 141"
> "141) For example, if the object being thrown is of a class with a copy
> constructor, std::terminate() will be called if that copy constructor exits
> with an exception during a throw."
> ...

I'm not sure that this applies in this situation.  An instance of BadE is constructed because it is thrown, but BadE::BadE does not "[exit] via an uncaught exception".  It both throws and catches an exception, and then returns normally.  There is still an uncaught exception when BadE::BadE exits, but it is the one that caused BadE to be constructed, not the one that BadE::BadE has thrown (and caught).
Comment 11 Jonathan Wakely 2009-08-27 15:19:25 UTC
(In reply to comment #10)
> 
> I'm not sure that this applies in this situation.  An instance of BadE is
> constructed because it is thrown, but BadE::BadE does not "[exit] via an
> uncaught exception".  It both throws and catches an exception, and then returns
> normally.  There is still an uncaught exception when BadE::BadE exits, but it
> is the one that caused BadE to be constructed, not the one that BadE::BadE has
> thrown (and caught).

No, in [except.handle]/16 the standard says

"The exception being handled is rethrown if control reaches the end of a handler of the function-try-block of a constructor or destructor."

You cannot swallow exceptions in a constructor's function-try-block, they will be rethrown.

> 

Comment 12 Bill Ashby 2009-08-29 11:27:14 UTC
(In reply to comment #11)
> (In reply to comment #10)
> > 
> > I'm not sure that this applies in this situation.  An instance of BadE is
> > constructed because it is thrown, but BadE::BadE does not "[exit] via an
> > uncaught exception".  It both throws and catches an exception, and then returns
> > normally.  There is still an uncaught exception when BadE::BadE exits, but it
> > is the one that caused BadE to be constructed, not the one that BadE::BadE has
> > thrown (and caught).
> 
> No, in [except.handle]/16 the standard says
> 
> "The exception being handled is rethrown if control reaches the end of a
> handler of the function-try-block of a constructor or destructor."
> 
> You cannot swallow exceptions in a constructor's function-try-block, they will
> be rethrown.
> 
> > 
> 
1,000 apologies, of course you are right; my own code and shows that (blush).  Maybe uncaught_exception assumes that the throw BadE will succeed and doesn't notice that the BadE object is not fully constructed?
Comment 13 Bill Ashby 2009-08-29 12:50:00 UTC
(In reply to comment #12)
> ...
> Maybe uncaught_exception assumes that the throw BadE will succeed and doesn't
> notice that the BadE object is not fully constructed?
> 

Apparently that is the case.  Here's another test case where throw fails to throw an exception:

#include <cstdlib>
#include <iostream>
#include <exception>

void ae()
{
    std::cerr << "At exit std::uncaught_exception() = " << std::boolalpha
        << std::uncaught_exception() << ".\n";
}

struct NoE {
    NoE() { std::exit(0); }
};


int main()
{
    std::atexit(ae);
    throw NoE();
}

/* Output:
At exit std::uncaught_exception() = true.
*/
Comment 14 Paolo Carlini 2010-01-08 19:14:09 UTC
I'm asking Rth to have a look to this one, apparently unrelated to DR Core 475.
Comment 15 Jason Merrill 2014-01-27 13:58:11 UTC
Author: jason
Date: Mon Jan 27 13:57:39 2014
New Revision: 207129

URL: http://gcc.gnu.org/viewcvs?rev=207129&root=gcc&view=rev
Log:
	Core DR 475
	PR c++/41174
	PR c++/59224
	* libsupc++/eh_throw.cc (__cxa_throw): Set uncaughtExceptions.
	* libsupc++/eh_alloc.cc (__cxa_allocate_dependent_exception)
	(__cxa_allocate_exception): Don't set it here.

Added:
    trunk/gcc/testsuite/g++.dg/eh/uncaught4.C
Modified:
    trunk/gcc/testsuite/g++.dg/eh/uncaught1.C
    trunk/libstdc++-v3/ChangeLog
    trunk/libstdc++-v3/libsupc++/eh_alloc.cc
    trunk/libstdc++-v3/libsupc++/eh_throw.cc
Comment 16 Jason Merrill 2014-01-27 13:59:21 UTC
Author: jason
Date: Mon Jan 27 13:58:48 2014
New Revision: 207131

URL: http://gcc.gnu.org/viewcvs?rev=207131&root=gcc&view=rev
Log:
	Core DR 475
	PR c++/41174
	PR c++/59224
	* libsupc++/eh_throw.cc (__cxa_throw): Set uncaughtExceptions.
	* libsupc++/eh_alloc.cc (__cxa_allocate_dependent_exception)
	(__cxa_allocate_exception): Don't set it here.

Added:
    branches/gcc-4_8-branch/gcc/testsuite/g++.dg/eh/uncaught4.C
Modified:
    branches/gcc-4_8-branch/gcc/testsuite/g++.dg/eh/uncaught1.C
    branches/gcc-4_8-branch/libstdc++-v3/ChangeLog
    branches/gcc-4_8-branch/libstdc++-v3/libsupc++/eh_alloc.cc
    branches/gcc-4_8-branch/libstdc++-v3/libsupc++/eh_throw.cc
Comment 17 Jason Merrill 2014-01-27 14:03:46 UTC
Fixed for 4.8.3/4.9.
Comment 18 Jason Merrill 2014-04-01 17:29:01 UTC
Author: jason
Date: Tue Apr  1 17:28:29 2014
New Revision: 208991

URL: http://gcc.gnu.org/viewcvs?rev=208991&root=gcc&view=rev
Log:
	Core DR 475
	PR c++/41174
	PR c++/59224
	* libsupc++/eh_throw.cc (__cxa_throw): Set uncaughtExceptions.
	* libsupc++/eh_alloc.cc (__cxa_allocate_dependent_exception)
	(__cxa_allocate_exception): Don't set it here.

Added:
    branches/gcc-4_7-branch/gcc/testsuite/g++.dg/eh/uncaught4.C
Modified:
    branches/gcc-4_7-branch/gcc/testsuite/g++.dg/eh/uncaught1.C
    branches/gcc-4_7-branch/libstdc++-v3/ChangeLog
    branches/gcc-4_7-branch/libstdc++-v3/libsupc++/eh_alloc.cc
    branches/gcc-4_7-branch/libstdc++-v3/libsupc++/eh_throw.cc