aboutsummaryrefslogtreecommitdiff
path: root/libcxx/test
diff options
context:
space:
mode:
authorJan Kokemüller <jan.kokemueller@gmail.com>2024-01-22 15:05:39 +0100
committerGitHub <noreply@github.com>2024-01-22 09:05:39 -0500
commit4f4690530e8b40cdf3a17c76a352b26c2fb0446c (patch)
treec157c476049d6032b0a734ab92e73dbc6d6734e1 /libcxx/test
parentbf7b8dae0615884816fff54cac08bc691746b1ee (diff)
downloadllvm-4f4690530e8b40cdf3a17c76a352b26c2fb0446c.zip
llvm-4f4690530e8b40cdf3a17c76a352b26c2fb0446c.tar.gz
llvm-4f4690530e8b40cdf3a17c76a352b26c2fb0446c.tar.bz2
[libc++] Ensure that std::expected has no tail padding (#69673)
Currently std::expected can have some padding bytes in its tail due to [[no_unique_address]]. Those padding bytes can be used by other objects. For example, in the current implementation: sizeof(std::expected<std::optional<int>, bool>) == sizeof(std::expected<std::expected<std::optional<int>, bool>, bool>) As a result, the data layout of an std::expected<std::expected<std::optional<int>, bool>, bool> can look like this: +-- optional "has value" flag | +--padding /---int---\ | | 00 00 00 00 01 00 00 00 | | | +- "outer" expected "has value" flag | +- expected "has value" flag This is problematic because `emplace()`ing the "inner" expected can not only overwrite the "inner" expected "has value" flag (issue #68552) but also the tail padding where other objects might live. This patch fixes the problem by ensuring that std::expected has no tail padding, which is achieved by conditional usage of [[no_unique_address]] based on the tail padding that this would create. This is an ABI breaking change because the following property changes: sizeof(std::expected<std::optional<int>, bool>) < sizeof(std::expected<std::expected<std::optional<int>, bool>, bool>) Before the change, this relation didn't hold. After the change, the relation does hold, which means that the size of std::expected in these cases increases after this patch. The data layout will change in the following cases where tail padding can be reused by other objects: class foo : std::expected<std::optional<int>, bool> { bool b; }; or using [[no_unique_address]]: struct foo { [[no_unique_address]] std::expected<std::optional<int>, bool> e; bool b; }; The vendor communication is handled in #70820. Fixes: #70494 Co-authored-by: philnik777 <nikolasklauser@berlin.de> Co-authored-by: Louis Dionne <ldionne.2@gmail.com>
Diffstat (limited to 'libcxx/test')
-rw-r--r--libcxx/test/libcxx/utilities/expected/expected.expected/no_unique_address.compile.pass.cpp45
-rw-r--r--libcxx/test/libcxx/utilities/expected/expected.expected/transform_error.mandates.verify.cpp7
-rw-r--r--libcxx/test/libcxx/utilities/expected/expected.void/no_unique_address.compile.pass.cpp38
-rw-r--r--libcxx/test/libcxx/utilities/expected/expected.void/transform_error.mandates.verify.cpp6
-rw-r--r--libcxx/test/std/utilities/expected/expected.expected/assign/assign.U.pass.cpp14
-rw-r--r--libcxx/test/std/utilities/expected/expected.expected/assign/assign.copy.pass.cpp23
-rw-r--r--libcxx/test/std/utilities/expected/expected.expected/assign/assign.move.pass.cpp23
-rw-r--r--libcxx/test/std/utilities/expected/expected.expected/assign/emplace.pass.cpp14
-rw-r--r--libcxx/test/std/utilities/expected/expected.expected/monadic/transform.pass.cpp4
-rw-r--r--libcxx/test/std/utilities/expected/expected.expected/monadic/transform_error.pass.cpp4
-rw-r--r--libcxx/test/std/utilities/expected/expected.expected/swap/member.swap.pass.cpp22
-rw-r--r--libcxx/test/std/utilities/expected/expected.void/assign/assign.copy.pass.cpp22
-rw-r--r--libcxx/test/std/utilities/expected/expected.void/assign/assign.move.pass.cpp22
-rw-r--r--libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.copy.pass.cpp16
-rw-r--r--libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.move.pass.cpp17
-rw-r--r--libcxx/test/std/utilities/expected/expected.void/monadic/transform_error.pass.cpp4
-rw-r--r--libcxx/test/std/utilities/expected/expected.void/swap/member.swap.pass.cpp22
-rw-r--r--libcxx/test/std/utilities/expected/types.h137
18 files changed, 429 insertions, 11 deletions
diff --git a/libcxx/test/libcxx/utilities/expected/expected.expected/no_unique_address.compile.pass.cpp b/libcxx/test/libcxx/utilities/expected/expected.expected/no_unique_address.compile.pass.cpp
index c8c2170..cf1909b 100644
--- a/libcxx/test/libcxx/utilities/expected/expected.expected/no_unique_address.compile.pass.cpp
+++ b/libcxx/test/libcxx/utilities/expected/expected.expected/no_unique_address.compile.pass.cpp
@@ -12,7 +12,10 @@
// test [[no_unique_address]] is applied to the union
+#include <__type_traits/datasizeof.h>
#include <expected>
+#include <optional>
+#include <memory>
struct Empty {};
@@ -23,13 +26,49 @@ struct A {
struct B : public A {
int z_;
+ short z2_;
virtual ~B() = default;
};
+struct BoolWithPadding {
+ explicit operator bool() { return b; }
+
+private:
+ alignas(1024) bool b = false;
+};
+
static_assert(sizeof(std::expected<Empty, Empty>) == sizeof(bool));
static_assert(sizeof(std::expected<Empty, A>) == 2 * sizeof(int) + alignof(std::expected<Empty, A>));
-static_assert(sizeof(std::expected<Empty, B>) == sizeof(B) + alignof(std::expected<Empty, B>));
+static_assert(sizeof(std::expected<Empty, B>) == sizeof(B));
static_assert(sizeof(std::expected<A, Empty>) == 2 * sizeof(int) + alignof(std::expected<A, Empty>));
static_assert(sizeof(std::expected<A, A>) == 2 * sizeof(int) + alignof(std::expected<A, A>));
-static_assert(sizeof(std::expected<B, Empty>) == sizeof(B) + alignof(std::expected<B, Empty>));
-static_assert(sizeof(std::expected<B, B>) == sizeof(B) + alignof(std::expected<B, B>));
+static_assert(sizeof(std::expected<B, Empty>) == sizeof(B));
+static_assert(sizeof(std::expected<B, B>) == sizeof(B));
+
+// Check that `expected`'s datasize is large enough for the parameter type(s).
+static_assert(sizeof(std::expected<BoolWithPadding, Empty>) ==
+ std::__libcpp_datasizeof<std::expected<BoolWithPadding, Empty>>::value);
+static_assert(sizeof(std::expected<Empty, BoolWithPadding>) ==
+ std::__libcpp_datasizeof<std::expected<Empty, BoolWithPadding>>::value);
+
+// In this case, there should be tail padding in the `expected` because `A`
+// itself does _not_ have tail padding.
+static_assert(sizeof(std::expected<A, A>) > std::__libcpp_datasizeof<std::expected<A, A>>::value);
+
+// Test with some real types.
+static_assert(sizeof(std::expected<std::optional<int>, int>) == 8);
+static_assert(std::__libcpp_datasizeof<std::expected<std::optional<int>, int>>::value == 8);
+
+static_assert(sizeof(std::expected<int, std::optional<int>>) == 8);
+static_assert(std::__libcpp_datasizeof<std::expected<int, std::optional<int>>>::value == 8);
+
+static_assert(sizeof(std::expected<int, int>) == 8);
+static_assert(std::__libcpp_datasizeof<std::expected<int, int>>::value == 5);
+
+// clang-format off
+static_assert(std::__libcpp_datasizeof<int>::value == 4);
+static_assert(std::__libcpp_datasizeof<std::expected<int, int>>::value == 5);
+static_assert(std::__libcpp_datasizeof<std::expected<std::expected<int, int>, int>>::value == 8);
+static_assert(std::__libcpp_datasizeof<std::expected<std::expected<std::expected<int, int>, int>, int>>::value == 9);
+static_assert(std::__libcpp_datasizeof<std::expected<std::expected<std::expected<std::expected<int, int>, int>, int>, int>>::value == 12);
+// clang-format on
diff --git a/libcxx/test/libcxx/utilities/expected/expected.expected/transform_error.mandates.verify.cpp b/libcxx/test/libcxx/utilities/expected/expected.expected/transform_error.mandates.verify.cpp
index 82024a0..5bf094b 100644
--- a/libcxx/test/libcxx/utilities/expected/expected.expected/transform_error.mandates.verify.cpp
+++ b/libcxx/test/libcxx/utilities/expected/expected.expected/transform_error.mandates.verify.cpp
@@ -7,11 +7,15 @@
//===----------------------------------------------------------------------===//
// Clang-18 fixed some spurious clang diagnostics. Once clang-18 is the
-// minumum required version these obsolete tests can be removed.
+// minimum required version these obsolete tests can be removed.
// TODO(LLVM-20) remove spurious clang diagnostic tests.
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// With clang-cl, some warnings have a 'which is a Microsoft extension' suffix
+// which break the tests.
+// XFAIL: msvc
+
// Test the mandates
// template<class F> constexpr auto transform_error(F&& f) &;
@@ -52,6 +56,7 @@ void test() {
e.transform_error(return_unexpected<int&>); // expected-error-re@*:* {{static assertion failed {{.*}}The result of {{.*}} must be a valid template argument for unexpected}}
// expected-error-re@*:* 0-1 {{{{(excess elements in struct initializer|no matching constructor for initialization of)}}{{.*}}}}
// expected-error-re@*:* {{static assertion failed {{.*}}[expected.object.general] A program that instantiates the definition of template expected<T, E> for {{.*}} is ill-formed.}}
+ // expected-error-re@*:* {{union member {{.*}} has reference type {{.*}}}}
e.transform_error(return_no_object<int&>); // expected-error-re@*:* {{static assertion failed {{.*}}The result of {{.*}} must be a valid template argument for unexpected}}
// expected-error-re@*:* 0-1 {{{{(excess elements in struct initializer|no matching constructor for initialization of)}}{{.*}}}}
diff --git a/libcxx/test/libcxx/utilities/expected/expected.void/no_unique_address.compile.pass.cpp b/libcxx/test/libcxx/utilities/expected/expected.void/no_unique_address.compile.pass.cpp
index dc59a62..fdee8b7 100644
--- a/libcxx/test/libcxx/utilities/expected/expected.void/no_unique_address.compile.pass.cpp
+++ b/libcxx/test/libcxx/utilities/expected/expected.void/no_unique_address.compile.pass.cpp
@@ -11,8 +11,13 @@
// XFAIL: msvc
// test [[no_unique_address]] is applied to the union
+// In particular, we ensure that we reuse tail padding in the T
+// to store the discriminator whenever we can.
+#include <__type_traits/datasizeof.h>
#include <expected>
+#include <optional>
+#include <memory>
struct Empty {};
@@ -23,9 +28,40 @@ struct A {
struct B : public A {
int z_;
+ short z2_;
virtual ~B() = default;
};
+struct BoolWithPadding {
+ explicit operator bool() { return b; }
+
+private:
+ alignas(1024) bool b = false;
+};
+
static_assert(sizeof(std::expected<void, Empty>) == sizeof(bool));
static_assert(sizeof(std::expected<void, A>) == 2 * sizeof(int) + alignof(std::expected<void, A>));
-static_assert(sizeof(std::expected<void, B>) == sizeof(B) + alignof(std::expected<void, B>));
+static_assert(sizeof(std::expected<void, B>) == sizeof(B));
+
+// Check that `expected`'s datasize is large enough for the parameter type(s).
+static_assert(sizeof(std::expected<void, BoolWithPadding>) ==
+ std::__libcpp_datasizeof<std::expected<void, BoolWithPadding>>::value);
+
+// In this case, there should be tail padding in the `expected` because `A`
+// itself does _not_ have tail padding.
+static_assert(sizeof(std::expected<void, A>) > std::__libcpp_datasizeof<std::expected<void, A>>::value);
+
+// Test with some real types.
+static_assert(sizeof(std::expected<void, std::optional<int>>) == 8);
+static_assert(std::__libcpp_datasizeof<std::expected<void, std::optional<int>>>::value == 8);
+
+static_assert(sizeof(std::expected<void, int>) == 8);
+static_assert(std::__libcpp_datasizeof<std::expected<void, int>>::value == 5);
+
+// clang-format off
+static_assert(std::__libcpp_datasizeof<int>::value == 4);
+static_assert(std::__libcpp_datasizeof<std::expected<void, int>>::value == 5);
+static_assert(std::__libcpp_datasizeof<std::expected<void, std::expected<void, int>>>::value == 8);
+static_assert(std::__libcpp_datasizeof<std::expected<void, std::expected<void, std::expected<void, int>>>>::value == 9);
+static_assert(std::__libcpp_datasizeof<std::expected<void, std::expected<void, std::expected<void, std::expected<void, int>>>>>::value == 12);
+// clang-format on
diff --git a/libcxx/test/libcxx/utilities/expected/expected.void/transform_error.mandates.verify.cpp b/libcxx/test/libcxx/utilities/expected/expected.void/transform_error.mandates.verify.cpp
index 0f2fb58..4f4f5839 100644
--- a/libcxx/test/libcxx/utilities/expected/expected.void/transform_error.mandates.verify.cpp
+++ b/libcxx/test/libcxx/utilities/expected/expected.void/transform_error.mandates.verify.cpp
@@ -12,6 +12,10 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// With clang-cl, some warnings have a 'which is a Microsoft extension' suffix
+// which break the tests.
+// XFAIL: msvc
+
// Test the mandates
// template<class F> constexpr auto transform_error(F&& f) &;
@@ -52,6 +56,8 @@ void test() {
e.transform_error(return_unexpected<int&>); // expected-error-re@*:* {{static assertion failed {{.*}}The result of {{.*}} must be a valid template argument for unexpected}}
// expected-error-re@*:* 0-1 {{{{(excess elements in struct initializer|no matching constructor for initialization of)}}{{.*}}}}
// expected-error-re@*:* {{static assertion failed {{.*}}A program that instantiates expected<T, E> with a E that is not a valid argument for unexpected<E> is ill-formed}}
+ // expected-error-re@*:* {{call to deleted constructor of {{.*}}}}
+ // expected-error-re@*:* {{union member {{.*}} has reference type {{.*}}}}
e.transform_error(return_no_object<int&>); // expected-error-re@*:* {{static assertion failed {{.*}}The result of {{.*}} must be a valid template argument for unexpected}}
// expected-error-re@*:* 0-1 {{{{(excess elements in struct initializer|no matching constructor for initialization of)}}{{.*}}}}
diff --git a/libcxx/test/std/utilities/expected/expected.expected/assign/assign.U.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/assign/assign.U.pass.cpp
index 1c8750b..2d3b036 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/assign/assign.U.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/assign/assign.U.pass.cpp
@@ -310,6 +310,20 @@ constexpr bool test() {
assert(e.value().j == 8);
}
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true> e1(std::unexpect);
+ e1 = 42;
+ assert(e1.check());
+ }
+ {
+ CheckForInvalidWrites<false> e1(std::unexpect);
+ e1 = true;
+ assert(e1.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.expected/assign/assign.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/assign/assign.copy.pass.cpp
index 03fe888..2f52913 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/assign/assign.copy.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/assign/assign.copy.pass.cpp
@@ -240,6 +240,29 @@ constexpr bool test() {
assert(e1.error().data_ == 10);
assert(oldState.copyAssignCalled);
}
+
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true> e1(std::unexpect);
+ CheckForInvalidWrites<true> e2;
+
+ e1 = e2;
+
+ assert(e1.check());
+ assert(e2.check());
+ }
+ {
+ CheckForInvalidWrites<false> e1(std::unexpect);
+ CheckForInvalidWrites<false> e2;
+
+ e1 = e2;
+
+ assert(e1.check());
+ assert(e2.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.expected/assign/assign.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/assign/assign.move.pass.cpp
index 8c419af..065827a 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/assign/assign.move.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/assign/assign.move.pass.cpp
@@ -258,6 +258,29 @@ constexpr bool test() {
assert(e1.error().data_ == 10);
assert(oldState.moveAssignCalled);
}
+
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true> e1(std::unexpect);
+ CheckForInvalidWrites<true> e2;
+
+ e1 = std::move(e2);
+
+ assert(e1.check());
+ assert(e2.check());
+ }
+ {
+ CheckForInvalidWrites<false> e1(std::unexpect);
+ CheckForInvalidWrites<false> e2;
+
+ e1 = std::move(e2);
+
+ assert(e1.check());
+ assert(e2.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.expected/assign/emplace.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/assign/emplace.pass.cpp
index 491de2d..7e37f8b 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/assign/emplace.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/assign/emplace.pass.cpp
@@ -80,6 +80,20 @@ constexpr bool test() {
assert(e.has_value());
}
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true> e;
+ e.emplace();
+ assert(e.check());
+ }
+ {
+ CheckForInvalidWrites<false> e;
+ e.emplace();
+ assert(e.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.expected/monadic/transform.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/monadic/transform.pass.cpp
index f443613..d38a46f 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/monadic/transform.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/monadic/transform.pass.cpp
@@ -9,8 +9,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
// GCC has a issue for `Guaranteed copy elision for potentially-overlapping non-static data members`,
-// please refer to: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108333, but we have a workaround to
-// avoid this issue.
+// please refer to: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108333
+// XFAIL: gcc-13
// <expected>
diff --git a/libcxx/test/std/utilities/expected/expected.expected/monadic/transform_error.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/monadic/transform_error.pass.cpp
index 84b57ae..ec55f63 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/monadic/transform_error.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/monadic/transform_error.pass.cpp
@@ -9,8 +9,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
// GCC has a issue for `Guaranteed copy elision for potentially-overlapping non-static data members`,
-// please refer to: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108333, but we have a workaround to
-// avoid this issue.
+// please refer to: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108333.
+// XFAIL: gcc-13
// <expected>
diff --git a/libcxx/test/std/utilities/expected/expected.expected/swap/member.swap.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/swap/member.swap.pass.cpp
index 3478230..f19599d 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/swap/member.swap.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/swap/member.swap.pass.cpp
@@ -227,6 +227,28 @@ constexpr bool test() {
}
}
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true> x(std::unexpect);
+ CheckForInvalidWrites<true> y;
+
+ x.swap(y);
+
+ assert(x.check());
+ assert(y.check());
+ }
+ {
+ CheckForInvalidWrites<false> x(std::unexpect);
+ CheckForInvalidWrites<false> y;
+
+ x.swap(y);
+
+ assert(x.check());
+ assert(y.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.void/assign/assign.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/assign/assign.copy.pass.cpp
index b1968bc..a51916f 100644
--- a/libcxx/test/std/utilities/expected/expected.void/assign/assign.copy.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/assign/assign.copy.pass.cpp
@@ -99,6 +99,28 @@ constexpr bool test() {
assert(state.copyAssignCalled);
}
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true, true> e1;
+ CheckForInvalidWrites<true, true> e2(std::unexpect);
+
+ e1 = e2;
+
+ assert(e1.check());
+ assert(e2.check());
+ }
+ {
+ CheckForInvalidWrites<false, true> e1;
+ CheckForInvalidWrites<false, true> e2(std::unexpect);
+
+ e1 = e2;
+
+ assert(e1.check());
+ assert(e2.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.void/assign/assign.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/assign/assign.move.pass.cpp
index e6a29cd..60ae034 100644
--- a/libcxx/test/std/utilities/expected/expected.void/assign/assign.move.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/assign/assign.move.pass.cpp
@@ -125,6 +125,28 @@ constexpr bool test() {
assert(state.moveAssignCalled);
}
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true, true> e1;
+ CheckForInvalidWrites<true, true> e2(std::unexpect);
+
+ e1 = std::move(e2);
+
+ assert(e1.check());
+ assert(e2.check());
+ }
+ {
+ CheckForInvalidWrites<false, true> e1;
+ CheckForInvalidWrites<false, true> e2(std::unexpect);
+
+ e1 = std::move(e2);
+
+ assert(e1.check());
+ assert(e2.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.copy.pass.cpp
index 1ae9653..699597d 100644
--- a/libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.copy.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.copy.pass.cpp
@@ -91,6 +91,22 @@ constexpr bool test() {
assert(state1.copyAssignCalled);
}
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true, true> e;
+ std::unexpected<int> un(std::in_place, 42);
+ e = un;
+ assert(e.check());
+ }
+ {
+ CheckForInvalidWrites<false, true> e;
+ std::unexpected<bool> un(std::in_place, true);
+ e = un;
+ assert(e.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.move.pass.cpp
index ea94771..641eb492 100644
--- a/libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.move.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/assign/assign.unexpected.move.pass.cpp
@@ -172,6 +172,23 @@ constexpr bool test() {
assert(e1.error().data_ == 10);
assert(oldState.moveAssignCalled);
}
+
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true, true> e;
+ std::unexpected<int> un(std::in_place, 42);
+ e = std::move(un);
+ assert(e.check());
+ }
+ {
+ CheckForInvalidWrites<false, true> e;
+ std::unexpected<bool> un(std::in_place, true);
+ e = std::move(un);
+ assert(e.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.void/monadic/transform_error.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/monadic/transform_error.pass.cpp
index f0e19ac..cd6e5a5 100644
--- a/libcxx/test/std/utilities/expected/expected.void/monadic/transform_error.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/monadic/transform_error.pass.cpp
@@ -9,8 +9,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
// GCC has a issue for `Guaranteed copy elision for potentially-overlapping non-static data members`,
-// please refer to: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108333, but we have a workaround to
-// avoid this issue.
+// please refer to: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108333
+// XFAIL: gcc-13
// <expected>
diff --git a/libcxx/test/std/utilities/expected/expected.void/swap/member.swap.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/swap/member.swap.pass.cpp
index 07980de..25601af 100644
--- a/libcxx/test/std/utilities/expected/expected.void/swap/member.swap.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/swap/member.swap.pass.cpp
@@ -139,6 +139,28 @@ constexpr bool test() {
assert(y.has_value());
}
+ // CheckForInvalidWrites
+ {
+ {
+ CheckForInvalidWrites<true, true> x(std::unexpect);
+ CheckForInvalidWrites<true, true> y;
+
+ x.swap(y);
+
+ assert(x.check());
+ assert(y.check());
+ }
+ {
+ CheckForInvalidWrites<false, true> x(std::unexpect);
+ CheckForInvalidWrites<false, true> y;
+
+ x.swap(y);
+
+ assert(x.check());
+ assert(y.check());
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/types.h b/libcxx/test/std/utilities/expected/types.h
index 90768ed..2b6983f 100644
--- a/libcxx/test/std/utilities/expected/types.h
+++ b/libcxx/test/std/utilities/expected/types.h
@@ -199,4 +199,141 @@ static_assert(!std::is_trivially_move_constructible_v<TailClobbererNonTrivialMov
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