non-throwing, non-allocating exception_wrapper
[folly.git] / folly / ExceptionWrapper-inl.h
1 /*
2  * Copyright 2017-present 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  *
18  * Author: Eric Niebler <eniebler@fb.com>
19  */
20
21 namespace folly {
22
23 template <class Fn>
24 struct exception_wrapper::arg_type2_ {};
25 template <class Ret, class Class, class Arg>
26 struct exception_wrapper::arg_type2_<Ret (Class::*)(Arg)> {
27   using type = Arg;
28 };
29 template <class Ret, class Class, class Arg>
30 struct exception_wrapper::arg_type2_<Ret (Class::*)(Arg) const> {
31   using type = Arg;
32 };
33 template <class Ret, class Class>
34 struct exception_wrapper::arg_type2_<Ret (Class::*)(...)> {
35   using type = AnyException;
36 };
37 template <class Ret, class Class>
38 struct exception_wrapper::arg_type2_<Ret (Class::*)(...) const> {
39   using type = AnyException;
40 };
41
42 template <class Fn, class>
43 struct exception_wrapper::arg_type_ {};
44 template <class Fn>
45 struct exception_wrapper::arg_type_<Fn, void_t<decltype(&Fn::operator())>>
46     : public arg_type2_<decltype(&Fn::operator())> {};
47 template <class Ret, class Arg>
48 struct exception_wrapper::arg_type_<Ret (*)(Arg)> {
49   using type = Arg;
50 };
51 template <class Ret>
52 struct exception_wrapper::arg_type_<Ret (*)(...)> {
53   using type = AnyException;
54 };
55
56 template <class Ret, class... Args>
57 inline Ret exception_wrapper::noop_(Args...) {
58   return Ret();
59 }
60
61 inline std::type_info const* exception_wrapper::uninit_type_(
62     exception_wrapper const*) {
63   return &typeid(void);
64 }
65
66 template <class Ex, class DEx>
67 inline exception_wrapper::Buffer::Buffer(in_place_t, Ex&& ex) {
68   ::new (static_cast<void*>(&buff_)) DEx(std::forward<Ex>(ex));
69 }
70
71 template <class Ex>
72 inline Ex& exception_wrapper::Buffer::as() noexcept {
73   return *static_cast<Ex*>(static_cast<void*>(&buff_));
74 }
75 template <class Ex>
76 inline Ex const& exception_wrapper::Buffer::as() const noexcept {
77   return *static_cast<Ex const*>(static_cast<void const*>(&buff_));
78 }
79
80 inline std::exception const* exception_wrapper::as_exception_or_null_(
81     std::exception const& ex) {
82   return &ex;
83 }
84 inline std::exception const* exception_wrapper::as_exception_or_null_(
85     AnyException) {
86   return nullptr;
87 }
88
89 inline std::uintptr_t exception_wrapper::ExceptionPtr::as_int_(
90     std::exception const& e) {
91   return reinterpret_cast<std::uintptr_t>(&e);
92 }
93 inline std::uintptr_t exception_wrapper::ExceptionPtr::as_int_(AnyException e) {
94   return reinterpret_cast<std::uintptr_t>(e.typeinfo_) + 1;
95 }
96 inline bool exception_wrapper::ExceptionPtr::has_exception_() const {
97   return 0 == exception_or_type_ % 2;
98 }
99 inline std::exception const* exception_wrapper::ExceptionPtr::as_exception_()
100     const {
101   return reinterpret_cast<std::exception const*>(exception_or_type_);
102 }
103 inline std::type_info const* exception_wrapper::ExceptionPtr::as_type_() const {
104   return reinterpret_cast<std::type_info const*>(exception_or_type_ - 1);
105 }
106
107 inline void exception_wrapper::ExceptionPtr::copy_(
108     exception_wrapper const* from, exception_wrapper* to) {
109   ::new (static_cast<void*>(&to->eptr_)) ExceptionPtr(from->eptr_);
110 }
111 inline void exception_wrapper::ExceptionPtr::move_(
112     exception_wrapper* from, exception_wrapper* to) {
113   ::new (static_cast<void*>(&to->eptr_))
114       ExceptionPtr(std::move(from->eptr_));
115   delete_(from);
116 }
117 inline void exception_wrapper::ExceptionPtr::delete_(
118     exception_wrapper* that) {
119   that->eptr_.~ExceptionPtr();
120   that->vptr_ = &uninit_;
121 }
122 [[noreturn]] inline void exception_wrapper::ExceptionPtr::throw_(
123     exception_wrapper const* that) {
124   std::rethrow_exception(that->eptr_.ptr_);
125 }
126 inline std::type_info const* exception_wrapper::ExceptionPtr::type_(
127     exception_wrapper const* that) {
128   if (auto e = get_exception_(that)) {
129     return &typeid(*e);
130   }
131   return that->eptr_.as_type_();
132 }
133 inline std::exception const* exception_wrapper::ExceptionPtr::get_exception_(
134     exception_wrapper const* that) {
135   return that->eptr_.has_exception_() ? that->eptr_.as_exception_()
136                                       : nullptr;
137 }
138 inline exception_wrapper exception_wrapper::ExceptionPtr::get_exception_ptr_(
139     exception_wrapper const* that) {
140   return *that;
141 }
142
143 template <class Ex>
144 inline void exception_wrapper::InPlace<Ex>::copy_(
145     exception_wrapper const* from, exception_wrapper* to) {
146   ::new (static_cast<void*>(std::addressof(to->buff_.as<Ex>())))
147       Ex(from->buff_.as<Ex>());
148 }
149 template <class Ex>
150 inline void exception_wrapper::InPlace<Ex>::move_(
151     exception_wrapper* from, exception_wrapper* to) {
152   ::new (static_cast<void*>(std::addressof(to->buff_.as<Ex>())))
153       Ex(std::move(from->buff_.as<Ex>()));
154   delete_(from);
155 }
156 template <class Ex>
157 inline void exception_wrapper::InPlace<Ex>::delete_(
158     exception_wrapper* that) {
159   that->buff_.as<Ex>().~Ex();
160   that->vptr_ = &uninit_;
161 }
162 template <class Ex>
163 [[noreturn]] inline void exception_wrapper::InPlace<Ex>::throw_(
164     exception_wrapper const* that) {
165   throw that->buff_.as<Ex>(); // @nolint
166 }
167 template <class Ex>
168 inline std::type_info const* exception_wrapper::InPlace<Ex>::type_(
169     exception_wrapper const*) {
170   return &typeid(Ex);
171 }
172 template <class Ex>
173 inline std::exception const* exception_wrapper::InPlace<Ex>::get_exception_(
174     exception_wrapper const* that) {
175   return as_exception_or_null_(that->buff_.as<Ex>());
176 }
177 template <class Ex>
178 inline exception_wrapper exception_wrapper::InPlace<Ex>::get_exception_ptr_(
179     exception_wrapper const* that) {
180   try {
181     throw_(that);
182   } catch (Ex const& ex) {
183     return exception_wrapper{std::current_exception(), ex};
184   }
185 }
186
187 template <class Ex>
188 [[noreturn]] inline void
189 exception_wrapper::SharedPtr::Impl<Ex>::throw_() const {
190   throw ex_; // @nolint
191 }
192 template <class Ex>
193 inline std::exception const*
194 exception_wrapper::SharedPtr::Impl<Ex>::get_exception_() const noexcept {
195   return as_exception_or_null_(ex_);
196 }
197 template <class Ex>
198 inline exception_wrapper
199 exception_wrapper::SharedPtr::Impl<Ex>::get_exception_ptr_() const noexcept {
200   try {
201     throw_();
202   } catch (Ex& ex) {
203     return exception_wrapper{std::current_exception(), ex};
204   }
205 }
206 inline void exception_wrapper::SharedPtr::copy_(
207     exception_wrapper const* from, exception_wrapper* to) {
208   ::new (static_cast<void*>(std::addressof(to->sptr_)))
209       SharedPtr(from->sptr_);
210 }
211 inline void exception_wrapper::SharedPtr::move_(
212     exception_wrapper* from, exception_wrapper* to) {
213   ::new (static_cast<void*>(std::addressof(to->sptr_)))
214       SharedPtr(std::move(from->sptr_));
215   delete_(from);
216 }
217 inline void exception_wrapper::SharedPtr::delete_(
218     exception_wrapper* that) {
219   that->sptr_.~SharedPtr();
220   that->vptr_ = &uninit_;
221 }
222 [[noreturn]] inline void exception_wrapper::SharedPtr::throw_(
223     exception_wrapper const* that) {
224   that->sptr_.ptr_->throw_();
225   folly::assume_unreachable();
226 }
227 inline std::type_info const* exception_wrapper::SharedPtr::type_(
228     exception_wrapper const* that) {
229   return that->sptr_.ptr_->info_;
230 }
231 inline std::exception const* exception_wrapper::SharedPtr::get_exception_(
232     exception_wrapper const* that) {
233   return that->sptr_.ptr_->get_exception_();
234 }
235 inline exception_wrapper exception_wrapper::SharedPtr::get_exception_ptr_(
236     exception_wrapper const* that) {
237   return that->sptr_.ptr_->get_exception_ptr_();
238 }
239
240 template <class Ex, class DEx>
241 inline exception_wrapper::exception_wrapper(Ex&& ex, OnHeapTag)
242     : sptr_{std::make_shared<SharedPtr::Impl<DEx>>(std::forward<Ex>(ex))},
243       vptr_(&SharedPtr::ops_) {}
244
245 template <class Ex, class DEx>
246 inline exception_wrapper::exception_wrapper(Ex&& ex, InSituTag)
247     : buff_{in_place, std::forward<Ex>(ex)}, vptr_(&InPlace<DEx>::ops_) {}
248
249 inline exception_wrapper::exception_wrapper(exception_wrapper&& that) noexcept
250     : exception_wrapper{} {
251   (vptr_ = that.vptr_)->move_(&that, this); // Move into *this, won't throw
252 }
253
254 inline exception_wrapper::exception_wrapper(
255     exception_wrapper const& that) : exception_wrapper{} {
256   that.vptr_->copy_(&that, this); // could throw
257   vptr_ = that.vptr_;
258 }
259
260 // If `this == &that`, this move assignment operator leaves the object in a
261 // valid but unspecified state.
262 inline exception_wrapper& exception_wrapper::operator=(
263     exception_wrapper&& that) noexcept {
264   vptr_->delete_(this); // Free the current exception
265   (vptr_ = that.vptr_)->move_(&that, this); // Move into *this, won't throw
266   return *this;
267 }
268
269 inline exception_wrapper& exception_wrapper::operator=(
270     exception_wrapper const& that) {
271   exception_wrapper(that).swap(*this);
272   return *this;
273 }
274
275 inline exception_wrapper::~exception_wrapper() {
276   reset();
277 }
278
279 template <class Ex>
280 inline exception_wrapper::exception_wrapper(std::exception_ptr ptr, Ex& ex)
281     : eptr_{std::move(ptr), ExceptionPtr::as_int_(ex)},
282       vptr_(&ExceptionPtr::ops_) {
283   assert(eptr_.ptr_);
284 }
285
286 template <
287     class Ex,
288     class Ex_,
289     FOLLY_REQUIRES_DEF(
290         Conjunction<
291             exception_wrapper::IsStdException<Ex_>,
292             exception_wrapper::IsRegularExceptionType<Ex_>>())>
293 inline exception_wrapper::exception_wrapper(Ex&& ex)
294     : exception_wrapper{std::forward<Ex>(ex), PlacementOf<Ex_>{}} {
295   // Don't slice!!!
296   assert(typeid(ex) == typeid(Ex_) ||
297        !"Dynamic and static exception types don't match. Exception would "
298         "be sliced when storing in exception_wrapper.");
299 }
300
301 template <
302     class Ex,
303     class Ex_,
304     FOLLY_REQUIRES_DEF(
305         exception_wrapper::IsRegularExceptionType<Ex_>())>
306 inline exception_wrapper::exception_wrapper(in_place_t, Ex&& ex)
307     : exception_wrapper{std::forward<Ex>(ex), PlacementOf<Ex_>{}} {
308   // Don't slice!!!
309   assert(typeid(ex) == typeid(Ex_) ||
310        !"Dynamic and static exception types don't match. Exception would "
311         "be sliced when storing in exception_wrapper.");
312 }
313
314 inline void exception_wrapper::swap(exception_wrapper& that) noexcept {
315   exception_wrapper tmp(std::move(that));
316   that = std::move(*this);
317   *this = std::move(tmp);
318 }
319
320 inline exception_wrapper::operator bool() const noexcept {
321   return vptr_ != &uninit_;
322 }
323
324 inline bool exception_wrapper::operator!() const noexcept {
325   return !static_cast<bool>(*this);
326 }
327
328 inline void exception_wrapper::reset() {
329   vptr_->delete_(this);
330 }
331
332 inline bool exception_wrapper::has_exception_ptr() const noexcept {
333   return vptr_ == &ExceptionPtr::ops_;
334 }
335
336 inline std::exception* exception_wrapper::get_exception() noexcept {
337   return const_cast<std::exception*>(vptr_->get_exception_(this));
338 }
339 inline std::exception const* exception_wrapper::get_exception() const noexcept {
340   return vptr_->get_exception_(this);
341 }
342
343 inline std::exception_ptr const& exception_wrapper::to_exception_ptr()
344     noexcept {
345   // Computing an exception_ptr is expensive so cache the result.
346   return (*this = vptr_->get_exception_ptr_(this)).eptr_.ptr_;
347 }
348 inline std::exception_ptr exception_wrapper::to_exception_ptr() const noexcept {
349   return vptr_->get_exception_ptr_(this).eptr_.ptr_;
350 }
351
352 inline std::type_info const& exception_wrapper::none() noexcept {
353   return typeid(void);
354 }
355 inline std::type_info const& exception_wrapper::unknown() noexcept {
356   return typeid(Unknown);
357 }
358
359 inline std::type_info const& exception_wrapper::type() const noexcept {
360   return *vptr_->type_(this);
361 }
362
363 inline folly::fbstring exception_wrapper::what() const {
364   if (auto e = get_exception()) {
365     return class_name() + ": " + e->what();
366   }
367   return class_name();
368 }
369
370 inline folly::fbstring exception_wrapper::class_name() const {
371   auto& ti = type();
372   return ti == none()
373       ? ""
374       : ti == unknown() ? "<unknown exception>" : folly::demangle(ti);
375 }
376
377 template <class Ex>
378 inline bool exception_wrapper::is_compatible_with() const noexcept {
379   return with_exception([](Ex const&) {});
380 }
381
382 [[noreturn]] inline void exception_wrapper::throwException() const {
383   vptr_->throw_(this);
384   onNoExceptionError();
385 }
386
387 template <class CatchFn, bool IsConst>
388 struct exception_wrapper::ExceptionTypeOf {
389   using type = arg_type<_t<std::decay<CatchFn>>>;
390   static_assert(
391       std::is_reference<type>::value,
392       "Always catch exceptions by reference.");
393   static_assert(
394       !IsConst || std::is_const<_t<std::remove_reference<type>>>::value,
395       "handle() or with_exception() called on a const exception_wrapper "
396       "and asked to catch a non-const exception. Handler will never fire. "
397       "Catch exception by const reference to fix this.");
398 };
399
400 // Nests a throw in the proper try/catch blocks
401 template <bool IsConst>
402 struct exception_wrapper::HandleReduce {
403   bool* handled_;
404
405   template <
406       class ThrowFn,
407       class CatchFn,
408       FOLLY_REQUIRES(!IsCatchAll<CatchFn>::value)>
409   auto operator()(ThrowFn&& th, CatchFn& ca) const {
410     using Ex = _t<ExceptionTypeOf<CatchFn, IsConst>>;
411     return [ th = std::forward<ThrowFn>(th), &ca, handled_ = handled_ ] {
412       try {
413         th();
414       } catch (Ex& e) {
415         // If we got here because a catch function threw, rethrow.
416         if (*handled_) {
417           throw;
418         }
419         *handled_ = true;
420         ca(e);
421       }
422     };
423   }
424
425   template <
426       class ThrowFn,
427       class CatchFn,
428       FOLLY_REQUIRES(IsCatchAll<CatchFn>::value)>
429   auto operator()(ThrowFn&& th, CatchFn& ca) const {
430     return [ th = std::forward<ThrowFn>(th), &ca, handled_ = handled_ ] {
431       try {
432         th();
433       } catch (...) {
434         // If we got here because a catch function threw, rethrow.
435         if (*handled_) {
436           throw;
437         }
438         *handled_ = true;
439         ca();
440       }
441     };
442   }
443 };
444
445 // When all the handlers expect types derived from std::exception, we can
446 // sometimes invoke the handlers without throwing any exceptions.
447 template <bool IsConst>
448 struct exception_wrapper::HandleStdExceptReduce {
449   using StdEx = AddConstIf<IsConst, std::exception>;
450
451   template <
452       class ThrowFn,
453       class CatchFn,
454       FOLLY_REQUIRES(!IsCatchAll<CatchFn>::value)>
455   auto operator()(ThrowFn&& th, CatchFn& ca) const {
456     using Ex = _t<ExceptionTypeOf<CatchFn, IsConst>>;
457     return [ th = std::forward<ThrowFn>(th), &ca ](auto&& continuation)
458         -> StdEx* {
459       if (auto e = const_cast<StdEx*>(th(continuation))) {
460         if (auto e2 = dynamic_cast<_t<std::add_pointer<Ex>>>(e)) {
461           ca(*e2);
462         } else {
463           return e;
464         }
465       }
466       return nullptr;
467     };
468   }
469
470   template <
471       class ThrowFn,
472       class CatchFn,
473       FOLLY_REQUIRES(IsCatchAll<CatchFn>::value)>
474   auto operator()(ThrowFn&& th, CatchFn& ca) const {
475     return [ th = std::forward<ThrowFn>(th), &ca ](auto&&) -> StdEx* {
476       // The following continuation causes ca() to execute if *this contains
477       // an exception /not/ derived from std::exception.
478       auto continuation = [&ca](StdEx* e) {
479         return e != nullptr ? e : ((void)ca(), nullptr);
480       };
481       if (th(continuation) != nullptr) {
482         ca();
483       }
484       return nullptr;
485     };
486   }
487 };
488
489 // Called when some types in the catch clauses are not derived from
490 // std::exception.
491 template <class This, class... CatchFns>
492 inline void exception_wrapper::handle_(
493     std::false_type, This& this_, CatchFns&... fns) {
494   bool handled = false;
495   auto impl = exception_wrapper_detail::fold(
496       HandleReduce<std::is_const<This>::value>{&handled},
497       [&] { this_.throwException(); },
498       fns...);
499   impl();
500 }
501
502 // Called when all types in the catch clauses are either derived from
503 // std::exception or a catch-all clause.
504 template <class This, class... CatchFns>
505 inline void exception_wrapper::handle_(
506     std::true_type, This& this_, CatchFns&... fns) {
507   using StdEx = exception_wrapper_detail::
508       AddConstIf<std::is_const<This>::value, std::exception>;
509   auto impl = exception_wrapper_detail::fold(
510       HandleStdExceptReduce<std::is_const<This>::value>{},
511       [&](auto&& continuation) {
512         return continuation(
513             const_cast<StdEx*>(this_.vptr_->get_exception_(&this_)));
514       },
515       fns...);
516   // This continuation gets evaluated if CatchFns... does not include a
517   // catch-all handler. It is a no-op.
518   auto continuation = [](StdEx* ex) { return ex; };
519   if (StdEx* e = impl(continuation)) {
520     throw *e; // Not handled. Throw.
521   }
522 }
523
524 namespace exception_wrapper_detail {
525 template <class Ex, class Fn>
526 struct catch_fn {
527   Fn fn_;
528   auto operator()(Ex& ex) {
529     return fn_(ex);
530   }
531 };
532
533 template <class Ex, class Fn>
534 inline catch_fn<Ex, Fn> catch_(Ex*, Fn fn) {
535   return {std::move(fn)};
536 }
537 template <class Fn>
538 inline Fn catch_(void const*, Fn fn) {
539   return fn;
540 }
541 } // namespace exception_wrapper_detail
542
543 template <class Ex, class This, class Fn>
544 inline bool exception_wrapper::with_exception_(This& this_, Fn fn_) {
545   if (!this_) {
546     return false;
547   }
548   bool handled = true;
549   auto fn = exception_wrapper_detail::catch_(
550       static_cast<Ex*>(nullptr), std::move(fn_));
551   auto&& all = [&](...) { handled = false; };
552   handle_(IsStdException<arg_type<decltype(fn)>>{}, this_, fn, all);
553   return handled;
554 }
555
556 template <class Ex, class Fn>
557 inline bool exception_wrapper::with_exception(Fn fn) {
558   return with_exception_<Ex>(*this, std::move(fn));
559 }
560 template <class Ex, class Fn>
561 inline bool exception_wrapper::with_exception(Fn fn) const {
562   return with_exception_<Ex const>(*this, std::move(fn));
563 }
564
565 template <class... CatchFns>
566 inline void exception_wrapper::handle(CatchFns... fns) {
567   using AllStdEx =
568       exception_wrapper_detail::AllOf<IsStdException, arg_type<CatchFns>...>;
569   if (!*this) {
570     onNoExceptionError();
571   }
572   this->handle_(AllStdEx{}, *this, fns...);
573 }
574 template <class... CatchFns>
575 inline void exception_wrapper::handle(CatchFns... fns) const {
576   using AllStdEx =
577       exception_wrapper_detail::AllOf<IsStdException, arg_type<CatchFns>...>;
578   if (!*this) {
579     onNoExceptionError();
580   }
581   this->handle_(AllStdEx{}, *this, fns...);
582 }
583
584 } // namespace folly