diff --git a/libcxx/cmake/caches/Generic-hardening-mode-fast-with-abi-breaks.cmake b/libcxx/cmake/caches/Generic-hardening-mode-fast-with-abi-breaks.cmake index 4860b590dcde9..4a9389fdcb41c 100644 --- a/libcxx/cmake/caches/Generic-hardening-mode-fast-with-abi-breaks.cmake +++ b/libcxx/cmake/caches/Generic-hardening-mode-fast-with-abi-breaks.cmake @@ -1,2 +1,4 @@ set(LIBCXX_HARDENING_MODE "fast" CACHE STRING "") set(LIBCXX_ABI_DEFINES "_LIBCPP_ABI_BOUNDED_ITERATORS" CACHE STRING "") +set(LIBCXX_ABI_DEFINES "_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING" CACHE STRING "") +set(LIBCXX_ABI_DEFINES "_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR" CACHE STRING "") diff --git a/libcxx/docs/Hardening.rst b/libcxx/docs/Hardening.rst index 9aac059d27ecf..67791a5e55ac7 100644 --- a/libcxx/docs/Hardening.rst +++ b/libcxx/docs/Hardening.rst @@ -325,6 +325,22 @@ Vendors can use the following ABI options to enable additional hardening checks: - ``span``; - ``string_view``. +- ``_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING`` -- changes the iterator type of + ``basic_string`` to a bounded iterator that keeps track of whether it's within + the bounds of the original container and asserts it on every dereference and + when performing iterator arithmetics. + + ABI impact: changes the iterator type of ``basic_string`` and its + specializations, such as ``string`` and ``wstring``. + +- ``_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR`` -- changes the iterator type of + ``vector`` to a bounded iterator that keeps track of whether it's within the + bounds of the original container and asserts it on every dereference and when + performing iterator arithmetics. Note: this doesn't yet affect + ``vector``. + + ABI impact: changes the iterator type of ``vector`` (except ``vector``). + ABI tags -------- @@ -367,10 +383,10 @@ Hardened containers status - ❌ * - ``vector`` - ✅ - - ❌ + - ✅ (see note) * - ``string`` - ✅ - - ❌ + - ✅ (see note) * - ``list`` - ✅ - ❌ @@ -429,6 +445,12 @@ Hardened containers status - ❌ - N/A +Note: for ``vector`` and ``string``, the iterator does not check for +invalidation (accesses made via an invalidated iterator still lead to undefined +behavior) + +Note: ``vector`` iterator is not currently hardened. + Testing ======= diff --git a/libcxx/docs/ReleaseNotes/19.rst b/libcxx/docs/ReleaseNotes/19.rst index 43767552960e4..439f552db59a8 100644 --- a/libcxx/docs/ReleaseNotes/19.rst +++ b/libcxx/docs/ReleaseNotes/19.rst @@ -87,6 +87,20 @@ Improvements and New Features - ``std::ignore``\s ``const __ignore_t& operator=(_Tp&&) const`` was changed to ``const __ignore_type& operator=(const _Tp&) const noexcept`` for all language versions. +- Vendors can now configure the ABI so that ``string`` and ``vector`` will use bounded iterators when hardening is + enabled. Note that checks for iterator invalidation are currently not supported -- any accesses made through an + invalidated bounded iterator will still result in undefined behavior (bounded iterators follow the normal invalidation + rules of the associated container). ``string`` bounded iterators use the logical size of the container (``index + < str.size()``) whereas ``vector`` bounded iterators use the "physical" size of the container (``index + < vec.capacity()``) which is a less strict check; refer to the implementation for further details. + + Bounded iterators can be enabled via the ``_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING`` ABI macro for ``string`` and via + the ``_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR`` ABI macro for ``vector``; note that checks will only be performed if + the hardening mode is set to ``fast`` or above (i.e., no checking is performed in the unchecked mode, even if bounded + iterators are enabled in the ABI configuration). + + Note: bounded iterators currently are not supported for ``vector``. + Deprecations and Removals ------------------------- diff --git a/libcxx/include/__configuration/abi.h b/libcxx/include/__configuration/abi.h index 710548d90a649..0422b645727d8 100644 --- a/libcxx/include/__configuration/abi.h +++ b/libcxx/include/__configuration/abi.h @@ -141,6 +141,19 @@ // - `string_view`. // #define _LIBCPP_ABI_BOUNDED_ITERATORS +// Changes the iterator type of `basic_string` to a bounded iterator that keeps track of whether it's within the bounds +// of the original container and asserts it on every dereference and when performing iterator arithmetics. +// +// ABI impact: changes the iterator type of `basic_string` and its specializations, such as `string` and `wstring`. +// #define _LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING + +// Changes the iterator type of `vector` to a bounded iterator that keeps track of whether it's within the bounds of the +// original container and asserts it on every dereference and when performing iterator arithmetics. Note: this doesn't +// yet affect `vector`. +// +// ABI impact: changes the iterator type of `vector` (except `vector`). +// #define _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR + #if defined(_LIBCPP_COMPILER_CLANG_BASED) # if defined(__APPLE__) # if defined(__i386__) || defined(__x86_64__) diff --git a/libcxx/include/__iterator/bounded_iter.h b/libcxx/include/__iterator/bounded_iter.h index a8f66f4a0126f..ce0823b8c97e4 100644 --- a/libcxx/include/__iterator/bounded_iter.h +++ b/libcxx/include/__iterator/bounded_iter.h @@ -225,6 +225,8 @@ struct __bounded_iter { private: template friend struct pointer_traits; + template + friend struct __bounded_iter; _Iterator __current_; // current iterator _Iterator __begin_, __end_; // valid range represented as [begin, end] }; diff --git a/libcxx/include/string b/libcxx/include/string index 2fd1b1e745908..ba86a32090825 100644 --- a/libcxx/include/string +++ b/libcxx/include/string @@ -598,6 +598,7 @@ basic_string operator""s( const char32_t *str, size_t len ); #include <__functional/unary_function.h> #include <__fwd/string.h> #include <__ios/fpos.h> +#include <__iterator/bounded_iter.h> #include <__iterator/distance.h> #include <__iterator/iterator_traits.h> #include <__iterator/reverse_iterator.h> @@ -822,9 +823,16 @@ public: "Allocator::value_type must be same type as value_type"); static_assert(__check_valid_allocator::value, ""); - // TODO: Implement iterator bounds checking without requiring the global database. +#ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING + // Users might provide custom allocators, and prior to C++20 we have no existing way to detect whether the allocator's + // pointer type is contiguous (though it has to be by the Standard). Using the wrapper type ensures the iterator is + // considered contiguous. + typedef __bounded_iter<__wrap_iter> iterator; + typedef __bounded_iter<__wrap_iter> const_iterator; +#else typedef __wrap_iter iterator; typedef __wrap_iter const_iterator; +#endif typedef std::reverse_iterator reverse_iterator; typedef std::reverse_iterator const_reverse_iterator; @@ -940,10 +948,33 @@ private: __init_with_sentinel(std::move(__first), std::move(__last)); } - _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 iterator __make_iterator(pointer __p) { return iterator(__p); } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 iterator __make_iterator(pointer __p) { +#ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING + // Bound the iterator according to the size (and not the capacity, unlike vector). + // + // By the Standard, string iterators are generally not guaranteed to stay valid when the container is modified, + // regardless of whether reallocation occurs. This allows us to check for out-of-bounds accesses using logical size, + // a stricter check, since correct code can never rely on being able to access newly-added elements via an existing + // iterator. + return std::__make_bounded_iter( + std::__wrap_iter(__p), + std::__wrap_iter(__get_pointer()), + std::__wrap_iter(__get_pointer() + size())); +#else + return iterator(__p); +#endif // _LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING + } _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 const_iterator __make_const_iterator(const_pointer __p) const { +#ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING + // Bound the iterator according to the size (and not the capacity, unlike vector). + return std::__make_bounded_iter( + std::__wrap_iter(__p), + std::__wrap_iter(__get_pointer()), + std::__wrap_iter(__get_pointer() + size())); +#else return const_iterator(__p); +#endif // _LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING } public: diff --git a/libcxx/include/vector b/libcxx/include/vector index 45980043a3c15..4d83d4b6edda8 100644 --- a/libcxx/include/vector +++ b/libcxx/include/vector @@ -327,6 +327,7 @@ template requires is-vector-bool-reference // Since C++ #include <__functional/unary_function.h> #include <__fwd/vector.h> #include <__iterator/advance.h> +#include <__iterator/bounded_iter.h> #include <__iterator/distance.h> #include <__iterator/iterator_traits.h> #include <__iterator/reverse_iterator.h> @@ -400,9 +401,16 @@ public: typedef typename __alloc_traits::difference_type difference_type; typedef typename __alloc_traits::pointer pointer; typedef typename __alloc_traits::const_pointer const_pointer; - // TODO: Implement iterator bounds checking without requiring the global database. +#ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR + // Users might provide custom allocators, and prior to C++20 we have no existing way to detect whether the allocator's + // pointer type is contiguous (though it has to be by the Standard). Using the wrapper type ensures the iterator is + // considered contiguous. + typedef __bounded_iter<__wrap_iter> iterator; + typedef __bounded_iter<__wrap_iter> const_iterator; +#else typedef __wrap_iter iterator; typedef __wrap_iter const_iterator; +#endif typedef std::reverse_iterator reverse_iterator; typedef std::reverse_iterator const_reverse_iterator; @@ -827,12 +835,39 @@ private: _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __append(size_type __n); _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __append(size_type __n, const_reference __x); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator __make_iter(pointer __p) _NOEXCEPT { +#ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR + // Bound the iterator according to the capacity, rather than the size. + // + // Vector guarantees that iterators stay valid as long as no reallocation occurs even if new elements are inserted + // into the container; for these cases, we need to make sure that the newly-inserted elements can be accessed + // through the bounded iterator without failing checks. The downside is that the bounded iterator won't catch + // access that is logically out-of-bounds, i.e., goes beyond the size, but is still within the capacity. With the + // current implementation, there is no connection between a bounded iterator and its associated container, so we + // don't have a way to update existing valid iterators when the container is resized and thus have to go with + // a laxer approach. + return std::__make_bounded_iter( + std::__wrap_iter(__p), + std::__wrap_iter(this->__begin_), + std::__wrap_iter(this->__end_cap())); +#else return iterator(__p); +#endif // _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR } + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_iterator __make_iter(const_pointer __p) const _NOEXCEPT { +#ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR + // Bound the iterator according to the capacity, rather than the size. + return std::__make_bounded_iter( + std::__wrap_iter(__p), + std::__wrap_iter(this->__begin_), + std::__wrap_iter(this->__end_cap())); +#else return const_iterator(__p); +#endif // _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR } + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __swap_out_circular_buffer(__split_buffer& __v); _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer diff --git a/libcxx/test/libcxx/containers/sequences/vector/abi.compile.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/abi.compile.pass.cpp index b03f48434a0e0..5dcbdad41968a 100644 --- a/libcxx/test/libcxx/containers/sequences/vector/abi.compile.pass.cpp +++ b/libcxx/test/libcxx/containers/sequences/vector/abi.compile.pass.cpp @@ -14,6 +14,14 @@ template class small_pointer { +public: + using value_type = T; + using difference_type = std::int16_t; + using pointer = small_pointer; + using reference = T&; + using iterator_category = std::random_access_iterator_tag; + +private: std::uint16_t offset; }; diff --git a/libcxx/test/libcxx/containers/sequences/vector/debug.iterator.add.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/assert.iterator.add.pass.cpp similarity index 64% rename from libcxx/test/libcxx/containers/sequences/vector/debug.iterator.add.pass.cpp rename to libcxx/test/libcxx/containers/sequences/vector/assert.iterator.add.pass.cpp index 42021824ce6ae..a066ad30ebd71 100644 --- a/libcxx/test/libcxx/containers/sequences/vector/debug.iterator.add.pass.cpp +++ b/libcxx/test/libcxx/containers/sequences/vector/assert.iterator.add.pass.cpp @@ -10,13 +10,14 @@ // Add to iterator out of bounds. -// REQUIRES: has-unix-headers -// UNSUPPORTED: !libcpp-has-legacy-debug-mode, c++03 +// REQUIRES: has-unix-headers, libcpp-has-abi-bounded-iterators-in-vector +// UNSUPPORTED: libcpp-hardening-mode=none, c++03 #include #include #include "check_assertion.h" +#include "fill_to_capacity.h" #include "min_allocator.h" int main(int, char**) { @@ -24,22 +25,24 @@ int main(int, char**) { typedef int T; typedef std::vector C; C c(1); + fill_to_capacity(c); C::iterator i = c.begin(); - i += 1; + i += c.size(); assert(i == c.end()); i = c.begin(); - TEST_LIBCPP_ASSERT_FAILURE(i + 2, "Attempted to add/subtract an iterator outside its valid range"); + TEST_LIBCPP_ASSERT_FAILURE(i + 2, "__bounded_iter::operator+=: Attempt to advance an iterator past the end"); } { typedef int T; typedef std::vector > C; C c(1); + fill_to_capacity(c); C::iterator i = c.begin(); - i += 1; + i += c.size(); assert(i == c.end()); i = c.begin(); - TEST_LIBCPP_ASSERT_FAILURE(i + 2, "Attempted to add/subtract an iterator outside its valid range"); + TEST_LIBCPP_ASSERT_FAILURE(i + 2, "__bounded_iter::operator+=: Attempt to advance an iterator past the end"); } return 0; diff --git a/libcxx/test/libcxx/containers/sequences/vector/debug.iterator.decrement.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/assert.iterator.decrement.pass.cpp similarity index 70% rename from libcxx/test/libcxx/containers/sequences/vector/debug.iterator.decrement.pass.cpp rename to libcxx/test/libcxx/containers/sequences/vector/assert.iterator.decrement.pass.cpp index d134527a967e5..59b9c16a6aa0e 100644 --- a/libcxx/test/libcxx/containers/sequences/vector/debug.iterator.decrement.pass.cpp +++ b/libcxx/test/libcxx/containers/sequences/vector/assert.iterator.decrement.pass.cpp @@ -10,8 +10,8 @@ // Decrement iterator prior to begin. -// REQUIRES: has-unix-headers -// UNSUPPORTED: !libcpp-has-legacy-debug-mode, c++03 +// REQUIRES: has-unix-headers, libcpp-has-abi-bounded-iterators-in-vector +// UNSUPPORTED: libcpp-hardening-mode=none, c++03 #include #include @@ -27,7 +27,7 @@ int main(int, char**) { C::iterator i = c.end(); --i; assert(i == c.begin()); - TEST_LIBCPP_ASSERT_FAILURE(--i, "Attempted to decrement a non-decrementable iterator"); + TEST_LIBCPP_ASSERT_FAILURE(--i, "__bounded_iter::operator--: Attempt to rewind an iterator past the start"); } { @@ -37,7 +37,7 @@ int main(int, char**) { C::iterator i = c.end(); --i; assert(i == c.begin()); - TEST_LIBCPP_ASSERT_FAILURE(--i, "Attempted to decrement a non-decrementable iterator"); + TEST_LIBCPP_ASSERT_FAILURE(--i, "__bounded_iter::operator--: Attempt to rewind an iterator past the start"); } return 0; diff --git a/libcxx/test/libcxx/containers/sequences/vector/debug.iterator.dereference.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/assert.iterator.dereference.pass.cpp similarity index 64% rename from libcxx/test/libcxx/containers/sequences/vector/debug.iterator.dereference.pass.cpp rename to libcxx/test/libcxx/containers/sequences/vector/assert.iterator.dereference.pass.cpp index 918cdd74b7916..877d3655fbe2e 100644 --- a/libcxx/test/libcxx/containers/sequences/vector/debug.iterator.dereference.pass.cpp +++ b/libcxx/test/libcxx/containers/sequences/vector/assert.iterator.dereference.pass.cpp @@ -10,12 +10,13 @@ // Dereference non-dereferenceable iterator. -// REQUIRES: has-unix-headers -// UNSUPPORTED: !libcpp-has-legacy-debug-mode, c++03 +// REQUIRES: has-unix-headers, libcpp-has-abi-bounded-iterators-in-vector +// UNSUPPORTED: libcpp-hardening-mode=none, c++03 #include #include "check_assertion.h" +#include "fill_to_capacity.h" #include "min_allocator.h" int main(int, char**) { @@ -23,16 +24,18 @@ int main(int, char**) { typedef int T; typedef std::vector C; C c(1); + fill_to_capacity(c); C::iterator i = c.end(); - TEST_LIBCPP_ASSERT_FAILURE(*i, "Attempted to dereference a non-dereferenceable iterator"); + TEST_LIBCPP_ASSERT_FAILURE(*i, "__bounded_iter::operator*: Attempt to dereference an iterator at the end"); } { typedef int T; typedef std::vector > C; C c(1); + fill_to_capacity(c); C::iterator i = c.end(); - TEST_LIBCPP_ASSERT_FAILURE(*i, "Attempted to dereference a non-dereferenceable iterator"); + TEST_LIBCPP_ASSERT_FAILURE(*i, "__bounded_iter::operator*: Attempt to dereference an iterator at the end"); } return 0; diff --git a/libcxx/test/libcxx/containers/sequences/vector/debug.iterator.increment.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/assert.iterator.increment.pass.cpp similarity index 63% rename from libcxx/test/libcxx/containers/sequences/vector/debug.iterator.increment.pass.cpp rename to libcxx/test/libcxx/containers/sequences/vector/assert.iterator.increment.pass.cpp index d3e4b4ec3143f..e540f40f8c476 100644 --- a/libcxx/test/libcxx/containers/sequences/vector/debug.iterator.increment.pass.cpp +++ b/libcxx/test/libcxx/containers/sequences/vector/assert.iterator.increment.pass.cpp @@ -10,13 +10,14 @@ // Increment iterator past end. -// REQUIRES: has-unix-headers -// UNSUPPORTED: !libcpp-has-legacy-debug-mode, c++03 +// REQUIRES: has-unix-headers, libcpp-has-abi-bounded-iterators-in-vector +// UNSUPPORTED: libcpp-hardening-mode=none, c++03 #include #include #include "check_assertion.h" +#include "fill_to_capacity.h" #include "min_allocator.h" int main(int, char**) { @@ -24,20 +25,22 @@ int main(int, char**) { typedef int T; typedef std::vector C; C c(1); + fill_to_capacity(c); C::iterator i = c.begin(); - ++i; + i += c.size(); assert(i == c.end()); - TEST_LIBCPP_ASSERT_FAILURE(++i, "Attempted to increment a non-incrementable iterator"); + TEST_LIBCPP_ASSERT_FAILURE(++i, "__bounded_iter::operator++: Attempt to advance an iterator past the end"); } { typedef int T; typedef std::vector > C; C c(1); + fill_to_capacity(c); C::iterator i = c.begin(); - ++i; + i += c.size(); assert(i == c.end()); - TEST_LIBCPP_ASSERT_FAILURE(++i, "Attempted to increment a non-incrementable iterator"); + TEST_LIBCPP_ASSERT_FAILURE(++i, "__bounded_iter::operator++: Attempt to advance an iterator past the end"); } return 0; diff --git a/libcxx/test/libcxx/containers/sequences/vector/debug.iterator.index.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/assert.iterator.index.pass.cpp similarity index 52% rename from libcxx/test/libcxx/containers/sequences/vector/debug.iterator.index.pass.cpp rename to libcxx/test/libcxx/containers/sequences/vector/assert.iterator.index.pass.cpp index 8e8f6a5dae69d..63354af5af022 100644 --- a/libcxx/test/libcxx/containers/sequences/vector/debug.iterator.index.pass.cpp +++ b/libcxx/test/libcxx/containers/sequences/vector/assert.iterator.index.pass.cpp @@ -10,13 +10,14 @@ // Index iterator out of bounds. -// REQUIRES: has-unix-headers -// UNSUPPORTED: !libcpp-has-legacy-debug-mode, c++03 +// REQUIRES: has-unix-headers, libcpp-has-abi-bounded-iterators-in-vector +// UNSUPPORTED: libcpp-hardening-mode=none, c++03 #include #include #include "check_assertion.h" +#include "fill_to_capacity.h" #include "min_allocator.h" int main(int, char**) { @@ -24,18 +25,26 @@ int main(int, char**) { typedef int T; typedef std::vector C; C c(1); + fill_to_capacity(c); C::iterator i = c.begin(); assert(i[0] == 0); - TEST_LIBCPP_ASSERT_FAILURE(i[1], "Attempted to subscript an iterator outside its valid range"); + TEST_LIBCPP_ASSERT_FAILURE( + i[c.size()], "__bounded_iter::operator[]: Attempt to index an iterator at or past the end"); + TEST_LIBCPP_ASSERT_FAILURE( + i[c.size() + 1], "__bounded_iter::operator[]: Attempt to index an iterator at or past the end"); } { typedef int T; typedef std::vector > C; C c(1); + fill_to_capacity(c); C::iterator i = c.begin(); assert(i[0] == 0); - TEST_LIBCPP_ASSERT_FAILURE(i[1], "Attempted to subscript an iterator outside its valid range"); + TEST_LIBCPP_ASSERT_FAILURE( + i[c.size()], "__bounded_iter::operator[]: Attempt to index an iterator at or past the end"); + TEST_LIBCPP_ASSERT_FAILURE( + i[c.size() + 1], "__bounded_iter::operator[]: Attempt to index an iterator at or past the end"); } return 0; diff --git a/libcxx/test/libcxx/containers/sequences/vector/fill_to_capacity.h b/libcxx/test/libcxx/containers/sequences/vector/fill_to_capacity.h new file mode 100644 index 0000000000000..abf88c477fece --- /dev/null +++ b/libcxx/test/libcxx/containers/sequences/vector/fill_to_capacity.h @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LIBCXX_TEST_LIBCXX_CONTAINERS_SEQUENCES_VECTOR_FILL_TO_CAPACITY_H +#define LIBCXX_TEST_LIBCXX_CONTAINERS_SEQUENCES_VECTOR_FILL_TO_CAPACITY_H + +#include + +template +void fill_to_capacity(std::vector& vec) { + // Fill the given vector up to its capacity. Our bounded iterators are currently unable to catch an out-of-bounds + // access that goes beyond the container's logical storage (above the size) but is still within its physical storage + // (below the capacity) due to iterator stability guarantees. Filling a vector makes this distinction go away. + while (vec.size() < vec.capacity()) { + vec.push_back(T()); + } +} + +#endif // LIBCXX_TEST_LIBCXX_CONTAINERS_SEQUENCES_VECTOR_FILL_TO_CAPACITY_H diff --git a/libcxx/test/libcxx/strings/basic.string/alignof.compile.pass.cpp b/libcxx/test/libcxx/strings/basic.string/alignof.compile.pass.cpp index 7b4d54ed410b0..00943ef8762f8 100644 --- a/libcxx/test/libcxx/strings/basic.string/alignof.compile.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/alignof.compile.pass.cpp @@ -10,6 +10,7 @@ // UNSUPPORTED: c++03 +#include #include #include "test_macros.h" @@ -18,6 +19,14 @@ template class small_pointer { +public: + using value_type = T; + using difference_type = std::int16_t; + using pointer = small_pointer; + using reference = T&; + using iterator_category = std::random_access_iterator_tag; + +private: std::uint16_t offset; }; diff --git a/libcxx/test/libcxx/strings/basic.string/sizeof.compile.pass.cpp b/libcxx/test/libcxx/strings/basic.string/sizeof.compile.pass.cpp index 6e00e43618b2e..b85895ffcd837 100644 --- a/libcxx/test/libcxx/strings/basic.string/sizeof.compile.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/sizeof.compile.pass.cpp @@ -8,6 +8,7 @@ // Ensure that we never change the size or alignment of `basic_string` +#include #include #include "test_macros.h" @@ -16,6 +17,14 @@ template class small_pointer { +public: + using value_type = T; + using difference_type = std::int16_t; + using pointer = small_pointer; + using reference = T&; + using iterator_category = std::random_access_iterator_tag; + +private: std::uint16_t offset; }; diff --git a/libcxx/test/libcxx/strings/basic.string/string.iterators/debug.iterator.add.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.iterators/assert.iterator.add.pass.cpp similarity index 77% rename from libcxx/test/libcxx/strings/basic.string/string.iterators/debug.iterator.add.pass.cpp rename to libcxx/test/libcxx/strings/basic.string/string.iterators/assert.iterator.add.pass.cpp index 8459284637dc5..56c9d63d0dbaf 100644 --- a/libcxx/test/libcxx/strings/basic.string/string.iterators/debug.iterator.add.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/string.iterators/assert.iterator.add.pass.cpp @@ -10,8 +10,8 @@ // Add to iterator out of bounds. -// REQUIRES: has-unix-headers -// UNSUPPORTED: !libcpp-has-legacy-debug-mode, c++03 +// REQUIRES: has-unix-headers, libcpp-has-abi-bounded-iterators-in-string +// UNSUPPORTED: libcpp-hardening-mode=none, c++03 #include #include @@ -26,7 +26,7 @@ void test() { i += 1; assert(i == c.end()); i = c.begin(); - TEST_LIBCPP_ASSERT_FAILURE(i += 2, "Attempted to add/subtract an iterator outside its valid range"); + TEST_LIBCPP_ASSERT_FAILURE(i += 2, "__bounded_iter::operator+=: Attempt to advance an iterator past the end"); } int main(int, char**) { diff --git a/libcxx/test/libcxx/strings/basic.string/string.iterators/debug.iterator.decrement.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.iterators/assert.iterator.decrement.pass.cpp similarity index 76% rename from libcxx/test/libcxx/strings/basic.string/string.iterators/debug.iterator.decrement.pass.cpp rename to libcxx/test/libcxx/strings/basic.string/string.iterators/assert.iterator.decrement.pass.cpp index f1fa08d006a1e..43a9739bf936f 100644 --- a/libcxx/test/libcxx/strings/basic.string/string.iterators/debug.iterator.decrement.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/string.iterators/assert.iterator.decrement.pass.cpp @@ -10,8 +10,8 @@ // Decrement iterator prior to begin. -// REQUIRES: has-unix-headers -// UNSUPPORTED: !libcpp-has-legacy-debug-mode, c++03 +// REQUIRES: has-unix-headers, libcpp-has-abi-bounded-iterators-in-string +// UNSUPPORTED: libcpp-hardening-mode=none, c++03 #include #include @@ -25,7 +25,7 @@ void test() { typename C::iterator i = c.end(); --i; assert(i == c.begin()); - TEST_LIBCPP_ASSERT_FAILURE(--i, "Attempted to decrement a non-decrementable iterator"); + TEST_LIBCPP_ASSERT_FAILURE(--i, "__bounded_iter::operator--: Attempt to rewind an iterator past the start"); } int main(int, char**) { diff --git a/libcxx/test/libcxx/strings/basic.string/string.iterators/debug.iterator.dereference.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.iterators/assert.iterator.dereference.pass.cpp similarity index 75% rename from libcxx/test/libcxx/strings/basic.string/string.iterators/debug.iterator.dereference.pass.cpp rename to libcxx/test/libcxx/strings/basic.string/string.iterators/assert.iterator.dereference.pass.cpp index 0bf295c6c4f4f..e2326be021033 100644 --- a/libcxx/test/libcxx/strings/basic.string/string.iterators/debug.iterator.dereference.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/string.iterators/assert.iterator.dereference.pass.cpp @@ -10,8 +10,8 @@ // Dereference non-dereferenceable iterator. -// REQUIRES: has-unix-headers -// UNSUPPORTED: !libcpp-has-legacy-debug-mode, c++03 +// REQUIRES: has-unix-headers, libcpp-has-abi-bounded-iterators-in-string +// UNSUPPORTED: libcpp-hardening-mode=none, c++03 #include @@ -22,7 +22,7 @@ template void test() { C c(1, '\0'); typename C::iterator i = c.end(); - TEST_LIBCPP_ASSERT_FAILURE(*i, "Attempted to dereference a non-dereferenceable iterator"); + TEST_LIBCPP_ASSERT_FAILURE(*i, "__bounded_iter::operator*: Attempt to dereference an iterator at the end"); } int main(int, char**) { diff --git a/libcxx/test/libcxx/strings/basic.string/string.iterators/debug.iterator.increment.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.iterators/assert.iterator.increment.pass.cpp similarity index 76% rename from libcxx/test/libcxx/strings/basic.string/string.iterators/debug.iterator.increment.pass.cpp rename to libcxx/test/libcxx/strings/basic.string/string.iterators/assert.iterator.increment.pass.cpp index 9cc9ab40bcdd6..a7453f3115197 100644 --- a/libcxx/test/libcxx/strings/basic.string/string.iterators/debug.iterator.increment.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/string.iterators/assert.iterator.increment.pass.cpp @@ -10,8 +10,8 @@ // Increment iterator past end. -// REQUIRES: has-unix-headers -// UNSUPPORTED: !libcpp-has-legacy-debug-mode, c++03 +// REQUIRES: has-unix-headers, libcpp-has-abi-bounded-iterators-in-string +// UNSUPPORTED: libcpp-hardening-mode=none, c++03 #include #include @@ -25,7 +25,7 @@ void test() { typename C::iterator i = c.begin(); ++i; assert(i == c.end()); - TEST_LIBCPP_ASSERT_FAILURE(++i, "Attempted to increment a non-incrementable iterator"); + TEST_LIBCPP_ASSERT_FAILURE(++i, "__bounded_iter::operator++: Attempt to advance an iterator past the end"); } int main(int, char**) { diff --git a/libcxx/test/libcxx/strings/basic.string/string.iterators/debug.iterator.index.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.iterators/assert.iterator.index.pass.cpp similarity index 66% rename from libcxx/test/libcxx/strings/basic.string/string.iterators/debug.iterator.index.pass.cpp rename to libcxx/test/libcxx/strings/basic.string/string.iterators/assert.iterator.index.pass.cpp index 34060065d2046..e7d384413b589 100644 --- a/libcxx/test/libcxx/strings/basic.string/string.iterators/debug.iterator.index.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/string.iterators/assert.iterator.index.pass.cpp @@ -10,8 +10,8 @@ // Index iterator out of bounds. -// REQUIRES: has-unix-headers -// UNSUPPORTED: !libcpp-has-legacy-debug-mode, c++03 +// REQUIRES: has-unix-headers, libcpp-has-abi-bounded-iterators-in-string +// UNSUPPORTED: libcpp-hardening-mode=none, c++03 #include #include @@ -23,9 +23,10 @@ template void test() { using T = decltype(std::uint8_t() - std::uint8_t()); C c(1, '\0'); - C::iterator i = c.begin(); + typename C::iterator i = c.begin(); assert(i[0] == 0); - TEST_LIBCPP_ASSERT_FAILURE(i[1], "Attempted to subscript an iterator outside its valid range"); + TEST_LIBCPP_ASSERT_FAILURE(i[1], "__bounded_iter::operator[]: Attempt to index an iterator at or past the end"); + TEST_LIBCPP_ASSERT_FAILURE(i[-1], "__bounded_iter::operator[]: Attempt to index an iterator past the start"); } int main(int, char**) { diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/assert.push_back.invalidation.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/assert.push_back.invalidation.pass.cpp new file mode 100644 index 0000000000000..193c00891da7f --- /dev/null +++ b/libcxx/test/std/containers/sequences/vector/vector.modifiers/assert.push_back.invalidation.pass.cpp @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// + +// void push_back(const value_type& x); +// +// If no reallocation happens, then references, pointers, and iterators before +// the insertion point remain valid but those at or after the insertion point, +// including the past-the-end iterator, are invalidated. + +// REQUIRES: has-unix-headers, libcpp-has-abi-bounded-iterators-in-vector +// UNSUPPORTED: c++03 +// UNSUPPORTED: libcpp-hardening-mode=none +// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing + +#include +#include +#include + +#include "check_assertion.h" + +int main(int, char**) { + std::vector vec; + vec.reserve(4); + std::size_t old_capacity = vec.capacity(); + assert(old_capacity >= 4); + + vec.push_back(0); + vec.push_back(1); + vec.push_back(2); + auto it = vec.begin(); + vec.push_back(3); + assert(vec.capacity() == old_capacity); + + // The capacity did not change, so the iterator remains valid and can reach the new element. + assert(*it == 0); + assert(*(it + 1) == 1); + assert(*(it + 2) == 2); + assert(*(it + 3) == 3); + + while (vec.capacity() == old_capacity) { + vec.push_back(42); + } + TEST_LIBCPP_ASSERT_FAILURE( + *(it + old_capacity), "__bounded_iter::operator*: Attempt to dereference an iterator at the end"); + // Unfortunately, the bounded iterator does not detect that it's been invalidated and will still allow attempts to + // dereference elements 0 to 3 (even though they refer to memory that's been reallocated). + + return 0; +} diff --git a/libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp b/libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp index edc8b67808b85..abd284852a189 100644 --- a/libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.capacity/resize_and_overwrite.pass.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include "make_string.h" @@ -29,7 +30,7 @@ constexpr void test_appending(std::size_t k, size_t N, size_t new_capacity) { s.resize_and_overwrite(new_capacity, [&](auto* p, auto n) { assert(n == new_capacity); LIBCPP_ASSERT(s.size() == new_capacity); - LIBCPP_ASSERT(s.begin().base() == p); + LIBCPP_ASSERT(std::to_address(s.begin()) == p); assert(std::all_of(p, p + k, [](const auto ch) { return ch == 'a'; })); std::fill(p + k, p + n, 'b'); p[n] = 'c'; // will be overwritten @@ -48,7 +49,7 @@ constexpr void test_truncating(std::size_t o, size_t N) { s.resize_and_overwrite(N, [&](auto* p, auto n) { assert(n == N); LIBCPP_ASSERT(s.size() == n); - LIBCPP_ASSERT(s.begin().base() == p); + LIBCPP_ASSERT(std::to_address(s.begin()) == p); assert(std::all_of(p, p + n, [](auto ch) { return ch == 'a'; })); p[n - 1] = 'b'; p[n] = 'c'; // will be overwritten diff --git a/libcxx/utils/libcxx/test/features.py b/libcxx/utils/libcxx/test/features.py index 5e708da4f8fbe..e978875d543f3 100644 --- a/libcxx/utils/libcxx/test/features.py +++ b/libcxx/utils/libcxx/test/features.py @@ -352,6 +352,8 @@ def _mingwSupportsModules(cfg): "_LIBCPP_NO_VCRUNTIME": "libcpp-no-vcruntime", "_LIBCPP_ABI_VERSION": "libcpp-abi-version", "_LIBCPP_ABI_BOUNDED_ITERATORS": "libcpp-has-abi-bounded-iterators", + "_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING": "libcpp-has-abi-bounded-iterators-in-string", + "_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR": "libcpp-has-abi-bounded-iterators-in-vector", "_LIBCPP_DEPRECATED_ABI_DISABLE_PAIR_TRIVIAL_COPY_CTOR": "libcpp-deprecated-abi-disable-pair-trivial-copy-ctor", "_LIBCPP_HAS_NO_FILESYSTEM": "no-filesystem", "_LIBCPP_HAS_NO_RANDOM_DEVICE": "no-random-device",