[libcxx-commits] [libcxx] [libc++] Assert that the resize_and_overwrite operation returns a size in [0, n] (PR #230605)

Ilyas Mallah via libcxx-commits libcxx-commits at lists.llvm.org
Tue Oct 13 03:28:18 PDT 2026


https://github.com/ilyas-mallah updated https://github.com/llvm/llvm-project/pull/230605

>From 60e9f009b948a9a258e7cbe123d2a7438a3157ff Mon Sep 17 00:00:00 2001
From: Ilyas Mallah <github at ilyasm.dev>
Date: Fri, 9 Oct 2026 16:03:41 +0200
Subject: [PATCH 1/4] [libc++] Assert that the resize_and_overwrite operation
 returns a size in [0, n]

---
 libcxx/include/string                         | 12 +++-
 .../assert.resize_and_overwrite.pass.cpp      | 71 +++++++++++++++++++
 2 files changed, 82 insertions(+), 1 deletion(-)
 create mode 100644 libcxx/test/libcxx/strings/basic.string/string.capacity/assert.resize_and_overwrite.pass.cpp

diff --git a/libcxx/include/string b/libcxx/include/string
index a27f9344916d1..fcd27483e1396 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -639,9 +639,11 @@ basic_string<char32_t> operator""s( const char32_t *str, size_t len );
 #  include <__type_traits/is_nothrow_constructible.h>
 #  include <__type_traits/is_relocatable.h>
 #  include <__type_traits/is_same.h>
+#  include <__type_traits/is_signed.h>
 #  include <__type_traits/is_standard_layout.h>
 #  include <__type_traits/is_trivially_constructible.h>
 #  include <__type_traits/is_trivially_copyable.h>
+#  include <__type_traits/make_unsigned.h>
 #  include <__type_traits/remove_cv.h>
 #  include <__type_traits/remove_cvref.h>
 #  include <__utility/assume.h>
@@ -1250,7 +1252,15 @@ public:
     __annotate_delete();
     __set_size(__n);
     __annotate_new(__n);
-    __erase_to_end(std::move(__op)(data(), auto(__n)));
+    __result_type __r = std::move(__op)(data(), auto(__n));
+    if constexpr (__integer_like<__result_type>) { // Avoid extra errors after a failed static_assert
+      if constexpr (is_signed_v<__result_type>)
+        _LIBCPP_ASSERT_VALID_INPUT_RANGE(
+            __r >= 0, "string::resize_and_overwrite: the operation returned a negative size");
+      _LIBCPP_ASSERT_VALID_INPUT_RANGE(std::__to_unsigned_like(__r) <= __n,
+                                       "string::resize_and_overwrite: the operation returned a size larger than n");
+    }
+    __erase_to_end(__r);
   }
 #  endif
 
diff --git a/libcxx/test/libcxx/strings/basic.string/string.capacity/assert.resize_and_overwrite.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.capacity/assert.resize_and_overwrite.pass.cpp
new file mode 100644
index 0000000000000..35d901f8da3bd
--- /dev/null
+++ b/libcxx/test/libcxx/strings/basic.string/string.capacity/assert.resize_and_overwrite.pass.cpp
@@ -0,0 +1,71 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// <string>
+
+// template<class Operation>
+// constexpr void resize_and_overwrite(size_type n, Operation op); // since C++23
+
+// The result r of the operation must satisfy 0 <= r <= n.
+
+// REQUIRES: std-at-least-c++23
+// REQUIRES: can-test-hardening-assertions-fast
+// Execution would continue into an out-of-bounds write.
+// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
+
+#include <cstddef>
+#include <string>
+
+#include "check_assertion.h"
+#include "min_allocator.h"
+#include "test_macros.h"
+
+template <class S>
+void test() {
+  {
+    S s;
+    TEST_LIBCPP_ASSERT_FAILURE(s.resize_and_overwrite(5, [](auto*, std::size_t n) { return n + 1; }),
+                               "string::resize_and_overwrite: the operation returned a size larger than n");
+  }
+  {
+    S s(40, 'x');
+    TEST_LIBCPP_ASSERT_FAILURE(s.resize_and_overwrite(40, [](auto*, std::size_t n) { return n + 4096; }),
+                               "string::resize_and_overwrite: the operation returned a size larger than n");
+  }
+  {
+    S s(40, 'x');
+    TEST_LIBCPP_ASSERT_FAILURE(s.resize_and_overwrite(40, [](auto*, std::size_t) { return -1; }),
+                               "string::resize_and_overwrite: the operation returned a negative size");
+  }
+  {
+    // A narrow signed result must not be compared as its unsigned counterpart, where -1 would be 255.
+    S s;
+    TEST_LIBCPP_ASSERT_FAILURE(
+        s.resize_and_overwrite(300, [](auto*, std::size_t) { return static_cast<signed char>(-1); }),
+        "string::resize_and_overwrite: the operation returned a negative size");
+  }
+#ifndef TEST_HAS_NO_INT128
+  {
+    // A wide result must not be truncated to size_type before the comparison, where 2^64 + 1 would be 1.
+    S s;
+    TEST_LIBCPP_ASSERT_FAILURE(
+        s.resize_and_overwrite(5, [](auto*, std::size_t) { return (static_cast<__int128_t>(1) << 64) + 1; }),
+        "string::resize_and_overwrite: the operation returned a size larger than n");
+  }
+#endif
+}
+
+int main(int, char**) {
+  test<std::string>();
+  test<std::basic_string<char, std::char_traits<char>, min_allocator<char> > >();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test<std::wstring>();
+#endif
+
+  return 0;
+}

>From 5e165df2afa18bc1595d6db11f37411df04dbde9 Mon Sep 17 00:00:00 2001
From: Ilyas Mallah <github at ilyasm.dev>
Date: Fri, 9 Oct 2026 21:21:21 +0200
Subject: [PATCH 2/4] [libc++] Reorder test file headers

---
 .../assert.resize_and_overwrite.pass.cpp               | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/libcxx/test/libcxx/strings/basic.string/string.capacity/assert.resize_and_overwrite.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.capacity/assert.resize_and_overwrite.pass.cpp
index 35d901f8da3bd..563f70f00a2a6 100644
--- a/libcxx/test/libcxx/strings/basic.string/string.capacity/assert.resize_and_overwrite.pass.cpp
+++ b/libcxx/test/libcxx/strings/basic.string/string.capacity/assert.resize_and_overwrite.pass.cpp
@@ -6,6 +6,11 @@
 //
 //===----------------------------------------------------------------------===//
 
+// REQUIRES: std-at-least-c++23
+// REQUIRES: can-test-hardening-assertions-fast
+// Execution would continue into an out-of-bounds write.
+// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
+
 // <string>
 
 // template<class Operation>
@@ -13,11 +18,6 @@
 
 // The result r of the operation must satisfy 0 <= r <= n.
 
-// REQUIRES: std-at-least-c++23
-// REQUIRES: can-test-hardening-assertions-fast
-// Execution would continue into an out-of-bounds write.
-// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
-
 #include <cstddef>
 #include <string>
 

>From 8a2c04bb3319858b2aa398a3946b17c3f10393e3 Mon Sep 17 00:00:00 2001
From: Ilyas Mallah <github at ilyasm.dev>
Date: Tue, 13 Oct 2026 12:01:18 +0200
Subject: [PATCH 3/4] [libc++] Remove the guard and expect the follow-on errors
 in the verify test

---
 libcxx/include/string                                | 12 +++++-------
 .../string.capacity/resize_and_overwrite.verify.cpp  |  5 +++++
 2 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/libcxx/include/string b/libcxx/include/string
index fcd27483e1396..6a672e89057da 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -1253,13 +1253,11 @@ public:
     __set_size(__n);
     __annotate_new(__n);
     __result_type __r = std::move(__op)(data(), auto(__n));
-    if constexpr (__integer_like<__result_type>) { // Avoid extra errors after a failed static_assert
-      if constexpr (is_signed_v<__result_type>)
-        _LIBCPP_ASSERT_VALID_INPUT_RANGE(
-            __r >= 0, "string::resize_and_overwrite: the operation returned a negative size");
-      _LIBCPP_ASSERT_VALID_INPUT_RANGE(std::__to_unsigned_like(__r) <= __n,
-                                       "string::resize_and_overwrite: the operation returned a size larger than n");
-    }
+    if constexpr (is_signed_v<__result_type>)
+      _LIBCPP_ASSERT_VALID_INPUT_RANGE(
+          __r >= 0, "string::resize_and_overwrite: the operation returned a negative size");
+    _LIBCPP_ASSERT_VALID_INPUT_RANGE(std::__to_unsigned_like(__r) <= __n,
+                                     "string::resize_and_overwrite: the operation returned a size larger than n");
     __erase_to_end(__r);
   }
 #  endif
diff --git a/libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.verify.cpp b/libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.verify.cpp
index 323f49b72c2c8..8d916dcd97f0b 100644
--- a/libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.verify.cpp
+++ b/libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.verify.cpp
@@ -15,12 +15,15 @@
 
 // Verify that the operation's return type must be integer-like
 
+// When hardening is enabled, the checks on the result add an error after the static_assert.
+
 #include <string>
 
 void test_bool_return_type() {
   std::string s;
   s.resize_and_overwrite(10, [](char*, std::size_t) {
     return true; // expected-error-re@*:* {{{{(static_assertion|static assertion)}}{{.*}}integer-like}}
+                 // expected-error@*:* 0-1 {{no matching function for call to '__to_unsigned_like'}}
   });
 }
 
@@ -29,6 +32,7 @@ void test_pointer_return_type() {
   s.resize_and_overwrite(10, [](char* p, std::size_t) {
     return p; // expected-error-re@*:* {{{{(static_assertion|static assertion)}}{{.*}}integer-like}}
               // expected-error@*:* {{cannot initialize}}
+              // expected-error@*:* 0-1 {{no matching function for call to '__to_unsigned_like'}}
   });
 }
 
@@ -36,5 +40,6 @@ void test_float_return_type() {
   std::string s;
   s.resize_and_overwrite(10, [](char*, std::size_t) {
     return 5.0f; // expected-error-re@*:* {{{{(static_assertion|static assertion)}}{{.*}}integer-like}}
+                 // expected-error@*:* 0-1 {{no matching function for call to '__to_unsigned_like'}}
   });
 }

>From ae751639f3ef54440accf4c6769de630c3ec5931 Mon Sep 17 00:00:00 2001
From: Ilyas Mallah <github at ilyasm.dev>
Date: Tue, 13 Oct 2026 12:15:46 +0200
Subject: [PATCH 4/4] [libc++] Drop the is_signed guard on the sign check

---
 libcxx/include/string                                        | 5 +----
 .../string.capacity/resize_and_overwrite.verify.cpp          | 3 ++-
 2 files changed, 3 insertions(+), 5 deletions(-)

diff --git a/libcxx/include/string b/libcxx/include/string
index 6a672e89057da..63997b3fb18e4 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -639,7 +639,6 @@ basic_string<char32_t> operator""s( const char32_t *str, size_t len );
 #  include <__type_traits/is_nothrow_constructible.h>
 #  include <__type_traits/is_relocatable.h>
 #  include <__type_traits/is_same.h>
-#  include <__type_traits/is_signed.h>
 #  include <__type_traits/is_standard_layout.h>
 #  include <__type_traits/is_trivially_constructible.h>
 #  include <__type_traits/is_trivially_copyable.h>
@@ -1253,9 +1252,7 @@ public:
     __set_size(__n);
     __annotate_new(__n);
     __result_type __r = std::move(__op)(data(), auto(__n));
-    if constexpr (is_signed_v<__result_type>)
-      _LIBCPP_ASSERT_VALID_INPUT_RANGE(
-          __r >= 0, "string::resize_and_overwrite: the operation returned a negative size");
+    _LIBCPP_ASSERT_VALID_INPUT_RANGE(__r >= 0, "string::resize_and_overwrite: the operation returned a negative size");
     _LIBCPP_ASSERT_VALID_INPUT_RANGE(std::__to_unsigned_like(__r) <= __n,
                                      "string::resize_and_overwrite: the operation returned a size larger than n");
     __erase_to_end(__r);
diff --git a/libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.verify.cpp b/libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.verify.cpp
index 8d916dcd97f0b..dbf6e6f87bdf3 100644
--- a/libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.verify.cpp
+++ b/libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.verify.cpp
@@ -15,7 +15,7 @@
 
 // Verify that the operation's return type must be integer-like
 
-// When hardening is enabled, the checks on the result add an error after the static_assert.
+// When hardening is enabled, the checks on the result add errors after the static_assert.
 
 #include <string>
 
@@ -32,6 +32,7 @@ void test_pointer_return_type() {
   s.resize_and_overwrite(10, [](char* p, std::size_t) {
     return p; // expected-error-re@*:* {{{{(static_assertion|static assertion)}}{{.*}}integer-like}}
               // expected-error@*:* {{cannot initialize}}
+              // expected-error@*:* 0-1 {{ordered comparison between pointer and zero}}
               // expected-error@*:* 0-1 {{no matching function for call to '__to_unsigned_like'}}
   });
 }



More information about the libcxx-commits mailing list