From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id D14F53985466 for ; Fri, 13 May 2022 20:01:06 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org D14F53985466 Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-248-LMP3Rf_GOJy-piwqHOHHug-1; Fri, 13 May 2022 16:01:03 -0400 X-MC-Unique: LMP3Rf_GOJy-piwqHOHHug-1 Received: from smtp.corp.redhat.com (int-mx08.intmail.prod.int.rdu2.redhat.com [10.11.54.8]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id ED538185A79C; Fri, 13 May 2022 20:01:02 +0000 (UTC) Received: from localhost (unknown [10.33.36.192]) by smtp.corp.redhat.com (Postfix) with ESMTP id B3EA3C44AE3; Fri, 13 May 2022 20:01:02 +0000 (UTC) From: Jonathan Wakely To: libstdc++@gcc.gnu.org, gcc-patches@gcc.gnu.org Subject: [committed] libstdc++: Make std::rethrow_if_nested work without RTTI Date: Fri, 13 May 2022 21:01:02 +0100 Message-Id: <20220513200102.4138631-1-jwakely@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.85 on 10.11.54.8 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-12.4 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_LOW, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libstdc++@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Libstdc++ mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 13 May 2022 20:01:08 -0000 Tested powerpc64l4-linux, pushed to trunk. -- >8 -- 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. --- libstdc++-v3/libsupc++/nested_exception.h | 114 +++++++++++++++--- .../rethrow_if_nested-term.cc | 33 +++++ 2 files changed, 129 insertions(+), 18 deletions(-) create mode 100644 libstdc++-v3/testsuite/18_support/nested_exception/rethrow_if_nested-term.cc 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 + } +} -- 2.34.3