llvm/libcxx/test/std/utilities/expected/types.h

//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef TEST_STD_UTILITIES_EXPECTED_TYPES_H
#define TEST_STD_UTILITIES_EXPECTED_TYPES_H

#include <cstring>
#include <utility>
#include <type_traits>
#include "test_macros.h"

template <bool copyMoveNoexcept, bool convertNoexcept = true>
struct TracedBase {
  struct state {
    bool copyCtorCalled   = false;
    bool copyAssignCalled = false;
    bool moveCtorCalled   = false;
    bool moveAssignCalled = false;
    bool dtorCalled       = false;
  };

  state* state_      = nullptr;
  bool copiedFromInt = false;
  bool movedFromInt  = false;
  bool copiedFromTmp = false;
  bool movedFromTmp  = false;
  int data_;

  constexpr TracedBase(const int& ii) noexcept(convertNoexcept) : data_(ii) { copiedFromInt = true; }
  constexpr TracedBase(int&& ii) noexcept(convertNoexcept) : data_(ii) { movedFromInt = true; }
  constexpr TracedBase(state& s, int ii) noexcept : state_(&s), data_(ii) {}
  constexpr TracedBase(const TracedBase& other) noexcept(copyMoveNoexcept) : state_(other.state_), data_(other.data_) {
    if (state_) {
      state_->copyCtorCalled = true;
    } else {
      copiedFromTmp = true;
    }
  }
  constexpr TracedBase(TracedBase&& other) noexcept(copyMoveNoexcept) : state_(other.state_), data_(other.data_) {
    if (state_) {
      state_->moveCtorCalled = true;
    } else {
      movedFromTmp = true;
    }
  }
  constexpr TracedBase& operator=(const TracedBase& other) noexcept(copyMoveNoexcept) {
    data_                    = other.data_;
    state_->copyAssignCalled = true;
    return *this;
  }
  constexpr TracedBase& operator=(TracedBase&& other) noexcept(copyMoveNoexcept) {
    data_                    = other.data_;
    state_->moveAssignCalled = true;
    return *this;
  }
  constexpr ~TracedBase() {
    if (state_) {
      state_->dtorCalled = true;
    }
  }
};

using Traced         = TracedBase<false>;
using TracedNoexcept = TracedBase<true>;

using MoveThrowConvNoexcept = TracedBase<false, true>;
using MoveNoexceptConvThrow = TracedBase<true, false>;
using BothMayThrow          = TracedBase<false, false>;
using BothNoexcept          = TracedBase<true, true>;

struct ADLSwap {
  int i;
  bool adlSwapCalled = false;
  constexpr ADLSwap(int ii) : i(ii) {}
  constexpr friend void swap(ADLSwap& x, ADLSwap& y) {
    std::swap(x.i, y.i);
    x.adlSwapCalled = true;
    y.adlSwapCalled = true;
  }
};

template <bool Noexcept>
struct TrackedMove {
  int i;
  int numberOfMoves = 0;
  bool swapCalled   = false;

  constexpr TrackedMove(int ii) : i(ii) {}
  constexpr TrackedMove(TrackedMove&& other) noexcept(Noexcept)
      : i(other.i), numberOfMoves(other.numberOfMoves), swapCalled(other.swapCalled) {
    ++numberOfMoves;
  }

  constexpr friend void swap(TrackedMove& x, TrackedMove& y) {
    std::swap(x.i, y.i);
    std::swap(x.numberOfMoves, y.numberOfMoves);
    x.swapCalled = true;
    y.swapCalled = true;
  }
};

#ifndef TEST_HAS_NO_EXCEPTIONS
struct Except {};

struct ThrowOnCopyConstruct {
  ThrowOnCopyConstruct() = default;
  ThrowOnCopyConstruct(const ThrowOnCopyConstruct&) { throw Except{}; }
  ThrowOnCopyConstruct& operator=(const ThrowOnCopyConstruct&) = default;
};

struct ThrowOnMoveConstruct {
  ThrowOnMoveConstruct() = default;
  ThrowOnMoveConstruct(ThrowOnMoveConstruct&&) { throw Except{}; }
  ThrowOnMoveConstruct& operator=(ThrowOnMoveConstruct&&) = default;
};

struct ThrowOnConvert {
  ThrowOnConvert() = default;
  ThrowOnConvert(const int&) { throw Except{}; }
  ThrowOnConvert(int&&) { throw Except{}; }
  ThrowOnConvert(const ThrowOnConvert&) noexcept(false) {}
  ThrowOnConvert& operator=(const ThrowOnConvert&) = default;
  ThrowOnConvert(ThrowOnConvert&&) noexcept(false) {}
  ThrowOnConvert& operator=(ThrowOnConvert&&) = default;
};

struct ThrowOnMove {
  bool* destroyed = nullptr;
  ThrowOnMove()   = default;
  ThrowOnMove(bool& d) : destroyed(&d) {}
  ThrowOnMove(ThrowOnMove&&) { throw Except{}; };
  ThrowOnMove& operator=(ThrowOnMove&&) = default;
  ~ThrowOnMove() {
    if (destroyed) {
      *destroyed = true;
    }
  }
};

#endif // TEST_HAS_NO_EXCEPTIONS

struct MoveOnlyErrorType {
  constexpr MoveOnlyErrorType(int) {}
  MoveOnlyErrorType(MoveOnlyErrorType&&) {}
  MoveOnlyErrorType(const MoveOnlyErrorType&&) {}
  MoveOnlyErrorType(const MoveOnlyErrorType&)            = delete;
  MoveOnlyErrorType& operator=(const MoveOnlyErrorType&) = delete;
};

// This type has one byte of tail padding where `std::expected` may put its
// "has value" flag. The constructor will clobber all bytes including the
// tail padding. With this type we can check that `std::expected` handles
// the case where the "has value" flag is an overlapping subobject correctly.
//
// See https://github.com/llvm/llvm-project/issues/68552 for details.
template <int Constant>
struct TailClobberer {
  constexpr TailClobberer() noexcept {
    if (!std::is_constant_evaluated()) {
      std::memset(this, Constant, sizeof(*this));
    }
    // Always set `b` itself to `false` so that the comparison works.
    b = false;
  }
  constexpr TailClobberer(const TailClobberer&) : TailClobberer() {}
  constexpr TailClobberer(TailClobberer&&) = default;
  // Converts from `int`/`std::initializer_list<int>, used in some tests.
  constexpr TailClobberer(int) : TailClobberer() {}
  constexpr TailClobberer(std::initializer_list<int>) noexcept : TailClobberer() {}

  friend constexpr bool operator==(const TailClobberer&, const TailClobberer&) = default;

  friend constexpr void swap(TailClobberer&, TailClobberer&) {}

private:
  alignas(2) bool b;
};
static_assert(!std::is_trivially_copy_constructible_v<TailClobberer<0>>);
static_assert(std::is_trivially_move_constructible_v<TailClobberer<0>>);

template <int Constant, bool Noexcept = true, bool ThrowOnMove = false>
struct TailClobbererNonTrivialMove : TailClobberer<Constant> {
  using TailClobberer<Constant>::TailClobberer;
  constexpr TailClobbererNonTrivialMove(TailClobbererNonTrivialMove&&) noexcept(Noexcept) : TailClobberer<Constant>() {
#ifndef TEST_HAS_NO_EXCEPTIONS
    if constexpr (!Noexcept && ThrowOnMove)
      throw Except{};
#endif
  }
};
static_assert(!std::is_trivially_copy_constructible_v<TailClobbererNonTrivialMove<0>>);
static_assert(std::is_move_constructible_v<TailClobbererNonTrivialMove<0>>);
static_assert(!std::is_trivially_move_constructible_v<TailClobbererNonTrivialMove<0>>);
static_assert(std::is_nothrow_move_constructible_v<TailClobbererNonTrivialMove<0, true>>);
static_assert(!std::is_nothrow_move_constructible_v<TailClobbererNonTrivialMove<0, false>>);

// The `CheckForInvalidWrites` class recreates situations where other objects
// may be placed into a `std::expected`'s tail padding (see
// https://github.com/llvm/llvm-project/issues/70494). With a template
// parameter `WithPaddedExpected` two cases can be tested:
//
// 1. The `std::expected<T, E>` itself has padding, because `T`/`E` _don't_
//    have tail padding. This is modelled by `CheckForInvalidWrites<true>`
//    which has a (potential) data layout like this:
//
//                +- `expected`'s "has value" flag
//                |
//                |             +- `please_dont_overwrite_me`
//                |             |
//    /---int---\ |  /----------^-------\                                    //
//    00 00 00 00 01 01 01 01 01 01 01 01
//                   \--v---/
//                      |
//                      |
//                      +- `expected`'s tail padding which
//                         gets repurposed by `please_dont_overwrite_me`
//
// 2. There is tail padding in the union of `T` and `E` which means the
//    "has value" flag can be put into this tail padding. In this case, the
//    `std::expected` itself _must not_ have any tail padding as it may get
//    overwritten on mutating operations such as `emplace()`. This case is
//    modelled by `CheckForInvalidWrites<false>` with a (potential) data
//    layout like this:
//
//    +- bool
//    |                                +- please_dont_overwrite_me
//    |  +- "has value" flag           |
//    |  |                    /--------^---------\                           //
//    00 00 00 00 00 00 00 00 01 01 01 01 01 01 01 00
//          \---padding-----/                      |
//                                                 +- `CheckForInvalidWrites`
//                                                    padding
//
// Note that other implementation strategies are viable, including one that
// doesn't make use of `[[no_unique_address]]`. But if an implementation uses
// the strategy above, it must make sure that those tail padding bytes are not
// overwritten improperly on operations such as `emplace()`.

struct BoolWithPadding {
  constexpr explicit BoolWithPadding() noexcept : BoolWithPadding(false) {}
  constexpr BoolWithPadding(bool val) noexcept {
    if (!std::is_constant_evaluated()) {
      std::memset(this, 0, sizeof(*this));
    }
    val_ = val;
  }
  constexpr BoolWithPadding(const BoolWithPadding& other) noexcept : BoolWithPadding(other.val_) {}
  constexpr BoolWithPadding& operator=(const BoolWithPadding& other) noexcept {
    val_ = other.val_;
    return *this;
  }
  // The previous data layout of libc++'s `expected` required `T` to be
  // trivially move constructible to employ the `[[no_unique_address]]`
  // optimization. To trigger bugs with the old implementation, make
  // `BoolWithPadding` trivially move constructible.
  constexpr BoolWithPadding(BoolWithPadding&&) = default;

private:
  alignas(8) bool val_;
};

struct IntWithoutPadding {
  constexpr explicit IntWithoutPadding() noexcept : IntWithoutPadding(0) {}
  constexpr IntWithoutPadding(int val) noexcept {
    if (!std::is_constant_evaluated()) {
      std::memset(this, 0, sizeof(*this));
    }
    val_ = val;
  }
  constexpr IntWithoutPadding(const IntWithoutPadding& other) noexcept : IntWithoutPadding(other.val_) {}
  constexpr IntWithoutPadding& operator=(const IntWithoutPadding& other) noexcept {
    val_ = other.val_;
    return *this;
  }
  // See comment on `BoolWithPadding`.
  constexpr IntWithoutPadding(IntWithoutPadding&&) = default;

private:
  int val_;
};

template <bool WithPaddedExpected, bool ExpectedVoid>
struct CheckForInvalidWritesBaseImpl;
template <>
struct CheckForInvalidWritesBaseImpl<true, false> {
  using type = std::expected<IntWithoutPadding, bool>;
};
template <>
struct CheckForInvalidWritesBaseImpl<false, false> {
  using type = std::expected<BoolWithPadding, bool>;
};
template <>
struct CheckForInvalidWritesBaseImpl<true, true> {
  using type = std::expected<void, IntWithoutPadding>;
};
template <>
struct CheckForInvalidWritesBaseImpl<false, true> {
  using type = std::expected<void, BoolWithPadding>;
};

template <bool WithPaddedExpected, bool ExpectedVoid>
using CheckForInvalidWritesBase = typename CheckForInvalidWritesBaseImpl<WithPaddedExpected, ExpectedVoid>::type;

template <bool WithPaddedExpected, bool ExpectedVoid = false>
struct CheckForInvalidWrites : public CheckForInvalidWritesBase<WithPaddedExpected, ExpectedVoid> {
  constexpr CheckForInvalidWrites() = default;
  constexpr CheckForInvalidWrites(std::unexpect_t)
      : CheckForInvalidWritesBase<WithPaddedExpected, ExpectedVoid>(std::unexpect) {}

  constexpr CheckForInvalidWrites& operator=(const CheckForInvalidWrites& other) {
    CheckForInvalidWritesBase<WithPaddedExpected, ExpectedVoid>::operator=(other);
    return *this;
  }

  constexpr CheckForInvalidWrites& operator=(CheckForInvalidWrites&& other) {
    CheckForInvalidWritesBase<WithPaddedExpected, ExpectedVoid>::operator=(std::move(other));
    return *this;
  }

  using CheckForInvalidWritesBase<WithPaddedExpected, ExpectedVoid>::operator=;

  const bool please_dont_overwrite_me[7] = {true, true, true, true, true, true, true};

  constexpr bool check() {
    for (bool i : please_dont_overwrite_me) {
      if (!i) {
        return false;
      }
    }
    return true;
  }
};

#endif // TEST_STD_UTILITIES_EXPECTED_TYPES_H