Skip to content

<memory>: Make algorithms properly destroy objects in constant evaluation #5449

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 32 additions & 9 deletions stl/inc/memory
Original file line number Diff line number Diff line change
Expand Up @@ -584,12 +584,18 @@ namespace ranges {
template <_No_throw_input_iterator _It, _No_throw_sentinel_for<_It> _Se>
requires destructible<iter_value_t<_It>>
_NODISCARD constexpr _It _Destroy_unchecked(_It _First, _Se _Last) noexcept {
if constexpr (is_trivially_destructible_v<iter_value_t<_It>>) {
_RANGES advance(_First, _STD move(_Last));
} else {
if (_STD is_constant_evaluated()) {
for (; _First != _Last; ++_First) {
_RANGES destroy_at(_STD addressof(*_First));
}
} else {
if constexpr (is_trivially_destructible_v<iter_value_t<_It>>) {
_RANGES advance(_First, _STD move(_Last));
} else {
for (; _First != _Last; ++_First) {
_RANGES destroy_at(_STD addressof(*_First));
}
}
}

return _First;
Expand Down Expand Up @@ -630,12 +636,21 @@ _CONSTEXPR20 _NoThrowFwdIt destroy_n(_NoThrowFwdIt _First, const _Diff _Count_ra
}

auto _UFirst = _STD _Get_unwrapped_n(_First, _Count);
if constexpr (is_trivially_destructible_v<_Iter_value_t<_NoThrowFwdIt>>) {
_STD advance(_UFirst, _Count);
} else {
#if _HAS_CXX20
if (_STD is_constant_evaluated()) {
for (; _Count > 0; --_Count, (void) ++_UFirst) {
_STD _Destroy_in_place(*_UFirst);
}
} else
#endif // _HAS_CXX20
{
if constexpr (is_trivially_destructible_v<_Iter_value_t<_NoThrowFwdIt>>) {
_STD advance(_UFirst, _Count);
} else {
for (; _Count > 0; --_Count, (void) ++_UFirst) {
_STD _Destroy_in_place(*_UFirst);
}
}
}

_STD _Seek_wrapped(_First, _UFirst);
Expand All @@ -658,14 +673,22 @@ namespace ranges {
}

auto _UFirst = _STD _Get_unwrapped_n(_STD move(_First), _Count);
if constexpr (is_trivially_destructible_v<iter_value_t<_It>>) {
_RANGES advance(_UFirst, _Count);
} else {
if (_STD is_constant_evaluated()) {
do {
_RANGES destroy_at(_STD addressof(*_UFirst));
++_UFirst;
--_Count;
} while (_Count > 0);
} else {
if constexpr (is_trivially_destructible_v<iter_value_t<_It>>) {
_RANGES advance(_UFirst, _Count);
} else {
do {
_RANGES destroy_at(_STD addressof(*_UFirst));
++_UFirst;
--_Count;
} while (_Count > 0);
}
}

_STD _Seek_wrapped(_First, _STD move(_UFirst));
Expand Down
12 changes: 10 additions & 2 deletions stl/inc/xmemory
Original file line number Diff line number Diff line change
Expand Up @@ -1111,11 +1111,19 @@ _CONSTEXPR20 void _Destroy_range(_Alloc_ptr_t<_Alloc> _First, const _Alloc_ptr_t

template <class _NoThrowFwdIt, class _NoThrowSentinel>
_CONSTEXPR20 void _Destroy_range(_NoThrowFwdIt _First, const _NoThrowSentinel _Last) noexcept {
// note that this is an optimization for debug mode codegen; in release mode the BE removes all of this
if constexpr (!is_trivially_destructible_v<_Iter_value_t<_NoThrowFwdIt>>) {
#if _HAS_CXX20
if (_STD is_constant_evaluated()) {
for (; _First != _Last; ++_First) {
_STD _Destroy_in_place(*_First);
}
} else
#endif // _HAS_CXX20
{ // note that this is an optimization for debug mode codegen; in release mode the BE removes all of this
if constexpr (!is_trivially_destructible_v<_Iter_value_t<_NoThrowFwdIt>>) {
for (; _First != _Last; ++_First) {
_STD _Destroy_in_place(*_First);
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,42 @@ static_assert(!CanDestroyN<const char*>);
static_assert(!CanDestroyN<volatile char*>);
static_assert(!CanDestroyN<const volatile char*>);

#ifdef __clang__ // TRANSITION, DevCom-10642767 (MSVC), DevCom-10896316 (EDG)
// Test that destroy, destroy_at, and destroy_n properly destroy trivially destructible objects
// during constant evaluation.
// After such destruction, further access will cause core language undefined behavior,
// which will in turn cause constant evaluation failure.

template <auto>
struct require_valid_constant;

template <class Fn>
constexpr int consteval_validate_destruction(Fn op) {
struct S {
int n;
};

S arr[1]{{42}};
op(arr);
return arr[0].n;
}

template <auto Fn>
constexpr bool CanWellDefinedlyAccessAfterOperation =
requires { typename require_valid_constant<consteval_validate_destruction(Fn)>; };

static_assert(CanWellDefinedlyAccessAfterOperation<[](auto&) {}>);
static_assert(!CanWellDefinedlyAccessAfterOperation<[](auto& arr) { destroy(arr + 0, arr + 1); }>);
static_assert(!CanWellDefinedlyAccessAfterOperation<[](auto& arr) { destroy_at(arr + 0); }>);
static_assert(!CanWellDefinedlyAccessAfterOperation<[](auto& arr) { destroy_at(&arr); }>);
static_assert(!CanWellDefinedlyAccessAfterOperation<[](auto& arr) { destroy_n(arr + 0, 1); }>);
static_assert(!CanWellDefinedlyAccessAfterOperation<[](auto& arr) { ranges::destroy(arr + 0, arr + 1); }>);
static_assert(!CanWellDefinedlyAccessAfterOperation<[](auto& arr) { ranges::destroy(arr); }>);
static_assert(!CanWellDefinedlyAccessAfterOperation<[](auto& arr) { ranges::destroy_at(arr + 0); }>);
static_assert(!CanWellDefinedlyAccessAfterOperation<[](auto& arr) { ranges::destroy_at(&arr); }>);
static_assert(!CanWellDefinedlyAccessAfterOperation<[](auto& arr) { ranges::destroy_n(arr + 0, 1); }>);
#endif // ^^^ no workaround ^^^

int main() {
test_runtime(1234);
test_runtime(string("hello world"));
Expand Down
Loading