/* * Copyright (c) Meta Platforms, Inc. and 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. */ #pragma once #include #include #include #include #include #include #if __has_include() #include #endif #include #include #include #include #include #include #include #include #include #include namespace folly { /// throw_exception /// /// Throw an exception if exceptions are enabled, or terminate if compiled with /// -fno-exceptions. template [[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void throw_exception(Ex&& ex) { #if FOLLY_HAS_EXCEPTIONS throw static_cast(ex); #else (void)ex; std::terminate(); #endif } /// terminate_with /// /// Terminates as if by forwarding to throw_exception but in a noexcept context. template [[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void terminate_with( Ex&& ex) noexcept { throw_exception(static_cast(ex)); } namespace detail { struct throw_exception_arg_array_ { template using v = std::remove_extent_t>; template using apply = std::enable_if_t>::value, v*>; }; struct throw_exception_arg_trivial_ { template using apply = remove_cvref_t; }; struct throw_exception_arg_base_ { template using apply = R; }; template using throw_exception_arg_ = // conditional_t< std::is_array>::value, throw_exception_arg_array_, conditional_t< std::is_trivially_copyable_v>, throw_exception_arg_trivial_, throw_exception_arg_base_>>; template using throw_exception_arg_t = typename throw_exception_arg_::template apply; template using throw_exception_arg_fmt_t = remove_cvref_t::template apply>; template [[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void throw_exception_( Args... args) { throw_exception(Ex(static_cast(args)...)); } template [[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void terminate_with_( Args... args) noexcept { throw_exception(Ex(static_cast(args)...)); } } // namespace detail /// throw_exception /// /// Construct and throw an exception if exceptions are enabled, or terminate if /// compiled with -fno-exceptions. /// /// Does not perfectly forward all its arguments. Instead, in the interest of /// minimizing common-case inline code size, decays its arguments as follows: /// * refs to arrays of char const are decayed to char const* /// * refs to arrays are otherwise invalid /// * refs to trivial types are decayed to values /// /// The reason for treating refs to arrays as invalid is to avoid having two /// behaviors for refs to arrays, one for the general case and one for where the /// inner type is char const. Having two behaviors can be surprising, so avoid. template [[noreturn]] FOLLY_ERASE void throw_exception(Args&&... args) { detail::throw_exception_...>( static_cast(args)...); } /// terminate_with /// /// Terminates as if by forwarding to throw_exception within a noexcept context. template [[noreturn]] FOLLY_ERASE void terminate_with(Args&&... args) { detail::terminate_with_...>( static_cast(args)...); } #if __has_include() namespace detail { template [[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void throw_exception_fmt_format_(Str str, Args&&... args) { auto what = [&] { return fmt::format(str, static_cast(args)...); }; if constexpr (std::is_constructible_v) { throw_exception(what()); } else { throw_exception(what().c_str()); } } template [[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void terminate_with_fmt_format_(Str str, Args&&... args) noexcept { auto what = [&] { return fmt::format(str, static_cast(args)...); }; if constexpr (std::is_constructible_v) { throw_exception(what()); } else { throw_exception(what().c_str()); } } #if FMT_VERSION >= 80000 template using fmt_format_string = fmt::format_string...>; #else template using fmt_format_string = fmt::string_view; #endif } // namespace detail template [[noreturn]] FOLLY_ERASE void throw_exception_fmt_format( detail::fmt_format_string str, Args&&... args) { detail::throw_exception_fmt_format_< // Ex, detail::throw_exception_arg_t...>( str, static_cast(args)...); } template [[noreturn]] FOLLY_ERASE void terminate_with_fmt_format( detail::fmt_format_string str, Args&&... args) { detail::terminate_with_fmt_format_< // Ex, detail::throw_exception_arg_t...>( str, static_cast(args)...); } #endif /// invoke_cold /// /// Invoke the provided function with the provided arguments. /// /// Usage note: /// Passing extra values as arguments rather than capturing them allows smaller /// inlined native code at the call-site. Passing function-pointers or function- /// references rather than general callables with captures allows allows smaller /// inlined native code at the call-site as well. /// /// Example: /// /// if (i < 0) { /// invoke_cold( /// [](int j) { /// std::string ret = doStepA(); /// doStepB(ret); /// doStepC(ret); /// }, /// i); /// } template < typename F, typename... A, typename FD = std::remove_pointer_t>, std::enable_if_t::value, int> = 0, typename R = decltype(FOLLY_DECLVAL(F&&)(FOLLY_DECLVAL(A&&)...))> [[FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE R invoke_cold(F&& f, A&&... a) // noexcept(noexcept(static_cast(f)(static_cast(a)...))) { return static_cast(f)(static_cast(a)...); } template < typename F, typename... A, typename FD = std::remove_pointer_t>, std::enable_if_t::value, int> = 0, typename R = decltype(FOLLY_DECLVAL(F&&)(FOLLY_DECLVAL(A&&)...))> FOLLY_ERASE R invoke_cold(F&& f, A&&... a) // noexcept(noexcept(f(static_cast(a)...))) { return f(static_cast(a)...); } /// invoke_noreturn_cold /// /// Invoke the provided function with the provided arguments. If the invocation /// returns, terminate. /// /// May be used with throw_exception in cases where construction of the object /// to be thrown requires more than just invoking its constructor with a given /// sequence of arguments passed by reference - for example, if a string message /// must be computed before being passed to the constructor of the object to be /// thrown. /// /// Usage note: /// Passing extra values as arguments rather than capturing them allows smaller /// inlined native code at the call-site. /// /// Example: /// /// if (i < 0) { /// invoke_noreturn_cold( /// [](int j) { /// throw_exceptions(runtime_error(to("invalid: ", j))); /// }, /// i); /// } template [[noreturn, FOLLY_ATTR_GNU_COLD]] FOLLY_NOINLINE void invoke_noreturn_cold(F&& f, A&&... a) noexcept( /* formatting */ noexcept(static_cast(f)(static_cast(a)...))) { static_cast(f)(static_cast(a)...); std::terminate(); } /// catch_exception /// /// Invokes t; if exceptions are enabled (if not compiled with -fno-exceptions), /// catches a thrown exception e of type E and invokes c, forwarding e and any /// trailing arguments. /// /// Usage note: /// As a general rule, pass Ex const& rather than unqualified Ex as the explicit /// template argument E. The catch statement catches E without qualifiers so /// if E is Ex then that translates to catch (Ex), but if E is Ex const& then /// that translates to catch (Ex const&). /// /// Usage note: /// Passing extra values as arguments rather than capturing them allows smaller /// inlined native code at the call-site. /// /// Example: /// /// int input = // ... /// int def = 45; /// auto result = catch_exception( /// [=] { /// if (input < 0) throw std::runtime_error("foo"); /// return input; /// }, /// [](auto&& e, int num) { return num; }, /// def); /// assert(result == input < 0 ? def : input); template < typename E, typename Try, typename Catch, typename... CatchA, typename R = std::common_type_t< decltype(FOLLY_DECLVAL(Try&&)()), decltype(FOLLY_DECLVAL(Catch&&)( FOLLY_DECLVAL(E&), FOLLY_DECLVAL(CatchA&&)...))>> FOLLY_ERASE_TRYCATCH R catch_exception(Try&& t, Catch&& c, CatchA&&... a) { #if FOLLY_HAS_EXCEPTIONS try { return static_cast(t)(); } catch (E e) { return invoke_cold(static_cast(c), e, static_cast(a)...); } #else [](auto&&...) {}(c, a...); // ignore return static_cast(t)(); #endif } /// catch_exception /// /// Invokes t; if exceptions are enabled (if not compiled with -fno-exceptions), /// catches a thrown exception of any type and invokes c, forwarding any /// trailing arguments. // /// Usage note: /// Passing extra values as arguments rather than capturing them allows smaller /// inlined native code at the call-site. /// /// Example: /// /// int input = // ... /// int def = 45; /// auto result = catch_exception( /// [=] { /// if (input < 0) throw 11; /// return input; /// }, /// [](int num) { return num; }, /// def); /// assert(result == input < 0 ? def : input); template < typename Try, typename Catch, typename... CatchA, typename R = std::common_type_t< decltype(FOLLY_DECLVAL(Try&&)()), decltype(FOLLY_DECLVAL(Catch&&)(FOLLY_DECLVAL(CatchA&&)...))>> FOLLY_ERASE_TRYCATCH R catch_exception(Try&& t, Catch&& c, CatchA&&... a) noexcept( noexcept(static_cast(c)(static_cast(a)...))) { #if FOLLY_HAS_EXCEPTIONS try { return static_cast(t)(); } catch (...) { return invoke_cold(static_cast(c), static_cast(a)...); } #else [](auto&&...) {}(c, a...); // ignore return static_cast(t)(); #endif } /// rethrow_current_exception /// /// Equivalent to: /// /// throw; [[noreturn]] FOLLY_ERASE void rethrow_current_exception() { #if FOLLY_HAS_EXCEPTIONS throw; #else std::terminate(); #endif } namespace detail { unsigned int* uncaught_exceptions_ptr() noexcept; } // namespace detail /// uncaught_exceptions /// /// An accelerated version of std::uncaught_exceptions. /// /// mimic: std::uncaught_exceptions, c++17 [[FOLLY_ATTR_GNU_PURE]] FOLLY_EXPORT FOLLY_ALWAYS_INLINE int uncaught_exceptions() noexcept { #if defined(__APPLE__) return std::uncaught_exceptions(); #elif defined(_CPPLIB_VER) return std::uncaught_exceptions(); #elif defined(__has_feature) && !FOLLY_HAS_FEATURE(cxx_thread_local) return std::uncaught_exceptions(); #else thread_local unsigned int* ct; return to_signed( FOLLY_LIKELY(!!ct) ? *ct : *(ct = detail::uncaught_exceptions_ptr())); #endif } /// current_exception /// /// An accelerated version of std::current_exception. /// /// mimic: std::current_exception, c++11 std::exception_ptr current_exception() noexcept; namespace detail { #if FOLLY_APPLE_IOS #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_12_0 inline constexpr bool exception_ptr_access_ct = false; #else inline constexpr bool exception_ptr_access_ct = true; #endif #else inline constexpr bool exception_ptr_access_ct = true; #endif // 0 unknown, 1 true, -1 false extern std::atomic exception_ptr_access_rt_cache_; [[FOLLY_ATTR_GNU_COLD]] bool exception_ptr_access_rt_v_() noexcept; [[FOLLY_ATTR_GNU_COLD]] bool exception_ptr_access_rt_() noexcept; inline bool exception_ptr_access_rt() noexcept { auto const& cache = exception_ptr_access_rt_cache_; auto const value = cache.load(std::memory_order_relaxed); return FOLLY_LIKELY(value) ? value > 0 : exception_ptr_access_rt_(); } inline std::nullptr_t exception_ptr_nullptr() { return nullptr; } template auto exception_ptr_catching(std::exception_ptr const& ptr, Catch catch_) { auto const try_ = [&] { return ptr ? (std::rethrow_exception(ptr), nullptr) : nullptr; }; return catch_exception( [&] { return catch_exception(try_, catch_); }, exception_ptr_nullptr); } std::type_info const* exception_ptr_exception_typeid( std::exception const&) noexcept; std::type_info const* exception_ptr_get_type_( std::exception_ptr const& ptr) noexcept; void* exception_ptr_get_object_( std::exception_ptr const&, std::type_info const*) noexcept; } // namespace detail // exception_ptr_access // // Whether exception_ptr_get_type and template exception_ptr_get_object always // return the type or object or only do so when the stored object is of some // concrete type inheriting std::exception, and whether the non non-template // overloads of exception_ptr_get_object works at all. // // Non-authoritative. For some known platforms, inspection of exception-ptr // objects fails. This is likely to do with mismatch between the application // ABI and the system-provided libstdc++/libc++/cxxabi ABI. May falsely return // true on other platforms. [[FOLLY_ATTR_GNU_PURE]] inline bool exception_ptr_access() noexcept { return detail::exception_ptr_access_ct || detail::exception_ptr_access_rt(); } // exception_ptr_get_type // // Returns the true runtime type info of the exception as stored. inline std::type_info const* exception_ptr_get_type( std::exception_ptr const& ptr) noexcept { if (!exception_ptr_access()) { return detail::exception_ptr_catching( ptr, detail::exception_ptr_exception_typeid); } return detail::exception_ptr_get_type_(ptr); } // exception_ptr_get_object // // Returns the address of the stored exception as if it were upcast to the // given type, if it could be upcast to that type. If no type is passed, // returns the address of the stored exception without upcasting. // // Note that the stored exception is always a copy of the thrown exception, and // on some platforms caught exceptions may be copied from the stored exception. // The address is only the address of the object as stored, not as thrown and // not as caught. inline void* exception_ptr_get_object( std::exception_ptr const& ptr, std::type_info const* const target) noexcept { FOLLY_SAFE_CHECK(exception_ptr_access(), "unsupported"); return detail::exception_ptr_get_object_(ptr, target); } // exception_ptr_get_object // // Returns the true address of the exception as stored without upcasting. inline void* exception_ptr_get_object( // std::exception_ptr const& ptr) noexcept { return exception_ptr_get_object(ptr, nullptr); } // exception_ptr_get_object // // Returns the address of the stored exception as if it were upcast to the // given type, if it could be upcast to that type. template T* exception_ptr_get_object(std::exception_ptr const& ptr) noexcept { static_assert(!std::is_reference::value, "is a reference"); if (!exception_ptr_access()) { return detail::exception_ptr_catching( ptr, +[](T& ex) { return std::addressof(ex); }); } auto const target = type_info_of(); auto const object = !to_bool(target) ? nullptr : exception_ptr_get_object(ptr, target); return static_cast(object); } // exception_ptr_use_count // // Returns the reference count of the stored exception. // // Returns 0 for an empty exception_ptr. Otherwise, returns the number of // exception_ptr instances that refer to the same stored exception object. // // Analogous to std::shared_ptr::use_count. std::size_t exception_ptr_use_count(std::exception_ptr const& ptr) noexcept; // exception_ptr_unique // // Returns whether the stored exception is uniquely referenced. // // Returns false for an empty exception_ptr. Otherwise, returns true if this // is the only exception_ptr instance referring to the stored exception object. // // Analogous to std::shared_ptr::unique. bool exception_ptr_unique(std::exception_ptr const& ptr) noexcept; /// exception_ptr_try_get_object_exact_fast /// /// Returns the address of the stored exception as if it were upcast to the /// given type, if its concrete type is exactly equal to one of the types passed /// in the tag. /// /// May hypothetically fail in cases where multipe type-info objects exist for /// any of the given types. Positives are true but negatives may be either true /// or false. template T* exception_ptr_try_get_object_exact_fast( std::exception_ptr const& ptr, tag_t /*unused*/) noexcept { static_assert((std::is_convertible_v && ...)); if (!kHasRtti || !ptr || !exception_ptr_access()) { return nullptr; } auto const type = exception_ptr_get_type(ptr); if (!type) { return nullptr; } auto const object = exception_ptr_get_object(ptr); auto const fun = [&](auto const phantom, std::type_info const* const target) { assume(!!object); return type == target ? static_cast(object) : nullptr; }; T* out = nullptr; ((out = fun(static_cast(nullptr), FOLLY_TYPE_INFO_OF(S))) || ...); return out; } namespace detail { template using detect_folly_get_exception_hint_types = typename std::remove_cv_t::folly_get_exception_hint_types; } // namespace detail /// exception_ptr_get_object_hint /// /// Returns the address of the stored exception as if it were upcast to the /// given type, if it could be upcast to that type. /// /// If its concrete type is exactly equal to one of the types passed in the tag, /// this may be faster than `exception_ptr_get_object` without the hint. /// /// Prefer the next overload that uses `T::folly_get_exception_hint_types`. template T* exception_ptr_get_object_hint( std::exception_ptr const& ptr, tag_t const hint) noexcept { auto const val = exception_ptr_try_get_object_exact_fast(ptr, hint); return FOLLY_LIKELY(!!val) ? val : exception_ptr_get_object(ptr); } template T* exception_ptr_get_object_hint(std::exception_ptr const& ptr) noexcept { using hints = detected_or_t, detail::detect_folly_get_exception_hint_types, T>; return exception_ptr_get_object_hint(ptr, hints{}); } /// get_exception_tag_t /// /// A type that may contain an exception may take this passkey in the following /// member functions: /// - `get_exception(get_exception_tag_t) const` when implementing the /// `folly::get_exception()` protocol. /// - `get_mutable_exception(get_exception_tag_t)` when implementing the /// `folly::get_mutable_exception()` protocol. struct get_exception_tag_t {}; /// get_exception_fn /// get_exception /// get_mutable_exception_fn /// get_mutable_exception /// /// `get_exception(v)` is meant to become the default way for accessing /// exception-containers in `folly`. /// /// if (auto ex = get_exception(eptr)) { /// LOG(INFO) << ex->myErrorDetails(); /// } /// /// NB: Above, `ex` quacks like `MyError*`, but in some cases may be a proxy /// class type with `->`. Avoid having to know by writing `auto`, not `auto*`. /// /// For the less-common scenario where you need mutable access to an error, use /// `get_mutable_exception(v)`. This is a separate verb because: /// - Mutable exception access is rare. It may run into thread-safety bugs /// if a `std::current_exception()` pointer is accessed outside of the /// thread that threw it -- the standard permits reference semantics here! /// - Making mutable access explicit enables no-alloctions, no-atomics /// optimizations for the `const`-access path. /// /// ## Implementing this protocol /// /// Any exception container may provide these verbs via member functions /// `get_exception` and `get_mutable_exception` taking `get_exception_tag_t`. /// For an example, see `ExceptionWrapper.h`. Requirements: /// - Return a bare pointer to `Ex`, or a pointer-like with: /// - `operator bool` to test if an exception was found, /// - `operator*` and `operator->` to access the exception object, /// respecting the const-qualification of `Ex`. /// - Returns null if the container is not in an "error" state. /// - Returns null if the container has an error incompatible with `Ex`. /// - Returns a `Ex` pointer-like into the container, if it holds an error /// whose type `From` permits `std::is_convertible`, /// - `nullptr` for errors incompatible with `Ex*`. /// - `const` access via `get_exception`, mutable via `get_mutable_exception`. /// - `noexcept` /// /// This header provides `get_exception` support for `std::exception_ptr`. /// /// ## Design notes /// /// Lookup is more efficient if `Ex` matches the exact stored type, or if the /// type alias `Ex::folly_get_exception_hint_types` provides a correct hint. /// /// NB: `result` supports `get_exception(res)`, but `Try` currently /// omits `get_exception(get_exception_tag_t)`, because that might encourage /// "empty state" bugs: /// /// if (auto ex = get_exception(tryData)) { /// // handle error /// } else { // Should've checked `hasValue()`! /// doStuff(tryData.value()); // Oops, may throw `UsingUninitializedTry`! /// } /// /// The "lifetimebound" attribute provides _some_ use-after-free protection, /// see the `#if 0` manual test in `get_exception_from_std_exception_ptr`. template class get_exception_fn { public: template constexpr auto operator()( [[FOLLY_ATTR_CLANG_LIFETIMEBOUND]] const Src& src) const noexcept { if constexpr (std::is_same_v) { return exception_ptr_get_object_hint(src); } else { constexpr get_exception_tag_t passkey; static_assert( // Return type & `noexcept`ness must match std::is_same_v< const Ex&, decltype(*src.template get_exception(passkey))> && noexcept(noexcept(*src.template get_exception(passkey)))); return src.template get_exception(passkey); } } // For a mutable ptr, use `folly::get_mutable_exception(v)` instead. template constexpr auto operator()( [[FOLLY_ATTR_CLANG_LIFETIMEBOUND]] Src& s) const noexcept { return operator()(std::as_const(s)); } // It is unsafe to use `get_exception()` to get a pointer into an rvalue. // If you know what you're doing, add a `static_cast`. template void operator()(Src&&) const noexcept = delete; template void operator()(const Src&&) const noexcept = delete; }; template class get_mutable_exception_fn { public: template auto operator()([[FOLLY_ATTR_CLANG_LIFETIMEBOUND]] Src& src) const noexcept { if constexpr (std::is_same_v) { return exception_ptr_get_object_hint(src); } else { constexpr get_exception_tag_t passkey; static_assert( // Return type & `noexcept`ness must match std::is_same_v< Ex&, decltype(*src.template get_mutable_exception(passkey))> && noexcept(noexcept(*src.template get_mutable_exception(passkey)))); return src.template get_mutable_exception(passkey); } } // You want `folly::get_exception(v)` instead. template void operator()(const Src&) const noexcept = delete; // It is unsafe to use `get_mutable_exception()` to get a pointer into an // rvalue. If you know what you're doing, add a `static_cast`. template void operator()(Src&&) const noexcept = delete; template void operator()(const Src&&) const noexcept = delete; }; template inline constexpr get_exception_fn get_exception{}; template inline constexpr get_mutable_exception_fn get_mutable_exception{}; class rich_error_base; class rich_error_code_query; namespace detail { // The template declaration in `rich_error_code.h` explains this setup, where // neither of the headers includes the other. // // CRITICALLY IMPORTANT: Do not add or change specializations of this template // without reading the corresponding docblock in `rich_error_code.h`, which // explains the ODR risk inherent in this setup, and why it is currently safe. template struct get_rich_error_code_traits; template struct get_rich_error_code_traits< T, std::enable_if_t< !std::is_same_v && !std::is_base_of_v>> { static constexpr void retrieve_code( const T& container, rich_error_code_query& query) { if (auto ex = folly::get_exception(container)) { ex->retrieve_code(query); } } }; // The libc++ and cpplib implementations do not have a move constructor or a // move-assignment operator. To avoid refcount operations, we must improvise. // The libstdc++ implementation has a move constructor and a move-assignment // operator but having this does no harm. inline std::exception_ptr extract_exception_ptr( std::exception_ptr&& ptr) noexcept { constexpr auto sz = sizeof(std::exception_ptr); // assume relocatability on all platforms // assume nrvo for performance std::exception_ptr ret; std::memcpy(static_cast(&ret), &ptr, sz); std::memset(static_cast(&ptr), 0, sz); return ret; } struct make_exception_ptr_with_arg_ { using dtor_ret_t = std::conditional_t; size_t size = 0; std::type_info const* type = nullptr; void (*ctor)(void*, void*) = nullptr; dtor_ret_t (*dtor)(void*) = nullptr; template static void make(void* p, void* f) { ::new (p) E((*static_cast(f))()); } template static dtor_ret_t dtor_(void* ptr) { static_cast(ptr)->~E(); return dtor_ret_t(ptr); } template FOLLY_ERASE explicit constexpr make_exception_ptr_with_arg_( tag_t /*unused*/) noexcept : size{sizeof(E)}, type{FOLLY_TYPE_INFO_OF(E)}, ctor{make}, dtor{dtor_} {} }; std::exception_ptr make_exception_ptr_with_( make_exception_ptr_with_arg_ const&, void*) noexcept; template struct make_exception_ptr_with_fn_ { F& f_; FOLLY_ERASE std::exception_ptr operator()() const { return std::make_exception_ptr(f_()); } }; } // namespace detail /// make_exception_ptr_with_fn /// make_exception_ptr_with /// /// Constructs a std::exception_ptr. On some platforms, this form may be more /// efficient than std::make_exception_ptr. In particular, even when the latter /// is optimized not actually to throw, catch, and call std::current_exception /// internally, it remains specified to take its parameter by-value and to copy /// its parameter internally. Many in-practice exception types, including those /// which ship with standard libraries implementations, have copy constructors /// which may atomically modify refcounts; others may allocate and copy string /// data. In the best-case scenario, folly::make_exception_ptr_with may avoid /// these costs. // /// There are three overloads, with overload selection unambiguous. /// * A single invocable argument. The argument is invoked and its return value /// is the managed exception. /// * Variadic arguments, the first of which is in_place_type. An exception /// of type E is created in-place with the remaining arguments forwarded to /// the constructor of E, and it is the managed exception. /// * Two arguments, the first of which is in_place. The argument is moved or /// copied and the result is the managed exception. This form is the closest /// to std::make_exception_ptr. /// /// Example: /// /// std::exception_ptr eptr = make_exception_ptr_with( /// [] { return std::runtime_error("message string"); }); /// /// std::exception_ptr eptr = make_exception_ptr_with( /// std::in_place_type, "message string"); /// /// std::exception_ptr eptr = make_exception_ptr_with( /// std::in_place, std::runtime_error("message string"); /// /// In each example above, the variable eptr holds a managed exception object of /// type std::runtime_error with a message string "message string" that would be /// returned by member what(). /// /// Note that a managed exception object can have any value type whatsoever; it /// is not required to have value type of or inheriting std::exception. This is /// the same principle as for throw statements and throw_exception above. struct make_exception_ptr_with_fn { private: template using make_arg_ = conditional_t< std::is_array>::value, detail::throw_exception_arg_array_, detail::throw_exception_arg_base_>; template using make_arg_t = typename make_arg_::template apply; template auto make(A&&... a) const noexcept { return [&] { return E(static_cast(a)...); }; } public: template std::exception_ptr operator()(F f) const noexcept { if ((kIsGlibcxx || kIsLibcpp) && !kIsApple && !kIsWindows // && kHasRtti && exception_ptr_access()) { static const detail::make_exception_ptr_with_arg_ arg{tag}; return detail::make_exception_ptr_with_(arg, &f); } if (kHasExceptions) { return catch_exception( detail::make_exception_ptr_with_fn_{f}, current_exception); } return std::exception_ptr(); } template FOLLY_ERASE std::exception_ptr operator()( std::in_place_type_t /*unused*/, A&&... a) const noexcept { return operator()(make...>(static_cast(a)...)); } template FOLLY_ERASE std::exception_ptr operator()( std::in_place_t /*unused*/, E&& e) const noexcept { constexpr auto tag = std::in_place_type>; check_(FOLLY_TYPE_INFO_OF(std::decay_t), FOLLY_TYPE_INFO_OF(e)); return operator()(tag, static_cast(e)); } private: FOLLY_ALWAYS_INLINE void check_( std::type_info const* s, std::type_info const* d) const noexcept { FOLLY_SAFE_DCHECK( !s || !d || *s == *d, "mismatched static and dynamic types indicates object slicing"); } }; inline constexpr make_exception_ptr_with_fn make_exception_ptr_with{}; // exception_shared_string // // An immutable refcounted string, with the same layout as a pointer, suitable // for use in an exception. Exceptions are intended to cheaply nothrow-copy- // constructible and mostly do not need to optimize moves, and this affects how // exception messages are best stored. // // May be constructed with a string literal pointer, which will be stored with // no refcount required. class exception_shared_string { private: using format_sig_ = void(void*, char*, std::size_t); template using test_format_ = decltype(FOLLY_DECLVAL(F)(static_cast(nullptr), std::size_t(0))); static void test_params_(char const*, std::size_t); template static void ffun_(void* f, char* b, std::size_t l) { (*static_cast(f))(b, l); } struct state; // alignment is alignof(void*) struct tagged_what_t { static inline constexpr uintptr_t non_literal_mask = uintptr_t(1) << (sizeof(const char*) * 8 - 1); // The top bit of p_ encodes where the string lives: // - top bit == 0: in an immortal literal // - top bit == 1: in an refcounted allocated state // The top bit of a userspace pointer is zero on all supported platforms. const char* p_; void assert_top_bit_is_zero(const char* p) { // Debug-only on 64-bit platforms because all known ones leave the top // bit free. Userspace pointers MAY use the top bit on unsupported // 32-bit platforms -- abort in opt builds, instead of corrupting memory. // // Limitations of constexpr force a gap in assertion coverage -- if a // literal const char* sets the top bit (seems very unlikely!), and it is // only used in a constexpr exception_shared_string, then invalid memory // access would be triggered by what(), and the copy constructor. if constexpr (sizeof(void*) != 8 || kIsDebug) { FOLLY_SAFE_CHECK(!(uintptr_t(p) & non_literal_mask)); } } #if FOLLY_CPLUSPLUS >= 202002 && !defined(__NVCC__) constexpr tagged_what_t(vtag_t /*literal*/, const char* p) : p_{p} { if (!std::is_constant_evaluated()) { assert_top_bit_is_zero(p); } } #endif // NEVER make this `constexpr`; `what()` explains why. tagged_what_t(vtag_t /*allocated*/, const char* p) : p_{reinterpret_cast( non_literal_mask | reinterpret_cast(p))} { assert_top_bit_is_zero(p); } bool is_literal() const noexcept { return !(non_literal_mask & reinterpret_cast(p_)); } #if FOLLY_CPLUSPLUS >= 202002 constexpr const char* what() const noexcept { if (std::is_constant_evaluated()) { // Only the literal ctor is `constexpr`, so we can assume no // bit-twiddling is needed. Consteval code cannot do Pointer bithacks. return p_; } return reinterpret_cast( ~non_literal_mask & reinterpret_cast(p_)); } #else const char* what() const noexcept { return reinterpret_cast( ~non_literal_mask & reinterpret_cast(p_)); } #endif }; static_assert(sizeof(tagged_what_t) == sizeof(void*)); tagged_what_t tagged_what_; exception_shared_string(std::size_t, format_sig_&, void*); static char const* from_state(state const* state) noexcept; static state* to_state(const tagged_what_t&) noexcept; void ruin_state() noexcept; public: #if FOLLY_CPLUSPLUS >= 202002 && !defined(__NVCC__) constexpr explicit exception_shared_string(literal_c_str p) noexcept : tagged_what_{vtag, p.ptr} {} #endif exception_shared_string(char const*, std::size_t); template < typename String, typename = decltype(test_params_( FOLLY_DECLVAL(String const&).data(), FOLLY_DECLVAL(String const&).size()))> explicit exception_shared_string(String const& str) : exception_shared_string{str.data(), str.size()} {} template ()), 0)) = 0> exception_shared_string(std::size_t size, F func) : exception_shared_string( size, ffun_, &reinterpret_cast(func)) {} exception_shared_string(exception_shared_string const&) noexcept; exception_shared_string& operator=(exception_shared_string const&) noexcept; // It would be extra effort to implement move support in C++17, but there is // currently no demand for it. #if FOLLY_CPLUSPLUS >= 202002 && !defined(__NVCC__) constexpr exception_shared_string(exception_shared_string&& that) noexcept : tagged_what_{that.tagged_what_} { that.tagged_what_ = tagged_what_t{vtag, ""}; // safe-to-read moved-out state } exception_shared_string& operator=(exception_shared_string&&) noexcept; #else exception_shared_string(exception_shared_string&&) = delete; exception_shared_string& operator=(exception_shared_string&&) = delete; #endif #if FOLLY_CPLUSPLUS >= 202002 && defined(__cpp_lib_is_constant_evaluated) constexpr ~exception_shared_string() { if (!std::is_constant_evaluated()) { ruin_state(); } } #else ~exception_shared_string() { ruin_state(); } #endif #if FOLLY_CPLUSPLUS >= 202002 constexpr #endif char const* what() const noexcept { return tagged_what_.what(); } }; } // namespace folly