[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