Skip to content

Commit 43feda9

Browse files
committed
let_value, _error, & _stopped: Use After Move On Exception From Connect
When a completion signal is sent on the appropriate completion channel (i.e. set_value, set_error, or set_stopped) let_value, let_error, and let_stopped (respectively): 1. Store the values associated with the signal, if any, 2. Invoke their associated invocable with those stored values, and then 3. Connect the sender returned by that invocation Prior to this commit the implementation of the operations enumerated above moved the final receiver into the connect operation in step 3 (more precisely those implementations moved the final receiver into a wrapper receiver which was in turn moved into that connect operation). This meant that if the connect operation: 1. Decay-copied the receiver (thereby moving from it), and then 2. Threw an exception The resulting error completion signal would be sent to a receiver which had been moved from. Reproduced in a unit test and fixed.
1 parent de7420a commit 43feda9

File tree

2 files changed

+41
-2
lines changed

2 files changed

+41
-2
lines changed

include/stdexec/__detail/__let.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ namespace stdexec {
111111
return __env::__join(__env_, stdexec::get_env(__rcvr_));
112112
}
113113

114-
_Receiver __rcvr_;
114+
_Receiver& __rcvr_;
115115
const _Env2& __env_;
116116
};
117117
};
@@ -291,7 +291,7 @@ namespace stdexec {
291291

292292
template <class _ResultSender, class _OpState>
293293
auto __get_result_receiver(const _ResultSender&, _OpState& __op_state) -> decltype(auto) {
294-
return __rcvr_t{static_cast<_Receiver&&>(__op_state.__rcvr_), __env2_};
294+
return __rcvr_t{__op_state.__rcvr_, __env2_};
295295
}
296296

297297
STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS

test/stdexec/algos/adaptors/test_let_value.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
#include <exec/env.hpp>
2525

2626
#include <chrono> // IWYU pragma: keep for chrono_literals
27+
#include <exception>
28+
#include <memory>
2729

2830
namespace ex = stdexec;
2931

@@ -347,4 +349,41 @@ namespace {
347349
ex::start(op);
348350
CHECK(completed);
349351
}
352+
353+
struct throws_on_connect {
354+
using sender_concept = ::stdexec::sender_t;
355+
template <typename... Args>
356+
static consteval ::stdexec::completion_signatures<::stdexec::set_value_t()>
357+
get_completion_signatures(const Args&...) noexcept {
358+
return {};
359+
}
360+
template <typename Receiver>
361+
auto connect(Receiver) const
362+
-> ::stdexec::connect_result_t<decltype(::stdexec::just()), Receiver> {
363+
throw std::logic_error("TEST");
364+
}
365+
};
366+
367+
TEST_CASE(
368+
"When connecting the successor throws an exception let_value delivers an error completion "
369+
"signal to a valid receiver",
370+
"[adaptors][let_value]") {
371+
struct receiver {
372+
using receiver_concept = ::stdexec::receiver_t;
373+
std::shared_ptr<int> ptr;
374+
void set_value() noexcept {
375+
FAIL_CHECK("Operation should end in error");
376+
}
377+
void set_error(std::exception_ptr ex) noexcept {
378+
CHECK(ex);
379+
REQUIRE(ptr);
380+
*ptr = 5;
381+
}
382+
};
383+
const auto ptr = std::make_shared<int>(0);
384+
auto sender = ex::let_value(::stdexec::just(), []() noexcept { return throws_on_connect{}; });
385+
auto op = ex::connect(std::move(sender), receiver{ptr});
386+
ex::start(op);
387+
CHECK(*ptr == 5);
388+
}
350389
} // namespace

0 commit comments

Comments
 (0)