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