[libc++] Treat negative counts in copy_n & friends as no-ops (#207086)

The standard specifies copy_n, fill_n and generate_n (both std and
ranges) to be no-ops when passed a negative count. This patch fixes
libc++ to abide by that requirement, with tests.

It's worth noting that std::for_each_n makes n > 0 a precondition
instead: we add the check and tests for it.

Fixes #193613
This commit is contained in:
Louis Dionne 2026-07-15 13:24:03 -04:00 committed by GitHub
parent 0a933be62e
commit f4120c4a1d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 239 additions and 24 deletions

View file

@ -33,6 +33,8 @@ template <class _AlgPolicy,
__enable_if_t<__has_random_access_iterator_category<_InIter>::value, int> = 0>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 __in_out_result<_InIter, _OutIter>
__copy_n(_InIter __first, typename _IterOps<_AlgPolicy>::template __difference_type<_InIter> __n, _OutIter __result) {
if (__n <= 0)
return {std::move(__first), std::move(__result)};
return std::__copy(__first, __first + __n, std::move(__result));
}
@ -42,7 +44,7 @@ template <class _AlgPolicy,
__enable_if_t<!__has_random_access_iterator_category<_InIter>::value, int> = 0>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 __in_out_result<_InIter, _OutIter>
__copy_n(_InIter __first, typename _IterOps<_AlgPolicy>::template __difference_type<_InIter> __n, _OutIter __result) {
while (__n != 0) {
while (__n > 0) {
*__result = *__first;
++__first;
++__result;

View file

@ -12,6 +12,7 @@
#include <__algorithm/for_each.h>
#include <__algorithm/for_each_n_segment.h>
#include <__assert>
#include <__config>
#include <__functional/identity.h>
#include <__iterator/iterator_traits.h>
@ -32,15 +33,12 @@ _LIBCPP_BEGIN_NAMESPACE_STD
template <class _InputIterator, class _Size, class _Func, class _Proj>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _InputIterator
__for_each_n(_InputIterator __first, _Size __orig_n, _Func&& __f, _Proj& __proj) {
typedef decltype(std::__convert_to_integral(__orig_n)) _IntegralSize;
_IntegralSize __n = __orig_n;
__for_each_n(_InputIterator __first, _Size __n, _Func&& __f, _Proj& __proj) {
#ifndef _LIBCPP_CXX03_LANG
if constexpr (__is_segmented_iterator_v<_InputIterator>) {
using __local_iterator = typename __segmented_iterator_traits<_InputIterator>::__local_iterator;
if constexpr (__has_random_access_iterator_category<__local_iterator>::value) {
return std::__for_each_n_segment(__first, __orig_n, [&](__local_iterator __lfirst, __local_iterator __llast) {
return std::__for_each_n_segment(__first, __n, [&](__local_iterator __lfirst, __local_iterator __llast) {
std::__for_each(__lfirst, __llast, __f, __proj);
});
} else {
@ -61,10 +59,12 @@ __for_each_n(_InputIterator __first, _Size __orig_n, _Func&& __f, _Proj& __proj)
#if _LIBCPP_STD_VER >= 17
template <class _InputIterator, class _Size, class _Func>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _InputIterator
for_each_n(_InputIterator __first, _Size __orig_n, _Func __f) {
inline _LIBCPP_HIDE_FROM_ABI
_LIBCPP_CONSTEXPR_SINCE_CXX20 _InputIterator for_each_n(_InputIterator __first, _Size __orig_n, _Func __f) {
auto __n = std::__convert_to_integral(__orig_n);
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__n >= 0, "for_each_n requires a non-negative count");
__identity __proj;
return std::__for_each_n(__first, __orig_n, __f, __proj);
return std::__for_each_n(__first, __n, __f, __proj);
}
#endif // _LIBCPP_STD_VER >= 17

View file

@ -12,6 +12,7 @@
#include <__algorithm/for_each_n.h>
#include <__config>
#include <__functional/identity.h>
#include <__utility/convert_to_integral.h>
#include <__utility/forward.h>
#include <__utility/move.h>
@ -26,20 +27,20 @@ _LIBCPP_BEGIN_NAMESPACE_STD
template <class _OutputIterator, class _Size, class _Generator>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _OutputIterator
__generate_n(_OutputIterator __first, _Size __orig_n, _Generator& __gen) {
__generate_n(_OutputIterator __first, _Size __n, _Generator& __gen) {
if (__n <= 0)
return __first;
using __iter_ref = decltype(*__first);
__identity __proj;
return std::__for_each_n(
std::move(__first),
__orig_n,
[&](__iter_ref __element) { std::forward<__iter_ref>(__element) = __gen(); },
__proj);
std::move(__first), __n, [&](__iter_ref __element) { std::forward<__iter_ref>(__element) = __gen(); }, __proj);
}
template <class _OutputIterator, class _Size, class _Generator>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _OutputIterator
generate_n(_OutputIterator __first, _Size __orig_n, _Generator __gen) {
return std::__generate_n(std::move(__first), __orig_n, __gen);
return std::__generate_n(std::move(__first), std::__convert_to_integral(__orig_n), __gen);
}
_LIBCPP_END_NAMESPACE_STD

View file

@ -11,6 +11,7 @@
#include <__algorithm/for_each_n.h>
#include <__algorithm/in_fun_result.h>
#include <__assert>
#include <__config>
#include <__functional/identity.h>
#include <__iterator/concepts.h>
@ -40,6 +41,7 @@ struct __for_each_n {
template <input_iterator _Iter, class _Proj = identity, indirectly_unary_invocable<projected<_Iter, _Proj>> _Func>
_LIBCPP_HIDE_FROM_ABI constexpr for_each_n_result<_Iter, _Func>
operator()(_Iter __first, iter_difference_t<_Iter> __count, _Func __func, _Proj __proj = {}) const {
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__count >= 0, "for_each_n requires a non-negative count");
auto __last = std::__for_each_n(std::move(__first), __count, __func, __proj);
return {std::move(__last), std::move(__func)};
}

View file

@ -424,8 +424,8 @@ public:
}
#if _LIBCPP_STD_VER <= 17
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 friend bool
operator!=(const __bit_iterator& __x, const __bit_iterator& __y) {
_LIBCPP_HIDE_FROM_ABI
_LIBCPP_CONSTEXPR_SINCE_CXX20 friend bool operator!=(const __bit_iterator& __x, const __bit_iterator& __y) {
return !(__x == __y);
}
@ -573,12 +573,13 @@ struct __specialized_algorithm<_Algorithm::__fill_n, __single_iterator<__bit_ite
template <class _Size, class _Tp>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 static __bit_iterator<_Cp, false>
operator()(__bit_iterator<_Cp, false> __first, _Size __n, const _Tp& __value) {
if (__n > 0) {
if (__value)
__impl<true>(__first, __n);
else
__impl<false>(__first, __n);
}
if (__n <= 0)
return __first;
if (__value)
__impl<true>(__first, __n);
else
__impl<false>(__first, __n);
return __first + __n;
}
};

View file

@ -0,0 +1,33 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
// <algorithm>
// template<class InputIterator, class Size, class Function>
// constexpr InputIterator
// for_each_n(InputIterator first, Size n, Function f);
//
// [alg.foreach] requires `n >= 0`; passing a negative count is a precondition violation.
// REQUIRES: has-unix-headers
// UNSUPPORTED: c++03, c++11, c++14
// UNSUPPORTED: libcpp-hardening-mode=none
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
#include <algorithm>
#include "check_assertion.h"
int main(int, char**) {
int a[] = {1, 2, 3};
TEST_LIBCPP_ASSERT_FAILURE(std::for_each_n(a, -1, [](int) {}), "for_each_n requires a non-negative count");
TEST_LIBCPP_ASSERT_FAILURE(std::for_each_n(a, -10000000, [](int) {}), "for_each_n requires a non-negative count");
return 0;
}

View file

@ -0,0 +1,37 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
// <algorithm>
// template<input_iterator I, class Proj = identity,
// indirectly_unary_invocable<projected<I, Proj>> Fun>
// constexpr ranges::for_each_n_result<I, Fun>
// ranges::for_each_n(I first, iter_difference_t<I> n, Fun f, Proj proj = {});
//
// [alg.foreach] requires `n >= 0`; passing a negative count is a precondition violation.
// REQUIRES: has-unix-headers
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-hardening-mode=none
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
#include <algorithm>
#include <array>
#include "check_assertion.h"
int main(int, char**) {
std::array a = {1, 2, 3};
TEST_LIBCPP_ASSERT_FAILURE(
std::ranges::for_each_n(a.begin(), -1, [](int) {}), "for_each_n requires a non-negative count");
TEST_LIBCPP_ASSERT_FAILURE(
std::ranges::for_each_n(a.begin(), -10000000, [](int) {}), "for_each_n requires a non-negative count");
return 0;
}

View file

@ -12,9 +12,12 @@
// constexpr OutIter // constexpr after C++17
// copy_n(InIter first, InIter::difference_type n, OutIter result);
// XFAIL: FROZEN-CXX03-HEADERS-FIXME
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <iterator>
#include <vector>
#include "test_macros.h"
@ -24,6 +27,34 @@
typedef UserDefinedIntegral<unsigned> UDI;
// A minimal single-pass input iterator that counts how many times it is advanced. Used to verify that
// copy_n reads exactly n elements (advancing the iterator only n - 1 times).
struct CountingInputIterator {
using iterator_category = std::input_iterator_tag;
using value_type = int;
using difference_type = long;
using pointer = const int*;
using reference = const int&;
const int* ptr_;
int* increments_;
TEST_CONSTEXPR_CXX14 CountingInputIterator(const int* ptr, int* increment_counter)
: ptr_(ptr), increments_(increment_counter) {}
TEST_CONSTEXPR_CXX14 reference operator*() const { return *ptr_; }
TEST_CONSTEXPR_CXX14 CountingInputIterator& operator++() {
++ptr_;
++*increments_;
return *this;
}
TEST_CONSTEXPR_CXX14 CountingInputIterator operator++(int) {
CountingInputIterator __tmp = *this;
++*this;
return __tmp;
}
};
class PaddedBase {
public:
TEST_CONSTEXPR PaddedBase(std::int16_t a, std::int8_t b) : a_(a), b_(b) {}
@ -61,6 +92,15 @@ struct TestIterators {
assert(base(r) == ib + N / 2);
for (unsigned i = 0; i < N / 2; ++i)
assert(ia[i] == ib[i]);
{ // A negative count is a no-op that returns the unchanged output iterator.
// Regression test for https://llvm.org/PR193613.
int source[] = {1, 2, 3};
int dest[] = {-1, -2, -3};
OutIter ret = std::copy_n(InIter(source), -5, OutIter(dest));
assert(base(ret) == dest);
assert(dest[0] == -1 && dest[1] == -2 && dest[2] == -3);
}
}
};
};
@ -81,6 +121,13 @@ TEST_CONSTEXPR_CXX20 bool test_vector_bool(std::size_t N) {
for (std::size_t i = 0; i < N; ++i)
assert(out[i + 4] == in[i]);
}
{ // Negative count test
std::vector<bool> source(N, true);
std::vector<bool> dest(N, false);
std::vector<bool>::iterator r = std::copy_n(source.begin(), -5, dest.begin());
assert(r == dest.begin());
assert(dest == std::vector<bool>(N, false));
}
return true;
}
@ -114,6 +161,28 @@ TEST_CONSTEXPR_CXX20 bool test() {
assert(test_vector_bool(256));
}
{ // For a single-pass input iterator, copy_n reads exactly n elements -- advancing the iterator only
// n - 1 times. For n == 0, it shouldn't advance the iterator. See 99847d2bf132.
int in[] = {1, 2, 3, 4, 5};
{ // n == 0 is a no-op
int out[3] = {-1, -2, -3};
int increments = 0;
int* r = std::copy_n(CountingInputIterator(in, &increments), 0, out);
assert(r == out);
assert(increments == 0);
assert(out[0] == -1 && out[1] == -2 && out[2] == -3);
}
{ // n > 0 advances the iterator exactly n - 1 times
int out[3] = {0, 0, 0};
int increments = 0;
int* r = std::copy_n(CountingInputIterator(in, &increments), 3, out);
assert(r == out + 3);
assert(increments == 2);
assert(out[0] == 1 && out[1] == 2 && out[2] == 3);
}
}
return true;
}

View file

@ -60,6 +60,17 @@ constexpr void test_iterators() {
assert(base(ret.in) == in.data());
assert(base(ret.out) == out.data());
}
{ // A negative count is a no-op that returns the unchanged iterators.
// Regression test for https://llvm.org/PR193613.
std::array in{1, 2, 3};
std::array out{-1, -2, -3};
std::same_as<std::ranges::in_out_result<In, Out>> auto ret =
std::ranges::copy_n(In(in.data()), -5, Out(out.data()));
assert(base(ret.in) == in.data());
assert(base(ret.out) == out.data());
assert((out == std::array{-1, -2, -3}));
}
}
#if TEST_STD_VER >= 23
@ -79,6 +90,13 @@ constexpr bool test_vector_bool(std::size_t N) {
for (std::size_t i = 0; i < N; ++i)
assert(out[i + 4] == in[i]);
}
{ // Negative count test
std::vector<bool> source(N, true);
std::vector<bool> dest(N, false);
auto ret = std::ranges::copy_n(source.begin(), -5, dest.begin());
assert(ret.out == dest.begin());
assert(dest == std::vector<bool>(N, false));
}
return true;
};

View file

@ -13,6 +13,8 @@
// constexpr OutputIterator // constexpr after C++17
// fill_n(Iter first, Size n, const T& value);
// XFAIL: FROZEN-CXX03-HEADERS-FIXME
#include <algorithm>
#include <array>
#include <cassert>
@ -52,6 +54,14 @@ struct Test {
std::array<T, 4> expected = {1, 5, 5, 4};
test<Iter>(in, 1, 2, 5, expected);
}
{ // A negative count is a no-op that returns the unchanged iterator.
// Regression test for https://llvm.org/PR193613.
std::array<T, 4> in = {1, 2, 3, 4};
std::array<T, 4> expected = {1, 2, 3, 4};
Iter it = std::fill_n(Iter(in.data()), -5, T(9));
assert(base(it) == in.data());
assert(in == expected);
}
}
};
@ -126,6 +136,13 @@ TEST_CONSTEXPR_CXX20 bool test_vector_bool(std::size_t N) {
}
}
{ // Negative count test
std::vector<bool> v(N, false);
std::vector<bool>::iterator r = std::fill_n(v.begin(), -5, true);
assert(r == v.begin());
assert(v == std::vector<bool>(N, false));
}
return true;
}

View file

@ -51,6 +51,14 @@ constexpr void test_iterators() {
auto ret = std::ranges::fill_n(It(a.data()), 0, 1);
assert(base(ret) == a.data());
}
{ // A negative count is a no-op that returns the unchanged iterator.
// Regression test for https://llvm.org/PR193613.
int a[3] = {-1, -2, -3};
std::same_as<It> decltype(auto) ret = std::ranges::fill_n(It(a), -5, 1);
assert(base(ret) == a);
assert(a[0] == -1 && a[1] == -2 && a[2] == -3);
}
}
// The `ranges::{fill, fill_n}` algorithms require `vector<bool, Alloc>::iterator` to satisfy
@ -99,6 +107,13 @@ constexpr bool test_vector_bool(std::size_t N) {
}
}
{ // Negative count test
std::vector<bool> v(N, false);
auto r = std::ranges::fill_n(v.begin(), -5, true);
assert(r == v.begin());
assert(v == std::vector<bool>(N, false));
}
return true;
}
#endif

View file

@ -70,6 +70,13 @@ test()
test2<Iter, float>();
test2<Iter, double>(); // this is PR#35498
test2<Iter, long double>();
{ // A negative count is a no-op that returns the unchanged iterator.
// Regression test for https://llvm.org/PR193613.
int ia[] = {1, 2, 3};
assert(std::generate_n(Iter(ia), -5, gen_test()) == Iter(ia));
assert(ia[0] == 1 && ia[1] == 2 && ia[2] == 3);
}
}
void deque_test() {

View file

@ -92,6 +92,20 @@ constexpr void test_iter() {
test_one<Iter>(std::array{-10, -20, -30, -40, -50}, 3, gen, {1, 2, 3, -40, -50});
// Longer sequence, n = 5.
test_one<Iter>(std::array<int, 5>{}, 5, gen, {1, 2, 3, 4, 5});
{ // A negative count is a no-op that returns the unchanged iterator.
// Regression test for https://llvm.org/PR193613.
std::array in{-10, -20, -30};
int called = 0;
auto counting_gen = [&called] {
++called;
return 42;
};
std::same_as<Iter> decltype(auto) result = std::ranges::generate_n(Iter(in.data()), -5, counting_gen);
assert(base(result) == in.data());
assert(called == 0);
assert((in == std::array{-10, -20, -30}));
}
}
constexpr void test_iterators() {
@ -131,7 +145,6 @@ constexpr bool test() {
assert(gen_invocations == N2);
}
return true;
}