715 lines
27 KiB
C
715 lines
27 KiB
C
|
/*
|
||
|
* 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>
|
||
|
*/
|
||
|
|
||
|
#pragma once
|
||
|
|
||
|
#include <cassert>
|
||
|
#include <cstdint>
|
||
|
#include <exception>
|
||
|
#include <iosfwd>
|
||
|
#include <memory>
|
||
|
#include <new>
|
||
|
#include <type_traits>
|
||
|
#include <typeinfo>
|
||
|
#include <utility>
|
||
|
|
||
|
#include <folly/CPortability.h>
|
||
|
#include <folly/Demangle.h>
|
||
|
#include <folly/ExceptionString.h>
|
||
|
#include <folly/FBString.h>
|
||
|
#include <folly/Portability.h>
|
||
|
#include <folly/Traits.h>
|
||
|
#include <folly/Utility.h>
|
||
|
#include <folly/lang/Assume.h>
|
||
|
|
||
|
#ifdef __GNUC__
|
||
|
#pragma GCC diagnostic push
|
||
|
#pragma GCC diagnostic ignored "-Wpragmas"
|
||
|
#pragma GCC diagnostic ignored "-Wpotentially-evaluated-expression"
|
||
|
// GCC gets confused about lambda scopes and issues shadow-local warnings for
|
||
|
// parameters in totally different functions.
|
||
|
FOLLY_GCC_DISABLE_NEW_SHADOW_WARNINGS
|
||
|
#endif
|
||
|
|
||
|
#define FOLLY_EXCEPTION_WRAPPER_H_INCLUDED
|
||
|
|
||
|
namespace folly {
|
||
|
|
||
|
#define FOLLY_REQUIRES_DEF(...) \
|
||
|
std::enable_if_t<static_cast<bool>(__VA_ARGS__), long>
|
||
|
|
||
|
#define FOLLY_REQUIRES(...) FOLLY_REQUIRES_DEF(__VA_ARGS__) = __LINE__
|
||
|
|
||
|
namespace exception_wrapper_detail {
|
||
|
|
||
|
template <template <class> class T, class... As>
|
||
|
using AllOf = StrictConjunction<T<As>...>;
|
||
|
|
||
|
template <bool If, class T>
|
||
|
using AddConstIf = std::conditional_t<If, const T, T>;
|
||
|
|
||
|
template <class Fn, class A>
|
||
|
FOLLY_ERASE auto fold(Fn&&, A&& a) {
|
||
|
return static_cast<A&&>(a);
|
||
|
}
|
||
|
|
||
|
template <class Fn, class A, class B, class... Bs>
|
||
|
FOLLY_ERASE auto fold(Fn&& fn, A&& a, B&& b, Bs&&... bs) {
|
||
|
return fold(
|
||
|
// This looks like a use of fn after a move of fn, but in reality, this is
|
||
|
// just a cast and not a move. That's because regardless of which fold
|
||
|
// overload is selected, fn gets bound to a &&. Had fold taken fn by value
|
||
|
// there would indeed be a problem here.
|
||
|
static_cast<Fn&&>(fn),
|
||
|
static_cast<Fn&&>(fn)(static_cast<A&&>(a), static_cast<B&&>(b)),
|
||
|
static_cast<Bs&&>(bs)...);
|
||
|
}
|
||
|
|
||
|
} // namespace exception_wrapper_detail
|
||
|
|
||
|
//! Throwing exceptions can be a convenient way to handle errors. Storing
|
||
|
//! exceptions in an `exception_ptr` makes it easy to handle exceptions in a
|
||
|
//! different thread or at a later time. `exception_ptr` can also be used in a
|
||
|
//! very generic result/exception wrapper.
|
||
|
//!
|
||
|
//! However, there are some issues with throwing exceptions and
|
||
|
//! `std::exception_ptr`. These issues revolve around `throw` being expensive,
|
||
|
//! particularly in a multithreaded environment (see
|
||
|
//! ExceptionWrapperBenchmark.cpp).
|
||
|
//!
|
||
|
//! Imagine we have a library that has an API which returns a result/exception
|
||
|
//! wrapper. Let's consider some approaches for implementing this wrapper.
|
||
|
//! First, we could store a `std::exception`. This approach loses the derived
|
||
|
//! exception type, which can make exception handling more difficult for users
|
||
|
//! that prefer rethrowing the exception. We could use a `folly::dynamic` for
|
||
|
//! every possible type of exception. This is not very flexible - adding new
|
||
|
//! types of exceptions requires a change to the result/exception wrapper. We
|
||
|
//! could use an `exception_ptr`. However, constructing an `exception_ptr` as
|
||
|
//! well as accessing the error requires a call to throw. That means that there
|
||
|
//! will be two calls to throw in order to process the exception. For
|
||
|
//! performance sensitive applications, this may be unacceptable.
|
||
|
//!
|
||
|
//! `exception_wrapper` is designed to handle exception management for both
|
||
|
//! convenience and high performance use cases. `make_exception_wrapper` is
|
||
|
//! templated on derived type, allowing us to rethrow the exception properly for
|
||
|
//! users that prefer convenience. These explicitly named exception types can
|
||
|
//! therefore be handled without any peformance penalty. `exception_wrapper` is
|
||
|
//! also flexible enough to accept any type. If a caught exception is not of an
|
||
|
//! explicitly named type, then `std::exception_ptr` is used to preserve the
|
||
|
//! exception state. For performance sensitive applications, the accessor
|
||
|
//! methods can test or extract a pointer to a specific exception type with very
|
||
|
//! little overhead.
|
||
|
//!
|
||
|
//! \par Example usage:
|
||
|
//! \par
|
||
|
//! \code
|
||
|
//! exception_wrapper globalExceptionWrapper;
|
||
|
//!
|
||
|
//! // Thread1
|
||
|
//! void doSomethingCrazy() {
|
||
|
//! int rc = doSomethingCrazyWithLameReturnCodes();
|
||
|
//! if (rc == NAILED_IT) {
|
||
|
//! globalExceptionWrapper = exception_wrapper();
|
||
|
//! } else if (rc == FACE_PLANT) {
|
||
|
//! globalExceptionWrapper = make_exception_wrapper<FacePlantException>();
|
||
|
//! } else if (rc == FAIL_WHALE) {
|
||
|
//! globalExceptionWrapper = make_exception_wrapper<FailWhaleException>();
|
||
|
//! }
|
||
|
//! }
|
||
|
//!
|
||
|
//! // Thread2: Exceptions are ok!
|
||
|
//! void processResult() {
|
||
|
//! try {
|
||
|
//! globalExceptionWrapper.throw_exception();
|
||
|
//! } catch (const FacePlantException& e) {
|
||
|
//! LOG(ERROR) << "FACEPLANT!";
|
||
|
//! } catch (const FailWhaleException& e) {
|
||
|
//! LOG(ERROR) << "FAILWHALE!";
|
||
|
//! }
|
||
|
//! }
|
||
|
//!
|
||
|
//! // Thread2: Exceptions are bad!
|
||
|
//! void processResult() {
|
||
|
//! globalExceptionWrapper.handle(
|
||
|
//! [&](FacePlantException& faceplant) {
|
||
|
//! LOG(ERROR) << "FACEPLANT";
|
||
|
//! },
|
||
|
//! [&](FailWhaleException& failwhale) {
|
||
|
//! LOG(ERROR) << "FAILWHALE!";
|
||
|
//! },
|
||
|
//! [](...) {
|
||
|
//! LOG(FATAL) << "Unrecognized exception";
|
||
|
//! });
|
||
|
//! }
|
||
|
//! \endcode
|
||
|
class exception_wrapper final {
|
||
|
private:
|
||
|
struct FOLLY_EXPORT AnyException : std::exception {
|
||
|
std::type_info const* typeinfo_;
|
||
|
template <class T>
|
||
|
/* implicit */ AnyException(T&& t) noexcept : typeinfo_(&typeid(t)) {}
|
||
|
};
|
||
|
|
||
|
template <class Fn>
|
||
|
struct arg_type_;
|
||
|
template <class Fn>
|
||
|
using arg_type = _t<arg_type_<Fn>>;
|
||
|
|
||
|
// exception_wrapper is implemented as a simple variant over four
|
||
|
// different representations:
|
||
|
// 0. Empty, no exception.
|
||
|
// 1. An small object stored in-situ.
|
||
|
// 2. A larger object stored on the heap and referenced with a
|
||
|
// std::shared_ptr.
|
||
|
// 3. A std::exception_ptr, together with either:
|
||
|
// a. A pointer to the referenced std::exception object, or
|
||
|
// b. A pointer to a std::type_info object for the referenced exception,
|
||
|
// or for an unspecified type if the type is unknown.
|
||
|
// This is accomplished with the help of a union and a pointer to a hand-
|
||
|
// rolled virtual table. This virtual table contains pointers to functions
|
||
|
// that know which field of the union is active and do the proper action.
|
||
|
// The class invariant ensures that the vtable ptr and the union stay in sync.
|
||
|
struct VTable {
|
||
|
void (*copy_)(exception_wrapper const*, exception_wrapper*);
|
||
|
void (*move_)(exception_wrapper*, exception_wrapper*);
|
||
|
void (*delete_)(exception_wrapper*);
|
||
|
void (*throw_)(exception_wrapper const*);
|
||
|
std::type_info const* (*type_)(exception_wrapper const*);
|
||
|
std::exception const* (*get_exception_)(exception_wrapper const*);
|
||
|
exception_wrapper (*get_exception_ptr_)(exception_wrapper const*);
|
||
|
};
|
||
|
|
||
|
[[noreturn]] static void onNoExceptionError(char const* name);
|
||
|
|
||
|
template <class Ret, class... Args>
|
||
|
static Ret noop_(Args...);
|
||
|
|
||
|
static std::type_info const* uninit_type_(exception_wrapper const*);
|
||
|
|
||
|
static VTable const uninit_;
|
||
|
|
||
|
template <class Ex>
|
||
|
using IsStdException = std::is_base_of<std::exception, std::decay_t<Ex>>;
|
||
|
template <bool B, class T>
|
||
|
using AddConstIf = exception_wrapper_detail::AddConstIf<B, T>;
|
||
|
template <class CatchFn>
|
||
|
using IsCatchAll =
|
||
|
std::is_same<arg_type<std::decay_t<CatchFn>>, AnyException>;
|
||
|
|
||
|
struct Unknown {};
|
||
|
|
||
|
// Sadly, with the gcc-4.9 platform, std::logic_error and std::runtime_error
|
||
|
// do not fit here. They also don't have noexcept copy-ctors, so the internal
|
||
|
// storage wouldn't be used anyway. For the gcc-5 platform, both logic_error
|
||
|
// and runtime_error can be safely stored internally.
|
||
|
struct Buffer {
|
||
|
using Storage =
|
||
|
std::aligned_storage_t<2 * sizeof(void*), alignof(std::exception)>;
|
||
|
Storage buff_;
|
||
|
|
||
|
Buffer() : buff_{} {}
|
||
|
|
||
|
template <class Ex, typename... As>
|
||
|
Buffer(in_place_type_t<Ex>, As&&... as_);
|
||
|
template <class Ex>
|
||
|
Ex& as() noexcept;
|
||
|
template <class Ex>
|
||
|
Ex const& as() const noexcept;
|
||
|
};
|
||
|
|
||
|
struct ThrownTag {};
|
||
|
struct InSituTag {};
|
||
|
struct OnHeapTag {};
|
||
|
|
||
|
template <class T>
|
||
|
using PlacementOf = std::conditional_t<
|
||
|
!IsStdException<T>::value,
|
||
|
ThrownTag,
|
||
|
std::conditional_t<
|
||
|
sizeof(T) <= sizeof(Buffer::Storage) &&
|
||
|
alignof(T) <= alignof(Buffer::Storage) &&
|
||
|
noexcept(T(std::declval<T&&>())) &&
|
||
|
noexcept(T(std::declval<T const&>())),
|
||
|
InSituTag,
|
||
|
OnHeapTag>>;
|
||
|
|
||
|
static std::exception const* as_exception_or_null_(std::exception const& ex);
|
||
|
static std::exception const* as_exception_or_null_(AnyException);
|
||
|
|
||
|
struct ExceptionPtr {
|
||
|
std::exception_ptr ptr_;
|
||
|
std::uintptr_t exception_or_type_; // odd for type_info
|
||
|
static_assert(
|
||
|
1 < alignof(std::exception) && 1 < alignof(std::type_info),
|
||
|
"Surprise! std::exception and std::type_info don't have alignment "
|
||
|
"greater than one. as_int_ below will not work!");
|
||
|
|
||
|
static std::uintptr_t as_int_(
|
||
|
std::exception_ptr const& ptr,
|
||
|
std::exception const& e) noexcept;
|
||
|
static std::uintptr_t as_int_(
|
||
|
std::exception_ptr const& ptr,
|
||
|
AnyException e) noexcept;
|
||
|
bool has_exception_() const;
|
||
|
std::exception const* as_exception_() const;
|
||
|
std::type_info const* as_type_() const;
|
||
|
static void copy_(exception_wrapper const* from, exception_wrapper* to);
|
||
|
static void move_(exception_wrapper* from, exception_wrapper* to);
|
||
|
static void delete_(exception_wrapper* that);
|
||
|
[[noreturn]] static void throw_(exception_wrapper const* that);
|
||
|
static std::type_info const* type_(exception_wrapper const* that);
|
||
|
static std::exception const* get_exception_(exception_wrapper const* that);
|
||
|
static exception_wrapper get_exception_ptr_(exception_wrapper const* that);
|
||
|
static VTable const ops_;
|
||
|
};
|
||
|
|
||
|
template <class Ex>
|
||
|
struct InPlace {
|
||
|
static_assert(IsStdException<Ex>::value, "only deriving std::exception");
|
||
|
static void copy_(exception_wrapper const* from, exception_wrapper* to);
|
||
|
static void move_(exception_wrapper* from, exception_wrapper* to);
|
||
|
static void delete_(exception_wrapper* that);
|
||
|
[[noreturn]] static void throw_(exception_wrapper const* that);
|
||
|
static std::type_info const* type_(exception_wrapper const*);
|
||
|
static std::exception const* get_exception_(exception_wrapper const* that);
|
||
|
static exception_wrapper get_exception_ptr_(exception_wrapper const* that);
|
||
|
static constexpr VTable const ops_{copy_,
|
||
|
move_,
|
||
|
delete_,
|
||
|
throw_,
|
||
|
type_,
|
||
|
get_exception_,
|
||
|
get_exception_ptr_};
|
||
|
};
|
||
|
|
||
|
struct SharedPtr {
|
||
|
struct Base {
|
||
|
std::type_info const* info_;
|
||
|
Base() = default;
|
||
|
explicit Base(std::type_info const& info) : info_(&info) {}
|
||
|
virtual ~Base() {}
|
||
|
virtual void throw_() const = 0;
|
||
|
virtual std::exception const* get_exception_() const noexcept = 0;
|
||
|
virtual exception_wrapper get_exception_ptr_() const noexcept = 0;
|
||
|
};
|
||
|
template <class Ex>
|
||
|
struct Impl final : public Base {
|
||
|
static_assert(IsStdException<Ex>::value, "only deriving std::exception");
|
||
|
Ex ex_;
|
||
|
Impl() = default;
|
||
|
// clang-format off
|
||
|
template <typename... As>
|
||
|
explicit Impl(As&&... as)
|
||
|
: Base{typeid(Ex)}, ex_(std::forward<As>(as)...) {}
|
||
|
[[noreturn]] void throw_() const override;
|
||
|
// clang-format on
|
||
|
std::exception const* get_exception_() const noexcept override;
|
||
|
exception_wrapper get_exception_ptr_() const noexcept override;
|
||
|
};
|
||
|
std::shared_ptr<Base> ptr_;
|
||
|
|
||
|
static void copy_(exception_wrapper const* from, exception_wrapper* to);
|
||
|
static void move_(exception_wrapper* from, exception_wrapper* to);
|
||
|
static void delete_(exception_wrapper* that);
|
||
|
[[noreturn]] static void throw_(exception_wrapper const* that);
|
||
|
static std::type_info const* type_(exception_wrapper const* that);
|
||
|
static std::exception const* get_exception_(exception_wrapper const* that);
|
||
|
static exception_wrapper get_exception_ptr_(exception_wrapper const* that);
|
||
|
static VTable const ops_;
|
||
|
};
|
||
|
|
||
|
union {
|
||
|
Buffer buff_{};
|
||
|
ExceptionPtr eptr_;
|
||
|
SharedPtr sptr_;
|
||
|
};
|
||
|
VTable const* vptr_{&uninit_};
|
||
|
|
||
|
template <class Ex, typename... As>
|
||
|
exception_wrapper(ThrownTag, in_place_type_t<Ex>, As&&... as);
|
||
|
|
||
|
template <class Ex, typename... As>
|
||
|
exception_wrapper(OnHeapTag, in_place_type_t<Ex>, As&&... as);
|
||
|
|
||
|
template <class Ex, typename... As>
|
||
|
exception_wrapper(InSituTag, in_place_type_t<Ex>, As&&... as);
|
||
|
|
||
|
template <class T>
|
||
|
struct IsRegularExceptionType
|
||
|
: StrictConjunction<
|
||
|
std::is_copy_constructible<T>,
|
||
|
Negation<std::is_base_of<exception_wrapper, T>>,
|
||
|
Negation<std::is_abstract<T>>> {};
|
||
|
|
||
|
template <class CatchFn, bool IsConst = false>
|
||
|
struct ExceptionTypeOf;
|
||
|
|
||
|
template <bool IsConst>
|
||
|
struct HandleReduce;
|
||
|
|
||
|
template <bool IsConst>
|
||
|
struct HandleStdExceptReduce;
|
||
|
|
||
|
template <class This, class... CatchFns>
|
||
|
static void handle_(std::false_type, This& this_, CatchFns&... fns);
|
||
|
|
||
|
template <class This, class... CatchFns>
|
||
|
static void handle_(std::true_type, This& this_, CatchFns&... fns);
|
||
|
|
||
|
template <class Ex, class This, class Fn>
|
||
|
static bool with_exception_(This& this_, Fn fn_);
|
||
|
|
||
|
public:
|
||
|
static exception_wrapper from_exception_ptr(
|
||
|
std::exception_ptr const& eptr) noexcept;
|
||
|
|
||
|
//! Default-constructs an empty `exception_wrapper`
|
||
|
//! \post `type() == none()`
|
||
|
exception_wrapper() noexcept {}
|
||
|
|
||
|
//! Move-constructs an `exception_wrapper`
|
||
|
//! \post `*this` contains the value of `that` prior to the move
|
||
|
//! \post `that.type() == none()`
|
||
|
exception_wrapper(exception_wrapper&& that) noexcept;
|
||
|
|
||
|
//! Copy-constructs an `exception_wrapper`
|
||
|
//! \post `*this` contains a copy of `that`, and `that` is unmodified
|
||
|
//! \post `type() == that.type()`
|
||
|
exception_wrapper(exception_wrapper const& that) noexcept;
|
||
|
|
||
|
//! Move-assigns an `exception_wrapper`
|
||
|
//! \pre `this != &that`
|
||
|
//! \post `*this` contains the value of `that` prior to the move
|
||
|
//! \post `that.type() == none()`
|
||
|
exception_wrapper& operator=(exception_wrapper&& that) noexcept;
|
||
|
|
||
|
//! Copy-assigns an `exception_wrapper`
|
||
|
//! \post `*this` contains a copy of `that`, and `that` is unmodified
|
||
|
//! \post `type() == that.type()`
|
||
|
exception_wrapper& operator=(exception_wrapper const& that) noexcept;
|
||
|
|
||
|
~exception_wrapper();
|
||
|
|
||
|
//! \pre `ptr` is empty, or it holds a reference to an exception that is not
|
||
|
//! derived from `std::exception`.
|
||
|
//! \post `!ptr || bool(*this)`
|
||
|
//! \post `hasThrownException() == true`
|
||
|
//! \post `type() == unknown()`
|
||
|
explicit exception_wrapper(std::exception_ptr ptr) noexcept;
|
||
|
|
||
|
//! \pre `ptr` holds a reference to `ex`.
|
||
|
//! \post `hasThrownException() == true`
|
||
|
//! \post `bool(*this)`
|
||
|
//! \post `type() == typeid(ex)`
|
||
|
template <class Ex>
|
||
|
exception_wrapper(std::exception_ptr ptr, Ex& ex) noexcept;
|
||
|
|
||
|
//! \pre `typeid(ex) == typeid(typename decay<Ex>::type)`
|
||
|
//! \post `bool(*this)`
|
||
|
//! \post `hasThrownException() == false`
|
||
|
//! \post `type() == typeid(ex)`
|
||
|
//! \note Exceptions of types derived from `std::exception` can be implicitly
|
||
|
//! converted to an `exception_wrapper`.
|
||
|
template <
|
||
|
class Ex,
|
||
|
class Ex_ = std::decay_t<Ex>,
|
||
|
FOLLY_REQUIRES(
|
||
|
Conjunction<IsStdException<Ex_>, IsRegularExceptionType<Ex_>>::value)>
|
||
|
/* implicit */ exception_wrapper(Ex&& ex);
|
||
|
|
||
|
//! \pre `typeid(ex) == typeid(typename decay<Ex>::type)`
|
||
|
//! \post `bool(*this)`
|
||
|
//! \post `hasThrownException() == false`
|
||
|
//! \post `type() == typeid(ex)`
|
||
|
//! \note Exceptions of types not derived from `std::exception` can still be
|
||
|
//! used to construct an `exception_wrapper`, but you must specify
|
||
|
//! `folly::in_place` as the first parameter.
|
||
|
template <
|
||
|
class Ex,
|
||
|
class Ex_ = std::decay_t<Ex>,
|
||
|
FOLLY_REQUIRES(IsRegularExceptionType<Ex_>::value)>
|
||
|
exception_wrapper(in_place_t, Ex&& ex);
|
||
|
|
||
|
template <
|
||
|
class Ex,
|
||
|
typename... As,
|
||
|
FOLLY_REQUIRES(IsRegularExceptionType<Ex>::value)>
|
||
|
exception_wrapper(in_place_type_t<Ex>, As&&... as);
|
||
|
|
||
|
//! Swaps the value of `*this` with the value of `that`
|
||
|
void swap(exception_wrapper& that) noexcept;
|
||
|
|
||
|
//! \return `true` if `*this` is holding an exception.
|
||
|
explicit operator bool() const noexcept;
|
||
|
|
||
|
//! \return `!bool(*this)`
|
||
|
bool operator!() const noexcept;
|
||
|
|
||
|
//! Make this `exception_wrapper` empty
|
||
|
//! \post `!*this`
|
||
|
void reset();
|
||
|
|
||
|
//! \return `true` if this `exception_wrapper` holds a reference to an
|
||
|
//! exception that was thrown (i.e., if it was constructed with
|
||
|
//! a `std::exception_ptr`, or if `to_exception_ptr()` was called on a
|
||
|
//! (non-const) reference to `*this`).
|
||
|
bool has_exception_ptr() const noexcept;
|
||
|
|
||
|
//! \return a pointer to the `std::exception` held by `*this`, if it holds
|
||
|
//! one; otherwise, returns `nullptr`.
|
||
|
//! \note This function does not mutate the `exception_wrapper` object.
|
||
|
//! \note This function never causes an exception to be thrown.
|
||
|
std::exception* get_exception() noexcept;
|
||
|
//! \overload
|
||
|
std::exception const* get_exception() const noexcept;
|
||
|
|
||
|
//! \returns a pointer to the `Ex` held by `*this`, if it holds an object
|
||
|
//! whose type `From` permits `std::is_convertible<From*, Ex*>`;
|
||
|
//! otherwise, returns `nullptr`.
|
||
|
//! \note This function does not mutate the `exception_wrapper` object.
|
||
|
//! \note This function may cause an exception to be thrown and immediately
|
||
|
//! caught internally, affecting runtime performance.
|
||
|
template <typename Ex>
|
||
|
Ex* get_exception() noexcept;
|
||
|
//! \overload
|
||
|
template <typename Ex>
|
||
|
Ex const* get_exception() const noexcept;
|
||
|
|
||
|
//! \return A `std::exception_ptr` that references either the exception held
|
||
|
//! by `*this`, or a copy of same.
|
||
|
//! \note This function may need to throw an exception to complete the action.
|
||
|
//! \note The non-const overload of this function mutates `*this` to cache the
|
||
|
//! computed `std::exception_ptr`; that is, this function may cause
|
||
|
//! `has_exception_ptr()` to change from `false` to `true`.
|
||
|
std::exception_ptr to_exception_ptr() noexcept;
|
||
|
//! \overload
|
||
|
std::exception_ptr to_exception_ptr() const noexcept;
|
||
|
|
||
|
//! \return the `typeid` of an unspecified type used by
|
||
|
//! `exception_wrapper::type()` to denote an empty `exception_wrapper`.
|
||
|
static std::type_info const& none() noexcept;
|
||
|
//! \return the `typeid` of an unspecified type used by
|
||
|
//! `exception_wrapper::type()` to denote an `exception_wrapper` that
|
||
|
//! holds an exception of unknown type.
|
||
|
static std::type_info const& unknown() noexcept;
|
||
|
|
||
|
//! Returns the `typeid` of the wrapped exception object. If there is no
|
||
|
//! wrapped exception object, returns `exception_wrapper::none()`. If
|
||
|
//! this instance wraps an exception of unknown type not derived from
|
||
|
//! `std::exception`, returns `exception_wrapper::unknown()`.
|
||
|
std::type_info const& type() const noexcept;
|
||
|
|
||
|
//! \return If `get_exception() != nullptr`, `class_name() + ": " +
|
||
|
//! get_exception()->what()`; otherwise, `class_name()`.
|
||
|
folly::fbstring what() const;
|
||
|
|
||
|
//! \return If `!*this`, the empty string; otherwise, if
|
||
|
//! `type() == unknown()`, the string `"<unknown exception>"`; otherwise,
|
||
|
//! the result of `type().name()` after demangling.
|
||
|
folly::fbstring class_name() const;
|
||
|
|
||
|
//! \tparam Ex The expression type to check for compatibility with.
|
||
|
//! \return `true` if and only if `*this` wraps an exception that would be
|
||
|
//! caught with a `catch(Ex const&)` clause.
|
||
|
//! \note If `*this` is empty, this function returns `false`.
|
||
|
template <class Ex>
|
||
|
bool is_compatible_with() const noexcept;
|
||
|
|
||
|
//! Throws the wrapped expression.
|
||
|
//! \pre `bool(*this)`
|
||
|
[[noreturn]] void throw_exception() const;
|
||
|
|
||
|
//! Throws the wrapped expression nested into another exception.
|
||
|
//! \pre `bool(*this)`
|
||
|
//! \tparam ex Exception in *this will be thrown nested into ex;
|
||
|
// see std::throw_with_nested() for details on this semantic.
|
||
|
template <class Ex>
|
||
|
[[noreturn]] void throw_with_nested(Ex&& ex) const;
|
||
|
|
||
|
//! Call `fn` with the wrapped exception (if any), if `fn` can accept it.
|
||
|
//! \par Example
|
||
|
//! \code
|
||
|
//! exception_wrapper ew{std::runtime_error("goodbye cruel world")};
|
||
|
//!
|
||
|
//! assert( ew.with_exception([](std::runtime_error& e){/*...*/}) );
|
||
|
//!
|
||
|
//! assert( !ew.with_exception([](int& e){/*...*/}) );
|
||
|
//!
|
||
|
//! assert( !exception_wrapper{}.with_exception([](int& e){/*...*/}) );
|
||
|
//! \endcode
|
||
|
//! \tparam Ex Optionally, the type of the exception that `fn` accepts.
|
||
|
//! \tparam Fn The type of a monomophic function object.
|
||
|
//! \param fn A function object to call with the wrapped exception
|
||
|
//! \return `true` if and only if `fn` was called.
|
||
|
//! \note Optionally, you may explicitly specify the type of the exception
|
||
|
//! that `fn` expects, as in
|
||
|
//! \code
|
||
|
//! ew.with_exception<std::runtime_error>([](auto&& e) { /*...*/; });
|
||
|
//! \endcode
|
||
|
//! \note The handler may or may not be invoked with an active exception.
|
||
|
//! **Do not try to rethrow the exception with `throw;` from within your
|
||
|
//! handler -- that is, a throw expression with no operand.** This may
|
||
|
//! cause your process to terminate. (It is perfectly ok to throw from
|
||
|
//! a handler so long as you specify the exception to throw, as in
|
||
|
//! `throw e;`.)
|
||
|
template <class Ex = void const, class Fn>
|
||
|
bool with_exception(Fn fn);
|
||
|
//! \overload
|
||
|
template <class Ex = void const, class Fn>
|
||
|
bool with_exception(Fn fn) const;
|
||
|
|
||
|
//! Handle the wrapped expression as if with a series of `catch` clauses,
|
||
|
//! propagating the exception if no handler matches.
|
||
|
//! \par Example
|
||
|
//! \code
|
||
|
//! exception_wrapper ew{std::runtime_error("goodbye cruel world")};
|
||
|
//!
|
||
|
//! ew.handle(
|
||
|
//! [&](std::logic_error const& e) {
|
||
|
//! LOG(DFATAL) << "ruh roh";
|
||
|
//! ew.throw_exception(); // rethrow the active exception without
|
||
|
//! // slicing it. Will not be caught by other
|
||
|
//! // handlers in this call.
|
||
|
//! },
|
||
|
//! [&](std::exception const& e) {
|
||
|
//! LOG(ERROR) << ew.what();
|
||
|
//! });
|
||
|
//! \endcode
|
||
|
//! In the above example, any exception _not_ derived from `std::exception`
|
||
|
//! will be propagated. To specify a catch-all clause, pass a lambda that
|
||
|
//! takes a C-style elipses, as in:
|
||
|
//! \code
|
||
|
//! ew.handle(/*...* /, [](...) { /* handle unknown exception */ } )
|
||
|
//! \endcode
|
||
|
//! \pre `!*this`
|
||
|
//! \tparam CatchFns... A pack of unary monomorphic function object types.
|
||
|
//! \param fns A pack of unary monomorphic function objects to be treated as
|
||
|
//! an ordered list of potential exception handlers.
|
||
|
//! \note The handlers may or may not be invoked with an active exception.
|
||
|
//! **Do not try to rethrow the exception with `throw;` from within your
|
||
|
//! handler -- that is, a throw expression with no operand.** This may
|
||
|
//! cause your process to terminate. (It is perfectly ok to throw from
|
||
|
//! a handler so long as you specify the exception to throw, as in
|
||
|
//! `throw e;`.)
|
||
|
template <class... CatchFns>
|
||
|
void handle(CatchFns... fns);
|
||
|
//! \overload
|
||
|
template <class... CatchFns>
|
||
|
void handle(CatchFns... fns) const;
|
||
|
};
|
||
|
|
||
|
template <class Ex>
|
||
|
constexpr exception_wrapper::VTable exception_wrapper::InPlace<Ex>::ops_;
|
||
|
|
||
|
/**
|
||
|
* \return An `exception_wrapper` that wraps an instance of type `Ex`
|
||
|
* that has been constructed with arguments `std::forward<As>(as)...`.
|
||
|
*/
|
||
|
template <class Ex, typename... As>
|
||
|
exception_wrapper make_exception_wrapper(As&&... as) {
|
||
|
return exception_wrapper{in_place_type<Ex>, std::forward<As>(as)...};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Inserts `ew.what()` into the ostream `sout`.
|
||
|
* \return `sout`
|
||
|
*/
|
||
|
template <class Ch>
|
||
|
std::basic_ostream<Ch>& operator<<(
|
||
|
std::basic_ostream<Ch>& sout,
|
||
|
exception_wrapper const& ew) {
|
||
|
return sout << ew.what();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Swaps the value of `a` with the value of `b`.
|
||
|
*/
|
||
|
inline void swap(exception_wrapper& a, exception_wrapper& b) noexcept {
|
||
|
a.swap(b);
|
||
|
}
|
||
|
|
||
|
// For consistency with exceptionStr() functions in ExceptionString.h
|
||
|
fbstring exceptionStr(exception_wrapper const& ew);
|
||
|
|
||
|
namespace detail {
|
||
|
template <typename F>
|
||
|
inline exception_wrapper try_and_catch_(F&& f) {
|
||
|
return (f(), exception_wrapper());
|
||
|
}
|
||
|
|
||
|
template <typename F, typename Ex, typename... Exs>
|
||
|
inline exception_wrapper try_and_catch_(F&& f) {
|
||
|
try {
|
||
|
return try_and_catch_<F, Exs...>(std::forward<F>(f));
|
||
|
} catch (Ex& ex) {
|
||
|
return exception_wrapper(std::current_exception(), ex);
|
||
|
}
|
||
|
}
|
||
|
} // namespace detail
|
||
|
|
||
|
//! `try_and_catch` is a simple replacement for `try {} catch(){}`` that allows
|
||
|
//! you to specify which derived exceptions you would like to catch and store in
|
||
|
//! an `exception_wrapper`.
|
||
|
//!
|
||
|
//! Because we cannot build an equivalent of `std::current_exception()`, we need
|
||
|
//! to catch every derived exception that we are interested in catching.
|
||
|
//!
|
||
|
//! Exceptions should be listed in the reverse order that you would write your
|
||
|
//! catch statements (that is, `std::exception&` should be first).
|
||
|
//!
|
||
|
//! \par Example Usage:
|
||
|
//! \code
|
||
|
//! // This catches my runtime_error and if I call throw_exception() on ew, it
|
||
|
//! // will throw a runtime_error
|
||
|
//! auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
|
||
|
//! if (badThingHappens()) {
|
||
|
//! throw std::runtime_error("ZOMG!");
|
||
|
//! }
|
||
|
//! });
|
||
|
//!
|
||
|
//! // This will catch the exception and if I call throw_exception() on ew, it
|
||
|
//! // will throw a std::exception
|
||
|
//! auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
|
||
|
//! if (badThingHappens()) {
|
||
|
//! throw std::exception();
|
||
|
//! }
|
||
|
//! });
|
||
|
//!
|
||
|
//! // This will not catch the exception and it will be thrown.
|
||
|
//! auto ew = folly::try_and_catch<std::runtime_error>([=]() {
|
||
|
//! if (badThingHappens()) {
|
||
|
//! throw std::exception();
|
||
|
//! }
|
||
|
//! });
|
||
|
//! \endcode
|
||
|
template <typename... Exceptions, typename F>
|
||
|
exception_wrapper try_and_catch(F&& fn) {
|
||
|
return detail::try_and_catch_<F, Exceptions...>(std::forward<F>(fn));
|
||
|
}
|
||
|
} // namespace folly
|
||
|
|
||
|
#include <folly/ExceptionWrapper-inl.h>
|
||
|
|
||
|
#undef FOLLY_REQUIRES
|
||
|
#undef FOLLY_REQUIRES_DEF
|
||
|
#ifdef __GNUC__
|
||
|
#pragma GCC diagnostic pop
|
||
|
#endif
|