/*
- * Copyright 2014 Facebook, Inc.
+ * Copyright 2015 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include <cassert>
#include <exception>
#include <memory>
-#include "folly/detail/ExceptionWrapper.h"
+#include <folly/String.h>
+#include <folly/detail/ExceptionWrapper.h>
namespace folly {
* 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. exception_wrapper is flexible enough to accept
- * any std::exception. For performance sensitive applications, exception_wrapper
- * exposes a get() function. These users can use dynamic_cast to retrieve
- * desired derived types (hence the decision to limit usage to just
- * std::exception instead of void*).
+ * 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.
*
* Example usage:
*
* // Thread2: Exceptions are bad!
* void processResult() {
* auto ep = globalExceptionWrapper.get();
- * if (ep) {
- * auto faceplant = dynamic_cast<FacePlantException*>(ep);
- * if (faceplant) {
+ * if (!ep.with_exception<FacePlantException>([&](
+ * FacePlantException& faceplant) {
* LOG(ERROR) << "FACEPLANT";
- * } else {
- * auto failwhale = dynamic_cast<FailWhaleException*>(ep);
- * if (failwhale) {
+ * })) {
+ * ep.with_exception<FailWhaleException>([&](
+ * FailWhaleException& failwhale) {
* LOG(ERROR) << "FAILWHALE!";
- * }
- * }
+ * });
* }
* }
*
*/
class exception_wrapper {
+ protected:
+ template <typename Ex>
+ struct optimize;
+
public:
- exception_wrapper() : throwfn_(nullptr) { }
+ exception_wrapper() = default;
+
+ // Implicitly construct an exception_wrapper from a qualifying exception.
+ // See the optimize struct for details.
+ template <typename Ex, typename =
+ typename std::enable_if<optimize<typename std::decay<Ex>::type>::value>
+ ::type>
+ /* implicit */ exception_wrapper(Ex&& exn) {
+ typedef typename std::decay<Ex>::type DEx;
+ item_ = std::make_shared<DEx>(std::forward<Ex>(exn));
+ throwfn_ = folly::detail::Thrower<DEx>::doThrow;
+ }
+
+ // 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);
+ }
+
+ explicit exception_wrapper(std::exception_ptr eptr) {
+ assign_eptr(eptr);
+ }
void throwException() const {
if (throwfn_) {
throwfn_(item_.get());
+ } else if (eptr_) {
+ std::rethrow_exception(eptr_);
}
}
- std::exception* get() { return item_.get(); }
- const std::exception* get() const { return item_.get(); }
+ explicit operator bool() const {
+ return item_ || eptr_;
+ }
- std::exception* operator->() { return get(); }
- const std::exception* operator->() const { return get(); }
+ // 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_;
+ }
+ }
- std::exception& operator*() { assert(get()); return *get(); }
- const std::exception& operator*() const { assert(get()); return *get(); }
+ bool operator!=(const exception_wrapper& a) const {
+ return !(*this == a);
+ }
- explicit operator bool() const { return get(); }
+ // 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(); }
+
+ fbstring what() const {
+ if (item_) {
+ return exceptionStr(*item_);
+ } else if (eptr_) {
+ return estr_;
+ } else {
+ return fbstring();
+ }
+ }
+
+ fbstring class_name() const {
+ if (item_) {
+ auto& i = *item_;
+ return demangle(typeid(i));
+ } else if (eptr_) {
+ return ename_;
+ } else {
+ return fbstring();
+ }
+ }
+
+ template <class Ex>
+ bool is_compatible_with() const {
+ if (item_) {
+ return dynamic_cast<const Ex*>(item_.get());
+ } else if (eptr_) {
+ try {
+ std::rethrow_exception(eptr_);
+ } catch (typename std::decay<Ex>::type&) {
+ return true;
+ } catch (...) {
+ // fall through
+ }
+ }
+ return false;
+ }
+
+ // 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>
+ typename std::enable_if<
+ std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
+ bool>::type
+ with_exception(F f) {
+ return with_exception1<typename std::decay<Ex>::type>(f, this);
+ }
+
+ // Const overload
+ template <class Ex, class F>
+ typename std::enable_if<
+ std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
+ bool>::type
+ with_exception(F f) const {
+ return with_exception1<const typename std::decay<Ex>::type>(f, this);
+ }
+
+ // Overload for non-exceptions. Always rethrows.
+ template <class Ex, class F>
+ typename std::enable_if<
+ !std::is_base_of<std::exception, typename std::decay<Ex>::type>::value,
+ bool>::type
+ with_exception(F f) const {
+ try {
+ throwException();
+ } catch (typename std::decay<Ex>::type& e) {
+ f(e);
+ return true;
+ } catch (...) {
+ // fall through
+ }
+ return false;
+ }
std::exception_ptr getExceptionPtr() const {
+ if (eptr_) {
+ return eptr_;
+ }
+
try {
throwException();
} catch (...) {
return std::exception_ptr();
}
- private:
+protected:
+ template <typename Ex>
+ struct optimize {
+ static const bool value =
+ std::is_base_of<std::exception, Ex>::value &&
+ std::is_copy_assignable<Ex>::value &&
+ !std::is_abstract<Ex>::value;
+ };
+
+ template <typename Ex>
+ void assign_eptr(std::exception_ptr eptr, Ex& e) {
+ this->eptr_ = eptr;
+ this->estr_ = exceptionStr(e).toStdString();
+ this->ename_ = demangle(typeid(e)).toStdString();
+ }
+
+ void assign_eptr(std::exception_ptr eptr) {
+ this->eptr_ = eptr;
+ }
+
+ // 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*);
+ 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::string estr_;
+ std::string ename_;
template <class T, class... Args>
friend exception_wrapper make_exception_wrapper(Args&&... args);
+
+private:
+ // 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) {
+ if (that->item_) {
+ if (auto ex = dynamic_cast<Ex*>(that->item_.get())) {
+ f(*ex);
+ return true;
+ }
+ } else if (that->eptr_) {
+ try {
+ std::rethrow_exception(that->eptr_);
+ } catch (Ex& e) {
+ f(e);
+ return true;
+ } catch (...) {
+ // fall through
+ }
+ }
+ return false;
+ }
};
template <class T, class... Args>
return ew;
}
+// For consistency with exceptionStr() functions in String.h
+inline fbstring exceptionStr(const exception_wrapper& ew) {
+ return ew.what();
+}
+
+/*
+ * 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();
+ * }
+ * });
+ */
+
+template <typename... Exceptions>
+class try_and_catch;
+
+template <typename LastException, typename... Exceptions>
+class try_and_catch<LastException, Exceptions...> :
+ public try_and_catch<Exceptions...> {
+ public:
+ template <typename F>
+ explicit try_and_catch(F&& fn) : Base() {
+ call_fn(fn);
+ }
+
+ protected:
+ typedef try_and_catch<Exceptions...> Base;
+
+ try_and_catch() : Base() {}
+
+ template <typename Ex>
+ typename std::enable_if<!exception_wrapper::optimize<Ex>::value>::type
+ assign_exception(Ex& e, std::exception_ptr eptr) {
+ exception_wrapper::assign_eptr(eptr, e);
+ }
+
+ template <typename Ex>
+ typename std::enable_if<exception_wrapper::optimize<Ex>::value>::type
+ assign_exception(Ex& e, std::exception_ptr /*eptr*/) {
+ this->item_ = std::make_shared<Ex>(e);
+ this->throwfn_ = folly::detail::Thrower<Ex>::doThrow;
+ }
+
+ template <typename F>
+ void call_fn(F&& fn) {
+ try {
+ Base::call_fn(std::move(fn));
+ } catch (LastException& e) {
+ if (typeid(e) == typeid(LastException&)) {
+ assign_exception(e, std::current_exception());
+ } else {
+ exception_wrapper::assign_eptr(std::current_exception(), e);
+ }
+ }
+ }
+};
+
+template<>
+class try_and_catch<> : public exception_wrapper {
+ public:
+ try_and_catch() = default;
+
+ protected:
+ template <typename F>
+ void call_fn(F&& fn) {
+ fn();
+ }
+};
}
#endif