[libc++][hardening] Use bounded iterators in std::vector and std::string (#78929)

~~NB: This PR depends on #78876. Ignore the first commit when reviewing,
and don't merge it until #78876 is resolved. When/if #78876 lands, I'll
clean this up.~~

This partially restores parity with the old, since removed debug build.
We now can re-enable a bunch of the disabled tests. Some things of note:

- `bounded_iter`'s converting constructor has never worked. It needs a
friend declaration to access the other `bound_iter` instantiation's
private fields.

- The old debug iterators also checked that callers did not try to
compare iterators from different objects. `bounded_iter` does not
currently do this, so I've left those disabled. However, I think we
probably should add those. See
https://github.com/llvm/llvm-project/issues/78771#issuecomment-1902999181

- The `std::vector` iterators are bounded up to capacity, not size. This
makes for a weaker safety check. This is because the STL promises not to
invalidate iterators when appending up to the capacity. Since we cannot
retroactively update all the iterators on `push_back()`, I've instead
sized it to the capacity. This is not as good, but at least will stop
the iterator from going off the end of the buffer.

There was also no test for this, so I've added one in the `std`
directory.

- `std::string` has two ambiguities to deal with. First, I opted not to
size it against the capacity. https://eel.is/c++draft/string.require#4
says iterators are invalidated on an non-const operation. Second,
whether the iterator can reach the NUL terminator. The previous debug
tests and the special-case in https://eel.is/c++draft/string.access#2
suggest no. If either of these causes widespread problems, I figure we
can revisit.

- `resize_and_overwrite.pass.cpp` assumed `std::string`'s iterator
supported `s.begin().base()`, but I see no promise of this in the
standard. GCC also doesn't support this. I fixed the test to use
`std::to_address`.

- `alignof.compile.pass.cpp`'s pointer isn't enough of a real pointer.
(It needs to satisfy `NullablePointer`, `LegacyRandomAccessIterator`,
and `LegacyContiguousIterator`.) `__bounded_iter` seems to instantiate
enough to notice. I've added a few more bits to satisfy it.

Fixes #78805
This commit is contained in:
David Benjamin 2024-07-22 22:44:25 -07:00 committed by GitHub
parent 99e6631e30
commit bcf9fb9802
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 294 additions and 47 deletions

View File

@ -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 "")

View File

@ -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<bool>``.
ABI impact: changes the iterator type of ``vector`` (except ``vector<bool>``).
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<bool>`` iterator is not currently hardened.
Testing
=======

View File

@ -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<bool>``.
Deprecations and Removals
-------------------------

View File

@ -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<bool>`.
//
// ABI impact: changes the iterator type of `vector` (except `vector<bool>`).
// #define _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR
#if defined(_LIBCPP_COMPILER_CLANG_BASED)
# if defined(__APPLE__)
# if defined(__i386__) || defined(__x86_64__)

View File

@ -225,6 +225,8 @@ public:
private:
template <class>
friend struct pointer_traits;
template <class, class>
friend struct __bounded_iter;
_Iterator __current_; // current iterator
_Iterator __begin_, __end_; // valid range represented as [begin, end]
};

View File

@ -598,6 +598,7 @@ basic_string<char32_t> 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<allocator_type>::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<pointer>> iterator;
typedef __bounded_iter<__wrap_iter<const_pointer>> const_iterator;
#else
typedef __wrap_iter<pointer> iterator;
typedef __wrap_iter<const_pointer> const_iterator;
#endif
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_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<pointer>(__p),
std::__wrap_iter<pointer>(__get_pointer()),
std::__wrap_iter<pointer>(__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<const_pointer>(__p),
std::__wrap_iter<const_pointer>(__get_pointer()),
std::__wrap_iter<const_pointer>(__get_pointer() + size()));
#else
return const_iterator(__p);
#endif // _LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING
}
public:

View File

@ -327,6 +327,7 @@ template<class T, class charT> requires is-vector-bool-reference<T> // 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<pointer>> iterator;
typedef __bounded_iter<__wrap_iter<const_pointer>> const_iterator;
#else
typedef __wrap_iter<pointer> iterator;
typedef __wrap_iter<const_pointer> const_iterator;
#endif
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_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<pointer>(__p),
std::__wrap_iter<pointer>(this->__begin_),
std::__wrap_iter<pointer>(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<const_pointer>(__p),
std::__wrap_iter<const_pointer>(this->__begin_),
std::__wrap_iter<const_pointer>(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<value_type, allocator_type&>& __v);
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer

View File

@ -14,6 +14,14 @@
template <class T>
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;
};

View File

@ -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 <vector>
#include <cassert>
#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<T> 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<T, min_allocator<T> > 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;

View File

@ -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 <vector>
#include <cassert>
@ -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;

View File

@ -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 <vector>
#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<T> 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<T, min_allocator<T> > 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;

View File

@ -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 <vector>
#include <cassert>
#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<T> 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<T, min_allocator<T> > 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;

View File

@ -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 <vector>
#include <cassert>
#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<T> 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<T, min_allocator<T> > 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;

View File

@ -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 <vector>
template <typename T, typename A>
void fill_to_capacity(std::vector<T, A>& 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

View File

@ -10,6 +10,7 @@
// UNSUPPORTED: c++03
#include <iterator>
#include <string>
#include "test_macros.h"
@ -18,6 +19,14 @@
template <class T>
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;
};

View File

@ -8,6 +8,7 @@
// Ensure that we never change the size or alignment of `basic_string`
#include <iterator>
#include <string>
#include "test_macros.h"
@ -16,6 +17,14 @@
template <class T>
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;
};

View File

@ -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 <string>
#include <cassert>
@ -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**) {

View File

@ -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 <string>
#include <cassert>
@ -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**) {

View File

@ -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 <string>
@ -22,7 +22,7 @@ template <class C>
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**) {

View File

@ -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 <string>
#include <cassert>
@ -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**) {

View File

@ -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 <string>
#include <cassert>
@ -23,9 +23,10 @@ template <class C>
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**) {

View File

@ -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
//
//===----------------------------------------------------------------------===//
// <vector>
// 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 <vector>
#include <cassert>
#include <cstddef>
#include "check_assertion.h"
int main(int, char**) {
std::vector<int> 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;
}

View File

@ -15,6 +15,7 @@
#include <algorithm>
#include <cassert>
#include <memory>
#include <string>
#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

View File

@ -352,6 +352,8 @@ macros = {
"_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",