mirror of
https://github.com/ecency/ecency-mobile.git
synced 2024-12-22 12:51:42 +03:00
481 lines
15 KiB
C++
481 lines
15 KiB
C++
/*
|
|
* Copyright 2016 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.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <cassert>
|
|
#include <exception>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <folly/ExceptionString.h>
|
|
#include <folly/detail/ExceptionWrapper.h>
|
|
|
|
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.
|
|
*
|
|
* Example usage:
|
|
*
|
|
* 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";
|
|
* }
|
|
*
|
|
*/
|
|
class exception_wrapper {
|
|
protected:
|
|
template <typename Ex>
|
|
struct optimize;
|
|
|
|
public:
|
|
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);
|
|
}
|
|
|
|
// If the exception_wrapper does not contain an exception, std::terminate()
|
|
// is invoked to assure the [[noreturn]] behaviour.
|
|
[[noreturn]] void throwException() const {
|
|
if (throwfn_) {
|
|
throwfn_(item_.get());
|
|
} else if (eptr_) {
|
|
std::rethrow_exception(eptr_);
|
|
}
|
|
std::cerr
|
|
<< "Cannot use `throwException` with an empty folly::exception_wrapper"
|
|
<< std::endl;
|
|
std::terminate();
|
|
}
|
|
|
|
explicit operator bool() const {
|
|
return item_ || eptr_;
|
|
}
|
|
|
|
// 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_;
|
|
}
|
|
}
|
|
|
|
bool operator!=(const exception_wrapper& a) const {
|
|
return !(*this == a);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
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<const 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>
|
|
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 {
|
|
if (*this) {
|
|
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 {
|
|
if (*this) {
|
|
throwException();
|
|
}
|
|
} catch (...) {
|
|
return std::current_exception();
|
|
}
|
|
return std::exception_ptr();
|
|
}
|
|
|
|
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*){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:
|
|
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;
|
|
};
|
|
|
|
// 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>
|
|
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;
|
|
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();
|
|
}
|
|
};
|
|
}
|