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