Skip to content

Commit

Permalink
Fix the _Choose_pocca_v branch in basic_string::operator=(const&) (
Browse files Browse the repository at this point in the history
…#4031)

Co-authored-by: Casey Carter <cartec69@gmail.com>
Co-authored-by: Stephan T. Lavavej <stl@nuwen.net>
  • Loading branch information
3 people authored Sep 21, 2023
1 parent 61cc2a5 commit 69ceca3
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 19 deletions.
12 changes: 7 additions & 5 deletions stl/inc/xstring
Original file line number Diff line number Diff line change
Expand Up @@ -3046,8 +3046,9 @@ private:

if (_Right_data._Large_mode_engaged()) { // steal buffer
_Construct_in_place(_My_data._Bx._Ptr, _Right_data._Bx._Ptr);
_Right_data._Bx._Ptr = nullptr;
_Swap_proxy_and_iterators(_Right);

_Destroy_in_place(_Right_data._Bx._Ptr);
} else { // copy small string buffer
_My_data._Activate_SSO_buffer();
_Traits::copy(_My_data._Bx._Buf, _Right_data._Bx._Buf, _Right_data._Mysize + 1);
Expand Down Expand Up @@ -3156,12 +3157,13 @@ public:
size_type _New_capacity = _Calculate_growth(_Right_size, _Small_string_capacity, _Right.max_size());
auto _Right_al_non_const = _Right_al;
const pointer _New_ptr = _Allocate_for_capacity(_Right_al_non_const, _New_capacity); // throws

_Traits::copy(_Unfancy(_New_ptr), _Right_ptr, _Right_size + 1);

_Tidy_deallocate();
_Mypair._Myval2._Bx._Ptr = _New_ptr;
_Mypair._Myval2._Mysize = _Right_size;
_Mypair._Myval2._Myres = _New_capacity;
_Construct_in_place(_Mypair._Myval2._Bx._Ptr, _New_ptr);
_Mypair._Myval2._Mysize = _Right_size;
_Mypair._Myval2._Myres = _New_capacity;
_ASAN_STRING_CREATE(*this);
} else {
_Tidy_deallocate();
_Traits::copy(_Mypair._Myval2._Bx._Buf, _Right_ptr, _Right_size + 1);
Expand Down
193 changes: 179 additions & 14 deletions tests/std/tests/VSO_0000000_allocator_propagation/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,160 @@ _CONSTEXPR20 void assert_is_permutation(const Container& cont, initializer_list<
assert(is_permutation(cont.begin(), cont.end(), il.begin(), il.end()));
}

class test_leak {
private:
char* res;

public:
test_leak(const test_leak&) = delete;
test_leak& operator=(const test_leak&) = delete;

_CONSTEXPR20 test_leak() noexcept /* terminates */ : res(allocator<char>{}.allocate(1)) {}
_CONSTEXPR20 ~test_leak() {
allocator<char>{}.deallocate(res, 1);
}
};

template <class T>
class nontrivial_pointer : private test_leak {
public:
T* ptr;
_CONSTEXPR20 nontrivial_pointer() noexcept : ptr(nullptr) {}
_CONSTEXPR20 explicit nontrivial_pointer(T* ptr_) noexcept : ptr(ptr_) {}
_CONSTEXPR20 /*implicit*/ nontrivial_pointer(nullptr_t) noexcept : ptr(nullptr) {}

_CONSTEXPR20 nontrivial_pointer(const nontrivial_pointer& other) noexcept : ptr(other.ptr) {}
template <class U, enable_if_t<is_convertible_v<U*, T*>, int> = 0>
_CONSTEXPR20 nontrivial_pointer(const nontrivial_pointer<U>& other) noexcept : ptr(other.ptr) {}
_CONSTEXPR20 nontrivial_pointer& operator=(const nontrivial_pointer& other) noexcept {
ptr = other.ptr;
return *this;
}

_CONSTEXPR20 ~nontrivial_pointer() = default;

_CONSTEXPR20 explicit operator bool() const noexcept {
#ifdef __EDG__ // TRANSITION, VSO-1888157
return static_cast<bool>(ptr);
#else // ^^^ workaround / no workaround vvv
return ptr != nullptr;
#endif // ^^^ no workaround ^^^
}
_CONSTEXPR20 add_lvalue_reference_t<T> operator*() const noexcept {
return *ptr;
}
_CONSTEXPR20 T* operator->() const noexcept {
return ptr;
}
template <class I, enable_if_t<is_integral_v<I>, int> = 0>
_CONSTEXPR20 add_lvalue_reference_t<T> operator[](I off) const noexcept {
return ptr[off];
}

_CONSTEXPR20 nontrivial_pointer& operator++() noexcept {
++ptr;
return *this;
}
_CONSTEXPR20 nontrivial_pointer operator++(int) noexcept {
return nontrivial_pointer(ptr++);
}
_CONSTEXPR20 nontrivial_pointer& operator--() noexcept {
--ptr;
return *this;
}
_CONSTEXPR20 nontrivial_pointer operator--(int) noexcept {
return nontrivial_pointer(ptr--);
}
template <class I, enable_if_t<is_integral_v<I>, int> = 0>
_CONSTEXPR20 nontrivial_pointer& operator+=(I diff) noexcept {
ptr += diff;
return *this;
}
template <class I, enable_if_t<is_integral_v<I>, int> = 0>
_CONSTEXPR20 nontrivial_pointer& operator-=(I diff) noexcept {
ptr -= diff;
return *this;
}

template <class I, enable_if_t<is_integral_v<I>, int> = 0>
friend _CONSTEXPR20 nontrivial_pointer operator+(nontrivial_pointer ptr, I diff) noexcept {
return ptr += diff;
}
template <class I, enable_if_t<is_integral_v<I>, int> = 0>
friend _CONSTEXPR20 nontrivial_pointer operator+(I diff, nontrivial_pointer ptr) noexcept {
return ptr += diff;
}
friend _CONSTEXPR20 ptrdiff_t operator-(nontrivial_pointer lhs, nontrivial_pointer rhs) noexcept {
return lhs.ptr - rhs.ptr;
}
template <class I, enable_if_t<is_integral_v<I>, int> = 0>
friend _CONSTEXPR20 nontrivial_pointer operator-(nontrivial_pointer ptr, I diff) noexcept {
return ptr -= diff;
}

friend _CONSTEXPR20 bool operator==(nontrivial_pointer lhs, nontrivial_pointer rhs) noexcept {
return lhs.ptr == rhs.ptr;
}
friend _CONSTEXPR20 bool operator==(nontrivial_pointer ptr, nullptr_t) noexcept {
return !ptr;
}
friend _CONSTEXPR20 bool operator==(nullptr_t, nontrivial_pointer ptr) noexcept {
return !ptr;
}
friend _CONSTEXPR20 bool operator!=(nontrivial_pointer lhs, nontrivial_pointer rhs) noexcept {
return lhs.ptr != rhs.ptr;
}
friend _CONSTEXPR20 bool operator!=(nontrivial_pointer ptr, nullptr_t) noexcept {
return static_cast<bool>(ptr);
}
friend _CONSTEXPR20 bool operator!=(nullptr_t, nontrivial_pointer ptr) noexcept {
return static_cast<bool>(ptr);
}
friend _CONSTEXPR20 bool operator<(nontrivial_pointer lhs, nontrivial_pointer rhs) noexcept {
return lhs.ptr < rhs.ptr;
}
friend _CONSTEXPR20 bool operator<=(nontrivial_pointer lhs, nontrivial_pointer rhs) noexcept {
return lhs.ptr <= rhs.ptr;
}
friend _CONSTEXPR20 bool operator>(nontrivial_pointer lhs, nontrivial_pointer rhs) noexcept {
return lhs.ptr > rhs.ptr;
}
friend _CONSTEXPR20 bool operator>=(nontrivial_pointer lhs, nontrivial_pointer rhs) noexcept {
return lhs.ptr >= rhs.ptr;
}
};

template <class T, bool IsVoid = is_void_v<T>>
struct impl_pointer_to {
// no pointer_to for void
};

template <class T>
struct impl_pointer_to<T, false> {
static constexpr nontrivial_pointer<T> pointer_to(T& r) noexcept {
return nontrivial_pointer<T>(addressof(r));
}
};

template <class T>
struct std::pointer_traits<nontrivial_pointer<T>> : impl_pointer_to<T> {
using pointer = nontrivial_pointer<T>;
using element_type = T;
using difference_type = ptrdiff_t;

template <class U>
using rebind = nontrivial_pointer<U>;
};

template <class T>
struct std::iterator_traits<nontrivial_pointer<T>> {
using iterator_category = random_access_iterator_tag;
using difference_type = ptrdiff_t;
using value_type = remove_const_t<T>;
using reference = add_lvalue_reference_t<T>;
using pointer = nontrivial_pointer<T>;
};

template <class T, class POCCA, class POCMA, class POCS, class EQUAL>
class MyAlloc {
private:
Expand All @@ -49,6 +203,7 @@ class MyAlloc {
}

using value_type = T;
using pointer = nontrivial_pointer<T>;

using propagate_on_container_copy_assignment = POCCA;
using propagate_on_container_move_assignment = POCMA;
Expand All @@ -70,12 +225,12 @@ class MyAlloc {
return equal_id() != other.equal_id();
}

[[nodiscard]] constexpr T* allocate(const size_t numElements) {
return allocator<T>{}.allocate(numElements + equal_id()) + equal_id();
[[nodiscard]] constexpr pointer allocate(const size_t numElements) {
return pointer(allocator<T>{}.allocate(numElements + equal_id()) + equal_id());
}

constexpr void deallocate(T* const first, const size_t numElements) noexcept {
allocator<T>{}.deallocate(first - equal_id(), numElements + equal_id());
constexpr void deallocate(pointer const first, const size_t numElements) noexcept {
allocator<T>{}.deallocate(_Unfancy(first - equal_id()), numElements + equal_id());
}
};

Expand Down Expand Up @@ -307,7 +462,12 @@ _CONSTEXPR20 bool test_sequence() {
test_sequence_copy_assign<Sequence, CopyAlloc<int>>(11, 22, 11); // POCCA, non-equal allocators
test_sequence_copy_assign<Sequence, CopyEqualAlloc<int>>(11, 22, 11); // POCCA, always-equal allocators

test_sequence_move_ctor<Sequence>();
#if _HAS_CXX20 && !defined(__clang__) && !defined(__EDG__) // TRANSITION, VSO-1888462
if (!is_constant_evaluated())
#endif // ^^^ workaround ^^^
{
test_sequence_move_ctor<Sequence>();
}

test_sequence_move_alloc_ctor<Sequence>(11, 11); // equal allocators
test_sequence_move_alloc_ctor<Sequence>(11, 22); // non-equal allocators
Expand Down Expand Up @@ -559,7 +719,7 @@ void test_flist() {

// NOTE: Having 4 elements of type char32_t bypasses the Small String Optimization.

void test_string_copy_ctor() {
_CONSTEXPR20 void test_string_copy_ctor() {
basic_string<char32_t, char_traits<char32_t>, StationaryAlloc<char32_t>> src(
{5, 10, 20, 30}, StationaryAlloc<char32_t>(11));
auto src_it = src.begin();
Expand All @@ -583,7 +743,7 @@ void test_string_copy_ctor() {
assert(dst.get_allocator().id() == 11);
}

void test_string_copy_alloc_ctor(const size_t id1, const size_t id2) {
_CONSTEXPR20 void test_string_copy_alloc_ctor(const size_t id1, const size_t id2) {
basic_string<char32_t, char_traits<char32_t>, StationaryAlloc<char32_t>> src(
{5, 10, 20, 30}, StationaryAlloc<char32_t>(id1));
auto src_it = src.begin();
Expand All @@ -608,7 +768,7 @@ void test_string_copy_alloc_ctor(const size_t id1, const size_t id2) {
}

template <class Alloc>
void test_string_copy_assign(const size_t id1, const size_t id2, const size_t id3) {
_CONSTEXPR20 void test_string_copy_assign(const size_t id1, const size_t id2, const size_t id3) {

basic_string<char32_t, char_traits<char32_t>, Alloc> src({5, 10, 20, 30}, Alloc(id1));
basic_string<char32_t, char_traits<char32_t>, Alloc> dst({0, 0, 0, 0}, Alloc(id2));
Expand Down Expand Up @@ -636,7 +796,7 @@ void test_string_copy_assign(const size_t id1, const size_t id2, const size_t id
assert(dst.get_allocator().id() == id3);
}

void test_string_copy_assign_pocca_sso() {
_CONSTEXPR20 void test_string_copy_assign_pocca_sso() {
// GH-3862 fixed a bug where the POCCA codepath in basic_string's copy assignment operator mishandled
// the scenario where the string on the right hand side has a large capacity but a small size - so while
// the RHS has dynamically allocated memory, the LHS should activate the Small String Optimization.
Expand All @@ -661,7 +821,7 @@ void test_string_copy_assign_pocca_sso() {
assert(right == "yyyyyyy");
}

void test_string_move_ctor() {
_CONSTEXPR20 void test_string_move_ctor() {
basic_string<char32_t, char_traits<char32_t>, StationaryAlloc<char32_t>> src(
{5, 10, 20, 30}, StationaryAlloc<char32_t>(11));
auto it1 = src.begin();
Expand Down Expand Up @@ -690,7 +850,7 @@ void test_string_move_ctor() {
assert(dst.get_allocator().id() == 11);
}

void test_string_move_alloc_ctor(const size_t id1, const size_t id2) {
_CONSTEXPR20 void test_string_move_alloc_ctor(const size_t id1, const size_t id2) {
basic_string<char32_t, char_traits<char32_t>, StationaryAlloc<char32_t>> src(
{5, 10, 20, 30}, StationaryAlloc<char32_t>(id1));
auto it1 = src.begin();
Expand Down Expand Up @@ -721,7 +881,7 @@ void test_string_move_alloc_ctor(const size_t id1, const size_t id2) {
}

template <class Alloc>
void test_string_move_assign(const size_t id1, const size_t id2, const size_t id3) {
_CONSTEXPR20 void test_string_move_assign(const size_t id1, const size_t id2, const size_t id3) {

basic_string<char32_t, char_traits<char32_t>, Alloc> src({5, 10, 20, 30}, Alloc(id1));
basic_string<char32_t, char_traits<char32_t>, Alloc> dst({0, 0, 0, 0}, Alloc(id2));
Expand Down Expand Up @@ -755,7 +915,7 @@ void test_string_move_assign(const size_t id1, const size_t id2, const size_t id
}

template <class Alloc>
void test_string_swap(const size_t id1, const size_t id2) {
_CONSTEXPR20 void test_string_swap(const size_t id1, const size_t id2) {

basic_string<char32_t, char_traits<char32_t>, Alloc> src({5, 10, 20, 30}, Alloc(id1));
basic_string<char32_t, char_traits<char32_t>, Alloc> dst({6, 40, 50, 60}, Alloc(id2));
Expand Down Expand Up @@ -784,7 +944,7 @@ void test_string_swap(const size_t id1, const size_t id2) {
assert(dst.get_allocator().id() == id1);
}

void test_string() {
_CONSTEXPR20 bool test_string() {
test_string_copy_ctor();

test_string_copy_alloc_ctor(11, 11); // equal allocators
Expand Down Expand Up @@ -814,6 +974,8 @@ void test_string() {
test_string_swap<SwapAlloc<char32_t>>(11, 11); // POCS, equal allocators
test_string_swap<SwapAlloc<char32_t>>(11, 22); // POCS, non-equal allocators
test_string_swap<SwapEqualAlloc<char32_t>>(11, 22); // POCS, always-equal allocators

return true;
}


Expand Down Expand Up @@ -1680,8 +1842,11 @@ int main() {
test_sequence<deque>();
test_sequence<list>();
test_sequence<vector>();

#if _HAS_CXX20
static_assert(test_sequence<vector>());

static_assert(test_string());
#endif // _HAS_CXX20

test_flist();
Expand Down

0 comments on commit 69ceca3

Please sign in to comment.