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



Reply via email to