From 1e412f7d1f787989a41cad38bcaa3eb452cdb20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Neboj=C5=A1a=20Cvetkovi=C4=87?= Date: Tue, 9 Jun 2026 02:25:22 +0100 Subject: [PATCH] fix(read_env): advertise inline_completion only for the tags it sends `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)` when evaluating the query throws - both complete inline within `start()`. `set_stopped_t` now falls through to `__unknown`, so the inline check correctly fails and the non-inline awaiter gets selected. --- include/stdexec/__detail/__read_env.hpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/include/stdexec/__detail/__read_env.hpp b/include/stdexec/__detail/__read_env.hpp index b0c88e998..9dca5ab43 100644 --- a/include/stdexec/__detail/__read_env.hpp +++ b/include/stdexec/__detail/__read_env.hpp @@ -80,9 +80,14 @@ namespace STDEXEC template struct __attrs { - template STDEXEC_ATTRIBUTE(nodiscard) - constexpr auto query(__get_completion_behavior_t<_SetTag>) const noexcept + constexpr auto query(__get_completion_behavior_t) const noexcept + { + return __completion_behavior::__inline_completion; + } + + STDEXEC_ATTRIBUTE(nodiscard) + constexpr auto query(__get_completion_behavior_t) const noexcept { return __completion_behavior::__inline_completion; }