]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Fix std::basic_string::resize_and_overwrite
authorJonathan Wakely <jwakely@redhat.com>
Tue, 15 Aug 2023 12:48:23 +0000 (13:48 +0100)
committerJonathan Wakely <jwakely@redhat.com>
Wed, 16 Aug 2023 17:36:37 +0000 (18:36 +0100)
The callable used for resize_and_overwrite was being passed the string's
expanded capacity, which might be greater than the new size being
requested. This is not conforming, as the standard requires the same n
to be passed to the callable that the user passed to
resize_and_overwrite.

The existing tests didn't catch this because they all used a value which
was more than twice the existing capacity, so the _M_create call
allocated exactly what was requested, and the value passed to the
callable was correct. But when the requested size is greater than the
current capacity but smaller than twice the current capacity, _M_create
will allocate twice the current capacity and then that value was being
passed to the callable.

I noticed this because std::format(L"{}", 0.25) was producing L"0.25XX"
where the XX characters were whatever happened to be on the stack before
the call. When std::format used resize_and_overwrite to widen a string
it was copying too many characters into the destination and setting the
result's length too long. I've added a test for this case, and a new
test that doesn't hardcode -std=gnu++20 so can be used to test
std::format in C++23 and C++26 modes.

libstdc++-v3/ChangeLog:

* include/bits/basic_string.tcc (resize_and_overwrite): Invoke
the callable with the same size as resize_and_overwrite was
called with.
* testsuite/21_strings/basic_string/capacity/char/resize_and_overwrite.cc:
Check with small values for the new size.
* testsuite/std/format/functions/format.cc: Check wide
formatting of double values that produce small strings.
* testsuite/std/format/functions/format_c++23.cc: New test.

libstdc++-v3/include/bits/basic_string.tcc
libstdc++-v3/testsuite/21_strings/basic_string/capacity/char/resize_and_overwrite.cc
libstdc++-v3/testsuite/std/format/functions/format.cc
libstdc++-v3/testsuite/std/format/functions/format_c++23.cc [new file with mode: 0644]

index d8a279fc9edbe0a7c2239c75637717f9fc0cb451..c759c2f9525270b136bf3d4d9f61e35fe7cbabbc 100644 (file)
@@ -566,13 +566,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   template<typename _Operation>
     constexpr void
     basic_string<_CharT, _Traits, _Alloc>::
-    resize_and_overwrite(size_type __n, _Operation __op)
+    resize_and_overwrite(const size_type __n, _Operation __op)
     {
       const size_type __capacity = capacity();
       _CharT* __p;
       if (__n > __capacity)
        {
-         __p = _M_create(__n, __capacity);
+         auto __new_capacity = __n; // Must not allow _M_create to modify __n.
+         __p = _M_create(__new_capacity, __capacity);
          this->_S_copy(__p, _M_data(), length()); // exclude trailing null
 #if __cpp_lib_is_constant_evaluated
          if (std::is_constant_evaluated())
@@ -580,7 +581,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif
          _M_dispose();
          _M_data(__p);
-         _M_capacity(__n);
+         _M_capacity(__new_capacity);
        }
       else
        __p = _M_data();
index f716030dad7f5e8a647bdb0e1edcff0a372ff2d2..0ea5e2b10ef6722a42a474a3012e884974be4026 100644 (file)
@@ -120,6 +120,26 @@ test05()
   return true;
 }
 
+void
+test06()
+{
+  std::string s = "0123456789";
+  s.resize_and_overwrite(16, [](char* p, int n) {
+    VERIFY( n == 16 );
+    std::char_traits<char>::copy(p + 10, "0123456798", 6);
+    return n;
+  });
+  VERIFY( s.size() == 16 );
+  VERIFY( s == "0123456789012345" );
+
+  s.resize_and_overwrite(4, [](char* p, int n) {
+    VERIFY( n == 4 );
+    std::char_traits<char>::copy(p, "abcd", 4);
+    return n;
+  });
+  VERIFY( s.size() == 4 );
+}
+
 int main()
 {
   test01();
@@ -127,4 +147,5 @@ int main()
   test03();
   test04();
   static_assert( test05() );
+  test06();
 }
index 471cffb2b36883f064c5a1214e302fcdd8b9a8d4..a8d5b652a5eb60e3480c73c686946e8264762f04 100644 (file)
@@ -256,6 +256,11 @@ test_wchar()
   std::locale loc;
   s = std::format(loc, L"{:L} {:.3s}{:Lc}", true, L"data"sv, '.');
   VERIFY( s == L"true dat." );
+
+  s = std::format(L"{}", 0.0625);
+  VERIFY( s == L"0.0625" );
+  s = std::format(L"{}", 0.25);
+  VERIFY( s == L"0.25" );
 }
 
 void
diff --git a/libstdc++-v3/testsuite/std/format/functions/format_c++23.cc b/libstdc++-v3/testsuite/std/format/functions/format_c++23.cc
new file mode 100644 (file)
index 0000000..f20c46c
--- /dev/null
@@ -0,0 +1,4 @@
+// { dg-do run { target c++23 } }
+// This test does not have -std=gnu++20 in dg-options so that format.cc
+// can be tested for e.g. -std=c++26
+#include "format.cc"