Skip to content

Commit

Permalink
[libc++][stringbuf] Test and document LWG2995. (llvm#100879)
Browse files Browse the repository at this point in the history
As mentioned in the LWG issue libc++ has already implemented the
optimization. This adds tests and documents the implementation defined
behaviour.

Drive-by fixes an initialization.
  • Loading branch information
mordante authored Aug 1, 2024
1 parent 4e89d11 commit d5a6ec1
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 10 deletions.
8 changes: 8 additions & 0 deletions libcxx/docs/ImplementationDefinedBehavior.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ E.g.
- ``std::hermite(unsigned n, T x)`` for ``n >= 128``


`[stringbuf.cons] <http://eel.is/c++draft/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
======================================================

Expand Down
2 changes: 1 addition & 1 deletion libcxx/docs/Status/Cxx20Issues.csv
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
"`2936 <https://wg21.link/LWG2936>`__","Path comparison is defined in terms of the generic format","San Diego","|Complete|",""
"`2943 <https://wg21.link/LWG2943>`__","Problematic specification of the wide version of ``basic_filebuf::open``\ ","San Diego","|Nothing To Do|",""
"`2960 <https://wg21.link/LWG2960>`__","[fund.ts.v3] ``nonesuch``\ is insufficiently useless","San Diego","|Complete|",""
"`2995 <https://wg21.link/LWG2995>`__","``basic_stringbuf``\ default constructor forbids it from using SSO capacity","San Diego","",""
"`2995 <https://wg21.link/LWG2995>`__","``basic_stringbuf``\ default constructor forbids it from using SSO capacity","San Diego","|Complete|","20.0"
"`2996 <https://wg21.link/LWG2996>`__","Missing rvalue overloads for ``shared_ptr``\ operations","San Diego","|Complete|","17.0"
"`3008 <https://wg21.link/LWG3008>`__","``make_shared``\ (sub)object destruction semantics are not specified","San Diego","|Complete|","16.0"
"`3022 <https://wg21.link/LWG3022>`__","``is_convertible<derived*, base*>``\ may lead to ODR","San Diego","Resolved by `P1285R0 <https://wg21.link/P1285R0>`__",""
Expand Down
14 changes: 11 additions & 3 deletions libcxx/include/sstream
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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
//
//===----------------------------------------------------------------------===//

// <sstream>

// 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 <sstream>
#include <cassert>

#include "test_macros.h"
#include "min_allocator.h"

template <class CharT>
struct test_buf : public std::basic_stringbuf<CharT> {
typedef std::basic_streambuf<CharT> 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<CharT>(which) {}

explicit test_buf(const std::basic_string<CharT>& s) : std::basic_stringbuf<CharT>(s) {}
#if TEST_STD_VER >= 20
explicit test_buf(const std::allocator<CharT>& a) : std::basic_stringbuf<CharT>(a) {}
test_buf(std::ios_base::openmode which, const std::allocator<CharT>& a) : std::basic_stringbuf<CharT>(which, a) {}
explicit test_buf(std::basic_string<CharT>&& s)
: std::basic_stringbuf<CharT>(std::forward<std::basic_string<CharT>>(s)) {}

test_buf(const std::basic_string<CharT, std::char_traits<CharT>, min_allocator<CharT>>& s,
const std::allocator<CharT>& a)
: std::basic_stringbuf<CharT>(s, a) {}
test_buf(const std::basic_string<CharT, std::char_traits<CharT>, min_allocator<CharT>>& s,
std::ios_base::openmode which,
const std::allocator<CharT>& a)
: std::basic_stringbuf<CharT>(s, which, a) {}
test_buf(const std::basic_string<CharT, std::char_traits<CharT>, min_allocator<CharT>>& s)
: std::basic_stringbuf<CharT>(s) {}
#endif // TEST_STD_VER >= 20

#if TEST_STD_VER >= 26
test_buf(std::basic_string_view<CharT> s) : std::basic_stringbuf<CharT>(s) {}
test_buf(std::basic_string_view<CharT> s, const std::allocator<CharT>& a) : std::basic_stringbuf<CharT>(s, a) {}
test_buf(std::basic_string_view<CharT> s, std::ios_base::openmode which, const std::allocator<CharT>& a)
: std::basic_stringbuf<CharT>(s, which, a) {}
#endif // TEST_STD_VER >= 26
};

template <class CharT>
static void test() {
std::size_t size = std::basic_string<CharT>().capacity(); // SSO buffer size.
{
test_buf<CharT> b;
assert(b.pbase() != nullptr);
assert(b.pptr() == b.pbase());
assert(b.epptr() == b.pbase() + size);
}
{
test_buf<CharT> b(std::ios_base::out);
assert(b.pbase() != nullptr);
assert(b.pptr() == b.pbase());
assert(b.epptr() == b.pbase() + size);
}
{
std::basic_string<CharT> s;
s.reserve(1024);
test_buf<CharT> 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<CharT> b = test_buf<CharT>(std::allocator<CharT>());
assert(b.pbase() != nullptr);
assert(b.pptr() == b.pbase());
assert(b.epptr() == b.pbase() + size);
}
{
test_buf<CharT> b = test_buf<CharT>(std::ios_base::out, std::allocator<CharT>());
assert(b.pbase() != nullptr);
assert(b.pptr() == b.pbase());
assert(b.epptr() == b.pbase() + size);
}
{
std::basic_string<CharT> s;
s.reserve(1024);
std::size_t capacity = s.capacity();
test_buf<CharT> b = test_buf<CharT>(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<CharT, std::char_traits<CharT>, min_allocator<CharT>> s;
s.reserve(1024);
test_buf<CharT> b = test_buf<CharT>(s, std::allocator<CharT>());
assert(b.pbase() != nullptr);
assert(b.pptr() == b.pbase());
assert(b.epptr() == b.pbase() + size); // copy so uses size
}
{
std::basic_string<CharT, std::char_traits<CharT>, min_allocator<CharT>> s;
s.reserve(1024);
test_buf<CharT> b = test_buf<CharT>(s, std::ios_base::out, std::allocator<CharT>());
assert(b.pbase() != nullptr);
assert(b.pptr() == b.pbase());
assert(b.epptr() == b.pbase() + size); // copy so uses size
}
{
std::basic_string<CharT, std::char_traits<CharT>, min_allocator<CharT>> s;
s.reserve(1024);
test_buf<CharT> b = test_buf<CharT>(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<CharT> s;
test_buf<CharT> b = test_buf<CharT>(s);
assert(b.pbase() != nullptr);
assert(b.pptr() == b.pbase());
assert(b.epptr() == b.pbase() + size);
}
{
std::basic_string_view<CharT> s;
test_buf<CharT> b = test_buf<CharT>(s, std::allocator<CharT>());
assert(b.pbase() != nullptr);
assert(b.pptr() == b.pbase());
assert(b.epptr() == b.pbase() + size);
}
{
std::basic_string_view<CharT> s;
test_buf<CharT> b = test_buf<CharT>(s, std::ios_base::out, std::allocator<CharT>());
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<char>();
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
test<wchar_t>();
#endif
return 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
};

Expand Down

0 comments on commit d5a6ec1

Please sign in to comment.