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