fix(read_env): only advertise inline_completion for set_value_t#2112
fix(read_env): only advertise inline_completion for set_value_t#2112nebkat wants to merge 1 commit into
Conversation
|
I think the more fundamental cause lies in a flaw of the current |
|
Right, I did think that was unusual! I presume this is still the correct completion behavior for a sender which only completes with |
a3791fd to
1e412f7
Compare
| } | ||
|
|
||
| STDEXEC_ATTRIBUTE(nodiscard) | ||
| constexpr auto query(__get_completion_behavior_t<set_error_t>) const noexcept |
There was a problem hiding this comment.
this should be constrained on whether the query expression is potentially throwing.
1e412f7 to
4aa7567
Compare
`read_env`'s `__attrs::query` was a member template, so it returned
`inline_completion` for every completion tag - even `set_stopped_t`,
which read_env never sends. That made `let_value` mistakenly classify
chains like
read_env(q) | let_value([](auto v) { return some_async_sender; })
as inline-completing. The check `__never_sends || behavior ==
inline_completion` passed for every tag because read_env claimed inline
across the board.
`__as_awaitable` then picked the inline `__sender_awaiter`, which holds
the operation state as a local in `await_suspend` and destroys it at the
closing brace. For any inner sender that hadn't actually completed by
then, this is a use-after-free.
Bind the query to the tags read_env actually completes with: always
`set_value_t`, and `set_error_t(exception_ptr)` only when the query is
potentially throwing - both complete inline within `start()`, matching
the completion signatures.
4aa7567 to
9a30194
Compare
read_env's__attrs::querywas a member template, so it returnedinline_completionfor every completion tag - evenset_error_tandset_stopped_t, which read_env never sends. That madelet_valuemistakenly classify chains likeas inline-completing. The check
__never_sends || behavior == inline_completionpassed for every tag because read_env claimed inline across the board.__as_awaitablethen picked the inline__sender_awaiter, which holds the operation state as a local inawait_suspendand destroys it at the closing brace. For any inner sender that hadn't actually completed by then, this is a use-after-free.Example: Godbolt
Drop the
_SetTagtemplate parameter and bind the query directly toset_value_t- the only tag read_env sends - matching what__just::__attrsalready does. The other tags now fall through to__unknown, the inline check correctly fails, and the non-inline awaiter gets selected.