From 6944e5f0c964cae15b2f3741c39c64d2f651539b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Tue, 10 Oct 2023 20:24:06 +0200 Subject: [PATCH 01/28] [libc++] Fix UB in 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 045370a486fae..08f35b1111f6b 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 @@ -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 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 requires(!is_same_v, in_place_t> && !is_same_v> && @@ -227,61 +200,47 @@ class expected { (!is_same_v, bool> || !__is_std_expected>::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 requires is_constructible_v<_Err, const _OtherErr&> _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v) 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 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 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 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 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 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 @@ -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 union __union_t { _LIBCPP_HIDE_FROM_ABI constexpr __union_t() {} + template + _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_in_place_tag, _Args&&... __args) + : __val_(std::forward<_Args>(__args)...) {} + + template + _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_unexpected_tag, _Args&&... __args) + : __unex_(std::forward<_Args>(__args)...) {} + template _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 + _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_in_place_tag, _Args&&... __args) + : __val_(std::forward<_Args>(__args)...) {} + + template + _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_unexpected_tag, _Args&&... __args) + : __unex_(std::forward<_Args>(__args)...) {} + template _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 + _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 + _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 requires __can_convert<_Up, _OtherErr, const _OtherErr&>::value _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v) 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 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 requires is_constructible_v<_Err, const _OtherErr&> _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v) 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 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 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 @@ -1502,11 +1469,16 @@ class expected<_Tp, _Err> { private: struct __empty_t {}; + struct __construct_unexpected_tag {}; template union __union_t { _LIBCPP_HIDE_FROM_ABI constexpr __union_t() : __empty_() {} + template + _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_unexpected_tag, _Args&&... __args) + : __unex_(std::forward<_Args>(__args)...) {} + template _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 + _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(__construct_unexpected_tag, _Args&&... __args) + : __unex_(std::forward<_Args>(__args)...) {} + template _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 + _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 + _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 27d657a065699..8979e0f45d44f 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 #include #include +#include #include #include @@ -30,6 +31,22 @@ static_assert(!HasValueNoexcept); static_assert(HasValueNoexcept>); static_assert(HasValueNoexcept>); +// 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 +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, long> { return 0; }; + + static constexpr auto f2 = [] -> std::expected, int> { + return f1().transform_error([](auto) { return 0; }); + }; + + auto e = f2(); + assert(e.has_value()); + } + { + const std::expected, bool> e = {}; + static_assert(sizeof(tail_clobberer<0>) == sizeof(e)); + assert(e.has_value()); + } + { + const std::expected> e(std::unexpect); + static_assert(sizeof(tail_clobberer<1>) == sizeof(e)); + assert(!e.has_value()); + } + return true; } From 2757d11080f91b53044ed2fe4f09c95ff66b27d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 15 Oct 2023 13:32:14 +0200 Subject: [PATCH 02/28] 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 08f35b1111f6b..6440b31e71832 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, bool> || !__is_std_expected>::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 requires is_constructible_v<_Err, const _OtherErr&> _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v) 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 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 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 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 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 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 union __union_t { _LIBCPP_HIDE_FROM_ABI constexpr __union_t() {} template - _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 - _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 @@ -903,11 +901,11 @@ class expected { _LIBCPP_HIDE_FROM_ABI constexpr __union_t& operator=(const __union_t&) = default; template - _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 - _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 @@ -936,14 +934,14 @@ class expected { template _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 _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) 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 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 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 @@ -1469,14 +1467,13 @@ class expected<_Tp, _Err> { private: struct __empty_t {}; - struct __construct_unexpected_tag {}; template union __union_t { _LIBCPP_HIDE_FROM_ABI constexpr __union_t() : __empty_() {} template - _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 @@ -1507,7 +1504,7 @@ class expected<_Tp, _Err> { _LIBCPP_HIDE_FROM_ABI constexpr __union_t& operator=(const __union_t&) = default; template - _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 @@ -1531,13 +1528,13 @@ class expected<_Tp, _Err> { template _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 _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 7f1c6c7222e7539120654b82ad39b2c7051303ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 15 Oct 2023 13:37:48 +0200 Subject: [PATCH 03/28] 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 6440b31e71832..55036d3f55c24 100644 --- a/libcxx/include/__expected/expected.h +++ b/libcxx/include/__expected/expected.h @@ -934,14 +934,18 @@ class expected { template _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 _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 _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 _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 fec6013102955da78ee85f99426457fd9e129555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 15 Oct 2023 13:47:06 +0200 Subject: [PATCH 04/28] 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 8979e0f45d44f..b203253e89779 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, long> { return 0; }; From 99771837135b9d657101adc1b59eceaebae26f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 15 Oct 2023 13:50:57 +0200 Subject: [PATCH 05/28] 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 b203253e89779..d7591718ae784 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 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 54db802337e3683ff56242cab7e5772b384e6f77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Tue, 17 Oct 2023 15:59:37 +0200 Subject: [PATCH 06/28] 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 55036d3f55c24..06c9a26c8e3a8 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 @@ -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 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 requires(!is_same_v, in_place_t> && !is_same_v> && @@ -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 + _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 + _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 - _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 - _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 requires __can_convert<_Up, _OtherErr, const _OtherErr&>::value _LIBCPP_HIDE_FROM_ABI constexpr explicit(!is_convertible_v) 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 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 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 + _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 + _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 - _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 - _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 6411556590d66095ddb0976cce909ec435a0b65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Tue, 17 Oct 2023 16:06:19 +0200 Subject: [PATCH 07/28] put new expected 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 d7591718ae784..74569cfa2083e 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 #include "test_macros.h" +#include "../../types.h" // Test noexcept template @@ -31,20 +32,6 @@ static_assert(!HasValueNoexcept); static_assert(HasValueNoexcept>); static_assert(HasValueNoexcept>); -// 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 -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, bool> e = {}; - static_assert(sizeof(tail_clobberer<0>) == sizeof(e)); + const std::expected, bool> e = {}; + static_assert(sizeof(TailClobberer<0>) == sizeof(e)); assert(e.has_value()); } - { - const std::expected> 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 42a173d60c898..89c79541f9955 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 #include "test_macros.h" +#include "../../types.h" // Test noexcept template @@ -43,6 +44,13 @@ constexpr bool test() { assert(!e.has_value()); } + // See comments of the corresponding test in "expected.expected". + { + const std::expected> 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 7c7e517785b4f..874b72b64e8ae 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 #include +#include #include "test_macros.h" template @@ -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 +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 cd1a2b8530d2391b7a60b7fb5b94cbd2a8b724ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Tue, 17 Oct 2023 18:21:47 +0200 Subject: [PATCH 08/28] 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 74569cfa2083e..3cb46bcf79abe 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, long> { return 0; }; static constexpr auto f2 = [] -> std::expected, int> { @@ -65,10 +66,14 @@ constexpr bool test() { auto e = f2(); assert(e.has_value()); +#endif } { const std::expected, 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 89c79541f9955..50098d10977b7 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> 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()); } From b31c8efacc509235779e031626dd7077e2f77d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Tue, 17 Oct 2023 19:57:07 +0200 Subject: [PATCH 09/28] make comment in libcxx/test/std/utilities/expected/types.h more general Co-authored-by: Louis Dionne --- libcxx/test/std/utilities/expected/types.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libcxx/test/std/utilities/expected/types.h b/libcxx/test/std/utilities/expected/types.h index 874b72b64e8ae..e6fd5ac479266 100644 --- a/libcxx/test/std/utilities/expected/types.h +++ b/libcxx/test/std/utilities/expected/types.h @@ -104,10 +104,12 @@ struct TrackedMove { } }; -// This type has one byte of tail padding where `std::expected` will put its +// This type has one byte of tail padding where `std::expected` may 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. +// tail padding. With this type we can check that `std::expected` handles +// the case where the "has value" flag is an overlapping subobject correctly. +// +// See https://github.com/llvm/llvm-project/issues/68552 for details. template struct TailClobberer { constexpr TailClobberer() { From 49f59a5972005e401d932572a9c3a6555ae0247a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Tue, 17 Oct 2023 20:12:00 +0200 Subject: [PATCH 10/28] spell out full path to corresponding test file --- .../expected/expected.void/observers/has_value.pass.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 50098d10977b7..06a45ed430180 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 @@ -44,7 +44,8 @@ constexpr bool test() { assert(!e.has_value()); } - // See comments of the corresponding test in "expected.expected". + // See comments of the corresponding test in + // "expected.expected/observers/has_value.pass.cpp". { const std::expected> e(std::unexpect); // clang-cl does not support [[no_unique_address]] yet. From 84a61daf9003ff2420e51c524586b04d26687bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Tue, 17 Oct 2023 20:14:28 +0200 Subject: [PATCH 11/28] directly construct into union member instead of trying to delegate to another constructor --- libcxx/include/__expected/expected.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h index 06c9a26c8e3a8..9d09485d22ae4 100644 --- a/libcxx/include/__expected/expected.h +++ b/libcxx/include/__expected/expected.h @@ -881,9 +881,9 @@ class expected { template _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_); + std::construct_at(std::addressof(__val_), std::forward<_Union>(__other).__val_); else - std::construct_at(this, std::unexpect, std::forward<_Union>(__other).__unex_); + std::construct_at(std::addressof(__unex_), std::forward<_Union>(__other).__unex_); } _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t() @@ -929,9 +929,9 @@ class expected { template _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_); + std::construct_at(std::addressof(__val_), std::forward<_Union>(__other).__val_); else - std::construct_at(this, std::unexpect, std::forward<_Union>(__other).__unex_); + std::construct_at(std::addressof(__unex_), std::forward<_Union>(__other).__unex_); } _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t() @@ -1488,9 +1488,9 @@ class expected<_Tp, _Err> { template _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(bool __has_val, _Union&& __other) { if (__has_val) - std::construct_at(this); + std::construct_at(std::addressof(__empty_)); else - std::construct_at(this, std::unexpect, std::forward<_Union>(__other).__unex_); + std::construct_at(std::addressof(__unex_), std::forward<_Union>(__other).__unex_); } _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t() @@ -1527,9 +1527,9 @@ class expected<_Tp, _Err> { template _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(bool __has_val, _Union&& __other) { if (__has_val) - std::construct_at(this); + std::construct_at(std::addressof(__empty_)); else - std::construct_at(this, std::unexpect, std::forward<_Union>(__other).__unex_); + std::construct_at(std::addressof(__unex_), std::forward<_Union>(__other).__unex_); } _LIBCPP_HIDE_FROM_ABI constexpr ~__union_t() From 5f70208208967b9365311641af151be532ee18f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Tue, 17 Oct 2023 20:24:03 +0200 Subject: [PATCH 12/28] remove unneeded '__empty_t' in union of non-void expected --- libcxx/include/__expected/expected.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h index 9d09485d22ae4..f874a3a126a58 100644 --- a/libcxx/include/__expected/expected.h +++ b/libcxx/include/__expected/expected.h @@ -854,12 +854,8 @@ class expected { } private: - struct __empty_t {}; - template union __union_t { - _LIBCPP_HIDE_FROM_ABI constexpr __union_t() {} - template _LIBCPP_HIDE_FROM_ABI constexpr explicit __union_t(std::in_place_t, _Args&&... __args) : __val_(std::forward<_Args>(__args)...) {} @@ -904,7 +900,6 @@ class expected { template requires(is_trivially_move_constructible_v<_ValueType> && is_trivially_move_constructible_v<_ErrorType>) union __union_t<_ValueType, _ErrorType> { - _LIBCPP_HIDE_FROM_ABI constexpr __union_t() : __empty_() {} _LIBCPP_HIDE_FROM_ABI constexpr __union_t(const __union_t&) = default; _LIBCPP_HIDE_FROM_ABI constexpr __union_t& operator=(const __union_t&) = default; @@ -943,7 +938,6 @@ class expected { requires(!is_trivially_destructible_v<_ValueType> || !is_trivially_destructible_v<_ErrorType>) {} - _LIBCPP_NO_UNIQUE_ADDRESS __empty_t __empty_; _LIBCPP_NO_UNIQUE_ADDRESS _ValueType __val_; _LIBCPP_NO_UNIQUE_ADDRESS _ErrorType __unex_; }; From 1ab8ff96ef4d3838139ed0ec59ea4a6d2e2be1d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Wed, 18 Oct 2023 03:22:39 +0200 Subject: [PATCH 13/28] make lambdas automatic variables --- .../expected/expected.expected/observers/has_value.pass.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 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 3cb46bcf79abe..0e19da0655946 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,16 +57,14 @@ 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, long> { return 0; }; + auto f1 = [] -> std::expected, long> { return 0; }; - static constexpr auto f2 = [] -> std::expected, int> { + auto f2 = [&f1] -> std::expected, int> { return f1().transform_error([](auto) { return 0; }); }; auto e = f2(); assert(e.has_value()); -#endif } { const std::expected, bool> e = {}; From 58c523cf52ea98cc119f332277763b55a08a58c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Fri, 20 Oct 2023 18:28:34 +0200 Subject: [PATCH 14/28] rename template parameter from "c" to "constant" Co-authored-by: Louis Dionne --- libcxx/test/std/utilities/expected/types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcxx/test/std/utilities/expected/types.h b/libcxx/test/std/utilities/expected/types.h index e6fd5ac479266..697a410cea5fe 100644 --- a/libcxx/test/std/utilities/expected/types.h +++ b/libcxx/test/std/utilities/expected/types.h @@ -110,7 +110,7 @@ struct TrackedMove { // the case where the "has value" flag is an overlapping subobject correctly. // // See https://github.com/llvm/llvm-project/issues/68552 for details. -template +template struct TailClobberer { constexpr TailClobberer() { if (!std::is_constant_evaluated()) { From de8a4b4dfc741a3a430f582d747a863f19a982b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Fri, 20 Oct 2023 20:55:20 +0200 Subject: [PATCH 15/28] fix build --- libcxx/test/std/utilities/expected/types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcxx/test/std/utilities/expected/types.h b/libcxx/test/std/utilities/expected/types.h index 697a410cea5fe..6a7990383ada8 100644 --- a/libcxx/test/std/utilities/expected/types.h +++ b/libcxx/test/std/utilities/expected/types.h @@ -114,7 +114,7 @@ template struct TailClobberer { constexpr TailClobberer() { if (!std::is_constant_evaluated()) { - std::memset(this, c, sizeof(*this)); + std::memset(this, constant, sizeof(*this)); } } alignas(2) bool b; From 07e02585674c667516e3ed9763d534dd6b9977f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 21 Oct 2023 18:23:27 +0200 Subject: [PATCH 16/28] add TailClobberer tests for expected.expected --- .../assign/emplace.intializer_list.pass.cpp | 8 +++++++ .../expected.expected/assign/emplace.pass.cpp | 7 ++++++ .../ctor/ctor.convert.copy.pass.cpp | 9 +++++++ .../ctor/ctor.convert.move.pass.cpp | 9 +++++++ .../expected.expected/ctor/ctor.copy.pass.cpp | 16 +++++++++++++ .../ctor/ctor.default.pass.cpp | 3 +++ .../ctor/ctor.inplace.pass.cpp | 16 ++++++++----- .../ctor/ctor.inplace_init_list.pass.cpp | 7 ++++++ .../expected.expected/ctor/ctor.move.pass.cpp | 16 +++++++++++++ .../expected.expected/ctor/ctor.u.pass.cpp | 16 ++++++++----- .../ctor/ctor.unexpect.pass.cpp | 16 ++++++++----- .../ctor/ctor.unexpect_init_list.pass.cpp | 7 ++++++ .../ctor/ctor.unexpected.copy.pass.cpp | 6 +++-- .../ctor/ctor.unexpected.move.pass.cpp | 6 +++-- libcxx/test/std/utilities/expected/types.h | 24 ++++++++++++++++++- 15 files changed, 143 insertions(+), 23 deletions(-) diff --git a/libcxx/test/std/utilities/expected/expected.expected/assign/emplace.intializer_list.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/assign/emplace.intializer_list.pass.cpp index 3cdfcde3f4d69..922200a8c0263 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/assign/emplace.intializer_list.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/assign/emplace.intializer_list.pass.cpp @@ -81,6 +81,14 @@ constexpr bool test() { assert(e.value().i == 10); } + // TailClobberer + { + std::expected, bool> e(std::unexpect); + auto list = {4, 5, 6}; + e.emplace(list); + assert(e.has_value()); + } + 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 c62e628935020..491de2dff0331 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,13 @@ constexpr bool test() { assert(e.value() == 10); } + // TailClobberer + { + std::expected, bool> e(std::unexpect); + e.emplace(); + assert(e.has_value()); + } + return true; } diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.convert.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.convert.copy.pass.cpp index 9274b9a2c030e..2cc952358e821 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.convert.copy.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.convert.copy.pass.cpp @@ -45,6 +45,7 @@ #include #include "test_macros.h" +#include "../../types.h" // Test Constraints: template @@ -161,6 +162,14 @@ constexpr bool test() { assert(e1.error() == 5); } + // convert TailClobberer + { + const std::expected, char> e1; + std::expected, char> e2 = e1; + assert(e2.has_value()); + assert(e1.has_value()); + } + return true; } diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.convert.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.convert.move.pass.cpp index 71979311bfd10..31819cd33ef6c 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.convert.move.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.convert.move.pass.cpp @@ -46,6 +46,7 @@ #include "MoveOnly.h" #include "test_macros.h" +#include "../../types.h" // Test Constraints: template @@ -160,6 +161,14 @@ constexpr bool test() { assert(e1.error().get() == 0); } + // convert TailClobberer + { + std::expected, char> e1; + std::expected, char> e2 = std::move(e1); + assert(e2.has_value()); + assert(e1.has_value()); + } + return true; } diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.copy.pass.cpp index 77d73485025ab..1b1c4a5960692 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.copy.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.copy.pass.cpp @@ -30,6 +30,7 @@ #include #include "test_macros.h" +#include "../../types.h" struct NonCopyable { NonCopyable(const NonCopyable&) = delete; @@ -93,6 +94,21 @@ constexpr bool test() { assert(!e2.has_value()); assert(e2.error() == 5); } + + // copy TailClobberer as value + { + const std::expected, bool> e1; + auto e2 = e1; + assert(e2.has_value()); + } + + // copy TailClobberer as error + { + const std::expected> e1(std::unexpect); + auto e2 = e1; + assert(!e2.has_value()); + } + return true; } diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.default.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.default.pass.cpp index 431e604e8b692..ea23d8aa735c8 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.default.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.default.pass.cpp @@ -22,6 +22,7 @@ #include #include "test_macros.h" +#include "../../types.h" struct NoDedefaultCtor { NoDedefaultCtor() = delete; @@ -45,6 +46,7 @@ constexpr void testDefaultCtor() { template constexpr void testTypes() { + testDefaultCtor(); testDefaultCtor(); testDefaultCtor(); } @@ -52,6 +54,7 @@ constexpr void testTypes() { constexpr bool test() { testTypes(); testTypes(); + testTypes>(); return true; } diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.inplace.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.inplace.pass.cpp index 92952551711e0..0b41eb44883e4 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.inplace.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.inplace.pass.cpp @@ -26,6 +26,7 @@ #include "MoveOnly.h" #include "test_macros.h" +#include "../../types.h" // Test Constraints: static_assert(std::is_constructible_v, std::in_place_t>); @@ -54,24 +55,24 @@ struct CopyOnly { friend constexpr bool operator==(const CopyOnly& mi, int ii) { return mi.i == ii; } }; -template +template constexpr void testInt() { - std::expected e(std::in_place, 5); + std::expected e(std::in_place, 5); assert(e.has_value()); assert(e.value() == 5); } -template +template constexpr void testLValue() { T t(5); - std::expected e(std::in_place, t); + std::expected e(std::in_place, t); assert(e.has_value()); assert(e.value() == 5); } -template +template constexpr void testRValue() { - std::expected e(std::in_place, T(5)); + std::expected e(std::in_place, T(5)); assert(e.has_value()); assert(e.value() == 5); } @@ -80,10 +81,13 @@ constexpr bool test() { testInt(); testInt(); testInt(); + testInt, bool>(); testLValue(); testLValue(); + testLValue, bool>(); testRValue(); testRValue(); + testRValue, bool>(); // no arg { diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.inplace_init_list.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.inplace_init_list.pass.cpp index b4cad54b860e9..143f9d7dab113 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.inplace_init_list.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.inplace_init_list.pass.cpp @@ -28,6 +28,7 @@ #include "MoveOnly.h" #include "test_macros.h" +#include "../../types.h" // Test Constraints: static_assert( @@ -90,6 +91,12 @@ constexpr bool test() { assert(m.get() == 0); } + // TailClobberer + { + std::expected, bool> e(std::in_place, {1, 2, 3}); + assert(e.has_value()); + } + return true; } diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.move.pass.cpp index 5e6749e50c16c..f542778b3d5ff 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.move.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.move.pass.cpp @@ -32,6 +32,7 @@ #include #include "test_macros.h" +#include "../../types.h" struct NonMovable { NonMovable(NonMovable&&) = delete; @@ -112,6 +113,21 @@ constexpr bool test() { assert(e2.error() == 5); assert(!e1.has_value()); } + + // move TailClobbererNonTrivialMove as value + { + std::expected, bool> e1; + auto e2 = std::move(e1); + assert(e2.has_value()); + } + + // move TailClobbererNonTrivialMove as error + { + std::expected> e1(std::unexpect); + auto e2 = std::move(e1); + assert(!e2.has_value()); + } + return true; } diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp index 9e82943f9f314..16dd531a410c2 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp @@ -29,6 +29,7 @@ #include "MoveOnly.h" #include "test_macros.h" +#include "../../types.h" // Test Constraints: static_assert(std::is_constructible_v, int>); @@ -70,24 +71,24 @@ struct CopyOnly { struct BaseError {}; struct DerivedError : BaseError {}; -template +template constexpr void testInt() { - std::expected e(5); + std::expected e(5); assert(e.has_value()); assert(e.value() == 5); } -template +template constexpr void testLValue() { T t(5); - std::expected e(t); + std::expected e(t); assert(e.has_value()); assert(e.value() == 5); } -template +template constexpr void testRValue() { - std::expected e(T(5)); + std::expected e(T(5)); assert(e.has_value()); assert(e.value() == 5); } @@ -96,10 +97,13 @@ constexpr bool test() { testInt(); testInt(); testInt(); + testInt, bool>(); testLValue(); testLValue(); + testLValue, bool>(); testRValue(); testRValue(); + testRValue, bool>(); // Test default template argument. // Without it, the template parameter cannot be deduced from an initializer list diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpect.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpect.pass.cpp index 5a78e41dfcae2..a0b334a25b0cb 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpect.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpect.pass.cpp @@ -26,6 +26,7 @@ #include "MoveOnly.h" #include "test_macros.h" +#include "../../types.h" // Test Constraints: static_assert(std::is_constructible_v, std::unexpect_t>); @@ -54,24 +55,24 @@ struct CopyOnly { friend constexpr bool operator==(const CopyOnly& mi, int ii) { return mi.i == ii; } }; -template +template constexpr void testInt() { - std::expected e(std::unexpect, 5); + std::expected e(std::unexpect, 5); assert(!e.has_value()); assert(e.error() == 5); } -template +template constexpr void testLValue() { T t(5); - std::expected e(std::unexpect, t); + std::expected e(std::unexpect, t); assert(!e.has_value()); assert(e.error() == 5); } -template +template constexpr void testRValue() { - std::expected e(std::unexpect, T(5)); + std::expected e(std::unexpect, T(5)); assert(!e.has_value()); assert(e.error() == 5); } @@ -80,10 +81,13 @@ constexpr bool test() { testInt(); testInt(); testInt(); + testInt, bool>(); testLValue(); testLValue(); + testLValue, bool>(); testRValue(); testRValue(); + testRValue, bool>(); // no arg { diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpect_init_list.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpect_init_list.pass.cpp index 7cc36b51e4153..4c280fc32a4e9 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpect_init_list.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpect_init_list.pass.cpp @@ -28,6 +28,7 @@ #include "MoveOnly.h" #include "test_macros.h" +#include "../../types.h" // Test Constraints: static_assert( @@ -90,6 +91,12 @@ constexpr bool test() { assert(m.get() == 0); } + // TailClobberer + { + std::expected> e(std::unexpect, {1, 2, 3}); + assert(!e.has_value()); + } + return true; } diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpected.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpected.copy.pass.cpp index 09ac91182b3b8..b3bb71cf36837 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpected.copy.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpected.copy.pass.cpp @@ -27,6 +27,7 @@ #include "MoveOnly.h" #include "test_macros.h" +#include "../../types.h" // Test Constraints static_assert(std::is_constructible_v, const std::unexpected&>); @@ -49,10 +50,10 @@ struct MyInt { friend constexpr bool operator==(const MyInt&, const MyInt&) = default; }; -template +template constexpr void testUnexpected() { const std::unexpected u(5); - std::expected e(u); + std::expected e(u); assert(!e.has_value()); assert(e.error() == 5); } @@ -60,6 +61,7 @@ constexpr void testUnexpected() { constexpr bool test() { testUnexpected(); testUnexpected(); + testUnexpected, bool>(); return true; } diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpected.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpected.move.pass.cpp index 9aaaa3fe1a448..419c789c86d51 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpected.move.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpected.move.pass.cpp @@ -27,6 +27,7 @@ #include "MoveOnly.h" #include "test_macros.h" +#include "../../types.h" // Test Constraints static_assert(std::is_constructible_v, std::unexpected>); @@ -49,10 +50,10 @@ struct MyInt { friend constexpr bool operator==(const MyInt&, const MyInt&) = default; }; -template +template constexpr void testInt() { std::unexpected u(5); - std::expected e(std::move(u)); + std::expected e(std::move(u)); assert(!e.has_value()); assert(e.error() == 5); } @@ -69,6 +70,7 @@ constexpr bool test() { testInt(); testInt(); testInt(); + testInt, bool>(); testMoveOnly(); return true; } diff --git a/libcxx/test/std/utilities/expected/types.h b/libcxx/test/std/utilities/expected/types.h index 6a7990383ada8..58c2df504bfb2 100644 --- a/libcxx/test/std/utilities/expected/types.h +++ b/libcxx/test/std/utilities/expected/types.h @@ -112,13 +112,35 @@ struct TrackedMove { // See https://github.com/llvm/llvm-project/issues/68552 for details. template struct TailClobberer { - constexpr TailClobberer() { + constexpr TailClobberer() noexcept { if (!std::is_constant_evaluated()) { std::memset(this, constant, sizeof(*this)); } + // Always set `b` itself to `false` so that the comparison works. + b = false; } + constexpr TailClobberer(const TailClobberer&) : TailClobberer() {} + constexpr TailClobberer(TailClobberer&&) = default; + // Converts from `int`/`std::initializer_list, used in some tests. + constexpr TailClobberer(int) : TailClobberer() {} + constexpr TailClobberer(std::initializer_list) noexcept : TailClobberer() {} + + friend constexpr bool operator==(const TailClobberer&, const TailClobberer&) = default; + +private: alignas(2) bool b; }; +static_assert(!std::is_trivially_copy_constructible_v>); +static_assert(std::is_trivially_move_constructible_v>); + +template +struct TailClobbererNonTrivialMove : TailClobberer { + using TailClobberer::TailClobberer; + constexpr TailClobbererNonTrivialMove(TailClobbererNonTrivialMove&&) noexcept : TailClobberer() {} +}; +static_assert(!std::is_trivially_copy_constructible_v>); +static_assert(std::is_move_constructible_v>); +static_assert(!std::is_trivially_move_constructible_v>); #ifndef TEST_HAS_NO_EXCEPTIONS struct Except {}; From 4471615ef8ab85421bddc577718d9c49a821184e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 21 Oct 2023 18:37:48 +0200 Subject: [PATCH 17/28] add some missing checks --- .../expected/expected.expected/ctor/ctor.move.pass.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.move.pass.cpp index f542778b3d5ff..aaf357fbc518a 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.move.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.move.pass.cpp @@ -119,6 +119,7 @@ constexpr bool test() { std::expected, bool> e1; auto e2 = std::move(e1); assert(e2.has_value()); + assert(e1.has_value()); } // move TailClobbererNonTrivialMove as error @@ -126,6 +127,7 @@ constexpr bool test() { std::expected> e1(std::unexpect); auto e2 = std::move(e1); assert(!e2.has_value()); + assert(!e1.has_value()); } return true; From dd7b119819b6555f1a1299f05acca9a83e37e8fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sat, 21 Oct 2023 18:38:04 +0200 Subject: [PATCH 18/28] add tests for expected --- .../expected.void/ctor/ctor.convert.copy.pass.cpp | 9 +++++++++ .../expected.void/ctor/ctor.convert.move.pass.cpp | 9 +++++++++ .../expected/expected.void/ctor/ctor.copy.pass.cpp | 8 ++++++++ .../expected/expected.void/ctor/ctor.move.pass.cpp | 9 +++++++++ .../expected/expected.void/ctor/ctor.unexpect.pass.cpp | 4 ++++ .../expected.void/ctor/ctor.unexpect_init_list.pass.cpp | 7 +++++++ .../expected.void/ctor/ctor.unexpected.copy.pass.cpp | 2 ++ .../expected.void/ctor/ctor.unexpected.move.pass.cpp | 2 ++ 8 files changed, 50 insertions(+) diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.copy.pass.cpp index 40f8efa5f94bf..118e4274e9ef8 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.copy.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.copy.pass.cpp @@ -33,6 +33,7 @@ #include #include "test_macros.h" +#include "../../types.h" // Test Constraints: template @@ -97,6 +98,14 @@ constexpr bool test() { assert(e1.error() == 5); } + // convert TailClobberer + { + const std::expected> e1; + std::expected> e2 = e1; + assert(!e2.has_value()); + assert(!e1.has_value()); + } + return true; } diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.move.pass.cpp index b28fc7a03bb34..32f6055bc5c61 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.move.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.move.pass.cpp @@ -34,6 +34,7 @@ #include "MoveOnly.h" #include "test_macros.h" +#include "../../types.h" // Test Constraints: template @@ -98,6 +99,14 @@ constexpr bool test() { assert(e1.error().get() == 0); } + // convert TailClobberer + { + std::expected> e1; + std::expected> e2 = std::move(e1); + assert(!e2.has_value()); + assert(!e1.has_value()); + } + return true; } diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.copy.pass.cpp index 689f152a3ac55..f46826a4e3289 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.copy.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.copy.pass.cpp @@ -62,6 +62,14 @@ constexpr bool test() { assert(!e2.has_value()); assert(e2.error() == 5); } + + // copy TailClobberer as error + { + const std::expected> e1(std::unexpect); + auto e2 = e1; + assert(!e2.has_value()); + } + return true; } diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.move.pass.cpp index 61bce2be4897f..38affdcadde3f 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.move.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.move.pass.cpp @@ -76,6 +76,15 @@ constexpr bool test() { assert(e2.error() == 5); assert(!e1.has_value()); } + + // move TailClobbererNonTrivialMove as error + { + std::expected> e1(std::unexpect); + auto e2 = std::move(e1); + assert(!e2.has_value()); + assert(!e1.has_value()); + } + return true; } diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpect.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpect.pass.cpp index 0a857c77d9c7a..62c3817a8a66f 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpect.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpect.pass.cpp @@ -26,6 +26,7 @@ #include "MoveOnly.h" #include "test_macros.h" +#include "../../types.h" // Test Constraints: static_assert(std::is_constructible_v, std::unexpect_t>); @@ -80,10 +81,13 @@ constexpr bool test() { testInt(); testInt(); testInt(); + testInt>(); testLValue(); testLValue(); + testLValue>(); testRValue(); testRValue(); + testRValue>(); // no arg { diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpect_init_list.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpect_init_list.pass.cpp index a73921225f1fa..28b4165bc27dc 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpect_init_list.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpect_init_list.pass.cpp @@ -28,6 +28,7 @@ #include "MoveOnly.h" #include "test_macros.h" +#include "../../types.h" // Test Constraints: static_assert( @@ -89,6 +90,12 @@ constexpr bool test() { assert(m.get() == 0); } + // TailClobberer + { + std::expected> e(std::unexpect, {1, 2, 3}); + assert(!e.has_value()); + } + return true; } diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpected.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpected.copy.pass.cpp index 89e1c9275e3e0..17063a97cb133 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpected.copy.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpected.copy.pass.cpp @@ -27,6 +27,7 @@ #include "MoveOnly.h" #include "test_macros.h" +#include "../../types.h" // Test Constraints static_assert(std::is_constructible_v, const std::unexpected&>); @@ -60,6 +61,7 @@ constexpr void testUnexpected() { constexpr bool test() { testUnexpected(); testUnexpected(); + testUnexpected>(); return true; } diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpected.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpected.move.pass.cpp index 2ddcb63c085f0..792ef341879ea 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpected.move.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpected.move.pass.cpp @@ -27,6 +27,7 @@ #include "MoveOnly.h" #include "test_macros.h" +#include "../../types.h" // Test Constraints static_assert(std::is_constructible_v, std::unexpected>); @@ -69,6 +70,7 @@ constexpr bool test() { testInt(); testInt(); testInt(); + testInt>(); testMoveOnly(); return true; } From 5f5fab011fd0c7d05574c73435df56f2584a0b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 22 Oct 2023 08:24:40 +0200 Subject: [PATCH 19/28] fix compile errors --- .../expected/expected.void/ctor/ctor.convert.copy.pass.cpp | 2 +- .../expected/expected.void/ctor/ctor.convert.move.pass.cpp | 2 +- .../utilities/expected/expected.void/ctor/ctor.copy.pass.cpp | 1 + .../utilities/expected/expected.void/ctor/ctor.move.pass.cpp | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.copy.pass.cpp index 118e4274e9ef8..30533206e65a3 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.copy.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.copy.pass.cpp @@ -100,7 +100,7 @@ constexpr bool test() { // convert TailClobberer { - const std::expected> e1; + const std::expected> e1(std::unexpect); std::expected> e2 = e1; assert(!e2.has_value()); assert(!e1.has_value()); diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.move.pass.cpp index 32f6055bc5c61..d9db49c273831 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.move.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.move.pass.cpp @@ -101,7 +101,7 @@ constexpr bool test() { // convert TailClobberer { - std::expected> e1; + std::expected> e1(std::unexpect); std::expected> e2 = std::move(e1); assert(!e2.has_value()); assert(!e1.has_value()); diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.copy.pass.cpp index f46826a4e3289..83e049857a93d 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.copy.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.copy.pass.cpp @@ -25,6 +25,7 @@ #include #include "test_macros.h" +#include "../../types.h" struct NonCopyable { NonCopyable(const NonCopyable&) = delete; diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.move.pass.cpp index 38affdcadde3f..777b412f65f64 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.move.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.move.pass.cpp @@ -25,6 +25,7 @@ #include #include "test_macros.h" +#include "../../types.h" struct NonMovable { NonMovable(NonMovable&&) = delete; From ebaccf9ca66a209238f41b04ce75a6fcf153f460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 22 Oct 2023 08:28:14 +0200 Subject: [PATCH 20/28] remove declaration of local Except struct --- .../expected/expected.expected/ctor/ctor.convert.copy.pass.cpp | 2 -- .../expected/expected.expected/ctor/ctor.convert.move.pass.cpp | 2 -- .../expected/expected.expected/ctor/ctor.copy.pass.cpp | 2 -- .../expected/expected.expected/ctor/ctor.default.pass.cpp | 2 -- .../expected/expected.expected/ctor/ctor.inplace.pass.cpp | 2 -- .../expected/expected.expected/ctor/ctor.move.pass.cpp | 2 -- .../expected/expected.void/ctor/ctor.convert.copy.pass.cpp | 2 -- .../expected/expected.void/ctor/ctor.convert.move.pass.cpp | 2 -- .../utilities/expected/expected.void/ctor/ctor.copy.pass.cpp | 2 -- .../utilities/expected/expected.void/ctor/ctor.move.pass.cpp | 2 -- .../expected/expected.void/ctor/ctor.unexpect.pass.cpp | 2 -- .../expected.void/ctor/ctor.unexpect_init_list.pass.cpp | 2 -- .../expected/expected.void/ctor/ctor.unexpected.copy.pass.cpp | 2 -- .../expected/expected.void/ctor/ctor.unexpected.move.pass.cpp | 2 -- 14 files changed, 28 deletions(-) diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.convert.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.convert.copy.pass.cpp index 2cc952358e821..16de28d970396 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.convert.copy.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.convert.copy.pass.cpp @@ -175,8 +175,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct ThrowingInt { ThrowingInt(int) { throw Except{}; } }; diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.convert.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.convert.move.pass.cpp index 31819cd33ef6c..0e30ea2c7fe0b 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.convert.move.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.convert.move.pass.cpp @@ -174,8 +174,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct ThrowingInt { ThrowingInt(int) { throw Except{}; } }; diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.copy.pass.cpp index 1b1c4a5960692..581df51207da2 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.copy.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.copy.pass.cpp @@ -114,8 +114,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct Throwing { Throwing() = default; Throwing(const Throwing&) { throw Except{}; } diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.default.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.default.pass.cpp index ea23d8aa735c8..dcd046bdd9d89 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.default.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.default.pass.cpp @@ -60,8 +60,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct Throwing { Throwing() { throw Except{}; }; }; diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.inplace.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.inplace.pass.cpp index 0b41eb44883e4..88ec41939439a 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.inplace.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.inplace.pass.cpp @@ -115,8 +115,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct Throwing { Throwing(int) { throw Except{}; }; }; diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.move.pass.cpp index aaf357fbc518a..cd89e2445860a 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.move.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.move.pass.cpp @@ -135,8 +135,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct Throwing { Throwing() = default; Throwing(Throwing&&) { throw Except{}; } diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.copy.pass.cpp index 30533206e65a3..05f556e25eac1 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.copy.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.copy.pass.cpp @@ -111,8 +111,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct ThrowingInt { ThrowingInt(int) { throw Except{}; } }; diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.move.pass.cpp index d9db49c273831..a48888be53ee0 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.move.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.convert.move.pass.cpp @@ -112,8 +112,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct ThrowingInt { ThrowingInt(int) { throw Except{}; } }; diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.copy.pass.cpp index 83e049857a93d..7c04a5fa9d044 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.copy.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.copy.pass.cpp @@ -76,8 +76,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct Throwing { Throwing() = default; Throwing(const Throwing&) { throw Except{}; } diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.move.pass.cpp index 777b412f65f64..bfb5028c9264d 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.move.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.move.pass.cpp @@ -91,8 +91,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct Throwing { Throwing() = default; Throwing(Throwing&&) { throw Except{}; } diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpect.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpect.pass.cpp index 62c3817a8a66f..85bc98d7f462d 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpect.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpect.pass.cpp @@ -115,8 +115,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct Throwing { Throwing(int) { throw Except{}; }; }; diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpect_init_list.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpect_init_list.pass.cpp index 28b4165bc27dc..4128668a6b07b 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpect_init_list.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpect_init_list.pass.cpp @@ -101,8 +101,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct Throwing { Throwing(std::initializer_list, int) { throw Except{}; }; }; diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpected.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpected.copy.pass.cpp index 17063a97cb133..ba738a3e339de 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpected.copy.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpected.copy.pass.cpp @@ -67,8 +67,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct Throwing { Throwing(int) { throw Except{}; } }; diff --git a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpected.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpected.move.pass.cpp index 792ef341879ea..33a5e7293df21 100644 --- a/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpected.move.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/ctor/ctor.unexpected.move.pass.cpp @@ -77,8 +77,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct Throwing { Throwing(int) { throw Except{}; } }; From c54292e74fe026bcdc8882ef0604677dd2c760e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Sun, 22 Oct 2023 09:23:43 +0200 Subject: [PATCH 21/28] remove more declarations of local Except structs --- .../expected.expected/ctor/ctor.inplace_init_list.pass.cpp | 2 -- .../utilities/expected/expected.expected/ctor/ctor.u.pass.cpp | 2 -- .../expected/expected.expected/ctor/ctor.unexpect.pass.cpp | 2 -- .../expected.expected/ctor/ctor.unexpect_init_list.pass.cpp | 2 -- .../expected.expected/ctor/ctor.unexpected.copy.pass.cpp | 2 -- .../expected.expected/ctor/ctor.unexpected.move.pass.cpp | 2 -- 6 files changed, 12 deletions(-) diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.inplace_init_list.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.inplace_init_list.pass.cpp index 143f9d7dab113..a97086fcc9195 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.inplace_init_list.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.inplace_init_list.pass.cpp @@ -102,8 +102,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct Throwing { Throwing(std::initializer_list, int) { throw Except{}; }; }; diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp index 16dd531a410c2..1cf3d9cc2ef49 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp @@ -157,8 +157,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct Throwing { Throwing(int) { throw Except{}; }; }; diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpect.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpect.pass.cpp index a0b334a25b0cb..27ce97737d288 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpect.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpect.pass.cpp @@ -115,8 +115,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct Throwing { Throwing(int) { throw Except{}; }; }; diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpect_init_list.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpect_init_list.pass.cpp index 4c280fc32a4e9..4f5d3d1492d37 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpect_init_list.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpect_init_list.pass.cpp @@ -102,8 +102,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct Throwing { Throwing(std::initializer_list, int) { throw Except{}; }; }; diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpected.copy.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpected.copy.pass.cpp index b3bb71cf36837..bbfd3048533c7 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpected.copy.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpected.copy.pass.cpp @@ -67,8 +67,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct Throwing { Throwing(int) { throw Except{}; } }; diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpected.move.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpected.move.pass.cpp index 419c789c86d51..800d47bda6958 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpected.move.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.unexpected.move.pass.cpp @@ -77,8 +77,6 @@ constexpr bool test() { void testException() { #ifndef TEST_HAS_NO_EXCEPTIONS - struct Except {}; - struct Throwing { Throwing(int) { throw Except{}; } }; From 737f6dd0af3b62fdd8af48451f272f781c858cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Fri, 27 Oct 2023 21:41:06 +0200 Subject: [PATCH 22/28] just return a reference to the union value in emplace() --- libcxx/include/__expected/expected.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h index f874a3a126a58..bf16c8f720d26 100644 --- a/libcxx/include/__expected/expected.h +++ b/libcxx/include/__expected/expected.h @@ -402,7 +402,7 @@ class expected { } std::construct_at(std::addressof(__union_.__val_), std::forward<_Args>(__args)...); __has_val_ = true; - return *std::addressof(__union_.__val_); + return __union_.__val_; } template @@ -415,7 +415,7 @@ class expected { } std::construct_at(std::addressof(__union_.__val_), __il, std::forward<_Args>(__args)...); __has_val_ = true; - return *std::addressof(__union_.__val_); + return __union_.__val_; } From 63e4c457064caefd8051e7de88f762c45b969a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Fri, 27 Oct 2023 21:58:16 +0200 Subject: [PATCH 23/28] use LIBCPP_STATIC_ASSERT to check size of std::expected containing a TailClobberer --- .../expected/expected.expected/observers/has_value.pass.cpp | 2 +- .../expected/expected.void/observers/has_value.pass.cpp | 2 +- 2 files changed, 2 insertions(+), 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 0e19da0655946..2b24b0ac24ddb 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 @@ -70,7 +70,7 @@ constexpr bool test() { const std::expected, 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)); + LIBCPP_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 06a45ed430180..fe92bb401643d 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 @@ -50,7 +50,7 @@ constexpr bool test() { const std::expected> 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)); + LIBCPP_STATIC_ASSERT(sizeof(TailClobberer<1>) == sizeof(e)); #endif assert(!e.has_value()); } From d5c5626d52eee3c4709ea5d14b8952208dd7615c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Mon, 30 Oct 2023 11:11:15 +0100 Subject: [PATCH 24/28] add swap tests involving TailClobberer --- .../expected.expected/swap/free.swap.pass.cpp | 62 ++++++++++++++ .../swap/member.swap.pass.cpp | 62 ++++++++++++++ .../expected.void/swap/free.swap.pass.cpp | 28 ++++++ .../expected.void/swap/member.swap.pass.cpp | 28 ++++++ libcxx/test/std/utilities/expected/types.h | 85 ++++++++++--------- 5 files changed, 227 insertions(+), 38 deletions(-) diff --git a/libcxx/test/std/utilities/expected/expected.expected/swap/free.swap.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/swap/free.swap.pass.cpp index 15c66d2b75076..ca612a84fee30 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/swap/free.swap.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/swap/free.swap.pass.cpp @@ -180,6 +180,35 @@ constexpr bool test() { assert(!e2.error().swapCalled); } + // TailClobberer + { + // is_nothrow_move_constructible_v + { + std::expected, TailClobbererNonTrivialMove<1, true>> x(std::in_place); + std::expected, TailClobbererNonTrivialMove<1, true>> y(std::unexpect); + + swap(x, y); + + // Both of these would fail if adjusting the "has value" flags happened + // _before_ constructing the member objects inside the `swap`. + assert(!x.has_value()); + assert(y.has_value()); + } + + // !is_nothrow_move_constructible_v + { + std::expected, TailClobbererNonTrivialMove<1, false>> x(std::in_place); + std::expected, TailClobbererNonTrivialMove<1, false>> y(std::unexpect); + + swap(x, y); + + // Both of these would fail if adjusting the "has value" flags happened + // _before_ constructing the member objects inside the `swap`. + assert(!x.has_value()); + assert(y.has_value()); + } + } + return true; } @@ -210,6 +239,39 @@ void testException() { assert(*e1 == 5); } } + + // TailClobberer + { + // is_nothrow_move_constructible_v + { + std::expected, TailClobbererNonTrivialMove<1>> x(std::in_place); + std::expected, TailClobbererNonTrivialMove<1>> y(std::unexpect); + try { + swap(x, y); + assert(false); + } catch (Except) { + assert(x.has_value()); + // This would fail if `TailClobbererNonTrivialMove<1>` clobbered the + // flag when rolling back the swap. + assert(!y.has_value()); + } + } + + // !is_nothrow_move_constructible_v + { + std::expected, TailClobbererNonTrivialMove<1, false, true>> x(std::in_place); + std::expected, TailClobbererNonTrivialMove<1, false, true>> y(std::unexpect); + try { + swap(x, y); + assert(false); + } catch (Except) { + // This would fail if `TailClobbererNonTrivialMove<0>` clobbered the + // flag when rolling back the swap. + assert(x.has_value()); + assert(!y.has_value()); + } + } + } #endif // TEST_HAS_NO_EXCEPTIONS } 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 d2d4a09922092..97b871ed9cffe 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 @@ -198,6 +198,35 @@ constexpr bool test() { assert(!e2.error().swapCalled); } + // TailClobberer + { + // is_nothrow_move_constructible_v + { + std::expected, TailClobbererNonTrivialMove<1, true>> x(std::in_place); + std::expected, TailClobbererNonTrivialMove<1, true>> y(std::unexpect); + + x.swap(y); + + // Both of these would fail if adjusting the "has value" flags happened + // _before_ constructing the member objects inside the `swap`. + assert(!x.has_value()); + assert(y.has_value()); + } + + // !is_nothrow_move_constructible_v + { + std::expected, TailClobbererNonTrivialMove<1, false>> x(std::in_place); + std::expected, TailClobbererNonTrivialMove<1, false>> y(std::unexpect); + + x.swap(y); + + // Both of these would fail if adjusting the "has value" flags happened + // _before_ constructing the member objects inside the `swap`. + assert(!x.has_value()); + assert(y.has_value()); + } + } + return true; } @@ -228,6 +257,39 @@ void testException() { assert(*e1 == 5); } } + + // TailClobberer + { + // is_nothrow_move_constructible_v + { + std::expected, TailClobbererNonTrivialMove<1>> x(std::in_place); + std::expected, TailClobbererNonTrivialMove<1>> y(std::unexpect); + try { + x.swap(y); + assert(false); + } catch (Except) { + assert(x.has_value()); + // This would fail if `TailClobbererNonTrivialMove<1>` clobbered the + // flag when rolling back the swap. + assert(!y.has_value()); + } + } + + // !is_nothrow_move_constructible_v + { + std::expected, TailClobbererNonTrivialMove<1, false, true>> x(std::in_place); + std::expected, TailClobbererNonTrivialMove<1, false, true>> y(std::unexpect); + try { + x.swap(y); + assert(false); + } catch (Except) { + // This would fail if `TailClobbererNonTrivialMove<0>` clobbered the + // flag when rolling back the swap. + assert(x.has_value()); + assert(!y.has_value()); + } + } + } #endif // TEST_HAS_NO_EXCEPTIONS } diff --git a/libcxx/test/std/utilities/expected/expected.void/swap/free.swap.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/swap/free.swap.pass.cpp index ad1eb3f0bd20d..d49485f0d402b 100644 --- a/libcxx/test/std/utilities/expected/expected.void/swap/free.swap.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/swap/free.swap.pass.cpp @@ -117,6 +117,19 @@ constexpr bool test() { assert(s.dtorCalled); } + // TailClobberer + { + std::expected> x(std::in_place); + std::expected> y(std::unexpect); + + swap(x, y); + + // The next line would fail if adjusting the "has value" flag happened + // _before_ constructing the member object inside the `swap`. + assert(!x.has_value()); + assert(y.has_value()); + } + return true; } @@ -151,6 +164,21 @@ void testException() { assert(!e2Destroyed); } } + + // TailClobberer + { + std::expected> x(std::in_place); + std::expected> y(std::unexpect); + try { + swap(x, y); + assert(false); + } catch (Except) { + // This would fail if `TailClobbererNonTrivialMove<0, false, true>` + // clobbered the flag before throwing the exception. + assert(x.has_value()); + assert(!y.has_value()); + } + } #endif // TEST_HAS_NO_EXCEPTIONS } 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 a71c701a46930..166dca558f99b 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 @@ -126,6 +126,19 @@ constexpr bool test() { assert(s.dtorCalled); } + // TailClobberer + { + std::expected> x(std::in_place); + std::expected> y(std::unexpect); + + x.swap(y); + + // The next line would fail if adjusting the "has value" flag happened + // _before_ constructing the member object inside the `swap`. + assert(!x.has_value()); + assert(y.has_value()); + } + return true; } @@ -160,6 +173,21 @@ void testException() { assert(!e2Destroyed); } } + + // TailClobberer + { + std::expected> x(std::in_place); + std::expected> y(std::unexpect); + try { + x.swap(y); + assert(false); + } catch (Except) { + // This would fail if `TailClobbererNonTrivialMove<0, false, true>` + // clobbered the flag before throwing the exception. + assert(x.has_value()); + assert(!y.has_value()); + } + } #endif // TEST_HAS_NO_EXCEPTIONS } diff --git a/libcxx/test/std/utilities/expected/types.h b/libcxx/test/std/utilities/expected/types.h index 58c2df504bfb2..d2224cf869dfa 100644 --- a/libcxx/test/std/utilities/expected/types.h +++ b/libcxx/test/std/utilities/expected/types.h @@ -104,44 +104,6 @@ struct TrackedMove { } }; -// This type has one byte of tail padding where `std::expected` may put its -// "has value" flag. The constructor will clobber all bytes including the -// tail padding. With this type we can check that `std::expected` handles -// the case where the "has value" flag is an overlapping subobject correctly. -// -// See https://github.com/llvm/llvm-project/issues/68552 for details. -template -struct TailClobberer { - constexpr TailClobberer() noexcept { - if (!std::is_constant_evaluated()) { - std::memset(this, constant, sizeof(*this)); - } - // Always set `b` itself to `false` so that the comparison works. - b = false; - } - constexpr TailClobberer(const TailClobberer&) : TailClobberer() {} - constexpr TailClobberer(TailClobberer&&) = default; - // Converts from `int`/`std::initializer_list, used in some tests. - constexpr TailClobberer(int) : TailClobberer() {} - constexpr TailClobberer(std::initializer_list) noexcept : TailClobberer() {} - - friend constexpr bool operator==(const TailClobberer&, const TailClobberer&) = default; - -private: - alignas(2) bool b; -}; -static_assert(!std::is_trivially_copy_constructible_v>); -static_assert(std::is_trivially_move_constructible_v>); - -template -struct TailClobbererNonTrivialMove : TailClobberer { - using TailClobberer::TailClobberer; - constexpr TailClobbererNonTrivialMove(TailClobbererNonTrivialMove&&) noexcept : TailClobberer() {} -}; -static_assert(!std::is_trivially_copy_constructible_v>); -static_assert(std::is_move_constructible_v>); -static_assert(!std::is_trivially_move_constructible_v>); - #ifndef TEST_HAS_NO_EXCEPTIONS struct Except {}; @@ -190,4 +152,51 @@ struct MoveOnlyErrorType { MoveOnlyErrorType& operator=(const MoveOnlyErrorType&) = delete; }; +// This type has one byte of tail padding where `std::expected` may put its +// "has value" flag. The constructor will clobber all bytes including the +// tail padding. With this type we can check that `std::expected` handles +// the case where the "has value" flag is an overlapping subobject correctly. +// +// See https://github.com/llvm/llvm-project/issues/68552 for details. +template +struct TailClobberer { + constexpr TailClobberer() noexcept { + if (!std::is_constant_evaluated()) { + std::memset(this, Constant, sizeof(*this)); + } + // Always set `b` itself to `false` so that the comparison works. + b = false; + } + constexpr TailClobberer(const TailClobberer&) : TailClobberer() {} + constexpr TailClobberer(TailClobberer&&) = default; + // Converts from `int`/`std::initializer_list, used in some tests. + constexpr TailClobberer(int) : TailClobberer() {} + constexpr TailClobberer(std::initializer_list) noexcept : TailClobberer() {} + + friend constexpr bool operator==(const TailClobberer&, const TailClobberer&) = default; + + friend constexpr void swap(TailClobberer&, TailClobberer&){}; + +private: + alignas(2) bool b; +}; +static_assert(!std::is_trivially_copy_constructible_v>); +static_assert(std::is_trivially_move_constructible_v>); + +template +struct TailClobbererNonTrivialMove : TailClobberer { + using TailClobberer::TailClobberer; + constexpr TailClobbererNonTrivialMove(TailClobbererNonTrivialMove&&) noexcept(Noexcept) : TailClobberer() { +#ifndef TEST_HAS_NO_EXCEPTIONS + if (ThrowOnMove) + throw Except{}; +#endif + } +}; +static_assert(!std::is_trivially_copy_constructible_v>); +static_assert(std::is_move_constructible_v>); +static_assert(!std::is_trivially_move_constructible_v>); +static_assert(std::is_nothrow_move_constructible_v>); +static_assert(!std::is_nothrow_move_constructible_v>); + #endif // TEST_STD_UTILITIES_EXPECTED_TYPES_H From 8e866c64045f12fcd8393d255227843179645dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Mon, 30 Oct 2023 11:12:18 +0100 Subject: [PATCH 25/28] drive by fix: these should be free swaps --- .../expected/expected.expected/swap/free.swap.pass.cpp | 2 +- .../utilities/expected/expected.void/swap/free.swap.pass.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libcxx/test/std/utilities/expected/expected.expected/swap/free.swap.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/swap/free.swap.pass.cpp index ca612a84fee30..3c03efd832919 100644 --- a/libcxx/test/std/utilities/expected/expected.expected/swap/free.swap.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.expected/swap/free.swap.pass.cpp @@ -129,7 +129,7 @@ constexpr bool test() { std::expected, TrackedMove> e1(std::in_place, 5); std::expected, TrackedMove> e2(std::unexpect, 10); - e1.swap(e2); + swap(e1, e2); assert(!e1.has_value()); assert(e1.error().i == 10); diff --git a/libcxx/test/std/utilities/expected/expected.void/swap/free.swap.pass.cpp b/libcxx/test/std/utilities/expected/expected.void/swap/free.swap.pass.cpp index d49485f0d402b..f7314c79fa6db 100644 --- a/libcxx/test/std/utilities/expected/expected.void/swap/free.swap.pass.cpp +++ b/libcxx/test/std/utilities/expected/expected.void/swap/free.swap.pass.cpp @@ -91,7 +91,7 @@ constexpr bool test() { std::expected e1(std::in_place); std::expected e2(std::unexpect, s, 10); - e1.swap(e2); + swap(e1, e2); assert(!e1.has_value()); assert(e1.error().data_ == 10); @@ -107,7 +107,7 @@ constexpr bool test() { std::expected e1(std::unexpect, s, 10); std::expected e2(std::in_place); - e1.swap(e2); + swap(e1, e2); assert(e1.has_value()); assert(!e2.has_value()); From c637421afaf016fbf06f8b9963368d6788fedb69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Mon, 30 Oct 2023 11:12:44 +0100 Subject: [PATCH 26/28] make both member.swap.pass.cpp files clang-format friendly --- .../expected/expected.expected/swap/member.swap.pass.cpp | 2 +- .../utilities/expected/expected.void/swap/member.swap.pass.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 97b871ed9cffe..b6b112cfbeb8b 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 @@ -70,7 +70,7 @@ static_assert(!HasMemberSwap); // Test noexcept template -concept MemberSwapNoexcept = +concept MemberSwapNoexcept = // requires(std::expected x, std::expected y) { { x.swap(y) } noexcept; }; 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 166dca558f99b..70e004abef684 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 @@ -52,7 +52,7 @@ struct MoveMayThrow { }; template -concept MemberSwapNoexcept = +concept MemberSwapNoexcept = // requires(std::expected x, std::expected y) { { x.swap(y) } noexcept; }; From 19cfd8a5a8eff7d34184bce66886a449a58de1bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Mon, 30 Oct 2023 11:42:35 +0100 Subject: [PATCH 27/28] try to avoid -Werror=terminate error on GCC --- libcxx/test/std/utilities/expected/types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcxx/test/std/utilities/expected/types.h b/libcxx/test/std/utilities/expected/types.h index d2224cf869dfa..70b0e4ec6f9fa 100644 --- a/libcxx/test/std/utilities/expected/types.h +++ b/libcxx/test/std/utilities/expected/types.h @@ -188,7 +188,7 @@ struct TailClobbererNonTrivialMove : TailClobberer { using TailClobberer::TailClobberer; constexpr TailClobbererNonTrivialMove(TailClobbererNonTrivialMove&&) noexcept(Noexcept) : TailClobberer() { #ifndef TEST_HAS_NO_EXCEPTIONS - if (ThrowOnMove) + if (!Noexcept && ThrowOnMove) throw Except{}; #endif } From 05f7c85be795504de40a84657e17bba9b6679b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= Date: Mon, 30 Oct 2023 12:09:42 +0100 Subject: [PATCH 28/28] use if constexpr --- libcxx/test/std/utilities/expected/types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcxx/test/std/utilities/expected/types.h b/libcxx/test/std/utilities/expected/types.h index 70b0e4ec6f9fa..ac4a82f2baf78 100644 --- a/libcxx/test/std/utilities/expected/types.h +++ b/libcxx/test/std/utilities/expected/types.h @@ -188,7 +188,7 @@ struct TailClobbererNonTrivialMove : TailClobberer { using TailClobberer::TailClobberer; constexpr TailClobbererNonTrivialMove(TailClobbererNonTrivialMove&&) noexcept(Noexcept) : TailClobberer() { #ifndef TEST_HAS_NO_EXCEPTIONS - if (!Noexcept && ThrowOnMove) + if constexpr (!Noexcept && ThrowOnMove) throw Except{}; #endif }