Skip to content

fix(exec): own the written env in sequence write_env's operation state - #2306

Open
alwaysprince05 wants to merge 1 commit into
NVIDIA:mainfrom
alwaysprince05:fix/write-env-sequence-lifetime-2305
Open

alwaysprince05 wants to merge 1 commit into
NVIDIA:mainfrom
alwaysprince05:fix/write-env-sequence-lifetime-2305

Conversation

@alwaysprince05

Copy link
Copy Markdown
Contributor

Part 1 of #2305 (write_env over sequence senders). The iterate half of that issue will be a separate PR. This is fallout from #2294.

Problem

When a write_env sender wraps a sequence sender, exec::subscribe unwraps the adaptor one layer at a time: the adaptor's data is joined with the receiver's environment and the result is stored in the environment presented to the child. The transparent branch joined the adaptor's data by reference — __join_env_t<_Data const&, _Env> — and the reference pointed into the sender expression. The returned operation state outlives the sender expression, so a query into the written environment after subscribe returns reads dangling memory:

auto op = exec::subscribe(write_env(seq{}, my_env{...}), rcvr{});
// the write_env temporary is destroyed at the end of the statement
start(op);  // the child reads a dangling reference here

AddressSanitizer on main (f4c123f) reports heap-use-after-free for this pattern, as traced in #2305.

Fix

__write_env_t's __child_env_fn now owns the data it puts into the child's environment:

  • it constructs a decayed _Data — moving out of an rvalue sender, copying from an lvalue — and joins the owned value with the receiver's environment, which is what the regular (non-sequence) write_env path already does with its __state.__data_;
  • the call site forwards the sender's value category (__forward_like<__tfx_seq_t>) rather than the value category of the structured binding of the data member;
  • the previously unconditional noexcept on __child_env_fn::operator() is now conditional on constructing the owned data, and the transparent branch's computed noexcept includes that construction.

A static_assert(__nothrow_move_constructible<_Data>) guards the move that happens inside __env::__join's noexcept body, mirroring the constraint __fwd already imposes on the wrapped environment.

Tests

Three regression tests in test/exec/sequence/test_write_env_sequence.cpp, all failing on main and passing with this change:

  1. subscribing from a temporary write_env sender, destroying it, then starting the operation state and reading the injected query;
  2. the same with move-only env data (this one also guards against the naive always-copy fix, which fails to compile for move-only data);
  3. the same from a named (lvalue) sender.

The tests fail deterministically on unfixed main even without ASAN: the env data type poisons its own value on destruction, so a read through the dangling reference returns the poison instead of the original value.

Verification

  • test.exec (376 cases) and test.stdexec (626 cases) green with STDEXEC_ENABLE_EXTRA_TYPE_CHECKING both OFF and ON.
  • Both repro programs from the issue compile ASAN-clean after the change (and reproduce the use-after-free before it).

Refs #2305.

Subscribing to a write_env sender that wraps a sequence sender takes the
transparent-adaptor path in exec::subscribe: the adaptor's data is joined
with the receiver's environment and stored in the environment presented
to the child. The joined environment held a reference to the data member
of the sender expression itself, so once the sender expression is
destroyed, the operation state is left reading dangling memory.

Have __write_env_t's __child_env_fn own a decayed copy of the data --
moving out of an rvalue sender, copying from an lvalue -- and forward
the sender's value category to the transformation instead of the value
category of the data member binding. The unconditional noexcept on
__child_env_fn::operator() becomes conditional on constructing the owned
data, and the transparent branch of the subscribe machinery accounts for
that construction in its computed noexcept.

Refs NVIDIA#2305.
@copy-pr-bot

copy-pr-bot Bot commented Oct 6, 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.

@alwaysprince05

Copy link
Copy Markdown
Contributor Author

/ok to test

This branch has not been deployed

No deployments
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.

1 participant