Title
Potential dangling reference returned from `transform_sender`
Status
new
Section
[exec.domain.default]
Submitter
Eric Niebler

Created on 2025-08-31.00:00:00 last changed 1 month ago

Messages

Date: 2025-10-23.12:39:14

Proposed resolution:

This wording is relative to N5014.

  1. Modify [exec.domain.default] as indicated:

    template<sender Sndr, queryable... Env>
      requires (sizeof...(Env) <= 1)
    constexpr sender decltype(auto) transform_sender(Sndr&& sndr, const Env&... env)
      noexcept(see below);
    

    -2- Let `e` be the expression

    tag_of_t<Sndr>().transform_sender(std::forward<Sndr>(sndr), env...)
    

    if that expression is well-formed; otherwise, static_cast<Sndr>(std::forward<Sndr>(sndr)).

    -3- Returns: `e`.

    -4- Remarks: The exception specification is equivalent to `noexcept(e)`.

Date: 2025-10-15.00:00:00

[ 2025-10-23; Reflector poll. ]

Set priority to 1 after reflector poll.

"There's an NB comment to remove `transform_sender` entirely." "Nothing seems to prevent `Sndr` from being a reference type, so I would expect `decay_t` or `remove_cvref_t` (or `decayed-typeof`) instead of the `static_cast`."

"I think that's intentional. The dangling happens if `transform_sender` is invoked with an xvalue (where `Sndr` is deduced as a type). When invoked with and lvalue (where `Sndr` is deduced as an lvalue reference) we don't want to make a copy. So the `static_cast` makes a copy of an xvalue and not of an lvalue."

Date: 2025-08-31.00:00:00

The following has been reported by Trevor Gray to me:

There is a potential stack-use-after-scope in `execution::transform_sender` with `execution::default_domain::transform_sender`.

I'll give an example of the problem using `starts_on` with the `default_domain`.

`starts_on` defines a `transform_sender` so `execution::transform_sender` will expand to:

return transform_sender(
    dom,
    dom.transform_sender(std::forward<Sndr>(sndr), env...),
    env...);

where `dom` is the `default_domain` and `sndr` is `starts_on`.

Execution flow:

  • dom.transform_sender(std::forward<Sndr>(sndr), env...) uses `default_domain` to invoke `start_on`'s `transform_sender`. The return type is `T` (where `T` is a `let_value` sender)

  • transform_sender(dom, declval<T>(), env...) is then run which uses `default_domain` to just return std::forward<T>(t).

This means the value returned from the entire expression is T&& which a reference to a temporary variable in the frame of `transform_sender` which is no longer valid after the return.

In the reference implementation, this scenario does not create a dangling reference because its implementation of `default_domain::transform_sender` does not conform to the spec. By default, it returns an rvalue sender as a prvalue instead of an xvalue as the spec requires.

The fix is for the spec to follow suit and return prvalues when an xvalue would otherwise be returned.

History
Date User Action Args
2025-10-23 12:39:14adminsetmessages: + msg15405
2025-09-15 14:55:46adminsetmessages: + msg15053
2025-08-31 00:00:00admincreate