Commit aa7f74aa authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook GitHub Bot

exception_wrapper thrown variant via abi/runtime

Summary:
Reimplement `folly::exception_wrapper` thrown variant not to need to cache any pointers and rather to access the exception object and runtime type, and to perform runtime upcasting, via the platform runtime.

This both simplifies and extends the capability of the thrown variant.

Reviewed By: luciang

Differential Revision: D26331579

fbshipit-source-id: a6fe0f10434956efa5ba4b4bcd1bd3a1b48466a4
parent 808c2600
......@@ -127,56 +127,6 @@ inline std::exception const* exception_wrapper::as_exception_or_null_(
return nullptr;
}
static_assert(
!kMicrosoftAbiVer || (kMicrosoftAbiVer >= 1900 && kMicrosoftAbiVer <= 2000),
"exception_wrapper is untested and possibly broken on your version of "
"MSVC");
inline std::uintptr_t exception_wrapper::ExceptionPtr::as_int_(
std::exception_ptr const& ptr, std::exception const& e) noexcept {
if (!kMicrosoftAbiVer) {
return reinterpret_cast<std::uintptr_t>(&e);
} else {
// On Windows, as of MSVC2017, all thrown exceptions are copied to the stack
// first. Thus, we cannot depend on exception references associated with an
// exception_ptr to be live for the duration of the exception_ptr. We need
// to directly access the heap allocated memory inside the exception_ptr.
//
// std::exception_ptr is an opaque reinterpret_cast of
// std::shared_ptr<__ExceptionPtr>
// __ExceptionPtr is a non-virtual class with two members, a union and a
// bool. The union contains the now-undocumented EHExceptionRecord, which
// contains a struct which contains a void* which points to the heap
// allocated exception.
// We derive the offset to pExceptionObject via manual means.
FOLLY_PACK_PUSH
struct Win32ExceptionPtr {
char offset[8 + 4 * sizeof(void*)];
void* exceptionObject;
} FOLLY_PACK_ATTR;
FOLLY_PACK_POP
auto* win32ExceptionPtr =
reinterpret_cast<std::shared_ptr<Win32ExceptionPtr> const*>(&ptr)
->get();
return reinterpret_cast<std::uintptr_t>(win32ExceptionPtr->exceptionObject);
}
}
inline std::uintptr_t exception_wrapper::ExceptionPtr::as_int_(
std::exception_ptr const&, AnyException e) noexcept {
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_);
......@@ -196,14 +146,11 @@ inline void exception_wrapper::ExceptionPtr::delete_(exception_wrapper* that) {
}
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_();
return exception_ptr_get_type(that->eptr_.ptr_);
}
inline std::exception const* exception_wrapper::ExceptionPtr::get_exception_(
exception_wrapper const* that) {
return that->eptr_.has_exception_() ? that->eptr_.as_exception_() : nullptr;
return exception_ptr_get_object<std::exception>(that->eptr_.ptr_);
}
inline exception_wrapper exception_wrapper::ExceptionPtr::get_exception_ptr_(
exception_wrapper const* that) {
......@@ -248,8 +195,8 @@ 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};
} catch (...) {
return exception_wrapper{std::current_exception()};
}
}
......@@ -268,8 +215,8 @@ 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};
} catch (...) {
return exception_wrapper{std::current_exception()};
}
}
inline void exception_wrapper::SharedPtr::copy_(
......@@ -307,7 +254,7 @@ inline exception_wrapper exception_wrapper::SharedPtr::get_exception_ptr_(
template <class Ex, typename... As>
inline exception_wrapper::exception_wrapper(
ThrownTag, in_place_type_t<Ex>, As&&... as)
: eptr_{std::make_exception_ptr(Ex(std::forward<As>(as)...)), reinterpret_cast<std::uintptr_t>(std::addressof(typeid(Ex))) + 1u},
: eptr_{std::make_exception_ptr(Ex(std::forward<As>(as)...))},
vptr_(&ExceptionPtr::ops_) {}
template <class Ex, typename... As>
......@@ -355,9 +302,17 @@ inline exception_wrapper::~exception_wrapper() {
template <class Ex>
inline exception_wrapper::exception_wrapper(
std::exception_ptr ptr, Ex& ex) noexcept
: eptr_{ptr, ExceptionPtr::as_int_(ptr, ex)}, vptr_(&ExceptionPtr::ops_) {
std::exception_ptr const& ptr, Ex& ex) noexcept
: exception_wrapper{folly::copy(ptr), ex} {}
template <class Ex>
inline exception_wrapper::exception_wrapper(
std::exception_ptr&& ptr, Ex& ex) noexcept
: eptr_{std::move(ptr)}, vptr_(&ExceptionPtr::ops_) {
assert(eptr_.ptr_);
(void)ex;
assert(exception_ptr_get_object<Ex>(eptr_.ptr_));
assert(exception_ptr_get_object<Ex>(eptr_.ptr_) == &ex || kIsWindows);
}
namespace exception_wrapper_detail {
......@@ -457,9 +412,6 @@ inline std::exception_ptr exception_wrapper::to_exception_ptr() const noexcept {
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);
......@@ -474,9 +426,7 @@ inline folly::fbstring exception_wrapper::what() const {
inline folly::fbstring exception_wrapper::class_name() const {
auto& ti = type();
return ti == none() ? ""
: ti == unknown() ? "<unknown exception>"
: folly::demangle(ti);
return ti == none() ? "" : folly::demangle(ti);
}
template <class Ex>
......
......@@ -37,18 +37,6 @@ exception_wrapper::VTable const exception_wrapper::ExceptionPtr::ops_{
exception_wrapper::VTable const exception_wrapper::SharedPtr::ops_{
copy_, move_, delete_, throw_, type_, get_exception_, get_exception_ptr_};
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;
}
}
} // namespace
exception_wrapper exception_wrapper::from_exception_ptr(
std::exception_ptr const& ptr) noexcept {
return from_exception_ptr(folly::copy(ptr));
......@@ -56,31 +44,16 @@ exception_wrapper exception_wrapper::from_exception_ptr(
exception_wrapper exception_wrapper::from_exception_ptr(
std::exception_ptr&& ptr) noexcept {
if (!ptr) {
return exception_wrapper();
}
try {
std::rethrow_exception(std::move(ptr));
} catch (std::exception& e) {
return exception_wrapper(std::current_exception(), e);
} catch (...) {
return exception_wrapper(std::current_exception());
}
return !ptr ? exception_wrapper() : exception_wrapper(std::move(ptr));
}
exception_wrapper::exception_wrapper(std::exception_ptr ptr) noexcept
: exception_wrapper{} {
exception_wrapper::exception_wrapper(std::exception_ptr const& ptr) noexcept
: exception_wrapper{folly::copy(ptr)} {}
exception_wrapper::exception_wrapper(std::exception_ptr&& ptr) noexcept {
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};
}
::new (&eptr_) ExceptionPtr{std::move(ptr)};
vptr_ = &ExceptionPtr::ops_;
}
}
......
......@@ -31,6 +31,7 @@
#include <utility>
#include <folly/CPortability.h>
#include <folly/CppAttributes.h>
#include <folly/Demangle.h>
#include <folly/ExceptionString.h>
#include <folly/FBString.h>
......@@ -38,6 +39,7 @@
#include <folly/Traits.h>
#include <folly/Utility.h>
#include <folly/lang/Assume.h>
#include <folly/lang/Exception.h>
#ifdef __GNUC__
#pragma GCC diagnostic push
......@@ -177,10 +179,7 @@ class exception_wrapper final {
// 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.
// 3. A std::exception_ptr.
// 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.
......@@ -212,8 +211,6 @@ class exception_wrapper final {
using IsCatchAll =
std::is_same<arg_type<std::decay_t<CatchFn>>, AnyException>;
struct Unknown {};
// Sadly, with the gcc-4.9 platform, std::logic_error and std::runtime_error
// do not fit here. They also don't have noexcept copy-ctors, so the internal
// storage wouldn't be used anyway. For the gcc-5 platform, both logic_error
......@@ -255,19 +252,7 @@ class exception_wrapper final {
struct ExceptionPtr {
std::exception_ptr ptr_;
std::uintptr_t exception_or_type_; // odd for type_info
static_assert(
1 < alignof(std::exception) && 1 < alignof(std::type_info),
"Surprise! std::exception and std::type_info don't have alignment "
"greater than one. as_int_ below will not work!");
static std::uintptr_t as_int_(
std::exception_ptr const& ptr, std::exception const& e) noexcept;
static std::uintptr_t as_int_(
std::exception_ptr const& ptr, AnyException e) noexcept;
bool has_exception_() const;
std::exception const* as_exception_() const;
std::type_info const* as_type_() const;
static void copy_(exception_wrapper const* from, exception_wrapper* to);
static void move_(exception_wrapper* from, exception_wrapper* to);
static void delete_(exception_wrapper* that);
......@@ -408,19 +393,19 @@ class exception_wrapper final {
~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;
//! \post `hasThrownException() == bool(ptr)`
explicit exception_wrapper(std::exception_ptr const& ptr) noexcept;
explicit exception_wrapper(std::exception_ptr&& ptr) noexcept;
//! \pre `ptr` holds a reference to `ex`.
//! \post `hasThrownException() == true`
//! \post `bool(*this)`
//! \post `type() == typeid(ex)`
template <class Ex>
exception_wrapper(std::exception_ptr ptr, Ex& ex) noexcept;
exception_wrapper(std::exception_ptr const& ptr, Ex& ex) noexcept;
template <class Ex>
exception_wrapper(std::exception_ptr&& ptr, Ex& ex) noexcept;
//! \pre `typeid(ex) == typeid(typename decay<Ex>::type)`
//! \post `bool(*this)`
......@@ -506,23 +491,16 @@ class exception_wrapper final {
//! \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()`.
//! wrapped exception object, returns `exception_wrapper::none()`.
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,
//! \return If `!*this`, the empty string; otherwise,
//! the result of `type().name()` after demangling.
folly::fbstring class_name() const;
......@@ -658,8 +636,8 @@ template <typename F, typename Ex, typename... Exs>
inline exception_wrapper try_and_catch_(F&& f) {
try {
return try_and_catch_<F, Exs...>(std::forward<F>(f));
} catch (Ex& ex) {
return exception_wrapper(std::current_exception(), ex);
} catch (Ex&) {
return exception_wrapper(std::current_exception());
}
}
} // namespace detail
......@@ -709,8 +687,6 @@ exception_wrapper try_and_catch(F&& fn) noexcept {
try {
static_cast<F&&>(fn)();
return exception_wrapper{};
} catch (std::exception const& ex) {
return exception_wrapper{std::current_exception(), ex};
} catch (...) {
return exception_wrapper{std::current_exception()};
}
......
......@@ -239,13 +239,13 @@ TEST(ExceptionWrapper, get_or_make_exception_ptr_test) {
TEST(ExceptionWrapper, from_exception_ptr_empty) {
auto ep = std::exception_ptr();
auto ew = exception_wrapper::from_exception_ptr(ep);
auto ew = exception_wrapper{ep};
EXPECT_FALSE(bool(ew));
}
TEST(ExceptionWrapper, from_exception_ptr_exn) {
auto ep = std::make_exception_ptr(std::runtime_error("foo"));
auto ew = exception_wrapper::from_exception_ptr(ep);
auto ew = exception_wrapper{ep};
EXPECT_TRUE(bool(ew));
EXPECT_EQ(ep, folly::as_const(ew).to_exception_ptr());
EXPECT_EQ(ep, ew.to_exception_ptr());
......@@ -254,7 +254,7 @@ TEST(ExceptionWrapper, from_exception_ptr_exn) {
TEST(ExceptionWrapper, from_exception_ptr_any) {
auto ep = std::make_exception_ptr<int>(12);
auto ew = exception_wrapper::from_exception_ptr(ep);
auto ew = exception_wrapper{ep};
EXPECT_TRUE(bool(ew));
EXPECT_EQ(ep, folly::as_const(ew).to_exception_ptr());
EXPECT_EQ(ep, ew.to_exception_ptr());
......@@ -420,7 +420,7 @@ TEST(ExceptionWrapper, with_non_std_exception_test) {
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
auto ew = exception_wrapper(ep);
EXPECT_TRUE(bool(ew));
EXPECT_EQ(nullptr, ew.get_exception());
EXPECT_EQ(nullptr, ew.get_exception<std::exception>());
......@@ -428,9 +428,8 @@ TEST(ExceptionWrapper, with_exception_ptr_any_nil_test) {
EXPECT_EQ(12, *ew.get_exception<int>());
EXPECT_EQ(ep, folly::as_const(ew).to_exception_ptr());
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_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>());
......@@ -539,23 +538,16 @@ TEST(ExceptionWrapper, implicitConstruction) {
}
namespace {
struct BaseException {
virtual ~BaseException() {}
struct BaseNonStdException {
virtual ~BaseNonStdException() {}
};
struct DerivedException : BaseException {};
exception_wrapper testNonStdException() {
try {
throw DerivedException{};
} catch (const BaseException& e) {
return exception_wrapper{std::current_exception(), e};
}
}
struct DerivedNonStdException : BaseNonStdException {};
} // namespace
TEST(ExceptionWrapper, base_derived_non_std_exception_test) {
auto ew = testNonStdException();
EXPECT_TRUE(ew.type() == typeid(DerivedException));
EXPECT_TRUE(ew.with_exception([](const DerivedException&) {}));
exception_wrapper ew{std::make_exception_ptr(DerivedNonStdException{})};
EXPECT_TRUE(ew.type() == typeid(DerivedNonStdException));
EXPECT_TRUE(ew.with_exception([](const DerivedNonStdException&) {}));
}
namespace {
......@@ -842,26 +834,26 @@ TEST(ExceptionWrapper, handle_non_std_exception_big) {
}
TEST(ExceptionWrapper, handle_non_std_exception_rethrow_base_derived) {
auto ew = testNonStdException();
exception_wrapper ew{std::make_exception_ptr(DerivedNonStdException{})};
bool handled = false;
EXPECT_THROW(
ew.handle(
[&](const DerivedException& e) {
[&](const DerivedNonStdException& e) {
handled = true;
throw e;
},
[](const BaseException&) { ADD_FAILURE(); }),
DerivedException);
[](const BaseNonStdException&) { ADD_FAILURE(); }),
DerivedNonStdException);
EXPECT_TRUE(handled);
handled = false;
EXPECT_THROW(
ew.handle(
[&](const DerivedException& e) {
[&](const DerivedNonStdException& e) {
handled = true;
throw e;
},
[](...) { ADD_FAILURE(); }),
DerivedException);
DerivedNonStdException);
EXPECT_TRUE(handled);
}
......
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