From 55dcb84bc06c197d62e771d0e2081ea6fdca19b0 Mon Sep 17 00:00:00 2001 From: Eminy <57416756+emincan2000@users.noreply.github.com> Date: Thu, 29 Oct 2026 14:44:10 +0300 Subject: [PATCH] Fix find and rfind position handling in string and string_view (#1637) * Add docs and tutorial for etl::intrusive_avl_tree (#1608) * Add docs and tutorial for etl::intrusive_avl_tree Documents the intrusive AVL tree added in PR #1425: a full API reference page under Containers/Trees, and a tutorial demonstrating ordered access to intrusively-linked objects with unpredictable lifetimes, including move support and destructor-driven automatic removal from the tree. Co-Authored-By: Claude Sonnet 5 * Add complexity shortcode and document its usage Introduces a {{< complexity >}} Hugo shortcode (with light/dark styling for constant/logarithmic/linear/linearithmic/polynomial/exponential classes) for rendering Big-O complexity inline, applies it throughout the intrusive_avl_tree API reference in place of raw "O(...)" text, and documents the Description/Complexity structure and supported classes in DOCUMENTATION.md. Co-Authored-By: Claude Sonnet 5 * Fix issues found by @CodeRabbit * Put complexity notes on their own lines in intrusive_avl_tree docs Move the complexity shortcode from the inline "**Complexity:**" label to its own line under a "**Complexity**" label. Notes that give several alternatives (e.g. amortized vs. worst-case) now show each one on a separate line. Update the guideline in DOCUMENTATION.md to match. * Update complexity examples in DOCUMENTATION.md Add the hard line break after the "**Complexity**" label in the example, and rewrite the list of supported complexity classes in the new two-line format, including an example with several alternatives. * Add logarithmic complexity example to DOCUMENTATION.md --------- Co-authored-by: Claude Sonnet 5 Co-authored-by: John Wellbelove * Updated sponsors pages * Refactor the documentation css to group styles for light and dark themes * Update Codacy badge in README.md Updated Codacy badge link to reflect new project status. * Fix Codacy badge display * refresh codacy integration status * Expose more Zephyr Kconfigs for error handling (#1622) * zephyr: make `ETL_DEBUG` default to on if Zephyr assertions are enabled this is the more appropriate default: ETL_DEBUG controls some container assertions, so it should follow the Zephyr assertions by default * zephyr: expose new error-checking configs * zephyr: add Kconfig choices for error handling * Fix find and rfind position handling in string and string_view * Default the rfind string_view position to npos The string_view overload defaulted pos to 0, so rfind(view) only found a match at the start of the string. The other rfind overloads and std::basic_string use npos. --------- Co-authored-by: Sergei Co-authored-by: Claude Sonnet 5 Co-authored-by: John Wellbelove Co-authored-by: John Wellbelove Co-authored-by: Eden Frosst --- docs/strings/string.md | 1 + include/etl/basic_string.h | 34 ++++++--- include/etl/string_view.h | 13 +++- test/test_string_char.cpp | 32 +++++++++ test/test_string_char_external_buffer.cpp | 5 ++ test/test_string_u16.cpp | 5 ++ test/test_string_u16_external_buffer.cpp | 5 ++ test/test_string_u32.cpp | 5 ++ test/test_string_u32_external_buffer.cpp | 5 ++ test/test_string_u8.cpp | 5 ++ test/test_string_u8_external_buffer.cpp | 5 ++ test/test_string_view.cpp | 21 ++++++ test/test_string_wchar_t.cpp | 5 ++ test/test_string_wchar_t_external_buffer.cpp | 5 ++ zephyr/CMakeLists.txt | 12 +++- zephyr/Kconfig | 72 +++++++++++++++++++- 16 files changed, 212 insertions(+), 18 deletions(-) diff --git a/docs/strings/string.md b/docs/strings/string.md index b884fc58..5f53f8b1 100644 --- a/docs/strings/string.md +++ b/docs/strings/string.md @@ -707,6 +707,7 @@ size_t find(char c, size_t pos = 0) const; ```cpp size_t rfind(const etl::istring& str, size_t pos = npos) const; +size_t rfind(const etl::string_view& view, size_t pos = npos) const; size_t rfind(const char* s, size_t pos = npos) const; size_t rfind(const char* s, size_t pos, size_t n) const; size_t rfind(char c, size_t pos = npos) const; diff --git a/include/etl/basic_string.h b/include/etl/basic_string.h index 59db587a..d8256886 100644 --- a/include/etl/basic_string.h +++ b/include/etl/basic_string.h @@ -1476,6 +1476,11 @@ namespace etl //********************************************************************* size_type find(T c, size_type position = 0) const { + if (position >= size()) + { + return npos; + } + const_iterator i = etl::find(begin() + position, end(), c); if (i != end()) @@ -1504,7 +1509,7 @@ namespace etl ///\param pos The position to start searching from. //********************************************************************* template - size_type rfind(const etl::basic_string_view& view, size_type pos = 0) const + size_type rfind(const etl::basic_string_view& view, size_type pos = npos) const { return rfind_impl(view.rbegin(), view.rend(), view.size(), pos); } @@ -1544,12 +1549,8 @@ namespace etl //********************************************************************* size_type rfind(T c, size_type position = npos) const { - if (position >= size()) - { - position = size(); - } - - position = size() - position; + // Reverse offset of the first character to check, which is 'position' itself. + position = (position >= size()) ? 0 : (size() - position - 1); const_reverse_iterator i = etl::find(rbegin() + static_cast(position), rend(), c); @@ -2851,11 +2852,16 @@ namespace etl template size_type find_impl(TIterator first, TIterator last, size_type sz, size_type pos = 0) const { - if ((pos + sz) > size()) + if ((pos > size()) || (sz > (size() - pos))) { return npos; } + if (sz == 0) + { + return pos; + } + const_iterator iposition = etl::search(begin() + pos, end(), first, last); if (iposition == end()) @@ -2879,12 +2885,18 @@ namespace etl return npos; } - if (pos >= size()) + if (pos > (size() - sz)) { - pos = size(); + pos = size() - sz; } - pos = size() - pos; + if (sz == 0) + { + return pos; + } + + // Reverse offset of the last character of a match starting at 'pos'. + pos = size() - (pos + sz); const_reverse_iterator iposition = etl::search(rbegin() + static_cast(pos), rend(), rfirst, rlast); diff --git a/include/etl/string_view.h b/include/etl/string_view.h index 0827636c..0b65639e 100644 --- a/include/etl/string_view.h +++ b/include/etl/string_view.h @@ -554,11 +554,16 @@ namespace etl //************************************************************************* ETL_CONSTEXPR14 size_type find(etl::basic_string_view view, size_type position = 0) const ETL_NOEXCEPT { - if ((size() < view.size())) + if ((position > size()) || (view.size() > (size() - position))) { return npos; } + if (view.empty()) + { + return position; + } + const_iterator iposition = etl::search(begin() + position, end(), view.begin(), view.end()); if (iposition == end()) @@ -598,9 +603,11 @@ namespace etl position = etl::min(position, size() - view.size()); - const_iterator iposition = etl::find_end(begin(), begin() + position + view.size(), view.begin(), view.end()); + const_iterator search_end = begin() + position + view.size(); + const_iterator iposition = etl::find_end(begin(), search_end, view.begin(), view.end()); - if (iposition == end()) + // find_end returns the end of the searched range when there is no match. + if ((iposition == search_end) && !view.empty()) { return npos; } diff --git a/test/test_string_char.cpp b/test/test_string_char.cpp index b8271af4..ac660ce7 100644 --- a/test/test_string_char.cpp +++ b/test/test_string_char.cpp @@ -3815,6 +3815,33 @@ namespace CHECK_EQUAL(TextL::npos, position2); } + //************************************************************************* + TEST_FIXTURE(SetupFixture, test_find_rfind_all_positions) + { + const value_t the_haystack[] = STR("A haystack with a needle and another needle"); + + TextSTD compare_haystack(the_haystack); + TextL haystack(the_haystack); + + const value_t* needles[] = {STR("needle"), STR("e"), STR("")}; + + // Every position up to past the end, where the search must not read outside the string. + for (size_t position = 0UL; position <= compare_haystack.size() + 2UL; ++position) + { + for (size_t i = 0UL; i < 3UL; ++i) + { + CHECK_EQUAL(compare_haystack.find(needles[i], position), haystack.find(needles[i], position)); + CHECK_EQUAL(compare_haystack.rfind(needles[i], position), haystack.rfind(needles[i], position)); + } + + CHECK_EQUAL(compare_haystack.find(STR('e'), position), haystack.find(STR('e'), position)); + CHECK_EQUAL(compare_haystack.rfind(STR('e'), position), haystack.rfind(STR('e'), position)); + } + + CHECK_EQUAL(TextL::npos, haystack.find(STR('e'), TextL::npos)); + CHECK_EQUAL(TextL::npos, haystack.find(STR("needle"), TextL::npos)); + } + //************************************************************************* TEST_FIXTURE(SetupFixture, test_contains_string) { @@ -3991,6 +4018,11 @@ namespace position2 = haystack.rfind(needle_view, haystack.size() - 10); CHECK_EQUAL(position1, position2); + // The default position searches the whole string. + position1 = compare_haystack.rfind(compare_needle); + position2 = haystack.rfind(needle_view); + CHECK_EQUAL(position1, position2); + View pin_view(STR("pin")); position2 = haystack.rfind(pin_view); CHECK_EQUAL(TextL::npos, position2); diff --git a/test/test_string_char_external_buffer.cpp b/test/test_string_char_external_buffer.cpp index ee0789af..eba9ccdf 100644 --- a/test/test_string_char_external_buffer.cpp +++ b/test/test_string_char_external_buffer.cpp @@ -4362,6 +4362,11 @@ namespace position2 = haystack.rfind(needle_view, haystack.size() - 10); CHECK_EQUAL(position1, position2); + // The default position searches the whole string. + position1 = compare_haystack.rfind(compare_needle); + position2 = haystack.rfind(needle_view); + CHECK_EQUAL(position1, position2); + View pin_view(STR("pin")); position2 = haystack.rfind(pin_view); CHECK_EQUAL(TextL::npos, position2); diff --git a/test/test_string_u16.cpp b/test/test_string_u16.cpp index cced6e37..4f145fef 100644 --- a/test/test_string_u16.cpp +++ b/test/test_string_u16.cpp @@ -4063,6 +4063,11 @@ namespace position2 = haystack.rfind(needle_view, haystack.size() - 10); CHECK_EQUAL(position1, position2); + // The default position searches the whole string. + position1 = compare_haystack.rfind(compare_needle); + position2 = haystack.rfind(needle_view); + CHECK_EQUAL(position1, position2); + View pin_view(STR("pin")); position2 = haystack.rfind(pin_view); CHECK_EQUAL(TextL::npos, position2); diff --git a/test/test_string_u16_external_buffer.cpp b/test/test_string_u16_external_buffer.cpp index df59bf00..b3a98a25 100644 --- a/test/test_string_u16_external_buffer.cpp +++ b/test/test_string_u16_external_buffer.cpp @@ -4464,6 +4464,11 @@ namespace position2 = haystack.rfind(needle_view, haystack.size() - 10); CHECK_EQUAL(position1, position2); + // The default position searches the whole string. + position1 = compare_haystack.rfind(compare_needle); + position2 = haystack.rfind(needle_view); + CHECK_EQUAL(position1, position2); + View pin_view(STR("pin")); position2 = haystack.rfind(pin_view); CHECK_EQUAL(TextL::npos, position2); diff --git a/test/test_string_u32.cpp b/test/test_string_u32.cpp index 9a704319..1f63c9ec 100644 --- a/test/test_string_u32.cpp +++ b/test/test_string_u32.cpp @@ -4063,6 +4063,11 @@ namespace position2 = haystack.rfind(needle_view, haystack.size() - 10); CHECK_EQUAL(position1, position2); + // The default position searches the whole string. + position1 = compare_haystack.rfind(compare_needle); + position2 = haystack.rfind(needle_view); + CHECK_EQUAL(position1, position2); + View pin_view(STR("pin")); position2 = haystack.rfind(pin_view); CHECK_EQUAL(TextL::npos, position2); diff --git a/test/test_string_u32_external_buffer.cpp b/test/test_string_u32_external_buffer.cpp index 6b956c3f..0ab1863d 100644 --- a/test/test_string_u32_external_buffer.cpp +++ b/test/test_string_u32_external_buffer.cpp @@ -4464,6 +4464,11 @@ namespace position2 = haystack.rfind(needle_view, haystack.size() - 10); CHECK_EQUAL(position1, position2); + // The default position searches the whole string. + position1 = compare_haystack.rfind(compare_needle); + position2 = haystack.rfind(needle_view); + CHECK_EQUAL(position1, position2); + View pin_view(STR("pin")); position2 = haystack.rfind(pin_view); CHECK_EQUAL(TextL::npos, position2); diff --git a/test/test_string_u8.cpp b/test/test_string_u8.cpp index 36df7389..9f35de2d 100644 --- a/test/test_string_u8.cpp +++ b/test/test_string_u8.cpp @@ -4008,6 +4008,11 @@ namespace position2 = haystack.rfind(needle_view, haystack.size() - 10); CHECK_EQUAL(position1, position2); + // The default position searches the whole string. + position1 = compare_haystack.rfind(compare_needle); + position2 = haystack.rfind(needle_view); + CHECK_EQUAL(position1, position2); + View pin_view(STR("pin")); position2 = haystack.rfind(pin_view); CHECK_EQUAL(TextL::npos, position2); diff --git a/test/test_string_u8_external_buffer.cpp b/test/test_string_u8_external_buffer.cpp index 41c02cbc..32d1fa10 100644 --- a/test/test_string_u8_external_buffer.cpp +++ b/test/test_string_u8_external_buffer.cpp @@ -4383,6 +4383,11 @@ namespace position2 = haystack.rfind(needle_view, haystack.size() - 10); CHECK_EQUAL(position1, position2); + // The default position searches the whole string. + position1 = compare_haystack.rfind(compare_needle); + position2 = haystack.rfind(needle_view); + CHECK_EQUAL(position1, position2); + View pin_view(STR("pin")); position2 = haystack.rfind(pin_view); CHECK_EQUAL(TextL::npos, position2); diff --git a/test/test_string_view.cpp b/test/test_string_view.cpp index ad234834..b8d1760f 100644 --- a/test/test_string_view.cpp +++ b/test/test_string_view.cpp @@ -925,6 +925,27 @@ namespace CHECK(View::npos == view.rfind(s4, 0, 11)); } + //************************************************************************* + TEST(test_find_rfind_all_positions) + { + const std::string compare_text("abcdabcdab"); + View view(compare_text.c_str()); + + const char* needles[] = {"cd", "d", "x", ""}; + + // Every position up to past the end, where the search must not read outside the view. + for (size_t position = 0U; position <= compare_text.size() + 2U; ++position) + { + for (size_t i = 0U; i < 4U; ++i) + { + CHECK_EQUAL(compare_text.find(needles[i], position), view.find(needles[i], position)); + CHECK_EQUAL(compare_text.rfind(needles[i], position), view.rfind(needles[i], position)); + } + } + + CHECK_EQUAL(View::npos, view.find("cd", View::npos)); + } + //************************************************************************* TEST(test_find_first_of) { diff --git a/test/test_string_wchar_t.cpp b/test/test_string_wchar_t.cpp index 56688bfa..577f4261 100644 --- a/test/test_string_wchar_t.cpp +++ b/test/test_string_wchar_t.cpp @@ -4064,6 +4064,11 @@ namespace position2 = haystack.rfind(needle_view, haystack.size() - 10); CHECK_EQUAL(position1, position2); + // The default position searches the whole string. + position1 = compare_haystack.rfind(compare_needle); + position2 = haystack.rfind(needle_view); + CHECK_EQUAL(position1, position2); + View pin_view(STR("pin")); position2 = haystack.rfind(pin_view); CHECK_EQUAL(TextL::npos, position2); diff --git a/test/test_string_wchar_t_external_buffer.cpp b/test/test_string_wchar_t_external_buffer.cpp index 99ecedc0..d6260e50 100644 --- a/test/test_string_wchar_t_external_buffer.cpp +++ b/test/test_string_wchar_t_external_buffer.cpp @@ -4467,6 +4467,11 @@ namespace position2 = haystack.rfind(needle_view, haystack.size() - 10); CHECK_EQUAL(position1, position2); + // The default position searches the whole string. + position1 = compare_haystack.rfind(compare_needle); + position2 = haystack.rfind(needle_view); + CHECK_EQUAL(position1, position2); + View pin_view(STR("pin")); position2 = haystack.rfind(pin_view); CHECK_EQUAL(TextL::npos, position2); diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 60e5c2fb..4c360584 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -3,6 +3,16 @@ if(CONFIG_ETL) zephyr_link_libraries(etl::etl) zephyr_compile_definitions_ifdef(CONFIG_ETL_DEBUG ETL_DEBUG) - zephyr_compile_definitions_ifdef(CONFIG_ETL_CHECK_PUSH_POP ETL_CHECK_PUSH_POP) + zephyr_compile_definitions_ifdef(CONFIG_ETL_LOG_ERRORS ETL_LOG_ERRORS) + zephyr_compile_definitions_ifdef(CONFIG_ETL_USE_ASSERT_FUNCTION ETL_USE_ASSERT_FUNCTION) + zephyr_compile_definitions_ifdef(CONFIG_ETL_THROW_EXCEPTIONS ETL_THROW_EXCEPTIONS) + zephyr_compile_definitions_ifdef(CONFIG_ETL_NO_CHECKS ETL_NO_CHECKS) + + zephyr_compile_definitions_ifdef(CONFIG_ETL_VERBOSE_ERRORS ETL_VERBOSE_ERRORS) + zephyr_compile_definitions_ifdef(CONFIG_ETL_MINIMAL_ERRORS ETL_MINIMAL_ERRORS) + + zephyr_compile_definitions_ifdef(CONFIG_ETL_CHECK_PUSH_POP ETL_CHECK_PUSH_POP) + zephyr_compile_definitions_ifdef(CONFIG_ETL_CHECK_INDEX_OPERATOR ETL_CHECK_INDEX_OPERATOR) + zephyr_compile_definitions_ifdef(CONFIG_ETL_CHECK_EXTRA ETL_CHECK_EXTRA) endif() diff --git a/zephyr/Kconfig b/zephyr/Kconfig index ad8cd12d..b5ac8b47 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -8,12 +8,78 @@ if ETL config ETL_DEBUG bool "Build ETL with debugging checks" - default y if DEBUG + default y if ASSERT + +choice ETL_ERROR_MODE + prompt "ETL error handling mode" + help + What ETL does when one of its checks fails. + +config ETL_ASSERT_ERRORS + bool "Assert in ETL_DEBUG builds, otherwise ignore" + help + With ETL_DEBUG, a failed check calls assert(). Without it, nothing + is reported, but checks that guard an operation still skip it. + With ETL_LOG_ERRORS, a failed check calls the error handler instead, + with or without ETL_DEBUG. + +config ETL_USE_ASSERT_FUNCTION + bool "Call a user assert function" + help + A failed check calls the function registered with + etl::set_assert_function(), and checks that guard an operation + still skip it. Until one is registered, it calls assert(false). + +config ETL_THROW_EXCEPTIONS + bool "Throw an exception" + depends on CPP_EXCEPTIONS + help + A failed check throws an etl::exception. + +config ETL_NO_CHECKS + bool "No checks" + help + Check conditions are not evaluated. + +endchoice # ETL_ERROR_MODE + +config ETL_LOG_ERRORS + bool "Call the ETL error handler" + depends on !ETL_USE_ASSERT_FUNCTION && !ETL_NO_CHECKS + help + A failed check calls the callback registered with + etl::error_handler::set_callback(). With ETL_THROW_EXCEPTIONS it + then throws; otherwise, checks that guard an operation still skip + it. With no callback registered, errors are discarded. + +choice ETL_ERROR_DETAIL + prompt "ETL error detail level" + help + What information the etl::exception for a failed check carries, + whether it is thrown or passed to the error handler or assert + function. Unused when a failed check calls plain assert(). + +config ETL_TERSE_ERRORS + bool "Short error code only" + +config ETL_VERBOSE_ERRORS + bool "Message, file and line" + +config ETL_MINIMAL_ERRORS + bool "Nothing" + +endchoice # ETL_ERROR_DETAIL config ETL_CHECK_PUSH_POP bool "Enable push/pop checks in ETL" + depends on !ETL_NO_CHECKS -config ETL_LOG_ERRORS - bool "Call ETL error handler instead of assert" +config ETL_CHECK_INDEX_OPERATOR + bool "Enable bounds checks on ETL operator[]" + depends on !ETL_NO_CHECKS + +config ETL_CHECK_EXTRA + bool "Enable extra bounds and empty checks in ETL containers" + depends on !ETL_NO_CHECKS endif # ETL