[libcxx-commits] [libcxx] [libc++] Fix UB in <expected> related to "has value" flag (#68552) (PR #68733)

Jan Kokemüller via libcxx-commits libcxx-commits at lists.llvm.org
Tue Oct 17 09:22:10 PDT 2023


https://github.com/jiixyj updated https://github.com/llvm/llvm-project/pull/68733

>From 9832a97756cdb800947827ff881f0509159bcc12 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= <jan.kokemueller at gmail.com>
Date: Tue, 10 Oct 2023 20:24:06 +0200
Subject: [PATCH 1/8] [libc++] Fix UB in <expected> related to "has value" flag
 (#68552)

The calls to `std::construct_at` might overwrite the previously set
`__has_value_` flag, so reverse the order everywhere. Where possible,
avoid calling `std::construct_at` and construct the value/error
directly into the union.
---
 libcxx/include/__expected/expected.h          | 168 ++++++++----------
 .../observers/has_value.pass.cpp              |  39 ++++
 2 files changed, 117 insertions(+), 90 deletions(-)

diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 045370a486fae6b..08f35b1111f6bf9 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -119,9 +119,7 @@ class expected {
   _LIBCPP_HIDE_FROM_ABI constexpr expected()
     noexcept(is_nothrow_default_constructible_v<_Tp>) // strengthened
     requires is_default_constructible_v<_Tp>
-      : __has_val_(true) {
-    std::construct_at(std::addressof(__union_.__val_));
-  }
+      : __union_(__construct_in_place_tag{}), __has_val_(true) {}
 
   _LIBCPP_HIDE_FROM_ABI constexpr expected(const expected&) = delete;
 
@@ -136,14 +134,7 @@ class expected {
     noexcept(is_nothrow_copy_constructible_v<_Tp> && is_nothrow_copy_constructible_v<_Err>) // strengthened
     requires(is_copy_constructible_v<_Tp> && is_copy_constructible_v<_Err> &&
              !(is_trivially_copy_constructible_v<_Tp> && is_trivially_copy_constructible_v<_Err>))
-      : __has_val_(__other.__has_val_) {
-    if (__has_val_) {
-      std::construct_at(std::addressof(__union_.__val_), __other.__union_.__val_);
-    } else {
-      std::construct_at(std::addressof(__union_.__unex_), __other.__union_.__unex_);
-    }
-  }
-
+      : __union_(__union_from_expected(__other)), __has_val_(__other.__has_val_) { }
 
   _LIBCPP_HIDE_FROM_ABI constexpr expected(expected&&)
     requires(is_move_constructible_v<_Tp> && is_move_constructible_v<_Err>
@@ -154,13 +145,7 @@ class expected {
     noexcept(is_nothrow_move_constructible_v<_Tp> && is_nothrow_move_constructible_v<_Err>)
     requires(is_move_constructible_v<_Tp> && is_move_constructible_v<_Err> &&
              !(is_trivially_move_constructible_v<_Tp> && is_trivially_move_constructible_v<_Err>))
-      : __has_val_(__other.__has_val_) {
-    if (__has_val_) {
-      std::construct_at(std::addressof(__union_.__val_), std::move(__other.__union_.__val_));
-    } else {
-      std::construct_at(std::addressof(__union_.__unex_), std::move(__other.__union_.__unex_));
-    }
-  }
+      : __union_(__union_from_expected(std::move(__other))), __has_val_(__other.__has_val_) { }
 
 private:
   template <class _Up, class _OtherErr, class _UfQual, class _OtherErrQual>
@@ -200,26 +185,14 @@ class expected {
   expected(const expected<_Up, _OtherErr>& __other)
     noexcept(is_nothrow_constructible_v<_Tp, const _Up&> &&
              is_nothrow_constructible_v<_Err, const _OtherErr&>) // strengthened
-      : __has_val_(__other.__has_val_) {
-    if (__has_val_) {
-      std::construct_at(std::addressof(__union_.__val_), __other.__union_.__val_);
-    } else {
-      std::construct_at(std::addressof(__union_.__unex_), __other.__union_.__unex_);
-    }
-  }
+      : __union_(__union_from_expected(__other)), __has_val_(__other.__has_val_) {}
 
   template <class _Up, class _OtherErr>
     requires __can_convert<_Up, _OtherErr, _Up, _OtherErr>::value
   _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_Up, _Tp> || !is_convertible_v<_OtherErr, _Err>)
   expected(expected<_Up, _OtherErr>&& __other)
     noexcept(is_nothrow_constructible_v<_Tp, _Up> && is_nothrow_constructible_v<_Err, _OtherErr>) // strengthened
-      : __has_val_(__other.__has_val_) {
-    if (__has_val_) {
-      std::construct_at(std::addressof(__union_.__val_), std::move(__other.__union_.__val_));
-    } else {
-      std::construct_at(std::addressof(__union_.__unex_), std::move(__other.__union_.__unex_));
-    }
-  }
+      : __union_(__union_from_expected(std::move(__other))), __has_val_(__other.__has_val_) {}
 
   template <class _Up = _Tp>
     requires(!is_same_v<remove_cvref_t<_Up>, in_place_t> && !is_same_v<expected, remove_cvref_t<_Up>> &&
@@ -227,61 +200,47 @@ class expected {
              (!is_same_v<remove_cv_t<_Tp>, bool> || !__is_std_expected<remove_cvref_t<_Up>>::value))
   _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_Up, _Tp>)
       expected(_Up&& __u) noexcept(is_nothrow_constructible_v<_Tp, _Up>) // strengthened
-      : __has_val_(true) {
-    std::construct_at(std::addressof(__union_.__val_), std::forward<_Up>(__u));
-  }
+      : __union_(__construct_in_place_tag{}, std::forward<_Up>(__u)), __has_val_(true) {}
 
   template <class _OtherErr>
     requires is_constructible_v<_Err, const _OtherErr&>
   _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<const _OtherErr&, _Err>)
   expected(const unexpected<_OtherErr>& __unex)
     noexcept(is_nothrow_constructible_v<_Err, const _OtherErr&>) // strengthened
-      : __has_val_(false) {
-    std::construct_at(std::addressof(__union_.__unex_), __unex.error());
-  }
+      : __union_(__construct_unexpected_tag{}, __unex.error()), __has_val_(false) {}
 
   template <class _OtherErr>
     requires is_constructible_v<_Err, _OtherErr>
   _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_OtherErr, _Err>)
   expected(unexpected<_OtherErr>&& __unex)
     noexcept(is_nothrow_constructible_v<_Err, _OtherErr>) // strengthened
-      : __has_val_(false) {
-    std::construct_at(std::addressof(__union_.__unex_), std::move(__unex.error()));
-  }
+      : __union_(__construct_unexpected_tag{}, std::move(__unex.error())), __has_val_(false) {}
 
   template <class... _Args>
     requires is_constructible_v<_Tp, _Args...>
   _LIBCPP_HIDE_FROM_ABI constexpr explicit expected(in_place_t, _Args&&... __args)
     noexcept(is_nothrow_constructible_v<_Tp, _Args...>) // strengthened
-      : __has_val_(true) {
-    std::construct_at(std::addressof(__union_.__val_), std::forward<_Args>(__args)...);
-  }
+      : __union_(__construct_in_place_tag{}, std::forward<_Args>(__args)...), __has_val_(true) {}
 
   template <class _Up, class... _Args>
     requires is_constructible_v< _Tp, initializer_list<_Up>&, _Args... >
   _LIBCPP_HIDE_FROM_ABI constexpr explicit
   expected(in_place_t, initializer_list<_Up> __il, _Args&&... __args)
     noexcept(is_nothrow_constructible_v<_Tp, initializer_list<_Up>&, _Args...>) // strengthened
-      : __has_val_(true) {
-    std::construct_at(std::addressof(__union_.__val_), __il, std::forward<_Args>(__args)...);
-  }
+      : __union_(__construct_in_place_tag{}, __il, std::forward<_Args>(__args)...), __has_val_(true) {}
 
   template <class... _Args>
     requires is_constructible_v<_Err, _Args...>
   _LIBCPP_HIDE_FROM_ABI constexpr explicit expected(unexpect_t, _Args&&... __args)
-    noexcept(is_nothrow_constructible_v<_Err, _Args...>)  // strengthened
-      : __has_val_(false) {
-    std::construct_at(std::addressof(__union_.__unex_), std::forward<_Args>(__args)...);
-  }
+    noexcept(is_nothrow_constructible_v<_Err, _Args...>) // strengthened
+      : __union_(__construct_unexpected_tag{}, std::forward<_Args>(__args)...), __has_val_(false) {}
 
   template <class _Up, class... _Args>
     requires is_constructible_v< _Err, initializer_list<_Up>&, _Args... >
   _LIBCPP_HIDE_FROM_ABI constexpr explicit
   expected(unexpect_t, initializer_list<_Up> __il, _Args&&... __args)
     noexcept(is_nothrow_constructible_v<_Err, initializer_list<_Up>&, _Args...>) // strengthened
-      : __has_val_(false) {
-    std::construct_at(std::addressof(__union_.__unex_), __il, std::forward<_Args>(__args)...);
-  }
+      : __union_(__construct_unexpected_tag{}, __il, std::forward<_Args>(__args)...), __has_val_(false) {}
 
   // [expected.object.dtor], destructor
 
@@ -440,9 +399,10 @@ class expected {
       std::destroy_at(std::addressof(__union_.__val_));
     } else {
       std::destroy_at(std::addressof(__union_.__unex_));
-      __has_val_ = true;
     }
-    return *std::construct_at(std::addressof(__union_.__val_), std::forward<_Args>(__args)...);
+    std::construct_at(std::addressof(__union_.__val_), std::forward<_Args>(__args)...);
+    __has_val_ = true;
+    return *std::addressof(__union_.__val_);
   }
 
   template <class _Up, class... _Args>
@@ -452,9 +412,10 @@ class expected {
       std::destroy_at(std::addressof(__union_.__val_));
     } else {
       std::destroy_at(std::addressof(__union_.__unex_));
-      __has_val_ = true;
     }
-    return *std::construct_at(std::addressof(__union_.__val_), __il, std::forward<_Args>(__args)...);
+    std::construct_at(std::addressof(__union_.__val_), __il, std::forward<_Args>(__args)...);
+    __has_val_ = true;
+    return *std::addressof(__union_.__val_);
   }
 
 
@@ -894,11 +855,21 @@ class expected {
 
 private:
   struct __empty_t {};
+  struct __construct_in_place_tag {};
+  struct __construct_unexpected_tag {};
 
   template <class _ValueType, class _ErrorType>
   union __union_t {
     _LIBCPP_HIDE_FROM_ABI constexpr __union_t() {}
 
+    template <class... _Args>
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_in_place_tag, _Args&&... __args)
+        : __val_(std::forward<_Args>(__args)...) {}
+
+    template <class... _Args>
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_unexpected_tag, _Args&&... __args)
+        : __unex_(std::forward<_Args>(__args)...) {}
+
     template <class _Func, class... _Args>
     _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(
         std::__expected_construct_in_place_from_invoke_tag, _Func&& __f, _Args&&... __args)
@@ -931,6 +902,14 @@ class expected {
     _LIBCPP_HIDE_FROM_ABI constexpr __union_t(const __union_t&) = default;
     _LIBCPP_HIDE_FROM_ABI constexpr __union_t& operator=(const __union_t&) = default;
 
+    template <class... _Args>
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_in_place_tag, _Args&&... __args)
+        : __val_(std::forward<_Args>(__args)...) {}
+
+    template <class... _Args>
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_unexpected_tag, _Args&&... __args)
+        : __unex_(std::forward<_Args>(__args)...) {}
+
     template <class _Func, class... _Args>
     _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(
         std::__expected_construct_in_place_from_invoke_tag, _Func&& __f, _Args&&... __args)
@@ -955,6 +934,18 @@ class expected {
     _LIBCPP_NO_UNIQUE_ADDRESS _ErrorType __unex_;
   };
 
+  template <class _Up, class _OtherErr>
+  _LIBCPP_HIDE_FROM_ABI constexpr __union_t<_Tp, _Err> __union_from_expected(const expected<_Up, _OtherErr>& __other) {
+    return __other.__has_val_ ? __union_t<_Tp, _Err>(__construct_in_place_tag{}, __other.__union_.__val_)
+                              : __union_t<_Tp, _Err>(__construct_unexpected_tag{}, __other.__union_.__unex_);
+  }
+
+  template <class _Up, class _OtherErr>
+  _LIBCPP_HIDE_FROM_ABI constexpr __union_t<_Tp, _Err> __union_from_expected(expected<_Up, _OtherErr>&& __other) {
+    return __other.__has_val_ ? __union_t<_Tp, _Err>(__construct_in_place_tag{}, std::move(__other.__union_.__val_))
+                              : __union_t<_Tp, _Err>(__construct_unexpected_tag{}, std::move(__other.__union_.__unex_));
+  }
+
   _LIBCPP_NO_UNIQUE_ADDRESS __union_t<_Tp, _Err> __union_;
   bool __has_val_;
 };
@@ -998,11 +989,7 @@ class expected<_Tp, _Err> {
   _LIBCPP_HIDE_FROM_ABI constexpr expected(const expected& __rhs)
     noexcept(is_nothrow_copy_constructible_v<_Err>) // strengthened
     requires(is_copy_constructible_v<_Err> && !is_trivially_copy_constructible_v<_Err>)
-      : __has_val_(__rhs.__has_val_) {
-    if (!__rhs.__has_val_) {
-      std::construct_at(std::addressof(__union_.__unex_), __rhs.__union_.__unex_);
-    }
-  }
+      : __union_(__union_from_expected(__rhs)), __has_val_(__rhs.__has_val_) {}
 
   _LIBCPP_HIDE_FROM_ABI constexpr expected(expected&&)
     requires(is_move_constructible_v<_Err> && is_trivially_move_constructible_v<_Err>)
@@ -1011,51 +998,35 @@ class expected<_Tp, _Err> {
   _LIBCPP_HIDE_FROM_ABI constexpr expected(expected&& __rhs)
     noexcept(is_nothrow_move_constructible_v<_Err>)
     requires(is_move_constructible_v<_Err> && !is_trivially_move_constructible_v<_Err>)
-      : __has_val_(__rhs.__has_val_) {
-    if (!__rhs.__has_val_) {
-      std::construct_at(std::addressof(__union_.__unex_), std::move(__rhs.__union_.__unex_));
-    }
-  }
+      : __union_(__union_from_expected(std::move(__rhs))), __has_val_(__rhs.__has_val_) {}
 
   template <class _Up, class _OtherErr>
     requires __can_convert<_Up, _OtherErr, const _OtherErr&>::value
   _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<const _OtherErr&, _Err>)
   expected(const expected<_Up, _OtherErr>& __rhs)
     noexcept(is_nothrow_constructible_v<_Err, const _OtherErr&>) // strengthened
-      : __has_val_(__rhs.__has_val_) {
-    if (!__rhs.__has_val_) {
-      std::construct_at(std::addressof(__union_.__unex_), __rhs.__union_.__unex_);
-    }
-  }
+      : __union_(__union_from_expected(__rhs)), __has_val_(__rhs.__has_val_) {}
 
   template <class _Up, class _OtherErr>
     requires __can_convert<_Up, _OtherErr, _OtherErr>::value
   _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_OtherErr, _Err>)
   expected(expected<_Up, _OtherErr>&& __rhs)
     noexcept(is_nothrow_constructible_v<_Err, _OtherErr>) // strengthened
-      : __has_val_(__rhs.__has_val_) {
-    if (!__rhs.__has_val_) {
-      std::construct_at(std::addressof(__union_.__unex_), std::move(__rhs.__union_.__unex_));
-    }
-  }
+      : __union_(__union_from_expected(std::move(__rhs))), __has_val_(__rhs.__has_val_) {}
 
   template <class _OtherErr>
     requires is_constructible_v<_Err, const _OtherErr&>
   _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<const _OtherErr&, _Err>)
   expected(const unexpected<_OtherErr>& __unex)
     noexcept(is_nothrow_constructible_v<_Err, const _OtherErr&>) // strengthened
-      : __has_val_(false) {
-    std::construct_at(std::addressof(__union_.__unex_), __unex.error());
-  }
+      : __union_(__construct_unexpected_tag{}, __unex.error()), __has_val_(false) {}
 
   template <class _OtherErr>
     requires is_constructible_v<_Err, _OtherErr>
   _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_OtherErr, _Err>)
   expected(unexpected<_OtherErr>&& __unex)
     noexcept(is_nothrow_constructible_v<_Err, _OtherErr>) // strengthened
-      : __has_val_(false) {
-    std::construct_at(std::addressof(__union_.__unex_), std::move(__unex.error()));
-  }
+      : __union_(__construct_unexpected_tag{}, std::move(__unex.error())), __has_val_(false) {}
 
   _LIBCPP_HIDE_FROM_ABI constexpr explicit expected(in_place_t) noexcept : __has_val_(true) {}
 
@@ -1063,17 +1034,13 @@ class expected<_Tp, _Err> {
     requires is_constructible_v<_Err, _Args...>
   _LIBCPP_HIDE_FROM_ABI constexpr explicit expected(unexpect_t, _Args&&... __args)
     noexcept(is_nothrow_constructible_v<_Err, _Args...>) // strengthened
-      : __has_val_(false) {
-    std::construct_at(std::addressof(__union_.__unex_), std::forward<_Args>(__args)...);
-  }
+      : __union_(__construct_unexpected_tag{}, std::forward<_Args>(__args)...), __has_val_(false) {}
 
   template <class _Up, class... _Args>
     requires is_constructible_v< _Err, initializer_list<_Up>&, _Args... >
   _LIBCPP_HIDE_FROM_ABI constexpr explicit expected(unexpect_t, initializer_list<_Up> __il, _Args&&... __args)
     noexcept(is_nothrow_constructible_v<_Err, initializer_list<_Up>&, _Args...>) // strengthened
-      : __has_val_(false) {
-    std::construct_at(std::addressof(__union_.__unex_), __il, std::forward<_Args>(__args)...);
-  }
+      : __union_(__construct_unexpected_tag{}, __il, std::forward<_Args>(__args)...), __has_val_(false) {}
 
 private:
   template <class _Func>
@@ -1502,11 +1469,16 @@ class expected<_Tp, _Err> {
 
 private:
   struct __empty_t {};
+  struct __construct_unexpected_tag {};
 
   template <class _ErrorType>
   union __union_t {
     _LIBCPP_HIDE_FROM_ABI constexpr __union_t() : __empty_() {}
 
+    template <class... _Args>
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_unexpected_tag, _Args&&... __args)
+        : __unex_(std::forward<_Args>(__args)...) {}
+
     template <class _Func, class... _Args>
     _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(
         __expected_construct_unexpected_from_invoke_tag, _Func&& __f, _Args&&... __args)
@@ -1534,6 +1506,10 @@ class expected<_Tp, _Err> {
     _LIBCPP_HIDE_FROM_ABI constexpr __union_t(const __union_t&) = default;
     _LIBCPP_HIDE_FROM_ABI constexpr __union_t& operator=(const __union_t&) = default;
 
+    template <class... _Args>
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_unexpected_tag, _Args&&... __args)
+        : __unex_(std::forward<_Args>(__args)...) {}
+
     template <class _Func, class... _Args>
     _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(
         __expected_construct_unexpected_from_invoke_tag, _Func&& __f, _Args&&... __args)
@@ -1552,6 +1528,18 @@ class expected<_Tp, _Err> {
     _LIBCPP_NO_UNIQUE_ADDRESS _ErrorType __unex_;
   };
 
+  template <class _Up, class _OtherErr>
+  _LIBCPP_HIDE_FROM_ABI constexpr __union_t<_Err> __union_from_expected(const expected<_Up, _OtherErr>& __other) {
+    return __other.__has_val_ ? __union_t<_Err>()
+                              : __union_t<_Err>(__construct_unexpected_tag{}, __other.__union_.__unex_);
+  }
+
+  template <class _Up, class _OtherErr>
+  _LIBCPP_HIDE_FROM_ABI constexpr __union_t<_Err> __union_from_expected(expected<_Up, _OtherErr>&& __other) {
+    return __other.__has_val_ ? __union_t<_Err>()
+                              : __union_t<_Err>(__construct_unexpected_tag{}, std::move(__other.__union_.__unex_));
+  }
+
   _LIBCPP_NO_UNIQUE_ADDRESS __union_t<_Err> __union_;
   bool __has_val_;
 };
diff --git a/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp
index 27d657a065699ea..8979e0f45d44f50 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp
@@ -12,6 +12,7 @@
 #include <cassert>
 #include <concepts>
 #include <expected>
+#include <optional>
 #include <type_traits>
 #include <utility>
 
@@ -30,6 +31,22 @@ static_assert(!HasValueNoexcept<Foo>);
 static_assert(HasValueNoexcept<std::expected<int, int>>);
 static_assert(HasValueNoexcept<const std::expected<int, int>>);
 
+// This type has one byte of tail padding where `std::expected` will put its
+// "has value" flag. The constructor will clobber all bytes including the
+// tail padding. With this type we can check that `std::expected` will set
+// its "has value" flag _after_ the value/error object is constructed.
+template <int c>
+struct tail_clobberer {
+  constexpr tail_clobberer() {
+    if (!std::is_constant_evaluated()) {
+      // This `memset` might actually be UB (?) but suffices to reproduce bugs
+      // related to the "has value" flag.
+      std::memset(this, c, sizeof(*this));
+    }
+  }
+  alignas(2) bool b;
+};
+
 constexpr bool test() {
   // has_value
   {
@@ -43,6 +60,28 @@ constexpr bool test() {
     assert(!e.has_value());
   }
 
+  // See https://github.com/llvm/llvm-project/issues/68552
+  {
+    static constexpr auto f1 = [] -> std::expected<std::optional<int>, long> { return 0; };
+
+    static constexpr auto f2 = [] -> std::expected<std::optional<int>, int> {
+      return f1().transform_error([](auto) { return 0; });
+    };
+
+    auto e = f2();
+    assert(e.has_value());
+  }
+  {
+    const std::expected<tail_clobberer<0>, bool> e = {};
+    static_assert(sizeof(tail_clobberer<0>) == sizeof(e));
+    assert(e.has_value());
+  }
+  {
+    const std::expected<void, tail_clobberer<1>> e(std::unexpect);
+    static_assert(sizeof(tail_clobberer<1>) == sizeof(e));
+    assert(!e.has_value());
+  }
+
   return true;
 }
 

>From 1f9028a0de836e8e30a58f4da4bcc1b4610c21fb 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 13:32:14 +0200
Subject: [PATCH 2/8] replace home-grown constructor tags with standard ones

---
 libcxx/include/__expected/expected.h | 51 +++++++++++++---------------
 1 file changed, 24 insertions(+), 27 deletions(-)

diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 08f35b1111f6bf9..6440b31e7183201 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -119,7 +119,7 @@ class expected {
   _LIBCPP_HIDE_FROM_ABI constexpr expected()
     noexcept(is_nothrow_default_constructible_v<_Tp>) // strengthened
     requires is_default_constructible_v<_Tp>
-      : __union_(__construct_in_place_tag{}), __has_val_(true) {}
+      : __union_(std::in_place), __has_val_(true) {}
 
   _LIBCPP_HIDE_FROM_ABI constexpr expected(const expected&) = delete;
 
@@ -200,47 +200,47 @@ class expected {
              (!is_same_v<remove_cv_t<_Tp>, bool> || !__is_std_expected<remove_cvref_t<_Up>>::value))
   _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_Up, _Tp>)
       expected(_Up&& __u) noexcept(is_nothrow_constructible_v<_Tp, _Up>) // strengthened
-      : __union_(__construct_in_place_tag{}, std::forward<_Up>(__u)), __has_val_(true) {}
+      : __union_(std::in_place, std::forward<_Up>(__u)), __has_val_(true) {}
 
   template <class _OtherErr>
     requires is_constructible_v<_Err, const _OtherErr&>
   _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<const _OtherErr&, _Err>)
   expected(const unexpected<_OtherErr>& __unex)
     noexcept(is_nothrow_constructible_v<_Err, const _OtherErr&>) // strengthened
-      : __union_(__construct_unexpected_tag{}, __unex.error()), __has_val_(false) {}
+      : __union_(std::unexpect, __unex.error()), __has_val_(false) {}
 
   template <class _OtherErr>
     requires is_constructible_v<_Err, _OtherErr>
   _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_OtherErr, _Err>)
   expected(unexpected<_OtherErr>&& __unex)
     noexcept(is_nothrow_constructible_v<_Err, _OtherErr>) // strengthened
-      : __union_(__construct_unexpected_tag{}, std::move(__unex.error())), __has_val_(false) {}
+      : __union_(std::unexpect, std::move(__unex.error())), __has_val_(false) {}
 
   template <class... _Args>
     requires is_constructible_v<_Tp, _Args...>
   _LIBCPP_HIDE_FROM_ABI constexpr explicit expected(in_place_t, _Args&&... __args)
     noexcept(is_nothrow_constructible_v<_Tp, _Args...>) // strengthened
-      : __union_(__construct_in_place_tag{}, std::forward<_Args>(__args)...), __has_val_(true) {}
+      : __union_(std::in_place, std::forward<_Args>(__args)...), __has_val_(true) {}
 
   template <class _Up, class... _Args>
     requires is_constructible_v< _Tp, initializer_list<_Up>&, _Args... >
   _LIBCPP_HIDE_FROM_ABI constexpr explicit
   expected(in_place_t, initializer_list<_Up> __il, _Args&&... __args)
     noexcept(is_nothrow_constructible_v<_Tp, initializer_list<_Up>&, _Args...>) // strengthened
-      : __union_(__construct_in_place_tag{}, __il, std::forward<_Args>(__args)...), __has_val_(true) {}
+      : __union_(std::in_place, __il, std::forward<_Args>(__args)...), __has_val_(true) {}
 
   template <class... _Args>
     requires is_constructible_v<_Err, _Args...>
   _LIBCPP_HIDE_FROM_ABI constexpr explicit expected(unexpect_t, _Args&&... __args)
     noexcept(is_nothrow_constructible_v<_Err, _Args...>) // strengthened
-      : __union_(__construct_unexpected_tag{}, std::forward<_Args>(__args)...), __has_val_(false) {}
+      : __union_(std::unexpect, std::forward<_Args>(__args)...), __has_val_(false) {}
 
   template <class _Up, class... _Args>
     requires is_constructible_v< _Err, initializer_list<_Up>&, _Args... >
   _LIBCPP_HIDE_FROM_ABI constexpr explicit
   expected(unexpect_t, initializer_list<_Up> __il, _Args&&... __args)
     noexcept(is_nothrow_constructible_v<_Err, initializer_list<_Up>&, _Args...>) // strengthened
-      : __union_(__construct_unexpected_tag{}, __il, std::forward<_Args>(__args)...), __has_val_(false) {}
+      : __union_(std::unexpect, __il, std::forward<_Args>(__args)...), __has_val_(false) {}
 
   // [expected.object.dtor], destructor
 
@@ -855,19 +855,17 @@ class expected {
 
 private:
   struct __empty_t {};
-  struct __construct_in_place_tag {};
-  struct __construct_unexpected_tag {};
 
   template <class _ValueType, class _ErrorType>
   union __union_t {
     _LIBCPP_HIDE_FROM_ABI constexpr __union_t() {}
 
     template <class... _Args>
-    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_in_place_tag, _Args&&... __args)
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::in_place_t, _Args&&... __args)
         : __val_(std::forward<_Args>(__args)...) {}
 
     template <class... _Args>
-    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_unexpected_tag, _Args&&... __args)
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::unexpect_t, _Args&&... __args)
         : __unex_(std::forward<_Args>(__args)...) {}
 
     template <class _Func, class... _Args>
@@ -903,11 +901,11 @@ class expected {
     _LIBCPP_HIDE_FROM_ABI constexpr __union_t& operator=(const __union_t&) = default;
 
     template <class... _Args>
-    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_in_place_tag, _Args&&... __args)
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::in_place_t, _Args&&... __args)
         : __val_(std::forward<_Args>(__args)...) {}
 
     template <class... _Args>
-    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_unexpected_tag, _Args&&... __args)
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::unexpect_t, _Args&&... __args)
         : __unex_(std::forward<_Args>(__args)...) {}
 
     template <class _Func, class... _Args>
@@ -936,14 +934,14 @@ class expected {
 
   template <class _Up, class _OtherErr>
   _LIBCPP_HIDE_FROM_ABI constexpr __union_t<_Tp, _Err> __union_from_expected(const expected<_Up, _OtherErr>& __other) {
-    return __other.__has_val_ ? __union_t<_Tp, _Err>(__construct_in_place_tag{}, __other.__union_.__val_)
-                              : __union_t<_Tp, _Err>(__construct_unexpected_tag{}, __other.__union_.__unex_);
+    return __other.__has_val_ ? __union_t<_Tp, _Err>(std::in_place, __other.__union_.__val_)
+                              : __union_t<_Tp, _Err>(std::unexpect, __other.__union_.__unex_);
   }
 
   template <class _Up, class _OtherErr>
   _LIBCPP_HIDE_FROM_ABI constexpr __union_t<_Tp, _Err> __union_from_expected(expected<_Up, _OtherErr>&& __other) {
-    return __other.__has_val_ ? __union_t<_Tp, _Err>(__construct_in_place_tag{}, std::move(__other.__union_.__val_))
-                              : __union_t<_Tp, _Err>(__construct_unexpected_tag{}, std::move(__other.__union_.__unex_));
+    return __other.__has_val_ ? __union_t<_Tp, _Err>(std::in_place, std::move(__other.__union_.__val_))
+                              : __union_t<_Tp, _Err>(std::unexpect, std::move(__other.__union_.__unex_));
   }
 
   _LIBCPP_NO_UNIQUE_ADDRESS __union_t<_Tp, _Err> __union_;
@@ -1019,14 +1017,14 @@ class expected<_Tp, _Err> {
   _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<const _OtherErr&, _Err>)
   expected(const unexpected<_OtherErr>& __unex)
     noexcept(is_nothrow_constructible_v<_Err, const _OtherErr&>) // strengthened
-      : __union_(__construct_unexpected_tag{}, __unex.error()), __has_val_(false) {}
+      : __union_(std::unexpect, __unex.error()), __has_val_(false) {}
 
   template <class _OtherErr>
     requires is_constructible_v<_Err, _OtherErr>
   _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_OtherErr, _Err>)
   expected(unexpected<_OtherErr>&& __unex)
     noexcept(is_nothrow_constructible_v<_Err, _OtherErr>) // strengthened
-      : __union_(__construct_unexpected_tag{}, std::move(__unex.error())), __has_val_(false) {}
+      : __union_(std::unexpect, std::move(__unex.error())), __has_val_(false) {}
 
   _LIBCPP_HIDE_FROM_ABI constexpr explicit expected(in_place_t) noexcept : __has_val_(true) {}
 
@@ -1034,13 +1032,13 @@ class expected<_Tp, _Err> {
     requires is_constructible_v<_Err, _Args...>
   _LIBCPP_HIDE_FROM_ABI constexpr explicit expected(unexpect_t, _Args&&... __args)
     noexcept(is_nothrow_constructible_v<_Err, _Args...>) // strengthened
-      : __union_(__construct_unexpected_tag{}, std::forward<_Args>(__args)...), __has_val_(false) {}
+      : __union_(std::unexpect, std::forward<_Args>(__args)...), __has_val_(false) {}
 
   template <class _Up, class... _Args>
     requires is_constructible_v< _Err, initializer_list<_Up>&, _Args... >
   _LIBCPP_HIDE_FROM_ABI constexpr explicit expected(unexpect_t, initializer_list<_Up> __il, _Args&&... __args)
     noexcept(is_nothrow_constructible_v<_Err, initializer_list<_Up>&, _Args...>) // strengthened
-      : __union_(__construct_unexpected_tag{}, __il, std::forward<_Args>(__args)...), __has_val_(false) {}
+      : __union_(std::unexpect, __il, std::forward<_Args>(__args)...), __has_val_(false) {}
 
 private:
   template <class _Func>
@@ -1469,14 +1467,13 @@ class expected<_Tp, _Err> {
 
 private:
   struct __empty_t {};
-  struct __construct_unexpected_tag {};
 
   template <class _ErrorType>
   union __union_t {
     _LIBCPP_HIDE_FROM_ABI constexpr __union_t() : __empty_() {}
 
     template <class... _Args>
-    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_unexpected_tag, _Args&&... __args)
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::unexpect_t, _Args&&... __args)
         : __unex_(std::forward<_Args>(__args)...) {}
 
     template <class _Func, class... _Args>
@@ -1507,7 +1504,7 @@ class expected<_Tp, _Err> {
     _LIBCPP_HIDE_FROM_ABI constexpr __union_t& operator=(const __union_t&) = default;
 
     template <class... _Args>
-    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_unexpected_tag, _Args&&... __args)
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::unexpect_t, _Args&&... __args)
         : __unex_(std::forward<_Args>(__args)...) {}
 
     template <class _Func, class... _Args>
@@ -1531,13 +1528,13 @@ class expected<_Tp, _Err> {
   template <class _Up, class _OtherErr>
   _LIBCPP_HIDE_FROM_ABI constexpr __union_t<_Err> __union_from_expected(const expected<_Up, _OtherErr>& __other) {
     return __other.__has_val_ ? __union_t<_Err>()
-                              : __union_t<_Err>(__construct_unexpected_tag{}, __other.__union_.__unex_);
+                              : __union_t<_Err>(std::unexpect, __other.__union_.__unex_);
   }
 
   template <class _Up, class _OtherErr>
   _LIBCPP_HIDE_FROM_ABI constexpr __union_t<_Err> __union_from_expected(expected<_Up, _OtherErr>&& __other) {
     return __other.__has_val_ ? __union_t<_Err>()
-                              : __union_t<_Err>(__construct_unexpected_tag{}, std::move(__other.__union_.__unex_));
+                              : __union_t<_Err>(std::unexpect, std::move(__other.__union_.__unex_));
   }
 
   _LIBCPP_NO_UNIQUE_ADDRESS __union_t<_Err> __union_;

>From ecac62ff16a0253c91c2ff261212085d0eadb615 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 13:37:48 +0200
Subject: [PATCH 3/8] replace use of ternary operator by explicit if/else

---
 libcxx/include/__expected/expected.h | 24 ++++++++++++++++--------
 1 file changed, 16 insertions(+), 8 deletions(-)

diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 6440b31e7183201..55036d3f55c2432 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -934,14 +934,18 @@ class expected {
 
   template <class _Up, class _OtherErr>
   _LIBCPP_HIDE_FROM_ABI constexpr __union_t<_Tp, _Err> __union_from_expected(const expected<_Up, _OtherErr>& __other) {
-    return __other.__has_val_ ? __union_t<_Tp, _Err>(std::in_place, __other.__union_.__val_)
-                              : __union_t<_Tp, _Err>(std::unexpect, __other.__union_.__unex_);
+    if (__other.__has_val_)
+      return __union_t<_Tp, _Err>(std::in_place, __other.__union_.__val_);
+    else
+      return __union_t<_Tp, _Err>(std::unexpect, __other.__union_.__unex_);
   }
 
   template <class _Up, class _OtherErr>
   _LIBCPP_HIDE_FROM_ABI constexpr __union_t<_Tp, _Err> __union_from_expected(expected<_Up, _OtherErr>&& __other) {
-    return __other.__has_val_ ? __union_t<_Tp, _Err>(std::in_place, std::move(__other.__union_.__val_))
-                              : __union_t<_Tp, _Err>(std::unexpect, std::move(__other.__union_.__unex_));
+    if (__other.__has_val_)
+      return __union_t<_Tp, _Err>(std::in_place, std::move(__other.__union_.__val_));
+    else
+      return __union_t<_Tp, _Err>(std::unexpect, std::move(__other.__union_.__unex_));
   }
 
   _LIBCPP_NO_UNIQUE_ADDRESS __union_t<_Tp, _Err> __union_;
@@ -1527,14 +1531,18 @@ class expected<_Tp, _Err> {
 
   template <class _Up, class _OtherErr>
   _LIBCPP_HIDE_FROM_ABI constexpr __union_t<_Err> __union_from_expected(const expected<_Up, _OtherErr>& __other) {
-    return __other.__has_val_ ? __union_t<_Err>()
-                              : __union_t<_Err>(std::unexpect, __other.__union_.__unex_);
+    if (__other.__has_val_)
+      return __union_t<_Err>();
+    else
+      return __union_t<_Err>(std::unexpect, __other.__union_.__unex_);
   }
 
   template <class _Up, class _OtherErr>
   _LIBCPP_HIDE_FROM_ABI constexpr __union_t<_Err> __union_from_expected(expected<_Up, _OtherErr>&& __other) {
-    return __other.__has_val_ ? __union_t<_Err>()
-                              : __union_t<_Err>(std::unexpect, std::move(__other.__union_.__unex_));
+    if (__other.__has_val_)
+      return __union_t<_Err>();
+    else
+      return __union_t<_Err>(std::unexpect, std::move(__other.__union_.__unex_));
   }
 
   _LIBCPP_NO_UNIQUE_ADDRESS __union_t<_Err> __union_;

>From 83488c8485cb206295995098d432ae82c31143c9 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 13:47:06 +0200
Subject: [PATCH 4/8] add a short description to the tests

---
 .../expected.expected/observers/has_value.pass.cpp   | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp
index 8979e0f45d44f50..b203253e897795e 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp
@@ -60,7 +60,17 @@ constexpr bool test() {
     assert(!e.has_value());
   }
 
-  // See https://github.com/llvm/llvm-project/issues/68552
+  // The following tests check that the "has_value" flag is not overwritten
+  // by the constructor of the value. This could happen because the flag is
+  // stored in the tail padding of the value.
+  //
+  // The first test is a simplified version of the real code where this was
+  // first observed.
+  //
+  // The other tests use a synthetic struct that clobbers its tail padding
+  // on construction, making the issue easier to reproduce.
+  //
+  // See https://github.com/llvm/llvm-project/issues/68552 and the linked PR.
   {
     static constexpr auto f1 = [] -> std::expected<std::optional<int>, long> { return 0; };
 

>From 7b81fb560f60f9742f754e8f7adbffcdab8a7efb 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 13:50:57 +0200
Subject: [PATCH 5/8] remove comment about memset being UB here

---
 .../expected/expected.expected/observers/has_value.pass.cpp     | 2 --
 1 file changed, 2 deletions(-)

diff --git a/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp
index b203253e897795e..d7591718ae784b6 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp
@@ -39,8 +39,6 @@ template <int c>
 struct tail_clobberer {
   constexpr tail_clobberer() {
     if (!std::is_constant_evaluated()) {
-      // This `memset` might actually be UB (?) but suffices to reproduce bugs
-      // related to the "has value" flag.
       std::memset(this, c, sizeof(*this));
     }
   }

>From 238d1ca6560069d4d8e2558ee171782e14e50195 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= <jan.kokemueller at gmail.com>
Date: Tue, 17 Oct 2023 15:59:37 +0200
Subject: [PATCH 6/8] add constructors to __union_t from expected parts and
 refactor

GCC 13 didn't like the previous approach.
---
 libcxx/include/__expected/expected.h | 80 ++++++++++++++--------------
 1 file changed, 40 insertions(+), 40 deletions(-)

diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 55036d3f55c2432..06c9a26c8e3a88f 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -134,7 +134,7 @@ class expected {
     noexcept(is_nothrow_copy_constructible_v<_Tp> && is_nothrow_copy_constructible_v<_Err>) // strengthened
     requires(is_copy_constructible_v<_Tp> && is_copy_constructible_v<_Err> &&
              !(is_trivially_copy_constructible_v<_Tp> && is_trivially_copy_constructible_v<_Err>))
-      : __union_(__union_from_expected(__other)), __has_val_(__other.__has_val_) { }
+      : __union_(__other.__has_val_, __other.__union_), __has_val_(__other.__has_val_) { }
 
   _LIBCPP_HIDE_FROM_ABI constexpr expected(expected&&)
     requires(is_move_constructible_v<_Tp> && is_move_constructible_v<_Err>
@@ -145,7 +145,7 @@ class expected {
     noexcept(is_nothrow_move_constructible_v<_Tp> && is_nothrow_move_constructible_v<_Err>)
     requires(is_move_constructible_v<_Tp> && is_move_constructible_v<_Err> &&
              !(is_trivially_move_constructible_v<_Tp> && is_trivially_move_constructible_v<_Err>))
-      : __union_(__union_from_expected(std::move(__other))), __has_val_(__other.__has_val_) { }
+      : __union_(__other.__has_val_, std::move(__other.__union_)), __has_val_(__other.__has_val_) { }
 
 private:
   template <class _Up, class _OtherErr, class _UfQual, class _OtherErrQual>
@@ -185,14 +185,14 @@ class expected {
   expected(const expected<_Up, _OtherErr>& __other)
     noexcept(is_nothrow_constructible_v<_Tp, const _Up&> &&
              is_nothrow_constructible_v<_Err, const _OtherErr&>) // strengthened
-      : __union_(__union_from_expected(__other)), __has_val_(__other.__has_val_) {}
+      : __union_(__other.__has_val_, __other.__union_), __has_val_(__other.__has_val_) {}
 
   template <class _Up, class _OtherErr>
     requires __can_convert<_Up, _OtherErr, _Up, _OtherErr>::value
   _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_Up, _Tp> || !is_convertible_v<_OtherErr, _Err>)
   expected(expected<_Up, _OtherErr>&& __other)
     noexcept(is_nothrow_constructible_v<_Tp, _Up> && is_nothrow_constructible_v<_Err, _OtherErr>) // strengthened
-      : __union_(__union_from_expected(std::move(__other))), __has_val_(__other.__has_val_) {}
+      : __union_(__other.__has_val_, std::move(__other.__union_)), __has_val_(__other.__has_val_) {}
 
   template <class _Up = _Tp>
     requires(!is_same_v<remove_cvref_t<_Up>, in_place_t> && !is_same_v<expected, remove_cvref_t<_Up>> &&
@@ -878,6 +878,14 @@ class expected {
         std::__expected_construct_unexpected_from_invoke_tag, _Func&& __f, _Args&&... __args)
         : __unex_(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
 
+    template <class _Union>
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(bool __has_val, _Union&& __other) {
+      if (__has_val)
+        std::construct_at(this, std::in_place, std::forward<_Union>(__other).__val_);
+      else
+        std::construct_at(this, std::unexpect, std::forward<_Union>(__other).__unex_);
+    }
+
     _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t()
       requires(is_trivially_destructible_v<_ValueType> && is_trivially_destructible_v<_ErrorType>)
     = default;
@@ -918,6 +926,14 @@ class expected {
         std::__expected_construct_unexpected_from_invoke_tag, _Func&& __f, _Args&&... __args)
         : __unex_(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
 
+    template <class _Union>
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(bool __has_val, _Union&& __other) {
+      if (__has_val)
+        std::construct_at(this, std::in_place, std::forward<_Union>(__other).__val_);
+      else
+        std::construct_at(this, std::unexpect, std::forward<_Union>(__other).__unex_);
+    }
+
     _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t()
       requires(is_trivially_destructible_v<_ValueType> && is_trivially_destructible_v<_ErrorType>)
     = default;
@@ -932,22 +948,6 @@ class expected {
     _LIBCPP_NO_UNIQUE_ADDRESS _ErrorType __unex_;
   };
 
-  template <class _Up, class _OtherErr>
-  _LIBCPP_HIDE_FROM_ABI constexpr __union_t<_Tp, _Err> __union_from_expected(const expected<_Up, _OtherErr>& __other) {
-    if (__other.__has_val_)
-      return __union_t<_Tp, _Err>(std::in_place, __other.__union_.__val_);
-    else
-      return __union_t<_Tp, _Err>(std::unexpect, __other.__union_.__unex_);
-  }
-
-  template <class _Up, class _OtherErr>
-  _LIBCPP_HIDE_FROM_ABI constexpr __union_t<_Tp, _Err> __union_from_expected(expected<_Up, _OtherErr>&& __other) {
-    if (__other.__has_val_)
-      return __union_t<_Tp, _Err>(std::in_place, std::move(__other.__union_.__val_));
-    else
-      return __union_t<_Tp, _Err>(std::unexpect, std::move(__other.__union_.__unex_));
-  }
-
   _LIBCPP_NO_UNIQUE_ADDRESS __union_t<_Tp, _Err> __union_;
   bool __has_val_;
 };
@@ -991,7 +991,7 @@ class expected<_Tp, _Err> {
   _LIBCPP_HIDE_FROM_ABI constexpr expected(const expected& __rhs)
     noexcept(is_nothrow_copy_constructible_v<_Err>) // strengthened
     requires(is_copy_constructible_v<_Err> && !is_trivially_copy_constructible_v<_Err>)
-      : __union_(__union_from_expected(__rhs)), __has_val_(__rhs.__has_val_) {}
+      : __union_(__rhs.__has_val_, __rhs.__union_), __has_val_(__rhs.__has_val_) {}
 
   _LIBCPP_HIDE_FROM_ABI constexpr expected(expected&&)
     requires(is_move_constructible_v<_Err> && is_trivially_move_constructible_v<_Err>)
@@ -1000,21 +1000,21 @@ class expected<_Tp, _Err> {
   _LIBCPP_HIDE_FROM_ABI constexpr expected(expected&& __rhs)
     noexcept(is_nothrow_move_constructible_v<_Err>)
     requires(is_move_constructible_v<_Err> && !is_trivially_move_constructible_v<_Err>)
-      : __union_(__union_from_expected(std::move(__rhs))), __has_val_(__rhs.__has_val_) {}
+      : __union_(__rhs.__has_val_, std::move(__rhs.__union_)), __has_val_(__rhs.__has_val_) {}
 
   template <class _Up, class _OtherErr>
     requires __can_convert<_Up, _OtherErr, const _OtherErr&>::value
   _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<const _OtherErr&, _Err>)
   expected(const expected<_Up, _OtherErr>& __rhs)
     noexcept(is_nothrow_constructible_v<_Err, const _OtherErr&>) // strengthened
-      : __union_(__union_from_expected(__rhs)), __has_val_(__rhs.__has_val_) {}
+      : __union_(__rhs.__has_val_, __rhs.__union_), __has_val_(__rhs.__has_val_) {}
 
   template <class _Up, class _OtherErr>
     requires __can_convert<_Up, _OtherErr, _OtherErr>::value
   _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v<_OtherErr, _Err>)
   expected(expected<_Up, _OtherErr>&& __rhs)
     noexcept(is_nothrow_constructible_v<_Err, _OtherErr>) // strengthened
-      : __union_(__union_from_expected(std::move(__rhs))), __has_val_(__rhs.__has_val_) {}
+      : __union_(__rhs.__has_val_, std::move(__rhs.__union_)), __has_val_(__rhs.__has_val_) {}
 
   template <class _OtherErr>
     requires is_constructible_v<_Err, const _OtherErr&>
@@ -1485,6 +1485,14 @@ class expected<_Tp, _Err> {
         __expected_construct_unexpected_from_invoke_tag, _Func&& __f, _Args&&... __args)
         : __unex_(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
 
+    template <class _Union>
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(bool __has_val, _Union&& __other) {
+      if (__has_val)
+        std::construct_at(this);
+      else
+        std::construct_at(this, std::unexpect, std::forward<_Union>(__other).__unex_);
+    }
+
     _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t()
       requires(is_trivially_destructible_v<_ErrorType>)
     = default;
@@ -1516,6 +1524,14 @@ class expected<_Tp, _Err> {
         __expected_construct_unexpected_from_invoke_tag, _Func&& __f, _Args&&... __args)
         : __unex_(std::invoke(std::forward<_Func>(__f), std::forward<_Args>(__args)...)) {}
 
+    template <class _Union>
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(bool __has_val, _Union&& __other) {
+      if (__has_val)
+        std::construct_at(this);
+      else
+        std::construct_at(this, std::unexpect, std::forward<_Union>(__other).__unex_);
+    }
+
     _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t()
       requires(is_trivially_destructible_v<_ErrorType>)
     = default;
@@ -1529,22 +1545,6 @@ class expected<_Tp, _Err> {
     _LIBCPP_NO_UNIQUE_ADDRESS _ErrorType __unex_;
   };
 
-  template <class _Up, class _OtherErr>
-  _LIBCPP_HIDE_FROM_ABI constexpr __union_t<_Err> __union_from_expected(const expected<_Up, _OtherErr>& __other) {
-    if (__other.__has_val_)
-      return __union_t<_Err>();
-    else
-      return __union_t<_Err>(std::unexpect, __other.__union_.__unex_);
-  }
-
-  template <class _Up, class _OtherErr>
-  _LIBCPP_HIDE_FROM_ABI constexpr __union_t<_Err> __union_from_expected(expected<_Up, _OtherErr>&& __other) {
-    if (__other.__has_val_)
-      return __union_t<_Err>();
-    else
-      return __union_t<_Err>(std::unexpect, std::move(__other.__union_.__unex_));
-  }
-
   _LIBCPP_NO_UNIQUE_ADDRESS __union_t<_Err> __union_;
   bool __has_val_;
 };

>From dfa9b3cf6e5951b4935e3e310b3e338f721beada Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= <jan.kokemueller at gmail.com>
Date: Tue, 17 Oct 2023 16:06:19 +0200
Subject: [PATCH 7/8] put new expected<void> test into appropriate folder

---
 .../observers/has_value.pass.cpp              | 24 +++----------------
 .../observers/has_value.pass.cpp              |  8 +++++++
 libcxx/test/std/utilities/expected/types.h    | 16 +++++++++++++
 3 files changed, 27 insertions(+), 21 deletions(-)

diff --git a/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp
index d7591718ae784b6..74569cfa2083e92 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp
@@ -17,6 +17,7 @@
 #include <utility>
 
 #include "test_macros.h"
+#include "../../types.h"
 
 // Test noexcept
 template <class T>
@@ -31,20 +32,6 @@ static_assert(!HasValueNoexcept<Foo>);
 static_assert(HasValueNoexcept<std::expected<int, int>>);
 static_assert(HasValueNoexcept<const std::expected<int, int>>);
 
-// This type has one byte of tail padding where `std::expected` will put its
-// "has value" flag. The constructor will clobber all bytes including the
-// tail padding. With this type we can check that `std::expected` will set
-// its "has value" flag _after_ the value/error object is constructed.
-template <int c>
-struct tail_clobberer {
-  constexpr tail_clobberer() {
-    if (!std::is_constant_evaluated()) {
-      std::memset(this, c, sizeof(*this));
-    }
-  }
-  alignas(2) bool b;
-};
-
 constexpr bool test() {
   // has_value
   {
@@ -80,15 +67,10 @@ constexpr bool test() {
     assert(e.has_value());
   }
   {
-    const std::expected<tail_clobberer<0>, bool> e = {};
-    static_assert(sizeof(tail_clobberer<0>) == sizeof(e));
+    const std::expected<TailClobberer<0>, bool> e = {};
+    static_assert(sizeof(TailClobberer<0>) == sizeof(e));
     assert(e.has_value());
   }
-  {
-    const std::expected<void, tail_clobberer<1>> e(std::unexpect);
-    static_assert(sizeof(tail_clobberer<1>) == sizeof(e));
-    assert(!e.has_value());
-  }
 
   return true;
 }
diff --git a/libcxx/test/std/utilities/expected/expected.void/observers/has_value.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/observers/has_value.pass.cpp
index 42a173d60c898c6..89c79541f995526 100644
--- a/libcxx/test/std/utilities/expected/expected.void/observers/has_value.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/observers/has_value.pass.cpp
@@ -16,6 +16,7 @@
 #include <utility>
 
 #include "test_macros.h"
+#include "../../types.h"
 
 // Test noexcept
 template <class T>
@@ -43,6 +44,13 @@ constexpr bool test() {
     assert(!e.has_value());
   }
 
+  // See comments of the corresponding test in "expected.expected".
+  {
+    const std::expected<void, TailClobberer<1>> e(std::unexpect);
+    static_assert(sizeof(TailClobberer<1>) == sizeof(e));
+    assert(!e.has_value());
+  }
+
   return true;
 }
 
diff --git a/libcxx/test/std/utilities/expected/types.h b/libcxx/test/std/utilities/expected/types.h
index 7c7e517785b4f78..874b72b64e8ae11 100644
--- a/libcxx/test/std/utilities/expected/types.h
+++ b/libcxx/test/std/utilities/expected/types.h
@@ -9,7 +9,9 @@
 #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>
@@ -102,6 +104,20 @@ struct TrackedMove {
   }
 };
 
+// This type has one byte of tail padding where `std::expected` will put its
+// "has value" flag. The constructor will clobber all bytes including the
+// tail padding. With this type we can check that `std::expected` will set
+// its "has value" flag _after_ the value/error object is constructed.
+template <int c>
+struct TailClobberer {
+  constexpr TailClobberer() {
+    if (!std::is_constant_evaluated()) {
+      std::memset(this, c, sizeof(*this));
+    }
+  }
+  alignas(2) bool b;
+};
+
 #ifndef TEST_HAS_NO_EXCEPTIONS
 struct Except {};
 

>From 3028a7c37c3f7208fc55ca11029d86da2f06c0da Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= <jan.kokemueller at gmail.com>
Date: Tue, 17 Oct 2023 18:21:47 +0200
Subject: [PATCH 8/8] disable new tests on compilers that cannot build them

---
 .../expected/expected.expected/observers/has_value.pass.cpp  | 5 +++++
 .../expected/expected.void/observers/has_value.pass.cpp      | 3 +++
 2 files changed, 8 insertions(+)

diff --git a/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp
index 74569cfa2083e92..3cb46bcf79abee3 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/observers/has_value.pass.cpp
@@ -57,6 +57,7 @@ constexpr bool test() {
   //
   // See https://github.com/llvm/llvm-project/issues/68552 and the linked PR.
   {
+#if !defined(TEST_COMPILER_CLANG) || TEST_CLANG_VER >= 1600
     static constexpr auto f1 = [] -> std::expected<std::optional<int>, long> { return 0; };
 
     static constexpr auto f2 = [] -> std::expected<std::optional<int>, int> {
@@ -65,10 +66,14 @@ constexpr bool test() {
 
     auto e = f2();
     assert(e.has_value());
+#endif
   }
   {
     const std::expected<TailClobberer<0>, bool> e = {};
+    // clang-cl does not support [[no_unique_address]] yet.
+#if !(defined(TEST_COMPILER_CLANG) && defined(_MSC_VER))
     static_assert(sizeof(TailClobberer<0>) == sizeof(e));
+#endif
     assert(e.has_value());
   }
 
diff --git a/libcxx/test/std/utilities/expected/expected.void/observers/has_value.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/observers/has_value.pass.cpp
index 89c79541f995526..50098d10977b702 100644
--- a/libcxx/test/std/utilities/expected/expected.void/observers/has_value.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.void/observers/has_value.pass.cpp
@@ -47,7 +47,10 @@ constexpr bool test() {
   // See comments of the corresponding test in "expected.expected".
   {
     const std::expected<void, TailClobberer<1>> e(std::unexpect);
+    // clang-cl does not support [[no_unique_address]] yet.
+#if !(defined(TEST_COMPILER_CLANG) && defined(_MSC_VER))
     static_assert(sizeof(TailClobberer<1>) == sizeof(e));
+#endif
     assert(!e.has_value());
   }
 



More information about the libcxx-commits mailing list