diff --git a/libcxx/docs/ImplementationDefinedBehavior.rst b/libcxx/docs/ImplementationDefinedBehavior.rst index f0ef733fc2c5..1f95de77db0e 100644 --- a/libcxx/docs/ImplementationDefinedBehavior.rst +++ b/libcxx/docs/ImplementationDefinedBehavior.rst @@ -62,6 +62,14 @@ E.g. - ``std::hermite(unsigned n, T x)`` for ``n >= 128`` +`[stringbuf.cons] `_ Whether sequence pointers are initialized to null pointers +---------------------------------------------------------------------------------------------------------------------- + +Libc++ does not initialize the pointers to null pointers. It resizes the buffer +to its capacity and uses that size. This means the SSO buffer of +``std::string`` is used as initial output buffer. + + Listed in the index of implementation-defined behavior ====================================================== diff --git a/libcxx/docs/Status/Cxx20Issues.csv b/libcxx/docs/Status/Cxx20Issues.csv index 97ecf5e8e05a..1b76b0c5acff 100644 --- a/libcxx/docs/Status/Cxx20Issues.csv +++ b/libcxx/docs/Status/Cxx20Issues.csv @@ -101,7 +101,7 @@ "`2936 `__","Path comparison is defined in terms of the generic format","San Diego","|Complete|","" "`2943 `__","Problematic specification of the wide version of ``basic_filebuf::open``\ ","San Diego","|Nothing To Do|","" "`2960 `__","[fund.ts.v3] ``nonesuch``\ is insufficiently useless","San Diego","|Complete|","" -"`2995 `__","``basic_stringbuf``\ default constructor forbids it from using SSO capacity","San Diego","","" +"`2995 `__","``basic_stringbuf``\ default constructor forbids it from using SSO capacity","San Diego","|Complete|","20.0" "`2996 `__","Missing rvalue overloads for ``shared_ptr``\ operations","San Diego","|Complete|","17.0" "`3008 `__","``make_shared``\ (sub)object destruction semantics are not specified","San Diego","|Complete|","16.0" "`3022 `__","``is_convertible``\ may lead to ODR","San Diego","Resolved by `P1285R0 `__","" diff --git a/libcxx/include/sstream b/libcxx/include/sstream index 9ba43ffeb850..272d8861d59c 100644 --- a/libcxx/include/sstream +++ b/libcxx/include/sstream @@ -354,9 +354,15 @@ private: public: // [stringbuf.cons] constructors: - _LIBCPP_HIDE_FROM_ABI basic_stringbuf() : __hm_(nullptr), __mode_(ios_base::in | ios_base::out) {} + _LIBCPP_HIDE_FROM_ABI basic_stringbuf() : __hm_(nullptr), __mode_(ios_base::in | ios_base::out) { + // it is implementation-defined whether we initialize eback() & friends to nullptr, and libc++ doesn't + __init_buf_ptrs(); + } - _LIBCPP_HIDE_FROM_ABI explicit basic_stringbuf(ios_base::openmode __wch) : __hm_(nullptr), __mode_(__wch) {} + _LIBCPP_HIDE_FROM_ABI explicit basic_stringbuf(ios_base::openmode __wch) : __hm_(nullptr), __mode_(__wch) { + // it is implementation-defined whether we initialize eback() & friends to nullptr, and libc++ doesn't + __init_buf_ptrs(); + } _LIBCPP_HIDE_FROM_ABI explicit basic_stringbuf(const string_type& __s, ios_base::openmode __wch = ios_base::in | ios_base::out) @@ -369,7 +375,9 @@ public: : basic_stringbuf(ios_base::in | ios_base::out, __a) {} _LIBCPP_HIDE_FROM_ABI basic_stringbuf(ios_base::openmode __wch, const allocator_type& __a) - : __str_(__a), __hm_(nullptr), __mode_(__wch) {} + : __str_(__a), __hm_(nullptr), __mode_(__wch) { + __init_buf_ptrs(); + } _LIBCPP_HIDE_FROM_ABI explicit basic_stringbuf(string_type&& __s, ios_base::openmode __wch = ios_base::in | ios_base::out) diff --git a/libcxx/test/libcxx/input.output/string.streams/stringbuf/const_sso_buffer.pass.cpp b/libcxx/test/libcxx/input.output/string.streams/stringbuf/const_sso_buffer.pass.cpp new file mode 100644 index 000000000000..d6caa3389b8f --- /dev/null +++ b/libcxx/test/libcxx/input.output/string.streams/stringbuf/const_sso_buffer.pass.cpp @@ -0,0 +1,169 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// + +// How the constructors of basic_stringbuf initialize the buffer pointers is +// not specified. For some constructors it's implementation defined whether the +// pointers are set to nullptr. Libc++'s implementation directly uses the SSO +// buffer of a std::string as the initial size. This test validates that +// behaviour. +// +// This behaviour is allowed by LWG2995. + +#include +#include + +#include "test_macros.h" +#include "min_allocator.h" + +template +struct test_buf : public std::basic_stringbuf { + typedef std::basic_streambuf base; + typedef typename base::char_type char_type; + typedef typename base::int_type int_type; + typedef typename base::traits_type traits_type; + + char_type* pbase() const { return base::pbase(); } + char_type* pptr() const { return base::pptr(); } + char_type* epptr() const { return base::epptr(); } + void gbump(int n) { base::gbump(n); } + + virtual int_type overflow(int_type c = traits_type::eof()) { return base::overflow(c); } + + test_buf() = default; + explicit test_buf(std::ios_base::openmode which) : std::basic_stringbuf(which) {} + + explicit test_buf(const std::basic_string& s) : std::basic_stringbuf(s) {} +#if TEST_STD_VER >= 20 + explicit test_buf(const std::allocator& a) : std::basic_stringbuf(a) {} + test_buf(std::ios_base::openmode which, const std::allocator& a) : std::basic_stringbuf(which, a) {} + explicit test_buf(std::basic_string&& s) + : std::basic_stringbuf(std::forward>(s)) {} + + test_buf(const std::basic_string, min_allocator>& s, + const std::allocator& a) + : std::basic_stringbuf(s, a) {} + test_buf(const std::basic_string, min_allocator>& s, + std::ios_base::openmode which, + const std::allocator& a) + : std::basic_stringbuf(s, which, a) {} + test_buf(const std::basic_string, min_allocator>& s) + : std::basic_stringbuf(s) {} +#endif // TEST_STD_VER >= 20 + +#if TEST_STD_VER >= 26 + test_buf(std::basic_string_view s) : std::basic_stringbuf(s) {} + test_buf(std::basic_string_view s, const std::allocator& a) : std::basic_stringbuf(s, a) {} + test_buf(std::basic_string_view s, std::ios_base::openmode which, const std::allocator& a) + : std::basic_stringbuf(s, which, a) {} +#endif // TEST_STD_VER >= 26 +}; + +template +static void test() { + std::size_t size = std::basic_string().capacity(); // SSO buffer size. + { + test_buf b; + assert(b.pbase() != nullptr); + assert(b.pptr() == b.pbase()); + assert(b.epptr() == b.pbase() + size); + } + { + test_buf b(std::ios_base::out); + assert(b.pbase() != nullptr); + assert(b.pptr() == b.pbase()); + assert(b.epptr() == b.pbase() + size); + } + { + std::basic_string s; + s.reserve(1024); + test_buf b(s); + assert(b.pbase() != nullptr); + assert(b.pptr() == b.pbase()); + assert(b.epptr() == b.pbase() + size); // copy so uses size + } +#if TEST_STD_VER >= 20 + { + test_buf b = test_buf(std::allocator()); + assert(b.pbase() != nullptr); + assert(b.pptr() == b.pbase()); + assert(b.epptr() == b.pbase() + size); + } + { + test_buf b = test_buf(std::ios_base::out, std::allocator()); + assert(b.pbase() != nullptr); + assert(b.pptr() == b.pbase()); + assert(b.epptr() == b.pbase() + size); + } + { + std::basic_string s; + s.reserve(1024); + std::size_t capacity = s.capacity(); + test_buf b = test_buf(std::move(s)); + assert(b.pbase() != nullptr); + assert(b.pptr() == b.pbase()); + assert(b.epptr() >= b.pbase() + capacity); // move so uses s.capacity() + } + { + std::basic_string, min_allocator> s; + s.reserve(1024); + test_buf b = test_buf(s, std::allocator()); + assert(b.pbase() != nullptr); + assert(b.pptr() == b.pbase()); + assert(b.epptr() == b.pbase() + size); // copy so uses size + } + { + std::basic_string, min_allocator> s; + s.reserve(1024); + test_buf b = test_buf(s, std::ios_base::out, std::allocator()); + assert(b.pbase() != nullptr); + assert(b.pptr() == b.pbase()); + assert(b.epptr() == b.pbase() + size); // copy so uses size + } + { + std::basic_string, min_allocator> s; + s.reserve(1024); + test_buf b = test_buf(s); + assert(b.pbase() != nullptr); + assert(b.pptr() == b.pbase()); + assert(b.epptr() == b.pbase() + size); // copy so uses size + } +#endif // TEST_STD_VER >= 20 +#if TEST_STD_VER >= 26 + { + std::basic_string_view s; + test_buf b = test_buf(s); + assert(b.pbase() != nullptr); + assert(b.pptr() == b.pbase()); + assert(b.epptr() == b.pbase() + size); + } + { + std::basic_string_view s; + test_buf b = test_buf(s, std::allocator()); + assert(b.pbase() != nullptr); + assert(b.pptr() == b.pbase()); + assert(b.epptr() == b.pbase() + size); + } + { + std::basic_string_view s; + test_buf b = test_buf(s, std::ios_base::out, std::allocator()); + assert(b.pbase() != nullptr); + assert(b.pptr() == b.pbase()); + assert(b.epptr() == b.pbase() + size); + } +#endif // TEST_STD_VER >= 26 +} + +int main(int, char**) { + test(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif + return 0; +} diff --git a/libcxx/test/std/input.output/string.streams/stringbuf/stringbuf.cons/default.pass.cpp b/libcxx/test/std/input.output/string.streams/stringbuf/stringbuf.cons/default.pass.cpp index 6451d44e91b5..d131f5c9a6fa 100644 --- a/libcxx/test/std/input.output/string.streams/stringbuf/stringbuf.cons/default.pass.cpp +++ b/libcxx/test/std/input.output/string.streams/stringbuf/stringbuf.cons/default.pass.cpp @@ -29,12 +29,18 @@ struct testbuf { void check() { - assert(this->eback() == NULL); - assert(this->gptr() == NULL); - assert(this->egptr() == NULL); - assert(this->pbase() == NULL); - assert(this->pptr() == NULL); - assert(this->epptr() == NULL); + // LWG2995 + // It is implementation-defined whether the sequence pointers (eback(), + // gptr(), egptr(), pbase(), pptr(), epptr()) are initialized to null + // pointers. + // This tests the libc++ specific implementation. + LIBCPP_ASSERT(this->eback() != nullptr); + LIBCPP_ASSERT(this->gptr() != nullptr); + LIBCPP_ASSERT(this->egptr() != nullptr); + LIBCPP_ASSERT(this->pbase() != nullptr); + LIBCPP_ASSERT(this->pptr() != nullptr); + LIBCPP_ASSERT(this->epptr() != nullptr); + assert(this->str().empty()); } };