/* * Copyright (c) Facebook, Inc. and its affiliates. * * 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( !kMicrosoftAbiVer || (kMicrosoftAbiVer >= 1900 && kMicrosoftAbiVer <= 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) noexcept { if (!kMicrosoftAbiVer) { 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[8 + 4 * sizeof(void*)]; 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) noexcept { 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>(); } 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_; } 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( ThrownTag, in_place_type_t<Ex>, As&&... as) : eptr_{std::make_exception_ptr(Ex(std::forward<As>(as)...)), reinterpret_cast<std::uintptr_t>(std::addressof(typeid(Ex))) + 1u}, vptr_(&ExceptionPtr::ops_) {} 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) noexcept : exception_wrapper{} { that.vptr_->copy_(&that, this); // Copy into *this, won't 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) noexcept { 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) noexcept : 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(std::decay_t<Ex>) || !"Dynamic and static exception types don't match. Exception would " "be sliced when storing in exception_wrapper."); return std::forward<Ex>(ex); } } // namespace exception_wrapper_detail 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 exception_wrapper::to_exception_ptr() noexcept { if (*this) { // Computing an exception_ptr is expensive so cache the result. return (*this = vptr_->get_exception_ptr_(this)).eptr_.ptr_; } return {}; } 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 Ex> [[noreturn]] inline void exception_wrapper::throw_with_nested(Ex&& ex) const { try { throw_exception(); } catch (...) { std::throw_with_nested(std::forward<Ex>(ex)); } } template <class CatchFn, bool IsConst> struct exception_wrapper::ExceptionTypeOf { using type = arg_type<std::decay_t<CatchFn>>; static_assert( std::is_reference<type>::value, "Always catch exceptions by reference."); static_assert( !IsConst || std::is_const<std::remove_reference_t<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<std::add_pointer_t<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 (nullptr != impl(continuation)) { this_.throw_exception(); } } 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