diff options
author | Jonathan Wakely <jwakely@redhat.com> | 2022-05-12 14:18:06 +0100 |
---|---|---|
committer | Jonathan Wakely <jwakely@redhat.com> | 2022-05-13 20:39:59 +0100 |
commit | c470f3ea86a28caacb9e9124307c4a03b2396288 (patch) | |
tree | 5fe55f44b217038f4ff32e08a2daf5387186ac31 | |
parent | e97e99296505e6015bc9e281364818bb89ca8a49 (diff) | |
download | gcc-c470f3ea86a28caacb9e9124307c4a03b2396288.zip gcc-c470f3ea86a28caacb9e9124307c4a03b2396288.tar.gz gcc-c470f3ea86a28caacb9e9124307c4a03b2396288.tar.bz2 |
libstdc++: Make std::rethrow_if_nested work without RTTI
This allows std::rethrow_if_nested to work with -fno-rtti by not
attempting the dynamic_cast if it requires RTTI, since that's ill-formed
with -fno-rtti. The cast will still work if a static upcast to
std::nested_exception is allowed.
Also use if-constexpr to avoid the compile-time overload resolution (and
SFINAE) and run-time dispatching for std::rethrow_if_nested and
std::throw_with_nested.
Also add better doxygen comments throughout the file.
libstdc++-v3/ChangeLog:
* libsupc++/nested_exception.h (throw_with_nested) [C++17]: Use
if-constexpr instead of tag dispatching.
(rethrow_if_nested) [C++17]: Likewise.
(rethrow_if_nested) [!__cpp_rtti]: Do not use dynamic_cast if it
would require RTTI.
* testsuite/18_support/nested_exception/rethrow_if_nested-term.cc:
New test.
-rw-r--r-- | libstdc++-v3/libsupc++/nested_exception.h | 116 | ||||
-rw-r--r-- | libstdc++-v3/testsuite/18_support/nested_exception/rethrow_if_nested-term.cc | 33 |
2 files changed, 130 insertions, 19 deletions
diff --git a/libstdc++-v3/libsupc++/nested_exception.h b/libstdc++-v3/libsupc++/nested_exception.h index 002a54e..dec3c0c 100644 --- a/libstdc++-v3/libsupc++/nested_exception.h +++ b/libstdc++-v3/libsupc++/nested_exception.h @@ -35,6 +35,7 @@ #else #include <bits/move.h> +#include <bits/exception_ptr.h> 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<typename _Tp> @@ -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<typename _Tp> [[noreturn]] inline void @@ -119,25 +152,27 @@ namespace std _GLIBCXX_VISIBILITY(default) = __and_<is_copy_constructible<_Up>, 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<nested_exception, _Up>) + throw _Nested_exception<_Up>{std::forward<_Tp>(__t)}; + throw std::forward<_Tp>(__t); +#else using __nest = __and_<is_class<_Up>, __bool_constant<!__is_final(_Up)>, __not_<is_base_of<nested_exception, _Up>>>; std::__throw_with_nested_impl(std::forward<_Tp>(__t), __nest{}); +#endif } +#if __cplusplus < 201703L || ! defined __cpp_if_constexpr /// @cond undocumented - // Determine if dynamic_cast<const nested_exception&> would be well-formed. - template<typename _Tp> - using __rethrow_if_nested_cond = typename enable_if< - __and_<is_polymorphic<_Tp>, - __or_<__not_<is_base_of<nested_exception, _Tp>>, - is_convertible<_Tp*, nested_exception*>>>::value - >::type; - // Attempt dynamic_cast to nested_exception and call rethrow_nested(). template<typename _Ex> - 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<const nested_exception*>(__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 - - /// If @p __ex is derived from nested_exception, @p __ex.rethrow_nested(). +#endif + + /** 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<typename _Ex> +# 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_<is_polymorphic<_Ex>, + __or_<__not_<is_base_of<nested_exception, _Ex>>, + is_convertible<_Ex*, nested_exception*>>>; +# else + using __cast = __and_<is_polymorphic<_Ex>, + is_base_of<nested_exception, _Ex>, + 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<nested_exception, _Ex> + && !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<nested_exception, _Ex>) + return; // Cannot do polymorphic casts without RTTI. +# endif + else if (auto __ne_ptr = dynamic_cast<const nested_exception*>(__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 0000000..5913392 --- /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 <exception> +#include <cstdlib> + +[[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<const std::nested_exception*>(&a) + // so std::rethrow_if_nested(a) just returns normally. + return 0; +#endif + } +} |