Keep the std::exception* or std::type_info* in folly::exception_wrapper
[folly.git] / folly / ExceptionWrapper.h
1 /*
2  * Copyright 2017 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #pragma once
18
19 #include <exception>
20 #include <memory>
21 #include <string>
22 #include <tuple>
23 #include <type_traits>
24 #include <utility>
25
26 #include <folly/ExceptionString.h>
27 #include <folly/FBString.h>
28 #include <folly/Traits.h>
29
30 namespace folly {
31
32 /*
33  * Throwing exceptions can be a convenient way to handle errors. Storing
34  * exceptions in an exception_ptr makes it easy to handle exceptions in a
35  * different thread or at a later time. exception_ptr can also be used in a very
36  * generic result/exception wrapper.
37  *
38  * However, there are some issues with throwing exceptions and
39  * std::exception_ptr. These issues revolve around throw being expensive,
40  * particularly in a multithreaded environment (see
41  * ExceptionWrapperBenchmark.cpp).
42  *
43  * Imagine we have a library that has an API which returns a result/exception
44  * wrapper. Let's consider some approaches for implementing this wrapper.
45  * First, we could store a std::exception. This approach loses the derived
46  * exception type, which can make exception handling more difficult for users
47  * that prefer rethrowing the exception. We could use a folly::dynamic for every
48  * possible type of exception. This is not very flexible - adding new types of
49  * exceptions requires a change to the result/exception wrapper. We could use an
50  * exception_ptr. However, constructing an exception_ptr as well as accessing
51  * the error requires a call to throw. That means that there will be two calls
52  * to throw in order to process the exception. For performance sensitive
53  * applications, this may be unacceptable.
54  *
55  * exception_wrapper is designed to handle exception management for both
56  * convenience and high performance use cases. make_exception_wrapper is
57  * templated on derived type, allowing us to rethrow the exception properly for
58  * users that prefer convenience. These explicitly named exception types can
59  * therefore be handled without any peformance penalty.  exception_wrapper is
60  * also flexible enough to accept any type. If a caught exception is not of an
61  * explicitly named type, then std::exception_ptr is used to preserve the
62  * exception state. For performance sensitive applications, the accessor methods
63  * can test or extract a pointer to a specific exception type with very little
64  * overhead.
65  *
66  * \par Example usage:
67  * \par
68  * \code
69  * exception_wrapper globalExceptionWrapper;
70  *
71  * // Thread1
72  * void doSomethingCrazy() {
73  *   int rc = doSomethingCrazyWithLameReturnCodes();
74  *   if (rc == NAILED_IT) {
75  *     globalExceptionWrapper = exception_wrapper();
76  *   } else if (rc == FACE_PLANT) {
77  *     globalExceptionWrapper = make_exception_wrapper<FacePlantException>();
78  *   } else if (rc == FAIL_WHALE) {
79  *     globalExceptionWrapper = make_exception_wrapper<FailWhaleException>();
80  *   }
81  * }
82  *
83  * // Thread2: Exceptions are ok!
84  * void processResult() {
85  *   try {
86  *     globalExceptionWrapper.throwException();
87  *   } catch (const FacePlantException& e) {
88  *     LOG(ERROR) << "FACEPLANT!";
89  *   } catch (const FailWhaleException& e) {
90  *     LOG(ERROR) << "FAILWHALE!";
91  *   }
92  * }
93  *
94  * // Thread2: Exceptions are bad!
95  * void processResult() {
96  *   globalExceptionWrapper.with_exception(
97  *       [&](FacePlantException& faceplant) {
98  *         LOG(ERROR) << "FACEPLANT";
99  *       }) ||
100  *   globalExceptionWrapper.with_exception(
101  *       [&](FailWhaleException& failwhale) {
102  *         LOG(ERROR) << "FAILWHALE!";
103  *       }) ||
104  *   LOG(FATAL) << "Unrecognized exception";
105  * }
106  * \endcode
107  *
108  */
109 class exception_wrapper {
110  private:
111   template <typename T>
112   using is_exception_ = std::is_base_of<std::exception, T>;
113
114   template <typename Ex>
115   struct optimize;
116
117  public:
118   exception_wrapper() = default;
119
120   // Implicitly construct an exception_wrapper from a qualifying exception.
121   // See the optimize struct for details.
122   template <
123       typename Ex,
124       typename = _t<std::enable_if<optimize<_t<std::decay<Ex>>>::value>>>
125   /* implicit */ exception_wrapper(Ex&& exn) {
126     assign_sptr(std::make_shared<_t<std::decay<Ex>>>(std::forward<Ex>(exn)));
127   }
128
129   // The following two constructors are meant to emulate the behavior of
130   // try_and_catch in performance sensitive code as well as to be flexible
131   // enough to wrap exceptions of unknown type. There is an overload that
132   // takes an exception reference so that the wrapper can extract and store
133   // the exception's type and what() when possible.
134   //
135   // The canonical use case is to construct an all-catching exception wrapper
136   // with minimal overhead like so:
137   //
138   //   try {
139   //     // some throwing code
140   //   } catch (const std::exception& e) {
141   //     // won't lose e's type and what()
142   //     exception_wrapper ew{std::current_exception(), e};
143   //   } catch (...) {
144   //     // everything else
145   //     exception_wrapper ew{std::current_exception()};
146   //   }
147   //
148   // try_and_catch is cleaner and preferable. Use it unless you're sure you need
149   // something like this instead.
150   template <typename Ex>
151   explicit exception_wrapper(std::exception_ptr eptr, Ex& exn) {
152     assign_eptr(eptr, exn);
153   }
154
155   explicit exception_wrapper(std::exception_ptr eptr) {
156     assign_eptr(eptr);
157   }
158
159   // If the exception_wrapper does not contain an exception, std::terminate()
160   // is invoked to assure the [[noreturn]] behaviour.
161   [[noreturn]] void throwException() const;
162
163   explicit operator bool() const {
164     return item_ || eptr_;
165   }
166
167   // This implementation is similar to std::exception_ptr's implementation
168   // where two exception_wrappers are equal when the address in the underlying
169   // reference field both point to the same exception object.  The reference
170   // field remains the same when the exception_wrapper is copied or when
171   // the exception_wrapper is "rethrown".
172   bool operator==(const exception_wrapper& a) const {
173     if (item_) {
174       return a.item_ && item_.get() == a.item_.get();
175     } else {
176       return eptr_ == a.eptr_;
177     }
178   }
179
180   bool operator!=(const exception_wrapper& a) const {
181     return !(*this == a);
182   }
183
184   // This will return a non-nullptr only if the exception is held as a
185   // copy.  It is the only interface which will distinguish between an
186   // exception held this way, and by exception_ptr.  You probably
187   // shouldn't use it at all.
188   std::exception* getCopied() { return item_.get(); }
189   const std::exception* getCopied() const { return item_.get(); }
190
191   fbstring what() const;
192   fbstring class_name() const;
193
194   template <class Ex>
195   bool is_compatible_with() const {
196     return with_exception<Ex>([](const Ex&) {});
197   }
198
199   template <class F>
200   bool with_exception(F&& f) {
201     using arg_type = _t<std::decay<typename functor_traits<F>::arg_type>>;
202     return with_exception<arg_type>(std::forward<F>(f));
203   }
204
205   template <class F>
206   bool with_exception(F&& f) const {
207     using arg_type = _t<std::decay<typename functor_traits<F>::arg_type>>;
208     return with_exception<arg_type>(std::forward<F>(f));
209   }
210
211   // If this exception wrapper wraps an exception of type Ex, with_exception
212   // will call f with the wrapped exception as an argument and return true, and
213   // will otherwise return false.
214   template <class Ex, class F>
215   bool with_exception(F f) {
216     return with_exception1<_t<std::decay<Ex>>>(std::forward<F>(f), this);
217   }
218
219   // Const overload
220   template <class Ex, class F>
221   bool with_exception(F f) const {
222     return with_exception1<_t<std::decay<Ex>>>(std::forward<F>(f), this);
223   }
224
225   std::exception_ptr getExceptionPtr() const {
226     if (eptr_) {
227       return eptr_;
228     }
229
230     try {
231       if (*this) {
232         throwException();
233       }
234     } catch (...) {
235       return std::current_exception();
236     }
237     return std::exception_ptr();
238   }
239
240  private:
241   template <typename Ex>
242   struct optimize {
243     static const bool value =
244       std::is_base_of<std::exception, Ex>::value &&
245       std::is_copy_assignable<Ex>::value &&
246       !std::is_abstract<Ex>::value;
247   };
248
249   template <typename Ex>
250   void assign_sptr(std::shared_ptr<Ex> sptr) {
251     this->item_ = std::move(sptr);
252     this->throwfn_ = Thrower<Ex>::doThrow;
253   }
254
255   template <typename Ex>
256   _t<std::enable_if<is_exception_<Ex>::value>> assign_eptr(
257       std::exception_ptr eptr,
258       Ex& e) {
259     this->eptr_ = eptr;
260     this->eobj_ = &const_cast<_t<std::remove_const<Ex>>&>(e);
261   }
262
263   template <typename Ex>
264   _t<std::enable_if<!is_exception_<Ex>::value>> assign_eptr(
265       std::exception_ptr eptr,
266       Ex& e) {
267     this->eptr_ = eptr;
268     this->etype_ = &typeid(e);
269   }
270
271   void assign_eptr(std::exception_ptr eptr) {
272     this->eptr_ = eptr;
273   }
274
275   // Optimized case: if we know what type the exception is, we can
276   // store a copy of the concrete type, and a helper function so we
277   // can rethrow it.
278   std::shared_ptr<std::exception> item_;
279   void (*throwfn_)(std::exception&){nullptr};
280   // Fallback case: store the library wrapper, which is less efficient
281   // but gets the job done.  Also store exceptionPtr() the name of the
282   // exception type, so we can at least get those back out without
283   // having to rethrow.
284   std::exception_ptr eptr_;
285   std::exception* eobj_{nullptr};
286   const std::type_info* etype_{nullptr};
287
288   template <class T, class... Args>
289   friend exception_wrapper make_exception_wrapper(Args&&... args);
290
291  private:
292   template <typename F>
293   struct functor_traits {
294     template <typename T>
295     struct impl;
296     template <typename C, typename R, typename A>
297     struct impl<R(C::*)(A)> { using arg_type = A; };
298     template <typename C, typename R, typename A>
299     struct impl<R(C::*)(A) const> { using arg_type = A; };
300     using functor_op = decltype(&_t<std::decay<F>>::operator());
301     using arg_type = typename impl<functor_op>::arg_type;
302   };
303
304   template <class T>
305   class Thrower {
306    public:
307     static void doThrow(std::exception& obj) {
308       throw static_cast<T&>(obj);
309     }
310   };
311
312   template <typename T, typename F>
313   static _t<std::enable_if<is_exception_<T>::value, T*>>
314   try_dynamic_cast_exception(F* from) {
315     return dynamic_cast<T*>(from);
316   }
317   template <typename T, typename F>
318   static _t<std::enable_if<!is_exception_<T>::value, T*>>
319   try_dynamic_cast_exception(F*) {
320     return nullptr;
321   }
322
323   // What makes this useful is that T can be exception_wrapper* or
324   // const exception_wrapper*, and the compiler will use the
325   // instantiation which works with F.
326   template <class Ex, class F, class T>
327   static bool with_exception1(F f, T* that) {
328     using CEx = _t<std::conditional<std::is_const<T>::value, const Ex, Ex>>;
329     if (is_exception_<Ex>::value &&
330         (that->item_ || (that->eptr_ && that->eobj_))) {
331       auto raw =
332           that->item_ ? that->item_.get() : that->eptr_ ? that->eobj_ : nullptr;
333       if (auto ex = try_dynamic_cast_exception<CEx>(raw)) {
334         f(*ex);
335         return true;
336       }
337     } else if (that->eptr_) {
338       try {
339         std::rethrow_exception(that->eptr_);
340       } catch (CEx& e) {
341         f(e);
342         return true;
343       } catch (...) {
344         // fall through
345       }
346     }
347     return false;
348   }
349 };
350
351 template <class T, class... Args>
352 exception_wrapper make_exception_wrapper(Args&&... args) {
353   exception_wrapper ew;
354   ew.assign_sptr(std::make_shared<T>(std::forward<Args>(args)...));
355   return ew;
356 }
357
358 // For consistency with exceptionStr() functions in ExceptionString.h
359 fbstring exceptionStr(const exception_wrapper& ew);
360
361 /*
362  * try_and_catch is a simple replacement for try {} catch(){} that allows you to
363  * specify which derived exceptions you would like to catch and store in an
364  * exception_wrapper.
365  *
366  * Because we cannot build an equivalent of std::current_exception(), we need
367  * to catch every derived exception that we are interested in catching.
368  *
369  * Exceptions should be listed in the reverse order that you would write your
370  * catch statements (that is, std::exception& should be first).
371  *
372  * NOTE: Although implemented as a derived class (for syntactic delight), don't
373  * be confused - you should not pass around try_and_catch objects!
374  *
375  * Example Usage:
376  *
377  * // This catches my runtime_error and if I call throwException() on ew, it
378  * // will throw a runtime_error
379  * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
380  *   if (badThingHappens()) {
381  *     throw std::runtime_error("ZOMG!");
382  *   }
383  * });
384  *
385  * // This will catch the exception and if I call throwException() on ew, it
386  * // will throw a std::exception
387  * auto ew = folly::try_and_catch<std::exception, std::runtime_error>([=]() {
388  *   if (badThingHappens()) {
389  *     throw std::exception();
390  *   }
391  * });
392  *
393  * // This will not catch the exception and it will be thrown.
394  * auto ew = folly::try_and_catch<std::runtime_error>([=]() {
395  *   if (badThingHappens()) {
396  *     throw std::exception();
397  *   }
398  * });
399  */
400
401 namespace try_and_catch_detail {
402
403 template <typename... Args>
404 using is_wrap_ctor = std::is_constructible<exception_wrapper, Args...>;
405
406 template <typename Ex>
407 inline _t<std::enable_if<!is_wrap_ctor<Ex&>::value, exception_wrapper>> make(
408     Ex& ex) {
409   return exception_wrapper(std::current_exception(), ex);
410 }
411
412 template <typename Ex>
413 inline _t<std::enable_if<is_wrap_ctor<Ex&>::value, exception_wrapper>> make(
414     Ex& ex) {
415   return typeid(Ex&) == typeid(ex)
416       ? exception_wrapper(ex)
417       : exception_wrapper(std::current_exception(), ex);
418 }
419
420 template <typename F>
421 inline exception_wrapper impl(F&& f) {
422   return (f(), exception_wrapper());
423 }
424
425 template <typename F, typename Ex, typename... Exs>
426 inline exception_wrapper impl(F&& f) {
427   try {
428     return impl<F, Exs...>(std::forward<F>(f));
429   } catch (Ex& ex) {
430     return make(ex);
431   }
432 }
433 } // try_and_catch_detail
434
435 template <typename... Exceptions, typename F>
436 exception_wrapper try_and_catch(F&& fn) {
437   return try_and_catch_detail::impl<F, Exceptions...>(std::forward<F>(fn));
438 }
439 } // folly