diff --git a/libcxx/docs/ReleaseNotes.rst b/libcxx/docs/ReleaseNotes.rst --- a/libcxx/docs/ReleaseNotes.rst +++ b/libcxx/docs/ReleaseNotes.rst @@ -41,6 +41,9 @@ Improvements and New Features ----------------------------- +- ``std::string_view`` now provides iterators that check for out-of-bounds accesses when the safe + libc++ mode is enabled. + Deprecations and Removals ------------------------- diff --git a/libcxx/include/__chrono/formatter.h b/libcxx/include/__chrono/formatter.h --- a/libcxx/include/__chrono/formatter.h +++ b/libcxx/include/__chrono/formatter.h @@ -169,7 +169,7 @@ if (__year < 1000 || __year > 9999) __formatter::__format_century(__year, __sstr); else - __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1); + __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), std::to_address(__s), std::to_address(__it + 1)); } break; case _CharT('j'): @@ -180,7 +180,7 @@ // an intemediate step. __sstr << chrono::duration_cast(chrono::duration_cast(__value)).count(); else - __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1); + __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), std::to_address(__s), std::to_address(__it + 1)); break; case _CharT('q'): @@ -208,7 +208,7 @@ case _CharT('S'): case _CharT('T'): - __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1); + __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), std::to_address(__s), std::to_address(__it + 1)); if constexpr (__use_fraction<_Tp>()) __formatter::__format_sub_seconds(__value, __sstr); break; @@ -252,7 +252,7 @@ if (__year < 1000) __formatter::__format_year(__year, __sstr); else - __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1); + __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), std::to_address(__s), std::to_address(__it + 1)); } break; case _CharT('F'): { @@ -261,7 +261,7 @@ __formatter::__format_year(__year, __sstr); __sstr << std::format(_LIBCPP_STATICALLY_WIDEN(_CharT, "-{:02}-{:02}"), __t.tm_mon + 1, __t.tm_mday); } else - __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1); + __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), std::to_address(__s), std::to_address(__it + 1)); } break; case _CharT('O'): @@ -271,7 +271,7 @@ // fractional part should be formatted. if (*(__it + 1) == 'S') { ++__it; - __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1); + __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), std::to_address(__s), std::to_address(__it + 1)); __formatter::__format_sub_seconds(__value, __sstr); break; } @@ -281,7 +281,7 @@ ++__it; [[fallthrough]]; default: - __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), __s, __it + 1); + __facet.put({__sstr}, __sstr, _CharT(' '), std::addressof(__t), std::to_address(__s), std::to_address(__it + 1)); break; } } else { diff --git a/libcxx/include/__chrono/parser_std_format_spec.h b/libcxx/include/__chrono/parser_std_format_spec.h --- a/libcxx/include/__chrono/parser_std_format_spec.h +++ b/libcxx/include/__chrono/parser_std_format_spec.h @@ -137,17 +137,19 @@ template class _LIBCPP_TEMPLATE_VIS __parser_chrono { + using _ConstIterator = typename basic_format_parse_context<_CharT>::const_iterator; + public: _LIBCPP_HIDE_FROM_ABI constexpr auto __parse(basic_format_parse_context<_CharT>& __parse_ctx, __fields __fields, __flags __flags) -> decltype(__parse_ctx.begin()) { - const _CharT* __begin = __parser_.__parse(__parse_ctx, __fields); - const _CharT* __end = __parse_ctx.end(); + _ConstIterator __begin = __parser_.__parse(__parse_ctx, __fields); + _ConstIterator __end = __parse_ctx.end(); if (__begin == __end) return __begin; - const _CharT* __last = __parse_chrono_specs(__begin, __end, __flags); - __chrono_specs_ = basic_string_view<_CharT>{__begin, __last}; + _ConstIterator __last = __parse_chrono_specs(__begin, __end, __flags); + __chrono_specs_ = basic_string_view<_CharT>{__begin, __last}; return __last; } @@ -156,8 +158,8 @@ basic_string_view<_CharT> __chrono_specs_; private: - _LIBCPP_HIDE_FROM_ABI constexpr const _CharT* - __parse_chrono_specs(const _CharT* __begin, const _CharT* __end, __flags __flags) { + _LIBCPP_HIDE_FROM_ABI constexpr _ConstIterator + __parse_chrono_specs(_ConstIterator __begin, _ConstIterator __end, __flags __flags) { _LIBCPP_ASSERT(__begin != __end, "When called with an empty input the function will cause " "undefined behavior by evaluating data not in the input"); @@ -190,7 +192,7 @@ /// \pre *__begin == '%' /// \post __begin points at the end parsed conversion-spec _LIBCPP_HIDE_FROM_ABI constexpr void - __parse_conversion_spec(const _CharT*& __begin, const _CharT* __end, __flags __flags) { + __parse_conversion_spec(_ConstIterator& __begin, _ConstIterator __end, __flags __flags) { ++__begin; if (__begin == __end) std::__throw_format_error("End of input while parsing the modifier chrono conversion-spec"); @@ -304,7 +306,7 @@ /// \pre *__begin == 'E' /// \post __begin is incremented by one. _LIBCPP_HIDE_FROM_ABI constexpr void - __parse_modifier_E(const _CharT*& __begin, const _CharT* __end, __flags __flags) { + __parse_modifier_E(_ConstIterator& __begin, _ConstIterator __end, __flags __flags) { ++__begin; if (__begin == __end) std::__throw_format_error("End of input while parsing the modifier E"); @@ -343,7 +345,7 @@ /// \pre *__begin == 'O' /// \post __begin is incremented by one. _LIBCPP_HIDE_FROM_ABI constexpr void - __parse_modifier_O(const _CharT*& __begin, const _CharT* __end, __flags __flags) { + __parse_modifier_O(_ConstIterator& __begin, _ConstIterator __end, __flags __flags) { ++__begin; if (__begin == __end) std::__throw_format_error("End of input while parsing the modifier O"); diff --git a/libcxx/include/__format/formatter_output.h b/libcxx/include/__format/formatter_output.h --- a/libcxx/include/__format/formatter_output.h +++ b/libcxx/include/__format/formatter_output.h @@ -503,7 +503,7 @@ __unicode::__code_point_view<_CharT> __view{__values.begin(), __values.end()}; while (!__view.__at_end()) { - const _CharT* __first = __view.__position(); + auto __first = __view.__position(); typename __unicode::__consume_p2286_result __result = __view.__consume_p2286(); if (__result.__ill_formed_size == 0) { if (!__formatter::__is_escaped_sequence_written(__str, __result.__value, __mark)) diff --git a/libcxx/include/__format/formatter_tuple.h b/libcxx/include/__format/formatter_tuple.h --- a/libcxx/include/__format/formatter_tuple.h +++ b/libcxx/include/__format/formatter_tuple.h @@ -51,7 +51,7 @@ template _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __parse_ctx) { - const _CharT* __begin = __parser_.__parse(__parse_ctx, __format_spec::__fields_tuple); + auto __begin = __parser_.__parse(__parse_ctx, __format_spec::__fields_tuple); // [format.tuple]/7 // ... For each element e in underlying_, if e.set_debug_format() @@ -61,7 +61,7 @@ std::__set_debug_format(std::get<_Index>(__underlying_)); }); - const _CharT* __end = __parse_ctx.end(); + auto __end = __parse_ctx.end(); if (__begin == __end) return __begin; diff --git a/libcxx/include/__format/range_formatter.h b/libcxx/include/__format/range_formatter.h --- a/libcxx/include/__format/range_formatter.h +++ b/libcxx/include/__format/range_formatter.h @@ -55,8 +55,8 @@ template _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __parse_ctx) { - const _CharT* __begin = __parser_.__parse(__parse_ctx, __format_spec::__fields_range); - const _CharT* __end = __parse_ctx.end(); + auto __begin = __parser_.__parse(__parse_ctx, __format_spec::__fields_range); + auto __end = __parse_ctx.end(); if (__begin == __end) return __begin; @@ -211,7 +211,8 @@ __format_spec::__parser<_CharT> __parser_{.__alignment_ = __format_spec::__alignment::__left}; private: - _LIBCPP_HIDE_FROM_ABI constexpr void __parse_type(const _CharT*& __begin, const _CharT* __end) { + template + _LIBCPP_HIDE_FROM_ABI constexpr void __parse_type(_Iterator& __begin, _Iterator __end) { switch (*__begin) { case _CharT('m'): if constexpr (__fmt_pair_like<_Tp>) { diff --git a/libcxx/include/string_view b/libcxx/include/string_view --- a/libcxx/include/string_view +++ b/libcxx/include/string_view @@ -208,6 +208,7 @@ #include <__functional/hash.h> #include <__functional/unary_function.h> #include <__fwd/string_view.h> +#include <__iterator/bounded_iter.h> #include <__iterator/concepts.h> #include <__iterator/readable_traits.h> #include <__iterator/reverse_iterator.h> @@ -265,7 +266,11 @@ using const_pointer = const _CharT*; using reference = _CharT&; using const_reference = const _CharT&; +#ifdef _LIBCPP_DEBUG_ITERATOR_BOUNDS_CHECKING + using const_iterator = __bounded_iter; +#else using const_iterator = const_pointer; // See [string.view.iterators] +#endif using iterator = const_iterator; using const_reverse_iterator = _VSTD::reverse_iterator; using reverse_iterator = const_reverse_iterator; @@ -343,10 +348,22 @@ const_iterator end() const _NOEXCEPT { return cend(); } _LIBCPP_CONSTEXPR _LIBCPP_INLINE_VISIBILITY - const_iterator cbegin() const _NOEXCEPT { return __data_; } + const_iterator cbegin() const _NOEXCEPT { +#ifdef _LIBCPP_DEBUG_ITERATOR_BOUNDS_CHECKING + return std::__make_bounded_iter(data(), data(), data() + size()); +#else + return __data_; +#endif + } _LIBCPP_CONSTEXPR _LIBCPP_INLINE_VISIBILITY - const_iterator cend() const _NOEXCEPT { return __data_ + __size_; } + const_iterator cend() const _NOEXCEPT { +#ifdef _LIBCPP_DEBUG_ITERATOR_BOUNDS_CHECKING + return std::__make_bounded_iter(data() + size(), data(), data() + size()); +#else + return __data_ + __size_; +#endif + } _LIBCPP_CONSTEXPR_SINCE_CXX17 _LIBCPP_INLINE_VISIBILITY const_reverse_iterator rbegin() const _NOEXCEPT { return const_reverse_iterator(cend()); } diff --git a/libcxx/test/libcxx/strings/string.view/string.view.iterators/debug.iterator-indexing.pass.cpp b/libcxx/test/libcxx/strings/string.view/string.view.iterators/debug.iterator-indexing.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/strings/string.view/string.view.iterators/debug.iterator-indexing.pass.cpp @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// Make sure that std::string_view's iterators check for OOB accesses when the debug mode is enabled. + +// REQUIRES: has-unix-headers +// UNSUPPORTED: !libcpp-has-debug-mode + +#include + +#include "check_assertion.h" + +int main(int, char**) { + // string_view::iterator + { + std::string_view const str("hello world"); + { + auto it = str.end(); + TEST_LIBCPP_ASSERT_FAILURE(*it, "__bounded_iter::operator*: Attempt to dereference an out-of-range iterator"); + } + { + auto it = str.end(); + TEST_LIBCPP_ASSERT_FAILURE(it.operator->(), "__bounded_iter::operator->: Attempt to dereference an out-of-range iterator"); + } + { + auto it = str.begin(); + TEST_LIBCPP_ASSERT_FAILURE(it[99], "__bounded_iter::operator[]: Attempt to index an iterator out-of-range"); + } + } + + // string_view::const_iterator + { + std::string_view const str("hello world"); + { + auto it = str.cend(); + TEST_LIBCPP_ASSERT_FAILURE(*it, "__bounded_iter::operator*: Attempt to dereference an out-of-range iterator"); + } + { + auto it = str.cend(); + TEST_LIBCPP_ASSERT_FAILURE(it.operator->(), "__bounded_iter::operator->: Attempt to dereference an out-of-range iterator"); + } + { + auto it = str.cbegin(); + TEST_LIBCPP_ASSERT_FAILURE(it[99], "__bounded_iter::operator[]: Attempt to index an iterator out-of-range"); + } + } + + // string_view::reverse_iterator + { + std::string_view const str("hello world"); + { + auto it = str.rend(); + TEST_LIBCPP_ASSERT_FAILURE(*it, "__bounded_iter::operator*: Attempt to dereference an out-of-range iterator"); + } + { + auto it = str.rend(); + TEST_LIBCPP_ASSERT_FAILURE(it.operator->(), "__bounded_iter::operator->: Attempt to dereference an out-of-range iterator"); + } + { + auto it = str.rbegin(); + TEST_LIBCPP_ASSERT_FAILURE(it[99], "__bounded_iter::operator*: Attempt to dereference an out-of-range iterator"); + } + } + + // string_view::const_reverse_iterator + { + std::string_view const str("hello world"); + { + auto it = str.crend(); + TEST_LIBCPP_ASSERT_FAILURE(*it, "__bounded_iter::operator*: Attempt to dereference an out-of-range iterator"); + } + { + auto it = str.crend(); + TEST_LIBCPP_ASSERT_FAILURE(it.operator->(), "__bounded_iter::operator->: Attempt to dereference an out-of-range iterator"); + } + { + auto it = str.crbegin(); + TEST_LIBCPP_ASSERT_FAILURE(it[99], "__bounded_iter::operator*: Attempt to dereference an out-of-range iterator"); + } + } + + return 0; +} diff --git a/libcxx/test/libcxx/utilities/format/format.string/format.string.std/extended_grapheme_cluster.pass.cpp b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/extended_grapheme_cluster.pass.cpp --- a/libcxx/test/libcxx/utilities/format/format.string/format.string.std/extended_grapheme_cluster.pass.cpp +++ b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/extended_grapheme_cluster.pass.cpp @@ -62,11 +62,11 @@ for (const auto& d : data) { assert(d.code_points.size() == d.breaks.size()); - std::__unicode::__extended_grapheme_cluster_view view{d.input.data(), d.input.data() + d.input.size()}; + std::__unicode::__extended_grapheme_cluster_view view{d.input.begin(), d.input.end()}; for (size_t i = 0; i < d.breaks.size(); ++i) { auto r = view.__consume(); assert(r.__code_point_ == d.code_points[i]); - assert(r.__last_ == d.input.data() + d.breaks[i]); + assert(r.__last_ == d.input.begin() + d.breaks[i]); } } } diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/base.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/base.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/base.pass.cpp @@ -25,8 +25,8 @@ constexpr MoveOnlyView(std::string_view v) : view_(v) {} constexpr MoveOnlyView(MoveOnlyView&&) = default; constexpr MoveOnlyView& operator=(MoveOnlyView&&) = default; - constexpr const char* begin() const { return view_.begin(); } - constexpr const char* end() const { return view_.end(); } + constexpr std::string_view::const_iterator begin() const { return view_.begin(); } + constexpr std::string_view::const_iterator end() const { return view_.end(); } constexpr bool operator==(MoveOnlyView rhs) const { return view_ == rhs.view_; } }; static_assert( std::ranges::view); diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctor.range.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctor.range.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctor.range.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/ctor.range.pass.cpp @@ -80,8 +80,8 @@ // C++23 and later. template constexpr StrView(R&& r) : buffer_(r.begin(), r.end()) {} - constexpr const char* begin() const { return buffer_.begin(); } - constexpr const char* end() const { return buffer_.end(); } + constexpr std::string_view::const_iterator begin() const { return buffer_.begin(); } + constexpr std::string_view::const_iterator end() const { return buffer_.end(); } constexpr bool operator==(const StrView& rhs) const { return buffer_ == rhs.buffer_; } }; static_assert( std::ranges::random_access_range); diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/end.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/end.pass.cpp --- a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/end.pass.cpp +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/end.pass.cpp @@ -29,8 +29,8 @@ constexpr ForwardViewCommonIfConst& operator=(const ForwardViewCommonIfConst&) = default; constexpr forward_iterator begin() { return forward_iterator(nullptr); } constexpr std::default_sentinel_t end() { return std::default_sentinel; } - constexpr forward_iterator begin() const { return forward_iterator(view_.begin()); } - constexpr forward_iterator end() const { return forward_iterator(view_.end()); } + constexpr forward_iterator begin() const { return forward_iterator(view_.begin()); } + constexpr forward_iterator end() const { return forward_iterator(view_.end()); } }; bool operator==(forward_iterator, std::default_sentinel_t) { return false; } @@ -45,10 +45,10 @@ constexpr ForwardViewNonCommonRange& operator=(const ForwardViewNonCommonRange&) = default; constexpr forward_iterator begin() { return forward_iterator(nullptr); } constexpr std::default_sentinel_t end() { return std::default_sentinel; } - constexpr forward_iterator begin() const { return forward_iterator(view_.begin()); } + constexpr forward_iterator begin() const { return forward_iterator(view_.begin()); } constexpr std::default_sentinel_t end() const { return std::default_sentinel; } }; -bool operator==(forward_iterator, std::default_sentinel_t) { return false; } +bool operator==(forward_iterator, std::default_sentinel_t) { return false; } constexpr bool test() { // non-const: forward_range && simple_view && simple_view

-> outer-iterator diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/types.h b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/types.h --- a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/types.h +++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/types.h @@ -24,8 +24,8 @@ constexpr explicit CopyableView() = default; constexpr CopyableView(const char* ptr) : view_(ptr) {} constexpr CopyableView(std::string_view v) : view_(v) {} - constexpr forward_iterator begin() const { return forward_iterator(view_.begin()); } - constexpr forward_iterator end() const { return forward_iterator(view_.end()); } + constexpr forward_iterator begin() const { return forward_iterator(view_.begin()); } + constexpr forward_iterator end() const { return forward_iterator(view_.end()); } constexpr bool operator==(const CopyableView& rhs) const { return view_ == rhs.view_; } }; static_assert( std::ranges::forward_range); @@ -42,8 +42,8 @@ constexpr ForwardView(std::string_view v) : view_(v) {} constexpr ForwardView(ForwardView&&) = default; constexpr ForwardView& operator=(ForwardView&&) = default; - constexpr forward_iterator begin() const { return forward_iterator(view_.begin()); } - constexpr forward_iterator end() const { return forward_iterator(view_.end()); } + constexpr forward_iterator begin() const { return forward_iterator(view_.begin()); } + constexpr forward_iterator end() const { return forward_iterator(view_.end()); } }; static_assert( std::ranges::forward_range); static_assert( std::ranges::forward_range); @@ -122,13 +122,13 @@ constexpr ForwardOnlyIfNonConstView(ForwardOnlyIfNonConstView&&) = default; constexpr ForwardOnlyIfNonConstView& operator=(ForwardOnlyIfNonConstView&&) = default; - constexpr forward_iterator begin() { return forward_iterator(view_.begin()); } - constexpr forward_iterator end() { return forward_iterator(view_.end()); } - constexpr almost_forward_iterator begin() const { - return almost_forward_iterator(view_.begin()); + constexpr forward_iterator begin() { return forward_iterator(view_.begin()); } + constexpr forward_iterator end() { return forward_iterator(view_.end()); } + constexpr almost_forward_iterator begin() const { + return almost_forward_iterator(view_.begin()); } - constexpr almost_forward_iterator end() const { - return almost_forward_iterator(view_.end()); + constexpr almost_forward_iterator end() const { + return almost_forward_iterator(view_.end()); } }; static_assert( std::ranges::forward_range);