non-throwing, non-allocating exception_wrapper
authorEric Niebler <eniebler@fb.com>
Tue, 11 Apr 2017 23:12:59 +0000 (16:12 -0700)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Tue, 11 Apr 2017 23:23:28 +0000 (16:23 -0700)
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

folly/CPortability.h
folly/ExceptionWrapper-inl.h [new file with mode: 0644]
folly/ExceptionWrapper.cpp
folly/ExceptionWrapper.h
folly/Makefile.am
folly/futures/test/FutureSplitterTest.cpp
folly/test/ExceptionWrapperTest.cpp

index 0989cbc..5098447 100644 (file)
 #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
diff --git a/folly/ExceptionWrapper-inl.h b/folly/ExceptionWrapper-inl.h
new file mode 100644 (file)
index 0000000..1754844
--- /dev/null
@@ -0,0 +1,584 @@
+/*
+ * 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
index c6ad166..c1440a5 100644 (file)
  */
 #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_);
-  } else {
-    return fbstring();
+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 {
+      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();
 }
 
index bd5bc11..fa91d45 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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.
  * 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*);
+  };
 
-  // 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(); }
+  [[noreturn]] static void onNoExceptionError();
 
-  fbstring what() const;
-  fbstring class_name() const;
+  template <class Ret, class... Args>
+  static Ret noop_(Args...);
 
-  template <class Ex>
-  bool is_compatible_with() const {
-    return with_exception<Ex>([](const Ex&) {});
-  }
+  static std::type_info const* uninit_type_(exception_wrapper const*);
 
-  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));
-  }
+  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 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>
+  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;
+  };
 
-  // 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);
-  }
+  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_};
+  };
 
-  // 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);
-  }
+  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_};
+  };
 
-  std::exception_ptr to_exception_ptr() const {
-    if (eptr_) {
-      return eptr_;
-    }
-
-    try {
-      if (*this) {
-        throwException();
-      }
-    } catch (...) {
-      return std::current_exception();
-    }
-    return std::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 {
+      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_};
+  };
 
- 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;
-  }
+  union {
+    Buffer buff_{};
+    ExceptionPtr eptr_;
+    SharedPtr sptr_;
+  };
+  VTable const* vptr_{&uninit_};
 
-  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 Ex, class DEx = _t<std::decay<Ex>>>
+  exception_wrapper(Ex&& ex, OnHeapTag);
 
-  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 <class Ex, class DEx = _t<std::decay<Ex>>>
+  exception_wrapper(Ex&& ex, InSituTag);
 
-  void assign_eptr(std::exception_ptr eptr) {
-    this->eptr_ = eptr;
-  }
+  template <class T>
+  struct IsRegularExceptionType
+      : StrictConjunction<
+            std::is_copy_constructible<T>,
+            Negation<std::is_base_of<exception_wrapper, T>>,
+            Negation<std::is_abstract<T>>> {};
 
-  // 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 CatchFn, bool IsConst = false>
+  struct ExceptionTypeOf;
 
- 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 <bool IsConst>
+  struct HandleReduce;
 
-  template <class T>
-  class Thrower {
-   public:
-    static void doThrow(std::exception& obj) {
-      throw static_cast<T&>(obj);
-    }
-  };
+  template <bool IsConst>
+  struct HandleStdExceptReduce;
 
-  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;
-  }
+  template <class This, class... CatchFns>
+  static void handle_(std::false_type, This& this_, CatchFns&... fns);
 
-  // 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;
-  }
-};
+  template <class This, class... CatchFns>
+  static void handle_(std::true_type, This& this_, CatchFns&... fns);
 
-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, class This, class Fn>
+  static bool with_exception_(This& this_, Fn fn_);
 
-// For consistency with exceptionStr() functions in ExceptionString.h
-fbstring exceptionStr(const exception_wrapper& ew);
+ public:
+  //! 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;
 
-/*
- * 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();
- *   }
- * });
- */
+  //! \pre `bool(*this)`
+  //! Throws the wrapped expression.
+  [[noreturn]] void throwException() const;
 
-namespace try_and_catch_detail {
+  //! 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 <typename... Args>
-using is_wrap_ctor = std::is_constructible<exception_wrapper, Args...>;
+template <class Ex>
+constexpr exception_wrapper::VTable exception_wrapper::InPlace<Ex>::ops_;
 
-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);
+/**
+ * \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)...}};
 }
 
-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);
+/**
+ * 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 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
index 94c8fe4..0e6467b 100644 (file)
@@ -93,6 +93,7 @@ nobase_follyinclude_HEADERS = \
        Exception.h \
        ExceptionString.h \
        ExceptionWrapper.h \
+       ExceptionWrapper-inl.h \
        Executor.h \
        Expected.h \
        experimental/AsymmetricMemoryBarrier.h \
index c14e30c..927a2d4 100644 (file)
@@ -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());
index 4a4325a..9158158 100644 (file)
@@ -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
+}