Document number: P0770R0 Date: 2017-08-21 Audience: CWG, EWG Reply-to: Michael Kilburn # A Proposal to Specify Behavior in Case of Exception Allocation Failure ## 1. Introduction (and motivation) Memory for exception object is allocated in an 'unspecified way' (18.1/4). This makes it impossible for application to handle such allocation failures. If implementation uses dynamic storage for exception this makes it impossible to write code that reliably handles lack of space in dynamic storage (aka *out-of-memory* (OOM)). On the other hand code that uses C-style error handling doesn't have this issue. This proposal aims to "close the gap" by mandating certain behavior in case of exception allocation failure and providing tools to handle it. ## 2. Impact On the Standard Failure to allocate memory for exception being thrown will have a well-defined behavior. ## 3. Problem Consider this snippet in context of typical stack-based implementation (e.g. MSVC/GCC): ```c int foo() { return -1; } ... int res = foo(); if (res) return res; ``` and this one: ```c++ void foo() { throw -1; } ... foo(); ``` First snippet can have only two outcomes: - stack overflow if there is no space on stack for another frame - error object (-1) is successfully returned to caller Second snippet can have three outcomes: - stack overflow - exception object (-1) is successfully constructed and thrown - **unspecified** if memory allocation for exception object fails, no way to handle something unspecified in the code With GCC (which allocates exceptions in dynamic storage and calls *std::terminate()* on failure) this means you can't write code that reliably survives OOM. It is understandable why GCC chose *std::terminate()* (over throwing *std::bad_alloc*, for example) -- no one expects `throw -1` to throw something else instead. Note that fundamentally these two snippets do the same thing -- construct error object and pass it to caller. The important difference that in first one object memory is already preallocated by caller and in second one -- it gets allocated by callee when it is time to throw. ## 4. Solution In short: * introduce new exception *XX* (eXceptional eXception) that is guaranteed to be thrown without fail * change 18.1/4 to mandate that `throw ABC` will throw *XX* if runtime can't allocate memory for *ABC* * (optional) clarify 21.8.6/8 -- mention that *XX* can be thrown if memory for *std::bad_exception* can't be allocated Subsequent sections describe "problem points" discovered so far. ### 4.1 New exception It is better to (re)use *std::bad_alloc* for *XX*. But it may be useful to distinguish between running out of dynamic storage and running out of "exception storage" -- if it is important, *std::bad_alloc* can be extended with additional member variable that specifies storage we ran out of. Using std::bad_alloc means stdlib doesn't need to be changed to accomodate new exception. (Regardless of choice made wrt std::bad_alloc) *XX* support should be built in such way that throwing it does not consume storage used for normal exception mechanisms. If it is impossible to implement -- this proposal can be disregarded. With GCC (which uses Itanium C++ ABI), no-fault *throw XX* should be possible -- for example by passing NULL into *\_\_cxa_throw()* and coding internals accordingly. ### 4.2 Ability to detect 'out of exception storage' condition Implementation must be able to detect allocation failure. It is expected that it shouldn't be a problem (certainly not for GCC), but if it is (for example if exception gets allocated on stack with unknown size) -- this proposal may need an update. ### 4.3 std::current_exception() *std::current_exception()* is declared *noexcept* and returns (equivalent of) NULL pointer if no exception is being handled right now. In some implementations it makes a copy of exception object, which can throw and fail to allocate memory for exception -- in this case returned *exception_ptr* should represent *XX* (probably a pointer to statically allocated object or magical value recognized by *rethrow_exception()*/etc) ### 4.4 Existing code Only very small portion of existing code can be expected to "break" -- in a sense that unspecified behaviour of an extremely rare situation will get replaced with defined (but somewhat surprising) behavior. Majority of code is written to handle exceptions *in general* and will not need to be changed. Also, typically user exceptions have throwing copy constructors and it is expected that *throw MyException(...)* can throw *std::bad_alloc*. ### 4.5 General thoughts on implementations Implementations using dynamic storage for exceptions (like GCC) should not have any problems. Implementations that use call stack as exception storage (like MSVC) could be considered problematic since C++ traditionally doesn't provide means of handling out-of-stack condition (probably because this will make impossible to create *no-fail* functions). My belief that it shouldn't be a game changer since at time of allocation implementation should know how much stack has been left and should be able to check it. Implementation can also use a separate stack for exceptions -- to be created on first throw (in current thread) with every allocation checked against remaining free space. This will allow stack unwinding to work very similar to C-style error propagation -- by dropping stack frames one-by-one as unwinding makes progress (thus making it available to be used for calling destructors). ## 5. Proposed Text TBD