diff --git a/libstdc++-v3/libsupc++/nested_exception.h b/libstdc++-v3/libsupc++/nested_exception.h index 002a54e9fef..dec3c0c2a05 100644 --- a/libstdc++-v3/libsupc++/nested_exception.h +++ b/libstdc++-v3/libsupc++/nested_exception.h @@ -35,6 +35,7 @@ #else #include +#include extern "C++" { @@ -45,12 +46,22 @@ namespace std _GLIBCXX_VISIBILITY(default) * @{ */ - /// Exception class with exception_ptr data member. + /** Mixin class that stores the current exception. + * + * This type can be used via `std::throw_with_nested` to store + * the current exception nested within another exception. + * + * @headerfile exception + * @since C++11 + * @see std::throw_with_nested + * @ingroup exceptions + */ class nested_exception { exception_ptr _M_ptr; public: + /// The default constructor stores the current exception (if any). nested_exception() noexcept : _M_ptr(current_exception()) { } nested_exception(const nested_exception&) noexcept = default; @@ -59,6 +70,7 @@ namespace std _GLIBCXX_VISIBILITY(default) virtual ~nested_exception() noexcept; + /// Rethrow the stored exception, or terminate if none was stored. [[noreturn]] void rethrow_nested() const @@ -68,6 +80,7 @@ namespace std _GLIBCXX_VISIBILITY(default) std::terminate(); } + /// Access the stored exception. exception_ptr nested_ptr() const noexcept { return _M_ptr; } @@ -87,6 +100,7 @@ namespace std _GLIBCXX_VISIBILITY(default) { } }; +#if __cplusplus < 201703L || ! defined __cpp_if_constexpr // [except.nested]/8 // Throw an exception of unspecified type that is publicly derived from // both remove_reference_t<_Tp> and nested_exception. @@ -95,8 +109,7 @@ namespace std _GLIBCXX_VISIBILITY(default) inline void __throw_with_nested_impl(_Tp&& __t, true_type) { - using _Up = typename remove_reference<_Tp>::type; - throw _Nested_exception<_Up>{std::forward<_Tp>(__t)}; + throw _Nested_exception<__remove_cvref_t<_Tp>>{std::forward<_Tp>(__t)}; } template @@ -104,11 +117,31 @@ namespace std _GLIBCXX_VISIBILITY(default) inline void __throw_with_nested_impl(_Tp&& __t, false_type) { throw std::forward<_Tp>(__t); } +#endif /// @endcond - /// If @p __t is derived from nested_exception, throws @p __t. - /// Else, throws an implementation-defined object derived from both. + /** Throw an exception that also stores the currently active exception. + * + * If `_Tp` is derived from `std::nested_exception` or is not usable + * as a base-class, throws a copy of `__t`. + * Otherwise, throws an object of an implementation-defined type derived + * from both `_Tp` and `std::nested_exception`, containing a copy of `__t` + * and the result of `std::current_exception()`. + * + * In other words, throws the argument as a new exception that contains + * the currently active exception nested within it. This is intended for + * use in a catch handler to replace the caught exception with a different + * type, while still preserving the original exception. When the new + * exception is caught, the nested exception can be rethrown by using + * `std::rethrow_if_nested`. + * + * This can be used at API boundaries, for example to catch a library's + * internal exception type and rethrow it nested with a `std::runtime_error`, + * or vice versa. + * + * @since C++11 + */ template [[noreturn]] inline void @@ -119,25 +152,27 @@ namespace std _GLIBCXX_VISIBILITY(default) = __and_, is_move_constructible<_Up>>; static_assert(_CopyConstructible::value, "throw_with_nested argument must be CopyConstructible"); + +#if __cplusplus >= 201703L && __cpp_if_constexpr + if constexpr (is_class_v<_Up>) + if constexpr (!is_final_v<_Up>) + if constexpr (!is_base_of_v) + throw _Nested_exception<_Up>{std::forward<_Tp>(__t)}; + throw std::forward<_Tp>(__t); +#else using __nest = __and_, __bool_constant, __not_>>; std::__throw_with_nested_impl(std::forward<_Tp>(__t), __nest{}); +#endif } +#if __cplusplus < 201703L || ! defined __cpp_if_constexpr /// @cond undocumented - // Determine if dynamic_cast would be well-formed. - template - using __rethrow_if_nested_cond = typename enable_if< - __and_, - __or_<__not_>, - is_convertible<_Tp*, nested_exception*>>>::value - >::type; - // Attempt dynamic_cast to nested_exception and call rethrow_nested(). template - inline __rethrow_if_nested_cond<_Ex> - __rethrow_if_nested_impl(const _Ex* __ptr) + inline void + __rethrow_if_nested_impl(const _Ex* __ptr, true_type) { if (auto __ne_ptr = dynamic_cast(__ptr)) __ne_ptr->rethrow_nested(); @@ -145,16 +180,59 @@ namespace std _GLIBCXX_VISIBILITY(default) // Otherwise, no effects. inline void - __rethrow_if_nested_impl(const void*) + __rethrow_if_nested_impl(const void*, false_type) { } /// @endcond +#endif - /// If @p __ex is derived from nested_exception, @p __ex.rethrow_nested(). + /** Rethrow a nested exception + * + * If `__ex` contains a `std::nested_exception` object, call its + * `rethrow_nested()` member to rethrow the stored exception. + * + * After catching an exception thrown by a call to `std::throw_with_nested` + * this function can be used to rethrow the exception that was active when + * `std::throw_with_nested` was called. + * + * @since C++11 + */ + // _GLIBCXX_RESOLVE_LIB_DEFECTS + // 2484. rethrow_if_nested() is doubly unimplementable + // 2784. Resolution to LWG 2484 is missing "otherwise, no effects" and [...] template +# if ! __cpp_rtti + [[__gnu__::__always_inline__]] +#endif inline void rethrow_if_nested(const _Ex& __ex) - { std::__rethrow_if_nested_impl(std::__addressof(__ex)); } + { + const _Ex* __ptr = __builtin_addressof(__ex); +#if __cplusplus < 201703L || ! defined __cpp_if_constexpr +# if __cpp_rtti + using __cast = __and_, + __or_<__not_>, + is_convertible<_Ex*, nested_exception*>>>; +# else + using __cast = __and_, + is_base_of, + is_convertible<_Ex*, nested_exception*>>; +# endif + std::__rethrow_if_nested_impl(__ptr, __cast{}); +#else + if constexpr (!is_polymorphic_v<_Ex>) + return; + else if constexpr (is_base_of_v + && !is_convertible_v<_Ex*, nested_exception*>) + return; // nested_exception base class is inaccessible or ambiguous. +# if ! __cpp_rtti + else if constexpr (!is_base_of_v) + return; // Cannot do polymorphic casts without RTTI. +# endif + else if (auto __ne_ptr = dynamic_cast(__ptr)) + __ne_ptr->rethrow_nested(); +#endif + } /// @} group exceptions } // namespace std diff --git a/libstdc++-v3/testsuite/18_support/nested_exception/rethrow_if_nested-term.cc b/libstdc++-v3/testsuite/18_support/nested_exception/rethrow_if_nested-term.cc new file mode 100644 index 00000000000..5913392bd46 --- /dev/null +++ b/libstdc++-v3/testsuite/18_support/nested_exception/rethrow_if_nested-term.cc @@ -0,0 +1,33 @@ +// { dg-do run { target c++11 } } +// { dg-skip-if "" { *-*-* } { "-fno-exceptions" } } + +#include +#include + +[[noreturn]] void terminate_cleanly() noexcept { std::exit(0); } + +struct A { virtual ~A() = default; }; + +int main() +{ + try + { + // At this point std::current_exception() == nullptr so the + // std::nested_exception object is empty. + std::throw_with_nested(A{}); + } + catch (const A& a) + { + std::set_terminate(terminate_cleanly); + std::rethrow_if_nested(a); +#if __cpp_rtti + // No nested exception, so trying to rethrow it calls std::terminate() + // which calls std::exit(0). Shoud not reach this point. + std::abort(); +#else + // Without RTTI we can't dynamic_cast(&a) + // so std::rethrow_if_nested(a) just returns normally. + return 0; +#endif + } +}