On Mon, 12 Oct 2026 at 09:22 -0400, Nathan Myers wrote:
acfdad706d8 added a basic_string member _M_create_plus to use
when allocating string storage, but retained the old _M_create
symbol so that object files built against older releases would
still find it. However, code built into some old object files has
a latent bug that assumes _M_create will, if the old capacity is
It's not a bug, see below.
zero, allocate only as many bytes as requested, something it no
longer does.
This patch restores _M_create's exact previous behavior, to
rescue those old object files.
libstdc++-v3/ChangeLog:
PR libstdc++/127228
* include/bits/basic_string.tcc (_M_create): Restore old version.
* include/bits/basic_string.h (__expose_old_M_create):
Add test instrumentation.
* testsuite/21_strings/basic_string/127228.cc: New test.
---
libstdc++-v3/include/bits/basic_string.h | 5 +++++
libstdc++-v3/include/bits/basic_string.tcc | 12 ++++++++---
.../21_strings/basic_string/127228.cc | 20 +++++++++++++++++++
3 files changed, 34 insertions(+), 3 deletions(-)
create mode 100644 libstdc++-v3/testsuite/21_strings/basic_string/127228.cc
diff --git a/libstdc++-v3/include/bits/basic_string.h
b/libstdc++-v3/include/bits/basic_string.h
index fd9419220a4..ba9a35e7ffd 100644
--- a/libstdc++-v3/include/bits/basic_string.h
+++ b/libstdc++-v3/include/bits/basic_string.h
@@ -134,6 +134,11 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
typedef const_iterator __const_iterator;
#endif
+#ifdef __TEST_PR127228
This macro is not in the _GLIBCXX_ namespace.
+ size_type __expose_old_M_create(size_type __sz)
Do we need a separate function for this? Why not just make
_M_create public when _GLIBCXX_TEST_PR127288 is defined?
Or better yet, compile the test with -fno-access-control via a
dg-options directive. Then you don't need any new function or special
macro.
+ { return _M_create(__sz, 0), __sz; }
+#endif
+
private:
// For ABI reasons this must remain, though unused.
static _GLIBCXX20_CONSTEXPR pointer
diff --git a/libstdc++-v3/include/bits/basic_string.tcc
b/libstdc++-v3/include/bits/basic_string.tcc
index 64c99fbb9d6..de82e997888 100644
--- a/libstdc++-v3/include/bits/basic_string.tcc
+++ b/libstdc++-v3/include/bits/basic_string.tcc
@@ -171,9 +171,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
basic_string<_CharT, _Traits, _Alloc>::
_M_create(size_type& __capacity, size_type __old_capacity)
{
- _Alloc_result __r = _M_create_plus(__capacity, __old_capacity);
- __capacity = __r.__count - 1; // Leave room for NUL.
- return __r.__ptr;
+ if (__capacity > max_size())
+ std::__throw_length_error(__N("basic_string::_M_create"));
+ if (__capacity > __old_capacity && __capacity < 2 * __old_capacity)
+ {
+ __capacity = 2 * __old_capacity;
+ if (__capacity > max_size())
+ __capacity = max_size();
+ }
+ return _S_allocate(_M_get_allocator(), __capacity + 1);
}
// NB: This is the special case for Input Iterators, used in
diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/127228.cc
b/libstdc++-v3/testsuite/21_strings/basic_string/127228.cc
new file mode 100644
index 00000000000..8738ae41192
--- /dev/null
+++ b/libstdc++-v3/testsuite/21_strings/basic_string/127228.cc
@@ -0,0 +1,20 @@
+// { dg-do run { target c++23 } }
+
+// libstdc++/127228
+// Object files built against older libstdc++ still call
+// _M_create, and in some cases (incorrectly) rely on it to
+// allocate, and report allocating, no more storage than was
+// asked for.
Why "incorrectly"? That was the function's interface. The two calls to
_M_create(x, size_type(0)) are relying on that behaviour, because
that was the function's behaviour.
I think it's only the calls with 0 as the second argument that rely on
this, so the commit msg and this comment could be more precise (and
lose the judgment that they were incorrect and had a latent bug - the
code was working exactly as intended).
+
+#define _TEST_PR127228
+#include <string>
+#include <testsuite_hooks.h>
+
+struct T : public std::string
+{ using std::string::__expose_old_M_create; };
+
+int main()
+{
+ T s;
+ VERIFY(s.__expose_old_M_create(25) == 25);
Please test calling s._M_create(25, size_type(0)) since that's the
exact form that the affected callers use.
I'd also like to see a check with a new capacity that's less than the
size of the SSO buffer. We should verify that _M_create(2, 0) and
_M_create(25, 0) both work correctly.
+}
--
2.55.0