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