From 7f2da5d4794a9bfa372fd3d9771c8589ba9f0198 Mon Sep 17 00:00:00 2001 From: Krystian Stasiowski Date: Sat, 15 Feb 2020 17:00:57 -0500 Subject: [PATCH] Fix handling of precondition violations in insert and replace --- include/boost/static_string/static_string.hpp | 200 ++++++++++-------- test/static_string.cpp | 23 +- 2 files changed, 126 insertions(+), 97 deletions(-) diff --git a/include/boost/static_string/static_string.hpp b/include/boost/static_string/static_string.hpp index 4de00bf..bfd24e4 100644 --- a/include/boost/static_string/static_string.hpp +++ b/include/boost/static_string/static_string.hpp @@ -1465,7 +1465,13 @@ public: insert( size_type index, const_pointer s, - size_type count); + size_type count) + { + BOOST_STATIC_STRING_THROW_IF( + index > size(), std::out_of_range{"index > size()"}); + insert(data() + index, s, s + count); + return *this; + } /** Insert a string. @@ -1734,8 +1740,7 @@ public: const_iterator pos, std::initializer_list ilist) { - const auto offset = pos - begin(); - return insert_unchecked(offset, ilist.begin(), ilist.size()).begin() + offset; + return insert_unchecked(pos, ilist.begin(), ilist.size()); } /** Insert characters from an object convertible to `string_view_type`. @@ -2688,8 +2693,12 @@ public: size_type pos, size_type n1, const_pointer s, - size_type n2); - + size_type n2) + { + BOOST_STATIC_STRING_THROW_IF( + pos > size(), std::out_of_range{"pos > size()"}); + return replace(data() + pos, data() + pos + (std::min)(n1, size() - pos), s, n2); + } /** Replace a substring with a string. @@ -2753,7 +2762,12 @@ public: size_type pos, size_type n1, size_type n2, - value_type c); + value_type c) + { + BOOST_STATIC_STRING_THROW_IF( + pos > size(), std::out_of_range{"pos > size()"}); + return replace(data() + pos, data() + pos + (std::min)(n1, size() - pos), n2, c); + } /** Replace a range with a string. @@ -2768,7 +2782,11 @@ public: Strong guarantee. - @note All references, pointers, or iterators + @note The replacement is done unchecked when + the capacity of `str` differs from that of the + string the function is called on. + + All references, pointers, or iterators referring to contained elements are invalidated. Any past-the-end iterators are also invalidated. @@ -2790,9 +2808,21 @@ public: const_iterator i1, const_iterator i2, const basic_static_string& str) + { + return replace_unchecked(i1, i2, str.data(), str.size()); + } + +#ifndef GENERATING_DOCUMENTATION + BOOST_STATIC_STRING_CPP14_CONSTEXPR + basic_static_string& + replace( + const_iterator i1, + const_iterator i2, + const basic_static_string& str) { return replace(i1, i2, str.data(), str.size()); } +#endif /** Replace a range with an object convertible to `string_view_type`. @@ -2844,7 +2874,7 @@ public: const T& t) { string_view_type sv = t; - return replace(i1 - begin(), i2 - i1, sv.data(), sv.size()); + return replace(i1, i2, sv.begin(), sv.end()); } /** Replace a range with a string. @@ -2882,7 +2912,7 @@ public: const_pointer s, size_type n) { - return replace(i1 - begin(), i2 - i1, s, n); + return replace(i1, i2, s, s + n); } /** Replace a range with a string. @@ -2954,10 +2984,7 @@ public: const_iterator i1, const_iterator i2, size_type n, - value_type c) - { - return replace(i1 - begin(), i2 - i1, n, c); - } + value_type c); /** Replace a range with a range. @@ -3061,7 +3088,7 @@ public: const_iterator i2, std::initializer_list il) { - return replace_unchecked(i1 - begin(), i2 - i1, il.begin(), il.size()); + return replace_unchecked(i1, i2, il.begin(), il.size()); } //-------------------------------------------------------------------------- @@ -4040,6 +4067,19 @@ private: size_type pos, size_type n1, const_pointer s, + size_type n2) + { + BOOST_STATIC_STRING_THROW_IF( + pos > size(), std::out_of_range{"pos > size()"}); + return replace_unchecked(data() + pos, data() + pos + n1, s, n2); + } + + BOOST_STATIC_STRING_CPP14_CONSTEXPR + basic_static_string& + replace_unchecked( + const_iterator i1, + const_iterator i2, + const_pointer s, size_type n2); BOOST_STATIC_STRING_CPP14_CONSTEXPR @@ -4047,6 +4087,19 @@ private: insert_unchecked( size_type index, const_pointer s, + size_type count) + { + BOOST_STATIC_STRING_THROW_IF( + index > size(), std::out_of_range{"index > size()"}); + insert_unchecked(data() + index, s, count); + return *this; + } + + BOOST_STATIC_STRING_CPP14_CONSTEXPR + iterator + insert_unchecked( + const_iterator pos, + const_pointer s, size_type count); }; @@ -4394,7 +4447,7 @@ operator<<( std::basic_ostream& os, const basic_static_string& s) { - return os << basic_string_view(s.data(), s.size()); + return os << typename basic_static_string::string_view_type(s.data(), s.size()); } //------------------------------------------------------------------------------ @@ -4891,24 +4944,6 @@ clear() noexcept term(); } -template -BOOST_STATIC_STRING_CPP14_CONSTEXPR -auto -basic_static_string:: -insert( - size_type index, - const_pointer s, - size_type count) -> - basic_static_string& -{ - const auto curr_size = size(); - const auto curr_data = data(); - BOOST_STATIC_STRING_THROW_IF( - index > curr_size, std::out_of_range{"index > size()"}); - insert(curr_data + index, s, s + count); - return *this; -} - template BOOST_STATIC_STRING_CPP14_CONSTEXPR auto @@ -4924,10 +4959,9 @@ insert( BOOST_STATIC_STRING_THROW_IF( count > max_size() - curr_size, std::length_error{"count() > max_size() - size()"}); const auto index = pos - curr_data; - traits_type::move(&curr_data[index + count], &curr_data[index], curr_size - index); + traits_type::move(&curr_data[index + count], &curr_data[index], curr_size - index + 1); traits_type::assign(&curr_data[index], count, ch); this->set_size(curr_size + count); - term(); return &curr_data[index]; } @@ -4994,9 +5028,7 @@ insert( const auto curr_size = size(); const auto curr_data = data(); const auto count = read_back(first, last); - const auto index = pos - curr_data; - BOOST_STATIC_STRING_THROW_IF( - index > curr_size, std::out_of_range{"index > size()"}); + const std::size_t index = pos - curr_data; std::rotate(&curr_data[index], &curr_data[curr_size + 1], &curr_data[curr_size + count + 1]); this->set_size(curr_size + count); return curr_data + index; @@ -5015,9 +5047,9 @@ erase( const auto curr_data = data(); BOOST_STATIC_STRING_THROW_IF( index > curr_size, std::out_of_range{"index > size()"}); - const auto n = (std::min)(count, curr_size - index); - traits_type::move(&curr_data[index], &curr_data[index + n], curr_size - (index + n) + 1); - this->set_size(curr_size - n); + count = (std::min)(count, curr_size - index); + traits_type::move(&curr_data[index], &curr_data[index + count], curr_size - (index + count) + 1); + this->set_size(curr_size - count); return *this; } @@ -5025,9 +5057,8 @@ template BOOST_STATIC_STRING_CPP14_CONSTEXPR auto basic_static_string:: -erase( - const_iterator pos) -> - iterator +erase(const_iterator pos) -> + iterator { erase(pos - begin(), 1); return begin() + (pos - begin()); @@ -5184,39 +5215,23 @@ BOOST_STATIC_STRING_CPP14_CONSTEXPR auto basic_static_string:: replace( - size_type pos, - size_type n1, - const_pointer s, - size_type n2) -> - basic_static_string& -{ - BOOST_STATIC_STRING_THROW_IF( - pos > size(), std::out_of_range{"pos > size()"}); - return replace(data() + pos, data() + pos + n1, s, s + n2); -} - -template -BOOST_STATIC_STRING_CPP14_CONSTEXPR -auto -basic_static_string:: -replace( - size_type pos, - size_type n1, - size_type n2, + const_iterator i1, + const_iterator i2, + size_type n, value_type c) -> basic_static_string& { const auto curr_size = size(); const auto curr_data = data(); + const std::size_t n1 = i2 - i1; BOOST_STATIC_STRING_THROW_IF( - pos > curr_size, std::out_of_range{"pos > size()"}); - BOOST_STATIC_STRING_THROW_IF( - curr_size - (std::min)(n1, curr_size - pos) >= max_size() - n2, + n > max_size() || + curr_size - n1 >= max_size() - n, std::length_error{"replaced string exceeds max_size()"}); - n1 = (std::min)(n1, curr_size - pos); - traits_type::move(&curr_data[pos + n2], &curr_data[pos + n1], curr_size - pos - n1 + 1); - traits_type::assign(&curr_data[pos], n2, c); - this->set_size((curr_size - n1) + n2); + const auto pos = i1 - curr_data; + traits_type::move(&curr_data[pos + n], i2, (end() - i2) + 1); + traits_type::assign(&curr_data[pos], n, c); + this->set_size((curr_size - n1) + n); return *this; } @@ -5236,14 +5251,14 @@ replace( { const auto curr_size = size(); const auto curr_data = data(); - std::size_t n1 = detail::distance(i1, i2); + const auto first_addr = &*j1; + const std::size_t n1 = i2 - i1; const std::size_t n2 = detail::distance(j1, j2); const std::size_t pos = i1 - curr_data; - const auto first_addr = &*j1; BOOST_STATIC_STRING_THROW_IF( + n2 > max_size() || curr_size - (std::min)(n1, curr_size - pos) >= max_size() - n2, std::length_error{"replaced string exceeds max_size()"}); - n1 = (std::min)(n1, curr_size - pos); const bool inside = detail::ptr_in_range(curr_data, curr_data + curr_size, first_addr); // due to short circuit evaluation, the second operand of the logical // AND expression will only be evaluated if s is within the string, @@ -5303,19 +5318,18 @@ replace( { const auto curr_size = size(); const auto curr_data = data(); - std::size_t n1 = detail::distance(i1, i2); + const std::size_t n1 = detail::distance(i1, i2); const std::size_t n2 = read_back(j1, j2); const std::size_t pos = i1 - curr_data; BOOST_STATIC_STRING_THROW_IF( - pos > curr_size, std::out_of_range{"pos > size()"}); - BOOST_STATIC_STRING_THROW_IF( + n2 > max_size() || curr_size - (std::min)(n1, curr_size - pos) >= max_size() - n2, std::length_error{"replaced string exceeds max_size()"}); - // Rotate to the correct order. [i2, end] will now start with the replaced string, continue to the existing string not being replaced, and end with a null terminator + // Rotate to the correct order. [i2, end] will now start with the replaced string, + // continue to the existing string not being replaced, and end with a null terminator std::rotate(&curr_data[pos], &curr_data[curr_size + 1], &curr_data[curr_size + n2 + 1]); - // Cap the size - n1 = (std::min)(n1, curr_size - pos); - // Move everything from the end of the splice point to the end of the rotated string to the begining of the splice point + // Move everything from the end of the splice point to the end of the rotated string to + // the begining of the splice point traits_type::move(&curr_data[pos + n2], &curr_data[pos + n2 + n1], ((curr_size - n1) + n2) - pos); this->set_size((curr_size - n1) + n2); return *this; @@ -5489,22 +5503,21 @@ BOOST_STATIC_STRING_CPP14_CONSTEXPR auto basic_static_string:: replace_unchecked( - size_type pos, - size_type n1, + const_iterator i1, + const_iterator i2, const_pointer s, size_type n2) -> basic_static_string& { const auto curr_data = data(); const auto curr_size = size(); + const std::size_t pos = i1 - curr_data; + const std::size_t n1 = i2 - i1; BOOST_STATIC_STRING_THROW_IF( - pos > curr_size, std::out_of_range{"pos > size()"}); - BOOST_STATIC_STRING_THROW_IF( + n2 > max_size() || curr_size - (std::min)(n1, curr_size - pos) >= max_size() - n2, std::length_error{"replaced string exceeds max_size()"}); - if (pos + n1 >= curr_size) - n1 = curr_size - pos; - traits_type::move(&curr_data[pos + n2], &curr_data[pos + n1], curr_size - pos - n1 + 1); + traits_type::move(&curr_data[pos + n2], i2, (end() - i2) + 1); traits_type::copy(&curr_data[pos], s, n2); this->set_size((curr_size - n1) + n2); return *this; @@ -5515,22 +5528,21 @@ BOOST_STATIC_STRING_CPP14_CONSTEXPR auto basic_static_string:: insert_unchecked( - size_type index, + const_iterator pos, const_pointer s, size_type count) -> - basic_static_string& + iterator { const auto curr_data = data(); const auto curr_size = size(); BOOST_STATIC_STRING_THROW_IF( - index > curr_size, std::out_of_range{"index > size()"}); - BOOST_STATIC_STRING_THROW_IF( - count > max_size() - size(), + count > max_size() - curr_size, std::length_error{"count > max_size() - size()"}); - traits_type::move(&curr_data[index + count], &curr_data[index], curr_size - index + 1); + const std::size_t index = pos - curr_data; + traits_type::move(&curr_data[index + count], pos, (end() - pos) + 1); traits_type::copy(&curr_data[index], s, count); this->set_size(curr_size + count); - return *this; + return curr_data + index; } } // static_string } // boost diff --git a/test/static_string.cpp b/test/static_string.cpp index eec2305..58d4fa1 100644 --- a/test/static_string.cpp +++ b/test/static_string.cpp @@ -210,9 +210,12 @@ testR(S s, typename S::size_type pos, typename S::size_type n1, const typename S s.size(); if (pos <= old_size) { - s.replace(pos, n1, str, n2); - s0.replace(s0.begin() + pos, s0.begin() + pos + n1, str, str + n2); - return s == expected && s0 == expected; + if (pos + n1 > s0.size()) + // this is a precondition violation for the const_iterator overload + return s.replace(pos, n1, str, n2) == expected; + else + return s.replace(pos, n1, str, n2) == expected && + s0.replace(s0.begin() + pos, s0.begin() + pos + n1, str, str + n2) == expected; } else { @@ -3595,6 +3598,7 @@ void testToStaticString() { BOOST_TEST(to_static_string(0) == "0"); + BOOST_TEST(to_static_string(0u) == "0"); BOOST_TEST(to_static_string(1) == "1"); BOOST_TEST(to_static_string(0xffff) == "65535"); BOOST_TEST(to_static_string(0x10000) == "65536"); @@ -5668,6 +5672,8 @@ testFind() BOOST_TEST(testFLN(S("hnbrcplsjfgiktoedmaq"), "qprlsfojamgndekthibc", 21, 20, S::npos)); } +#include + // done void testReplace() @@ -6635,6 +6641,10 @@ testReplace() BOOST_TEST(testR(S("abcdefghijklmnopqrst"), 20, 1, "12345678901234567890", 10, S("abcdefghijklmnopqrst1234567890"))); BOOST_TEST(testR(S("abcdefghijklmnopqrst"), 20, 1, "12345678901234567890", 19, S("abcdefghijklmnopqrst1234567890123456789"))); BOOST_TEST(testR(S("abcdefghijklmnopqrst"), 20, 1, "12345678901234567890", 20, S("abcdefghijklmnopqrst12345678901234567890"))); + + using T = static_string<10>; + BOOST_TEST_THROWS(T("12345").replace(0, 1, 500, 'a'), std::length_error); + BOOST_TEST_THROWS(T("12345").replace(0, 1, "aaaaaaaaaaaaaa"), std::length_error); } // done @@ -6901,6 +6911,13 @@ testStartsEnds() BOOST_TEST(!S("1234567890").ends_with("234")); BOOST_TEST(!S("1234567890").ends_with("12345678900")); BOOST_TEST(S("1234567890").ends_with(string_view("1234567890"))); + + BOOST_TEST(!S().starts_with('0')); + BOOST_TEST(!S().starts_with("0")); + BOOST_TEST(!S().starts_with(string_view("0"))); + BOOST_TEST(!S().ends_with('0')); + BOOST_TEST(!S().ends_with("0")); + BOOST_TEST(!S().ends_with(string_view("0"))); } void