Commit 19e3e9fe authored by Eric Niebler's avatar Eric Niebler Committed by Facebook Github Bot

non-throwing, non-allocating exception_wrapper

Summary:
The purpose of this reimplementation of `exception_wrapper` is threefold:

- Make `exception_wrapper` smaller. It goes from 48 bytes to 24.
- Give it `noexcept` ~~copy and~~ move
- Store small exception objects in an internal buffer; i.e., with zero allocations.

The ultimate goal is to change `folly::Try<T>` to a thin wrapper over `folly::Expected<T, exception_wrapper>`. (Currently, it stores the `exception_wrapper` on the heap.)

As part of this redesign, I:

- Remove `exception_wrapper::getCopied`. The user shouldn't care how the `exception_wrapper` stores the exception.
- Remove `exception_wrapper::operator==`. It was only used in 2 places in test code. The existing semantics (return true IFF two `exception_wrapper`s point to the //same// exception object) prevented the small-object optimization.
- Add new `handle()` API that behaves like cascading `catch` clauses. For instance:
```lang=c++
exception_wrapper ew = ...;
ew.handle(
    [&](const SomeException& e) { /*...*/ },
    [&](const AnotherException& e) { /*...*/ },
    [&](...) { /* catch all*/ }, // yes, lambda with ellipses works!
```
- Add a `type()` member for accessing the `typeid` of the wrapped exception, if it's known or can be determined with a `catch(std::exception&)`.

This table shows the percent improvement for the exception_wrapper_benchmark test:

| Test  | Percent improvement (gcc-5)  | Percent improvement (gcc-4)
| -----  | -----  | -----
| exception_wrapper_create_and_test  | 14.33%    | -6.50%
| exception_wrapper_create_and_test_concurrent | 11.91% | 20.15%
| exception_wrapper_create_and_throw | -0.82% | -0.25%
| exception_wrapper_create_and_cast | 15.02% | 14.31%
| exception_wrapper_create_and_throw_concurrent | 18.37% | 8.03%
| exception_wrapper_create_and_cast_concurrent | 28.18% | -10.77%

The percent win for gcc-5 is 15% on average. The non-throwing tests show a greater win since the cost of actually throwing an exception drowns out the other improvements. (One of the reasons to use `exception_wrapper` is to not need to throw in the first place.) On gcc-4, there is roughly no change since the gcc-4 standard exceptions (`std::runtime_error`, std::logic_error`) are non-conforming since they have throwing copy operations.

Reviewed By: yfeldblum

Differential Revision: D4385822

fbshipit-source-id: 63a8316c2923b29a79f8fa446126a8c37aa32989
parent c5b9338e
......@@ -126,3 +126,12 @@
#else
# define FOLLY_ALWAYS_INLINE inline
#endif
// attribute hidden
#if _MSC_VER
#define FOLLY_ATTR_VISIBILITY_HIDDEN
#elif defined(__clang__) || defined(__GNUC__)
#define FOLLY_ATTR_VISIBILITY_HIDDEN __attribute__((__visibility__("hidden")))
#else
#define FOLLY_ATTR_VISIBILITY_HIDDEN
#endif
/*
* Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
*
* Author: Eric Niebler <eniebler@fb.com>
*/
namespace folly {
template <class Fn>
struct exception_wrapper::arg_type2_ {};
template <class Ret, class Class, class Arg>
struct exception_wrapper::arg_type2_<Ret (Class::*)(Arg)> {
using type = Arg;
};
template <class Ret, class Class, class Arg>
struct exception_wrapper::arg_type2_<Ret (Class::*)(Arg) const> {
using type = Arg;
};
template <class Ret, class Class>
struct exception_wrapper::arg_type2_<Ret (Class::*)(...)> {
using type = AnyException;
};
template <class Ret, class Class>
struct exception_wrapper::arg_type2_<Ret (Class::*)(...) const> {
using type = AnyException;
};
template <class Fn, class>
struct exception_wrapper::arg_type_ {};
template <class Fn>
struct exception_wrapper::arg_type_<Fn, void_t<decltype(&Fn::operator())>>
: public arg_type2_<decltype(&Fn::operator())> {};
template <class Ret, class Arg>
struct exception_wrapper::arg_type_<Ret (*)(Arg)> {
using type = Arg;
};
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, class DEx>
inline exception_wrapper::Buffer::Buffer(in_place_t, Ex&& ex) {
::new (static_cast<void*>(&buff_)) DEx(std::forward<Ex>(ex));
}
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;
}
inline std::uintptr_t exception_wrapper::ExceptionPtr::as_int_(
std::exception const& e) {
return reinterpret_cast<std::uintptr_t>(&e);
}
inline std::uintptr_t exception_wrapper::ExceptionPtr::as_int_(AnyException e) {
return reinterpret_cast<std::uintptr_t>(e.typeinfo_) + 1;
}
inline bool exception_wrapper::ExceptionPtr::has_exception_() const {
return 0 == exception_or_type_ % 2;
}
inline std::exception const* exception_wrapper::ExceptionPtr::as_exception_()
const {
return reinterpret_cast<std::exception const*>(exception_or_type_);
}
inline std::type_info const* exception_wrapper::ExceptionPtr::as_type_() const {
return reinterpret_cast<std::type_info const*>(exception_or_type_ - 1);
}
inline void exception_wrapper::ExceptionPtr::copy_(
exception_wrapper const* from, exception_wrapper* to) {
::new (static_cast<void*>(&to->eptr_)) ExceptionPtr(from->eptr_);
}
inline void exception_wrapper::ExceptionPtr::move_(
exception_wrapper* from, exception_wrapper* to) {
::new (static_cast<void*>(&to->eptr_))
ExceptionPtr(std::move(from->eptr_));
delete_(from);
}
inline void exception_wrapper::ExceptionPtr::delete_(
exception_wrapper* that) {
that->eptr_.~ExceptionPtr();
that->vptr_ = &uninit_;
}
[[noreturn]] inline void exception_wrapper::ExceptionPtr::throw_(
exception_wrapper const* that) {
std::rethrow_exception(that->eptr_.ptr_);
}
inline std::type_info const* exception_wrapper::ExceptionPtr::type_(
exception_wrapper const* that) {
if (auto e = get_exception_(that)) {
return &typeid(*e);
}
return that->eptr_.as_type_();
}
inline std::exception const* exception_wrapper::ExceptionPtr::get_exception_(
exception_wrapper const* that) {
return that->eptr_.has_exception_() ? that->eptr_.as_exception_()
: nullptr;
}
inline exception_wrapper exception_wrapper::ExceptionPtr::get_exception_ptr_(
exception_wrapper const* that) {
return *that;
}
template <class Ex>
inline void exception_wrapper::InPlace<Ex>::copy_(
exception_wrapper const* from, exception_wrapper* to) {
::new (static_cast<void*>(std::addressof(to->buff_.as<Ex>())))
Ex(from->buff_.as<Ex>());
}
template <class Ex>
inline void exception_wrapper::InPlace<Ex>::move_(
exception_wrapper* from, exception_wrapper* to) {
::new (static_cast<void*>(std::addressof(to->buff_.as<Ex>())))
Ex(std::move(from->buff_.as<Ex>()));
delete_(from);
}
template <class Ex>
inline void exception_wrapper::InPlace<Ex>::delete_(
exception_wrapper* that) {
that->buff_.as<Ex>().~Ex();
that->vptr_ = &uninit_;
}
template <class Ex>
[[noreturn]] inline void exception_wrapper::InPlace<Ex>::throw_(
exception_wrapper const* that) {
throw that->buff_.as<Ex>(); // @nolint
}
template <class Ex>
inline std::type_info const* exception_wrapper::InPlace<Ex>::type_(
exception_wrapper const*) {
return &typeid(Ex);
}
template <class Ex>
inline std::exception const* exception_wrapper::InPlace<Ex>::get_exception_(
exception_wrapper const* that) {
return as_exception_or_null_(that->buff_.as<Ex>());
}
template <class Ex>
inline exception_wrapper exception_wrapper::InPlace<Ex>::get_exception_ptr_(
exception_wrapper const* that) {
try {
throw_(that);
} catch (Ex const& ex) {
return exception_wrapper{std::current_exception(), ex};
}
}
template <class Ex>
[[noreturn]] inline void
exception_wrapper::SharedPtr::Impl<Ex>::throw_() const {
throw ex_; // @nolint
}
template <class Ex>
inline std::exception const*
exception_wrapper::SharedPtr::Impl<Ex>::get_exception_() const noexcept {
return as_exception_or_null_(ex_);
}
template <class Ex>
inline exception_wrapper
exception_wrapper::SharedPtr::Impl<Ex>::get_exception_ptr_() const noexcept {
try {
throw_();
} catch (Ex& ex) {
return exception_wrapper{std::current_exception(), ex};
}
}
inline void exception_wrapper::SharedPtr::copy_(
exception_wrapper const* from, exception_wrapper* to) {
::new (static_cast<void*>(std::addressof(to->sptr_)))
SharedPtr(from->sptr_);
}
inline void exception_wrapper::SharedPtr::move_(
exception_wrapper* from, exception_wrapper* to) {
::new (static_cast<void*>(std::addressof(to->sptr_)))
SharedPtr(std::move(from->sptr_));
delete_(from);
}
inline void exception_wrapper::SharedPtr::delete_(
exception_wrapper* that) {
that->sptr_.~SharedPtr();
that->vptr_ = &uninit_;
}
[[noreturn]] inline void exception_wrapper::SharedPtr::throw_(
exception_wrapper const* that) {
that->sptr_.ptr_->throw_();
folly::assume_unreachable();
}
inline std::type_info const* exception_wrapper::SharedPtr::type_(
exception_wrapper const* that) {
return that->sptr_.ptr_->info_;
}
inline std::exception const* exception_wrapper::SharedPtr::get_exception_(
exception_wrapper const* that) {
return that->sptr_.ptr_->get_exception_();
}
inline exception_wrapper exception_wrapper::SharedPtr::get_exception_ptr_(
exception_wrapper const* that) {
return that->sptr_.ptr_->get_exception_ptr_();
}
template <class Ex, class DEx>
inline exception_wrapper::exception_wrapper(Ex&& ex, OnHeapTag)
: sptr_{std::make_shared<SharedPtr::Impl<DEx>>(std::forward<Ex>(ex))},
vptr_(&SharedPtr::ops_) {}
template <class Ex, class DEx>
inline exception_wrapper::exception_wrapper(Ex&& ex, InSituTag)
: buff_{in_place, std::forward<Ex>(ex)}, vptr_(&InPlace<DEx>::ops_) {}
inline exception_wrapper::exception_wrapper(exception_wrapper&& that) noexcept
: exception_wrapper{} {
(vptr_ = that.vptr_)->move_(&that, this); // Move into *this, won't throw
}
inline exception_wrapper::exception_wrapper(
exception_wrapper const& that) : exception_wrapper{} {
that.vptr_->copy_(&that, this); // could throw
vptr_ = that.vptr_;
}
// If `this == &that`, this move assignment operator leaves the object in a
// valid but unspecified state.
inline exception_wrapper& exception_wrapper::operator=(
exception_wrapper&& that) noexcept {
vptr_->delete_(this); // Free the current exception
(vptr_ = that.vptr_)->move_(&that, this); // Move into *this, won't throw
return *this;
}
inline exception_wrapper& exception_wrapper::operator=(
exception_wrapper const& that) {
exception_wrapper(that).swap(*this);
return *this;
}
inline exception_wrapper::~exception_wrapper() {
reset();
}
template <class Ex>
inline exception_wrapper::exception_wrapper(std::exception_ptr ptr, Ex& ex)
: eptr_{std::move(ptr), ExceptionPtr::as_int_(ex)},
vptr_(&ExceptionPtr::ops_) {
assert(eptr_.ptr_);
}
template <
class Ex,
class Ex_,
FOLLY_REQUIRES_DEF(
Conjunction<
exception_wrapper::IsStdException<Ex_>,
exception_wrapper::IsRegularExceptionType<Ex_>>())>
inline exception_wrapper::exception_wrapper(Ex&& ex)
: exception_wrapper{std::forward<Ex>(ex), PlacementOf<Ex_>{}} {
// Don't slice!!!
assert(typeid(ex) == typeid(Ex_) ||
!"Dynamic and static exception types don't match. Exception would "
"be sliced when storing in exception_wrapper.");
}
template <
class Ex,
class Ex_,
FOLLY_REQUIRES_DEF(
exception_wrapper::IsRegularExceptionType<Ex_>())>
inline exception_wrapper::exception_wrapper(in_place_t, Ex&& ex)
: exception_wrapper{std::forward<Ex>(ex), PlacementOf<Ex_>{}} {
// Don't slice!!!
assert(typeid(ex) == typeid(Ex_) ||
!"Dynamic and static exception types don't match. Exception would "
"be sliced when storing in exception_wrapper.");
}
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);
}
inline std::exception_ptr const& exception_wrapper::to_exception_ptr()
noexcept {
// Computing an exception_ptr is expensive so cache the result.
return (*this = vptr_->get_exception_ptr_(this)).eptr_.ptr_;
}
inline std::exception_ptr exception_wrapper::to_exception_ptr() const noexcept {
return vptr_->get_exception_ptr_(this).eptr_.ptr_;
}
inline std::type_info const& exception_wrapper::none() noexcept {
return typeid(void);
}
inline std::type_info const& exception_wrapper::unknown() noexcept {
return typeid(Unknown);
}
inline std::type_info const& exception_wrapper::type() const noexcept {
return *vptr_->type_(this);
}
inline folly::fbstring exception_wrapper::what() const {
if (auto e = get_exception()) {
return class_name() + ": " + e->what();
}
return class_name();
}
inline folly::fbstring exception_wrapper::class_name() const {
auto& ti = type();
return ti == none()
? ""
: ti == unknown() ? "<unknown exception>" : folly::demangle(ti);
}
template <class Ex>
inline bool exception_wrapper::is_compatible_with() const noexcept {
return with_exception([](Ex const&) {});
}
[[noreturn]] inline void exception_wrapper::throwException() const {
vptr_->throw_(this);
onNoExceptionError();
}
template <class CatchFn, bool IsConst>
struct exception_wrapper::ExceptionTypeOf {
using type = arg_type<_t<std::decay<CatchFn>>>;
static_assert(
std::is_reference<type>::value,
"Always catch exceptions by reference.");
static_assert(
!IsConst || std::is_const<_t<std::remove_reference<type>>>::value,
"handle() or with_exception() called on a const exception_wrapper "
"and asked to catch a non-const exception. Handler will never fire. "
"Catch exception by const reference to fix this.");
};
// Nests a throw in the proper try/catch blocks
template <bool IsConst>
struct exception_wrapper::HandleReduce {
bool* handled_;
template <
class ThrowFn,
class CatchFn,
FOLLY_REQUIRES(!IsCatchAll<CatchFn>::value)>
auto operator()(ThrowFn&& th, CatchFn& ca) const {
using Ex = _t<ExceptionTypeOf<CatchFn, IsConst>>;
return [ th = std::forward<ThrowFn>(th), &ca, handled_ = handled_ ] {
try {
th();
} catch (Ex& e) {
// If we got here because a catch function threw, rethrow.
if (*handled_) {
throw;
}
*handled_ = true;
ca(e);
}
};
}
template <
class ThrowFn,
class CatchFn,
FOLLY_REQUIRES(IsCatchAll<CatchFn>::value)>
auto operator()(ThrowFn&& th, CatchFn& ca) const {
return [ th = std::forward<ThrowFn>(th), &ca, handled_ = handled_ ] {
try {
th();
} catch (...) {
// If we got here because a catch function threw, rethrow.
if (*handled_) {
throw;
}
*handled_ = true;
ca();
}
};
}
};
// When all the handlers expect types derived from std::exception, we can
// sometimes invoke the handlers without throwing any exceptions.
template <bool IsConst>
struct exception_wrapper::HandleStdExceptReduce {
using StdEx = AddConstIf<IsConst, std::exception>;
template <
class ThrowFn,
class CatchFn,
FOLLY_REQUIRES(!IsCatchAll<CatchFn>::value)>
auto operator()(ThrowFn&& th, CatchFn& ca) const {
using Ex = _t<ExceptionTypeOf<CatchFn, IsConst>>;
return [ th = std::forward<ThrowFn>(th), &ca ](auto&& continuation)
-> StdEx* {
if (auto e = const_cast<StdEx*>(th(continuation))) {
if (auto e2 = dynamic_cast<_t<std::add_pointer<Ex>>>(e)) {
ca(*e2);
} else {
return e;
}
}
return nullptr;
};
}
template <
class ThrowFn,
class CatchFn,
FOLLY_REQUIRES(IsCatchAll<CatchFn>::value)>
auto operator()(ThrowFn&& th, CatchFn& ca) const {
return [ th = std::forward<ThrowFn>(th), &ca ](auto&&) -> StdEx* {
// The following continuation causes ca() to execute if *this contains
// an exception /not/ derived from std::exception.
auto continuation = [&ca](StdEx* e) {
return e != nullptr ? e : ((void)ca(), nullptr);
};
if (th(continuation) != nullptr) {
ca();
}
return nullptr;
};
}
};
// Called when some types in the catch clauses are not derived from
// std::exception.
template <class This, class... CatchFns>
inline void exception_wrapper::handle_(
std::false_type, This& this_, CatchFns&... fns) {
bool handled = false;
auto impl = exception_wrapper_detail::fold(
HandleReduce<std::is_const<This>::value>{&handled},
[&] { this_.throwException(); },
fns...);
impl();
}
// Called when all types in the catch clauses are either derived from
// std::exception or a catch-all clause.
template <class This, class... CatchFns>
inline void exception_wrapper::handle_(
std::true_type, This& this_, CatchFns&... fns) {
using StdEx = exception_wrapper_detail::
AddConstIf<std::is_const<This>::value, std::exception>;
auto impl = exception_wrapper_detail::fold(
HandleStdExceptReduce<std::is_const<This>::value>{},
[&](auto&& continuation) {
return continuation(
const_cast<StdEx*>(this_.vptr_->get_exception_(&this_)));
},
fns...);
// This continuation gets evaluated if CatchFns... does not include a
// catch-all handler. It is a no-op.
auto continuation = [](StdEx* ex) { return ex; };
if (StdEx* e = impl(continuation)) {
throw *e; // Not handled. Throw.
}
}
namespace exception_wrapper_detail {
template <class Ex, class Fn>
struct catch_fn {
Fn fn_;
auto operator()(Ex& ex) {
return fn_(ex);
}
};
template <class Ex, class Fn>
inline catch_fn<Ex, Fn> catch_(Ex*, Fn fn) {
return {std::move(fn)};
}
template <class Fn>
inline Fn catch_(void const*, Fn fn) {
return fn;
}
} // namespace exception_wrapper_detail
template <class Ex, class This, class Fn>
inline bool exception_wrapper::with_exception_(This& this_, Fn fn_) {
if (!this_) {
return false;
}
bool handled = true;
auto fn = exception_wrapper_detail::catch_(
static_cast<Ex*>(nullptr), std::move(fn_));
auto&& all = [&](...) { handled = false; };
handle_(IsStdException<arg_type<decltype(fn)>>{}, this_, fn, all);
return handled;
}
template <class Ex, class Fn>
inline bool exception_wrapper::with_exception(Fn fn) {
return with_exception_<Ex>(*this, std::move(fn));
}
template <class Ex, class Fn>
inline bool exception_wrapper::with_exception(Fn fn) const {
return with_exception_<Ex const>(*this, std::move(fn));
}
template <class... CatchFns>
inline void exception_wrapper::handle(CatchFns... fns) {
using AllStdEx =
exception_wrapper_detail::AllOf<IsStdException, arg_type<CatchFns>...>;
if (!*this) {
onNoExceptionError();
}
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();
}
this->handle_(AllStdEx{}, *this, fns...);
}
} // namespace folly
......@@ -15,50 +15,53 @@
*/
#include <folly/ExceptionWrapper.h>
#include <exception>
#include <iostream>
#include <folly/Logging.h>
namespace folly {
[[noreturn]] void exception_wrapper::throwException() const {
if (throwfn_) {
throwfn_(*item_);
} else if (eptr_) {
std::rethrow_exception(eptr_);
constexpr exception_wrapper::VTable const exception_wrapper::uninit_;
constexpr exception_wrapper::VTable const exception_wrapper::ExceptionPtr::ops_;
constexpr exception_wrapper::VTable const exception_wrapper::SharedPtr::ops_;
namespace {
std::exception const* get_std_exception_(std::exception_ptr eptr) noexcept {
try {
std::rethrow_exception(eptr);
} catch (const std::exception& ex) {
return &ex;
} catch (...) {
return nullptr;
}
std::ios_base::Init ioinit_; // ensure std::cerr is alive
std::cerr
<< "Cannot use `throwException` with an empty folly::exception_wrapper"
<< std::endl;
std::terminate();
}
}
fbstring exception_wrapper::class_name() const {
if (item_) {
auto& i = *item_;
return demangle(typeid(i));
} else if (eptr_ && eobj_) {
return demangle(typeid(*eobj_));
} else if (eptr_ && etype_) {
return demangle(*etype_);
exception_wrapper::exception_wrapper(std::exception_ptr ptr) noexcept
: exception_wrapper{} {
if (ptr) {
if (auto e = get_std_exception_(ptr)) {
LOG(DFATAL)
<< "Performance error: Please construct exception_wrapper with a "
"reference to the std::exception along with the "
"std::exception_ptr.";
*this = exception_wrapper{std::move(ptr), *e};
} else {
return fbstring();
Unknown uk;
*this = exception_wrapper{ptr, uk};
}
}
}
fbstring exception_wrapper::what() const {
if (item_) {
return exceptionStr(*item_);
} else if (eptr_ && eobj_) {
return class_name() + ": " + eobj_->what();
} else if (eptr_ && etype_) {
return class_name();
} else {
return class_name();
}
[[noreturn]] void exception_wrapper::onNoExceptionError() {
std::ios_base::Init ioinit_; // ensure std::cerr is alive
std::cerr
<< "Cannot use `throwException` with an empty folly::exception_wrapper"
<< std::endl;
std::terminate();
}
fbstring exceptionStr(const exception_wrapper& ew) {
fbstring exceptionStr(exception_wrapper const& ew) {
return ew.what();
}
......
/*
* Copyright 2017 Facebook, Inc.
* Copyright 2017-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -13,416 +13,681 @@
* 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 <string>
#include <tuple>
#include <new>
#include <type_traits>
#include <typeinfo>
#include <utility>
#include <folly/Assume.h>
#include <folly/CPortability.h>
#include <folly/Demangle.h>
#include <folly/ExceptionString.h>
#include <folly/FBString.h>
#include <folly/Traits.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.
#pragma GCC diagnostic ignored "-Wshadow-local"
#pragma GCC diagnostic ignored "-Wshadow-compatible-local"
#endif
#define FOLLY_EXCEPTION_WRAPPER_H_INCLUDED
namespace folly {
/*
* 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.throwException();
* } catch (const FacePlantException& e) {
* LOG(ERROR) << "FACEPLANT!";
* } catch (const FailWhaleException& e) {
* LOG(ERROR) << "FAILWHALE!";
* }
* }
*
* // Thread2: Exceptions are bad!
* void processResult() {
* globalExceptionWrapper.with_exception(
* [&](FacePlantException& faceplant) {
* LOG(ERROR) << "FACEPLANT";
* }) ||
* globalExceptionWrapper.with_exception(
* [&](FailWhaleException& failwhale) {
* LOG(ERROR) << "FAILWHALE!";
* }) ||
* LOG(FATAL) << "Unrecognized exception";
* }
* \endcode
*
*/
class exception_wrapper {
private:
template <typename T>
using is_exception_ = std::is_base_of<std::exception, T>;
#define FOLLY_REQUIRES_DEF(...) \
_t<std::enable_if<static_cast<bool>(__VA_ARGS__), long>>
public:
exception_wrapper() = default;
#define FOLLY_REQUIRES(...) FOLLY_REQUIRES_DEF(__VA_ARGS__) = __LINE__
template <
typename Ex,
typename DEx = _t<std::decay<Ex>>,
typename = _t<std::enable_if<is_exception_<DEx>::value>>,
typename = decltype(DEx(std::forward<Ex>(std::declval<Ex&&>())))>
/* implicit */ exception_wrapper(Ex&& exn) {
assign_sptr<DEx>(std::forward<Ex>(exn));
}
namespace exception_wrapper_detail {
// The following two constructors are meant to emulate the behavior of
// try_and_catch in performance sensitive code as well as to be flexible
// enough to wrap exceptions of unknown type. There is an overload that
// takes an exception reference so that the wrapper can extract and store
// the exception's type and what() when possible.
//
// The canonical use case is to construct an all-catching exception wrapper
// with minimal overhead like so:
//
// try {
// // some throwing code
// } catch (const std::exception& e) {
// // won't lose e's type and what()
// exception_wrapper ew{std::current_exception(), e};
// } catch (...) {
// // everything else
// exception_wrapper ew{std::current_exception()};
// }
//
// try_and_catch is cleaner and preferable. Use it unless you're sure you need
// something like this instead.
template <typename Ex>
explicit exception_wrapper(std::exception_ptr eptr, Ex& exn) {
assign_eptr(eptr, exn);
}
template <template <class> class T, class... As>
using AllOf = StrictConjunction<T<As>...>;
explicit exception_wrapper(std::exception_ptr eptr) {
assign_eptr(eptr);
}
template <bool If, class T>
using AddConstIf = _t<std::conditional<If, const T, T>>;
// If the exception_wrapper does not contain an exception, std::terminate()
// is invoked to assure the [[noreturn]] behaviour.
[[noreturn]] void throwException() const;
template <class Fn, class A>
FOLLY_ALWAYS_INLINE FOLLY_ATTR_VISIBILITY_HIDDEN
auto fold(Fn&&, A&& a) {
return static_cast<A&&>(a);
}
explicit operator bool() const {
return item_ || eptr_;
}
template <class Fn, class A, class B, class... Bs>
FOLLY_ALWAYS_INLINE FOLLY_ATTR_VISIBILITY_HIDDEN
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)...);
}
// This implementation is similar to std::exception_ptr's implementation
// where two exception_wrappers are equal when the address in the underlying
// reference field both point to the same exception object. The reference
// field remains the same when the exception_wrapper is copied or when
// the exception_wrapper is "rethrown".
bool operator==(const exception_wrapper& a) const {
if (item_) {
return a.item_ && item_.get() == a.item_.get();
} else {
return eptr_ == a.eptr_;
}
}
} // 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.throwException();
//! } 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 AnyException : std::exception {
std::type_info const* typeinfo_;
template <class T>
/* implicit */ AnyException(T&& t) noexcept : typeinfo_(&typeid(t)) {}
};
bool operator!=(const exception_wrapper& a) const {
return !(*this == a);
}
template <class Fn>
struct arg_type2_;
template <class Fn, class = void>
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();
template <class Ret, class... Args>
static Ret noop_(Args...);
// This will return a non-nullptr only if the exception is held as a
// copy. It is the only interface which will distinguish between an
// exception held this way, and by exception_ptr. You probably
// shouldn't use it at all.
std::exception* getCopied() { return item_.get(); }
const std::exception* getCopied() const { return item_.get(); }
static std::type_info const* uninit_type_(exception_wrapper const*);
fbstring what() const;
fbstring class_name() const;
static constexpr VTable const uninit_{
&noop_<void, exception_wrapper const*, exception_wrapper*>,
&noop_<void, exception_wrapper*, exception_wrapper*>,
&noop_<void, exception_wrapper*>,
&noop_<void, exception_wrapper const*>,
&uninit_type_,
&noop_<std::exception const*, exception_wrapper const*>,
&noop_<exception_wrapper, exception_wrapper const*>};
template <class Ex>
bool is_compatible_with() const {
return with_exception<Ex>([](const Ex&) {});
}
using IsStdException = std::is_base_of<std::exception, _t<std::decay<Ex>>>;
template <bool B, class T>
using AddConstIf = exception_wrapper_detail::AddConstIf<B, T>;
template <class CatchFn>
using IsCatchAll =
std::is_same<arg_type<_t<std::decay<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 =
_t<std::aligned_storage<2 * sizeof(void*), alignof(std::exception)>>;
Storage buff_;
Buffer() : buff_{} {}
template <class Ex, class DEx = _t<std::decay<Ex>>>
Buffer(in_place_t, Ex&& ex);
template <class Ex>
Ex& as() noexcept;
template <class Ex>
Ex const& as() const noexcept;
};
template <class F>
bool with_exception(F&& f) {
using arg_type = _t<std::decay<typename functor_traits<F>::arg_type>>;
return with_exception<arg_type>(std::forward<F>(f));
}
enum class Placement { kInSitu, kOnHeap };
template <class T>
using PlacementOf = std::integral_constant<
Placement,
sizeof(T) <= sizeof(Buffer::Storage) &&
alignof(T) <= alignof(Buffer::Storage) &&
noexcept(T(std::declval<T&&>()))
? Placement::kInSitu
: Placement::kOnHeap>;
using InSituTag = std::integral_constant<Placement, Placement::kInSitu>;
using OnHeapTag = std::integral_constant<Placement, Placement::kOnHeap>;
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 const& e);
static std::uintptr_t as_int_(AnyException e);
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 constexpr VTable const ops_{copy_,
move_,
delete_,
throw_,
type_,
get_exception_,
get_exception_ptr_};
};
template <class F>
bool with_exception(F&& f) const {
using arg_type = _t<std::decay<typename functor_traits<F>::arg_type>>;
return with_exception<arg_type>(std::forward<F>(f));
}
template <class Ex>
struct InPlace {
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_};
};
// If this exception wrapper wraps an exception of type Ex, with_exception
// will call f with the wrapped exception as an argument and return true, and
// will otherwise return false.
template <class Ex, class F>
bool with_exception(F f) {
return with_exception1<_t<std::decay<Ex>>>(std::forward<F>(f), this);
}
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 {
Ex ex_;
Impl() = default;
explicit Impl(Ex const& ex) : Base{typeid(ex)}, ex_(ex) {}
explicit Impl(Ex&& ex)
: Base{typeid(ex)},
ex_(std::move(ex)){}[[noreturn]] void throw_() const override;
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 constexpr VTable ops_{copy_,
move_,
delete_,
throw_,
type_,
get_exception_,
get_exception_ptr_};
};
// Const overload
template <class Ex, class F>
bool with_exception(F f) const {
return with_exception1<_t<std::decay<Ex>>>(std::forward<F>(f), this);
}
union {
Buffer buff_{};
ExceptionPtr eptr_;
SharedPtr sptr_;
};
VTable const* vptr_{&uninit_};
std::exception_ptr to_exception_ptr() const {
if (eptr_) {
return eptr_;
}
template <class Ex, class DEx = _t<std::decay<Ex>>>
exception_wrapper(Ex&& ex, OnHeapTag);
try {
if (*this) {
throwException();
}
} catch (...) {
return std::current_exception();
}
return std::exception_ptr();
}
template <class Ex, class DEx = _t<std::decay<Ex>>>
exception_wrapper(Ex&& ex, InSituTag);
private:
template <typename Ex, typename... Args>
void assign_sptr(Args&&... args) {
this->item_ = std::make_shared<Ex>(std::forward<Args>(args)...);
this->throwfn_ = Thrower<Ex>::doThrow;
}
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 <typename Ex>
_t<std::enable_if<is_exception_<Ex>::value>> assign_eptr(
std::exception_ptr eptr,
Ex& e) {
this->eptr_ = eptr;
this->eobj_ = &const_cast<_t<std::remove_const<Ex>>&>(e);
}
template <class CatchFn, bool IsConst = false>
struct ExceptionTypeOf;
template <typename Ex>
_t<std::enable_if<!is_exception_<Ex>::value>> assign_eptr(
std::exception_ptr eptr,
Ex& e) {
this->eptr_ = eptr;
this->etype_ = &typeid(e);
}
template <bool IsConst>
struct HandleReduce;
void assign_eptr(std::exception_ptr eptr) {
this->eptr_ = eptr;
}
template <bool IsConst>
struct HandleStdExceptReduce;
// Optimized case: if we know what type the exception is, we can
// store a copy of the concrete type, and a helper function so we
// can rethrow it.
std::shared_ptr<std::exception> item_;
void (*throwfn_)(std::exception&){nullptr};
// Fallback case: store the library wrapper, which is less efficient
// but gets the job done. Also store exceptionPtr() the name of the
// exception type, so we can at least get those back out without
// having to rethrow.
std::exception_ptr eptr_;
std::exception* eobj_{nullptr};
const std::type_info* etype_{nullptr};
template <class T, class... Args>
friend exception_wrapper make_exception_wrapper(Args&&... args);
template <class This, class... CatchFns>
static void handle_(std::false_type, This& this_, CatchFns&... fns);
private:
template <typename F>
struct functor_traits {
template <typename T>
struct impl;
template <typename C, typename R, typename A>
struct impl<R(C::*)(A)> { using arg_type = A; };
template <typename C, typename R, typename A>
struct impl<R(C::*)(A) const> { using arg_type = A; };
using functor_op = decltype(&_t<std::decay<F>>::operator());
using arg_type = typename impl<functor_op>::arg_type;
};
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_);
template <class T>
class Thrower {
public:
static void doThrow(std::exception& obj) {
throw static_cast<T&>(obj);
}
};
//! 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);
//! 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);
~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);
//! \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_ = _t<std::decay<Ex>>,
FOLLY_REQUIRES(
Conjunction<IsStdException<Ex_>, IsRegularExceptionType<Ex_>>())>
/* 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_ = _t<std::decay<Ex>>,
FOLLY_REQUIRES(IsRegularExceptionType<Ex_>())>
exception_wrapper(in_place_t, Ex&& ex);
//! Swaps the value of `*this` with the value of `that`
void swap(exception_wrapper& that) noexcept;
//! \return `true` if `*this` is not 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;
//! \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 const& 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;
template <typename T, typename F>
static _t<std::enable_if<is_exception_<T>::value, T*>>
try_dynamic_cast_exception(F* from) {
return dynamic_cast<T*>(from);
}
template <typename T, typename F>
static _t<std::enable_if<!is_exception_<T>::value, T*>>
try_dynamic_cast_exception(F*) {
return nullptr;
}
//! \pre `bool(*this)`
//! Throws the wrapped expression.
[[noreturn]] void throwException() const;
// What makes this useful is that T can be exception_wrapper* or
// const exception_wrapper*, and the compiler will use the
// instantiation which works with F.
template <class Ex, class F, class T>
static bool with_exception1(F f, T* that) {
using CEx = _t<std::conditional<std::is_const<T>::value, const Ex, Ex>>;
if (is_exception_<Ex>::value &&
(that->item_ || (that->eptr_ && that->eobj_))) {
auto raw =
that->item_ ? that->item_.get() : that->eptr_ ? that->eobj_ : nullptr;
if (auto ex = try_dynamic_cast_exception<CEx>(raw)) {
f(*ex);
return true;
}
} else if (that->eptr_) {
try {
std::rethrow_exception(that->eptr_);
} catch (CEx& e) {
f(e);
return true;
} catch (...) {
// fall through
}
}
return false;
}
//! 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.throwException(); // 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, class... Args>
exception_wrapper make_exception_wrapper(Args&&... args) {
exception_wrapper ew;
ew.assign_sptr<Ex>(std::forward<Args>(args)...);
return ew;
}
template <class Ex>
constexpr exception_wrapper::VTable exception_wrapper::InPlace<Ex>::ops_;
// For consistency with exceptionStr() functions in ExceptionString.h
fbstring exceptionStr(const exception_wrapper& ew);
/*
* 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).
*
* NOTE: Although implemented as a derived class (for syntactic delight), don't
* be confused - you should not pass around try_and_catch objects!
*
* Example Usage:
*
* // This catches my runtime_error and if I call throwException() 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 throwException() 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();
* }
* });
/**
* \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{Ex{std::forward<As>(as)...}};
}
namespace try_and_catch_detail {
template <typename... Args>
using is_wrap_ctor = std::is_constructible<exception_wrapper, Args...>;
template <typename Ex>
inline _t<std::enable_if<!is_wrap_ctor<Ex&>::value, exception_wrapper>> make(
Ex& ex) {
return exception_wrapper(std::current_exception(), ex);
/**
* 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();
}
template <typename Ex>
inline _t<std::enable_if<is_wrap_ctor<Ex&>::value, exception_wrapper>> make(
Ex& ex) {
return typeid(Ex&) == typeid(ex)
? exception_wrapper(ex)
: exception_wrapper(std::current_exception(), ex);
/**
* 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 impl(F&& f) {
inline exception_wrapper try_and_catch_(F&& f) {
return (f(), exception_wrapper());
}
template <typename F, typename Ex, typename... Exs>
inline exception_wrapper impl(F&& f) {
inline exception_wrapper try_and_catch_(F&& f) {
try {
return impl<F, Exs...>(std::forward<F>(f));
return try_and_catch_<F, Exs...>(std::forward<F>(f));
} catch (Ex& ex) {
return make(ex);
return exception_wrapper(std::current_exception(), ex);
}
}
} // try_and_catch_detail
} // 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 throwException() 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 throwException() 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 try_and_catch_detail::impl<F, Exceptions...>(std::forward<F>(fn));
return detail::try_and_catch_<F, Exceptions...>(std::forward<F>(fn));
}
} // folly
#include <folly/ExceptionWrapper-inl.h>
#undef FOLLY_REQUIRES
#undef FOLLY_REQUIRES_DEF
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
......@@ -93,6 +93,7 @@ nobase_follyinclude_HEADERS = \
Exception.h \
ExceptionString.h \
ExceptionWrapper.h \
ExceptionWrapper-inl.h \
Executor.h \
Expected.h \
experimental/AsymmetricMemoryBarrier.h \
......
......@@ -126,8 +126,8 @@ TEST(FutureSplitter, splitFutureFailure) {
EXPECT_FALSE(f1.isReady());
try {
throw std::runtime_error("Oops");
} catch (...) {
p.setException(exception_wrapper(std::current_exception()));
} catch (std::exception& e) {
p.setException(exception_wrapper(std::current_exception(), e));
}
EXPECT_TRUE(f1.isReady());
EXPECT_TRUE(f1.hasException());
......
......@@ -50,6 +50,17 @@ const static std::string kIntExceptionClassName =
demangle(typeid(IntException)).toStdString();
const static std::string kIntClassName = demangle(typeid(int)).toStdString();
template <typename T>
T& from_eptr(std::exception_ptr& eptr) {
try {
std::rethrow_exception(eptr);
} catch (T& e) {
return e;
} catch (...) {
throw std::logic_error("impossible");
}
}
// Tests that when we call throwException, the proper type is thrown (derived)
TEST(ExceptionWrapper, throw_test) {
std::runtime_error e("payload");
......@@ -78,41 +89,6 @@ TEST(ExceptionWrapper, members) {
EXPECT_EQ(ew.class_name(), kRuntimeErrorClassName);
}
TEST(ExceptionWrapper, equals) {
std::runtime_error e("payload");
auto ew1 = make_exception_wrapper<std::runtime_error>(e);
auto ew2 = ew1;
EXPECT_EQ(ew1, ew2);
auto ew3 = try_and_catch<std::exception>([&]() {
throw std::runtime_error("payload");
});
auto ew4 = try_and_catch<std::exception>([&]() {
ew3.throwException();
});
EXPECT_EQ(ew3, ew4);
}
TEST(ExceptionWrapper, not_equals) {
std::runtime_error e1("payload");
std::runtime_error e2("payload");
auto ew1 = make_exception_wrapper<std::runtime_error>(e1);
auto ew2 = make_exception_wrapper<std::runtime_error>(e2);
EXPECT_NE(ew1, ew2);
auto ew3 = make_exception_wrapper<std::runtime_error>(e1);
auto ew4 = make_exception_wrapper<std::runtime_error>(e1);
EXPECT_NE(ew3, ew4);
auto ew5 = try_and_catch<std::exception>([&]() {
throw e1;
});
auto ew6 = try_and_catch<std::exception>([&]() {
throw e1;
});
EXPECT_NE(ew5, ew6);
}
TEST(ExceptionWrapper, try_and_catch_test) {
std::string expected = "payload";
......@@ -122,7 +98,6 @@ TEST(ExceptionWrapper, try_and_catch_test) {
throw std::runtime_error(expected);
});
EXPECT_TRUE(bool(ew));
EXPECT_TRUE(ew.getCopied());
EXPECT_EQ(ew.what(), kRuntimeErrorClassName + ": payload");
EXPECT_EQ(ew.class_name(), kRuntimeErrorClassName);
auto rep = ew.is_compatible_with<std::runtime_error>();
......@@ -135,7 +110,6 @@ TEST(ExceptionWrapper, try_and_catch_test) {
});
EXPECT_TRUE(bool(ew2));
// We are catching a std::exception, not std::runtime_error.
EXPECT_FALSE(ew2.getCopied());
// But, we can still get the actual type if we want it.
rep = ew2.is_compatible_with<std::runtime_error>();
EXPECT_TRUE(rep);
......@@ -181,10 +155,11 @@ TEST(ExceptionWrapper, with_exception_test) {
EXPECT_TRUE(bool(ew2));
EXPECT_EQ(ew2.what(), kIntExceptionClassName + ": int == 23");
EXPECT_EQ(ew2.class_name(), kIntExceptionClassName);
EXPECT_TRUE(ew2.with_exception([&](AbstractIntException& ie) {
bool res = ew2.with_exception([&](AbstractIntException& ie) {
EXPECT_EQ(ie.getInt(), expected);
EXPECT_TRUE(dynamic_cast<IntException*>(&ie));
}));
});
EXPECT_TRUE(res);
// Test with const this. If this compiles and does not crash due to
// infinite loop when it runs, it succeeds.
......@@ -235,6 +210,156 @@ TEST(ExceptionWrapper, get_or_make_exception_ptr_test) {
EXPECT_FALSE(eptr);
}
TEST(ExceptionWrapper, with_exception_ptr_empty) {
auto ew = exception_wrapper(std::exception_ptr());
EXPECT_EQ(exception_wrapper::none(), ew.type());
EXPECT_FALSE(bool(ew));
EXPECT_EQ(nullptr, ew.get_exception());
EXPECT_FALSE(ew.has_exception_ptr());
EXPECT_EQ(nullptr, ew.to_exception_ptr());
EXPECT_FALSE(ew.has_exception_ptr());
EXPECT_EQ("", ew.class_name());
EXPECT_EQ("", ew.what());
EXPECT_FALSE(ew.is_compatible_with<std::exception>());
EXPECT_FALSE(ew.is_compatible_with<int>());
EXPECT_DEATH(ew.throwException(), "empty folly::exception_wrapper");
}
TEST(ExceptionWrapper, with_shared_ptr_test) {
auto ew = exception_wrapper(std::runtime_error("foo"));
EXPECT_TRUE(bool(ew));
EXPECT_EQ(typeid(std::runtime_error), ew.type());
EXPECT_NE(nullptr, ew.get_exception());
EXPECT_FALSE(ew.has_exception_ptr());
EXPECT_NE(nullptr, ew.to_exception_ptr());
EXPECT_TRUE(ew.has_exception_ptr());
EXPECT_EQ("std::runtime_error", ew.class_name());
EXPECT_EQ("std::runtime_error: foo", ew.what());
EXPECT_TRUE(ew.is_compatible_with<std::exception>());
EXPECT_TRUE(ew.is_compatible_with<std::runtime_error>());
EXPECT_FALSE(ew.is_compatible_with<int>());
EXPECT_THROW(ew.throwException(), std::runtime_error);
exception_wrapper(std::move(ew));
EXPECT_FALSE(bool(ew));
EXPECT_EQ(exception_wrapper::none(), ew.type());
EXPECT_EQ(nullptr, ew.get_exception());
EXPECT_EQ(nullptr, ew.to_exception_ptr());
EXPECT_EQ("", ew.class_name());
EXPECT_EQ("", ew.what());
EXPECT_FALSE(ew.is_compatible_with<std::exception>());
EXPECT_FALSE(ew.is_compatible_with<std::runtime_error>());
EXPECT_FALSE(ew.is_compatible_with<int>());
}
TEST(ExceptionWrapper, with_exception_ptr_exn_test) {
auto ep = std::make_exception_ptr(std::runtime_error("foo"));
auto ew = exception_wrapper(ep, from_eptr<std::runtime_error>(ep));
EXPECT_TRUE(bool(ew));
EXPECT_EQ(typeid(std::runtime_error), ew.type());
EXPECT_NE(nullptr, ew.get_exception());
EXPECT_TRUE(ew.has_exception_ptr());
EXPECT_EQ(ep, ew.to_exception_ptr());
EXPECT_TRUE(ew.has_exception_ptr());
EXPECT_EQ("std::runtime_error", ew.class_name());
EXPECT_EQ("std::runtime_error: foo", ew.what());
EXPECT_TRUE(ew.is_compatible_with<std::exception>());
EXPECT_TRUE(ew.is_compatible_with<std::runtime_error>());
EXPECT_FALSE(ew.is_compatible_with<int>());
EXPECT_THROW(ew.throwException(), std::runtime_error);
exception_wrapper(std::move(ew));
EXPECT_FALSE(bool(ew));
EXPECT_EQ(exception_wrapper::none(), ew.type());
EXPECT_EQ(nullptr, ew.get_exception());
EXPECT_EQ(nullptr, ew.to_exception_ptr());
EXPECT_EQ("", ew.class_name());
EXPECT_EQ("", ew.what());
EXPECT_FALSE(ew.is_compatible_with<std::exception>());
EXPECT_FALSE(ew.is_compatible_with<std::runtime_error>());
EXPECT_FALSE(ew.is_compatible_with<int>());
}
TEST(ExceptionWrapper, with_exception_ptr_any_test) {
auto ep = std::make_exception_ptr<int>(12);
auto ew = exception_wrapper(ep, from_eptr<int>(ep));
EXPECT_TRUE(bool(ew));
EXPECT_EQ(nullptr, ew.get_exception());
EXPECT_TRUE(ew.has_exception_ptr());
EXPECT_EQ(ep, ew.to_exception_ptr());
EXPECT_TRUE(ew.has_exception_ptr());
EXPECT_EQ("int", ew.class_name());
EXPECT_EQ("int", ew.what());
EXPECT_FALSE(ew.is_compatible_with<std::exception>());
EXPECT_FALSE(ew.is_compatible_with<std::runtime_error>());
EXPECT_TRUE(ew.is_compatible_with<int>());
EXPECT_THROW(ew.throwException(), int);
exception_wrapper(std::move(ew));
EXPECT_FALSE(bool(ew));
EXPECT_EQ(nullptr, ew.get_exception());
EXPECT_EQ(nullptr, ew.to_exception_ptr());
EXPECT_FALSE(ew.has_exception_ptr());
EXPECT_EQ("", ew.class_name());
EXPECT_EQ("", ew.what());
EXPECT_FALSE(ew.is_compatible_with<std::exception>());
EXPECT_FALSE(ew.is_compatible_with<std::runtime_error>());
EXPECT_FALSE(ew.is_compatible_with<int>());
}
TEST(ExceptionWrapper, with_non_std_exception_test) {
auto ew = exception_wrapper(folly::in_place, 42);
EXPECT_TRUE(bool(ew));
EXPECT_EQ(nullptr, ew.get_exception());
EXPECT_FALSE(ew.has_exception_ptr());
EXPECT_EQ("int", ew.class_name());
EXPECT_EQ("int", ew.what());
EXPECT_NE(nullptr, ew.to_exception_ptr());
EXPECT_TRUE(ew.has_exception_ptr());
EXPECT_EQ("int", ew.class_name());
EXPECT_EQ("int", ew.what());
EXPECT_FALSE(ew.is_compatible_with<std::exception>());
EXPECT_FALSE(ew.is_compatible_with<std::runtime_error>());
EXPECT_TRUE(ew.is_compatible_with<int>());
EXPECT_THROW(ew.throwException(), int);
exception_wrapper(std::move(ew));
EXPECT_FALSE(bool(ew));
EXPECT_EQ(nullptr, ew.get_exception());
EXPECT_EQ(nullptr, ew.to_exception_ptr());
EXPECT_FALSE(ew.has_exception_ptr());
EXPECT_EQ("", ew.class_name());
EXPECT_EQ("", ew.what());
EXPECT_FALSE(ew.is_compatible_with<std::exception>());
EXPECT_FALSE(ew.is_compatible_with<std::runtime_error>());
EXPECT_FALSE(ew.is_compatible_with<int>());
}
TEST(ExceptionWrapper, with_exception_ptr_any_nil_test) {
auto ep = std::make_exception_ptr<int>(12);
auto ew = exception_wrapper(ep); // concrete type is erased
EXPECT_TRUE(bool(ew));
EXPECT_EQ(nullptr, ew.get_exception());
EXPECT_EQ(ep, ew.to_exception_ptr());
EXPECT_EQ("<unknown exception>", ew.class_name()); // because concrete type is
// erased
EXPECT_EQ("<unknown exception>", ew.what());
EXPECT_FALSE(ew.is_compatible_with<std::exception>());
EXPECT_FALSE(ew.is_compatible_with<std::runtime_error>());
EXPECT_TRUE(ew.is_compatible_with<int>());
EXPECT_THROW(ew.throwException(), int);
exception_wrapper(std::move(ew));
EXPECT_FALSE(bool(ew));
EXPECT_EQ(nullptr, ew.get_exception());
EXPECT_EQ(nullptr, ew.to_exception_ptr());
EXPECT_EQ("", ew.class_name());
EXPECT_EQ("", ew.what());
EXPECT_FALSE(ew.is_compatible_with<std::exception>());
EXPECT_FALSE(ew.is_compatible_with<std::runtime_error>());
EXPECT_FALSE(ew.is_compatible_with<int>());
}
TEST(ExceptionWrapper, with_exception_deduction) {
auto ew = make_exception_wrapper<std::runtime_error>("hi");
EXPECT_TRUE(ew.with_exception([](std::runtime_error&) {}));
......@@ -320,3 +445,283 @@ TEST(ExceptionWrapper, implicitConstruction) {
testEW(e);
testEW(TestException());
}
namespace {
struct BaseException {
virtual ~BaseException() {}
};
struct DerivedException : BaseException {};
exception_wrapper testNonStdException() {
try {
throw DerivedException{};
} catch (const BaseException& e) {
return exception_wrapper{std::current_exception(), e};
}
}
}
TEST(ExceptionWrapper, base_derived_non_std_exception_test) {
auto ew = testNonStdException();
EXPECT_TRUE(ew.type() == typeid(DerivedException));
EXPECT_TRUE(ew.with_exception([](const DerivedException&) {}));
}
namespace {
// Cannot be stored within an exception_wrapper
struct BigRuntimeError : std::runtime_error {
using std::runtime_error::runtime_error;
char data_[sizeof(exception_wrapper) + 1]{};
};
struct BigNonStdError {
char data_[sizeof(exception_wrapper) + 1]{};
};
}
TEST(ExceptionWrapper, handle_std_exception) {
auto ep = std::make_exception_ptr(std::runtime_error{"hello world"});
exception_wrapper const ew_eptr(ep, from_eptr<std::runtime_error>(ep));
exception_wrapper const ew_small(std::runtime_error{"hello world"});
exception_wrapper const ew_big(BigRuntimeError{"hello world"});
bool handled = false;
auto expect_runtime_error_yes_catch_all = [&](const exception_wrapper& ew) {
ew.handle(
[](const std::logic_error&) { EXPECT_TRUE(false); },
[&](const std::runtime_error&) { handled = true; },
[](const std::exception&) { EXPECT_TRUE(false); },
[](...) { EXPECT_TRUE(false); });
};
expect_runtime_error_yes_catch_all(ew_eptr);
EXPECT_EQ(true, handled);
handled = false;
expect_runtime_error_yes_catch_all(ew_small);
EXPECT_EQ(true, handled);
handled = false;
expect_runtime_error_yes_catch_all(ew_big);
EXPECT_EQ(true, handled);
handled = false;
auto expect_runtime_error_no_catch_all = [&](const exception_wrapper& ew) {
ew.handle(
[](const std::logic_error&) { EXPECT_TRUE(false); },
[&](const std::runtime_error&) { handled = true; },
[](const std::exception&) { EXPECT_TRUE(false); });
};
expect_runtime_error_no_catch_all(ew_eptr);
EXPECT_EQ(true, handled);
handled = false;
expect_runtime_error_no_catch_all(ew_small);
EXPECT_EQ(true, handled);
handled = false;
expect_runtime_error_no_catch_all(ew_big);
EXPECT_EQ(true, handled);
handled = false;
auto expect_runtime_error_catch_non_std = [&](const exception_wrapper& ew) {
ew.handle(
[](const std::logic_error&) { EXPECT_TRUE(false); },
[&](const std::runtime_error&) { handled = true; },
[](const std::exception&) { EXPECT_TRUE(false); },
[](const int&) { EXPECT_TRUE(false); });
};
expect_runtime_error_catch_non_std(ew_eptr);
EXPECT_EQ(true, handled);
handled = false;
expect_runtime_error_catch_non_std(ew_small);
EXPECT_EQ(true, handled);
handled = false;
expect_runtime_error_catch_non_std(ew_big);
EXPECT_EQ(true, handled);
handled = false;
// Test that an exception thrown from one handler is not caught by an
// outer handler:
auto expect_runtime_error_rethrow = [&](const exception_wrapper& ew) {
ew.handle(
[](const std::logic_error&) { EXPECT_TRUE(false); },
[&](const std::runtime_error& e) {
handled = true;
throw e;
},
[](const std::exception&) { EXPECT_TRUE(false); });
};
EXPECT_THROW(expect_runtime_error_rethrow(ew_eptr), std::runtime_error);
EXPECT_EQ(true, handled);
handled = false;
EXPECT_THROW(expect_runtime_error_rethrow(ew_small), std::runtime_error);
EXPECT_EQ(true, handled);
handled = false;
EXPECT_THROW(expect_runtime_error_rethrow(ew_big), std::runtime_error);
EXPECT_EQ(true, handled);
}
TEST(ExceptionWrapper, handle_std_exception_unhandled) {
auto ep = std::make_exception_ptr(std::exception{});
exception_wrapper const ew_eptr(ep, from_eptr<std::exception>(ep));
exception_wrapper const ew_small(std::exception{});
bool handled = false;
auto expect_runtime_error_yes_catch_all = [&](const exception_wrapper& ew) {
ew.handle(
[](const std::logic_error&) { EXPECT_TRUE(false); },
[](const std::runtime_error&) { EXPECT_TRUE(false); },
[&](...) { handled = true; });
};
expect_runtime_error_yes_catch_all(ew_eptr);
EXPECT_EQ(true, handled);
handled = false;
expect_runtime_error_yes_catch_all(ew_small);
EXPECT_EQ(true, handled);
}
TEST(ExceptionWrapper, handle_non_std_exception_small) {
auto ep = std::make_exception_ptr(42);
exception_wrapper const ew_eptr1(ep);
exception_wrapper const ew_eptr2(ep, from_eptr<int>(ep));
exception_wrapper const ew_small(folly::in_place, 42);
bool handled = false;
auto expect_int_yes_catch_all = [&](const exception_wrapper& ew) {
ew.handle(
[](const std::exception&) { EXPECT_TRUE(false); },
[&](...) { handled = true; });
};
expect_int_yes_catch_all(ew_eptr1);
EXPECT_EQ(true, handled);
handled = false;
expect_int_yes_catch_all(ew_eptr2);
EXPECT_EQ(true, handled);
handled = false;
expect_int_yes_catch_all(ew_small);
EXPECT_EQ(true, handled);
handled = false;
auto expect_int_no_catch_all = [&](const exception_wrapper& ew) {
ew.handle(
[](const std::exception&) { EXPECT_TRUE(false); },
[&](const int&) { handled = true; });
};
expect_int_no_catch_all(ew_eptr1);
EXPECT_EQ(true, handled);
handled = false;
expect_int_no_catch_all(ew_eptr2);
EXPECT_EQ(true, handled);
handled = false;
expect_int_no_catch_all(ew_small);
EXPECT_EQ(true, handled);
handled = false;
auto expect_int_no_catch_all_2 = [&](const exception_wrapper& ew) {
ew.handle(
[&](const int&) { handled = true; },
[](const std::exception&) { EXPECT_TRUE(false); });
};
expect_int_no_catch_all_2(ew_eptr1);
EXPECT_EQ(true, handled);
handled = false;
expect_int_no_catch_all_2(ew_eptr2);
EXPECT_EQ(true, handled);
handled = false;
expect_int_no_catch_all_2(ew_small);
EXPECT_EQ(true, handled);
}
TEST(ExceptionWrapper, handle_non_std_exception_big) {
auto ep = std::make_exception_ptr(BigNonStdError{});
exception_wrapper const ew_eptr1(ep);
exception_wrapper const ew_eptr2(ep, from_eptr<BigNonStdError>(ep));
exception_wrapper const ew_big(folly::in_place, BigNonStdError{});
bool handled = false;
auto expect_int_yes_catch_all = [&](const exception_wrapper& ew) {
ew.handle(
[](const std::exception&) { EXPECT_TRUE(false); },
[&](...) { handled = true; });
};
expect_int_yes_catch_all(ew_eptr1);
EXPECT_EQ(true, handled);
handled = false;
expect_int_yes_catch_all(ew_eptr2);
EXPECT_EQ(true, handled);
handled = false;
expect_int_yes_catch_all(ew_big);
EXPECT_EQ(true, handled);
handled = false;
auto expect_int_no_catch_all = [&](const exception_wrapper& ew) {
ew.handle(
[](const std::exception&) { EXPECT_TRUE(false); },
[&](const BigNonStdError&) { handled = true; });
};
expect_int_no_catch_all(ew_eptr1);
EXPECT_EQ(true, handled);
handled = false;
expect_int_no_catch_all(ew_eptr2);
EXPECT_EQ(true, handled);
handled = false;
expect_int_no_catch_all(ew_big);
EXPECT_EQ(true, handled);
handled = false;
auto expect_int_no_catch_all_2 = [&](const exception_wrapper& ew) {
ew.handle(
[&](const BigNonStdError&) { handled = true; },
[](const std::exception&) { EXPECT_TRUE(false); });
};
expect_int_no_catch_all_2(ew_eptr1);
EXPECT_EQ(true, handled);
handled = false;
expect_int_no_catch_all_2(ew_eptr2);
EXPECT_EQ(true, handled);
handled = false;
expect_int_no_catch_all_2(ew_big);
EXPECT_EQ(true, handled);
handled = false;
EXPECT_THROW(
expect_int_no_catch_all_2(exception_wrapper{folly::in_place, 42}), int);
}
TEST(ExceptionWrapper, handle_non_std_exception_rethrow_base_derived) {
auto ew = testNonStdException();
bool handled = false;
EXPECT_THROW(
ew.handle(
[&](const DerivedException& e) {
handled = true;
throw e;
},
[](const BaseException&) { EXPECT_TRUE(false); }),
DerivedException);
EXPECT_EQ(true, handled);
handled = false;
EXPECT_THROW(
ew.handle(
[&](const DerivedException& e) {
handled = true;
throw e;
},
[](...) { EXPECT_TRUE(false); }),
DerivedException);
EXPECT_EQ(true, handled);
}
TEST(ExceptionWrapper, self_swap_test) {
exception_wrapper ew(std::runtime_error("hello world"));
folly::swap(ew, ew);
EXPECT_STREQ("std::runtime_error: hello world", ew.what().c_str());
auto& ew2 = ew;
ew = std::move(ew2); // should not crash
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment