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