xref: /llvm-project/libcxx/test/std/utilities/expected/types.h (revision 71315698c91d0cda054b903da0594ca6f072c350)
1 //===----------------------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #ifndef TEST_STD_UTILITIES_EXPECTED_TYPES_H
10 #define TEST_STD_UTILITIES_EXPECTED_TYPES_H
11 
12 #include <cstring>
13 #include <utility>
14 #include <type_traits>
15 #include "test_macros.h"
16 
17 template <bool copyMoveNoexcept, bool convertNoexcept = true>
18 struct TracedBase {
19   struct state {
20     bool copyCtorCalled   = false;
21     bool copyAssignCalled = false;
22     bool moveCtorCalled   = false;
23     bool moveAssignCalled = false;
24     bool dtorCalled       = false;
25   };
26 
27   state* state_      = nullptr;
28   bool copiedFromInt = false;
29   bool movedFromInt  = false;
30   bool copiedFromTmp = false;
31   bool movedFromTmp  = false;
32   int data_;
33 
34   constexpr TracedBase(const int& ii) noexcept(convertNoexcept) : data_(ii) { copiedFromInt = true; }
35   constexpr TracedBase(int&& ii) noexcept(convertNoexcept) : data_(ii) { movedFromInt = true; }
36   constexpr TracedBase(state& s, int ii) noexcept : state_(&s), data_(ii) {}
37   constexpr TracedBase(const TracedBase& other) noexcept(copyMoveNoexcept) : state_(other.state_), data_(other.data_) {
38     if (state_) {
39       state_->copyCtorCalled = true;
40     } else {
41       copiedFromTmp = true;
42     }
43   }
44   constexpr TracedBase(TracedBase&& other) noexcept(copyMoveNoexcept) : state_(other.state_), data_(other.data_) {
45     if (state_) {
46       state_->moveCtorCalled = true;
47     } else {
48       movedFromTmp = true;
49     }
50   }
51   constexpr TracedBase& operator=(const TracedBase& other) noexcept(copyMoveNoexcept) {
52     data_                    = other.data_;
53     state_->copyAssignCalled = true;
54     return *this;
55   }
56   constexpr TracedBase& operator=(TracedBase&& other) noexcept(copyMoveNoexcept) {
57     data_                    = other.data_;
58     state_->moveAssignCalled = true;
59     return *this;
60   }
61   constexpr ~TracedBase() {
62     if (state_) {
63       state_->dtorCalled = true;
64     }
65   }
66 };
67 
68 using Traced         = TracedBase<false>;
69 using TracedNoexcept = TracedBase<true>;
70 
71 using MoveThrowConvNoexcept = TracedBase<false, true>;
72 using MoveNoexceptConvThrow = TracedBase<true, false>;
73 using BothMayThrow          = TracedBase<false, false>;
74 using BothNoexcept          = TracedBase<true, true>;
75 
76 struct ADLSwap {
77   int i;
78   bool adlSwapCalled = false;
79   constexpr ADLSwap(int ii) : i(ii) {}
80   constexpr friend void swap(ADLSwap& x, ADLSwap& y) {
81     std::swap(x.i, y.i);
82     x.adlSwapCalled = true;
83     y.adlSwapCalled = true;
84   }
85 };
86 
87 template <bool Noexcept>
88 struct TrackedMove {
89   int i;
90   int numberOfMoves = 0;
91   bool swapCalled   = false;
92 
93   constexpr TrackedMove(int ii) : i(ii) {}
94   constexpr TrackedMove(TrackedMove&& other) noexcept(Noexcept)
95       : i(other.i), numberOfMoves(other.numberOfMoves), swapCalled(other.swapCalled) {
96     ++numberOfMoves;
97   }
98 
99   constexpr friend void swap(TrackedMove& x, TrackedMove& y) {
100     std::swap(x.i, y.i);
101     std::swap(x.numberOfMoves, y.numberOfMoves);
102     x.swapCalled = true;
103     y.swapCalled = true;
104   }
105 };
106 
107 #ifndef TEST_HAS_NO_EXCEPTIONS
108 struct Except {};
109 
110 struct ThrowOnCopyConstruct {
111   ThrowOnCopyConstruct() = default;
112   ThrowOnCopyConstruct(const ThrowOnCopyConstruct&) { throw Except{}; }
113   ThrowOnCopyConstruct& operator=(const ThrowOnCopyConstruct&) = default;
114 };
115 
116 struct ThrowOnMoveConstruct {
117   ThrowOnMoveConstruct() = default;
118   ThrowOnMoveConstruct(ThrowOnMoveConstruct&&) { throw Except{}; }
119   ThrowOnMoveConstruct& operator=(ThrowOnMoveConstruct&&) = default;
120 };
121 
122 struct ThrowOnConvert {
123   ThrowOnConvert() = default;
124   ThrowOnConvert(const int&) { throw Except{}; }
125   ThrowOnConvert(int&&) { throw Except{}; }
126   ThrowOnConvert(const ThrowOnConvert&) noexcept(false) {}
127   ThrowOnConvert& operator=(const ThrowOnConvert&) = default;
128   ThrowOnConvert(ThrowOnConvert&&) noexcept(false) {}
129   ThrowOnConvert& operator=(ThrowOnConvert&&) = default;
130 };
131 
132 struct ThrowOnMove {
133   bool* destroyed = nullptr;
134   ThrowOnMove()   = default;
135   ThrowOnMove(bool& d) : destroyed(&d) {}
136   ThrowOnMove(ThrowOnMove&&) { throw Except{}; };
137   ThrowOnMove& operator=(ThrowOnMove&&) = default;
138   ~ThrowOnMove() {
139     if (destroyed) {
140       *destroyed = true;
141     }
142   }
143 };
144 
145 #endif // TEST_HAS_NO_EXCEPTIONS
146 
147 struct MoveOnlyErrorType {
148   constexpr MoveOnlyErrorType(int) {}
149   MoveOnlyErrorType(MoveOnlyErrorType&&) {}
150   MoveOnlyErrorType(const MoveOnlyErrorType&&) {}
151   MoveOnlyErrorType(const MoveOnlyErrorType&)            = delete;
152   MoveOnlyErrorType& operator=(const MoveOnlyErrorType&) = delete;
153 };
154 
155 // This type has one byte of tail padding where `std::expected` may put its
156 // "has value" flag. The constructor will clobber all bytes including the
157 // tail padding. With this type we can check that `std::expected` handles
158 // the case where the "has value" flag is an overlapping subobject correctly.
159 //
160 // See https://github.com/llvm/llvm-project/issues/68552 for details.
161 template <int Constant>
162 struct TailClobberer {
163   constexpr TailClobberer() noexcept {
164     if (!std::is_constant_evaluated()) {
165       std::memset(static_cast<void*>(this), Constant, sizeof(*this));
166     }
167     // Always set `b` itself to `false` so that the comparison works.
168     b = false;
169   }
170   constexpr TailClobberer(const TailClobberer&) : TailClobberer() {}
171   constexpr TailClobberer(TailClobberer&&) = default;
172   // Converts from `int`/`std::initializer_list<int>, used in some tests.
173   constexpr TailClobberer(int) : TailClobberer() {}
174   constexpr TailClobberer(std::initializer_list<int>) noexcept : TailClobberer() {}
175 
176   friend constexpr bool operator==(const TailClobberer&, const TailClobberer&) = default;
177 
178   friend constexpr void swap(TailClobberer&, TailClobberer&) {}
179 
180 private:
181   alignas(2) bool b;
182 };
183 static_assert(!std::is_trivially_copy_constructible_v<TailClobberer<0>>);
184 static_assert(std::is_trivially_move_constructible_v<TailClobberer<0>>);
185 
186 template <int Constant, bool Noexcept = true, bool ThrowOnMove = false>
187 struct TailClobbererNonTrivialMove : TailClobberer<Constant> {
188   using TailClobberer<Constant>::TailClobberer;
189   constexpr TailClobbererNonTrivialMove(TailClobbererNonTrivialMove&&) noexcept(Noexcept) : TailClobberer<Constant>() {
190 #ifndef TEST_HAS_NO_EXCEPTIONS
191     if constexpr (!Noexcept && ThrowOnMove)
192       throw Except{};
193 #endif
194   }
195 };
196 static_assert(!std::is_trivially_copy_constructible_v<TailClobbererNonTrivialMove<0>>);
197 static_assert(std::is_move_constructible_v<TailClobbererNonTrivialMove<0>>);
198 static_assert(!std::is_trivially_move_constructible_v<TailClobbererNonTrivialMove<0>>);
199 static_assert(std::is_nothrow_move_constructible_v<TailClobbererNonTrivialMove<0, true>>);
200 static_assert(!std::is_nothrow_move_constructible_v<TailClobbererNonTrivialMove<0, false>>);
201 
202 // The `CheckForInvalidWrites` class recreates situations where other objects
203 // may be placed into a `std::expected`'s tail padding (see
204 // https://github.com/llvm/llvm-project/issues/70494). With a template
205 // parameter `WithPaddedExpected` two cases can be tested:
206 //
207 // 1. The `std::expected<T, E>` itself has padding, because `T`/`E` _don't_
208 //    have tail padding. This is modelled by `CheckForInvalidWrites<true>`
209 //    which has a (potential) data layout like this:
210 //
211 //                +- `expected`'s "has value" flag
212 //                |
213 //                |             +- `please_dont_overwrite_me`
214 //                |             |
215 //    /---int---\ |  /----------^-------\                                    //
216 //    00 00 00 00 01 01 01 01 01 01 01 01
217 //                   \--v---/
218 //                      |
219 //                      |
220 //                      +- `expected`'s tail padding which
221 //                         gets repurposed by `please_dont_overwrite_me`
222 //
223 // 2. There is tail padding in the union of `T` and `E` which means the
224 //    "has value" flag can be put into this tail padding. In this case, the
225 //    `std::expected` itself _must not_ have any tail padding as it may get
226 //    overwritten on mutating operations such as `emplace()`. This case is
227 //    modelled by `CheckForInvalidWrites<false>` with a (potential) data
228 //    layout like this:
229 //
230 //    +- bool
231 //    |                                +- please_dont_overwrite_me
232 //    |  +- "has value" flag           |
233 //    |  |                    /--------^---------\                           //
234 //    00 00 00 00 00 00 00 00 01 01 01 01 01 01 01 00
235 //          \---padding-----/                      |
236 //                                                 +- `CheckForInvalidWrites`
237 //                                                    padding
238 //
239 // Note that other implementation strategies are viable, including one that
240 // doesn't make use of `[[no_unique_address]]`. But if an implementation uses
241 // the strategy above, it must make sure that those tail padding bytes are not
242 // overwritten improperly on operations such as `emplace()`.
243 
244 struct BoolWithPadding {
245   constexpr explicit BoolWithPadding() noexcept : BoolWithPadding(false) {}
246   constexpr BoolWithPadding(bool val) noexcept {
247     if (!std::is_constant_evaluated()) {
248       std::memset(static_cast<void*>(this), 0, sizeof(*this));
249     }
250     val_ = val;
251   }
252   constexpr BoolWithPadding(const BoolWithPadding& other) noexcept : BoolWithPadding(other.val_) {}
253   constexpr BoolWithPadding& operator=(const BoolWithPadding& other) noexcept {
254     val_ = other.val_;
255     return *this;
256   }
257   // The previous data layout of libc++'s `expected` required `T` to be
258   // trivially move constructible to employ the `[[no_unique_address]]`
259   // optimization. To trigger bugs with the old implementation, make
260   // `BoolWithPadding` trivially move constructible.
261   constexpr BoolWithPadding(BoolWithPadding&&) = default;
262 
263 private:
264   alignas(8) bool val_;
265 };
266 
267 struct IntWithoutPadding {
268   constexpr explicit IntWithoutPadding() noexcept : IntWithoutPadding(0) {}
269   constexpr IntWithoutPadding(int val) noexcept {
270     if (!std::is_constant_evaluated()) {
271       std::memset(static_cast<void*>(this), 0, sizeof(*this));
272     }
273     val_ = val;
274   }
275   constexpr IntWithoutPadding(const IntWithoutPadding& other) noexcept : IntWithoutPadding(other.val_) {}
276   constexpr IntWithoutPadding& operator=(const IntWithoutPadding& other) noexcept {
277     val_ = other.val_;
278     return *this;
279   }
280   // See comment on `BoolWithPadding`.
281   constexpr IntWithoutPadding(IntWithoutPadding&&) = default;
282 
283 private:
284   int val_;
285 };
286 
287 template <bool WithPaddedExpected, bool ExpectedVoid>
288 struct CheckForInvalidWritesBaseImpl;
289 template <>
290 struct CheckForInvalidWritesBaseImpl<true, false> {
291   using type = std::expected<IntWithoutPadding, bool>;
292 };
293 template <>
294 struct CheckForInvalidWritesBaseImpl<false, false> {
295   using type = std::expected<BoolWithPadding, bool>;
296 };
297 template <>
298 struct CheckForInvalidWritesBaseImpl<true, true> {
299   using type = std::expected<void, IntWithoutPadding>;
300 };
301 template <>
302 struct CheckForInvalidWritesBaseImpl<false, true> {
303   using type = std::expected<void, BoolWithPadding>;
304 };
305 
306 template <bool WithPaddedExpected, bool ExpectedVoid>
307 using CheckForInvalidWritesBase = typename CheckForInvalidWritesBaseImpl<WithPaddedExpected, ExpectedVoid>::type;
308 
309 template <bool WithPaddedExpected, bool ExpectedVoid = false>
310 struct CheckForInvalidWrites : public CheckForInvalidWritesBase<WithPaddedExpected, ExpectedVoid> {
311   constexpr CheckForInvalidWrites() = default;
312   constexpr CheckForInvalidWrites(std::unexpect_t)
313       : CheckForInvalidWritesBase<WithPaddedExpected, ExpectedVoid>(std::unexpect) {}
314 
315   constexpr CheckForInvalidWrites& operator=(const CheckForInvalidWrites& other) {
316     CheckForInvalidWritesBase<WithPaddedExpected, ExpectedVoid>::operator=(other);
317     return *this;
318   }
319 
320   constexpr CheckForInvalidWrites& operator=(CheckForInvalidWrites&& other) {
321     CheckForInvalidWritesBase<WithPaddedExpected, ExpectedVoid>::operator=(std::move(other));
322     return *this;
323   }
324 
325   using CheckForInvalidWritesBase<WithPaddedExpected, ExpectedVoid>::operator=;
326 
327   const bool please_dont_overwrite_me[7] = {true, true, true, true, true, true, true};
328 
329   constexpr bool check() {
330     for (bool i : please_dont_overwrite_me) {
331       if (!i) {
332         return false;
333       }
334     }
335     return true;
336   }
337 };
338 
339 #endif // TEST_STD_UTILITIES_EXPECTED_TYPES_H
340