Skip to content

fix(read_env): only advertise inline_completion for set_value_t#2112

Open
nebkat wants to merge 1 commit into
NVIDIA:mainfrom
nebkat:fix/read_env-attrs-set-value-only
Open

fix(read_env): only advertise inline_completion for set_value_t#2112
nebkat wants to merge 1 commit into
NVIDIA:mainfrom
nebkat:fix/read_env-attrs-set-value-only

Conversation

@nebkat

@nebkat nebkat commented Jun 9, 2026

Copy link
Copy Markdown

read_env's __attrs::query was a member template, so it returned inline_completion for every completion tag - even set_error_t and 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.

Example: Godbolt


Drop the _SetTag template parameter and bind the query directly to set_value_t - the only tag read_env sends - matching what __just::__attrs already does. The other tags now fall through to __unknown, the inline check correctly fails, and the non-inline awaiter gets selected.

`read_env`'s `__attrs::query` was a member template, so it returned
`inline_completion` for every completion tag - even `set_error_t` and
`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.

Drop the `_SetTag` template parameter and bind the query directly to
`set_value_t` - the only tag read_env sends - matching what
`__just::__attrs` already does. The other tags now fall through to
`__unknown`, the inline check correctly fails, and the non-inline
awaiter gets selected.
@copy-pr-bot

copy-pr-bot Bot commented Jun 9, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@Cra3z

Cra3z commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

I think the more fundamental cause lies in a flaw of the current let algorithm: right now, the attributes of a let sender merely forward those of the predecessor sender. This means that as long as the predecessor sender is inline-completing, the let sender becomes inline-completing, without taking the completion behavior of the successor sender into account.
See #2098 (comment).

@nebkat

nebkat commented Jun 9, 2026

Copy link
Copy Markdown
Author

Right, I did think that was unusual!

I presume this is still the correct completion behavior for a sender which only completes with set_value? just() uses the _JustTag::__tag_t to template __attrs<_SetTag> so it only defines the set_value completion behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants