Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lifetime of data associated with std::current_exception

Consider the following code:

std::exception_ptr eptr{ std::current_exception() };
const char* msg = 0;
try {
    if (eptr != std::exception_ptr{}) {
        std::rethrow_exception(eptr);
    }
} catch(const std::exception& ex) {
    msg = ex.what();
}

Can I use msg outside of catch? In other words does ex references to the same exception instance as eptr? Thanks!

like image 645
AlexT Avatar asked Jan 12 '23 05:01

AlexT


2 Answers

The description of rethrow_exception says:

Throws: the exception object to which p refers.

So the thrown object is the same one to which the pointer refers. The pointer may or may not refer to the original "current exception", because current_exception might make a copy of the exception object, but that's irrelevant to your question.

So yes, the exception object that you rethrow is the same one that's owned by eptr and thus remains alive as long at least as long as that pointer owns it.

like image 113
Kerrek SB Avatar answered Jan 30 '23 09:01

Kerrek SB


TL;DR: I would not, because ex and eptr (might ?) reference different exceptions. (see edit: it might be guaranteed, but the standard is unclear enough that I would avoid relying on it)

The lifetime of exceptions is ruled by §15.1 [except.throw], and specifically:

4/ The memory for the exception object is allocated in an unspecified way, except as noted in §3.7.4.1. If a handler exits by rethrowing, control is passed to another handler for the same exception. The exception object is destroyed after either the last remaining active handler for the exception exits by any means other than rethrowing, or the last object of type std::exception_ptr (§18.8.5) that refers to the exception object is destroyed, whichever is later. In the former case, the destruction occurs when the handler exits, immediately after the destruction of the object declared in the exception-declaration in the handler, if any. In the latter case, the destruction occurs before the destructor of std::exception_ptr returns. The implementation may then deallocate the memory for the exception object; any such deallocation is done in an unspecified way. [ Note: a thrown exception does not propagate to other threads unless caught, stored, and rethrown using appropriate library functions; see §18.8.5 and §30.6. —end note ]

However, you also need to take into account the previous paragraph:

3/ Throwing an exception copy-initializes (§8.5, §12.8) a temporary object, called the exception object. The temporary is an lvalue and is used to initialize the variable named in the matching handler (§15.3). [...]

Thus, what happens in your case:

  • eptr is initialized to point to the current exception in flight, guaranteeing this exception lives at least as long as it does
  • ex is initialized to reference a copy of the exception pointed to by eptr
  • when the catch clause ends, the copy dies
  • msg was pointing into the copy, and thus is left dangling... unless both copy and original shared the same message under the covers.

The only way to guarantee that an exception lives long enough for your purpose is to create a std::exception_ptr, directly.

EDIT Ralph Tandetzky brough to my attention that §18.8.5 [propagation] says:

7/ [...] [ Note: if rethrow_exception rethrows the same exception object (rather than a copy), concurrent access to that rethrown exception object may introduce a data race. Changes in the number of exception_ptr objects that refer to a particular exception do not introduce a data race. —end note ]

Which suggests that in the following paragraph:

[[noreturn]] void rethrow_exception(exception_ptr p);

9/ Requires: p shall not be a null pointer.

10/ Throws: the exception object to which p refers.

the Throws clause should not be interpreted as some kind of throw *p;, but instead could have the same behavior as throw; (re-throwing the very same instance, not a copy). However, since there is a if in paragraph 7, it seems it might be a copy too...


Note: interestingly experiments suggests that the underlying object may be shared... which contradicts my reading of the standard; I don't quite understand how copy-initializes could be interpreted differently though.

like image 32
Matthieu M. Avatar answered Jan 30 '23 09:01

Matthieu M.