Skip to content

Commit e8a6a7b

Browse files
authored
fix UB from pointer-interconvertibility issue in __detail::__op_state implementation (#1478)
The current implementation casts from a pointer to the first member of a struct (`__state_box::__state_`) to a pointer to the containing struct (`__state_box`), but that is only valid if `__state_box::__state_` is standard layout, which is not guaranteed. This PR changes `__state_box::__state_` into a byte array, into which the state object is emplaced. That makes `__state_box` a standard layout type unconditionally, which makes the cast from the member to the enclosing struct well-defined.
1 parent b888185 commit e8a6a7b

1 file changed

Lines changed: 35 additions & 6 deletions

File tree

include/stdexec/__detail/__basic_sender.hpp

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
#include <utility> // for tuple_size/tuple_element
3030
#include <cstddef>
31+
#include <new> // IWYU pragma: keep for placement new
3132
#include <type_traits>
3233

3334
namespace stdexec {
@@ -235,11 +236,26 @@ namespace stdexec {
235236
using __state_t = __state_type_t<__tag_t, _Sexpr, _Receiver>;
236237

237238
__state_box(_Sexpr&& __sndr, _Receiver& __rcvr) //
238-
noexcept(__nothrow_callable<decltype(__sexpr_impl<__tag_t>::get_state), _Sexpr, _Receiver>)
239-
: __state_(__sexpr_impl<__tag_t>::get_state(static_cast<_Sexpr&&>(__sndr), __rcvr)) {
239+
noexcept(__nothrow_callable<decltype(__sexpr_impl<__tag_t>::get_state), _Sexpr, _Receiver>) {
240+
::new (static_cast<void*>(__buf_)) auto(
241+
__sexpr_impl<__tag_t>::get_state(static_cast<_Sexpr&&>(__sndr), __rcvr));
240242
}
241243

242-
__state_t __state_;
244+
~__state_box() {
245+
reinterpret_cast<__state_t*>(__buf_)->~__state_t();
246+
}
247+
248+
STDEXEC_ATTRIBUTE((always_inline)) auto __state() & noexcept -> __state_t& {
249+
return *reinterpret_cast<__state_t*>(__buf_);
250+
}
251+
252+
STDEXEC_ATTRIBUTE((always_inline)) auto __state() const & noexcept -> const __state_t& {
253+
return *reinterpret_cast<const __state_t*>(__buf_);
254+
}
255+
256+
// We use a buffer to store the state object to make __state_box a standard-layout type
257+
// regardless of whether __state_t is standard-layout or not.
258+
alignas(__state_t) std::byte __buf_[sizeof(__state_t)]; // NOLINT(modernize-avoid-c-arrays)
243259
};
244260

245261
template <class _Sexpr, class _Receiver, class _State>
@@ -250,6 +266,8 @@ namespace stdexec {
250266
#endif
251267
auto __receiver() noexcept -> decltype(auto) {
252268
void* __state = static_cast<_State*>(this);
269+
// The following cast use the pointer-interconvertibility between the __state_box::__buf_
270+
// member and the containing __state_box object itself.
253271
auto* __sbox = static_cast<__state_box<_Sexpr, _Receiver>*>(__state);
254272
return (static_cast<__op_base<_Sexpr, _Receiver>*>(__sbox)->__rcvr_);
255273
}
@@ -279,6 +297,14 @@ namespace stdexec {
279297
, __state_(__sexpr_impl<__tag_t>::get_state(static_cast<_Sexpr&&>(__sndr), __rcvr_)) {
280298
}
281299

300+
STDEXEC_ATTRIBUTE((always_inline)) auto __state() & noexcept -> __state_t& {
301+
return __state_;
302+
}
303+
304+
STDEXEC_ATTRIBUTE((always_inline)) auto __state() const & noexcept -> const __state_t& {
305+
return __state_;
306+
}
307+
282308
STDEXEC_ATTRIBUTE((always_inline)) auto __rcvr() & noexcept -> _Receiver& {
283309
return __rcvr_;
284310
}
@@ -302,6 +328,9 @@ namespace stdexec {
302328
noexcept(__nothrow_decay_copyable<_Receiver> && __nothrow_move_constructible<__state_t>)
303329
: __receiver_box<_Receiver>{static_cast<_Receiver&&>(__rcvr)}
304330
, __state_box<_Sexpr, _Receiver>{static_cast<_Sexpr&&>(__sndr), this->__rcvr_} {
331+
// This is necessary to ensure that the state object is pointer-interconvertible
332+
// with the __state_box object for the sake of __enable_receiver_from_this.
333+
static_assert(std::is_standard_layout_v<__state_box<_Sexpr, _Receiver>>);
305334
}
306335
};
307336

@@ -391,7 +420,7 @@ namespace stdexec {
391420
auto&& __rcvr = this->__rcvr();
392421
__inner_ops_.apply(
393422
[&](auto&... __ops) noexcept {
394-
__sexpr_impl<__tag_t>::start(this->__state_, __rcvr, __ops...);
423+
__sexpr_impl<__tag_t>::start(this->__state(), __rcvr, __ops...);
395424
},
396425
__inner_ops_);
397426
}
@@ -405,15 +434,15 @@ namespace stdexec {
405434
__sexpr_impl<__tag_t>::complete(_Index(), *this, _Tag2(), static_cast<_Args&&>(__args)...);
406435
} else {
407436
__sexpr_impl<__tag_t>::complete(
408-
_Index(), this->__state_, __rcvr, _Tag2(), static_cast<_Args&&>(__args)...);
437+
_Index(), this->__state(), __rcvr, _Tag2(), static_cast<_Args&&>(__args)...);
409438
}
410439
}
411440

412441
template <class _Index>
413442
STDEXEC_ATTRIBUTE((always_inline)) auto __get_env(_Index) const noexcept
414443
-> __env_type_t<_Index, __tag_t, _Index, _Sexpr, _Receiver> {
415444
const auto& __rcvr = this->__rcvr();
416-
return __sexpr_impl<__tag_t>::get_env(_Index(), this->__state_, __rcvr);
445+
return __sexpr_impl<__tag_t>::get_env(_Index(), this->__state(), __rcvr);
417446
}
418447
};
419448

0 commit comments

Comments
 (0)