What happened + What you expected to happen
ray::StatusOr<T> (src/ray/common/status_or.h) stores its value in a union member data_ that is only a live object when ok(); in the error state data_ is left unconstructed. Two paths do not respect that:
-
swap() calls swap(data_, rhs.data_) unconditionally. When either operand is in the error state, this move-constructs from / assigns into unconstructed union storage — UB for any non-trivial T.
-
operator=(const StatusOr&) and operator=(StatusOr&&) set status_ to OK before calling AssignValue. AssignValue destroys the existing value only when ok() is true, so with the status flipped first it runs data_.~T() on unconstructed storage whenever the destination was previously an error.
Every other path (copy/move ctor, AssignStatus) correctly guards on the pre-mutation ok() state; these two are the exceptions.
Expected: swapping or assigning into an error-state StatusOr<T> must not construct/destroy through the unconstructed union member.
swap is not called directly today but is reachable via ADL in generic code (std::swap, std::sort); the assignment operators are on common paths.
Versions / Dependencies
master (ray-project/ray), all platforms. C++ core (src/ray/common).
Reproduction script
No standalone script — found by code review. A unit test with a non-trivial T that assigns/swaps across error/value states (e.g. tracking live-instance count, or under ASan) exercises the UB; details in the linked PR.
Issue Severity
Medium: latent UB, narrow trigger (non-trivial T in an error state), but real.
What happened + What you expected to happen
ray::StatusOr<T>(src/ray/common/status_or.h) stores its value in a union memberdata_that is only a live object whenok(); in the error statedata_is left unconstructed. Two paths do not respect that:swap()callsswap(data_, rhs.data_)unconditionally. When either operand is in the error state, this move-constructs from / assigns into unconstructed union storage — UB for any non-trivialT.operator=(const StatusOr&)andoperator=(StatusOr&&)setstatus_to OK before callingAssignValue.AssignValuedestroys the existing value only whenok()is true, so with the status flipped first it runsdata_.~T()on unconstructed storage whenever the destination was previously an error.Every other path (copy/move ctor,
AssignStatus) correctly guards on the pre-mutationok()state; these two are the exceptions.Expected: swapping or assigning into an error-state
StatusOr<T>must not construct/destroy through the unconstructed union member.swapis not called directly today but is reachable via ADL in generic code (std::swap,std::sort); the assignment operators are on common paths.Versions / Dependencies
master (
ray-project/ray), all platforms. C++ core (src/ray/common).Reproduction script
No standalone script — found by code review. A unit test with a non-trivial
Tthat assigns/swaps across error/value states (e.g. tracking live-instance count, or under ASan) exercises the UB; details in the linked PR.Issue Severity
Medium: latent UB, narrow trigger (non-trivial
Tin an error state), but real.