[libcxx-commits] [libcxx] [libc++] Ensure that `std::expected` has no tail padding (PR #69673)
Jan Kokemüller via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Oct 19 19:07:56 PDT 2023
https://github.com/jiixyj created https://github.com/llvm/llvm-project/pull/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:
```c++
sizeof(std::expected<std::optional<int>, bool>) == sizeof(std::expected<std::expected<std::optional<int>, bool>, bool>);
```
...so 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 <https://github.com/llvm/llvm-project/issues/68552>) but _also_ the tail padding where other objects might live.
@philnik777 proposed to add a "char __padding_[]" array to the end of the `expected` to make sure that the `expected` itself never has any tail padding bytes that might get used by other objects.
This is an ABI breaking change because
`sizeof(std::expected<std::optional<int>, bool>) < sizeof(std::expected<std::expected<std::optional<int>, bool>, bool>);` afterwards. The data layout will change in the following cases where tail padding can be reused by other objects:
```c++
class foo : std::expected<std::optional<int>, bool> {
bool b;
};
```
or using `[[no_unique_address]]`:
```c++
struct foo {
[[no_unique_address]] std::expected<std::optional<int>, bool> e;
bool b;
};
```
>From 74b266db78accc3f23e0cc1d96aa7277eeccdb2f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= <jan.kokemueller at gmail.com>
Date: Sun, 15 Oct 2023 22:09:51 +0200
Subject: [PATCH] ensure that expected has no tail padding
---
libcxx/include/__expected/expected.h | 20 +++++++++++++++++++
.../expected.expected/assign/emplace.pass.cpp | 16 +++++++++++++++
2 files changed, 36 insertions(+)
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 045370a486fae6b..463060702235742 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -18,6 +18,7 @@
#include <__memory/addressof.h>
#include <__memory/construct_at.h>
#include <__type_traits/conjunction.h>
+#include <__type_traits/datasizeof.h>
#include <__type_traits/disjunction.h>
#include <__type_traits/integral_constant.h>
#include <__type_traits/is_assignable.h>
@@ -955,8 +956,17 @@ class expected {
_LIBCPP_NO_UNIQUE_ADDRESS _ErrorType __unex_;
};
+ _LIBCPP_HIDE_FROM_ABI static constexpr auto __calculate_padding() {
+ struct __calc_expected {
+ _LIBCPP_NO_UNIQUE_ADDRESS __union_t<_Tp, _Err> __union_;
+ bool __has_val_;
+ };
+ return sizeof(__calc_expected) - __libcpp_datasizeof<__calc_expected>::value;
+ }
+
_LIBCPP_NO_UNIQUE_ADDRESS __union_t<_Tp, _Err> __union_;
bool __has_val_;
+ char __padding_[__calculate_padding()]{};
};
template <class _Tp, class _Err>
@@ -1552,8 +1562,18 @@ class expected<_Tp, _Err> {
_LIBCPP_NO_UNIQUE_ADDRESS _ErrorType __unex_;
};
+
+ _LIBCPP_HIDE_FROM_ABI static constexpr auto __calculate_padding() {
+ struct __calc_expected {
+ _LIBCPP_NO_UNIQUE_ADDRESS __union_t<_Err> __union_;
+ bool __has_val_;
+ };
+ return sizeof(__calc_expected) - __libcpp_datasizeof<__calc_expected>::value;
+ }
+
_LIBCPP_NO_UNIQUE_ADDRESS __union_t<_Err> __union_;
bool __has_val_;
+ char __padding_[__calculate_padding()]{};
};
_LIBCPP_END_NAMESPACE_STD
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 c62e6289350201a..5be8d2b5fb74e40 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
@@ -73,6 +73,22 @@ constexpr bool test() {
assert(e.value() == 10);
}
+ // Test that `expected` has no tail padding.
+ {
+ struct BoolWithPadding {
+ explicit operator bool() { return b; }
+
+ private:
+ alignas(4) bool b = false;
+ };
+
+ static_assert(sizeof(std::expected<BoolWithPadding, bool>) ==
+ std::__libcpp_datasizeof<std::expected<BoolWithPadding, bool>>::value);
+
+ static_assert(sizeof(std::expected<void, BoolWithPadding>) ==
+ std::__libcpp_datasizeof<std::expected<void, BoolWithPadding>>::value);
+ }
+
return true;
}
More information about the libcxx-commits
mailing list