[libcxx-commits] [libcxx] 8e26c31 - [libc++][NFC] Add checks for lifetime issues in classic algorithms.
Konstantin Varlamov via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Jul 26 16:15:32 PDT 2022
Author: Konstantin Varlamov
Date: 2022-07-26T16:15:11-07:00
New Revision: 8e26c315a70f4833c38c23ed9efcee90c69e9069
URL: https://github.com/llvm/llvm-project/commit/8e26c315a70f4833c38c23ed9efcee90c69e9069
DIFF: https://github.com/llvm/llvm-project/commit/8e26c315a70f4833c38c23ed9efcee90c69e9069.diff
LOG: [libc++][NFC] Add checks for lifetime issues in classic algorithms.
Differential Revision: https://reviews.llvm.org/D130330
Added:
libcxx/test/std/algorithms/robust_against_proxy_iterators_lifetime_bugs.pass.cpp
Modified:
Removed:
libcxx/test/std/algorithms/alg.sorting/alg.sort/sort/sort_proxy.pass.cpp
################################################################################
diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.sort/sort/sort_proxy.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.sort/sort/sort_proxy.pass.cpp
deleted file mode 100644
index 3ebcb93e59b9d..0000000000000
--- a/libcxx/test/std/algorithms/alg.sorting/alg.sort/sort/sort_proxy.pass.cpp
+++ /dev/null
@@ -1,131 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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>
-
-#include <algorithm>
-#include <cassert>
-#include <vector>
-
-struct Cpp17ProxyIterator {
- struct Reference {
- int* i_;
- Reference(int& i) : i_(&i) {}
-
- operator int() const { return *i_; }
-
- Reference& operator=(int i) {
- *i_ = i;
- return *this;
- }
-
- friend bool operator<(const Reference& x, const Reference& y) { return *x.i_ < *y.i_; }
-
- friend bool operator==(const Reference& x, const Reference& y) { return *x.i_ == *y.i_; }
-
- friend void swap(Reference x, Reference y) { std::swap(*(x.i_), *(y.i_)); }
- };
-
- using
diff erence_type = int;
- using value_type = int;
- using reference = Reference;
- using pointer = void*;
- using iterator_category = std::random_access_iterator_tag;
-
- int* ptr_;
-
- Cpp17ProxyIterator(int* ptr) : ptr_(ptr) {}
-
- Reference operator*() const { return Reference(*ptr_); }
-
- Cpp17ProxyIterator& operator++() {
- ++ptr_;
- return *this;
- }
-
- Cpp17ProxyIterator operator++(int) {
- auto tmp = *this;
- ++*this;
- return tmp;
- }
-
- friend bool operator==(const Cpp17ProxyIterator& x, const Cpp17ProxyIterator& y) { return x.ptr_ == y.ptr_; }
- friend bool operator!=(const Cpp17ProxyIterator& x, const Cpp17ProxyIterator& y) { return x.ptr_ != y.ptr_; }
-
- Cpp17ProxyIterator& operator--() {
- --ptr_;
- return *this;
- }
-
- Cpp17ProxyIterator operator--(int) {
- auto tmp = *this;
- --*this;
- return tmp;
- }
-
- Cpp17ProxyIterator& operator+=(
diff erence_type n) {
- ptr_ += n;
- return *this;
- }
-
- Cpp17ProxyIterator& operator-=(
diff erence_type n) {
- ptr_ -= n;
- return *this;
- }
-
- Reference operator[](
diff erence_type i) const { return Reference(*(ptr_ + i)); }
-
- friend bool operator<(const Cpp17ProxyIterator& x, const Cpp17ProxyIterator& y) { return x.ptr_ < y.ptr_; }
-
- friend bool operator>(const Cpp17ProxyIterator& x, const Cpp17ProxyIterator& y) { return x.ptr_ > y.ptr_; }
-
- friend bool operator<=(const Cpp17ProxyIterator& x, const Cpp17ProxyIterator& y) { return x.ptr_ <= y.ptr_; }
-
- friend bool operator>=(const Cpp17ProxyIterator& x, const Cpp17ProxyIterator& y) { return x.ptr_ >= y.ptr_; }
-
- friend Cpp17ProxyIterator operator+(const Cpp17ProxyIterator& x,
diff erence_type n) {
- return Cpp17ProxyIterator(x.ptr_ + n);
- }
-
- friend Cpp17ProxyIterator operator+(
diff erence_type n, const Cpp17ProxyIterator& x) {
- return Cpp17ProxyIterator(n + x.ptr_);
- }
-
- friend Cpp17ProxyIterator operator-(const Cpp17ProxyIterator& x,
diff erence_type n) {
- return Cpp17ProxyIterator(x.ptr_ - n);
- }
-
- friend
diff erence_type operator-(Cpp17ProxyIterator x, Cpp17ProxyIterator y) {
- return static_cast<int>(x.ptr_ - y.ptr_);
- }
-};
-
-void test() {
- // TODO: use a custom proxy iterator instead of (or in addition to) `vector<bool>`.
- std::vector<bool> v(5, false);
- v[1] = true;
- v[3] = true;
- std::sort(v.begin(), v.end());
- assert(std::is_sorted(v.begin(), v.end()));
-}
-
-void testCustomProxyIterator() {
- int a[] = {5, 1, 3, 2, 4};
- std::sort(Cpp17ProxyIterator(a), Cpp17ProxyIterator(a + 5));
- assert(a[0] == 1);
- assert(a[1] == 2);
- assert(a[2] == 3);
- assert(a[3] == 4);
- assert(a[4] == 5);
-}
-
-int main(int, char**) {
- test();
- testCustomProxyIterator();
- return 0;
-}
diff --git a/libcxx/test/std/algorithms/robust_against_proxy_iterators_lifetime_bugs.pass.cpp b/libcxx/test/std/algorithms/robust_against_proxy_iterators_lifetime_bugs.pass.cpp
new file mode 100644
index 0000000000000..db68423825ea4
--- /dev/null
+++ b/libcxx/test/std/algorithms/robust_against_proxy_iterators_lifetime_bugs.pass.cpp
@@ -0,0 +1,771 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// Making this test file support C++03 is
diff icult; the lack of support for initializer lists is a major issue.
+// UNSUPPORTED: c++03
+
+// <algorithm>
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <random>
+#include <set>
+
+#include "test_macros.h"
+
+// This file contains checks for lifetime issues across all the classic algorithms. It uses two complementary
+// approaches:
+// - runtime checks using a proxy iterator that tracks the lifetime of itself and its objects to catch potential
+// lifetime issues;
+// - `constexpr` checks using a `constexpr`-friendly proxy iterator that catch undefined behavior.
+
+// A random-access proxy iterator that tracks the lifetime of itself and its `value_type` and `reference` objects to
+// prevent potential lifetime issues in algorithms.
+//
+// This class cannot be `constexpr` because its cache is a static variable. The cache cannot be provided as
+// a constructor parameter because `LifetimeIterator` has to be default-constructible.
+class LifetimeIterator {
+ // The cache simply tracks addresses of the local variables.
+ class LifetimeCache {
+ std::set<const void*> cache_;
+
+ public:
+ bool contains(const void* ptr) const { return cache_.find(ptr) != cache_.end(); }
+
+ void insert(const void* ptr) {
+ assert(!contains(ptr));
+ cache_.insert(ptr);
+ }
+
+ void erase(const void* ptr) {
+ assert(contains(ptr));
+ cache_.erase(ptr);
+ }
+ };
+
+ public:
+ struct Value {
+ int i_;
+ bool moved_from_ = false; // Check for double moves and reads after moving.
+
+ Value() { lifetime_cache.insert(this); }
+ Value(int i) : i_(i) { lifetime_cache.insert(this); }
+ ~Value() { lifetime_cache.erase(this); }
+
+ Value(const Value& rhs) : i_(rhs.i_) {
+ assert(lifetime_cache.contains(&rhs));
+ assert(!rhs.moved_from_);
+
+ lifetime_cache.insert(this);
+ }
+
+ Value(Value&& rhs) noexcept : i_(rhs.i_) {
+ assert(lifetime_cache.contains(&rhs));
+
+ assert(!rhs.moved_from_);
+ rhs.moved_from_ = true;
+
+ // It's ok if it throws -- since it's a test, terminating the program is acceptable.
+ lifetime_cache.insert(this);
+ }
+
+ Value& operator=(const Value& rhs) {
+ assert(lifetime_cache.contains(this) && lifetime_cache.contains(&rhs));
+ assert(!rhs.moved_from_);
+
+ i_ = rhs.i_;
+ moved_from_ = false;
+
+ return *this;
+ }
+
+ Value& operator=(Value&& rhs) noexcept {
+ assert(lifetime_cache.contains(this) && lifetime_cache.contains(&rhs));
+
+ assert(!rhs.moved_from_);
+ rhs.moved_from_ = true;
+
+ i_ = rhs.i_;
+ moved_from_ = false;
+
+ return *this;
+ }
+
+ friend bool operator<(const Value& lhs, const Value& rhs) {
+ assert(lifetime_cache.contains(&lhs) && lifetime_cache.contains(&rhs));
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+
+ return lhs.i_ < rhs.i_;
+ }
+
+ friend bool operator==(const Value& lhs, const Value& rhs) {
+ assert(lifetime_cache.contains(&lhs) && lifetime_cache.contains(&rhs));
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+
+ return lhs.i_ == rhs.i_;
+ }
+
+ };
+
+ struct Reference {
+ Value* v_;
+ bool moved_from_ = false; // Check for double moves and reads after moving.
+
+ Reference(Value& v) : v_(&v) {
+ lifetime_cache.insert(this);
+ }
+
+ ~Reference() {
+ lifetime_cache.erase(this);
+ }
+
+ Reference(const Reference& rhs) : v_(rhs.v_) {
+ assert(lifetime_cache.contains(&rhs));
+ assert(!rhs.moved_from_);
+
+ lifetime_cache.insert(this);
+ }
+
+ Reference(Reference&& rhs) noexcept : v_(rhs.v_) {
+ assert(lifetime_cache.contains(&rhs));
+
+ assert(!rhs.moved_from_);
+ rhs.moved_from_ = true;
+
+ lifetime_cache.insert(this);
+ }
+
+ Reference& operator=(const Reference& rhs) {
+ assert(lifetime_cache.contains(this) && lifetime_cache.contains(&rhs));
+ assert(!rhs.moved_from_);
+
+ v_ = rhs.v_;
+ moved_from_ = false;
+
+ return *this;
+ }
+
+ Reference& operator=(Reference&& rhs) noexcept {
+ assert(lifetime_cache.contains(this) && lifetime_cache.contains(&rhs));
+
+ assert(!rhs.moved_from_);
+ rhs.moved_from_ = true;
+
+ v_ = rhs.v_;
+ moved_from_ = false;
+
+ return *this;
+ }
+
+ operator Value() const {
+ assert(lifetime_cache.contains(this));
+ assert(!moved_from_);
+
+ return *v_;
+ }
+
+ Reference& operator=(Value v) {
+ assert(lifetime_cache.contains(this));
+ assert(!moved_from_);
+
+ *v_ = v;
+ moved_from_ = false;
+
+ return *this;
+ }
+
+ friend bool operator<(const Reference& lhs, const Reference& rhs) {
+ assert(lifetime_cache.contains(&lhs) && lifetime_cache.contains(&rhs));
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+
+ return *lhs.v_ < *rhs.v_;
+ }
+
+ friend bool operator==(const Reference& lhs, const Reference& rhs) {
+ assert(lifetime_cache.contains(&lhs) && lifetime_cache.contains(&rhs));
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+
+ return *lhs.v_ == *rhs.v_;
+ }
+
+ friend void swap(Reference lhs, Reference rhs) {
+ assert(lifetime_cache.contains(&lhs) && lifetime_cache.contains(&rhs));
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+
+ std::swap(*(lhs.v_), *(rhs.v_));
+ }
+ };
+
+ using
diff erence_type = int;
+ using value_type = Value;
+ using reference = Reference;
+ using pointer = void;
+ using iterator_category = std::random_access_iterator_tag;
+
+ Value* ptr_ = nullptr;
+ bool moved_from_ = false; // Check for double moves and reads after moving.
+
+ LifetimeIterator() = default;
+ LifetimeIterator(Value* ptr) : ptr_(ptr) {}
+
+ LifetimeIterator(const LifetimeIterator& rhs) : ptr_(rhs.ptr_) {
+ assert(!rhs.moved_from_);
+ }
+
+ LifetimeIterator& operator=(const LifetimeIterator& rhs) {
+ assert(!rhs.moved_from_);
+
+ ptr_ = rhs.ptr_;
+ moved_from_ = false;
+
+ return *this;
+ }
+
+ LifetimeIterator(LifetimeIterator&& rhs) noexcept : ptr_(rhs.ptr_) {
+ assert(!rhs.moved_from_);
+ rhs.moved_from_ = true;
+ rhs.ptr_ = nullptr;
+ }
+
+ LifetimeIterator& operator=(LifetimeIterator&& rhs) noexcept {
+ assert(!rhs.moved_from_);
+ rhs.moved_from_ = true;
+ moved_from_ = false;
+
+ ptr_ = rhs.ptr_;
+ rhs.ptr_ = nullptr;
+
+ return *this;
+ }
+
+ Reference operator*() const {
+ assert(!moved_from_);
+ return Reference(*ptr_);
+ }
+
+ LifetimeIterator& operator++() {
+ assert(!moved_from_);
+
+ ++ptr_;
+ return *this;
+ }
+
+ LifetimeIterator operator++(int) {
+ assert(!moved_from_);
+
+ auto tmp = *this;
+ ++*this;
+ return tmp;
+ }
+
+ friend bool operator==(const LifetimeIterator& lhs, const LifetimeIterator& rhs) {
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+ return lhs.ptr_ == rhs.ptr_;
+ }
+ friend bool operator!=(const LifetimeIterator& lhs, const LifetimeIterator& rhs) {
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+ return lhs.ptr_ != rhs.ptr_;
+ }
+
+ LifetimeIterator& operator--() {
+ assert(!moved_from_);
+
+ --ptr_;
+ return *this;
+ }
+
+ LifetimeIterator operator--(int) {
+ assert(!moved_from_);
+
+ auto tmp = *this;
+ --*this;
+ return tmp;
+ }
+
+ LifetimeIterator& operator+=(
diff erence_type n) {
+ assert(!moved_from_);
+
+ ptr_ += n;
+ return *this;
+ }
+
+ LifetimeIterator& operator-=(
diff erence_type n) {
+ assert(!moved_from_);
+
+ ptr_ -= n;
+ return *this;
+ }
+
+ Reference operator[](
diff erence_type i) const {
+ assert(!moved_from_);
+ return Reference(*(ptr_ + i));
+ }
+
+ friend bool operator<(const LifetimeIterator& lhs, const LifetimeIterator& rhs) {
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+ return lhs.ptr_ < rhs.ptr_;
+ }
+
+ friend bool operator>(const LifetimeIterator& lhs, const LifetimeIterator& rhs) {
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+ return lhs.ptr_ > rhs.ptr_;
+ }
+
+ friend bool operator<=(const LifetimeIterator& lhs, const LifetimeIterator& rhs) {
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+ return lhs.ptr_ <= rhs.ptr_;
+ }
+
+ friend bool operator>=(const LifetimeIterator& lhs, const LifetimeIterator& rhs) {
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+ return lhs.ptr_ >= rhs.ptr_;
+ }
+
+ friend LifetimeIterator operator+(const LifetimeIterator& lhs,
diff erence_type n) {
+ assert(!lhs.moved_from_);
+ return LifetimeIterator(lhs.ptr_ + n);
+ }
+
+ friend LifetimeIterator operator+(
diff erence_type n, const LifetimeIterator& lhs) {
+ assert(!lhs.moved_from_);
+ return LifetimeIterator(n + lhs.ptr_);
+ }
+
+ friend LifetimeIterator operator-(const LifetimeIterator& lhs,
diff erence_type n) {
+ assert(!lhs.moved_from_);
+ return LifetimeIterator(lhs.ptr_ - n);
+ }
+
+ friend
diff erence_type operator-(LifetimeIterator lhs, LifetimeIterator rhs) {
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+ return static_cast<int>(lhs.ptr_ - rhs.ptr_);
+ }
+
+ static LifetimeCache lifetime_cache;
+};
+
+LifetimeIterator::LifetimeCache LifetimeIterator::lifetime_cache;
+
+#if TEST_STD_VER > 17
+// A constexpr-friendly proxy iterator to check for undefined behavior in algorithms (since undefined behavior is
+// statically caught in `constexpr` context).
+class ConstexprIterator {
+ public:
+ struct Reference {
+ int* v_;
+ bool moved_from_ = false; // Check for double moves and reads after moving.
+
+ constexpr Reference(int& v) : v_(&v) { }
+
+ constexpr Reference(const Reference& rhs) = default;
+ constexpr Reference& operator=(const Reference& rhs) {
+ assert(!rhs.moved_from_);
+ v_ = rhs.v_;
+ moved_from_ = false;
+
+ return *this;
+ }
+
+ constexpr Reference(Reference&& rhs) noexcept : v_(rhs.v_) {
+ assert(!rhs.moved_from_);
+ rhs.moved_from_ = true;
+ }
+
+ constexpr Reference& operator=(Reference&& rhs) noexcept {
+ assert(!rhs.moved_from_);
+ rhs.moved_from_ = true;
+ moved_from_ = false;
+
+ v_ = rhs.v_;
+ return *this;
+ }
+
+ constexpr operator int() const {
+ assert(!moved_from_);
+ return *v_;
+ }
+
+ constexpr Reference& operator=(int v) {
+ *v_ = v;
+ moved_from_ = false;
+
+ return *this;
+ }
+
+ friend constexpr bool operator<(const Reference& lhs, const Reference& rhs) {
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+ return *lhs.v_ < *rhs.v_;
+ }
+
+ friend constexpr bool operator==(const Reference& lhs, const Reference& rhs) {
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+ return *lhs.v_ == *rhs.v_;
+ }
+
+ friend constexpr void swap(Reference lhs, Reference rhs) {
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+ std::swap(*(lhs.v_), *(rhs.v_));
+ }
+ };
+
+ using
diff erence_type = int;
+ using value_type = int;
+ using reference = Reference;
+ using pointer = void;
+ using iterator_category = std::random_access_iterator_tag;
+
+ int* ptr_ = nullptr;
+ bool moved_from_ = false; // Check for double moves and reads after moving.
+
+ constexpr ConstexprIterator() = default;
+ constexpr ConstexprIterator(int* ptr) : ptr_(ptr) {}
+
+ constexpr ConstexprIterator(const ConstexprIterator& rhs) : ptr_(rhs.ptr_) {
+ assert(!rhs.moved_from_);
+ }
+
+ constexpr ConstexprIterator& operator=(const ConstexprIterator& rhs) {
+ assert(!rhs.moved_from_);
+
+ ptr_ = rhs.ptr_;
+ moved_from_ = false;
+
+ return *this;
+ }
+
+ constexpr ConstexprIterator(ConstexprIterator&& rhs) noexcept : ptr_(rhs.ptr_) {
+ assert(!rhs.moved_from_);
+ rhs.moved_from_ = true;
+ rhs.ptr_ = nullptr;
+ }
+
+ constexpr ConstexprIterator& operator=(ConstexprIterator&& rhs) noexcept {
+ assert(!rhs.moved_from_);
+ rhs.moved_from_ = true;
+ moved_from_ = false;
+
+ ptr_ = rhs.ptr_;
+ rhs.ptr_ = nullptr;
+
+ return *this;
+ }
+
+ constexpr Reference operator*() const {
+ assert(!moved_from_);
+ return Reference(*ptr_);
+ }
+
+ constexpr ConstexprIterator& operator++() {
+ assert(!moved_from_);
+
+ ++ptr_;
+ return *this;
+ }
+
+ constexpr ConstexprIterator operator++(int) {
+ assert(!moved_from_);
+
+ auto tmp = *this;
+ ++*this;
+ return tmp;
+ }
+
+ friend constexpr bool operator==(const ConstexprIterator& lhs, const ConstexprIterator& rhs) {
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+ return lhs.ptr_ == rhs.ptr_;
+ }
+
+ friend constexpr bool operator!=(const ConstexprIterator& lhs, const ConstexprIterator& rhs) {
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+ return lhs.ptr_ != rhs.ptr_;
+ }
+
+ constexpr ConstexprIterator& operator--() {
+ assert(!moved_from_);
+
+ --ptr_;
+ return *this;
+ }
+
+ constexpr ConstexprIterator operator--(int) {
+ assert(!moved_from_);
+
+ auto tmp = *this;
+ --*this;
+ return tmp;
+ }
+
+ constexpr ConstexprIterator& operator+=(
diff erence_type n) {
+ assert(!moved_from_);
+
+ ptr_ += n;
+ return *this;
+ }
+
+ constexpr ConstexprIterator& operator-=(
diff erence_type n) {
+ assert(!moved_from_);
+
+ ptr_ -= n;
+ return *this;
+ }
+
+ constexpr Reference operator[](
diff erence_type i) const {
+ return Reference(*(ptr_ + i));
+ }
+
+ friend constexpr auto operator<=>(const ConstexprIterator& lhs, const ConstexprIterator& rhs) {
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+ return lhs.ptr_ <=> rhs.ptr_;
+ }
+
+ friend constexpr ConstexprIterator operator+(const ConstexprIterator& lhs,
diff erence_type n) {
+ assert(!lhs.moved_from_);
+ return ConstexprIterator(lhs.ptr_ + n);
+ }
+
+ friend constexpr ConstexprIterator operator+(
diff erence_type n, const ConstexprIterator& lhs) {
+ assert(!lhs.moved_from_);
+ return ConstexprIterator(n + lhs.ptr_);
+ }
+
+ friend constexpr ConstexprIterator operator-(const ConstexprIterator& lhs,
diff erence_type n) {
+ assert(!lhs.moved_from_);
+ return ConstexprIterator(lhs.ptr_ - n);
+ }
+
+ friend constexpr
diff erence_type operator-(ConstexprIterator lhs, ConstexprIterator rhs) {
+ assert(!lhs.moved_from_ && !rhs.moved_from_);
+ return static_cast<int>(lhs.ptr_ - rhs.ptr_);
+ }
+};
+
+#endif // TEST_STD_VER > 17
+
+template <class T, size_t N = 32>
+class Input {
+ using Array = std::array<T, N>;
+
+ size_t size_ = 0;
+ Array values_ = {};
+
+public:
+ template <size_t N2>
+ TEST_CONSTEXPR_CXX20 Input(std::array<T, N2> from) {
+ static_assert(N2 <= N);
+
+ std::copy(from.begin(), from.end(), begin());
+ size_ = N2;
+ }
+
+ TEST_CONSTEXPR_CXX20 typename Array::iterator begin() { return values_.begin(); }
+ TEST_CONSTEXPR_CXX20 typename Array::iterator end() { return values_.begin() + size_; }
+ TEST_CONSTEXPR_CXX20 size_t size() const { return size_; }
+};
+
+// TODO: extend `Value` and `Reference` so that it's possible to pass plain integers to all the algorithms.
+
+// Several generic inputs that are useful for many algorithms. Provides two unsorted sequences with and without
+// duplicates, with positive and negative values; and a few corner cases, like an empty sequence, a sequence of all
+// duplicates, and so on.
+template <class Iter>
+TEST_CONSTEXPR_CXX20 std::array<Input<typename Iter::value_type>, 8> get_simple_in() {
+ using T = typename Iter::value_type;
+ std::array<Input<T>, 8> result = {
+ Input<T>({std::array<T, 0>{ }}),
+ Input<T>({std::array<T, 1>{ T{1} }}),
+ Input<T>({std::array<T, 1>{ T{-1} }}),
+ Input<T>({std::array<T, 2>{ T{-1}, {1} }}),
+ Input<T>({std::array<T, 3>{ T{1}, {1}, {1} }}),
+ Input<T>({std::array<T, 3>{ T{-1}, {-1}, {-1} }}),
+ Input<T>({std::array<T, 9>{ T{-8}, {6}, {3}, {2}, {1}, {5}, {-4}, {-9}, {3} }}),
+ Input<T>({std::array<T, 9>{ T{-8}, {3}, {3}, {2}, {5}, {-4}, {-4}, {-4}, {1} }}),
+ };
+ return result;
+}
+
+// Sorted inputs of varying lengths.
+template <class Iter>
+TEST_CONSTEXPR_CXX20 std::array<Input<typename Iter::value_type>, 8> get_sorted_in() {
+ using T = typename Iter::value_type;
+ std::array<Input<T>, 8> result = {
+ Input<T>({std::array<T, 0>{ }}),
+ Input<T>({std::array<T, 1>{ T{1} }}),
+ Input<T>({std::array<T, 1>{ T{-1} }}),
+ Input<T>({std::array<T, 2>{ T{-1}, {1} }}),
+ Input<T>({std::array<T, 3>{ T{1}, {1}, {1} }}),
+ Input<T>({std::array<T, 3>{ T{-1}, {-1}, {-1} }}),
+ Input<T>({std::array<T, 8>{ T{-8}, {-5}, {-3}, {-1}, {1}, {4}, {5}, {9} }}),
+ Input<T>({std::array<T, 11>{ T{-8}, {-5}, {-3}, {-3}, {-1}, {1}, {4}, {5}, {5}, {9}, {9} }}),
+ };
+ return result;
+}
+
+// Inputs for testing `std::sort`. These have been manually verified to exercise all internal functions in `std::sort`
+// except the branchless sort ones (which can't be triggered with proxy arrays).
+template <class Iter>
+TEST_CONSTEXPR_CXX20 std::array<Input<typename Iter::value_type>, 8> get_sort_test_in() {
+ using T = typename Iter::value_type;
+ std::array<Input<T>, 8> result = {
+ Input<T>({std::array<T, 0>{ }}),
+ Input<T>({std::array<T, 1>{ T{1} }}),
+ Input<T>({std::array<T, 1>{ T{-1} }}),
+ Input<T>({std::array<T, 2>{ T{-1}, {1} }}),
+ Input<T>({std::array<T, 3>{ T{1}, {1}, {1} }}),
+ Input<T>({std::array<T, 3>{ T{-1}, {-1}, {-1} }}),
+ Input<T>({std::array<T, 8>{ T{-8}, {-5}, {-3}, {-1}, {1}, {4}, {5}, {9} }}),
+ Input<T>({std::array<T, 11>{ T{-8}, {-5}, {-3}, {-3}, {-1}, {1}, {4}, {5}, {5}, {9}, {9} }}),
+ };
+ return result;
+}
+
+template <class Input, size_t N, class Func>
+TEST_CONSTEXPR_CXX20 void test(std::array<Input, N> inputs, Func func) {
+ for (auto&& in : inputs) {
+ func(in.begin(), in.end());
+ }
+}
+
+template <class Input, size_t N, class Func>
+TEST_CONSTEXPR_CXX20 void test_n(std::array<Input, N> inputs, Func func) {
+ for (auto&& in : inputs) {
+ func(in.begin(), in.size());
+ }
+}
+
+constexpr int to_int(int x) { return x; }
+int to_int(LifetimeIterator::Value x) { return x.i_; }
+
+std::mt19937 rand_gen() { return std::mt19937(); }
+
+template <class Iter>
+TEST_CONSTEXPR_CXX20 bool test() {
+ using T = typename Iter::value_type;
+
+ auto is_neg = [](const T& val) { return to_int(val) < 0; };
+ auto gen = [] { return T{42}; };
+ auto identity = [] (T val) -> T { return val; };
+
+ constexpr int N = 32;
+ std::array<T, N> output;
+ auto out = output.begin();
+ T x{1};
+ T y{3};
+
+ auto simple_in = get_simple_in<Iter>();
+ auto sorted_in = get_sorted_in<Iter>();
+ auto sort_test_in = get_sort_test_in<Iter>();
+
+ using I = Iter;
+
+ test(simple_in, [&](I b, I e) { std::any_of(b, e, is_neg); });
+ test(simple_in, [&](I b, I e) { std::all_of(b, e, is_neg); });
+ test(simple_in, [&](I b, I e) { std::none_of(b, e, is_neg); });
+ test(simple_in, [&](I b, I e) { std::find(b, e, T{1}); });
+ test(simple_in, [&](I b, I e) { std::find_if(b, e, is_neg); });
+ test(simple_in, [&](I b, I e) { std::find_if_not(b, e, is_neg); });
+ // TODO: find_first_of
+ test(simple_in, [&](I b, I e) { std::adjacent_find(b, e); });
+ // TODO: mismatch
+ // TODO: equal
+ // TODO: lexicographical_compare
+ // TODO: partition_point
+ test(sorted_in, [&](I b, I e) { std::lower_bound(b, e, x); });
+ test(sorted_in, [&](I b, I e) { std::upper_bound(b, e, x); });
+ test(sorted_in, [&](I b, I e) { std::equal_range(b, e, x); });
+ test(sorted_in, [&](I b, I e) { std::binary_search(b, e, x); });
+ // `min`, `max` and `minmax` don't use iterators.
+ test(simple_in, [&](I b, I e) { std::min_element(b, e); });
+ test(simple_in, [&](I b, I e) { std::max_element(b, e); });
+ test(simple_in, [&](I b, I e) { std::minmax_element(b, e); });
+ test(simple_in, [&](I b, I e) { std::count(b, e, x); });
+ test(simple_in, [&](I b, I e) { std::count_if(b, e, is_neg); });
+ // TODO: search
+ // TODO: search_n
+ // TODO: find_end
+ // TODO: is_partitioned
+ // TODO: is_sorted
+ // TODO: is_sorted_until
+ // TODO: includes
+ // TODO: is_heap
+ // TODO: is_heap_until
+ // `clamp` doesn't use iterators.
+ // TODO: is_permutation
+ test(simple_in, [&](I b, I e) { std::for_each(b, e, is_neg); });
+#if TEST_STD_VER > 14
+ test_n(simple_in, [&](I b, size_t n) { std::for_each_n(b, n, is_neg); });
+#endif
+ test(simple_in, [&](I b, I e) { std::copy(b, e, out); });
+ test_n(simple_in, [&](I b, size_t n) { std::copy_n(b, n, out); });
+ test(simple_in, [&](I b, I e) { std::copy_backward(b, e, out + N); });
+ test(simple_in, [&](I b, I e) { std::copy_if(b, e, out, is_neg); });
+ test(simple_in, [&](I b, I e) { std::move(b, e, out); });
+ test(simple_in, [&](I b, I e) { std::move_backward(b, e, out + N); });
+ test(simple_in, [&](I b, I e) { std::transform(b, e, out, identity); });
+ test(simple_in, [&](I b, I e) { std::generate(b, e, gen); });
+ test_n(simple_in, [&](I b, size_t n) { std::generate_n(b, n, gen); });
+ test(simple_in, [&](I b, I e) { std::remove_copy(b, e, out, x); });
+ test(simple_in, [&](I b, I e) { std::remove_copy_if(b, e, out, is_neg); });
+ test(simple_in, [&](I b, I e) { std::replace(b, e, x, y); });
+ test(simple_in, [&](I b, I e) { std::replace_if(b, e, is_neg, y); });
+ test(simple_in, [&](I b, I e) { std::replace_copy(b, e, out, x, y); });
+ test(simple_in, [&](I b, I e) { std::replace_copy_if(b, e, out, is_neg, y); });
+ // TODO: swap_ranges
+ test(simple_in, [&](I b, I e) { std::reverse_copy(b, e, out); });
+ // TODO: rotate_copy
+ // TODO: sample
+ // TODO: unique_copy
+ // TODO: partition_copy
+ // TODO: partial_sort_copy
+ // TODO: merge
+ // TODO: set_
diff erence
+ // TODO: set_intersection
+ // TODO: set_symmetric_
diff erence
+ // TODO: set_union
+ test(simple_in, [&](I b, I e) { std::remove(b, e, x); });
+ test(simple_in, [&](I b, I e) { std::remove_if(b, e, is_neg); });
+ test(simple_in, [&](I b, I e) { std::reverse(b, e); });
+ // TODO: rotate
+ if (!TEST_IS_CONSTANT_EVALUATED)
+ test(simple_in, [&](I b, I e) { std::shuffle(b, e, rand_gen()); });
+ // TODO: unique
+ test(simple_in, [&](I b, I e) { std::partition(b, e, is_neg); });
+ if (!TEST_IS_CONSTANT_EVALUATED)
+ test(simple_in, [&](I b, I e) { std::stable_partition(b, e, is_neg); });
+ if (!TEST_IS_CONSTANT_EVALUATED)
+ test(sort_test_in, [&](I b, I e) { std::sort(b, e); });
+ if (!TEST_IS_CONSTANT_EVALUATED)
+ test(sort_test_in, [&](I b, I e) { std::stable_sort(b, e); });
+ // TODO: partial_sort
+ // TODO: nth_element
+ // TODO: inplace_merge
+ test(simple_in, [&](I b, I e) { std::make_heap(b, e); });
+ // TODO: push_heap
+ // TODO: pop_heap
+ // TODO: sort_heap
+ test(simple_in, [&](I b, I e) { std::prev_permutation(b, e); });
+ test(simple_in, [&](I b, I e) { std::next_permutation(b, e); });
+
+ // TODO: algorithms in `<numeric>`
+ // TODO: algorithms in `<memory>`
+
+ return true;
+}
+
+void test_all() {
+ test<LifetimeIterator>();
+#if TEST_STD_VER > 17 // Most algorithms are only `constexpr` starting from C++20.
+ static_assert(test<ConstexprIterator>());
+#endif
+}
+
+int main(int, char**) {
+ test_all();
+
+ return 0;
+}
More information about the libcxx-commits
mailing list