Skip to content

Commit 63aeb77

Browse files
<sstream>: Don't swap basic_stringbufs in move assignment and allocator-extended construction (#4239)
Co-authored-by: Stephan T. Lavavej <stl@nuwen.net>
1 parent 2644138 commit 63aeb77

File tree

4 files changed

+136
-43
lines changed

4 files changed

+136
-43
lines changed

stl/inc/sstream

+94-40
Original file line numberDiff line numberDiff line change
@@ -66,24 +66,87 @@ public:
6666
const basic_string<_Elem, _Traits, _Alloc2>& _Str, ios_base::openmode _Mode = ios_base::in | ios_base::out)
6767
: basic_stringbuf(_Str, _Mode, _Alloc{}) {}
6868

69-
basic_stringbuf(basic_stringbuf&& _Right, const _Alloc& _Al_) : _Mystate(0), _Al(_Al_) {
70-
_Assign_rv(_STD move(_Right));
69+
basic_stringbuf(basic_stringbuf&& _Right, const _Alloc& _Al_) : _Seekhigh(nullptr), _Mystate(0), _Al(_Al_) {
70+
if constexpr (!allocator_traits<_Alloc>::is_always_equal::value) {
71+
if (_Al != _Right._Al) {
72+
_Copy_into_self_and_tidy(_STD move(_Right));
73+
return;
74+
}
75+
}
76+
77+
_Take_contents(_STD move(_Right));
7178
}
7279
#endif // _HAS_CXX20
7380

74-
basic_stringbuf(basic_stringbuf&& _Right) : _Mystate(0) {
75-
_Assign_rv(_STD move(_Right));
81+
basic_stringbuf(basic_stringbuf&& _Right)
82+
: _Seekhigh(_STD exchange(_Right._Seekhigh, nullptr)), _Mystate(_STD exchange(_Right._Mystate, 0)),
83+
_Al(_Right._Al) {
84+
_Mysb::swap(_Right);
7685
}
7786

78-
basic_stringbuf& operator=(basic_stringbuf&& _Right) noexcept /* strengthened */ {
79-
_Assign_rv(_STD move(_Right));
87+
basic_stringbuf& operator=(basic_stringbuf&& _Right) noexcept(
88+
_Choose_pocma_v<_Alloc> != _Pocma_values::_No_propagate_allocators) /* strengthened */ {
89+
if (this != _STD addressof(_Right)) {
90+
_Assign_rv_no_alias(_STD move(_Right));
91+
}
8092
return *this;
8193
}
8294

83-
void _Assign_rv(basic_stringbuf&& _Right) noexcept {
84-
if (this != _STD addressof(_Right)) {
85-
_Tidy();
86-
this->swap(_Right);
95+
void _Take_contents(basic_stringbuf&& _Right) noexcept {
96+
// pre: *this holds no dynamic buffer and _Al == _Right._Al
97+
_Seekhigh = _STD exchange(_Right._Seekhigh, nullptr);
98+
_Mystate = _STD exchange(_Right._Mystate, 0);
99+
_Mysb::swap(_Right);
100+
}
101+
102+
void _Copy_into_self_and_tidy(basic_stringbuf&& _Right) {
103+
// pre: *this holds no dynamic buffer and _Al != _Right._Al
104+
if ((_Right._Mystate & _Allocated) == 0) {
105+
return;
106+
}
107+
108+
const auto _Right_data = _Right._Mysb::eback();
109+
const auto _Right_capacity = static_cast<typename allocator_traits<_Alloc>::size_type>(
110+
(_Right._Mysb::pptr() ? _Right._Mysb::epptr() : _Right._Mysb::egptr()) - _Right_data);
111+
112+
auto _New_capacity = _Right_capacity;
113+
const auto _New_data = _STD _Unfancy(_STD _Allocate_at_least_helper(_Al, _New_capacity));
114+
115+
_Mysb::setg(_New_data, _New_data + (_Right._Mysb::gptr() - _Right_data),
116+
_New_data + (_Right._Mysb::egptr() - _Right_data));
117+
if (_Right._Mysb::pbase() != nullptr) {
118+
const auto _Pbase_diff = _Right._Mysb::pbase() - _Right_data;
119+
const auto _Pptr_diff = _Right._Mysb::pptr() - _Right_data;
120+
const auto _Epptr_diff = _Right._Mysb::epptr() - _Right_data;
121+
_Mysb::setp(_New_data + _Pbase_diff, _New_data + _Pptr_diff, _New_data + _Epptr_diff);
122+
} else {
123+
_Mysb::setp(nullptr, nullptr, nullptr);
124+
}
125+
126+
const auto _Right_view = _Right._Get_buffer_view();
127+
if (_Right_view._Ptr != nullptr) {
128+
_Traits::copy(_New_data + (_Right_view._Ptr - _Right_data), _Right_view._Ptr, _Right_view._Size);
129+
}
130+
131+
_Seekhigh = _New_data + _New_capacity;
132+
_Mystate = _Right._Mystate;
133+
134+
_Right._Tidy();
135+
}
136+
137+
void _Assign_rv_no_alias(basic_stringbuf&& _Right) noexcept(
138+
_Choose_pocma_v<_Alloc> != _Pocma_values::_No_propagate_allocators) {
139+
// pre: this != std::addressof(_Right)
140+
_Tidy();
141+
if constexpr (_Choose_pocma_v<_Alloc> == _Pocma_values::_No_propagate_allocators) {
142+
if (_Al == _Right._Al) {
143+
_Take_contents(_STD move(_Right));
144+
} else {
145+
_Copy_into_self_and_tidy(_STD move(_Right));
146+
}
147+
} else {
148+
_Pocma(_Al, _Right._Al);
149+
_Take_contents(_STD move(_Right));
87150
}
88151
}
89152

@@ -584,20 +647,17 @@ public:
584647
: _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Str, _Mode | ios_base::in) {}
585648
#endif // _HAS_CXX20
586649

587-
basic_istringstream(basic_istringstream&& _Right) : _Mybase(_STD addressof(_Stringbuffer)) {
588-
_Assign_rv(_STD move(_Right));
589-
}
650+
basic_istringstream(basic_istringstream&& _Right)
651+
: _Mybase(_STD addressof(_Stringbuffer)),
652+
_Stringbuffer((_Mybase::swap(_Right), _STD move(_Right._Stringbuffer))) {}
590653

591-
basic_istringstream& operator=(basic_istringstream&& _Right) noexcept /* strengthened */ {
592-
_Assign_rv(_STD move(_Right));
593-
return *this;
594-
}
595-
596-
void _Assign_rv(basic_istringstream&& _Right) noexcept {
654+
basic_istringstream& operator=(basic_istringstream&& _Right) noexcept(
655+
_Choose_pocma_v<_Alloc> != _Pocma_values::_No_propagate_allocators) /* strengthened */ {
597656
if (this != _STD addressof(_Right)) {
598657
_Mybase::swap(_Right);
599-
_Stringbuffer._Assign_rv(_STD move(_Right._Stringbuffer));
658+
_Stringbuffer._Assign_rv_no_alias(_STD move(_Right._Stringbuffer));
600659
}
660+
return *this;
601661
}
602662

603663
void swap(basic_istringstream& _Right) noexcept /* strengthened */ {
@@ -704,20 +764,17 @@ public:
704764
: _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Str, _Mode | ios_base::out) {}
705765
#endif // _HAS_CXX20
706766

707-
basic_ostringstream(basic_ostringstream&& _Right) : _Mybase(_STD addressof(_Stringbuffer)) {
708-
_Assign_rv(_STD move(_Right));
709-
}
767+
basic_ostringstream(basic_ostringstream&& _Right)
768+
: _Mybase(_STD addressof(_Stringbuffer)),
769+
_Stringbuffer((_Mybase::swap(_Right), _STD move(_Right._Stringbuffer))) {}
710770

711-
basic_ostringstream& operator=(basic_ostringstream&& _Right) noexcept /* strengthened */ {
712-
_Assign_rv(_STD move(_Right));
713-
return *this;
714-
}
715-
716-
void _Assign_rv(basic_ostringstream&& _Right) noexcept {
771+
basic_ostringstream& operator=(basic_ostringstream&& _Right) noexcept(
772+
_Choose_pocma_v<_Alloc> != _Pocma_values::_No_propagate_allocators) /* strengthened */ {
717773
if (this != _STD addressof(_Right)) {
718774
_Mybase::swap(_Right);
719-
_Stringbuffer._Assign_rv(_STD move(_Right._Stringbuffer));
775+
_Stringbuffer._Assign_rv_no_alias(_STD move(_Right._Stringbuffer));
720776
}
777+
return *this;
721778
}
722779

723780
void swap(basic_ostringstream& _Right) noexcept /* strengthened */ {
@@ -830,20 +887,17 @@ public:
830887
: _Mybase(_STD addressof(_Stringbuffer)), _Stringbuffer(_Str, _Mode) {}
831888
#endif // _HAS_CXX20
832889

833-
basic_stringstream(basic_stringstream&& _Right) : _Mybase(_STD addressof(_Stringbuffer)) {
834-
_Assign_rv(_STD move(_Right));
835-
}
890+
basic_stringstream(basic_stringstream&& _Right)
891+
: _Mybase(_STD addressof(_Stringbuffer)),
892+
_Stringbuffer((_Mybase::swap(_Right), _STD move(_Right._Stringbuffer))) {}
836893

837-
basic_stringstream& operator=(basic_stringstream&& _Right) noexcept /* strengthened */ {
838-
_Assign_rv(_STD move(_Right));
839-
return *this;
840-
}
841-
842-
void _Assign_rv(basic_stringstream&& _Right) noexcept {
894+
basic_stringstream& operator=(basic_stringstream&& _Right) noexcept(
895+
_Choose_pocma_v<_Alloc> != _Pocma_values::_No_propagate_allocators) /* strengthened */ {
843896
if (this != _STD addressof(_Right)) {
844897
_Mybase::swap(_Right);
845-
_Stringbuffer._Assign_rv(_STD move(_Right._Stringbuffer));
898+
_Stringbuffer._Assign_rv_no_alias(_STD move(_Right._Stringbuffer));
846899
}
900+
return *this;
847901
}
848902

849903
void swap(basic_stringstream& _Right) noexcept /* strengthened */ {

tests/libcxx/expected_results.txt

-3
Original file line numberDiff line numberDiff line change
@@ -513,9 +513,6 @@ std/algorithms/robust_against_adl.compile.pass.cpp FAIL
513513
# GH-3100: <memory> etc.: ADL should be avoided when calling _Construct_in_place and its friends
514514
std/utilities/function.objects/func.wrap/func.wrap.func/robust_against_adl.pass.cpp FAIL
515515

516-
# GH-4232: <sstream>: basic_stringbuf shouldn't implement moving with swapping
517-
std/input.output/string.streams/stringbuf/stringbuf.cons/move.alloc.pass.cpp FAIL
518-
519516
# GH-4238: <chrono>: file_clock::to_utc() overflows for file_time<nanoseconds>
520517
# This test also has a bogus assumption about the file_time epoch, and file_time<nanoseconds> is doomed on Windows.
521518
std/time/time.clock/time.clock.file/ostream.pass.cpp FAIL

tests/std/tests/GH_001858_iostream_exception/test.cpp

+25
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
#include <type_traits>
1616
#include <utility>
1717

18+
#if _HAS_CXX17
19+
#include <memory_resource>
20+
#endif // _HAS_CXX17
21+
1822
#if _HAS_CXX20
1923
#include <syncstream>
2024
#endif // _HAS_CXX20
@@ -322,6 +326,27 @@ STATIC_ASSERT(is_nothrow_move_assignable_v<wostringstream>);
322326
STATIC_ASSERT(is_nothrow_move_assignable_v<stringstream>);
323327
STATIC_ASSERT(is_nothrow_move_assignable_v<wstringstream>);
324328

329+
// GH-4232: <sstream>: basic_stringbuf shouldn't implement moving with swapping
330+
// These operations need to be able to throw exceptions when the source and target allocators are unequal.
331+
#if _HAS_CXX17
332+
STATIC_ASSERT(
333+
!is_nothrow_move_assignable_v<basic_stringbuf<char, char_traits<char>, pmr::polymorphic_allocator<char>>>);
334+
STATIC_ASSERT(
335+
!is_nothrow_move_assignable_v<basic_stringbuf<wchar_t, char_traits<wchar_t>, pmr::polymorphic_allocator<wchar_t>>>);
336+
STATIC_ASSERT(
337+
!is_nothrow_move_assignable_v<basic_istringstream<char, char_traits<char>, pmr::polymorphic_allocator<char>>>);
338+
STATIC_ASSERT(!is_nothrow_move_assignable_v<
339+
basic_istringstream<wchar_t, char_traits<wchar_t>, pmr::polymorphic_allocator<wchar_t>>>);
340+
STATIC_ASSERT(
341+
!is_nothrow_move_assignable_v<basic_ostringstream<char, char_traits<char>, pmr::polymorphic_allocator<char>>>);
342+
STATIC_ASSERT(!is_nothrow_move_assignable_v<
343+
basic_ostringstream<wchar_t, char_traits<wchar_t>, pmr::polymorphic_allocator<wchar_t>>>);
344+
STATIC_ASSERT(
345+
!is_nothrow_move_assignable_v<basic_stringstream<char, char_traits<char>, pmr::polymorphic_allocator<char>>>);
346+
STATIC_ASSERT(!is_nothrow_move_assignable_v<
347+
basic_stringstream<wchar_t, char_traits<wchar_t>, pmr::polymorphic_allocator<wchar_t>>>);
348+
#endif // _HAS_CXX17
349+
325350
STATIC_ASSERT(is_nothrow_std_swappable<stringbuf>);
326351
STATIC_ASSERT(is_nothrow_std_swappable<wstringbuf>);
327352
STATIC_ASSERT(is_nothrow_std_swappable<istringstream>);

tests/std/tests/P0408R7_efficient_access_to_stringbuf_buffer/test.cpp

+17
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <sstream>
1010
#include <string>
1111
#include <string_view>
12+
#include <type_traits>
1213
#include <utility>
1314

1415
#include <test_death.hpp>
@@ -116,6 +117,22 @@ struct test_pmr_allocator {
116117
}
117118
Stream stream2{init_value, init_value.get_allocator()};
118119
assert(stream2.rdbuf()->get_allocator() == init_value.get_allocator());
120+
121+
// GH-4232: <sstream>: basic_stringbuf shouldn't implement moving with swapping
122+
123+
// Move to another stream buffer with different allocators
124+
using SBuf = remove_pointer_t<decltype(stream.rdbuf())>;
125+
SBuf& sbuf = *stream.rdbuf();
126+
SBuf sbuf2{move(sbuf), init_value.get_allocator()};
127+
assert(sbuf2.get_allocator() != sbuf.get_allocator());
128+
assert(stream.view().empty());
129+
assert(sbuf2.view() == init_value);
130+
131+
// Move assignment between different memory resources
132+
sbuf = move(sbuf2);
133+
assert(sbuf2.get_allocator() != sbuf.get_allocator());
134+
assert(sbuf2.view().empty());
135+
assert(stream.view() == init_value);
119136
}
120137
};
121138

0 commit comments

Comments
 (0)