public inbox for libstdc++-cvs@sourceware.org
help / color / mirror / Atom feed
From: Jonathan Wakely <redi@gcc.gnu.org>
To: gcc-cvs@gcc.gnu.org, libstdc++-cvs@gcc.gnu.org
Subject: [gcc r12-10236] libstdc++: Fix std::basic_string::resize_and_overwrite
Date: Mon, 18 Mar 2024 14:04:39 +0000 (GMT)	[thread overview]
Message-ID: <20240318140439.D746B3858C5F@sourceware.org> (raw)

https://gcc.gnu.org/g:b9d09c7cfcaeb777c5cbc2848a84142a329992c9

commit r12-10236-gb9d09c7cfcaeb777c5cbc2848a84142a329992c9
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Tue Aug 15 13:48:23 2023 +0100

    libstdc++: Fix std::basic_string::resize_and_overwrite
    
    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.
    
    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.
    
    (cherry picked from commit 4a2b262597e4a6bc5732d4564673c1e19381dcfa)

Diff:
---
 libstdc++-v3/include/bits/basic_string.tcc          |  7 ++++---
 .../capacity/char/resize_and_overwrite.cc           | 21 +++++++++++++++++++++
 2 files changed, 25 insertions(+), 3 deletions(-)

diff --git a/libstdc++-v3/include/bits/basic_string.tcc b/libstdc++-v3/include/bits/basic_string.tcc
index f3911611aea..7d76cfde172 100644
--- a/libstdc++-v3/include/bits/basic_string.tcc
+++ b/libstdc++-v3/include/bits/basic_string.tcc
@@ -570,13 +570,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())
@@ -584,7 +585,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif
 	  _M_dispose();
 	  _M_data(__p);
-	  _M_capacity(__n);
+	  _M_capacity(__new_capacity);
 	}
       else
 	__p = _M_data();
diff --git a/libstdc++-v3/testsuite/21_strings/basic_string/capacity/char/resize_and_overwrite.cc b/libstdc++-v3/testsuite/21_strings/basic_string/capacity/char/resize_and_overwrite.cc
index f716030dad7..0ea5e2b10ef 100644
--- a/libstdc++-v3/testsuite/21_strings/basic_string/capacity/char/resize_and_overwrite.cc
+++ b/libstdc++-v3/testsuite/21_strings/basic_string/capacity/char/resize_and_overwrite.cc
@@ -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();
 }

                 reply	other threads:[~2024-03-18 14:04 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20240318140439.D746B3858C5F@sourceware.org \
    --to=redi@gcc.gnu.org \
    --cc=gcc-cvs@gcc.gnu.org \
    --cc=libstdc++-cvs@gcc.gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).