| /* |
| * Copyright 2017-present Facebook, Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| /* |
| * |
| * Author: Eric Niebler <eniebler@fb.com> |
| */ |
| |
| #include <folly/Portability.h> |
| |
| namespace folly { |
| |
| template <class Fn> |
| struct exception_wrapper::arg_type_ |
| : public arg_type_<decltype(&Fn::operator())> { |
| }; |
| template <class Ret, class Class, class Arg> |
| struct exception_wrapper::arg_type_<Ret (Class::*)(Arg)> { |
| using type = Arg; |
| }; |
| template <class Ret, class Class, class Arg> |
| struct exception_wrapper::arg_type_<Ret (Class::*)(Arg) const> { |
| using type = Arg; |
| }; |
| template <class Ret, class Arg> |
| struct exception_wrapper::arg_type_<Ret (Arg)> { |
| using type = Arg; |
| }; |
| template <class Ret, class Arg> |
| struct exception_wrapper::arg_type_<Ret (*)(Arg)> { |
| using type = Arg; |
| }; |
| template <class Ret, class Class> |
| struct exception_wrapper::arg_type_<Ret (Class::*)(...)> { |
| using type = AnyException; |
| }; |
| template <class Ret, class Class> |
| struct exception_wrapper::arg_type_<Ret (Class::*)(...) const> { |
| using type = AnyException; |
| }; |
| template <class Ret> |
| struct exception_wrapper::arg_type_<Ret (...)> { |
| using type = AnyException; |
| }; |
| template <class Ret> |
| struct exception_wrapper::arg_type_<Ret (*)(...)> { |
| using type = AnyException; |
| }; |
| |
| template <class Ret, class... Args> |
| inline Ret exception_wrapper::noop_(Args...) { |
| return Ret(); |
| } |
| |
| inline std::type_info const* exception_wrapper::uninit_type_( |
| exception_wrapper const*) { |
| return &typeid(void); |
| } |
| |
| template <class Ex, typename... As> |
| inline exception_wrapper::Buffer::Buffer(in_place_type_t<Ex>, As&&... as_) { |
| ::new (static_cast<void*>(&buff_)) Ex(std::forward<As>(as_)...); |
| } |
| |
| template <class Ex> |
| inline Ex& exception_wrapper::Buffer::as() noexcept { |
| return *static_cast<Ex*>(static_cast<void*>(&buff_)); |
| } |
| template <class Ex> |
| inline Ex const& exception_wrapper::Buffer::as() const noexcept { |
| return *static_cast<Ex const*>(static_cast<void const*>(&buff_)); |
| } |
| |
| inline std::exception const* exception_wrapper::as_exception_or_null_( |
| std::exception const& ex) { |
| return &ex; |
| } |
| inline std::exception const* exception_wrapper::as_exception_or_null_( |
| AnyException) { |
| return nullptr; |
| } |
| |
| static_assert( |
| !kIsWindows || sizeof(void*) == 8, |
| "exception_wrapper is untested on 32 bit Windows."); |
| static_assert( |
| !kIsWindows || (kMscVer >= 1900 && kMscVer <= 2000), |
| "exception_wrapper is untested and possibly broken on your version of " |
| "MSVC"); |
| |
| inline std::uintptr_t exception_wrapper::ExceptionPtr::as_int_( |
| std::exception_ptr const& ptr, |
| std::exception const& e) { |
| if (!kIsWindows) { |
| return reinterpret_cast<std::uintptr_t>(&e); |
| } else { |
| // On Windows, as of MSVC2017, all thrown exceptions are copied to the stack |
| // first. Thus, we cannot depend on exception references associated with an |
| // exception_ptr to be live for the duration of the exception_ptr. We need |
| // to directly access the heap allocated memory inside the exception_ptr. |
| // |
| // std::exception_ptr is an opaque reinterpret_cast of |
| // std::shared_ptr<__ExceptionPtr> |
| // __ExceptionPtr is a non-virtual class with two members, a union and a |
| // bool. The union contains the now-undocumented EHExceptionRecord, which |
| // contains a struct which contains a void* which points to the heap |
| // allocated exception. |
| // We derive the offset to pExceptionObject via manual means. |
| FOLLY_PACK_PUSH |
| struct Win32ExceptionPtr { |
| char offset[40]; |
| void* exceptionObject; |
| } FOLLY_PACK_ATTR; |
| FOLLY_PACK_POP |
| |
| auto* win32ExceptionPtr = |
| reinterpret_cast<std::shared_ptr<Win32ExceptionPtr> const*>( |
| &ptr)->get(); |
| return reinterpret_cast<std::uintptr_t>(win32ExceptionPtr->exceptionObject); |
| } |
| } |
| inline std::uintptr_t exception_wrapper::ExceptionPtr::as_int_( |
| std::exception_ptr const&, |
| AnyException e) { |
| return reinterpret_cast<std::uintptr_t>(e.typeinfo_) + 1; |
| } |
| inline bool exception_wrapper::ExceptionPtr::has_exception_() const { |
| return 0 == exception_or_type_ % 2; |
| } |
| inline std::exception const* exception_wrapper::ExceptionPtr::as_exception_() |
| const { |
| return reinterpret_cast<std::exception const*>(exception_or_type_); |
| } |
| inline std::type_info const* exception_wrapper::ExceptionPtr::as_type_() const { |
| return reinterpret_cast<std::type_info const*>(exception_or_type_ - 1); |
| } |
| |
| inline void exception_wrapper::ExceptionPtr::copy_( |
| exception_wrapper const* from, exception_wrapper* to) { |
| ::new (static_cast<void*>(&to->eptr_)) ExceptionPtr(from->eptr_); |
| } |
| inline void exception_wrapper::ExceptionPtr::move_( |
| exception_wrapper* from, exception_wrapper* to) { |
| ::new (static_cast<void*>(&to->eptr_)) |
| ExceptionPtr(std::move(from->eptr_)); |
| delete_(from); |
| } |
| inline void exception_wrapper::ExceptionPtr::delete_( |
| exception_wrapper* that) { |
| that->eptr_.~ExceptionPtr(); |
| that->vptr_ = &uninit_; |
| } |
| [[noreturn]] inline void exception_wrapper::ExceptionPtr::throw_( |
| exception_wrapper const* that) { |
| std::rethrow_exception(that->eptr_.ptr_); |
| } |
| inline std::type_info const* exception_wrapper::ExceptionPtr::type_( |
| exception_wrapper const* that) { |
| if (auto e = get_exception_(that)) { |
| return &typeid(*e); |
| } |
| return that->eptr_.as_type_(); |
| } |
| inline std::exception const* exception_wrapper::ExceptionPtr::get_exception_( |
| exception_wrapper const* that) { |
| return that->eptr_.has_exception_() ? that->eptr_.as_exception_() |
| : nullptr; |
| } |
| inline exception_wrapper exception_wrapper::ExceptionPtr::get_exception_ptr_( |
| exception_wrapper const* that) { |
| return *that; |
| } |
| |
| template <class Ex> |
| inline void exception_wrapper::InPlace<Ex>::copy_( |
| exception_wrapper const* from, exception_wrapper* to) { |
| ::new (static_cast<void*>(std::addressof(to->buff_.as<Ex>()))) |
| Ex(from->buff_.as<Ex>()); |
| } |
| template <class Ex> |
| inline void exception_wrapper::InPlace<Ex>::move_( |
| exception_wrapper* from, exception_wrapper* to) { |
| ::new (static_cast<void*>(std::addressof(to->buff_.as<Ex>()))) |
| Ex(std::move(from->buff_.as<Ex>())); |
| delete_(from); |
| } |
| template <class Ex> |
| inline void exception_wrapper::InPlace<Ex>::delete_( |
| exception_wrapper* that) { |
| that->buff_.as<Ex>().~Ex(); |
| that->vptr_ = &uninit_; |
| } |
| template <class Ex> |
| [[noreturn]] inline void exception_wrapper::InPlace<Ex>::throw_( |
| exception_wrapper const* that) { |
| throw that->buff_.as<Ex>(); // @nolint |
| } |
| template <class Ex> |
| inline std::type_info const* exception_wrapper::InPlace<Ex>::type_( |
| exception_wrapper const*) { |
| return &typeid(Ex); |
| } |
| template <class Ex> |
| inline std::exception const* exception_wrapper::InPlace<Ex>::get_exception_( |
| exception_wrapper const* that) { |
| return as_exception_or_null_(that->buff_.as<Ex>()); |
| } |
| template <class Ex> |
| inline exception_wrapper exception_wrapper::InPlace<Ex>::get_exception_ptr_( |
| exception_wrapper const* that) { |
| try { |
| throw_(that); |
| } catch (Ex const& ex) { |
| return exception_wrapper{std::current_exception(), ex}; |
| } |
| } |
| |
| template <class Ex> |
| [[noreturn]] inline void |
| exception_wrapper::SharedPtr::Impl<Ex>::throw_() const { |
| throw ex_; // @nolint |
| } |
| template <class Ex> |
| inline std::exception const* |
| exception_wrapper::SharedPtr::Impl<Ex>::get_exception_() const noexcept { |
| return as_exception_or_null_(ex_); |
| } |
| template <class Ex> |
| inline exception_wrapper |
| exception_wrapper::SharedPtr::Impl<Ex>::get_exception_ptr_() const noexcept { |
| try { |
| throw_(); |
| } catch (Ex& ex) { |
| return exception_wrapper{std::current_exception(), ex}; |
| } |
| } |
| inline void exception_wrapper::SharedPtr::copy_( |
| exception_wrapper const* from, exception_wrapper* to) { |
| ::new (static_cast<void*>(std::addressof(to->sptr_))) |
| SharedPtr(from->sptr_); |
| } |
| inline void exception_wrapper::SharedPtr::move_( |
| exception_wrapper* from, exception_wrapper* to) { |
| ::new (static_cast<void*>(std::addressof(to->sptr_))) |
| SharedPtr(std::move(from->sptr_)); |
| delete_(from); |
| } |
| inline void exception_wrapper::SharedPtr::delete_( |
| exception_wrapper* that) { |
| that->sptr_.~SharedPtr(); |
| that->vptr_ = &uninit_; |
| } |
| [[noreturn]] inline void exception_wrapper::SharedPtr::throw_( |
| exception_wrapper const* that) { |
| that->sptr_.ptr_->throw_(); |
| folly::assume_unreachable(); |
| } |
| inline std::type_info const* exception_wrapper::SharedPtr::type_( |
| exception_wrapper const* that) { |
| return that->sptr_.ptr_->info_; |
| } |
| inline std::exception const* exception_wrapper::SharedPtr::get_exception_( |
| exception_wrapper const* that) { |
| return that->sptr_.ptr_->get_exception_(); |
| } |
| inline exception_wrapper exception_wrapper::SharedPtr::get_exception_ptr_( |
| exception_wrapper const* that) { |
| return that->sptr_.ptr_->get_exception_ptr_(); |
| } |
| |
| template <class Ex, typename... As> |
| inline exception_wrapper::exception_wrapper(OnHeapTag, in_place_type_t<Ex>, As&&... as) |
| : sptr_{std::make_shared<SharedPtr::Impl<Ex>>(std::forward<As>(as)...)}, |
| vptr_(&SharedPtr::ops_) {} |
| |
| template <class Ex, typename... As> |
| inline exception_wrapper::exception_wrapper(InSituTag, in_place_type_t<Ex>, As&&... as) |
| : buff_{in_place_type<Ex>, std::forward<As>(as)...}, |
| vptr_(&InPlace<Ex>::ops_) {} |
| |
| inline exception_wrapper::exception_wrapper(exception_wrapper&& that) noexcept |
| : exception_wrapper{} { |
| (vptr_ = that.vptr_)->move_(&that, this); // Move into *this, won't throw |
| } |
| |
| inline exception_wrapper::exception_wrapper( |
| exception_wrapper const& that) : exception_wrapper{} { |
| that.vptr_->copy_(&that, this); // could throw |
| vptr_ = that.vptr_; |
| } |
| |
| // If `this == &that`, this move assignment operator leaves the object in a |
| // valid but unspecified state. |
| inline exception_wrapper& exception_wrapper::operator=( |
| exception_wrapper&& that) noexcept { |
| vptr_->delete_(this); // Free the current exception |
| (vptr_ = that.vptr_)->move_(&that, this); // Move into *this, won't throw |
| return *this; |
| } |
| |
| inline exception_wrapper& exception_wrapper::operator=( |
| exception_wrapper const& that) { |
| exception_wrapper(that).swap(*this); |
| return *this; |
| } |
| |
| inline exception_wrapper::~exception_wrapper() { |
| reset(); |
| } |
| |
| template <class Ex> |
| inline exception_wrapper::exception_wrapper(std::exception_ptr ptr, Ex& ex) |
| : eptr_{ptr, ExceptionPtr::as_int_(ptr, ex)}, |
| vptr_(&ExceptionPtr::ops_) { |
| assert(eptr_.ptr_); |
| } |
| |
| namespace exception_wrapper_detail { |
| template <class Ex> |
| Ex&& dont_slice(Ex&& ex) { |
| assert(typeid(ex) == typeid(_t<std::decay<Ex>>) || |
| !"Dynamic and static exception types don't match. Exception would " |
| "be sliced when storing in exception_wrapper."); |
| return std::forward<Ex>(ex); |
| } |
| } |
| |
| template < |
| class Ex, |
| class Ex_, |
| FOLLY_REQUIRES_DEF( |
| Conjunction< |
| exception_wrapper::IsStdException<Ex_>, |
| exception_wrapper::IsRegularExceptionType<Ex_>>::value)> |
| inline exception_wrapper::exception_wrapper(Ex&& ex) |
| : exception_wrapper{ |
| PlacementOf<Ex_>{}, |
| in_place_type<Ex_>, |
| exception_wrapper_detail::dont_slice(std::forward<Ex>(ex))} { |
| } |
| |
| template < |
| class Ex, |
| class Ex_, |
| FOLLY_REQUIRES_DEF( |
| exception_wrapper::IsRegularExceptionType<Ex_>::value)> |
| inline exception_wrapper::exception_wrapper(in_place_t, Ex&& ex) |
| : exception_wrapper{ |
| PlacementOf<Ex_>{}, |
| in_place_type<Ex_>, |
| exception_wrapper_detail::dont_slice(std::forward<Ex>(ex))} { |
| } |
| |
| template < |
| class Ex, |
| typename... As, |
| FOLLY_REQUIRES_DEF( |
| exception_wrapper::IsRegularExceptionType<Ex>::value)> |
| inline exception_wrapper::exception_wrapper(in_place_type_t<Ex>, As&&... as) |
| : exception_wrapper{ |
| PlacementOf<Ex>{}, |
| in_place_type<Ex>, |
| std::forward<As>(as)...} { |
| } |
| |
| inline void exception_wrapper::swap(exception_wrapper& that) noexcept { |
| exception_wrapper tmp(std::move(that)); |
| that = std::move(*this); |
| *this = std::move(tmp); |
| } |
| |
| inline exception_wrapper::operator bool() const noexcept { |
| return vptr_ != &uninit_; |
| } |
| |
| inline bool exception_wrapper::operator!() const noexcept { |
| return !static_cast<bool>(*this); |
| } |
| |
| inline void exception_wrapper::reset() { |
| vptr_->delete_(this); |
| } |
| |
| inline bool exception_wrapper::has_exception_ptr() const noexcept { |
| return vptr_ == &ExceptionPtr::ops_; |
| } |
| |
| inline std::exception* exception_wrapper::get_exception() noexcept { |
| return const_cast<std::exception*>(vptr_->get_exception_(this)); |
| } |
| inline std::exception const* exception_wrapper::get_exception() const noexcept { |
| return vptr_->get_exception_(this); |
| } |
| |
| template <typename Ex> |
| inline Ex* exception_wrapper::get_exception() noexcept { |
| Ex* object{nullptr}; |
| with_exception([&](Ex& ex) { object = &ex; }); |
| return object; |
| } |
| |
| template <typename Ex> |
| inline Ex const* exception_wrapper::get_exception() const noexcept { |
| Ex const* object{nullptr}; |
| with_exception([&](Ex const& ex) { object = &ex; }); |
| return object; |
| } |
| |
| inline std::exception_ptr const& exception_wrapper::to_exception_ptr() |
| noexcept { |
| // Computing an exception_ptr is expensive so cache the result. |
| return (*this = vptr_->get_exception_ptr_(this)).eptr_.ptr_; |
| } |
| inline std::exception_ptr exception_wrapper::to_exception_ptr() const noexcept { |
| return vptr_->get_exception_ptr_(this).eptr_.ptr_; |
| } |
| |
| inline std::type_info const& exception_wrapper::none() noexcept { |
| return typeid(void); |
| } |
| inline std::type_info const& exception_wrapper::unknown() noexcept { |
| return typeid(Unknown); |
| } |
| |
| inline std::type_info const& exception_wrapper::type() const noexcept { |
| return *vptr_->type_(this); |
| } |
| |
| inline folly::fbstring exception_wrapper::what() const { |
| if (auto e = get_exception()) { |
| return class_name() + ": " + e->what(); |
| } |
| return class_name(); |
| } |
| |
| inline folly::fbstring exception_wrapper::class_name() const { |
| auto& ti = type(); |
| return ti == none() |
| ? "" |
| : ti == unknown() ? "<unknown exception>" : folly::demangle(ti); |
| } |
| |
| template <class Ex> |
| inline bool exception_wrapper::is_compatible_with() const noexcept { |
| return with_exception([](Ex const&) {}); |
| } |
| |
| [[noreturn]] inline void exception_wrapper::throw_exception() const { |
| vptr_->throw_(this); |
| onNoExceptionError(__func__); |
| } |
| |
| template <class CatchFn, bool IsConst> |
| struct exception_wrapper::ExceptionTypeOf { |
| using type = arg_type<_t<std::decay<CatchFn>>>; |
| static_assert( |
| std::is_reference<type>::value, |
| "Always catch exceptions by reference."); |
| static_assert( |
| !IsConst || std::is_const<_t<std::remove_reference<type>>>::value, |
| "handle() or with_exception() called on a const exception_wrapper " |
| "and asked to catch a non-const exception. Handler will never fire. " |
| "Catch exception by const reference to fix this."); |
| }; |
| |
| // Nests a throw in the proper try/catch blocks |
| template <bool IsConst> |
| struct exception_wrapper::HandleReduce { |
| bool* handled_; |
| |
| template < |
| class ThrowFn, |
| class CatchFn, |
| FOLLY_REQUIRES(!IsCatchAll<CatchFn>::value)> |
| auto operator()(ThrowFn&& th, CatchFn& ca) const { |
| using Ex = _t<ExceptionTypeOf<CatchFn, IsConst>>; |
| return [ th = std::forward<ThrowFn>(th), &ca, handled_ = handled_ ] { |
| try { |
| th(); |
| } catch (Ex& e) { |
| // If we got here because a catch function threw, rethrow. |
| if (*handled_) { |
| throw; |
| } |
| *handled_ = true; |
| ca(e); |
| } |
| }; |
| } |
| |
| template < |
| class ThrowFn, |
| class CatchFn, |
| FOLLY_REQUIRES(IsCatchAll<CatchFn>::value)> |
| auto operator()(ThrowFn&& th, CatchFn& ca) const { |
| return [ th = std::forward<ThrowFn>(th), &ca, handled_ = handled_ ] { |
| try { |
| th(); |
| } catch (...) { |
| // If we got here because a catch function threw, rethrow. |
| if (*handled_) { |
| throw; |
| } |
| *handled_ = true; |
| ca(); |
| } |
| }; |
| } |
| }; |
| |
| // When all the handlers expect types derived from std::exception, we can |
| // sometimes invoke the handlers without throwing any exceptions. |
| template <bool IsConst> |
| struct exception_wrapper::HandleStdExceptReduce { |
| using StdEx = AddConstIf<IsConst, std::exception>; |
| |
| template < |
| class ThrowFn, |
| class CatchFn, |
| FOLLY_REQUIRES(!IsCatchAll<CatchFn>::value)> |
| auto operator()(ThrowFn&& th, CatchFn& ca) const { |
| using Ex = _t<ExceptionTypeOf<CatchFn, IsConst>>; |
| return [ th = std::forward<ThrowFn>(th), &ca ](auto&& continuation) |
| -> StdEx* { |
| if (auto e = const_cast<StdEx*>(th(continuation))) { |
| if (auto e2 = dynamic_cast<_t<std::add_pointer<Ex>>>(e)) { |
| ca(*e2); |
| } else { |
| return e; |
| } |
| } |
| return nullptr; |
| }; |
| } |
| |
| template < |
| class ThrowFn, |
| class CatchFn, |
| FOLLY_REQUIRES(IsCatchAll<CatchFn>::value)> |
| auto operator()(ThrowFn&& th, CatchFn& ca) const { |
| return [ th = std::forward<ThrowFn>(th), &ca ](auto&&) -> StdEx* { |
| // The following continuation causes ca() to execute if *this contains |
| // an exception /not/ derived from std::exception. |
| auto continuation = [&ca](StdEx* e) { |
| return e != nullptr ? e : ((void)ca(), nullptr); |
| }; |
| if (th(continuation) != nullptr) { |
| ca(); |
| } |
| return nullptr; |
| }; |
| } |
| }; |
| |
| // Called when some types in the catch clauses are not derived from |
| // std::exception. |
| template <class This, class... CatchFns> |
| inline void exception_wrapper::handle_( |
| std::false_type, This& this_, CatchFns&... fns) { |
| bool handled = false; |
| auto impl = exception_wrapper_detail::fold( |
| HandleReduce<std::is_const<This>::value>{&handled}, |
| [&] { this_.throw_exception(); }, |
| fns...); |
| impl(); |
| } |
| |
| // Called when all types in the catch clauses are either derived from |
| // std::exception or a catch-all clause. |
| template <class This, class... CatchFns> |
| inline void exception_wrapper::handle_( |
| std::true_type, This& this_, CatchFns&... fns) { |
| using StdEx = exception_wrapper_detail:: |
| AddConstIf<std::is_const<This>::value, std::exception>; |
| auto impl = exception_wrapper_detail::fold( |
| HandleStdExceptReduce<std::is_const<This>::value>{}, |
| [&](auto&& continuation) { |
| return continuation( |
| const_cast<StdEx*>(this_.vptr_->get_exception_(&this_))); |
| }, |
| fns...); |
| // This continuation gets evaluated if CatchFns... does not include a |
| // catch-all handler. It is a no-op. |
| auto continuation = [](StdEx* ex) { return ex; }; |
| if (StdEx* e = impl(continuation)) { |
| throw *e; // Not handled. Throw. |
| } |
| } |
| |
| namespace exception_wrapper_detail { |
| template <class Ex, class Fn> |
| struct catch_fn { |
| Fn fn_; |
| auto operator()(Ex& ex) { |
| return fn_(ex); |
| } |
| }; |
| |
| template <class Ex, class Fn> |
| inline catch_fn<Ex, Fn> catch_(Ex*, Fn fn) { |
| return {std::move(fn)}; |
| } |
| template <class Fn> |
| inline Fn catch_(void const*, Fn fn) { |
| return fn; |
| } |
| } // namespace exception_wrapper_detail |
| |
| template <class Ex, class This, class Fn> |
| inline bool exception_wrapper::with_exception_(This& this_, Fn fn_) { |
| if (!this_) { |
| return false; |
| } |
| bool handled = true; |
| auto fn = exception_wrapper_detail::catch_( |
| static_cast<Ex*>(nullptr), std::move(fn_)); |
| auto&& all = [&](...) { handled = false; }; |
| handle_(IsStdException<arg_type<decltype(fn)>>{}, this_, fn, all); |
| return handled; |
| } |
| |
| template <class Ex, class Fn> |
| inline bool exception_wrapper::with_exception(Fn fn) { |
| return with_exception_<Ex>(*this, std::move(fn)); |
| } |
| template <class Ex, class Fn> |
| inline bool exception_wrapper::with_exception(Fn fn) const { |
| return with_exception_<Ex const>(*this, std::move(fn)); |
| } |
| |
| template <class... CatchFns> |
| inline void exception_wrapper::handle(CatchFns... fns) { |
| using AllStdEx = |
| exception_wrapper_detail::AllOf<IsStdException, arg_type<CatchFns>...>; |
| if (!*this) { |
| onNoExceptionError(__func__); |
| } |
| this->handle_(AllStdEx{}, *this, fns...); |
| } |
| template <class... CatchFns> |
| inline void exception_wrapper::handle(CatchFns... fns) const { |
| using AllStdEx = |
| exception_wrapper_detail::AllOf<IsStdException, arg_type<CatchFns>...>; |
| if (!*this) { |
| onNoExceptionError(__func__); |
| } |
| this->handle_(AllStdEx{}, *this, fns...); |
| } |
| |
| } // namespace folly |