#endif // HOSTED
#if __cpp_lib_concepts
- // N.B. this is not the same as nothrow-forward-iterator, which doesn't
- // require noexcept operations, it just says it's undefined if they throw.
- // Here we require them to be actually noexcept.
- template<typename _Iter>
- concept __nothrow_contiguous_iterator
- = contiguous_iterator<_Iter> && requires (_Iter __i) {
- // If this operation can throw then the iterator could cause
- // the algorithm to exit early via an exception, in which case
- // we can't use memcpy.
- { *__i } noexcept;
- };
-
template<typename _OutIter, typename _InIter, typename _Sent = _InIter>
concept __memcpyable_iterators
- = __nothrow_contiguous_iterator<_OutIter>
- && __nothrow_contiguous_iterator<_InIter>
+ = contiguous_iterator<_OutIter> && contiguous_iterator<_InIter>
&& sized_sentinel_for<_Sent, _InIter>
- && requires (_OutIter __o, _InIter __i, _Sent __s) {
+ && requires (_OutIter __o, _InIter __i) {
requires !!__memcpyable<decltype(std::to_address(__o)),
decltype(std::to_address(__i))>::__value;
- { __i != __s } noexcept;
};
#endif
void* __dest = std::to_address(__result);
const void* __src = std::to_address(__first);
size_t __nbytes = __n * sizeof(iter_value_t<_InIter>);
- // Advance the iterators first, in case doing so throws.
- __result += __n;
- __first += __n;
+ // Advance the iterators and convert to pointers first.
+ // This gives the iterators a chance to do bounds checking.
+ (void) std::to_address(__result += __n);
+ (void) std::to_address(__first += __n);
__builtin_memmove(__dest, __src, __nbytes);
}
else if (__n == 1)
void* __dest = std::to_address(__result);
const void* __src = std::to_address(__first);
size_t __nbytes = __n * sizeof(iter_value_t<_InputIterator>);
- // Advance the iterators first, in case doing so throws.
- __result += __n;
- __first += __n;
+ // Advance the iterators and convert to pointers first.
+ // This gives the iterators a chance to do bounds checking.
+ (void) std::to_address(__result += __n);
+ (void) std::to_address(__first += __n);
__builtin_memmove(__dest, __src, __nbytes);
}
else if (__n == 1)
if (auto __n = __last - __first; __n > 1) [[likely]]
{
const void* __src = std::to_address(__first);
- // Advance the iterators first, in case doing so throws.
- __result -= __n;
- __first += __n;
+ // Advance the iterators and convert to pointers first.
+ // This gives the iterators a chance to do bounds checking.
+ (void) std::to_address(__result -= __n);
+ (void) std::to_address(__first += __n);
void* __dest = std::to_address(__result);
size_t __nbytes = __n * sizeof(iter_value_t<_BI1>);
__builtin_memmove(__dest, __src, __nbytes);
--- /dev/null
+// { dg-do run { target c++20 } }
+
+#include <algorithm>
+#include <iterator>
+#include <cstdlib>
+#include <testsuite_hooks.h>
+
+const int valid_size = 3;
+const int out_of_bound = valid_size + 1;
+// data is larger than valid_size so that `data + out_of_bound` does
+// not have undefined behaviour, but data[valid_size] is not allowed
+// to be accessed by Iter.
+int data[valid_size + 1]{ 1, 2, 3, -999 };
+
+struct Iter
+{
+ using iterator_category = std::contiguous_iterator_tag;
+ using value_type = int;
+ using different_type = int;
+ using reference = int&;
+ using pointer = int*;
+
+ static inline bool advance_error = false;
+ static inline bool address_error = false;
+
+ int index{};
+
+ int& operator*() const
+ { std::abort(); } // Should not happen if reads/writes are done on pointers.
+
+ int* operator->() const
+ {
+ if (index < 0 || index > valid_size)
+ {
+ address_error = true;
+ return data + valid_size;
+ }
+ return data + index;
+ }
+
+ int& operator[](int n) const { return *(*this + n); }
+
+ Iter& operator++() { return *this += 1; }
+ Iter& operator--() { return *this -= 1; }
+ Iter operator++(int) { return Iter{index++}; }
+ Iter operator--(int) { return Iter{index--}; }
+
+ bool operator==(const Iter&) const = default;
+ auto operator<=>(const Iter&) const = default;
+
+ Iter& operator+=(int n)
+ {
+ index += n;
+ if (index < 0 || index > valid_size)
+ advance_error = true;
+ return *this;
+ }
+ Iter& operator-=(int n) { return *this += -n; }
+
+ friend Iter operator+(Iter i, int n) { return i += n; }
+ friend Iter operator+(int n, Iter i) { return i + n; }
+ friend Iter operator-(Iter i, int n) { return i + -n; }
+ friend int operator-(Iter i, Iter j) { return i.index - j.index; }
+};
+
+static_assert( std::contiguous_iterator<Iter> );
+
+int main()
+{
+ // P3349R1 allows std::copy to lower contiguous iterators to pointers,
+ // but it must still advance the contiguous iterator and use std::to_address
+ // to get a pointer for both ends of the range.
+ // This test verifies that Iter::operator-> is called for an out-of-range
+ // iterator, so that a hardened iterator with error-checking is able to
+ // detect the error.
+
+ int i[out_of_bound];
+ // Attempt to read from an out-of-bound Iter:
+ std::copy(Iter{0}, Iter{out_of_bound}, i);
+ VERIFY( Iter::advance_error );
+ VERIFY( Iter::address_error );
+ Iter::advance_error = Iter::address_error = false;
+ // Attempt to write to an out-of-bound Iter:
+ std::copy(std::begin(i), std::end(i), Iter{0});
+ VERIFY( Iter::advance_error );
+ VERIFY( Iter::address_error );
+}
--- /dev/null
+// { dg-do run { target c++20 } }
+
+#include <algorithm>
+#include <iterator>
+#include <cstdlib>
+#include <testsuite_hooks.h>
+
+const int valid_size = 3;
+const int out_of_bound = valid_size + 1;
+// array is larger than valid_size so that `data + out_of_bound` and `data - 1`
+// do not have undefined behaviour, but data[valid_size] and data[-1] are
+// not allowed to be accessed by Iter.
+int array[1 + valid_size + 1]{ -999, 1, 2, 3, -999 };
+int* data = array + 1;
+
+struct Iter
+{
+ using iterator_category = std::contiguous_iterator_tag;
+ using value_type = int;
+ using different_type = int;
+ using reference = int&;
+ using pointer = int*;
+
+ static inline bool advance_error = false;
+ static inline bool address_error = false;
+
+ int index{};
+
+ int& operator*() const
+ { std::abort(); } // Should not happen if reads/writes are done on pointers.
+
+ int* operator->() const
+ {
+ if (index < 0 || index > valid_size)
+ {
+ address_error = true;
+ return data;
+ }
+ return data + index;
+ }
+
+ int& operator[](int n) const { return *(*this + n); }
+
+ Iter& operator++() { return *this += 1; }
+ Iter& operator--() { return *this -= 1; }
+ Iter operator++(int) { return Iter{index++}; }
+ Iter operator--(int) { return Iter{index--}; }
+
+ bool operator==(const Iter&) const = default;
+ auto operator<=>(const Iter&) const = default;
+
+ Iter& operator+=(int n)
+ {
+ index += n;
+ if (index < 0 || index > valid_size)
+ advance_error = true;
+ return *this;
+ }
+ Iter& operator-=(int n) { return *this += -n; }
+
+ friend Iter operator+(Iter i, int n) { return i += n; }
+ friend Iter operator+(int n, Iter i) { return i + n; }
+ friend Iter operator-(Iter i, int n) { return i + -n; }
+ friend int operator-(Iter i, Iter j) { return i.index - j.index; }
+};
+
+static_assert( std::contiguous_iterator<Iter> );
+
+int main()
+{
+ // P3349R1 allows std::copy_backward to lower contiguous iterators to pointers
+ // but it must still advance the contiguous iterator and use std::to_address
+ // to get a pointer for both ends of the range.
+ // This test verifies that Iter::operator-> is called for an out-of-range
+ // iterator, so that a hardened iterator with error-checking is able to
+ // detect the error.
+
+ int i[out_of_bound];
+ // Attempt to read from an out-of-bound Iter:
+ std::copy_backward(Iter{0}, Iter{out_of_bound}, i + out_of_bound);
+ VERIFY( Iter::advance_error );
+ VERIFY( Iter::address_error );
+ Iter::advance_error = Iter::address_error = false;
+ // Attempt to write to an out-of-bound Iter with index -1:
+ std::copy_backward(std::begin(i), std::end(i), Iter{valid_size});
+ VERIFY( Iter::advance_error );
+ VERIFY( Iter::address_error );
+}
--- /dev/null
+// { dg-do run { target c++20 } }
+
+#include <algorithm>
+#include <iterator>
+#include <cstdlib>
+#include <testsuite_hooks.h>
+
+const int valid_size = 3;
+const int out_of_bound = valid_size + 1;
+// data is larger than valid_size so that `data + out_of_bound` does
+// not have undefined behaviour, but data[valid_size] is not allowed
+// to be accessed by Iter.
+int data[valid_size + 1]{ 1, 2, 3, -999 };
+
+struct Iter
+{
+ using iterator_category = std::contiguous_iterator_tag;
+ using value_type = int;
+ using different_type = int;
+ using reference = int&;
+ using pointer = int*;
+
+ static inline bool advance_error = false;
+ static inline bool address_error = false;
+
+ int index{};
+
+ int& operator*() const
+ { std::abort(); } // Should not happen if reads/writes are done on pointers.
+
+ int* operator->() const
+ {
+ if (index < 0 || index > valid_size)
+ {
+ address_error = true;
+ return data + valid_size;
+ }
+ return data + index;
+ }
+
+ int& operator[](int n) const { return *(*this + n); }
+
+ Iter& operator++() { return *this += 1; }
+ Iter& operator--() { return *this -= 1; }
+ Iter operator++(int) { return Iter{index++}; }
+ Iter operator--(int) { return Iter{index--}; }
+
+ bool operator==(const Iter&) const = default;
+ auto operator<=>(const Iter&) const = default;
+
+ Iter& operator+=(int n)
+ {
+ index += n;
+ if (index < 0 || index > valid_size)
+ advance_error = true;
+ return *this;
+ }
+ Iter& operator-=(int n) { return *this += -n; }
+
+ friend Iter operator+(Iter i, int n) { return i += n; }
+ friend Iter operator+(int n, Iter i) { return i + n; }
+ friend Iter operator-(Iter i, int n) { return i + -n; }
+ friend int operator-(Iter i, Iter j) { return i.index - j.index; }
+};
+
+static_assert( std::contiguous_iterator<Iter> );
+
+int main()
+{
+ // P3349R1 allows std::copy_n to lower contiguous iterators to pointers,
+ // but it must still advance the contiguous iterator and use std::to_address
+ // to get a pointer for both ends of the range.
+ // This test verifies that Iter::operator-> is called for an out-of-range
+ // iterator, so that a hardened iterator with error-checking is able to
+ // detect the error.
+
+ int i[out_of_bound];
+ // Attempt to read from an out-of-bound Iter:
+ std::copy_n(Iter{0}, out_of_bound, i);
+ VERIFY( Iter::advance_error );
+ VERIFY( Iter::address_error );
+ Iter::advance_error = Iter::address_error = false;
+ // Attempt to write to an out-of-bound Iter:
+ std::copy_n(std::begin(i), out_of_bound, Iter{0});
+ VERIFY( Iter::advance_error );
+ VERIFY( Iter::address_error );
+}