// Copyright 2020 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/containers/checked_iterators.h" #include #include #include "base/check_op.h" #include "base/debug/alias.h" #include "base/ranges/algorithm.h" #include "base/test/gtest_util.h" #include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { TEST(CheckedContiguousIterator, SatisfiesContiguousIteratorConcept) { static_assert(std::contiguous_iterator>); } template constexpr CheckedContiguousConstIterator MakeConstIter(T (&arr)[N], size_t cur) { // We allow cur == N as that makes a pointer at one-past-the-end which is // considered part of the same allocation. CHECK_LE(cur, N); return // SAFETY: `arr` has 1 element, `arr + 1` is considered a pointer into the // same allocation, as it's one past the end. UNSAFE_BUFFERS( CheckedContiguousConstIterator(arr, arr + cur, arr + N)); } template constexpr CheckedContiguousIterator MakeIter(T (&arr)[N], size_t cur) { // We allow cur == N as that makes a pointer at one-past-the-end which is // considered part of the same allocation. CHECK_LE(cur, N); return // SAFETY: `arr` has 1 element, `arr + 1` is considered a pointer into the // same allocation, as it's one past the end. UNSAFE_BUFFERS(CheckedContiguousIterator(arr, arr + cur, arr + N)); } // Checks that constexpr CheckedContiguousConstIterators can be compared at // compile time. TEST(CheckedContiguousIterator, StaticComparisonOperators) { static constexpr int arr[] = {0}; constexpr CheckedContiguousConstIterator begin = MakeConstIter(arr, 0u); constexpr CheckedContiguousConstIterator end = MakeConstIter(arr, 1u); static_assert(begin == begin); static_assert(end == end); static_assert(begin != end); static_assert(end != begin); static_assert(begin < end); static_assert(begin <= begin); static_assert(begin <= end); static_assert(end <= end); static_assert(end > begin); static_assert(end >= end); static_assert(end >= begin); static_assert(begin >= begin); } // Checks that comparison between iterators and const iterators works in both // directions. TEST(CheckedContiguousIterator, ConvertingComparisonOperators) { static int arr[] = {0}; CheckedContiguousIterator begin = MakeIter(arr, 0u); CheckedContiguousConstIterator cbegin = MakeConstIter(arr, 0u); CheckedContiguousIterator end = MakeIter(arr, 1u); CheckedContiguousConstIterator cend = MakeConstIter(arr, 1u); EXPECT_EQ(begin, cbegin); EXPECT_EQ(cbegin, begin); EXPECT_EQ(end, cend); EXPECT_EQ(cend, end); EXPECT_NE(begin, cend); EXPECT_NE(cbegin, end); EXPECT_NE(end, cbegin); EXPECT_NE(cend, begin); EXPECT_LT(begin, cend); EXPECT_LT(cbegin, end); EXPECT_LE(begin, cbegin); EXPECT_LE(cbegin, begin); EXPECT_LE(begin, cend); EXPECT_LE(cbegin, end); EXPECT_LE(end, cend); EXPECT_LE(cend, end); EXPECT_GT(end, cbegin); EXPECT_GT(cend, begin); EXPECT_GE(end, cend); EXPECT_GE(cend, end); EXPECT_GE(end, cbegin); EXPECT_GE(cend, begin); EXPECT_GE(begin, cbegin); EXPECT_GE(cbegin, begin); } TEST(CheckedContiguousIteratorDeathTest, OutOfBounds) { static int arr[] = {0, 1, 2}; CheckedContiguousIterator it = MakeIter(arr, 1u); EXPECT_CHECK_DEATH(base::debug::Alias(&it[-2])); EXPECT_EQ(it[-1], 0); EXPECT_EQ(it[0], 1); EXPECT_EQ(it[1], 2); EXPECT_CHECK_DEATH(base::debug::Alias(&it[3])); it += 2; // At [3], in bounds (at end). it -= 3; // At [0], in bounds. it += 1; // Back to [1], in bounds. EXPECT_CHECK_DEATH({ it -= 2; base::debug::Alias(&it); }); EXPECT_CHECK_DEATH({ it += 3; base::debug::Alias(&it); }); EXPECT_CHECK_DEATH({ auto o = it - 2; base::debug::Alias(&o); }); EXPECT_CHECK_DEATH({ auto o = it + 3; base::debug::Alias(&o); }); it++; // At [2], in bounds. ++it; // At [3], in bounds (at end). EXPECT_CHECK_DEATH({ ++it; base::debug::Alias(&it); }); EXPECT_CHECK_DEATH({ it++; base::debug::Alias(&it); }); it -= 3; // At [0], in bounds. EXPECT_CHECK_DEATH({ --it; base::debug::Alias(&it); }); EXPECT_CHECK_DEATH({ it--; base::debug::Alias(&it); }); } } // namespace base namespace { // Helper template that wraps an iterator and disables its dereference and // increment operations. template struct DisableDerefAndIncr : Iterator { using Iterator::Iterator; // NOLINTNEXTLINE(google-explicit-constructor) constexpr DisableDerefAndIncr(const Iterator& iter) : Iterator(iter) {} void operator*() = delete; void operator++() = delete; void operator++(int) = delete; }; } // namespace // Inherit `pointer_traits` specialization from the base class. template struct std::pointer_traits> : ::std::pointer_traits {}; namespace base { // Tests that using std::copy with CheckedContiguousIterator results in an // optimized code-path that does not invoke the iterator's dereference and // increment operations, as expected in libc++. This fails to compile if // std::copy is not optimized. // NOTE: This test relies on implementation details of the STL and thus might // break in the future during a libc++ roll. If this does happen, please reach // out to memory-safety-dev@chromium.org to reevaluate whether this test will // still be needed. #if defined(_LIBCPP_VERSION) TEST(CheckedContiguousIterator, OptimizedCopy) { using Iter = DisableDerefAndIncr>; int arr_in[5] = {1, 2, 3, 4, 5}; int arr_out[5]; Iter in_begin = MakeIter(arr_in, 0u); Iter in_end = MakeIter(arr_in, 5u); Iter out_begin = MakeIter(arr_out, 0u); Iter out_end = std::copy(in_begin, in_end, out_begin); EXPECT_EQ(out_end, out_begin + (in_end - in_begin)); EXPECT_TRUE(ranges::equal(arr_in, arr_out)); } #endif // defined(_LIBCPP_VERSION) } // namespace base