/* * 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 */ #include namespace folly { template struct exception_wrapper::arg_type_ : public arg_type_ {}; template struct exception_wrapper::arg_type_ { using type = Arg; }; template struct exception_wrapper::arg_type_ { using type = Arg; }; template struct exception_wrapper::arg_type_ { using type = Arg; }; template struct exception_wrapper::arg_type_ { using type = Arg; }; template struct exception_wrapper::arg_type_ { using type = AnyException; }; template struct exception_wrapper::arg_type_ { using type = AnyException; }; template struct exception_wrapper::arg_type_ { using type = AnyException; }; template struct exception_wrapper::arg_type_ { using type = AnyException; }; template inline Ret exception_wrapper::noop_(Args...) { return Ret(); } inline std::type_info const* exception_wrapper::uninit_type_( exception_wrapper const*) { return &typeid(void); } template inline exception_wrapper::Buffer::Buffer(in_place_type_t, As&&... as_) { ::new (static_cast(&buff_)) Ex(std::forward(as_)...); } template inline Ex& exception_wrapper::Buffer::as() noexcept { return *static_cast(static_cast(&buff_)); } template inline Ex const& exception_wrapper::Buffer::as() const noexcept { return *static_cast(static_cast(&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(&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 const*>(&ptr) ->get(); return reinterpret_cast(win32ExceptionPtr->exceptionObject); } } inline std::uintptr_t exception_wrapper::ExceptionPtr::as_int_( std::exception_ptr const&, AnyException e) noexcept { return reinterpret_cast(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(exception_or_type_); } inline std::type_info const* exception_wrapper::ExceptionPtr::as_type_() const { return reinterpret_cast(exception_or_type_ - 1); } inline void exception_wrapper::ExceptionPtr::copy_( exception_wrapper const* from, exception_wrapper* to) { ::new (static_cast(&to->eptr_)) ExceptionPtr(from->eptr_); } inline void exception_wrapper::ExceptionPtr::move_( exception_wrapper* from, exception_wrapper* to) { ::new (static_cast(&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 inline void exception_wrapper::InPlace::copy_( exception_wrapper const* from, exception_wrapper* to) { ::new (static_cast(std::addressof(to->buff_.as()))) Ex(from->buff_.as()); } template inline void exception_wrapper::InPlace::move_( exception_wrapper* from, exception_wrapper* to) { ::new (static_cast(std::addressof(to->buff_.as()))) Ex(std::move(from->buff_.as())); delete_(from); } template inline void exception_wrapper::InPlace::delete_(exception_wrapper* that) { that->buff_.as().~Ex(); that->vptr_ = &uninit_; } template [[noreturn]] inline void exception_wrapper::InPlace::throw_( exception_wrapper const* that) { throw that->buff_.as(); } template inline std::type_info const* exception_wrapper::InPlace::type_( exception_wrapper const*) { return &typeid(Ex); } template inline std::exception const* exception_wrapper::InPlace::get_exception_( exception_wrapper const* that) { return as_exception_or_null_(that->buff_.as()); } template inline exception_wrapper exception_wrapper::InPlace::get_exception_ptr_( exception_wrapper const* that) { try { throw_(that); } catch (Ex const& ex) { return exception_wrapper{std::current_exception(), ex}; } } template [[noreturn]] inline void exception_wrapper::SharedPtr::Impl::throw_() const { throw ex_; } template inline std::exception const* exception_wrapper::SharedPtr::Impl::get_exception_() const noexcept { return as_exception_or_null_(ex_); } template inline exception_wrapper exception_wrapper::SharedPtr::Impl::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(std::addressof(to->sptr_))) SharedPtr(from->sptr_); } inline void exception_wrapper::SharedPtr::move_( exception_wrapper* from, exception_wrapper* to) { ::new (static_cast(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 inline exception_wrapper::exception_wrapper( ThrownTag, in_place_type_t, As&&... as) : eptr_{std::make_exception_ptr(Ex(std::forward(as)...)), reinterpret_cast(std::addressof(typeid(Ex))) + 1u}, vptr_(&ExceptionPtr::ops_) {} template inline exception_wrapper::exception_wrapper( OnHeapTag, in_place_type_t, As&&... as) : sptr_{std::make_shared>(std::forward(as)...)}, vptr_(&SharedPtr::ops_) {} template inline exception_wrapper::exception_wrapper( InSituTag, in_place_type_t, As&&... as) : buff_{in_place_type, std::forward(as)...}, vptr_(&InPlace::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 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 Ex&& dont_slice(Ex&& ex) { assert(typeid(ex) == typeid(std::decay_t) || !"Dynamic and static exception types don't match. Exception would " "be sliced when storing in exception_wrapper."); return std::forward(ex); } } // namespace exception_wrapper_detail template < class Ex, class Ex_, FOLLY_REQUIRES_DEF(Conjunction< exception_wrapper::IsStdException, exception_wrapper::IsRegularExceptionType>::value)> inline exception_wrapper::exception_wrapper(Ex&& ex) : exception_wrapper{ PlacementOf{}, in_place_type, exception_wrapper_detail::dont_slice(std::forward(ex))} {} template < class Ex, class Ex_, FOLLY_REQUIRES_DEF(exception_wrapper::IsRegularExceptionType::value)> inline exception_wrapper::exception_wrapper(in_place_t, Ex&& ex) : exception_wrapper{ PlacementOf{}, in_place_type, exception_wrapper_detail::dont_slice(std::forward(ex))} {} template < class Ex, typename... As, FOLLY_REQUIRES_DEF(exception_wrapper::IsRegularExceptionType::value)> inline exception_wrapper::exception_wrapper(in_place_type_t, As&&... as) : exception_wrapper{PlacementOf{}, in_place_type, std::forward(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(*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(vptr_->get_exception_(this)); } inline std::exception const* exception_wrapper::get_exception() const noexcept { return vptr_->get_exception_(this); } template inline Ex* exception_wrapper::get_exception() noexcept { Ex* object{nullptr}; with_exception([&](Ex& ex) { object = &ex; }); return object; } template 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() ? "" : folly::demangle(ti); } template 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 [[noreturn]] inline void exception_wrapper::throw_with_nested(Ex&& ex) const { try { throw_exception(); } catch (...) { std::throw_with_nested(std::forward(ex)); } } template struct exception_wrapper::ExceptionTypeOf { using type = arg_type>; static_assert( std::is_reference::value, "Always catch exceptions by reference."); static_assert( !IsConst || std::is_const>::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 struct exception_wrapper::HandleReduce { bool* handled_; template < class ThrowFn, class CatchFn, FOLLY_REQUIRES(!IsCatchAll::value)> auto operator()(ThrowFn&& th, CatchFn& ca) const { using Ex = _t>; return [th = std::forward(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::value)> auto operator()(ThrowFn&& th, CatchFn& ca) const { return [th = std::forward(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 struct exception_wrapper::HandleStdExceptReduce { using StdEx = AddConstIf; template < class ThrowFn, class CatchFn, FOLLY_REQUIRES(!IsCatchAll::value)> auto operator()(ThrowFn&& th, CatchFn& ca) const { using Ex = _t>; return [th = std::forward(th), &ca](auto&& continuation) -> StdEx* { if (auto e = const_cast(th(continuation))) { if (auto e2 = dynamic_cast>(e)) { ca(*e2); } else { return e; } } return nullptr; }; } template < class ThrowFn, class CatchFn, FOLLY_REQUIRES(IsCatchAll::value)> auto operator()(ThrowFn&& th, CatchFn& ca) const { return [th = std::forward(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 inline void exception_wrapper::handle_(std::false_type, This& this_, CatchFns&... fns) { bool handled = false; auto impl = exception_wrapper_detail::fold( HandleReduce::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 inline void exception_wrapper::handle_(std::true_type, This& this_, CatchFns&... fns) { using StdEx = exception_wrapper_detail:: AddConstIf::value, std::exception>; auto impl = exception_wrapper_detail::fold( HandleStdExceptReduce::value>{}, [&](auto&& continuation) { return continuation( const_cast(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 struct catch_fn { Fn fn_; auto operator()(Ex& ex) { return fn_(ex); } }; template inline catch_fn catch_(Ex*, Fn fn) { return {std::move(fn)}; } template inline Fn catch_(void const*, Fn fn) { return fn; } } // namespace exception_wrapper_detail template 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(nullptr), std::move(fn_)); auto&& all = [&](...) { handled = false; }; handle_(IsStdException>{}, this_, fn, all); return handled; } template inline bool exception_wrapper::with_exception(Fn fn) { return with_exception_(*this, std::move(fn)); } template inline bool exception_wrapper::with_exception(Fn fn) const { return with_exception_(*this, std::move(fn)); } template inline void exception_wrapper::handle(CatchFns... fns) { using AllStdEx = exception_wrapper_detail::AllOf...>; if (!*this) { onNoExceptionError(__func__); } this->handle_(AllStdEx{}, *this, fns...); } template inline void exception_wrapper::handle(CatchFns... fns) const { using AllStdEx = exception_wrapper_detail::AllOf...>; if (!*this) { onNoExceptionError(__func__); } this->handle_(AllStdEx{}, *this, fns...); } } // namespace folly