Skip to content

Commit 89091b6

Browse files
snarkmasterfacebook-github-bot
authored andcommitted
Make folly::get_mutable_exception separate from folly::get_exception
Summary: `get_mutable_exception` is a separate verb because: - Mutable exception access is rare. It may run into thread-safety bugs if a `std::current_exception()` pointer is accessed outside of the thread that threw it -- the standard permits reference semantics here! - Making mutable access explicit enables no-allocations, no-atomics optimizations for the `const`-access path. I specifically ran into the *latter* issue, since I was implementing immortal exception pointers, and there's no 0-overhead way for those to provide mutable access. On the other hand, with `get_mutable_exception`, it's completely fine for the accessor to modify the state of the potentially-immortal exception pointer to contain a just-copied `std::exception_ptr`. Reviewed By: ispeters Differential Revision: D73390138 fbshipit-source-id: ebfa60d54462cd372340b76b4dad08b24096e81a
1 parent 55657a8 commit 89091b6

File tree

6 files changed

+144
-54
lines changed

6 files changed

+144
-54
lines changed

third-party/folly/src/folly/ExceptionWrapper.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -338,13 +338,13 @@ class exception_wrapper final {
338338
template <class... CatchFns>
339339
void handle(CatchFns... fns) const;
340340

341-
// Implementation of `folly::get_exception<Ex>(ew)`
341+
// Implement the `folly::get_exception<Ex>(ew)` protocol
342342
template <typename Ex>
343-
Ex* get_exception(get_exception_tag_t) noexcept {
343+
Ex const* get_exception(get_exception_tag_t) const noexcept {
344344
return get_exception<Ex>();
345345
}
346346
template <typename Ex>
347-
Ex const* get_exception(get_exception_tag_t) const noexcept {
347+
Ex* get_mutable_exception(get_exception_tag_t) noexcept {
348348
return get_exception<Ex>();
349349
}
350350
};

third-party/folly/src/folly/lang/Exception.h

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -589,29 +589,44 @@ T* exception_ptr_get_object_hint(std::exception_ptr const& ptr) noexcept {
589589

590590
/// get_exception_tag_t
591591
///
592-
/// Passkey: To extend `folly::get_exception<Ex>()`, a type must have the member
593-
/// `get_exception<Ex>(get_exception_tag_t)`.
592+
/// A type that may contain an exception may take this passkey in the following
593+
/// member functions:
594+
/// - `get_exception<Ex>(get_exception_tag_t) const` when implementing the
595+
/// `folly::get_exception<Ex>()` protocol.
596+
/// - `get_mutable_exception<Ex>(get_exception_tag_t)` when implementing the
597+
/// `folly::get_mutable_exception<Ex>()` protocol.
594598
struct get_exception_tag_t {};
595599

596600
/// get_exception_fn
597601
/// get_exception
602+
/// get_mutable_exception_fn
603+
/// get_mutable_exception
598604
///
599-
/// `get_exception<Ex>(v)` is meant to become a uniform way for accessing
600-
/// exception-containers in `folly`. It returns:
605+
/// `get_exception<Ex>(v)` is meant to become the default way for accessing
606+
/// exception-containers in `folly`.
607+
///
608+
/// For the less-common scenario where you need mutable access to an error, use
609+
/// `get_mutable_exception<Ex>(v)`. This is a separate verb because:
610+
/// - Mutable exception access is rare. It may run into thread-safety bugs
611+
/// if a `std::current_exception()` pointer is accessed outside of the
612+
/// thread that threw it -- the standard permits reference semantics here!
613+
/// - Making mutable access explicit enables no-alloctions, no-atomics
614+
/// optimizations for the `const`-access path.
615+
///
616+
/// Both verbs return:
601617
/// - `nullptr` if `v` is of a variant type, but is not in an "error" state,
602618
/// - A pointer to the `Ex` held by `v`, if it holds an error whose type
603619
/// `From` permits `std::is_convertible<From*, Ex*>`,
604620
/// - `nullptr` for errors incompatible with `Ex*`.
605621
///
606622
/// In addition to the `std::exception_ptr` support above, a type can support
607-
/// this verb by providing a `get_exception<Ex>(get_exception_tag_t)` member.
608-
/// For an example, see `ExceptionWrapper.h`. Requirements:
623+
/// this verb by providing member functions taking `get_exception_tag_t`. For
624+
/// an example, see `ExceptionWrapper.h`. Requirements:
609625
/// - `noexcept`
610-
/// - `const`-correct
611-
/// - returns `Ex*` or `const Ex*` depending on the overload.
626+
/// - returns `Ex*` or `const Ex*` depending on the verb.
612627
///
613628
/// This is most efficient when `Ex` matches the exact stored type, or when the
614-
/// type alias `Ex::folly_get_exception_hint_types` has a good hint.
629+
/// type alias `Ex::folly_get_exception_hint_types` provides a correct hint.
615630
///
616631
/// NB: `result<T>` supports `get_exception<Ex>(res)`, but `Try<T>` currently
617632
/// omits `get_exception(get_exception_tag_t)`, because that might encourage
@@ -624,41 +639,66 @@ struct get_exception_tag_t {};
624639
/// }
625640
template <typename Ex>
626641
class get_exception_fn {
627-
private:
628-
template <typename CEx, typename SrcRef>
629-
CEx* impl(SrcRef src) const noexcept {
630-
using Src = remove_cvref_t<SrcRef>;
642+
public:
643+
template <typename Src>
644+
const Ex* operator()(const Src& src) const noexcept {
631645
if constexpr (std::is_same_v<Src, std::exception_ptr>) {
632-
return exception_ptr_get_object_hint<CEx>(src);
646+
return exception_ptr_get_object_hint<const Ex>(src);
633647
} else {
634648
constexpr get_exception_tag_t passkey;
635649
static_assert( // Return type & `noexcept`ness must match
636650
std::is_same_v<
637-
CEx*,
651+
const Ex*,
638652
decltype(src.template get_exception<Ex>(passkey))> &&
639653
noexcept(noexcept(src.template get_exception<Ex>(passkey))));
640654
return src.template get_exception<Ex>(passkey);
641655
}
642656
}
643-
644-
public:
657+
// For a mutable ptr, use `folly::get_mutable_exception<Ex>(v)` instead.
645658
template <typename Src>
646-
const Ex* operator()(const Src& src) const noexcept {
647-
return impl<const Ex, const Src&>(src);
659+
const Ex* operator()(Src& s) const noexcept {
660+
return operator()(std::as_const(s));
648661
}
662+
663+
// It is unsafe to use `get_exception()` to get a pointer into an rvalue.
664+
// If you know what you're doing, add a `static_cast`.
665+
template <typename Src>
666+
void operator()(Src&&) const noexcept = delete;
667+
template <typename Src>
668+
void operator()(const Src&&) const noexcept = delete;
669+
};
670+
template <typename Ex>
671+
class get_mutable_exception_fn {
672+
public:
649673
template <typename Src>
650674
Ex* operator()(Src& src) const noexcept {
651-
return impl<Ex, Src&>(src);
675+
if constexpr (std::is_same_v<Src, std::exception_ptr>) {
676+
return exception_ptr_get_object_hint<Ex>(src);
677+
} else {
678+
constexpr get_exception_tag_t passkey;
679+
static_assert( // Return type & `noexcept`ness must match
680+
std::is_same_v<
681+
Ex*,
682+
decltype(src.template get_mutable_exception<Ex>(passkey))> &&
683+
noexcept(noexcept(src.template get_mutable_exception<Ex>(passkey))));
684+
return src.template get_mutable_exception<Ex>(passkey);
685+
}
652686
}
653-
// It is unsafe to use `get_exception()` to get a pointer into an rvalue.
654-
// If you know what you're doing, add a `static_cast`.
687+
// You want `folly::get_exception<Ex>(v)` instead.
688+
template <typename Src>
689+
void operator()(const Src&) const noexcept = delete;
690+
691+
// It is unsafe to use `get_mutable_exception()` to get a pointer into an
692+
// rvalue. If you know what you're doing, add a `static_cast`.
655693
template <typename Src>
656694
void operator()(Src&&) const noexcept = delete;
657-
template <typename Src> // NB: Actually, redundant with `Src&&` overload.
695+
template <typename Src>
658696
void operator()(const Src&&) const noexcept = delete;
659697
};
660698
template <typename Ex = std::exception>
661699
inline constexpr get_exception_fn<Ex> get_exception{};
700+
template <typename Ex = std::exception>
701+
inline constexpr get_mutable_exception_fn<Ex> get_mutable_exception{};
662702

663703
namespace detail {
664704

third-party/folly/src/folly/lang/test/ExceptionTest.cpp

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -423,15 +423,21 @@ TEST_F(ExceptionTest, make_exception_ptr_with_in_place) {
423423

424424
TEST_F(ExceptionTest, get_exception_from_std_exception_ptr) {
425425
using folly::get_exception;
426+
using folly::get_mutable_exception;
426427

427428
static_assert(
428-
std::is_invocable_v< //
429+
std::is_invocable_v<
429430
folly::get_exception_fn<std::exception>,
430-
std::exception_ptr&>);
431+
const std::exception_ptr&>);
431432
static_assert(
432433
std::is_invocable_v<
433-
folly::get_exception_fn<std::exception>,
434+
folly::get_mutable_exception_fn<std::exception>,
435+
std::exception_ptr&>);
436+
static_assert(
437+
!std::is_invocable_v<
438+
folly::get_mutable_exception_fn<std::exception>,
434439
const std::exception_ptr&>);
440+
435441
// Unsafe to extract a pointer out of rvalues
436442
static_assert(
437443
!std::is_invocable_v<
@@ -441,6 +447,14 @@ TEST_F(ExceptionTest, get_exception_from_std_exception_ptr) {
441447
!std::is_invocable_v<
442448
folly::get_exception_fn<std::exception>,
443449
const std::exception_ptr&&>);
450+
static_assert(
451+
!std::is_invocable_v<
452+
folly::get_mutable_exception_fn<std::exception>,
453+
std::exception_ptr&&>);
454+
static_assert(
455+
!std::is_invocable_v<
456+
folly::get_mutable_exception_fn<std::exception>,
457+
const std::exception_ptr&&>);
444458

445459
auto eptr = folly::make_exception_ptr_with([]() {
446460
return std::runtime_error{"foo"};
@@ -449,17 +463,33 @@ TEST_F(ExceptionTest, get_exception_from_std_exception_ptr) {
449463
EXPECT_EQ(nullptr, get_exception<std::system_error>(eptr));
450464

451465
EXPECT_STREQ("foo", get_exception<std::exception>(eptr)->what());
466+
EXPECT_STREQ(
467+
"foo", get_exception<std::exception>(std::as_const(eptr))->what());
468+
EXPECT_STREQ("foo", get_mutable_exception<std::exception>(eptr)->what());
469+
452470
EXPECT_STREQ("foo", get_exception<const std::exception>(eptr)->what());
471+
EXPECT_STREQ(
472+
"foo", get_exception<const std::exception>(std::as_const(eptr))->what());
473+
// While this is a very silly kind of usage, it does work.
474+
EXPECT_STREQ(
475+
"foo", get_mutable_exception<const std::exception>(eptr)->what());
476+
453477
EXPECT_STREQ("foo", get_exception<>(eptr)->what());
478+
EXPECT_STREQ("foo", get_exception<>(std::as_const(eptr))->what());
479+
EXPECT_STREQ("foo", get_mutable_exception<>(eptr)->what());
454480

455481
EXPECT_STREQ("foo", get_exception<std::runtime_error>(eptr)->what());
456-
EXPECT_EQ(
457-
folly::exception_ptr_get_object<std::runtime_error>(eptr),
458-
get_exception<std::runtime_error>(eptr));
482+
EXPECT_STREQ(
483+
"foo", get_exception<std::runtime_error>(std::as_const(eptr))->what());
484+
EXPECT_STREQ("foo", get_mutable_exception<std::runtime_error>(eptr)->what());
485+
486+
auto* expected_p = folly::exception_ptr_get_object<std::runtime_error>(eptr);
487+
EXPECT_EQ(expected_p, get_exception<std::runtime_error>(eptr));
488+
EXPECT_EQ(expected_p, get_exception<std::runtime_error>(std::as_const(eptr)));
459489

460490
static_assert(
461491
std::is_same_v<
462-
std::runtime_error*,
492+
const std::runtime_error*,
463493
decltype(get_exception<std::runtime_error>(eptr))>);
464494
static_assert(
465495
std::is_same_v<
@@ -469,6 +499,10 @@ TEST_F(ExceptionTest, get_exception_from_std_exception_ptr) {
469499
std::is_same_v<
470500
const std::runtime_error*,
471501
decltype(get_exception<std::runtime_error>(std::as_const(eptr)))>);
502+
static_assert(
503+
std::is_same_v<
504+
std::runtime_error*,
505+
decltype(get_mutable_exception<std::runtime_error>(eptr))>);
472506
}
473507

474508
TEST_F(ExceptionTest, exception_shared_string) {

third-party/folly/src/folly/result/result.h

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,6 @@ class non_value_result {
144144

145145
template <typename Ex, typename EW>
146146
static Ex* get_exception_impl(EW& ew) {
147-
static_assert(
148-
// This "cheap" test is fine since `OperationCancelled` is final.
149-
!std::is_same_v<const OperationCancelled, const Ex>,
150-
"Test results for cancellation via `has_stopped()`");
151147
return folly::get_exception<Ex>(ew);
152148
}
153149

@@ -195,12 +191,18 @@ class non_value_result {
195191

196192
// Implement the `folly::get_exception<Ex>(res)` protocol
197193
template <typename Ex>
198-
Ex* get_exception(get_exception_tag_t) noexcept {
199-
return get_exception_impl<Ex>(ew_);
194+
const Ex* get_exception(get_exception_tag_t) const noexcept {
195+
static_assert( // Note: `OperationCancelled` is final
196+
!std::is_same_v<const OperationCancelled, const Ex>,
197+
"Test results for cancellation via `has_stopped()`");
198+
return folly::get_exception<Ex>(ew_);
200199
}
201200
template <typename Ex>
202-
const Ex* get_exception(get_exception_tag_t) const noexcept {
203-
return get_exception_impl<const Ex>(ew_);
201+
Ex* get_mutable_exception(get_exception_tag_t) noexcept {
202+
static_assert( // Note: `OperationCancelled` is final
203+
!std::is_same_v<const OperationCancelled, const Ex>,
204+
"Test results for cancellation via `has_stopped()`");
205+
return folly::get_mutable_exception<Ex>(ew_);
204206
}
205207

206208
// DO NOT USE these "legacy" functions outside of `folly` internals. See the
@@ -462,11 +464,11 @@ class result_crtp {
462464

463465
// Implement the `folly::get_exception<Ex>(res)` protocol
464466
template <typename Ex>
465-
Ex* get_exception(get_exception_tag_t) noexcept {
467+
Ex* get_mutable_exception(get_exception_tag_t) noexcept {
466468
if (!exp_.hasError()) {
467469
return nullptr;
468470
}
469-
return folly::get_exception<Ex>(exp_.error());
471+
return folly::get_mutable_exception<Ex>(exp_.error());
470472
}
471473
template <typename Ex>
472474
const Ex* get_exception(get_exception_tag_t) const noexcept {

third-party/folly/src/folly/result/test/result_test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -740,7 +740,7 @@ TEST(Result, accessError) {
740740

741741
// We can mutate the exception in-place.
742742
auto newErr = MyError{"buh-bye"};
743-
*get_exception<MyError>(r) = std::move(newErr);
743+
*get_mutable_exception<MyError>(r) = std::move(newErr);
744744
std::string msg2{"buh-bye"};
745745
EXPECT_EQ(msg2, get_exception<MyError>(r)->what());
746746

third-party/folly/src/folly/test/ExceptionWrapperTest.cpp

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -391,39 +391,49 @@ TEST(ExceptionWrapper, withExceptionPtrAnyNilTest) {
391391

392392
TEST(ExceptionWrapper, get_exception_from_exception_wrapper) {
393393
static_assert(
394-
std::is_invocable_v< //
394+
std::is_invocable_v<
395395
get_exception_fn<std::exception>,
396-
exception_wrapper&>);
396+
const exception_wrapper&>);
397397
static_assert(
398398
std::is_invocable_v<
399-
get_exception_fn<std::exception>,
399+
get_mutable_exception_fn<std::exception>,
400+
exception_wrapper&>);
401+
static_assert(
402+
!std::is_invocable_v<
403+
get_mutable_exception_fn<std::exception>,
400404
const exception_wrapper&>);
405+
401406
// Unsafe to extract a pointer out of rvalues
402407
static_assert(
403408
!std::is_invocable_v<
404409
get_exception_fn<std::exception>,
405-
exception_wrapper&&>);
410+
const exception_wrapper&&>);
406411
static_assert(
407412
!std::is_invocable_v<
408-
get_exception_fn<std::exception>,
409-
const exception_wrapper&&>);
413+
get_mutable_exception_fn<std::exception>,
414+
exception_wrapper&&>);
410415

411416
auto ew = make_exception_wrapper<std::runtime_error>("foo");
412417

413418
EXPECT_EQ(nullptr, get_exception<std::system_error>(ew));
419+
EXPECT_EQ(nullptr, get_mutable_exception<std::system_error>(ew));
414420

415421
EXPECT_STREQ("foo", get_exception<std::exception>(ew)->what());
416422
EXPECT_STREQ("foo", get_exception<const std::exception>(ew)->what());
417423
EXPECT_STREQ("foo", get_exception<>(ew)->what());
424+
EXPECT_STREQ("foo", get_exception<>(std::as_const(ew))->what());
425+
EXPECT_STREQ("foo", get_mutable_exception<>(ew)->what());
418426

419427
EXPECT_STREQ("foo", get_exception<std::runtime_error>(ew)->what());
420-
EXPECT_EQ(
421-
ew.get_exception<std::runtime_error>(),
422-
get_exception<std::runtime_error>(ew));
428+
EXPECT_STREQ("foo", get_mutable_exception<std::runtime_error>(ew)->what());
429+
430+
auto expected_p = ew.get_exception<std::runtime_error>();
431+
EXPECT_EQ(expected_p, get_exception<std::runtime_error>(ew));
432+
EXPECT_EQ(expected_p, get_mutable_exception<std::runtime_error>(ew));
423433

424434
static_assert(
425435
std::is_same_v<
426-
std::runtime_error*,
436+
const std::runtime_error*,
427437
decltype(get_exception<std::runtime_error>(ew))>);
428438
static_assert(
429439
std::is_same_v<
@@ -433,6 +443,10 @@ TEST(ExceptionWrapper, get_exception_from_exception_wrapper) {
433443
std::is_same_v<
434444
const std::runtime_error*,
435445
decltype(get_exception<std::runtime_error>(std::as_const(ew)))>);
446+
static_assert(
447+
std::is_same_v<
448+
std::runtime_error*,
449+
decltype(get_mutable_exception<std::runtime_error>(ew))>);
436450
}
437451

438452
TEST(ExceptionWrapper, withExceptionDeduction) {

0 commit comments

Comments
 (0)