Skip to content

Commit 427a5cf

Browse files
authored
[libc++] Add support for bounded iterators in std::array (llvm#110729)
This patch introduces a new kind of bounded iterator that knows the size of its valid range at compile-time, as in std::array. This allows computing the end of the range from the start of the range and the size, which requires storing only the start of the range in the iterator instead of both the start and the size (or start and end). The iterator wrapper is otherwise identical in design to the existing __bounded_iter. Since this requires changing the type of the iterators returned by std::array, this new bounded iterator is controlled by an ABI flag. As a drive-by, centralize the tests for std::array::operator[] and add missing tests for OOB operator[] on non-empty arrays. Fixes llvm#70864
1 parent e236a52 commit 427a5cf

File tree

16 files changed

+617
-56
lines changed

16 files changed

+617
-56
lines changed
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
11
set(LIBCXX_HARDENING_MODE "fast" CACHE STRING "")
2-
set(LIBCXX_ABI_DEFINES "_LIBCPP_ABI_BOUNDED_ITERATORS;_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING;_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR;_LIBCPP_ABI_BOUNDED_UNIQUE_PTR" CACHE STRING "")
2+
set(_defines
3+
_LIBCPP_ABI_BOUNDED_ITERATORS
4+
_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING
5+
_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR
6+
_LIBCPP_ABI_BOUNDED_UNIQUE_PTR
7+
_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY
8+
)
9+
set(LIBCXX_ABI_DEFINES "${_defines}" CACHE STRING "")

libcxx/docs/Hardening.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,12 @@ Vendors can use the following ABI options to enable additional hardening checks:
351351
of a few library types that use ``std::unique_ptr`` internally, such as
352352
the unordered containers.
353353

354+
- ``_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY`` -- changes the iterator type of ``std::array`` to a
355+
bounded iterator that keeps track of whether it's within the bounds of the container and asserts it
356+
on every dereference and when performing iterator arithmetic.
357+
358+
ABI impact: changes the iterator type of ``std::array``, its size and its layout.
359+
354360
ABI tags
355361
--------
356362

libcxx/docs/ReleaseNotes/20.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ Improvements and New Features
6666
detect out-of-bounds accesses in certain circumstances. ``std::unique_ptr<T[]>`` can now also detect out-of-bounds
6767
accesses for a limited set of types (non-trivially destructible types) when the ABI configuration is disabled.
6868

69+
- The ``_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY`` ABI configuration was added, which allows storing valid bounds
70+
in ``std::array::iterator`` and detecting OOB accesses when the appropriate hardening mode is enabled.
71+
6972
Deprecations and Removals
7073
-------------------------
7174

libcxx/include/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,7 @@ set(files
489489
__iterator/segmented_iterator.h
490490
__iterator/size.h
491491
__iterator/sortable.h
492+
__iterator/static_bounded_iter.h
492493
__iterator/unreachable_sentinel.h
493494
__iterator/wrap_iter.h
494495
__locale

libcxx/include/__configuration/abi.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,12 @@
176176
// ABI impact: changes the iterator type of `vector` (except `vector<bool>`).
177177
// #define _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR
178178

179+
// Changes the iterator type of `array` to a bounded iterator that keeps track of whether it's within the bounds of the
180+
// container and asserts it on every dereference and when performing iterator arithmetic.
181+
//
182+
// ABI impact: changes the iterator type of `array`, its size and its layout.
183+
// #define _LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY
184+
179185
// [[msvc::no_unique_address]] seems to mostly affect empty classes, so the padding scheme for Itanium doesn't work.
180186
#if defined(_LIBCPP_ABI_MICROSOFT) && !defined(_LIBCPP_ABI_NO_COMPRESSED_PAIR_PADDING)
181187
# define _LIBCPP_ABI_NO_COMPRESSED_PAIR_PADDING

libcxx/include/__iterator/bounded_iter.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ struct __bounded_iter {
6060

6161
// Create a singular iterator.
6262
//
63-
// Such an iterator points past the end of an empty span, so it is not dereferenceable.
64-
// Observing operations like comparison and assignment are valid.
63+
// Such an iterator points past the end of an empty range, so it is not dereferenceable.
64+
// Operations like comparison and assignment are valid.
6565
_LIBCPP_HIDE_FROM_ABI __bounded_iter() = default;
6666

6767
_LIBCPP_HIDE_FROM_ABI __bounded_iter(__bounded_iter const&) = default;
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
// -*- C++ -*-
2+
//===----------------------------------------------------------------------===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#ifndef _LIBCPP___ITERATOR_STATIC_BOUNDED_ITER_H
11+
#define _LIBCPP___ITERATOR_STATIC_BOUNDED_ITER_H
12+
13+
#include <__assert>
14+
#include <__compare/ordering.h>
15+
#include <__compare/three_way_comparable.h>
16+
#include <__config>
17+
#include <__cstddef/size_t.h>
18+
#include <__iterator/iterator_traits.h>
19+
#include <__memory/pointer_traits.h>
20+
#include <__type_traits/enable_if.h>
21+
#include <__type_traits/integral_constant.h>
22+
#include <__type_traits/is_convertible.h>
23+
#include <__utility/move.h>
24+
25+
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
26+
# pragma GCC system_header
27+
#endif
28+
29+
_LIBCPP_PUSH_MACROS
30+
#include <__undef_macros>
31+
32+
_LIBCPP_BEGIN_NAMESPACE_STD
33+
34+
template <class _Iterator, size_t _Size>
35+
struct __static_bounded_iter_storage {
36+
_LIBCPP_HIDE_FROM_ABI __static_bounded_iter_storage() = default;
37+
_LIBCPP_HIDE_FROM_ABI
38+
_LIBCPP_CONSTEXPR_SINCE_CXX14 explicit __static_bounded_iter_storage(_Iterator __current, _Iterator __begin)
39+
: __current_(__current), __begin_(__begin) {}
40+
41+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator& __current() _NOEXCEPT { return __current_; }
42+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __current() const _NOEXCEPT { return __current_; }
43+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __begin() const _NOEXCEPT { return __begin_; }
44+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __end() const _NOEXCEPT { return __begin_ + _Size; }
45+
46+
private:
47+
_Iterator __current_; // current iterator
48+
_Iterator __begin_; // start of the valid range, which is [__begin_, __begin_ + _Size)
49+
};
50+
51+
template <class _Iterator>
52+
struct __static_bounded_iter_storage<_Iterator, 0> {
53+
_LIBCPP_HIDE_FROM_ABI __static_bounded_iter_storage() = default;
54+
_LIBCPP_HIDE_FROM_ABI
55+
_LIBCPP_CONSTEXPR_SINCE_CXX14 explicit __static_bounded_iter_storage(_Iterator __current, _Iterator /* __begin */)
56+
: __current_(__current) {}
57+
58+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator& __current() _NOEXCEPT { return __current_; }
59+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __current() const _NOEXCEPT { return __current_; }
60+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __begin() const _NOEXCEPT { return __current_; }
61+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __end() const _NOEXCEPT { return __current_; }
62+
63+
private:
64+
_Iterator __current_; // current iterator
65+
};
66+
67+
// This is an iterator wrapper for contiguous iterators that points within a range
68+
// whose size is known at compile-time. This is very similar to `__bounded_iter`,
69+
// except that we don't have to store the end of the range in physical memory since
70+
// it can be computed from the start of the range.
71+
//
72+
// The operations on which this iterator wrapper traps are the same as `__bounded_iter`.
73+
template <class _Iterator, size_t _Size, class = __enable_if_t<__libcpp_is_contiguous_iterator<_Iterator>::value> >
74+
struct __static_bounded_iter {
75+
using value_type = typename iterator_traits<_Iterator>::value_type;
76+
using difference_type = typename iterator_traits<_Iterator>::difference_type;
77+
using pointer = typename iterator_traits<_Iterator>::pointer;
78+
using reference = typename iterator_traits<_Iterator>::reference;
79+
using iterator_category = typename iterator_traits<_Iterator>::iterator_category;
80+
#if _LIBCPP_STD_VER >= 20
81+
using iterator_concept = contiguous_iterator_tag;
82+
#endif
83+
84+
// Create a singular iterator.
85+
//
86+
// Such an iterator points past the end of an empty range, so it is not dereferenceable.
87+
// Operations like comparison and assignment are valid.
88+
_LIBCPP_HIDE_FROM_ABI __static_bounded_iter() = default;
89+
90+
_LIBCPP_HIDE_FROM_ABI __static_bounded_iter(__static_bounded_iter const&) = default;
91+
_LIBCPP_HIDE_FROM_ABI __static_bounded_iter(__static_bounded_iter&&) = default;
92+
93+
template <class _OtherIterator, __enable_if_t<is_convertible<_OtherIterator, _Iterator>::value, int> = 0>
94+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR
95+
__static_bounded_iter(__static_bounded_iter<_OtherIterator, _Size> const& __other) _NOEXCEPT
96+
: __storage_(__other.__storage_.__current(), __other.__storage_.__begin()) {}
97+
98+
// Assign a bounded iterator to another one, rebinding the bounds of the iterator as well.
99+
_LIBCPP_HIDE_FROM_ABI __static_bounded_iter& operator=(__static_bounded_iter const&) = default;
100+
_LIBCPP_HIDE_FROM_ABI __static_bounded_iter& operator=(__static_bounded_iter&&) = default;
101+
102+
private:
103+
// Create an iterator wrapping the given iterator, and whose bounds are described
104+
// by the provided [begin, begin + _Size] range.
105+
_LIBCPP_HIDE_FROM_ABI
106+
_LIBCPP_CONSTEXPR_SINCE_CXX14 explicit __static_bounded_iter(_Iterator __current, _Iterator __begin)
107+
: __storage_(__current, __begin) {
108+
_LIBCPP_ASSERT_INTERNAL(
109+
__begin <= __current, "__static_bounded_iter(current, begin): current and begin are inconsistent");
110+
_LIBCPP_ASSERT_INTERNAL(
111+
__current <= __end(), "__static_bounded_iter(current, begin): current and (begin + Size) are inconsistent");
112+
}
113+
114+
template <size_t _Sz, class _It>
115+
friend _LIBCPP_CONSTEXPR __static_bounded_iter<_It, _Sz> __make_static_bounded_iter(_It, _It);
116+
117+
public:
118+
// Dereference and indexing operations.
119+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 reference operator*() const _NOEXCEPT {
120+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
121+
__current() != __end(), "__static_bounded_iter::operator*: Attempt to dereference an iterator at the end");
122+
return *__current();
123+
}
124+
125+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 pointer operator->() const _NOEXCEPT {
126+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
127+
__current() != __end(), "__static_bounded_iter::operator->: Attempt to dereference an iterator at the end");
128+
return std::__to_address(__current());
129+
}
130+
131+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 reference operator[](difference_type __n) const _NOEXCEPT {
132+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
133+
__n >= __begin() - __current(),
134+
"__static_bounded_iter::operator[]: Attempt to index an iterator past the start");
135+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
136+
__n < __end() - __current(),
137+
"__static_bounded_iter::operator[]: Attempt to index an iterator at or past the end");
138+
return __current()[__n];
139+
}
140+
141+
// Arithmetic operations.
142+
//
143+
// These operations check that the iterator remains within `[begin, end]`.
144+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 __static_bounded_iter& operator++() _NOEXCEPT {
145+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
146+
__current() != __end(), "__static_bounded_iter::operator++: Attempt to advance an iterator past the end");
147+
++__current();
148+
return *this;
149+
}
150+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 __static_bounded_iter operator++(int) _NOEXCEPT {
151+
__static_bounded_iter __tmp(*this);
152+
++*this;
153+
return __tmp;
154+
}
155+
156+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 __static_bounded_iter& operator--() _NOEXCEPT {
157+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
158+
__current() != __begin(), "__static_bounded_iter::operator--: Attempt to rewind an iterator past the start");
159+
--__current();
160+
return *this;
161+
}
162+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 __static_bounded_iter operator--(int) _NOEXCEPT {
163+
__static_bounded_iter __tmp(*this);
164+
--*this;
165+
return __tmp;
166+
}
167+
168+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 __static_bounded_iter& operator+=(difference_type __n) _NOEXCEPT {
169+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
170+
__n >= __begin() - __current(),
171+
"__static_bounded_iter::operator+=: Attempt to rewind an iterator past the start");
172+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
173+
__n <= __end() - __current(), "__static_bounded_iter::operator+=: Attempt to advance an iterator past the end");
174+
__current() += __n;
175+
return *this;
176+
}
177+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 friend __static_bounded_iter
178+
operator+(__static_bounded_iter const& __self, difference_type __n) _NOEXCEPT {
179+
__static_bounded_iter __tmp(__self);
180+
__tmp += __n;
181+
return __tmp;
182+
}
183+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 friend __static_bounded_iter
184+
operator+(difference_type __n, __static_bounded_iter const& __self) _NOEXCEPT {
185+
__static_bounded_iter __tmp(__self);
186+
__tmp += __n;
187+
return __tmp;
188+
}
189+
190+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 __static_bounded_iter& operator-=(difference_type __n) _NOEXCEPT {
191+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
192+
__n <= __current() - __begin(),
193+
"__static_bounded_iter::operator-=: Attempt to rewind an iterator past the start");
194+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
195+
__n >= __current() - __end(), "__static_bounded_iter::operator-=: Attempt to advance an iterator past the end");
196+
__current() -= __n;
197+
return *this;
198+
}
199+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 friend __static_bounded_iter
200+
operator-(__static_bounded_iter const& __self, difference_type __n) _NOEXCEPT {
201+
__static_bounded_iter __tmp(__self);
202+
__tmp -= __n;
203+
return __tmp;
204+
}
205+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 friend difference_type
206+
operator-(__static_bounded_iter const& __x, __static_bounded_iter const& __y) _NOEXCEPT {
207+
return __x.__current() - __y.__current();
208+
}
209+
210+
// Comparison operations.
211+
//
212+
// These operations do not check whether the iterators are within their bounds.
213+
// The valid range for each iterator is also not considered as part of the comparison,
214+
// i.e. two iterators pointing to the same location will be considered equal even
215+
// if they have different validity ranges.
216+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR friend bool
217+
operator==(__static_bounded_iter const& __x, __static_bounded_iter const& __y) _NOEXCEPT {
218+
return __x.__current() == __y.__current();
219+
}
220+
221+
#if _LIBCPP_STD_VER <= 17
222+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR friend bool
223+
operator!=(__static_bounded_iter const& __x, __static_bounded_iter const& __y) _NOEXCEPT {
224+
return __x.__current() != __y.__current();
225+
}
226+
227+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR friend bool
228+
operator<(__static_bounded_iter const& __x, __static_bounded_iter const& __y) _NOEXCEPT {
229+
return __x.__current() < __y.__current();
230+
}
231+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR friend bool
232+
operator>(__static_bounded_iter const& __x, __static_bounded_iter const& __y) _NOEXCEPT {
233+
return __x.__current() > __y.__current();
234+
}
235+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR friend bool
236+
operator<=(__static_bounded_iter const& __x, __static_bounded_iter const& __y) _NOEXCEPT {
237+
return __x.__current() <= __y.__current();
238+
}
239+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR friend bool
240+
operator>=(__static_bounded_iter const& __x, __static_bounded_iter const& __y) _NOEXCEPT {
241+
return __x.__current() >= __y.__current();
242+
}
243+
244+
#else
245+
_LIBCPP_HIDE_FROM_ABI constexpr friend strong_ordering
246+
operator<=>(__static_bounded_iter const& __x, __static_bounded_iter const& __y) noexcept {
247+
if constexpr (three_way_comparable<_Iterator, strong_ordering>) {
248+
return __x.__current() <=> __y.__current();
249+
} else {
250+
if (__x.__current() < __y.__current())
251+
return strong_ordering::less;
252+
253+
if (__x.__current() == __y.__current())
254+
return strong_ordering::equal;
255+
256+
return strong_ordering::greater;
257+
}
258+
}
259+
#endif // _LIBCPP_STD_VER >= 20
260+
261+
private:
262+
template <class>
263+
friend struct pointer_traits;
264+
template <class, size_t, class>
265+
friend struct __static_bounded_iter;
266+
__static_bounded_iter_storage<_Iterator, _Size> __storage_;
267+
268+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator& __current() _NOEXCEPT {
269+
return __storage_.__current();
270+
}
271+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __current() const _NOEXCEPT {
272+
return __storage_.__current();
273+
}
274+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __begin() const _NOEXCEPT {
275+
return __storage_.__begin();
276+
}
277+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __end() const _NOEXCEPT { return __storage_.__end(); }
278+
};
279+
280+
template <size_t _Size, class _It>
281+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR __static_bounded_iter<_It, _Size>
282+
__make_static_bounded_iter(_It __it, _It __begin) {
283+
return __static_bounded_iter<_It, _Size>(std::move(__it), std::move(__begin));
284+
}
285+
286+
#if _LIBCPP_STD_VER <= 17
287+
template <class _Iterator, size_t _Size>
288+
struct __libcpp_is_contiguous_iterator<__static_bounded_iter<_Iterator, _Size> > : true_type {};
289+
#endif
290+
291+
template <class _Iterator, size_t _Size>
292+
struct pointer_traits<__static_bounded_iter<_Iterator, _Size> > {
293+
using pointer = __static_bounded_iter<_Iterator, _Size>;
294+
using element_type = typename pointer_traits<_Iterator>::element_type;
295+
using difference_type = typename pointer_traits<_Iterator>::difference_type;
296+
297+
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR static element_type* to_address(pointer __it) _NOEXCEPT {
298+
return std::__to_address(__it.__current());
299+
}
300+
};
301+
302+
_LIBCPP_END_NAMESPACE_STD
303+
304+
_LIBCPP_POP_MACROS
305+
306+
#endif // _LIBCPP___ITERATOR_STATIC_BOUNDED_ITER_H

0 commit comments

Comments
 (0)