4168. std::start_lifetime_as inadvertently has undefined behavior due to use of std::bit_cast

Section: 20.2.6 [obj.lifetime] Status: New Submitter: Jan Schultke Opened: 2024-10-23 Last modified: 2024-10-31

Priority: Not Prioritized

View all issues with New status.

Discussion:

Consider the motivating example from P2590R2: Explicit lifetime management:

struct X { int a, b; };

X* make_x() {
  X* p = std::start_lifetime_as<X>(myMalloc(sizeof(struct X));
  p->a = 1;
  p->b = 2;
  return p;
}

Assuming that myMalloc does not initialize the bytes of storage, this example has undefined behavior because the value of the resulting object of trivially copyable type X is determined as if by calling std::bit_cast<X>(a) for the implicitly-created object a of type X (20.2.6 [obj.lifetime] paragraph 3), whose object representation is filled with indeterminate bytes obtained from myMalloc. Such a call to std::bit_cast has undefined behavior because std::bit_cast does not tolerate the creation of an int where bits in the value representation are indeterminate (22.11.3 [bit.cast] paragraph 2), and such an int is the smallest enclosing object of some of the indeterminate bits.

Proposed resolution:

This wording is relative to N4993.

  1. Modify 20.2.6 [obj.lifetime] as indicated:

    [Drafting note: The proposed resolution does not alter the behavior for erroneous bits. Therefore, a call to std::start_lifetime_as may have erroneous behavior when used on storage with indeterminate bits, despite not accessing that storage. An alternative resolution would be to produce objects whose value is erroneous.]

    template<class T>
      T* start_lifetime_as(void* p) noexcept;
    template<class T>
      const T* start_lifetime_as(const void* p) noexcept;
    template<class T>
      volatile T* start_lifetime_as(volatile void* p) noexcept;
    template<class T>
      const volatile T* start_lifetime_as(const volatile void* p) noexcept;
    

    -1- Mandates: […]

    -2- Preconditions: […]

    -3- Effects: Implicitly creates objects (6.7.2 [intro.object]) within the denoted region consisting of an object a of type T whose address is p, and objects nested within a, as follows: The object representation of a is the contents of the storage prior to the call to start_lifetime_as. The value of each created object o of trivially copyable type (6.8.1 [basic.types.general]) U is determined in the same manner as for a call to bit_cast<U>(E) (22.11.3 [bit.cast]), where E is an lvalue of type U denoting o, except that the storage is not accessed and that for each indeterminate bit b in the value representation of the result, the smallest object containing that bit b has indeterminate value where the behavior would otherwise be undefined. The value of any other created object is unspecified.