[libcxx-commits] [libcxx] [libc++] Fix search_n with an invalid count (PR #223747)
via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Sep 15 08:58:16 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libcxx
Author: Louis Dionne (ldionne)
<details>
<summary>Changes</summary>
search_n would incorrectly compute pointer offsets based on the user-provided count, which wasn't checked for being negative. This patch adds checks for negativity to prevent computing and accessing potentially out-of-bounds pointers.
---
Full diff: https://github.com/llvm/llvm-project/pull/223747.diff
3 Files Affected:
- (modified) libcxx/include/__algorithm/search_n.h (+11-5)
- (modified) libcxx/test/std/algorithms/alg.nonmodifying/alg.search/ranges.search_n.pass.cpp (+16)
- (modified) libcxx/test/std/algorithms/alg.nonmodifying/alg.search/search_n.pass.cpp (+12)
``````````diff
diff --git a/libcxx/include/__algorithm/search_n.h b/libcxx/include/__algorithm/search_n.h
index 0962542e134cd..b978e12ee2dd0 100644
--- a/libcxx/include/__algorithm/search_n.h
+++ b/libcxx/include/__algorithm/search_n.h
@@ -18,6 +18,7 @@
#include <__type_traits/enable_if.h>
#include <__type_traits/invoke.h>
#include <__type_traits/is_callable.h>
+#include <__type_traits/make_unsigned.h>
#include <__utility/convert_to_integral.h>
#include <__utility/pair.h>
@@ -79,14 +80,19 @@ __find_longest_suffix(_RAIter __first, _RAIter __last, const _ValueT& __value, _
template <class _AlgPolicy, class _Pred, class _Iter, class _SizeT, class _Type, class _Proj, class _DiffT>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 std::pair<_Iter, _Iter> __search_n_random_access_impl(
_Iter __first, _SizeT __count_in, const _Type& __value, _Pred& __pred, _Proj& __proj, _DiffT __size) {
- auto __last = __first + __size;
- auto __count = static_cast<_DiffT>(__count_in);
-
- if (__count == 0)
+ // Trivially match if count is non-positive. This must be checked before we perform arithmetic on the count.
+ if (__count_in <= 0)
return std::make_pair(__first, __first);
- if (__size < __count)
+
+ auto __last = __first + __size;
+
+ // If count is larger than the sequence size, no match is possible. Compare as unsigned to prevent
+ // truncation between _DiffT and _SizeT.
+ if (std::__to_unsigned_like(__size) < std::__to_unsigned_like(__count_in))
return std::make_pair(__last, __last);
+ auto __count = static_cast<_DiffT>(__count_in); // representable as _DiffT since `0 < __count_in <= __size`
+
// [__match_start, __match_start + __count) is the subrange which we currently check whether it only contains matching
// elements. This subrange is returned in case all the elements match.
// [__match_start, __matched_until) is the longest subrange where all elements are known to match at any given point
diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/ranges.search_n.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/ranges.search_n.pass.cpp
index 6686e8de733bd..84a92fbd60f6c 100644
--- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/ranges.search_n.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/ranges.search_n.pass.cpp
@@ -170,6 +170,22 @@ constexpr void test_iterators() {
}
}
+ { // pattern has negative length. See https://wg21.link/LWG426.
+ {
+ int a[] = {6, 7, 8};
+ auto ret = std::ranges::search_n(Iter(a), Sent(Iter(a + 3)), -1, 7);
+ assert(base(ret.begin()) == a);
+ assert(base(ret.end()) == a);
+ }
+ {
+ int a[] = {6, 7, 8};
+ auto range = std::ranges::subrange(Iter(a), Sent(Iter(a + 3)));
+ auto ret = std::ranges::search_n(range, -1, 7);
+ assert(base(ret.begin()) == a);
+ assert(base(ret.end()) == a);
+ }
+ }
+
{ // range has zero length
{
std::array<int, 0> a = {};
diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/search_n.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/search_n.pass.cpp
index bbe9f0ec51b3d..68678d73bb340 100644
--- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/search_n.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.search/search_n.pass.cpp
@@ -16,6 +16,8 @@
#include <algorithm>
#include <array>
#include <cassert>
+#include <cstddef>
+#include <limits>
#include "test_macros.h"
#include "test_iterators.h"
@@ -79,6 +81,16 @@ TEST_CONSTEXPR_CXX20 bool test() {
auto ret = std::search_n(Iter(a), Iter(a + 3), 0, 7);
assert(base(ret) == a);
}
+ { // pattern has negative length. See https://wg21.link/LWG426.
+ int a[] = {6, 7, 8};
+ auto ret = std::search_n(Iter(a), Iter(a + 3), -1, 7);
+ assert(base(ret) == a);
+ }
+ { // pattern is longer than the range and doesn't fit in the iterator's difference_type
+ int a[] = {7, 7, 7};
+ auto ret = std::search_n(Iter(a), Iter(a + 3), std::numeric_limits<std::size_t>::max(), 7);
+ assert(base(ret) == a + 3);
+ }
{ // range has zero length
std::array<int, 0> a = {};
auto ret = std::search_n(Iter(a.data()), Iter(a.data()), 1, 1);
``````````
</details>
https://github.com/llvm/llvm-project/pull/223747
More information about the libcxx-commits
mailing list