Skip to content

Commit d5c5626

Browse files
committed
add swap tests involving TailClobberer
1 parent 63e4c45 commit d5c5626

File tree

5 files changed

+227
-38
lines changed

5 files changed

+227
-38
lines changed

libcxx/test/std/utilities/expected/expected.expected/swap/free.swap.pass.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,35 @@ constexpr bool test() {
180180
assert(!e2.error().swapCalled);
181181
}
182182

183+
// TailClobberer
184+
{
185+
// is_nothrow_move_constructible_v<E>
186+
{
187+
std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, true>> x(std::in_place);
188+
std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, true>> y(std::unexpect);
189+
190+
swap(x, y);
191+
192+
// Both of these would fail if adjusting the "has value" flags happened
193+
// _before_ constructing the member objects inside the `swap`.
194+
assert(!x.has_value());
195+
assert(y.has_value());
196+
}
197+
198+
// !is_nothrow_move_constructible_v<E>
199+
{
200+
std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, false>> x(std::in_place);
201+
std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, false>> y(std::unexpect);
202+
203+
swap(x, y);
204+
205+
// Both of these would fail if adjusting the "has value" flags happened
206+
// _before_ constructing the member objects inside the `swap`.
207+
assert(!x.has_value());
208+
assert(y.has_value());
209+
}
210+
}
211+
183212
return true;
184213
}
185214

@@ -210,6 +239,39 @@ void testException() {
210239
assert(*e1 == 5);
211240
}
212241
}
242+
243+
// TailClobberer
244+
{
245+
// is_nothrow_move_constructible_v<E>
246+
{
247+
std::expected<TailClobbererNonTrivialMove<0, false, true>, TailClobbererNonTrivialMove<1>> x(std::in_place);
248+
std::expected<TailClobbererNonTrivialMove<0, false, true>, TailClobbererNonTrivialMove<1>> y(std::unexpect);
249+
try {
250+
swap(x, y);
251+
assert(false);
252+
} catch (Except) {
253+
assert(x.has_value());
254+
// This would fail if `TailClobbererNonTrivialMove<1>` clobbered the
255+
// flag when rolling back the swap.
256+
assert(!y.has_value());
257+
}
258+
}
259+
260+
// !is_nothrow_move_constructible_v<E>
261+
{
262+
std::expected<TailClobbererNonTrivialMove<0>, TailClobbererNonTrivialMove<1, false, true>> x(std::in_place);
263+
std::expected<TailClobbererNonTrivialMove<0>, TailClobbererNonTrivialMove<1, false, true>> y(std::unexpect);
264+
try {
265+
swap(x, y);
266+
assert(false);
267+
} catch (Except) {
268+
// This would fail if `TailClobbererNonTrivialMove<0>` clobbered the
269+
// flag when rolling back the swap.
270+
assert(x.has_value());
271+
assert(!y.has_value());
272+
}
273+
}
274+
}
213275
#endif // TEST_HAS_NO_EXCEPTIONS
214276
}
215277

libcxx/test/std/utilities/expected/expected.expected/swap/member.swap.pass.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,35 @@ constexpr bool test() {
198198
assert(!e2.error().swapCalled);
199199
}
200200

201+
// TailClobberer
202+
{
203+
// is_nothrow_move_constructible_v<E>
204+
{
205+
std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, true>> x(std::in_place);
206+
std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, true>> y(std::unexpect);
207+
208+
x.swap(y);
209+
210+
// Both of these would fail if adjusting the "has value" flags happened
211+
// _before_ constructing the member objects inside the `swap`.
212+
assert(!x.has_value());
213+
assert(y.has_value());
214+
}
215+
216+
// !is_nothrow_move_constructible_v<E>
217+
{
218+
std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, false>> x(std::in_place);
219+
std::expected<TailClobbererNonTrivialMove<0, true>, TailClobbererNonTrivialMove<1, false>> y(std::unexpect);
220+
221+
x.swap(y);
222+
223+
// Both of these would fail if adjusting the "has value" flags happened
224+
// _before_ constructing the member objects inside the `swap`.
225+
assert(!x.has_value());
226+
assert(y.has_value());
227+
}
228+
}
229+
201230
return true;
202231
}
203232

@@ -228,6 +257,39 @@ void testException() {
228257
assert(*e1 == 5);
229258
}
230259
}
260+
261+
// TailClobberer
262+
{
263+
// is_nothrow_move_constructible_v<E>
264+
{
265+
std::expected<TailClobbererNonTrivialMove<0, false, true>, TailClobbererNonTrivialMove<1>> x(std::in_place);
266+
std::expected<TailClobbererNonTrivialMove<0, false, true>, TailClobbererNonTrivialMove<1>> y(std::unexpect);
267+
try {
268+
x.swap(y);
269+
assert(false);
270+
} catch (Except) {
271+
assert(x.has_value());
272+
// This would fail if `TailClobbererNonTrivialMove<1>` clobbered the
273+
// flag when rolling back the swap.
274+
assert(!y.has_value());
275+
}
276+
}
277+
278+
// !is_nothrow_move_constructible_v<E>
279+
{
280+
std::expected<TailClobbererNonTrivialMove<0>, TailClobbererNonTrivialMove<1, false, true>> x(std::in_place);
281+
std::expected<TailClobbererNonTrivialMove<0>, TailClobbererNonTrivialMove<1, false, true>> y(std::unexpect);
282+
try {
283+
x.swap(y);
284+
assert(false);
285+
} catch (Except) {
286+
// This would fail if `TailClobbererNonTrivialMove<0>` clobbered the
287+
// flag when rolling back the swap.
288+
assert(x.has_value());
289+
assert(!y.has_value());
290+
}
291+
}
292+
}
231293
#endif // TEST_HAS_NO_EXCEPTIONS
232294
}
233295

libcxx/test/std/utilities/expected/expected.void/swap/free.swap.pass.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,19 @@ constexpr bool test() {
117117
assert(s.dtorCalled);
118118
}
119119

120+
// TailClobberer
121+
{
122+
std::expected<void, TailClobbererNonTrivialMove<1>> x(std::in_place);
123+
std::expected<void, TailClobbererNonTrivialMove<1>> y(std::unexpect);
124+
125+
swap(x, y);
126+
127+
// The next line would fail if adjusting the "has value" flag happened
128+
// _before_ constructing the member object inside the `swap`.
129+
assert(!x.has_value());
130+
assert(y.has_value());
131+
}
132+
120133
return true;
121134
}
122135

@@ -151,6 +164,21 @@ void testException() {
151164
assert(!e2Destroyed);
152165
}
153166
}
167+
168+
// TailClobberer
169+
{
170+
std::expected<void, TailClobbererNonTrivialMove<0, false, true>> x(std::in_place);
171+
std::expected<void, TailClobbererNonTrivialMove<0, false, true>> y(std::unexpect);
172+
try {
173+
swap(x, y);
174+
assert(false);
175+
} catch (Except) {
176+
// This would fail if `TailClobbererNonTrivialMove<0, false, true>`
177+
// clobbered the flag before throwing the exception.
178+
assert(x.has_value());
179+
assert(!y.has_value());
180+
}
181+
}
154182
#endif // TEST_HAS_NO_EXCEPTIONS
155183
}
156184

libcxx/test/std/utilities/expected/expected.void/swap/member.swap.pass.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,19 @@ constexpr bool test() {
126126
assert(s.dtorCalled);
127127
}
128128

129+
// TailClobberer
130+
{
131+
std::expected<void, TailClobbererNonTrivialMove<1>> x(std::in_place);
132+
std::expected<void, TailClobbererNonTrivialMove<1>> y(std::unexpect);
133+
134+
x.swap(y);
135+
136+
// The next line would fail if adjusting the "has value" flag happened
137+
// _before_ constructing the member object inside the `swap`.
138+
assert(!x.has_value());
139+
assert(y.has_value());
140+
}
141+
129142
return true;
130143
}
131144

@@ -160,6 +173,21 @@ void testException() {
160173
assert(!e2Destroyed);
161174
}
162175
}
176+
177+
// TailClobberer
178+
{
179+
std::expected<void, TailClobbererNonTrivialMove<0, false, true>> x(std::in_place);
180+
std::expected<void, TailClobbererNonTrivialMove<0, false, true>> y(std::unexpect);
181+
try {
182+
x.swap(y);
183+
assert(false);
184+
} catch (Except) {
185+
// This would fail if `TailClobbererNonTrivialMove<0, false, true>`
186+
// clobbered the flag before throwing the exception.
187+
assert(x.has_value());
188+
assert(!y.has_value());
189+
}
190+
}
163191
#endif // TEST_HAS_NO_EXCEPTIONS
164192
}
165193

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

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -104,44 +104,6 @@ struct TrackedMove {
104104
}
105105
};
106106

107-
// This type has one byte of tail padding where `std::expected` may put its
108-
// "has value" flag. The constructor will clobber all bytes including the
109-
// tail padding. With this type we can check that `std::expected` handles
110-
// the case where the "has value" flag is an overlapping subobject correctly.
111-
//
112-
// See https://github.com/llvm/llvm-project/issues/68552 for details.
113-
template <int constant>
114-
struct TailClobberer {
115-
constexpr TailClobberer() noexcept {
116-
if (!std::is_constant_evaluated()) {
117-
std::memset(this, constant, sizeof(*this));
118-
}
119-
// Always set `b` itself to `false` so that the comparison works.
120-
b = false;
121-
}
122-
constexpr TailClobberer(const TailClobberer&) : TailClobberer() {}
123-
constexpr TailClobberer(TailClobberer&&) = default;
124-
// Converts from `int`/`std::initializer_list<int>, used in some tests.
125-
constexpr TailClobberer(int) : TailClobberer() {}
126-
constexpr TailClobberer(std::initializer_list<int>) noexcept : TailClobberer() {}
127-
128-
friend constexpr bool operator==(const TailClobberer&, const TailClobberer&) = default;
129-
130-
private:
131-
alignas(2) bool b;
132-
};
133-
static_assert(!std::is_trivially_copy_constructible_v<TailClobberer<0>>);
134-
static_assert(std::is_trivially_move_constructible_v<TailClobberer<0>>);
135-
136-
template <int constant>
137-
struct TailClobbererNonTrivialMove : TailClobberer<constant> {
138-
using TailClobberer<constant>::TailClobberer;
139-
constexpr TailClobbererNonTrivialMove(TailClobbererNonTrivialMove&&) noexcept : TailClobberer<constant>() {}
140-
};
141-
static_assert(!std::is_trivially_copy_constructible_v<TailClobbererNonTrivialMove<0>>);
142-
static_assert(std::is_move_constructible_v<TailClobbererNonTrivialMove<0>>);
143-
static_assert(!std::is_trivially_move_constructible_v<TailClobbererNonTrivialMove<0>>);
144-
145107
#ifndef TEST_HAS_NO_EXCEPTIONS
146108
struct Except {};
147109

@@ -190,4 +152,51 @@ struct MoveOnlyErrorType {
190152
MoveOnlyErrorType& operator=(const MoveOnlyErrorType&) = delete;
191153
};
192154

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(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 (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+
193202
#endif // TEST_STD_UTILITIES_EXPECTED_TYPES_H

0 commit comments

Comments
 (0)