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 f63436c7679478..699d3f8866861c 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,9 @@ set(LIBCXX_HARDENING_MODE "fast" CACHE STRING "") -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 "") +set(_defines + _LIBCPP_ABI_BOUNDED_ITERATORS + _LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING + _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR + _LIBCPP_ABI_BOUNDED_UNIQUE_PTR + _LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY +) +set(LIBCXX_ABI_DEFINES "${_defines}" CACHE STRING "") diff --git a/libcxx/docs/Hardening.rst b/libcxx/docs/Hardening.rst index fd0f3af5ef2f96..42aacfdcfb41a7 100644 --- a/libcxx/docs/Hardening.rst +++ b/libcxx/docs/Hardening.rst @@ -351,6 +351,12 @@ Vendors can use the following ABI options to enable additional hardening checks: of a few library types that use ``std::unique_ptr`` internally, such as the unordered containers. +- ``_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY`` -- changes the iterator type of ``std::array`` to a + bounded iterator that keeps track of whether it's within the bounds of the container and asserts it + on every dereference and when performing iterator arithmetic. + + ABI impact: changes the iterator type of ``std::array``, its size and its layout. + ABI tags -------- diff --git a/libcxx/docs/ReleaseNotes/20.rst b/libcxx/docs/ReleaseNotes/20.rst index 905fd81cd6faab..9039c6f046445b 100644 --- a/libcxx/docs/ReleaseNotes/20.rst +++ b/libcxx/docs/ReleaseNotes/20.rst @@ -66,6 +66,9 @@ Improvements and New Features detect out-of-bounds accesses in certain circumstances. ``std::unique_ptr`` can now also detect out-of-bounds accesses for a limited set of types (non-trivially destructible types) when the ABI configuration is disabled. +- The ``_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY`` ABI configuration was added, which allows storing valid bounds + in ``std::array::iterator`` and detecting OOB accesses when the appropriate hardening mode is enabled. + Deprecations and Removals ------------------------- diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index 87eaf64b245017..ae2e8bcb32aaa4 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -489,6 +489,7 @@ set(files __iterator/segmented_iterator.h __iterator/size.h __iterator/sortable.h + __iterator/static_bounded_iter.h __iterator/unreachable_sentinel.h __iterator/wrap_iter.h __locale diff --git a/libcxx/include/__configuration/abi.h b/libcxx/include/__configuration/abi.h index 7095d56c6dc39d..f5ed6df38da6c8 100644 --- a/libcxx/include/__configuration/abi.h +++ b/libcxx/include/__configuration/abi.h @@ -176,6 +176,12 @@ // ABI impact: changes the iterator type of `vector` (except `vector`). // #define _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR +// Changes the iterator type of `array` to a bounded iterator that keeps track of whether it's within the bounds of the +// container and asserts it on every dereference and when performing iterator arithmetic. +// +// ABI impact: changes the iterator type of `array`, its size and its layout. +// #define _LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY + // [[msvc::no_unique_address]] seems to mostly affect empty classes, so the padding scheme for Itanium doesn't work. #if defined(_LIBCPP_ABI_MICROSOFT) && !defined(_LIBCPP_ABI_NO_COMPRESSED_PAIR_PADDING) # define _LIBCPP_ABI_NO_COMPRESSED_PAIR_PADDING diff --git a/libcxx/include/__iterator/bounded_iter.h b/libcxx/include/__iterator/bounded_iter.h index 5a86bd98e71940..4811b0dde7f777 100644 --- a/libcxx/include/__iterator/bounded_iter.h +++ b/libcxx/include/__iterator/bounded_iter.h @@ -60,8 +60,8 @@ struct __bounded_iter { // Create a singular iterator. // - // Such an iterator points past the end of an empty span, so it is not dereferenceable. - // Observing operations like comparison and assignment are valid. + // Such an iterator points past the end of an empty range, so it is not dereferenceable. + // Operations like comparison and assignment are valid. _LIBCPP_HIDE_FROM_ABI __bounded_iter() = default; _LIBCPP_HIDE_FROM_ABI __bounded_iter(__bounded_iter const&) = default; diff --git a/libcxx/include/__iterator/static_bounded_iter.h b/libcxx/include/__iterator/static_bounded_iter.h new file mode 100644 index 00000000000000..2b80507cf56a01 --- /dev/null +++ b/libcxx/include/__iterator/static_bounded_iter.h @@ -0,0 +1,306 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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 _LIBCPP___ITERATOR_STATIC_BOUNDED_ITER_H +#define _LIBCPP___ITERATOR_STATIC_BOUNDED_ITER_H + +#include <__assert> +#include <__compare/ordering.h> +#include <__compare/three_way_comparable.h> +#include <__config> +#include <__cstddef/size_t.h> +#include <__iterator/iterator_traits.h> +#include <__memory/pointer_traits.h> +#include <__type_traits/enable_if.h> +#include <__type_traits/integral_constant.h> +#include <__type_traits/is_convertible.h> +#include <__utility/move.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +template +struct __static_bounded_iter_storage { + _LIBCPP_HIDE_FROM_ABI __static_bounded_iter_storage() = default; + _LIBCPP_HIDE_FROM_ABI + _LIBCPP_CONSTEXPR_SINCE_CXX14 explicit __static_bounded_iter_storage(_Iterator __current, _Iterator __begin) + : __current_(__current), __begin_(__begin) {} + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator& __current() _NOEXCEPT { return __current_; } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __current() const _NOEXCEPT { return __current_; } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __begin() const _NOEXCEPT { return __begin_; } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __end() const _NOEXCEPT { return __begin_ + _Size; } + +private: + _Iterator __current_; // current iterator + _Iterator __begin_; // start of the valid range, which is [__begin_, __begin_ + _Size) +}; + +template +struct __static_bounded_iter_storage<_Iterator, 0> { + _LIBCPP_HIDE_FROM_ABI __static_bounded_iter_storage() = default; + _LIBCPP_HIDE_FROM_ABI + _LIBCPP_CONSTEXPR_SINCE_CXX14 explicit __static_bounded_iter_storage(_Iterator __current, _Iterator /* __begin */) + : __current_(__current) {} + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator& __current() _NOEXCEPT { return __current_; } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __current() const _NOEXCEPT { return __current_; } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __begin() const _NOEXCEPT { return __current_; } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __end() const _NOEXCEPT { return __current_; } + +private: + _Iterator __current_; // current iterator +}; + +// This is an iterator wrapper for contiguous iterators that points within a range +// whose size is known at compile-time. This is very similar to `__bounded_iter`, +// except that we don't have to store the end of the range in physical memory since +// it can be computed from the start of the range. +// +// The operations on which this iterator wrapper traps are the same as `__bounded_iter`. +template ::value> > +struct __static_bounded_iter { + using value_type = typename iterator_traits<_Iterator>::value_type; + using difference_type = typename iterator_traits<_Iterator>::difference_type; + using pointer = typename iterator_traits<_Iterator>::pointer; + using reference = typename iterator_traits<_Iterator>::reference; + using iterator_category = typename iterator_traits<_Iterator>::iterator_category; +#if _LIBCPP_STD_VER >= 20 + using iterator_concept = contiguous_iterator_tag; +#endif + + // Create a singular iterator. + // + // Such an iterator points past the end of an empty range, so it is not dereferenceable. + // Operations like comparison and assignment are valid. + _LIBCPP_HIDE_FROM_ABI __static_bounded_iter() = default; + + _LIBCPP_HIDE_FROM_ABI __static_bounded_iter(__static_bounded_iter const&) = default; + _LIBCPP_HIDE_FROM_ABI __static_bounded_iter(__static_bounded_iter&&) = default; + + template ::value, int> = 0> + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR + __static_bounded_iter(__static_bounded_iter<_OtherIterator, _Size> const& __other) _NOEXCEPT + : __storage_(__other.__storage_.__current(), __other.__storage_.__begin()) {} + + // Assign a bounded iterator to another one, rebinding the bounds of the iterator as well. + _LIBCPP_HIDE_FROM_ABI __static_bounded_iter& operator=(__static_bounded_iter const&) = default; + _LIBCPP_HIDE_FROM_ABI __static_bounded_iter& operator=(__static_bounded_iter&&) = default; + +private: + // Create an iterator wrapping the given iterator, and whose bounds are described + // by the provided [begin, begin + _Size] range. + _LIBCPP_HIDE_FROM_ABI + _LIBCPP_CONSTEXPR_SINCE_CXX14 explicit __static_bounded_iter(_Iterator __current, _Iterator __begin) + : __storage_(__current, __begin) { + _LIBCPP_ASSERT_INTERNAL( + __begin <= __current, "__static_bounded_iter(current, begin): current and begin are inconsistent"); + _LIBCPP_ASSERT_INTERNAL( + __current <= __end(), "__static_bounded_iter(current, begin): current and (begin + Size) are inconsistent"); + } + + template + friend _LIBCPP_CONSTEXPR __static_bounded_iter<_It, _Sz> __make_static_bounded_iter(_It, _It); + +public: + // Dereference and indexing operations. + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 reference operator*() const _NOEXCEPT { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __current() != __end(), "__static_bounded_iter::operator*: Attempt to dereference an iterator at the end"); + return *__current(); + } + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 pointer operator->() const _NOEXCEPT { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __current() != __end(), "__static_bounded_iter::operator->: Attempt to dereference an iterator at the end"); + return std::__to_address(__current()); + } + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 reference operator[](difference_type __n) const _NOEXCEPT { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __n >= __begin() - __current(), + "__static_bounded_iter::operator[]: Attempt to index an iterator past the start"); + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __n < __end() - __current(), + "__static_bounded_iter::operator[]: Attempt to index an iterator at or past the end"); + return __current()[__n]; + } + + // Arithmetic operations. + // + // These operations check that the iterator remains within `[begin, end]`. + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 __static_bounded_iter& operator++() _NOEXCEPT { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __current() != __end(), "__static_bounded_iter::operator++: Attempt to advance an iterator past the end"); + ++__current(); + return *this; + } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 __static_bounded_iter operator++(int) _NOEXCEPT { + __static_bounded_iter __tmp(*this); + ++*this; + return __tmp; + } + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 __static_bounded_iter& operator--() _NOEXCEPT { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __current() != __begin(), "__static_bounded_iter::operator--: Attempt to rewind an iterator past the start"); + --__current(); + return *this; + } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 __static_bounded_iter operator--(int) _NOEXCEPT { + __static_bounded_iter __tmp(*this); + --*this; + return __tmp; + } + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 __static_bounded_iter& operator+=(difference_type __n) _NOEXCEPT { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __n >= __begin() - __current(), + "__static_bounded_iter::operator+=: Attempt to rewind an iterator past the start"); + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __n <= __end() - __current(), "__static_bounded_iter::operator+=: Attempt to advance an iterator past the end"); + __current() += __n; + return *this; + } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 friend __static_bounded_iter + operator+(__static_bounded_iter const& __self, difference_type __n) _NOEXCEPT { + __static_bounded_iter __tmp(__self); + __tmp += __n; + return __tmp; + } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 friend __static_bounded_iter + operator+(difference_type __n, __static_bounded_iter const& __self) _NOEXCEPT { + __static_bounded_iter __tmp(__self); + __tmp += __n; + return __tmp; + } + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 __static_bounded_iter& operator-=(difference_type __n) _NOEXCEPT { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __n <= __current() - __begin(), + "__static_bounded_iter::operator-=: Attempt to rewind an iterator past the start"); + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + __n >= __current() - __end(), "__static_bounded_iter::operator-=: Attempt to advance an iterator past the end"); + __current() -= __n; + return *this; + } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 friend __static_bounded_iter + operator-(__static_bounded_iter const& __self, difference_type __n) _NOEXCEPT { + __static_bounded_iter __tmp(__self); + __tmp -= __n; + return __tmp; + } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 friend difference_type + operator-(__static_bounded_iter const& __x, __static_bounded_iter const& __y) _NOEXCEPT { + return __x.__current() - __y.__current(); + } + + // Comparison operations. + // + // These operations do not check whether the iterators are within their bounds. + // The valid range for each iterator is also not considered as part of the comparison, + // i.e. two iterators pointing to the same location will be considered equal even + // if they have different validity ranges. + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR friend bool + operator==(__static_bounded_iter const& __x, __static_bounded_iter const& __y) _NOEXCEPT { + return __x.__current() == __y.__current(); + } + +#if _LIBCPP_STD_VER <= 17 + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR friend bool + operator!=(__static_bounded_iter const& __x, __static_bounded_iter const& __y) _NOEXCEPT { + return __x.__current() != __y.__current(); + } + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR friend bool + operator<(__static_bounded_iter const& __x, __static_bounded_iter const& __y) _NOEXCEPT { + return __x.__current() < __y.__current(); + } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR friend bool + operator>(__static_bounded_iter const& __x, __static_bounded_iter const& __y) _NOEXCEPT { + return __x.__current() > __y.__current(); + } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR friend bool + operator<=(__static_bounded_iter const& __x, __static_bounded_iter const& __y) _NOEXCEPT { + return __x.__current() <= __y.__current(); + } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR friend bool + operator>=(__static_bounded_iter const& __x, __static_bounded_iter const& __y) _NOEXCEPT { + return __x.__current() >= __y.__current(); + } + +#else + _LIBCPP_HIDE_FROM_ABI constexpr friend strong_ordering + operator<=>(__static_bounded_iter const& __x, __static_bounded_iter const& __y) noexcept { + if constexpr (three_way_comparable<_Iterator, strong_ordering>) { + return __x.__current() <=> __y.__current(); + } else { + if (__x.__current() < __y.__current()) + return strong_ordering::less; + + if (__x.__current() == __y.__current()) + return strong_ordering::equal; + + return strong_ordering::greater; + } + } +#endif // _LIBCPP_STD_VER >= 20 + +private: + template + friend struct pointer_traits; + template + friend struct __static_bounded_iter; + __static_bounded_iter_storage<_Iterator, _Size> __storage_; + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator& __current() _NOEXCEPT { + return __storage_.__current(); + } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __current() const _NOEXCEPT { + return __storage_.__current(); + } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __begin() const _NOEXCEPT { + return __storage_.__begin(); + } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iterator __end() const _NOEXCEPT { return __storage_.__end(); } +}; + +template +_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR __static_bounded_iter<_It, _Size> +__make_static_bounded_iter(_It __it, _It __begin) { + return __static_bounded_iter<_It, _Size>(std::move(__it), std::move(__begin)); +} + +#if _LIBCPP_STD_VER <= 17 +template +struct __libcpp_is_contiguous_iterator<__static_bounded_iter<_Iterator, _Size> > : true_type {}; +#endif + +template +struct pointer_traits<__static_bounded_iter<_Iterator, _Size> > { + using pointer = __static_bounded_iter<_Iterator, _Size>; + using element_type = typename pointer_traits<_Iterator>::element_type; + using difference_type = typename pointer_traits<_Iterator>::difference_type; + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR static element_type* to_address(pointer __it) _NOEXCEPT { + return std::__to_address(__it.__current()); + } +}; + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___ITERATOR_STATIC_BOUNDED_ITER_H diff --git a/libcxx/include/array b/libcxx/include/array index b1a9f0d29e68a8..99b38d049e129d 100644 --- a/libcxx/include/array +++ b/libcxx/include/array @@ -121,6 +121,7 @@ template const T&& get(const array&&) noexce #include <__cstddef/ptrdiff_t.h> #include <__fwd/array.h> #include <__iterator/reverse_iterator.h> +#include <__iterator/static_bounded_iter.h> #include <__iterator/wrap_iter.h> #include <__tuple/sfinae_helpers.h> #include <__type_traits/conditional.h> @@ -178,7 +179,10 @@ struct _LIBCPP_TEMPLATE_VIS array { using const_reference = const value_type&; using pointer = value_type*; using const_pointer = const value_type*; -#if defined(_LIBCPP_ABI_USE_WRAP_ITER_IN_STD_ARRAY) +#if defined(_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY) + using iterator = __static_bounded_iter; + using const_iterator = __static_bounded_iter; +#elif defined(_LIBCPP_ABI_USE_WRAP_ITER_IN_STD_ARRAY) using iterator = __wrap_iter; using const_iterator = __wrap_iter; #else @@ -202,13 +206,33 @@ struct _LIBCPP_TEMPLATE_VIS array { } // iterators: - _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 iterator begin() _NOEXCEPT { return iterator(data()); } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 iterator begin() _NOEXCEPT { +#if defined(_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY) + return std::__make_static_bounded_iter<_Size>(data(), data()); +#else + return iterator(data()); +#endif + } _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 const_iterator begin() const _NOEXCEPT { +#if defined(_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY) + return std::__make_static_bounded_iter<_Size>(data(), data()); +#else return const_iterator(data()); +#endif + } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 iterator end() _NOEXCEPT { +#if defined(_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY) + return std::__make_static_bounded_iter<_Size>(data() + _Size, data()); +#else + return iterator(data() + _Size); +#endif } - _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 iterator end() _NOEXCEPT { return iterator(data() + _Size); } _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 const_iterator end() const _NOEXCEPT { +#if defined(_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY) + return std::__make_static_bounded_iter<_Size>(data() + _Size, data()); +#else return const_iterator(data() + _Size); +#endif } _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 reverse_iterator rbegin() _NOEXCEPT { @@ -278,7 +302,10 @@ struct _LIBCPP_TEMPLATE_VIS array<_Tp, 0> { using const_reference = const value_type&; using pointer = value_type*; using const_pointer = const value_type*; -#if defined(_LIBCPP_ABI_USE_WRAP_ITER_IN_STD_ARRAY) +#if defined(_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY) + using iterator = __static_bounded_iter; + using const_iterator = __static_bounded_iter; +#elif defined(_LIBCPP_ABI_USE_WRAP_ITER_IN_STD_ARRAY) using iterator = __wrap_iter; using const_iterator = __wrap_iter; #else @@ -310,13 +337,33 @@ struct _LIBCPP_TEMPLATE_VIS array<_Tp, 0> { } // iterators: - _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 iterator begin() _NOEXCEPT { return iterator(data()); } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 iterator begin() _NOEXCEPT { +#if defined(_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY) + return std::__make_static_bounded_iter<0>(data(), data()); +#else + return iterator(data()); +#endif + } _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 const_iterator begin() const _NOEXCEPT { +#if defined(_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY) + return std::__make_static_bounded_iter<0>(data(), data()); +#else return const_iterator(data()); +#endif + } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 iterator end() _NOEXCEPT { +#if defined(_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY) + return std::__make_static_bounded_iter<0>(data(), data()); +#else + return iterator(data()); +#endif } - _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 iterator end() _NOEXCEPT { return iterator(data()); } _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 const_iterator end() const _NOEXCEPT { +#if defined(_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY) + return std::__make_static_bounded_iter<0>(data(), data()); +#else return const_iterator(data()); +#endif } _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 reverse_iterator rbegin() _NOEXCEPT { diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap index 70f91249a58ec0..6b0cc07fca0787 100644 --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -1442,6 +1442,7 @@ module std [system] { module segmented_iterator { header "__iterator/segmented_iterator.h" } module size { header "__iterator/size.h" } module sortable { header "__iterator/sortable.h" } + module static_bounded_iter { header "__iterator/static_bounded_iter.h" } module unreachable_sentinel { header "__iterator/unreachable_sentinel.h" } module wrap_iter { header "__iterator/wrap_iter.h" } diff --git a/libcxx/test/libcxx/containers/sequences/array/array.zero/assert.subscript.pass.cpp b/libcxx/test/libcxx/containers/sequences/array/array.zero/assert.subscript.pass.cpp deleted file mode 100644 index ab65ce223f7c54..00000000000000 --- a/libcxx/test/libcxx/containers/sequences/array/array.zero/assert.subscript.pass.cpp +++ /dev/null @@ -1,41 +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 -// -//===----------------------------------------------------------------------===// - -// REQUIRES: has-unix-headers -// UNSUPPORTED: c++03 -// UNSUPPORTED: libcpp-hardening-mode=none -// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing - -// test that array::operator[] triggers an assertion - -#include - -#include "check_assertion.h" - -int main(int, char**) { - { - typedef std::array C; - C c = {}; - C const& cc = c; - TEST_LIBCPP_ASSERT_FAILURE(c[0], "cannot call array::operator[] on a zero-sized array"); - TEST_LIBCPP_ASSERT_FAILURE(c[1], "cannot call array::operator[] on a zero-sized array"); - TEST_LIBCPP_ASSERT_FAILURE(cc[0], "cannot call array::operator[] on a zero-sized array"); - TEST_LIBCPP_ASSERT_FAILURE(cc[1], "cannot call array::operator[] on a zero-sized array"); - } - { - typedef std::array C; - C c = {{}}; - C const& cc = c; - TEST_LIBCPP_ASSERT_FAILURE(c[0], "cannot call array::operator[] on a zero-sized array"); - TEST_LIBCPP_ASSERT_FAILURE(c[1], "cannot call array::operator[] on a zero-sized array"); - TEST_LIBCPP_ASSERT_FAILURE(cc[0], "cannot call array::operator[] on a zero-sized array"); - TEST_LIBCPP_ASSERT_FAILURE(cc[1], "cannot call array::operator[] on a zero-sized array"); - } - - return 0; -} diff --git a/libcxx/test/libcxx/containers/sequences/array/array.zero/assert.back.pass.cpp b/libcxx/test/std/containers/sequences/array/assert.back.pass.cpp similarity index 96% rename from libcxx/test/libcxx/containers/sequences/array/array.zero/assert.back.pass.cpp rename to libcxx/test/std/containers/sequences/array/assert.back.pass.cpp index c20b0c9a804f26..b9dec01033334a 100644 --- a/libcxx/test/libcxx/containers/sequences/array/array.zero/assert.back.pass.cpp +++ b/libcxx/test/std/containers/sequences/array/assert.back.pass.cpp @@ -20,14 +20,14 @@ int main(int, char**) { { typedef std::array C; - C c = {}; + C c = {}; C const& cc = c; TEST_LIBCPP_ASSERT_FAILURE(c.back(), "cannot call array::back() on a zero-sized array"); TEST_LIBCPP_ASSERT_FAILURE(cc.back(), "cannot call array::back() on a zero-sized array"); } { typedef std::array C; - C c = {{}}; + C c = {{}}; C const& cc = c; TEST_LIBCPP_ASSERT_FAILURE(c.back(), "cannot call array::back() on a zero-sized array"); TEST_LIBCPP_ASSERT_FAILURE(cc.back(), "cannot call array::back() on a zero-sized array"); diff --git a/libcxx/test/libcxx/containers/sequences/array/array.zero/assert.front.pass.cpp b/libcxx/test/std/containers/sequences/array/assert.front.pass.cpp similarity index 91% rename from libcxx/test/libcxx/containers/sequences/array/array.zero/assert.front.pass.cpp rename to libcxx/test/std/containers/sequences/array/assert.front.pass.cpp index 1ea0035ee4cc50..67a70a1d63c521 100644 --- a/libcxx/test/libcxx/containers/sequences/array/array.zero/assert.front.pass.cpp +++ b/libcxx/test/std/containers/sequences/array/assert.front.pass.cpp @@ -11,7 +11,7 @@ // UNSUPPORTED: libcpp-hardening-mode=none // XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing -// test that array::back() triggers an assertion +// test that array::front() triggers an assertion #include @@ -20,14 +20,14 @@ int main(int, char**) { { typedef std::array C; - C c = {}; + C c = {}; C const& cc = c; TEST_LIBCPP_ASSERT_FAILURE(c.front(), "cannot call array::front() on a zero-sized array"); TEST_LIBCPP_ASSERT_FAILURE(cc.front(), "cannot call array::front() on a zero-sized array"); } { typedef std::array C; - C c = {{}}; + C c = {{}}; C const& cc = c; TEST_LIBCPP_ASSERT_FAILURE(c.front(), "cannot call array::front() on a zero-sized array"); TEST_LIBCPP_ASSERT_FAILURE(cc.front(), "cannot call array::front() on a zero-sized array"); diff --git a/libcxx/test/std/containers/sequences/array/assert.indexing.pass.cpp b/libcxx/test/std/containers/sequences/array/assert.indexing.pass.cpp new file mode 100644 index 00000000000000..ea0ffce3b3007d --- /dev/null +++ b/libcxx/test/std/containers/sequences/array/assert.indexing.pass.cpp @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: has-unix-headers +// UNSUPPORTED: c++03 +// UNSUPPORTED: libcpp-hardening-mode=none +// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing + +// + +// Test that operator[] triggers an assertion when accessing the array out-of-bounds. + +#include + +#include "check_assertion.h" + +int main(int, char**) { + // Check with an empty array + { + { + using Array = std::array; + Array c = {}; + Array const& cc = c; + TEST_LIBCPP_ASSERT_FAILURE(c[0], "cannot call array::operator[] on a zero-sized array"); + TEST_LIBCPP_ASSERT_FAILURE(c[1], "cannot call array::operator[] on a zero-sized array"); + TEST_LIBCPP_ASSERT_FAILURE(cc[0], "cannot call array::operator[] on a zero-sized array"); + TEST_LIBCPP_ASSERT_FAILURE(cc[1], "cannot call array::operator[] on a zero-sized array"); + } + { + using Array = std::array; + Array c = {{}}; + Array const& cc = c; + TEST_LIBCPP_ASSERT_FAILURE(c[0], "cannot call array::operator[] on a zero-sized array"); + TEST_LIBCPP_ASSERT_FAILURE(c[1], "cannot call array::operator[] on a zero-sized array"); + TEST_LIBCPP_ASSERT_FAILURE(cc[0], "cannot call array::operator[] on a zero-sized array"); + TEST_LIBCPP_ASSERT_FAILURE(cc[1], "cannot call array::operator[] on a zero-sized array"); + } + } + + // Check with non-empty arrays + { + { + using Array = std::array; + Array c = {}; + Array const& cc = c; + TEST_LIBCPP_ASSERT_FAILURE(c[2], "out-of-bounds access in std::array"); + TEST_LIBCPP_ASSERT_FAILURE(cc[2], "out-of-bounds access in std::array"); + } + { + using Array = std::array; + Array c = {{}}; + Array const& cc = c; + TEST_LIBCPP_ASSERT_FAILURE(c[2], "out-of-bounds access in std::array"); + TEST_LIBCPP_ASSERT_FAILURE(cc[2], "out-of-bounds access in std::array"); + } + + { + using Array = std::array; + Array c = {}; + Array const& cc = c; + TEST_LIBCPP_ASSERT_FAILURE(c[99], "out-of-bounds access in std::array"); + TEST_LIBCPP_ASSERT_FAILURE(cc[99], "out-of-bounds access in std::array"); + } + { + using Array = std::array; + Array c = {{}}; + Array const& cc = c; + TEST_LIBCPP_ASSERT_FAILURE(c[99], "out-of-bounds access in std::array"); + TEST_LIBCPP_ASSERT_FAILURE(cc[99], "out-of-bounds access in std::array"); + } + } + + return 0; +} diff --git a/libcxx/test/std/containers/sequences/array/assert.iterators.pass.cpp b/libcxx/test/std/containers/sequences/array/assert.iterators.pass.cpp new file mode 100644 index 00000000000000..21a763e71e18a5 --- /dev/null +++ b/libcxx/test/std/containers/sequences/array/assert.iterators.pass.cpp @@ -0,0 +1,145 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: has-unix-headers, libcpp-has-abi-bounded-iterators-in-std-array +// UNSUPPORTED: c++03 +// UNSUPPORTED: libcpp-hardening-mode=none +// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing + +// + +// Make sure that std::array's iterators check for OOB accesses when the right hardening settings +// are enabled. + +#include +#include +#include + +#include "check_assertion.h" + +template +void test_iterator(Iter begin, Iter end) { + std::ptrdiff_t distance = std::distance(begin, end); + + // Dereferencing an iterator at the end. + { + TEST_LIBCPP_ASSERT_FAILURE(*end, "__static_bounded_iter::operator*: Attempt to dereference an iterator at the end"); + TEST_LIBCPP_ASSERT_FAILURE( + end.operator->(), "__static_bounded_iter::operator->: Attempt to dereference an iterator at the end"); + } + + // Incrementing an iterator past the end. + { + auto it = end; + TEST_LIBCPP_ASSERT_FAILURE(it++, "__static_bounded_iter::operator++: Attempt to advance an iterator past the end"); + it = end; + TEST_LIBCPP_ASSERT_FAILURE(++it, "__static_bounded_iter::operator++: Attempt to advance an iterator past the end"); + } + + // Decrementing an iterator past the start. + { + auto it = begin; + TEST_LIBCPP_ASSERT_FAILURE(it--, "__static_bounded_iter::operator--: Attempt to rewind an iterator past the start"); + it = begin; + TEST_LIBCPP_ASSERT_FAILURE(--it, "__static_bounded_iter::operator--: Attempt to rewind an iterator past the start"); + } + + // Advancing past the end with operator+= and operator+. + { + [[maybe_unused]] const char* msg = "__static_bounded_iter::operator+=: Attempt to advance an iterator past the end"; + auto it = end; + TEST_LIBCPP_ASSERT_FAILURE(it += 1, msg); + TEST_LIBCPP_ASSERT_FAILURE(end + 1, msg); + it = begin; + TEST_LIBCPP_ASSERT_FAILURE(it += (distance + 1), msg); + TEST_LIBCPP_ASSERT_FAILURE(begin + (distance + 1), msg); + } + + // Advancing past the end with operator-= and operator-. + { + [[maybe_unused]] const char* msg = "__static_bounded_iter::operator-=: Attempt to advance an iterator past the end"; + auto it = end; + TEST_LIBCPP_ASSERT_FAILURE(it -= (-1), msg); + TEST_LIBCPP_ASSERT_FAILURE(end - (-1), msg); + it = begin; + TEST_LIBCPP_ASSERT_FAILURE(it -= (-distance - 1), msg); + TEST_LIBCPP_ASSERT_FAILURE(begin - (-distance - 1), msg); + } + + // Rewinding past the start with operator+= and operator+. + { + [[maybe_unused]] const char* msg = + "__static_bounded_iter::operator+=: Attempt to rewind an iterator past the start"; + auto it = begin; + TEST_LIBCPP_ASSERT_FAILURE(it += (-1), msg); + TEST_LIBCPP_ASSERT_FAILURE(begin + (-1), msg); + it = end; + TEST_LIBCPP_ASSERT_FAILURE(it += (-distance - 1), msg); + TEST_LIBCPP_ASSERT_FAILURE(end + (-distance - 1), msg); + } + + // Rewinding past the start with operator-= and operator-. + { + [[maybe_unused]] const char* msg = + "__static_bounded_iter::operator-=: Attempt to rewind an iterator past the start"; + auto it = begin; + TEST_LIBCPP_ASSERT_FAILURE(it -= 1, msg); + TEST_LIBCPP_ASSERT_FAILURE(begin - 1, msg); + it = end; + TEST_LIBCPP_ASSERT_FAILURE(it -= (distance + 1), msg); + TEST_LIBCPP_ASSERT_FAILURE(end - (distance + 1), msg); + } + + // Out-of-bounds operator[]. + { + [[maybe_unused]] const char* end_msg = + "__static_bounded_iter::operator[]: Attempt to index an iterator at or past the end"; + [[maybe_unused]] const char* past_end_msg = + "__static_bounded_iter::operator[]: Attempt to index an iterator at or past the end"; + [[maybe_unused]] const char* past_start_msg = + "__static_bounded_iter::operator[]: Attempt to index an iterator past the start"; + TEST_LIBCPP_ASSERT_FAILURE(begin[distance], end_msg); + TEST_LIBCPP_ASSERT_FAILURE(begin[distance + 1], past_end_msg); + TEST_LIBCPP_ASSERT_FAILURE(begin[-1], past_start_msg); + TEST_LIBCPP_ASSERT_FAILURE(begin[-99], past_start_msg); + + if (distance > 0) { + auto it = begin + 1; + TEST_LIBCPP_ASSERT_FAILURE(it[distance - 1], end_msg); + TEST_LIBCPP_ASSERT_FAILURE(it[distance], past_end_msg); + TEST_LIBCPP_ASSERT_FAILURE(it[-2], past_start_msg); + TEST_LIBCPP_ASSERT_FAILURE(it[-99], past_start_msg); + } + } +} + +int main(int, char**) { + // Empty array + { + std::array array = {}; + + // array::iterator + test_iterator(array.begin(), array.end()); + + // array::const_iterator + test_iterator(array.cbegin(), array.cend()); + } + + // Non-empty array + { + std::array array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + + // array::iterator + test_iterator(array.begin(), array.end()); + + // array::const_iterator + test_iterator(array.cbegin(), array.cend()); + } + + return 0; +} diff --git a/libcxx/utils/libcxx/test/features.py b/libcxx/utils/libcxx/test/features.py index afec2c7e2bf336..aca4b3616aa6ee 100644 --- a/libcxx/utils/libcxx/test/features.py +++ b/libcxx/utils/libcxx/test/features.py @@ -374,6 +374,7 @@ def _mingwSupportsModules(cfg): "_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_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY": "libcpp-has-abi-bounded-iterators-in-std-array", "_LIBCPP_ABI_BOUNDED_UNIQUE_PTR": "libcpp-has-abi-bounded-unique_ptr", "_LIBCPP_ABI_FIX_UNORDERED_CONTAINER_SIZE_TYPE": "libcpp-has-abi-fix-unordered-container-size-type", "_LIBCPP_DEPRECATED_ABI_DISABLE_PAIR_TRIVIAL_COPY_CTOR": "libcpp-deprecated-abi-disable-pair-trivial-copy-ctor",