/*
- * Copyright 2014 Facebook, Inc.
+ * Copyright 2017 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* limitations under the License.
*/
-#ifndef FOLLY_EXCEPTIONWRAPPER_H
-#define FOLLY_EXCEPTIONWRAPPER_H
+#pragma once
-#include <cassert>
#include <exception>
#include <memory>
-#include <folly/String.h>
-#include <folly/detail/ExceptionWrapper.h>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include <folly/ExceptionString.h>
+#include <folly/FBString.h>
namespace folly {
* can test or extract a pointer to a specific exception type with very little
* overhead.
*
- * Example usage:
- *
+ * \par Example usage:
+ * \par
+ * \code
* exception_wrapper globalExceptionWrapper;
*
* // Thread1
*
* // Thread2: Exceptions are bad!
* void processResult() {
- * auto ep = globalExceptionWrapper.get();
- * if (!ep.with_exception<FacePlantException>([&](
- * FacePlantException& faceplant) {
- * LOG(ERROR) << "FACEPLANT";
- * })) {
- * ep.with_exception<FailWhaleException>([&](
- * FailWhaleException& failwhale) {
+ * 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 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;
+ assign_sptr(std::make_shared<DEx>(std::forward<Ex>(exn)));
+ }
- void throwException() const {
- if (throwfn_) {
- throwfn_(item_.get());
- } else if (eptr_) {
- std::rethrow_exception(eptr_);
- }
+ // 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);
}
+ // If the exception_wrapper does not contain an exception, std::terminate()
+ // is invoked to assure the [[noreturn]] behaviour.
+ [[noreturn]] void throwException() const;
+
explicit operator bool() const {
return item_ || eptr_;
}
std::exception* getCopied() { return item_.get(); }
const std::exception* getCopied() const { return item_.get(); }
- fbstring what() const {
- if (item_) {
- return exceptionStr(*item_.get());
- } else if (eptr_) {
- return estr_;
- } else {
- return fbstring();
- }
- }
+ fbstring what() const;
+ fbstring class_name() const;
template <class Ex>
bool is_compatible_with() const {
- if (item_) {
- return dynamic_cast<const Ex*>(getCopied());
- } else if (eptr_) {
- try {
- std::rethrow_exception(eptr_);
- } catch (std::exception& e) {
- return dynamic_cast<const Ex*>(&e);
- } catch (...) {
- // fall through
- }
- }
- return false;
+ return with_exception<Ex>([](const Ex&) {});
+ }
+
+ template <class F>
+ bool with_exception(F&& f) {
+ using arg_type = typename functor_traits<F>::arg_type_decayed;
+ return with_exception<arg_type>(std::forward<F>(f));
}
+ template <class F>
+ bool with_exception(F&& f) const {
+ using arg_type = typename functor_traits<F>::arg_type_decayed;
+ return with_exception<arg_type>(std::forward<F>(f));
+ }
+
+ // 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<Ex>(f, this);
+ return with_exception1<typename std::decay<Ex>::type>(f, this);
}
+ // Const overload
template <class Ex, class F>
bool with_exception(F f) const {
- return with_exception1<const Ex>(f, this);
+ return with_exception1<typename std::decay<Ex>::type>(f, this);
}
std::exception_ptr getExceptionPtr() const {
}
try {
- throwException();
+ if (*this) {
+ throwException();
+ }
} catch (...) {
return std::current_exception();
}
return std::exception_ptr();
}
-protected:
+ private:
+ 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_sptr(std::shared_ptr<Ex> sptr) {
+ this->item_ = std::move(sptr);
+ this->throwfn_ = Thrower<Ex>::doThrow;
+ }
+
+ 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 the the what() string, so we
- // can at least get it back out without having to rethrow.
+ // 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:
+ 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_decayed = typename std::decay<F>::type;
+ using functor_op = decltype(&functor_decayed::operator());
+ using arg_type = typename impl<functor_op>::arg_type;
+ using arg_type_decayed = typename std::decay<arg_type>::type;
+ };
+
+ template <class T>
+ class Thrower {
+ public:
+ static void doThrow(std::exception& obj) {
+ throw static_cast<T&>(obj);
+ }
+ };
+
+ template <typename T>
+ using is_exception_ = std::is_base_of<std::exception, T>;
+
+ template <bool V, typename T, typename F>
+ using conditional_t_ = typename std::conditional<V, T, F>::type;
+
+ template <typename T, typename F>
+ static typename std::enable_if<is_exception_<T>::value, T*>::type
+ try_dynamic_cast_exception(F* from) {
+ return dynamic_cast<T*>(from);
+ }
+ template <typename T, typename F>
+ static typename std::enable_if<!is_exception_<T>::value, T*>::type
+ try_dynamic_cast_exception(F*) {
+ return nullptr;
+ }
+
// 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->getCopied())) {
+ using CEx = conditional_t_<std::is_const<T>::value, const Ex, Ex>;
+ if (is_exception_<Ex>::value && that->item_) {
+ if (auto ex = try_dynamic_cast_exception<CEx>(that->item_.get())) {
f(*ex);
return true;
}
} else if (that->eptr_) {
try {
std::rethrow_exception(that->eptr_);
- } catch (std::exception& e) {
- if (auto ex = dynamic_cast<Ex*>(&e)) {
- f(*ex);
- return true;
- }
+ } catch (CEx& e) {
+ f(e);
+ return true;
} catch (...) {
// fall through
}
template <class T, class... Args>
exception_wrapper make_exception_wrapper(Args&&... args) {
exception_wrapper ew;
- ew.item_ = std::make_shared<T>(std::forward<Args>(args)...);
- ew.throwfn_ = folly::detail::Thrower<T>::doThrow;
+ ew.assign_sptr(std::make_shared<T>(std::forward<Args>(args)...));
return ew;
}
+// For consistency with exceptionStr() functions in ExceptionString.h
+fbstring exceptionStr(const exception_wrapper& ew);
+
/*
* try_and_catch is a simple replacement for try {} catch(){} that allows you to
* specify which derived exceptions you would like to catch and store in an
* });
*/
-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;
+namespace try_and_catch_detail {
- try_and_catch() : Base() {}
-
- template <typename Ex>
- typename std::enable_if<std::is_base_of<std::exception, Ex>::value>::type
- assign_eptr(Ex& e) {
- this->eptr_ = std::current_exception();
- this->estr_ = exceptionStr(e).toStdString();
- }
+template <bool V, typename T = void>
+using enable_if_t_ = typename std::enable_if<V, T>::type;
- template <typename Ex>
- typename std::enable_if<!std::is_base_of<std::exception, Ex>::value>::type
- assign_eptr(Ex& e) {
- this->eptr_ = std::current_exception();
- this->estr_ = exceptionStr(e).toStdString();
- }
+template <typename... Args>
+using is_wrap_ctor = std::is_constructible<exception_wrapper, Args...>;
- 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>
+inline enable_if_t_<!is_wrap_ctor<Ex&>::value, exception_wrapper> make(Ex& ex) {
+ return exception_wrapper(std::current_exception(), ex);
+}
- template <typename Ex>
- typename std::enable_if<!optimize<Ex>::value>::type
- assign_exception(Ex& e) {
- assign_eptr(e);
- }
+template <typename Ex>
+inline enable_if_t_<is_wrap_ctor<Ex&>::value, exception_wrapper> make(Ex& ex) {
+ return typeid(Ex&) == typeid(ex)
+ ? exception_wrapper(ex)
+ : exception_wrapper(std::current_exception(), ex);
+}
- template <typename Ex>
- typename std::enable_if<optimize<Ex>::value>::type
- assign_exception(Ex& e) {
- this->item_ = std::make_shared<Ex>(e);
- this->throwfn_ = folly::detail::Thrower<Ex>::doThrow;
- }
+template <typename F>
+inline exception_wrapper impl(F&& f) {
+ return (f(), exception_wrapper());
+}
- 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);
- } else {
- assign_eptr(e);
- }
- }
+template <typename F, typename Ex, typename... Exs>
+inline exception_wrapper impl(F&& f) {
+ try {
+ return impl<F, Exs...>(std::forward<F>(f));
+ } catch (Ex& ex) {
+ return make(ex);
}
-};
-
-template<>
-class try_and_catch<> : public exception_wrapper {
- public:
- try_and_catch() {}
+}
+} // try_and_catch_detail
- protected:
- template <typename F>
- void call_fn(F&& fn) {
- fn();
- }
-};
+template <typename... Exceptions, typename F>
+exception_wrapper try_and_catch(F&& fn) {
+ return try_and_catch_detail::impl<F, Exceptions...>(std::forward<F>(fn));
}
-#endif
+} // folly