From 86a9ecef3473d1bbdcf54201bd401d9a6db8fd8c Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Wed, 27 Aug 2025 21:48:03 +0300 Subject: [PATCH 01/19] wip allow *all* cstr method to use flash memory arguments ensure *all* cstr methods flow through same paths, avoid implicit String(cstr) throw out flashstringhelper method implementations in favour of cstr --- cores/esp8266/WString.cpp | 282 +++++++++++++++----------------- cores/esp8266/WString.h | 127 ++++++++++---- tests/host/core/test_string.cpp | 10 +- 3 files changed, 238 insertions(+), 181 deletions(-) diff --git a/cores/esp8266/WString.cpp b/cores/esp8266/WString.cpp index 1e608c3c92..5252150053 100644 --- a/cores/esp8266/WString.cpp +++ b/cores/esp8266/WString.cpp @@ -27,6 +27,10 @@ #include +/*********************************************/ +/* OOM Debugging */ +/*********************************************/ + #define OOM_STRING_BORDER_DISPLAY 10 #define OOM_STRING_THRESHOLD_REALLOC_WARN 128 @@ -130,11 +134,6 @@ String::String(const String &value) { *this = value; } -String::String(const __FlashStringHelper *pstr) { - init(); - *this = pstr; // see operator = -} - String::String(String &&rval) noexcept { init(); move(rval); @@ -281,17 +280,6 @@ String &String::copy(const char *cstr, unsigned int length) { return *this; } -String &String::copy(const __FlashStringHelper *pstr, unsigned int length) { - if (!reserve(length)) { - invalidate(); - return *this; - } - setLen(length); - memcpy_P(wbuffer(), (PGM_P)pstr, length); // We know wbuffer() cannot ever be in PROGMEM, so memcpy safe here - wbuffer()[length] = 0; - return *this; -} - void String::move(String &rhs) noexcept { invalidate(); sso = rhs.sso; @@ -316,15 +304,7 @@ String &String::operator =(String &&rval) noexcept { String &String::operator =(const char *cstr) { if (cstr) - copy(cstr, strlen(cstr)); - else - invalidate(); - return *this; -} - -String &String::operator =(const __FlashStringHelper *pstr) { - if (pstr) - copy(pstr, strlen_P((PGM_P)pstr)); + copy(cstr, strlen_P(cstr)); else invalidate(); return *this; @@ -343,7 +323,7 @@ String &String::operator =(char c) { bool String::concat(const String &s) { // Special case if we're concatting ourself (s += s;) since we may end up // realloc'ing the buffer and moving s.buffer in the method called - if (&s == this) { + if (this == &s) { unsigned int newlen = 2 * len(); if (!s.buffer()) return false; @@ -377,7 +357,7 @@ bool String::concat(const char *cstr, unsigned int length) { bool String::concat(const char *cstr) { if (!cstr) return false; - return concat(cstr, strlen(cstr)); + return concat(cstr, strlen_P(cstr)); } bool String::concat(char c) { @@ -420,21 +400,6 @@ bool String::concat(double num) { return concat(String(num)); } -bool String::concat(const __FlashStringHelper *str) { - if (!str) - return false; - int length = strlen_P((PGM_P)str); - if (length == 0) - return true; - unsigned int newlen = len() + length; - if (!reserve(newlen)) - return false; - memcpy_P(wbuffer() + len(), (PGM_P)str, length); - setLen(newlen); - wbuffer()[newlen] = 0; - return true; -} - /*********************************************/ /* Insert */ /*********************************************/ @@ -459,11 +424,6 @@ String &String::insert(size_t position, const char *other, size_t other_length) return *this; } -String &String::insert(size_t position, const __FlashStringHelper *other) { - auto *p = reinterpret_cast(other); - return insert(position, p, strlen_P(p)); -} - String &String::insert(size_t position, char other) { char tmp[2] { other, '\0' }; return insert(position, tmp, 1); @@ -518,9 +478,12 @@ String operator +(char lhs, const String &rhs) { String operator +(const char *lhs, const String &rhs) { String res; - res.reserve(strlen_P(lhs) + rhs.length()); - res += lhs; - res += rhs; + + const auto lhs_len = strlen_P(lhs); + res.reserve(lhs_len + rhs.length()); + res.concat(lhs, lhs_len); + res.concat(rhs); + return res; } @@ -528,84 +491,99 @@ String operator +(const char *lhs, const String &rhs) { /* Comparison */ /*********************************************/ -int String::compareTo(const String &s) const { - if (!buffer() || !s.buffer()) { - if (s.buffer() && s.len() > 0) - return 0 - *(unsigned char *)s.buffer(); +int String::compareTo(const char *cstr, size_t length) const { + if (!buffer() || !cstr) { + if (cstr && length > 0) + return 0 - static_cast(pgm_read_byte(cstr)); if (buffer() && len() > 0) - return *(unsigned char *)buffer(); + return static_cast(buffer()[0]); return 0; } - return strcmp(buffer(), s.buffer()); + + return strncmp_P(buffer(), cstr, len()); +} + +int String::compareTo(const String &s) const { + return compareTo(s.buffer(), s.len()); +} + +int String::compareTo(const char *cstr) const { + return compareTo(cstr, strlen_P(cstr)); } -bool String::equals(const String &s2) const { - return (len() == s2.len() && compareTo(s2) == 0); +bool String::equals(const String &s) const { + return (len() == s.len() && compareTo(s) == 0); } bool String::equals(const char *cstr) const { if (len() == 0) - return (cstr == NULL || *cstr == 0); + return (cstr == NULL || pgm_read_byte(cstr) == 0); if (cstr == NULL) return buffer()[0] == 0; - return strcmp(buffer(), cstr) == 0; -} - -bool String::equals(const __FlashStringHelper *s) const { - return equals(String(s)); + return strncmp_P(buffer(), cstr, len()) == 0; } bool String::operator<(const String &rhs) const { return compareTo(rhs) < 0; } +bool String::operator<(const char *rhs) const { + return compareTo(rhs) < 0; +} bool String::operator>(const String &rhs) const { return compareTo(rhs) > 0; } +bool String::operator>(const char *rhs) const { + return compareTo(rhs) > 0; +} bool String::operator<=(const String &rhs) const { return compareTo(rhs) <= 0; } +bool String::operator<=(const char *rhs) const { + return compareTo(rhs) <= 0; +} bool String::operator>=(const String &rhs) const { return compareTo(rhs) >= 0; } +bool String::operator>=(const char *rhs) const { + return compareTo(rhs) >= 0; +} -bool String::equalsIgnoreCase(const String &s2) const { - if (this == &s2) - return true; - if (len() != s2.len()) +bool String::equalsIgnoreCase(const char *str, size_t length) const { + if (len() != length) return false; - if (len() == 0) + if (length == 0) return true; - const char *p1 = buffer(); - const char *p2 = s2.buffer(); - while (*p1) { - if (tolower(*p1++) != tolower(*p2++)) - return false; - } - return true; + return strncasecmp_P(buffer(), str, length) == 0; +} + +bool String::equalsIgnoreCase(const String &s) const { + if (this == &s) + return true; + return equalsIgnoreCase(s.buffer(), s.len()); } -bool String::equalsIgnoreCase(const __FlashStringHelper *s) const { - return equalsIgnoreCase(String(s)); +bool String::equalsIgnoreCase(const char *s) const { + return equalsIgnoreCase(s, strlen_P(s)); } -unsigned char String::equalsConstantTime(const String &s2) const { +unsigned char String::equalsConstantTime(const char *str, size_t length) const { // To avoid possible time-based attacks present function // compares given strings in a constant time. - if (len() != s2.len()) + if (len() != length) return 0; //at this point lengths are the same - if (len() == 0) + if (length == 0) return 1; //at this point lengths are the same and non-zero const char *p1 = buffer(); - const char *p2 = s2.buffer(); + const char *p2 = str; unsigned int equalchars = 0; unsigned int diffchars = 0; while (*p1) { - if (*p1 == *p2) + if (*p1 == pgm_read_byte(p2)) ++equalchars; else ++diffchars; @@ -618,42 +596,51 @@ unsigned char String::equalsConstantTime(const String &s2) const { return (equalcond & diffcond); //bitwise AND } -bool String::startsWith(const String &s2) const { - if (len() < s2.len()) +unsigned char String::equalsConstantTime(const String& s) const { + return equalsConstantTime(s.buffer(), s.len()); +} + +bool String::startsWith(const char *str, size_t length, unsigned int offset) const { + if (!buffer() || !str) + return false; + if (len() < length) + return false; + if (offset > (unsigned)(len() - length)) return false; - return startsWith(s2, 0); + return strncmp_P(&buffer()[offset], str, length) == 0; } -bool String::startsWith(const char *prefix) const { - return this->startsWith(String(prefix)); +bool String::startsWith(const String &prefix) const { + return startsWith(prefix, 0); } -bool String::startsWith(const __FlashStringHelper *prefix) const { - return this->startsWith(String(prefix)); + +bool String::startsWith(const char *prefix) const { + return startsWith(prefix, 0); } -bool String::startsWith(const String &s2, unsigned int offset) const { - if (offset > (unsigned)(len() - s2.len()) || !buffer() || !s2.buffer()) - return false; - return strncmp(&buffer()[offset], s2.buffer(), s2.len()) == 0; +bool String::startsWith(const String &prefix, unsigned int offset) const { + return startsWith(prefix.buffer(), prefix.len(), offset); } -bool String::startsWith(const __FlashStringHelper *prefix, unsigned int offset) const { - return startsWith(String(prefix), offset); +bool String::startsWith(const char *prefix, unsigned int offset) const { + return startsWith(prefix, strlen_P(prefix), offset); } -bool String::endsWith(const String &s2) const { - if (len() < s2.len() || !buffer() || !s2.buffer()) +bool String::endsWith(const char *str, size_t length) const { + if (!buffer() || !str) + return false; + if (len() < length) return false; - return strcmp(&buffer()[len() - s2.len()], s2.buffer()) == 0; + return strncmp_P(&buffer()[len() - length], str, length) == 0; } -bool String::endsWith(const char *suffix) const { - return this->endsWith(String(suffix)); -} -bool String::endsWith(const __FlashStringHelper *suffix) const { - return this->endsWith(String(suffix)); +bool String::endsWith(const String &suffix) const { + return endsWith(suffix.buffer(), suffix.len()); } +bool String::endsWith(const char *suffix) const { + return endsWith(suffix, strlen_P(suffix)); +} /*********************************************/ /* Character Access */ @@ -679,7 +666,7 @@ char String::operator[](unsigned int index) const { return buffer()[index]; } -void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index) const { +void String::toCharArray(char *buf, unsigned int bufsize, unsigned int index) const { if (!bufsize || !buf) return; if (index >= len()) { @@ -689,7 +676,7 @@ void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int ind unsigned int n = bufsize - 1; if (n > len() - index) n = len() - index; - strncpy((char *)buf, buffer() + index, n); + strncpy(buf, buffer() + index, n); buf[n] = 0; } @@ -716,7 +703,7 @@ int String::indexOf(const char *s2, unsigned int fromIndex) const { } int String::indexOf(const String &s2, unsigned int fromIndex) const { - return indexOf(s2.c_str(), fromIndex); + return indexOf(s2.buffer(), fromIndex); } int String::lastIndexOf(char ch) const { @@ -731,18 +718,14 @@ int String::lastIndexOf(char ch, unsigned int fromIndex) const { return index; } -int String::lastIndexOf(const String &s2) const { - return lastIndexOf(s2, len() - s2.len()); -} - -int String::lastIndexOf(const String &s2, unsigned int fromIndex) const { - if (s2.len() == 0 || len() == 0 || s2.len() > len()) +int String::lastIndexOf(const char *str, size_t length, unsigned int fromIndex) const { + if (!len() || !length || length > len()) return -1; if (fromIndex >= len()) fromIndex = len() - 1; int found = -1; for (const char *p = buffer(); p <= buffer() + fromIndex; p++) { - p = strstr(p, s2.buffer()); + p = strstr_P(p, str); if (!p) break; if ((unsigned int)(p - buffer()) <= fromIndex) @@ -751,14 +734,22 @@ int String::lastIndexOf(const String &s2, unsigned int fromIndex) const { return found; } -int String::lastIndexOf(const __FlashStringHelper *str) const { - return lastIndexOf(String(str)); +int String::lastIndexOf(const String &str) const { + return lastIndexOf(str, len() - str.len()); } -int String::lastIndexOf(const __FlashStringHelper *str, unsigned int fromIndex) const { - return lastIndexOf(String(str), fromIndex); +int String::lastIndexOf(const String &str, unsigned int fromIndex) const { + return lastIndexOf(str.buffer(), str.len(), fromIndex); } +int String::lastIndexOf(const char *str) const { + const auto length = strlen_P(str); + return lastIndexOf(str, length, len() - length); +} + +int String::lastIndexOf(const char *str, unsigned int fromIndex) const { + return lastIndexOf(str, strlen_P(str), fromIndex); +} String String::substring(unsigned int left, unsigned int right) const { if (left > right) { @@ -788,33 +779,33 @@ void String::replace(char find, char replace) { } } -void String::replace(const String &find, const String &replace) { - if (len() == 0 || find.len() == 0) +void String::replace(const char *find, size_t find_len, const char *replace, size_t replace_len) { + if (len() == 0 || find_len == 0) return; - int diff = replace.len() - find.len(); - char *readFrom = wbuffer(); - char *foundAt; + int diff = replace_len - find_len; + const char *readFrom = buffer(); + const char *foundAt; if (diff == 0) { - while ((foundAt = strstr(readFrom, find.buffer())) != NULL) { - memmove_P(foundAt, replace.buffer(), replace.len()); - readFrom = foundAt + replace.len(); + while ((foundAt = strstr_P(readFrom, find)) != NULL) { + memmove_P(const_cast(foundAt), replace, replace_len); + readFrom = foundAt + replace_len; } } else if (diff < 0) { char *writeTo = wbuffer(); - while ((foundAt = strstr(readFrom, find.buffer())) != NULL) { + while ((foundAt = strstr_P(readFrom, find)) != NULL) { unsigned int n = foundAt - readFrom; memmove_P(writeTo, readFrom, n); writeTo += n; - memmove_P(writeTo, replace.buffer(), replace.len()); - writeTo += replace.len(); - readFrom = foundAt + find.len(); + memmove_P(writeTo, replace, replace_len); + writeTo += replace_len; + readFrom = foundAt + find_len; setLen(len() + diff); } memmove_P(writeTo, readFrom, strlen(readFrom) + 1); } else { unsigned int size = len(); // compute size needed for result - while ((foundAt = strstr(readFrom, find.buffer())) != NULL) { - readFrom = foundAt + find.len(); + while ((foundAt = strstr_P(readFrom, find)) != NULL) { + readFrom = foundAt + find_len; size += diff; } if (size == len()) @@ -823,10 +814,10 @@ void String::replace(const String &find, const String &replace) { return; int index = len() - 1; while (index >= 0 && (index = lastIndexOf(find, index)) >= 0) { - readFrom = wbuffer() + index + find.len(); - memmove_P(readFrom + diff, readFrom, len() - (readFrom - buffer())); + readFrom = wbuffer() + index + find_len; + memmove_P(const_cast(readFrom) + diff, readFrom, len() - (readFrom - buffer())); int newLen = len() + diff; - memmove_P(wbuffer() + index, replace.buffer(), replace.len()); + memmove_P(wbuffer() + index, replace, replace_len); setLen(newLen); wbuffer()[newLen] = 0; index--; @@ -834,24 +825,19 @@ void String::replace(const String &find, const String &replace) { } } - -void String::replace(const char *find, const String &replace) { - this->replace(String(find), replace); -} -void String::replace(const __FlashStringHelper *find, const String &replace) { - this->replace(String(find), replace); +void String::replace(const String &find, const String &replace) { + this->replace(find.buffer(), find.len(), replace.buffer(), replace.len()); } -void String::replace(const char *find, const char *replace) { - this->replace(String(find), String(replace)); +void String::replace(const String& find, const char *replace) { + this->replace(find.buffer(), find.len(), replace, strlen_P(replace)); } -void String::replace(const __FlashStringHelper *find, const char *replace) { - this->replace(String(find), String(replace)); +void String::replace(const char *find, const String &replace) { + this->replace(find, strlen_P(find), replace.buffer(), replace.len()); } -void String::replace(const __FlashStringHelper *find, const __FlashStringHelper *replace) { - this->replace(String(find), String(replace)); +void String::replace(const char *find, const char *replace) { + this->replace(find, strlen_P(find), replace, strlen_P(replace)); } - void String::remove(unsigned int index, unsigned int count) { if (index >= len()) { return; diff --git a/cores/esp8266/WString.h b/cores/esp8266/WString.h index 1a2aebb298..3588a9ea10 100644 --- a/cores/esp8266/WString.h +++ b/cores/esp8266/WString.h @@ -54,9 +54,11 @@ class String { String() __attribute__((always_inline)) { // See init() init(); } - String(const char *cstr); String(const String &str); - String(const __FlashStringHelper *str); + String(const char *cstr); + String(const __FlashStringHelper *str) : + String(reinterpret_cast(str)) + {} String(String &&rval) noexcept; explicit String(char c) { @@ -134,7 +136,9 @@ class String { String &operator =(const String &rhs); String &operator =(String &&rval) noexcept; String &operator =(const char *cstr); - String &operator =(const __FlashStringHelper *str); + String &operator =(const __FlashStringHelper *str) { + return *this = reinterpret_cast(str); + } String &operator =(char c); String &operator =(unsigned char value) { @@ -190,7 +194,9 @@ class String { bool concat(const String &str); bool concat(const char *cstr); bool concat(const char *cstr, unsigned int length); - bool concat(const __FlashStringHelper *str); + bool concat(const __FlashStringHelper *str) { + return concat(reinterpret_cast(str)); + } bool concat(char c); bool concat(unsigned char c); @@ -218,36 +224,82 @@ class String { } int compareTo(const String &s) const; + int compareTo(const char *cstr) const; + int compareTo(const __FlashStringHelper *str) const { + return compareTo(reinterpret_cast(str)); + } + bool equals(const String &s) const; bool equals(const char *cstr) const; - bool equals(const __FlashStringHelper *s) const; + bool equals(const __FlashStringHelper *str) const { + return equals(reinterpret_cast(str)); + } + bool operator ==(const String &rhs) const { return equals(rhs); } bool operator ==(const char *cstr) const { return equals(cstr); } + bool operator ==(const __FlashStringHelper *str) const { + return equals(str); + } bool operator !=(const String &rhs) const { return !equals(rhs); } bool operator !=(const char *cstr) const { return !equals(cstr); } + bool operator !=(const __FlashStringHelper *str) const { + return equals(str); + } bool operator <(const String &rhs) const; + bool operator <(const char *rhs) const; + bool operator <(const __FlashStringHelper *rhs) const { + return *this < reinterpret_cast(rhs); + } bool operator >(const String &rhs) const; + bool operator >(const char *rhs) const; + bool operator >(const __FlashStringHelper *rhs) const { + return *this > reinterpret_cast(rhs); + } bool operator <=(const String &rhs) const; + bool operator <=(const char *rhs) const; + bool operator <=(const __FlashStringHelper *rhs) const { + return *this <= reinterpret_cast(rhs); + } bool operator >=(const String &rhs) const; + bool operator >=(const char *rhs) const; + bool operator >=(const __FlashStringHelper *rhs) const { + return *this >= reinterpret_cast(rhs); + } + bool equalsIgnoreCase(const String &s) const; - bool equalsIgnoreCase(const __FlashStringHelper *s) const; + bool equalsIgnoreCase(const char *s) const; + bool equalsIgnoreCase(const __FlashStringHelper *s) const { + return equalsIgnoreCase(reinterpret_cast(s)); + } + + unsigned char equalsConstantTime(const char *cstr, size_t) const; unsigned char equalsConstantTime(const String &s) const; + bool startsWith(const String &prefix) const; bool startsWith(const char *prefix) const; - bool startsWith(const __FlashStringHelper *prefix) const; + bool startsWith(const __FlashStringHelper *prefix) const { + return startsWith(reinterpret_cast(prefix)); + } + bool startsWith(const String &prefix, unsigned int offset) const; - bool startsWith(const __FlashStringHelper *prefix, unsigned int offset) const; + bool startsWith(const char *prefix, unsigned int offset) const; + bool startsWith(const __FlashStringHelper *prefix, unsigned int offset) const { + return startsWith(reinterpret_cast(prefix), offset); + } + bool endsWith(const String &suffix) const; bool endsWith(const char *suffix) const; - bool endsWith(const __FlashStringHelper *suffix) const; + bool endsWith(const __FlashStringHelper *suffix) const { + return endsWith(reinterpret_cast(suffix)); + } // character access char charAt(unsigned int index) const { @@ -256,10 +308,10 @@ class String { void setCharAt(unsigned int index, char c); char operator [](unsigned int index) const; char &operator [](unsigned int index); - void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index = 0) const; - void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const { - getBytes((unsigned char *) buf, bufsize, index); + void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index = 0) const { + toCharArray((char *)buf, bufsize, index); } + void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const; const char *c_str() const { return buffer(); } char *begin() { return wbuffer(); } char *end() { return wbuffer() + length(); } @@ -270,15 +322,21 @@ class String { int indexOf(char ch, unsigned int fromIndex = 0) const; int indexOf(const char *str, unsigned int fromIndex = 0) const; int indexOf(const __FlashStringHelper *str, unsigned int fromIndex = 0) const { - return indexOf((const char*)str, fromIndex); + return indexOf(reinterpret_cast(str), fromIndex); } int indexOf(const String &str, unsigned int fromIndex = 0) const; int lastIndexOf(char ch) const; int lastIndexOf(char ch, unsigned int fromIndex) const; int lastIndexOf(const String &str) const; int lastIndexOf(const String &str, unsigned int fromIndex) const; - int lastIndexOf(const __FlashStringHelper *str) const; - int lastIndexOf(const __FlashStringHelper *str, unsigned int fromIndex) const; + int lastIndexOf(const char *str) const; + int lastIndexOf(const char *str, unsigned int fromIndex) const; + int lastIndexOf(const __FlashStringHelper *str) const { + return lastIndexOf(reinterpret_cast(str)); + } + int lastIndexOf(const __FlashStringHelper *str, unsigned int fromIndex) const { + return lastIndexOf(reinterpret_cast(str), fromIndex); + } String substring(unsigned int beginIndex) const { return substring(beginIndex, len()); } @@ -287,11 +345,18 @@ class String { // modification void replace(char find, char replace); void replace(const String &find, const String &replace); + void replace(const String &find, const char *replace); void replace(const char *find, const String &replace); - void replace(const __FlashStringHelper *find, const String &replace); + void replace(const __FlashStringHelper *find, const String &replace) { + this->replace(reinterpret_cast(find), replace); + } void replace(const char *find, const char *replace); - void replace(const __FlashStringHelper *find, const char *replace); - void replace(const __FlashStringHelper *find, const __FlashStringHelper *replace); + void replace(const __FlashStringHelper *find, const char *replace) { + this->replace(reinterpret_cast(find), replace); + } + void replace(const __FlashStringHelper *find, const __FlashStringHelper *replace) { + this->replace(reinterpret_cast(find), reinterpret_cast(replace)); + } // Pass the biggest integer if the count is not specified. // The remove method below will take care of truncating it at the end of the string. @@ -350,7 +415,6 @@ class String { friend String operator +(const char *lhs, String &&rhs); friend String operator +(const __FlashStringHelper *lhs, String &&rhs); - protected: // TODO: replace init() with a union constructor, so it's called implicitly void init(void) __attribute__((always_inline)) { @@ -377,11 +441,23 @@ class String { // copy or insert at a specific position String ©(const char *cstr, unsigned int length); - String ©(const __FlashStringHelper *pstr, unsigned int length); + String ©(const __FlashStringHelper *str, unsigned int length) { + return copy(reinterpret_cast(str), length); + } + + int compareTo(const char *str, size_t length) const; + bool equalsIgnoreCase(const char *str, size_t length) const; + bool startsWith(const char *str, size_t length, unsigned int offset) const; + bool endsWith(const char *str, size_t length) const; + int lastIndexOf(const char *str, size_t length, unsigned int fromIndex) const; + + void replace(const char *find, size_t find_len, const char *replace, size_t replace_len); String &insert(size_t position, char); String &insert(size_t position, const char *); - String &insert(size_t position, const __FlashStringHelper *); + String &insert(size_t position, const __FlashStringHelper *str) { + return insert(position, reinterpret_cast(str)); + } String &insert(size_t position, const char *, size_t length); String &insert(size_t position, const String &); @@ -432,11 +508,6 @@ inline String operator +(char lhs, String &&rhs) { return std::move(rhs.insert(0, lhs)); } -// both `char*` and `__FlashStringHelper*` are implicitly converted into `String()`, calling the `operator+(const String& ...);` -// however, here we: -// - do an automatic `reserve(total length)` for the resulting string -// - possibly do rhs.insert(0, ...), when &&rhs capacity could fit both - String operator +(const char *lhs, const String &rhs); inline String operator +(const char *lhs, String &&rhs) { @@ -444,11 +515,11 @@ inline String operator +(const char *lhs, String &&rhs) { } inline String operator +(const __FlashStringHelper *lhs, const String &rhs) { - return reinterpret_cast(lhs) + rhs; + return reinterpret_cast(lhs) + rhs; } inline String operator +(const __FlashStringHelper *lhs, String &&rhs) { - return std::move(rhs.insert(0, lhs)); + return std::move(rhs.insert(0, reinterpret_cast(lhs))); } extern const String emptyString; diff --git a/tests/host/core/test_string.cpp b/tests/host/core/test_string.cpp index 92b37e4279..0ec5cc351e 100644 --- a/tests/host/core/test_string.cpp +++ b/tests/host/core/test_string.cpp @@ -581,11 +581,11 @@ TEST_CASE("String chaining", "[core][String]") // make sure we can chain a combination of things to form a String REQUIRE((String(chunks[0]) + String(chunks[1]) + String(chunks[2]) + String(chunks[3])) == all); - REQUIRE((chunks[0] + String(chunks[1]) + F(chunks[2]) + chunks[3]) == all); - REQUIRE((String(chunks[0]) + F(chunks[1]) + F(chunks[2]) + String(chunks[3])) == all); - REQUIRE(('~' + String(&chunks[0][0] + 1) + chunks[1] + String(chunks[2]) + F(chunks[3])) + REQUIRE((chunks[0] + String(chunks[1]) + FPSTR(chunks[2]) + chunks[3]) == all); + REQUIRE((String(chunks[0]) + FPSTR(chunks[1]) + FPSTR(chunks[2]) + String(chunks[3])) == all); + REQUIRE(('~' + String(&chunks[0][0] + 1) + chunks[1] + String(chunks[2]) + FPSTR(chunks[3])) == all); - REQUIRE((String(chunks[0]) + '6' + (&chunks[1][0] + 1) + String(chunks[2]) + F(chunks[3])) + REQUIRE((String(chunks[0]) + '6' + (&chunks[1][0] + 1) + String(chunks[2]) + FPSTR(chunks[3])) == all); // these are still invalid (and also cannot compile at all): @@ -600,7 +600,7 @@ TEST_CASE("String chaining", "[core][String]") String tmp(chunks[3]); tmp.reserve(2 * all.length()); auto* ptr = tmp.c_str(); - String result("~1" + String(&chunks[0][0] + 2) + F(chunks[1]) + chunks[2] + std::move(tmp)); + String result("~1" + String(&chunks[0][0] + 2) + FPSTR(chunks[1]) + chunks[2] + std::move(tmp)); REQUIRE(result == all); REQUIRE(static_cast(result.c_str()) == static_cast(ptr)); } From 78f2ba3a0a5351a8372324a74f793ed767999362 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Fri, 29 Aug 2025 19:14:29 +0300 Subject: [PATCH 02/19] drop arduino legacy buffer assumptions & enforce length checks instead --- cores/esp8266/WString.cpp | 42 ++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/cores/esp8266/WString.cpp b/cores/esp8266/WString.cpp index 5252150053..d6afcf10fe 100644 --- a/cores/esp8266/WString.cpp +++ b/cores/esp8266/WString.cpp @@ -492,15 +492,17 @@ String operator +(const char *lhs, const String &rhs) { /*********************************************/ int String::compareTo(const char *cstr, size_t length) const { - if (!buffer() || !cstr) { - if (cstr && length > 0) - return 0 - static_cast(pgm_read_byte(cstr)); - if (buffer() && len() > 0) - return static_cast(buffer()[0]); - return 0; - } + const auto min_len = std::min(len(), length); + int res = strncmp_P(buffer(), cstr, min_len); + if (!res) + return res; + + if (len() < length) + return -1; + if (len() > length) + return 1; - return strncmp_P(buffer(), cstr, len()); + return 0; } int String::compareTo(const String &s) const { @@ -512,15 +514,23 @@ int String::compareTo(const char *cstr) const { } bool String::equals(const String &s) const { - return (len() == s.len() && compareTo(s) == 0); + return equals(s.buffer(), s.len()); } bool String::equals(const char *cstr) const { - if (len() == 0) - return (cstr == NULL || pgm_read_byte(cstr) == 0); - if (cstr == NULL) - return buffer()[0] == 0; - return strncmp_P(buffer(), cstr, len()) == 0; + return equals(cstr, strlen_P(cstr)); +} + +bool String::equals(const char *cstr, size_t length) const { + if (!cstr) + return false; + + const auto same_length = len() == length; + if (same_length && length == 0) + return true; + + return same_length + && strncmp_P(buffer(), cstr, length) == 0; } bool String::operator<(const String &rhs) const { @@ -554,9 +564,9 @@ bool String::operator>=(const char *rhs) const { bool String::equalsIgnoreCase(const char *str, size_t length) const { if (len() != length) return false; - if (length == 0) + if (!cstr || !length) return true; - return strncasecmp_P(buffer(), str, length) == 0; + return strncasecmp_P(buffer(), cstr, length) == 0; } bool String::equalsIgnoreCase(const String &s) const { From 4ad1080e93755b2cb8eab72567587b18cd168515 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Fri, 29 Aug 2025 19:15:16 +0300 Subject: [PATCH 03/19] size_t -> unsigned int to simplify host build typing --- cores/esp8266/WString.cpp | 16 ++++++++-------- cores/esp8266/WString.h | 17 +++++++++-------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/cores/esp8266/WString.cpp b/cores/esp8266/WString.cpp index d6afcf10fe..6f628228dc 100644 --- a/cores/esp8266/WString.cpp +++ b/cores/esp8266/WString.cpp @@ -491,7 +491,7 @@ String operator +(const char *lhs, const String &rhs) { /* Comparison */ /*********************************************/ -int String::compareTo(const char *cstr, size_t length) const { +int String::compareTo(const char *cstr, unsigned int length) const { const auto min_len = std::min(len(), length); int res = strncmp_P(buffer(), cstr, min_len); if (!res) @@ -521,7 +521,7 @@ bool String::equals(const char *cstr) const { return equals(cstr, strlen_P(cstr)); } -bool String::equals(const char *cstr, size_t length) const { +bool String::equals(const char *cstr, unsigned int length) const { if (!cstr) return false; @@ -561,7 +561,7 @@ bool String::operator>=(const char *rhs) const { return compareTo(rhs) >= 0; } -bool String::equalsIgnoreCase(const char *str, size_t length) const { +bool String::equalsIgnoreCase(const char *cstr, unsigned int length) const { if (len() != length) return false; if (!cstr || !length) @@ -579,7 +579,7 @@ bool String::equalsIgnoreCase(const char *s) const { return equalsIgnoreCase(s, strlen_P(s)); } -unsigned char String::equalsConstantTime(const char *str, size_t length) const { +unsigned char String::equalsConstantTime(const char *str, unsigned int length) const { // To avoid possible time-based attacks present function // compares given strings in a constant time. if (len() != length) @@ -610,7 +610,7 @@ unsigned char String::equalsConstantTime(const String& s) const { return equalsConstantTime(s.buffer(), s.len()); } -bool String::startsWith(const char *str, size_t length, unsigned int offset) const { +bool String::startsWith(const char *str, unsigned int length, unsigned int offset) const { if (!buffer() || !str) return false; if (len() < length) @@ -636,7 +636,7 @@ bool String::startsWith(const char *prefix, unsigned int offset) const { return startsWith(prefix, strlen_P(prefix), offset); } -bool String::endsWith(const char *str, size_t length) const { +bool String::endsWith(const char *str, unsigned int length) const { if (!buffer() || !str) return false; if (len() < length) @@ -728,7 +728,7 @@ int String::lastIndexOf(char ch, unsigned int fromIndex) const { return index; } -int String::lastIndexOf(const char *str, size_t length, unsigned int fromIndex) const { +int String::lastIndexOf(const char *str, unsigned int length, unsigned int fromIndex) const { if (!len() || !length || length > len()) return -1; if (fromIndex >= len()) @@ -789,7 +789,7 @@ void String::replace(char find, char replace) { } } -void String::replace(const char *find, size_t find_len, const char *replace, size_t replace_len) { +void String::replace(const char *find, unsigned int find_len, const char *replace, unsigned int replace_len) { if (len() == 0 || find_len == 0) return; int diff = replace_len - find_len; diff --git a/cores/esp8266/WString.h b/cores/esp8266/WString.h index 3588a9ea10..a3a714dd73 100644 --- a/cores/esp8266/WString.h +++ b/cores/esp8266/WString.h @@ -225,12 +225,14 @@ class String { int compareTo(const String &s) const; int compareTo(const char *cstr) const; + int compareTo(const char *str, unsigned int length) const; int compareTo(const __FlashStringHelper *str) const { return compareTo(reinterpret_cast(str)); } bool equals(const String &s) const; bool equals(const char *cstr) const; + bool equals(const char *cstr, unsigned int length) const; bool equals(const __FlashStringHelper *str) const { return equals(reinterpret_cast(str)); } @@ -276,12 +278,13 @@ class String { bool equalsIgnoreCase(const String &s) const; bool equalsIgnoreCase(const char *s) const; + bool equalsIgnoreCase(const char *str, unsigned int length) const; bool equalsIgnoreCase(const __FlashStringHelper *s) const { return equalsIgnoreCase(reinterpret_cast(s)); } - unsigned char equalsConstantTime(const char *cstr, size_t) const; unsigned char equalsConstantTime(const String &s) const; + unsigned char equalsConstantTime(const char *cstr, unsigned int length) const; bool startsWith(const String &prefix) const; bool startsWith(const char *prefix) const; @@ -291,12 +294,14 @@ class String { bool startsWith(const String &prefix, unsigned int offset) const; bool startsWith(const char *prefix, unsigned int offset) const; + bool startsWith(const char *str, unsigned int length, unsigned int offset) const; bool startsWith(const __FlashStringHelper *prefix, unsigned int offset) const { return startsWith(reinterpret_cast(prefix), offset); } bool endsWith(const String &suffix) const; bool endsWith(const char *suffix) const; + bool endsWith(const char *str, unsigned int length) const; bool endsWith(const __FlashStringHelper *suffix) const { return endsWith(reinterpret_cast(suffix)); } @@ -331,6 +336,8 @@ class String { int lastIndexOf(const String &str, unsigned int fromIndex) const; int lastIndexOf(const char *str) const; int lastIndexOf(const char *str, unsigned int fromIndex) const; + int lastIndexOf(const char *str, unsigned int length, unsigned int fromIndex) const; + int lastIndexOf(const __FlashStringHelper *str) const { return lastIndexOf(reinterpret_cast(str)); } @@ -445,13 +452,7 @@ class String { return copy(reinterpret_cast(str), length); } - int compareTo(const char *str, size_t length) const; - bool equalsIgnoreCase(const char *str, size_t length) const; - bool startsWith(const char *str, size_t length, unsigned int offset) const; - bool endsWith(const char *str, size_t length) const; - int lastIndexOf(const char *str, size_t length, unsigned int fromIndex) const; - - void replace(const char *find, size_t find_len, const char *replace, size_t replace_len); + void replace(const char *find, unsigned int find_len, const char *replace, unsigned int replace_len); String &insert(size_t position, char); String &insert(size_t position, const char *); From acb9b49b68b3c20e65e2ae2851fa2c96deec814f Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Fri, 29 Aug 2025 19:15:38 +0300 Subject: [PATCH 04/19] fixup extern operator+ dependency on insert --- cores/esp8266/WString.h | 4 +++- tests/host/core/test_string.cpp | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/WString.h b/cores/esp8266/WString.h index a3a714dd73..aea7f7237f 100644 --- a/cores/esp8266/WString.h +++ b/cores/esp8266/WString.h @@ -509,6 +509,8 @@ inline String operator +(char lhs, String &&rhs) { return std::move(rhs.insert(0, lhs)); } +// Similarly, allow c-strings and `F(...)` as both lhs and rhs + String operator +(const char *lhs, const String &rhs); inline String operator +(const char *lhs, String &&rhs) { @@ -520,7 +522,7 @@ inline String operator +(const __FlashStringHelper *lhs, const String &rhs) { } inline String operator +(const __FlashStringHelper *lhs, String &&rhs) { - return std::move(rhs.insert(0, reinterpret_cast(lhs))); + return reinterpret_cast(lhs) + std::move(rhs); } extern const String emptyString; diff --git a/tests/host/core/test_string.cpp b/tests/host/core/test_string.cpp index 0ec5cc351e..b39d894613 100644 --- a/tests/host/core/test_string.cpp +++ b/tests/host/core/test_string.cpp @@ -600,7 +600,8 @@ TEST_CASE("String chaining", "[core][String]") String tmp(chunks[3]); tmp.reserve(2 * all.length()); auto* ptr = tmp.c_str(); - String result("~1" + String(&chunks[0][0] + 2) + FPSTR(chunks[1]) + chunks[2] + std::move(tmp)); + String result("~1" + String(&chunks[0][0] + 2) + FPSTR(chunks[1]) + chunks[2] + + std::move(tmp)); REQUIRE(result == all); REQUIRE(static_cast(result.c_str()) == static_cast(ptr)); } From a8532ba5b19da9405ca86580faf354eb1d43a9b9 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Fri, 29 Aug 2025 21:12:58 +0300 Subject: [PATCH 05/19] operator< & operator> differences --- tests/host/core/test_string.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/host/core/test_string.cpp b/tests/host/core/test_string.cpp index b39d894613..d1309a9d18 100644 --- a/tests/host/core/test_string.cpp +++ b/tests/host/core/test_string.cpp @@ -187,11 +187,14 @@ TEST_CASE("String concantenation", "[core][String]") TEST_CASE("String comparison", "[core][String]") { String alpha("I like fish!"); - REQUIRE(alpha < "I like tacos!"); - REQUIRE(alpha > "I like bacon!"); + REQUIRE(alpha < "I like tacos!"); // compareTo() + REQUIRE(alpha > "I like cod!"); + REQUIRE(alpha >= "I like beef!"); + REQUIRE(alpha <= "I like soup!"); REQUIRE(alpha.equalsIgnoreCase("i LiKe FiSh!")); + REQUIRE(!alpha.equalsIgnoreCase("i LiKe FiSh! And ChIPs!")); REQUIRE(alpha.equalsConstantTime("I like fish!")); - REQUIRE(alpha != "I like fish?"); + REQUIRE(alpha != "I like fish?"); // equals() REQUIRE(alpha.startsWith("I like")); REQUIRE(!alpha.startsWith("I lick")); REQUIRE(alpha.startsWith("fish", 7)); From 98ac26882aaba42006cb83e6d8d53f2c8a412002 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Fri, 29 Aug 2025 21:54:17 +0300 Subject: [PATCH 06/19] typo --- cores/esp8266/WString.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/WString.cpp b/cores/esp8266/WString.cpp index 6f628228dc..cc1c17a5be 100644 --- a/cores/esp8266/WString.cpp +++ b/cores/esp8266/WString.cpp @@ -494,7 +494,7 @@ String operator +(const char *lhs, const String &rhs) { int String::compareTo(const char *cstr, unsigned int length) const { const auto min_len = std::min(len(), length); int res = strncmp_P(buffer(), cstr, min_len); - if (!res) + if (res) return res; if (len() < length) From cbd6ab260094972def243949b86141a5825b5efd Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Fri, 29 Aug 2025 21:55:15 +0300 Subject: [PATCH 07/19] fixup set lookup output on failure --- tests/host/core/test_string.cpp | 4 ++-- tests/host/fs/test_fs.cpp | 2 +- tests/host/fs/test_fs.inc | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/host/core/test_string.cpp b/tests/host/core/test_string.cpp index d1309a9d18..a888d2ab20 100644 --- a/tests/host/core/test_string.cpp +++ b/tests/host/core/test_string.cpp @@ -187,14 +187,14 @@ TEST_CASE("String concantenation", "[core][String]") TEST_CASE("String comparison", "[core][String]") { String alpha("I like fish!"); - REQUIRE(alpha < "I like tacos!"); // compareTo() + REQUIRE(alpha < "I like tacos!"); // compareTo() REQUIRE(alpha > "I like cod!"); REQUIRE(alpha >= "I like beef!"); REQUIRE(alpha <= "I like soup!"); REQUIRE(alpha.equalsIgnoreCase("i LiKe FiSh!")); REQUIRE(!alpha.equalsIgnoreCase("i LiKe FiSh! And ChIPs!")); REQUIRE(alpha.equalsConstantTime("I like fish!")); - REQUIRE(alpha != "I like fish?"); // equals() + REQUIRE(alpha != "I like fish?"); // equals() REQUIRE(alpha.startsWith("I like")); REQUIRE(!alpha.startsWith("I lick")); REQUIRE(alpha.startsWith("fish", 7)); diff --git a/tests/host/fs/test_fs.cpp b/tests/host/fs/test_fs.cpp index 900521b47e..022c911706 100644 --- a/tests/host/fs/test_fs.cpp +++ b/tests/host/fs/test_fs.cpp @@ -13,7 +13,7 @@ all copies or substantial portions of the Software. */ -#include +#include #include #include #include "../common/spiffs_mock.h" diff --git a/tests/host/fs/test_fs.inc b/tests/host/fs/test_fs.inc index 2f7f96beaf..14c03f2b2f 100644 --- a/tests/host/fs/test_fs.inc +++ b/tests/host/fs/test_fs.inc @@ -21,7 +21,7 @@ static std::set listDir (const char* path) std::set result; Dir dir = FSTYPE.openDir(path); while (dir.next()) { - REQUIRE(result.find(dir.fileName()) == std::end(result)); + REQUIRE(!result.count(dir.fileName())); result.insert(dir.fileName()); } return result; From 3f60138fb89d1ca24affe7112efddcf903fed638 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Wed, 10 Sep 2025 05:36:25 +0300 Subject: [PATCH 08/19] branches for different str locations consistent behaviour when accepting cstr input --- cores/esp8266/WString.cpp | 219 ++++++++++++++++++++++++++++---------- cores/esp8266/WString.h | 31 +++++- 2 files changed, 190 insertions(+), 60 deletions(-) diff --git a/cores/esp8266/WString.cpp b/cores/esp8266/WString.cpp index cc1c17a5be..f4e47d1341 100644 --- a/cores/esp8266/WString.cpp +++ b/cores/esp8266/WString.cpp @@ -27,6 +27,66 @@ #include +/*********************************************/ +/* Actual flash string helpers */ +/*********************************************/ + +/* XCHAL_INSTROM0_VADDR (0x40200000) for flash contents + * XCHAL_INSTRAM0_VADDR (0x40000000) for instram, aka 1 << 30 */ +static inline bool __attribute__((always_inline)) __pgm_expected(const void *p) +{ + return ((uintptr_t)p & (uintptr_t)((1 << 31) | (1 << 30))) > 0; +} + +struct __StringImpl { + // XXX since we are always building w/ C++ headers, these cannot be resolved through the type alone + // don't depend on glibc quirks alone, just try to reduce number of overloads by explicitly requesting them + // ref. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51785 + + using internal_strstr_t = String::internal_strstr_t; + + using const_char_strstr_t = const char *(*)(const char *, const char *); + using char_strstr_t = char *(*)(char *, const char *); + + inline static internal_strstr_t __attribute__((always_inline)) + overloaded_strstr(const_char_strstr_t func) { + return reinterpret_cast(func); + } + + inline static internal_strstr_t __attribute__((always_inline)) + overloaded_strstr(internal_strstr_t func) { + return func; + } + + // jump to specific func depending on the src address + // (similar logic is done in libc, e.g. memmove_P) + + inline static internal_strstr_t __attribute__((always_inline)) + select_strstr(const char *str) { + return __pgm_expected(str) + ? strstr_P + : overloaded_strstr(strstr); + } + + using internal_strncmp_t = String::internal_strncmp_t; + + inline static internal_strncmp_t __attribute__((always_inline)) + select_strncmp(const char *str) { + return __pgm_expected(str) + ? strncmp_P + : strncmp; + } + + using internal_strncasecmp_t = String::internal_strncasecmp_t; + + inline static internal_strncasecmp_t __attribute__((always_inline)) + select_strncasecmp(const char *str) { + return __pgm_expected(str) + ? strncasecmp_P + : strncasecmp; + } +}; + /*********************************************/ /* OOM Debugging */ /*********************************************/ @@ -269,13 +329,13 @@ bool String::changeBuffer(unsigned int maxStrLen) { /* Copy and Move */ /*********************************************/ -String &String::copy(const char *cstr, unsigned int length) { +String &String::copy(const char *str, unsigned int length) { if (!reserve(length)) { invalidate(); return *this; } setLen(length); - memmove_P(wbuffer(), cstr, length); + memmove_P(wbuffer(), str, length); wbuffer()[length] = 0; return *this; } @@ -340,24 +400,24 @@ bool String::concat(const String &s) { } } -bool String::concat(const char *cstr, unsigned int length) { +bool String::concat(const char *str, unsigned int length) { unsigned int newlen = len() + length; - if (!cstr) + if (!str) return false; if (length == 0) return true; if (!reserve(newlen)) return false; - memmove_P(wbuffer() + len(), cstr, length); + memmove_P(wbuffer() + len(), str, length); setLen(newlen); wbuffer()[newlen] = 0; return true; } bool String::concat(const char *cstr) { - if (!cstr) - return false; - return concat(cstr, strlen_P(cstr)); + if (cstr) + return concat(cstr, strlen_P(cstr)); + return false; } bool String::concat(char c) { @@ -430,7 +490,7 @@ String &String::insert(size_t position, char other) { } String &String::insert(size_t position, const char *other) { - return insert(position, other, strlen(other)); + return insert(position, other, other ? strlen_P(other) : 0); } String &String::insert(size_t position, const String &other) { @@ -479,7 +539,7 @@ String operator +(char lhs, const String &rhs) { String operator +(const char *lhs, const String &rhs) { String res; - const auto lhs_len = strlen_P(lhs); + const auto lhs_len = lhs ? strlen_P(lhs) : 0; res.reserve(lhs_len + rhs.length()); res.concat(lhs, lhs_len); res.concat(rhs); @@ -491,9 +551,9 @@ String operator +(const char *lhs, const String &rhs) { /* Comparison */ /*********************************************/ -int String::compareTo(const char *cstr, unsigned int length) const { +int String::compareToImpl(internal_strncmp_t impl, const char *str, unsigned int length) const { const auto min_len = std::min(len(), length); - int res = strncmp_P(buffer(), cstr, min_len); + int res = impl(buffer(), str, min_len); if (res) return res; @@ -506,23 +566,19 @@ int String::compareTo(const char *cstr, unsigned int length) const { } int String::compareTo(const String &s) const { - return compareTo(s.buffer(), s.len()); + return compareToImpl(strncmp, s.buffer(), s.len()); } int String::compareTo(const char *cstr) const { - return compareTo(cstr, strlen_P(cstr)); + return compareToImpl(__StringImpl::select_strncmp(cstr), cstr, cstr ? strlen_P(cstr) : 0); } -bool String::equals(const String &s) const { - return equals(s.buffer(), s.len()); -} - -bool String::equals(const char *cstr) const { - return equals(cstr, strlen_P(cstr)); +int String::compareTo(const char *str, unsigned int length) const { + return compareToImpl(__StringImpl::select_strncmp(str), str, length); } -bool String::equals(const char *cstr, unsigned int length) const { - if (!cstr) +bool String::equalsImpl(internal_strncmp_t impl, const char *str, unsigned int length) const { + if (!str) return false; const auto same_length = len() == length; @@ -530,7 +586,19 @@ bool String::equals(const char *cstr, unsigned int length) const { return true; return same_length - && strncmp_P(buffer(), cstr, length) == 0; + && impl(buffer(), str, length) == 0; +} + +bool String::equals(const String &s) const { + return equalsImpl(strncmp, s.buffer(), s.len()); +} + +bool String::equals(const char *cstr) const { + return equalsImpl(__StringImpl::select_strncmp(cstr), cstr, cstr ? strlen_P(cstr) : 0); +} + +bool String::equals(const char *str, unsigned int length) const { + return equalsImpl(__StringImpl::select_strncmp(str), str, length); } bool String::operator<(const String &rhs) const { @@ -561,22 +629,26 @@ bool String::operator>=(const char *rhs) const { return compareTo(rhs) >= 0; } -bool String::equalsIgnoreCase(const char *cstr, unsigned int length) const { +bool String::equalsIgnoreCaseImpl(internal_strncasecmp_t impl, const char *str, unsigned int length) const { if (len() != length) return false; - if (!cstr || !length) + if (!str || !length) return true; - return strncasecmp_P(buffer(), cstr, length) == 0; + return impl(buffer(), str, length) == 0; } bool String::equalsIgnoreCase(const String &s) const { if (this == &s) return true; - return equalsIgnoreCase(s.buffer(), s.len()); + return equalsIgnoreCaseImpl(strncasecmp, s.buffer(), s.len()); } -bool String::equalsIgnoreCase(const char *s) const { - return equalsIgnoreCase(s, strlen_P(s)); +bool String::equalsIgnoreCase(const char *cstr) const { + return equalsIgnoreCaseImpl(__StringImpl::select_strncasecmp(cstr), cstr, cstr ? strlen_P(cstr) : 0); +} + +bool String::equalsIgnoreCase(const char *str, unsigned int length) const { + return equalsIgnoreCaseImpl(__StringImpl::select_strncasecmp(str), str, length); } unsigned char String::equalsConstantTime(const char *str, unsigned int length) const { @@ -610,14 +682,18 @@ unsigned char String::equalsConstantTime(const String& s) const { return equalsConstantTime(s.buffer(), s.len()); } -bool String::startsWith(const char *str, unsigned int length, unsigned int offset) const { +bool String::startsWithImpl(internal_strncmp_t impl, const char *str, unsigned int length, unsigned int offset) const { if (!buffer() || !str) return false; if (len() < length) return false; if (offset > (unsigned)(len() - length)) return false; - return strncmp_P(&buffer()[offset], str, length) == 0; + return impl(&buffer()[offset], str, length) == 0; +} + +bool String::startsWith(const char *str, unsigned int length, unsigned int offset) const { + return startsWithImpl(__StringImpl::select_strncmp(str), str, length, offset); } bool String::startsWith(const String &prefix) const { @@ -629,27 +705,31 @@ bool String::startsWith(const char *prefix) const { } bool String::startsWith(const String &prefix, unsigned int offset) const { - return startsWith(prefix.buffer(), prefix.len(), offset); + return startsWithImpl(strncmp, prefix.buffer(), prefix.len(), offset); } bool String::startsWith(const char *prefix, unsigned int offset) const { - return startsWith(prefix, strlen_P(prefix), offset); + return startsWithImpl(__StringImpl::select_strncmp(prefix), prefix, prefix ? strlen_P(prefix) : 0, offset); } -bool String::endsWith(const char *str, unsigned int length) const { +bool String::endsWithImpl(internal_strncmp_t impl, const char *str, unsigned int length) const { if (!buffer() || !str) return false; if (len() < length) return false; - return strncmp_P(&buffer()[len() - length], str, length) == 0; + return impl(&buffer()[len() - length], str, length) == 0; +} + +bool String::endsWith(const char *suffix, unsigned int length) const { + return endsWithImpl(__StringImpl::select_strncmp(suffix), suffix, length); } bool String::endsWith(const String &suffix) const { - return endsWith(suffix.buffer(), suffix.len()); + return endsWithImpl(strncmp, suffix.buffer(), suffix.len()); } bool String::endsWith(const char *suffix) const { - return endsWith(suffix, strlen_P(suffix)); + return endsWithImpl(__StringImpl::select_strncmp(suffix), suffix, suffix ? strlen_P(suffix) : 0); } /*********************************************/ @@ -703,17 +783,23 @@ int String::indexOf(char ch, unsigned int fromIndex) const { return temp - buffer(); } -int String::indexOf(const char *s2, unsigned int fromIndex) const { +int String::indexOfImpl(internal_strstr_t impl, const char *str, unsigned int length, unsigned int fromIndex) const { if (fromIndex >= len()) return -1; - const char *found = strstr_P(buffer() + fromIndex, s2); + if (length > len()) + return -1; + const char *found = impl(buffer() + fromIndex, str); if (found == NULL) return -1; return found - buffer(); } +int String::indexOf(const char *cstr, unsigned int fromIndex) const { + return indexOfImpl(__StringImpl::select_strstr(cstr), cstr, cstr ? strlen_P(cstr) : 0, fromIndex); +} + int String::indexOf(const String &s2, unsigned int fromIndex) const { - return indexOf(s2.buffer(), fromIndex); + return indexOfImpl(__StringImpl::overloaded_strstr(strstr), s2.buffer(), s2.len(), fromIndex); } int String::lastIndexOf(char ch) const { @@ -728,14 +814,14 @@ int String::lastIndexOf(char ch, unsigned int fromIndex) const { return index; } -int String::lastIndexOf(const char *str, unsigned int length, unsigned int fromIndex) const { +int String::lastIndexOfImpl(internal_strstr_t impl, const char *str, unsigned int length, unsigned int fromIndex) const { if (!len() || !length || length > len()) return -1; if (fromIndex >= len()) fromIndex = len() - 1; int found = -1; for (const char *p = buffer(); p <= buffer() + fromIndex; p++) { - p = strstr_P(p, str); + p = impl(p, str); if (!p) break; if ((unsigned int)(p - buffer()) <= fromIndex) @@ -744,8 +830,12 @@ int String::lastIndexOf(const char *str, unsigned int length, unsigned int fromI return found; } +int String::lastIndexOf(const char *str, unsigned int length, unsigned int fromIndex) const { + return lastIndexOfImpl(__StringImpl::select_strstr(str), str, length, fromIndex); +} + int String::lastIndexOf(const String &str) const { - return lastIndexOf(str, len() - str.len()); + return lastIndexOfImpl(__StringImpl::overloaded_strstr(strstr), str.buffer(), str.len(), len() - str.len()); } int String::lastIndexOf(const String &str, unsigned int fromIndex) const { @@ -753,12 +843,12 @@ int String::lastIndexOf(const String &str, unsigned int fromIndex) const { } int String::lastIndexOf(const char *str) const { - const auto length = strlen_P(str); + const auto length = str ? strlen_P(str) : 0; return lastIndexOf(str, length, len() - length); } int String::lastIndexOf(const char *str, unsigned int fromIndex) const { - return lastIndexOf(str, strlen_P(str), fromIndex); + return lastIndexOf(str, str ? strlen_P(str) : 0, fromIndex); } String String::substring(unsigned int left, unsigned int right) const { @@ -789,32 +879,38 @@ void String::replace(char find, char replace) { } } -void String::replace(const char *find, unsigned int find_len, const char *replace, unsigned int replace_len) { +void String::replaceImpl(internal_strstr_t impl, const char *find, unsigned int find_len, const char *replace, unsigned int replace_len) { if (len() == 0 || find_len == 0) return; int diff = replace_len - find_len; const char *readFrom = buffer(); const char *foundAt; if (diff == 0) { - while ((foundAt = strstr_P(readFrom, find)) != NULL) { + while ((foundAt = const_cast(impl(readFrom, find))) != NULL) { memmove_P(const_cast(foundAt), replace, replace_len); readFrom = foundAt + replace_len; } } else if (diff < 0) { + unsigned int remainingLen = len() + 1; char *writeTo = wbuffer(); - while ((foundAt = strstr_P(readFrom, find)) != NULL) { + while ((foundAt = const_cast(impl(readFrom, find))) != NULL) { unsigned int n = foundAt - readFrom; memmove_P(writeTo, readFrom, n); + writeTo += n; memmove_P(writeTo, replace, replace_len); + writeTo += replace_len; readFrom = foundAt + find_len; - setLen(len() + diff); + + unsigned int currentLen = len(); + remainingLen = currentLen - n - find_len + 1; + setLen(currentLen + diff); } - memmove_P(writeTo, readFrom, strlen(readFrom) + 1); + memmove_P(writeTo, readFrom, remainingLen); } else { unsigned int size = len(); // compute size needed for result - while ((foundAt = strstr_P(readFrom, find)) != NULL) { + while ((foundAt = const_cast(impl(readFrom, find))) != NULL) { readFrom = foundAt + find_len; size += diff; } @@ -823,7 +919,7 @@ void String::replace(const char *find, unsigned int find_len, const char *replac if (size > capacity() && !changeBuffer(size)) return; int index = len() - 1; - while (index >= 0 && (index = lastIndexOf(find, index)) >= 0) { + while (index >= 0 && (index = lastIndexOf(find, find_len, index)) >= 0) { readFrom = wbuffer() + index + find_len; memmove_P(const_cast(readFrom) + diff, readFrom, len() - (readFrom - buffer())); int newLen = len() + diff; @@ -835,17 +931,28 @@ void String::replace(const char *find, unsigned int find_len, const char *replac } } +void String::replace(const char *find, unsigned int find_len, const char *replace, unsigned int replace_len) { + this->replaceImpl(__StringImpl::select_strstr(find), find, find_len, replace, replace_len); +} void String::replace(const String &find, const String &replace) { - this->replace(find.buffer(), find.len(), replace.buffer(), replace.len()); + this->replaceImpl(__StringImpl::overloaded_strstr(strstr), + find.buffer(), find.len(), + replace.buffer(), replace.len()); } void String::replace(const String& find, const char *replace) { - this->replace(find.buffer(), find.len(), replace, strlen_P(replace)); + this->replaceImpl(__StringImpl::overloaded_strstr(strstr), + find.buffer(), find.len(), + replace, replace ? strlen_P(replace) : 0); } void String::replace(const char *find, const String &replace) { - this->replace(find, strlen_P(find), replace.buffer(), replace.len()); + this->replaceImpl(__StringImpl::select_strstr(find), + find, find ? strlen_P(find) : 0, + replace.buffer(), replace.len()); } void String::replace(const char *find, const char *replace) { - this->replace(find, strlen_P(find), replace, strlen_P(replace)); + this->replaceImpl(__StringImpl::select_strstr(find), + find, find ? strlen_P(find) : 0, + replace, replace ? strlen_P(replace) : 0); } void String::remove(unsigned int index, unsigned int count) { diff --git a/cores/esp8266/WString.h b/cores/esp8266/WString.h index aea7f7237f..c1fd9933c0 100644 --- a/cores/esp8266/WString.h +++ b/cores/esp8266/WString.h @@ -193,7 +193,7 @@ class String { // concatenation is considered unsuccessful. bool concat(const String &str); bool concat(const char *cstr); - bool concat(const char *cstr, unsigned int length); + bool concat(const char *str, unsigned int length); bool concat(const __FlashStringHelper *str) { return concat(reinterpret_cast(str)); } @@ -232,7 +232,7 @@ class String { bool equals(const String &s) const; bool equals(const char *cstr) const; - bool equals(const char *cstr, unsigned int length) const; + bool equals(const char *str, unsigned int length) const; bool equals(const __FlashStringHelper *str) const { return equals(reinterpret_cast(str)); } @@ -284,7 +284,7 @@ class String { } unsigned char equalsConstantTime(const String &s) const; - unsigned char equalsConstantTime(const char *cstr, unsigned int length) const; + unsigned char equalsConstantTime(const char *str, unsigned int length) const; bool startsWith(const String &prefix) const; bool startsWith(const char *prefix) const; @@ -353,11 +353,17 @@ class String { void replace(char find, char replace); void replace(const String &find, const String &replace); void replace(const String &find, const char *replace); + void replace(const String &find, const __FlashStringHelper *replace) { + this->replace(find, reinterpret_cast(replace)); + } void replace(const char *find, const String &replace); void replace(const __FlashStringHelper *find, const String &replace) { this->replace(reinterpret_cast(find), replace); } void replace(const char *find, const char *replace); + void replace(const char *find, const __FlashStringHelper *replace) { + this->replace(find, reinterpret_cast(replace)); + } void replace(const __FlashStringHelper *find, const char *replace) { this->replace(reinterpret_cast(find), replace); } @@ -447,7 +453,7 @@ class String { bool changeBuffer(unsigned int maxStrLen); // copy or insert at a specific position - String ©(const char *cstr, unsigned int length); + String ©(const char *str, unsigned int length); String ©(const __FlashStringHelper *str, unsigned int length) { return copy(reinterpret_cast(str), length); } @@ -464,6 +470,23 @@ class String { // rvalue helper void move(String &rhs) noexcept; + + // attempt to optimize internal implementations for either RAM of Flash + using internal_strncasecmp_t = int (*)(const char *, const char *, size_t); + using internal_strncmp_t = int (*)(const char *, const char *, size_t); + using internal_strstr_t = char *(*)(const char *, const char *); + + int compareToImpl(internal_strncmp_t, const char *str, unsigned int length) const; + bool equalsImpl(internal_strncmp_t, const char *str, unsigned int length) const; + bool equalsIgnoreCaseImpl(internal_strncasecmp_t, const char *str, unsigned int length) const; + bool startsWithImpl(internal_strncmp_t, const char *str, unsigned int length, unsigned int offset) const; + bool endsWithImpl(internal_strncmp_t, const char *str, unsigned int length) const; + + int indexOfImpl(internal_strstr_t, const char *str, unsigned int length, unsigned int fromIndex) const; + int lastIndexOfImpl(internal_strstr_t, const char *str, unsigned int length, unsigned int fromIndex) const; + void replaceImpl(internal_strstr_t impl, const char *find, unsigned int find_len, const char *replace, unsigned int replace_len); + + friend struct __StringImpl; }; // concatenation (note that it's done using non-method operators to handle both possible type refs) From 1b3b3f847cb36de79645d5c77a66f4da05460fe9 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Wed, 10 Sep 2025 05:41:29 +0300 Subject: [PATCH 09/19] note about strlen in replace --- cores/esp8266/WString.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/WString.cpp b/cores/esp8266/WString.cpp index f4e47d1341..aa5b7ad739 100644 --- a/cores/esp8266/WString.cpp +++ b/cores/esp8266/WString.cpp @@ -904,7 +904,7 @@ void String::replaceImpl(internal_strstr_t impl, const char *find, unsigned int readFrom = foundAt + find_len; unsigned int currentLen = len(); - remainingLen = currentLen - n - find_len + 1; + remainingLen = currentLen - n - find_len + 1; // avoid strlen(), ref. #5949 setLen(currentLen + diff); } memmove_P(writeTo, readFrom, remainingLen); From 6d28d99b0d0ed989c27cb06f25c2d145edc13257 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Wed, 10 Sep 2025 06:28:14 +0300 Subject: [PATCH 10/19] consistent null checks expect empty cstr for simple methods, avoid doing it w/ length present expect {w,}buffer to be present, things are catastrophic anyways also, redundant wbuffer check for free() --- cores/esp8266/WString.cpp | 103 +++++++++++++++++++------------------- cores/esp8266/WString.h | 10 ++-- 2 files changed, 56 insertions(+), 57 deletions(-) diff --git a/cores/esp8266/WString.cpp b/cores/esp8266/WString.cpp index aa5b7ad739..8922d5b434 100644 --- a/cores/esp8266/WString.cpp +++ b/cores/esp8266/WString.cpp @@ -31,13 +31,6 @@ /* Actual flash string helpers */ /*********************************************/ -/* XCHAL_INSTROM0_VADDR (0x40200000) for flash contents - * XCHAL_INSTRAM0_VADDR (0x40000000) for instram, aka 1 << 30 */ -static inline bool __attribute__((always_inline)) __pgm_expected(const void *p) -{ - return ((uintptr_t)p & (uintptr_t)((1 << 31) | (1 << 30))) > 0; -} - struct __StringImpl { // XXX since we are always building w/ C++ headers, these cannot be resolved through the type alone // don't depend on glibc quirks alone, just try to reduce number of overloads by explicitly requesting them @@ -58,6 +51,14 @@ struct __StringImpl { return func; } + // XCHAL_INSTROM0_VADDR (0x40200000) for flash contents + // XCHAL_INSTRAM0_VADDR (0x40000000) for instram, aka 1 << 30 + static inline bool __attribute__((always_inline, const)) + __pgm_expected(const void *p) + { + return ((uintptr_t)p & (uintptr_t)((1 << 31) | (1 << 30))) > 0; + } + // jump to specific func depending on the src address // (similar logic is done in libc, e.g. memmove_P) @@ -240,13 +241,13 @@ String::String(double value, unsigned char decimalPlaces) : /*********************************************/ void String::invalidate(void) { - if (!isSSO() && wbuffer()) + if (!isSSO()) free(wbuffer()); init(); } bool String::reserve(unsigned int size) { - if (buffer() && capacity() >= size) + if (capacity() >= size) return true; if (changeBuffer(size)) { if (len() == 0) @@ -371,8 +372,7 @@ String &String::operator =(const char *cstr) { } String &String::operator =(char c) { - char buffer[2] { c, '\0' }; - *this = buffer; + copy(&c, 1); return *this; } @@ -402,8 +402,6 @@ bool String::concat(const String &s) { bool String::concat(const char *str, unsigned int length) { unsigned int newlen = len() + length; - if (!str) - return false; if (length == 0) return true; if (!reserve(newlen)) @@ -464,16 +462,16 @@ bool String::concat(double num) { /* Insert */ /*********************************************/ -String &String::insert(size_t position, const char *other, size_t other_length) { - if (position > length()) +String &String::insert(size_t position, const char *other, unsigned int other_length) { + auto this_length = len(); + if (position > this_length) return *this; - auto len = length(); - auto total = len + other_length; + auto total = this_length + other_length; if (!reserve(total)) return *this; - auto left = len - position; + auto left = this_length - position; setLen(total); auto *start = wbuffer() + position; @@ -490,7 +488,9 @@ String &String::insert(size_t position, char other) { } String &String::insert(size_t position, const char *other) { - return insert(position, other, other ? strlen_P(other) : 0); + if (!other) + return *this; + return insert(position, other, strlen_P(other)); } String &String::insert(size_t position, const String &other) { @@ -538,12 +538,10 @@ String operator +(char lhs, const String &rhs) { String operator +(const char *lhs, const String &rhs) { String res; - const auto lhs_len = lhs ? strlen_P(lhs) : 0; res.reserve(lhs_len + rhs.length()); res.concat(lhs, lhs_len); res.concat(rhs); - return res; } @@ -570,7 +568,9 @@ int String::compareTo(const String &s) const { } int String::compareTo(const char *cstr) const { - return compareToImpl(__StringImpl::select_strncmp(cstr), cstr, cstr ? strlen_P(cstr) : 0); + if (!cstr) + return 1; + return compareToImpl(__StringImpl::select_strncmp(cstr), cstr, strlen_P(cstr)); } int String::compareTo(const char *str, unsigned int length) const { @@ -578,9 +578,6 @@ int String::compareTo(const char *str, unsigned int length) const { } bool String::equalsImpl(internal_strncmp_t impl, const char *str, unsigned int length) const { - if (!str) - return false; - const auto same_length = len() == length; if (same_length && length == 0) return true; @@ -632,7 +629,7 @@ bool String::operator>=(const char *rhs) const { bool String::equalsIgnoreCaseImpl(internal_strncasecmp_t impl, const char *str, unsigned int length) const { if (len() != length) return false; - if (!str || !length) + if (!length) return true; return impl(buffer(), str, length) == 0; } @@ -683,8 +680,6 @@ unsigned char String::equalsConstantTime(const String& s) const { } bool String::startsWithImpl(internal_strncmp_t impl, const char *str, unsigned int length, unsigned int offset) const { - if (!buffer() || !str) - return false; if (len() < length) return false; if (offset > (unsigned)(len() - length)) @@ -701,6 +696,8 @@ bool String::startsWith(const String &prefix) const { } bool String::startsWith(const char *prefix) const { + if (!prefix) + return false; return startsWith(prefix, 0); } @@ -709,12 +706,12 @@ bool String::startsWith(const String &prefix, unsigned int offset) const { } bool String::startsWith(const char *prefix, unsigned int offset) const { - return startsWithImpl(__StringImpl::select_strncmp(prefix), prefix, prefix ? strlen_P(prefix) : 0, offset); + if (!prefix) + return false; + return startsWithImpl(__StringImpl::select_strncmp(prefix), prefix, strlen_P(prefix), offset); } bool String::endsWithImpl(internal_strncmp_t impl, const char *str, unsigned int length) const { - if (!buffer() || !str) - return false; if (len() < length) return false; return impl(&buffer()[len() - length], str, length) == 0; @@ -729,7 +726,9 @@ bool String::endsWith(const String &suffix) const { } bool String::endsWith(const char *suffix) const { - return endsWithImpl(__StringImpl::select_strncmp(suffix), suffix, suffix ? strlen_P(suffix) : 0); + if (!suffix) + return false; + return endsWithImpl(__StringImpl::select_strncmp(suffix), suffix, strlen_P(suffix)); } /*********************************************/ @@ -743,7 +742,7 @@ void String::setCharAt(unsigned int loc, char c) { char &String::operator[](unsigned int index) { static char dummy_writable_char; - if (index >= len() || !buffer()) { + if (index >= len()) { dummy_writable_char = 0; return dummy_writable_char; } @@ -795,11 +794,13 @@ int String::indexOfImpl(internal_strstr_t impl, const char *str, unsigned int le } int String::indexOf(const char *cstr, unsigned int fromIndex) const { - return indexOfImpl(__StringImpl::select_strstr(cstr), cstr, cstr ? strlen_P(cstr) : 0, fromIndex); + if (!cstr) + return -1; + return indexOfImpl(__StringImpl::select_strstr(cstr), cstr, strlen_P(cstr), fromIndex); } -int String::indexOf(const String &s2, unsigned int fromIndex) const { - return indexOfImpl(__StringImpl::overloaded_strstr(strstr), s2.buffer(), s2.len(), fromIndex); +int String::indexOf(const String &str, unsigned int fromIndex) const { + return indexOfImpl(__StringImpl::overloaded_strstr(strstr), str.buffer(), str.len(), fromIndex); } int String::lastIndexOf(char ch) const { @@ -842,13 +843,16 @@ int String::lastIndexOf(const String &str, unsigned int fromIndex) const { return lastIndexOf(str.buffer(), str.len(), fromIndex); } -int String::lastIndexOf(const char *str) const { - const auto length = str ? strlen_P(str) : 0; - return lastIndexOf(str, length, len() - length); +int String::lastIndexOf(const char *cstr) const { + if (!cstr) + return -1; + return lastIndexOf(cstr, strlen_P(cstr), 0); } -int String::lastIndexOf(const char *str, unsigned int fromIndex) const { - return lastIndexOf(str, str ? strlen_P(str) : 0, fromIndex); +int String::lastIndexOf(const char *cstr, unsigned int fromIndex) const { + if (!cstr) + return -1; + return lastIndexOf(cstr, strlen_P(cstr), fromIndex); } String String::substring(unsigned int left, unsigned int right) const { @@ -871,8 +875,6 @@ String String::substring(unsigned int left, unsigned int right) const { /*********************************************/ void String::replace(char find, char replace) { - if (!buffer()) - return; for (char *p = wbuffer(); *p; p++) { if (*p == find) *p = replace; @@ -898,7 +900,8 @@ void String::replaceImpl(internal_strstr_t impl, const char *find, unsigned int memmove_P(writeTo, readFrom, n); writeTo += n; - memmove_P(writeTo, replace, replace_len); + if (replace_len) + memmove_P(writeTo, replace, replace_len); writeTo += replace_len; readFrom = foundAt + find_len; @@ -973,23 +976,19 @@ void String::remove(unsigned int index, unsigned int count) { } void String::toLowerCase(void) { - if (!buffer()) - return; for (char *p = wbuffer(); *p; p++) { *p = tolower(*p); } } void String::toUpperCase(void) { - if (!buffer()) - return; for (char *p = wbuffer(); *p; p++) { *p = toupper(*p); } } void String::trim(void) { - if (!buffer() || len() == 0) + if (len() == 0) return; char *begin = wbuffer(); while (isspace(*begin)) @@ -1009,19 +1008,19 @@ void String::trim(void) { /*********************************************/ long String::toInt(void) const { - if (buffer()) + if (len()) return atol(buffer()); return 0; } float String::toFloat(void) const { - if (buffer()) + if (len()) return atof(buffer()); return 0.0F; } double String::toDouble(void) const { - if (buffer()) + if (len()) return atof(buffer()); return 0.0; } diff --git a/cores/esp8266/WString.h b/cores/esp8266/WString.h index c1fd9933c0..e41920f3a5 100644 --- a/cores/esp8266/WString.h +++ b/cores/esp8266/WString.h @@ -334,8 +334,8 @@ class String { int lastIndexOf(char ch, unsigned int fromIndex) const; int lastIndexOf(const String &str) const; int lastIndexOf(const String &str, unsigned int fromIndex) const; - int lastIndexOf(const char *str) const; - int lastIndexOf(const char *str, unsigned int fromIndex) const; + int lastIndexOf(const char *cstr) const; + int lastIndexOf(const char *cstr, unsigned int fromIndex) const; int lastIndexOf(const char *str, unsigned int length, unsigned int fromIndex) const; int lastIndexOf(const __FlashStringHelper *str) const { @@ -461,12 +461,12 @@ class String { void replace(const char *find, unsigned int find_len, const char *replace, unsigned int replace_len); String &insert(size_t position, char); - String &insert(size_t position, const char *); + String &insert(size_t position, const char * str); String &insert(size_t position, const __FlashStringHelper *str) { return insert(position, reinterpret_cast(str)); } - String &insert(size_t position, const char *, size_t length); - String &insert(size_t position, const String &); + String &insert(size_t position, const char * str, unsigned int length); + String &insert(size_t position, const String &str); // rvalue helper void move(String &rhs) noexcept; From edb223078f28544e717be3fcabdc5b1413e43ccb Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Wed, 10 Sep 2025 06:30:38 +0300 Subject: [PATCH 11/19] update replacement test --- tests/host/core/test_string.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/tests/host/core/test_string.cpp b/tests/host/core/test_string.cpp index a888d2ab20..6c76a07461 100644 --- a/tests/host/core/test_string.cpp +++ b/tests/host/core/test_string.cpp @@ -44,12 +44,24 @@ TEST_CASE("String::trim", "[core][String]") TEST_CASE("String::replace", "[core][String]") { - String str; - str = "The quick brown fox jumped over the lazy dog."; - String find = "fox"; - String replace = "vulpes vulpes"; - str.replace(find, replace); + const char data[] = "The quick brown fox jumped over the lazy dog."; + String str = data; + str.replace("fox", "vulpes vulpes"); REQUIRE(str == "The quick brown vulpes vulpes jumped over the lazy dog."); + str.replace("vulpes", "lis lis"); + REQUIRE(str == "The quick brown lis lis lis lis jumped over the lazy dog."); + str.replace("lazy dog.", "canis piger"); + REQUIRE(str == "The quick brown lis lis lis lis jumped over the canis piger"); + str.replace("brown lis lis", "lis"); + REQUIRE(str == "The quick lis lis lis jumped over the canis piger"); + str.replace("lis lis", "fox"); + REQUIRE(str == "The quick fox lis jumped over the canis piger"); + str.replace("fox lis jumped", "brown fox"); + REQUIRE(str == "The quick brown fox over the canis piger"); + str.replace(" over ", " jumped over "); + str.replace("canis piger", "lazy dog"); + str.replace("dog", "dog."); + REQUIRE(str == data); } TEST_CASE("String(value, base)", "[core][String]") From d39fefbcb973056701fb85cd8e636584547b5874 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Wed, 10 Sep 2025 06:31:50 +0300 Subject: [PATCH 12/19] explicit strstr & strstr_P overload selection introduce sys/ pgmspace.h, string.h and stdio.h consistent signatures, avoid depending on newlib quirks allow strstr & strstr_P to be the same thing --- cores/esp8266/WString.cpp | 5 ++--- tests/host/common/stdio.h | 3 +++ tests/host/common/string.h | 3 +++ tests/host/core/test_string.cpp | 2 +- tests/host/sys/pgmspace.h | 38 ++------------------------------- tests/host/sys/string.h | 20 +++++++++++++++++ 6 files changed, 31 insertions(+), 40 deletions(-) create mode 100644 tests/host/common/stdio.h create mode 100644 tests/host/common/string.h create mode 100644 tests/host/sys/string.h diff --git a/cores/esp8266/WString.cpp b/cores/esp8266/WString.cpp index 8922d5b434..5bf5d6d0d8 100644 --- a/cores/esp8266/WString.cpp +++ b/cores/esp8266/WString.cpp @@ -32,8 +32,7 @@ /*********************************************/ struct __StringImpl { - // XXX since we are always building w/ C++ headers, these cannot be resolved through the type alone - // don't depend on glibc quirks alone, just try to reduce number of overloads by explicitly requesting them + // XXX String methods accepting `internal_strstr_t` should be able to properly select overloads // ref. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51785 using internal_strstr_t = String::internal_strstr_t; @@ -65,7 +64,7 @@ struct __StringImpl { inline static internal_strstr_t __attribute__((always_inline)) select_strstr(const char *str) { return __pgm_expected(str) - ? strstr_P + ? overloaded_strstr(strstr_P) : overloaded_strstr(strstr); } diff --git a/tests/host/common/stdio.h b/tests/host/common/stdio.h new file mode 100644 index 0000000000..dc82a195ab --- /dev/null +++ b/tests/host/common/stdio.h @@ -0,0 +1,3 @@ +#pragma once +#include_next +#include diff --git a/tests/host/common/string.h b/tests/host/common/string.h new file mode 100644 index 0000000000..400aed7969 --- /dev/null +++ b/tests/host/common/string.h @@ -0,0 +1,3 @@ +#pragma once +#include_next +#include diff --git a/tests/host/core/test_string.cpp b/tests/host/core/test_string.cpp index 6c76a07461..c4f767b59a 100644 --- a/tests/host/core/test_string.cpp +++ b/tests/host/core/test_string.cpp @@ -45,7 +45,7 @@ TEST_CASE("String::trim", "[core][String]") TEST_CASE("String::replace", "[core][String]") { const char data[] = "The quick brown fox jumped over the lazy dog."; - String str = data; + String str = data; str.replace("fox", "vulpes vulpes"); REQUIRE(str == "The quick brown vulpes vulpes jumped over the lazy dog."); str.replace("vulpes", "lis lis"); diff --git a/tests/host/sys/pgmspace.h b/tests/host/sys/pgmspace.h index ac60cb15be..2a1536f5f3 100644 --- a/tests/host/sys/pgmspace.h +++ b/tests/host/sys/pgmspace.h @@ -1,6 +1,7 @@ /* PGMSPACE.H - Accessor utilities/types for accessing PROGMEM data */ -#ifndef _PGMSPACE_H_ +#pragma once + #define _PGMSPACE_H_ // These are no-ops in anything but the ESP8266, where they are defined in @@ -50,38 +51,3 @@ #define pgm_read_dword_far(addr) pgm_read_dword(addr) #define pgm_read_float_far(addr) pgm_read_float(addr) #define pgm_read_ptr_far(addr) pgm_read_ptr(addr) - -// Wrapper inlines for _P functions -#include -#include -inline const char* strstr_P(const char* haystack, const char* needle) -{ - return strstr(haystack, needle); -} -inline char* strcpy_P(char* dest, const char* src) -{ - return strcpy(dest, src); -} -inline size_t strlen_P(const char* s) -{ - return strlen(s); -} -inline int vsnprintf_P(char* str, size_t size, const char* format, va_list ap) -{ - return vsnprintf(str, size, format, ap); -} - -#define memcpy_P memcpy -#define memmove_P memmove -#define strncpy_P strncpy -#define strcmp_P strcmp -#define strcasecmp_P strcasecmp -#define memccpy_P memccpy -#define snprintf_P snprintf -#define sprintf_P sprintf -#define strncmp_P strncmp -#define strncasecmp_P strncasecmp -#define strcat_P strcat -#define memcmp_P memcmp - -#endif diff --git a/tests/host/sys/string.h b/tests/host/sys/string.h new file mode 100644 index 0000000000..dcd11b90e7 --- /dev/null +++ b/tests/host/sys/string.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#define memccpy_P memccpy +#define memcmp_P memcmp +#define memcpy_P memcpy +#define memmove_P memmove +#define snprintf_P snprintf +#define sprintf_P sprintf +#define strcasecmp_P strcasecmp +#define strcat_P strcat +#define strcmp_P strcmp +#define strcpy_P strcpy +#define strlen_P strlen +#define strncasecmp_P strncasecmp +#define strncmp_P strncmp +#define strncpy_P strncpy +#define strstr_P strstr From d1adaefacf12ba2d692b52964542b6711be30d4f Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Mon, 27 Oct 2025 22:03:51 +0300 Subject: [PATCH 13/19] allow to use gdb as well as pass args to catch --- tests/host/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/host/Makefile b/tests/host/Makefile index 97a4c671fb..5794393771 100644 --- a/tests/host/Makefile +++ b/tests/host/Makefile @@ -194,6 +194,7 @@ INC_PATHS += \ ../../tools/sdk/lwip2/include \ ) +TEST_WRAPPER ?= TEST_ARGS ?= TEST_CPP_FILES := \ @@ -258,7 +259,7 @@ CI: # run CI doCI: build-info $(OUTPUT_BINARY) valgrind test gcov test: $(OUTPUT_BINARY) # run host test for CI - $(OUTPUT_BINARY) $(TEST_ARGS) + $(TEST_WRAPPER) $(OUTPUT_BINARY) $(TEST_ARGS) .PHONY: clean clean: clean-lcov clean-objects From 3da6ae7bdd8ff736801c74c191539f334fa3cedf Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Mon, 27 Oct 2025 22:58:21 +0300 Subject: [PATCH 14/19] missing reference tests for operator lt gt etc --- tests/host/core/test_string.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/host/core/test_string.cpp b/tests/host/core/test_string.cpp index c4f767b59a..d542e3b402 100644 --- a/tests/host/core/test_string.cpp +++ b/tests/host/core/test_string.cpp @@ -198,8 +198,12 @@ TEST_CASE("String concantenation", "[core][String]") TEST_CASE("String comparison", "[core][String]") { - String alpha("I like fish!"); - REQUIRE(alpha < "I like tacos!"); // compareTo() + REQUIRE(String("a") < String("b")); // compareTo() reference comparisons + REQUIRE(String("1") < String("2")); + REQUIRE(String("999") > String("1000")); + String alpha("I like fish!"); // compareTo() + REQUIRE(alpha < "I like tacos!"); + REQUIRE(alpha > "I like bacon!"); REQUIRE(alpha > "I like cod!"); REQUIRE(alpha >= "I like beef!"); REQUIRE(alpha <= "I like soup!"); From 6a223691325af4f524a0d72fc8e78cedfaff9dea Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Mon, 27 Oct 2025 22:58:55 +0300 Subject: [PATCH 15/19] fixup string.h missing memmem & pgm reading float / double --- tests/host/sys/pgmspace.h | 4 +++- tests/host/sys/string.h | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/host/sys/pgmspace.h b/tests/host/sys/pgmspace.h index 2a1536f5f3..f5217a8e93 100644 --- a/tests/host/sys/pgmspace.h +++ b/tests/host/sys/pgmspace.h @@ -32,12 +32,14 @@ #define pgm_read_word(addr) (*reinterpret_cast(addr)) #define pgm_read_dword(addr) (*reinterpret_cast(addr)) #define pgm_read_float(addr) (*reinterpret_cast(addr)) +#define pgm_read_double(addr) (*reinterpret_cast(addr)) #define pgm_read_ptr(addr) (*reinterpret_cast(addr)) #else #define pgm_read_byte(addr) (*(const uint8_t*)(addr)) #define pgm_read_word(addr) (*(const uint16_t*)(addr)) #define pgm_read_dword(addr) (*(const uint32_t*)(addr)) -#define pgm_read_float(addr) (*(const float)(addr)) +#define pgm_read_float(addr) (*(const float*)(addr)) +#define pgm_read_double(addr) (*(const double*)(addr)) #define pgm_read_ptr(addr) (*(const void* const*)(addr)) #endif diff --git a/tests/host/sys/string.h b/tests/host/sys/string.h index dcd11b90e7..286e7ca28f 100644 --- a/tests/host/sys/string.h +++ b/tests/host/sys/string.h @@ -1,11 +1,9 @@ #pragma once -#include -#include - #define memccpy_P memccpy #define memcmp_P memcmp #define memcpy_P memcpy +#define memmem_P memmem #define memmove_P memmove #define snprintf_P snprintf #define sprintf_P sprintf From 48f9fef63bc6e2f0bce9c1ab06680f09c30267e9 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Mon, 27 Oct 2025 22:59:51 +0300 Subject: [PATCH 16/19] prefer mem* funcs instead of str* wrappers expect length, thus no need to look for '\0' only missing piece is case-insensitive memcmp (memicmp?) since implementation requires it, manually do the tolower() loop update replace() w/o '\0' assumptions, always use mem ranges drop strstr usage and reuse loop blocks also returns actual len diff after comparison is otherwise equal --- cores/esp8266/WString.cpp | 267 +++++++++++++++++++------------- cores/esp8266/WString.h | 31 ++-- tests/host/core/test_string.cpp | 22 ++- 3 files changed, 192 insertions(+), 128 deletions(-) diff --git a/cores/esp8266/WString.cpp b/cores/esp8266/WString.cpp index 5bf5d6d0d8..5bb0d1a408 100644 --- a/cores/esp8266/WString.cpp +++ b/cores/esp8266/WString.cpp @@ -32,24 +32,6 @@ /*********************************************/ struct __StringImpl { - // XXX String methods accepting `internal_strstr_t` should be able to properly select overloads - // ref. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51785 - - using internal_strstr_t = String::internal_strstr_t; - - using const_char_strstr_t = const char *(*)(const char *, const char *); - using char_strstr_t = char *(*)(char *, const char *); - - inline static internal_strstr_t __attribute__((always_inline)) - overloaded_strstr(const_char_strstr_t func) { - return reinterpret_cast(func); - } - - inline static internal_strstr_t __attribute__((always_inline)) - overloaded_strstr(internal_strstr_t func) { - return func; - } - // XCHAL_INSTROM0_VADDR (0x40200000) for flash contents // XCHAL_INSTRAM0_VADDR (0x40000000) for instram, aka 1 << 30 static inline bool __attribute__((always_inline, const)) @@ -58,35 +40,79 @@ struct __StringImpl { return ((uintptr_t)p & (uintptr_t)((1 << 31) | (1 << 30))) > 0; } + // prefer string.h funcs w/ length argument + // avoid string.h cstring funcs, ref. #5949 + // jump to specific func depending on the src address - // (similar logic is done in libc, e.g. memmove_P) + // note that similar logic happens in libc too, + // e.g. memmove_P dealing with iram addresses - inline static internal_strstr_t __attribute__((always_inline)) - select_strstr(const char *str) { + inline static String::internal_memmem_t __attribute__((always_inline)) + select_memmem(const char *str) { return __pgm_expected(str) - ? overloaded_strstr(strstr_P) - : overloaded_strstr(strstr); + ? memmem_P + : memmem; } - using internal_strncmp_t = String::internal_strncmp_t; - - inline static internal_strncmp_t __attribute__((always_inline)) - select_strncmp(const char *str) { + inline static String::internal_memcmp_t __attribute__((always_inline)) + select_memcmp(const char *str) { return __pgm_expected(str) - ? strncmp_P - : strncmp; + ? memcmp_P + : memcmp; } - using internal_strncasecmp_t = String::internal_strncasecmp_t; - - inline static internal_strncasecmp_t __attribute__((always_inline)) - select_strncasecmp(const char *str) { + inline static String::internal_memcasecmp_t __attribute__((always_inline)) + select_memcasecmp(const char *str) { return __pgm_expected(str) - ? strncasecmp_P - : strncasecmp; + ? memcasecmp_P + : memcasecmp; } + + static int memcasecmp_P(const void *p1, const void *p2, size_t size); + static int memcasecmp(const void *p1, const void *p2, size_t size); }; +static inline int tolower_inline(const unsigned char *p) { + return tolower(*p); +} + +static inline int tolower_P(const unsigned char *p) { + return tolower(pgm_read_byte(p)); +} + +// missing from libc +// c/p strncasecmp, without '\0' check +template +static inline int +memcasecmp_impl(T&&, const void *, const void *, size_t) +__attribute__((always_inline)); + +template +static inline int memcasecmp_impl(T&& impl, const void *p1, const void *p2, size_t size) { + int diff = 0; + + const auto *c1 = static_cast(p1); + const auto *c2 = static_cast(p2); + + for (; size != 0; --size) { + const int i1 = impl(c1++); + const int i2 = impl(c2++); + if ((diff = i1 - i2) != 0) { + break; + } + } + + return diff; +} + +int __StringImpl::memcasecmp_P(const void *p1, const void *p2, size_t size) { + return memcasecmp_impl(tolower_inline, p1, p2, size); +} + +int __StringImpl::memcasecmp(const void *p1, const void *p2, size_t size) { + return memcasecmp_impl(tolower_P, p1, p2, size); +} + /*********************************************/ /* OOM Debugging */ /*********************************************/ @@ -548,35 +574,29 @@ String operator +(const char *lhs, const String &rhs) { /* Comparison */ /*********************************************/ -int String::compareToImpl(internal_strncmp_t impl, const char *str, unsigned int length) const { +int String::compareToImpl(internal_memcmp_t impl, const char *str, unsigned int length) const { const auto min_len = std::min(len(), length); int res = impl(buffer(), str, min_len); - if (res) - return res; - - if (len() < length) - return -1; - if (len() > length) - return 1; - - return 0; + if (!res) + res = static_cast(len()) - static_cast(length); + return res; } int String::compareTo(const String &s) const { - return compareToImpl(strncmp, s.buffer(), s.len()); + return compareToImpl(memcmp, s.buffer(), s.len()); } int String::compareTo(const char *cstr) const { if (!cstr) return 1; - return compareToImpl(__StringImpl::select_strncmp(cstr), cstr, strlen_P(cstr)); + return compareToImpl(__StringImpl::select_memcmp(cstr), cstr, strlen_P(cstr)); } int String::compareTo(const char *str, unsigned int length) const { - return compareToImpl(__StringImpl::select_strncmp(str), str, length); + return compareToImpl(__StringImpl::select_memcmp(str), str, length); } -bool String::equalsImpl(internal_strncmp_t impl, const char *str, unsigned int length) const { +bool String::equalsImpl(internal_memcmp_t impl, const char *str, unsigned int length) const { const auto same_length = len() == length; if (same_length && length == 0) return true; @@ -586,15 +606,15 @@ bool String::equalsImpl(internal_strncmp_t impl, const char *str, unsigned int l } bool String::equals(const String &s) const { - return equalsImpl(strncmp, s.buffer(), s.len()); + return equalsImpl(memcmp, s.buffer(), s.len()); } bool String::equals(const char *cstr) const { - return equalsImpl(__StringImpl::select_strncmp(cstr), cstr, cstr ? strlen_P(cstr) : 0); + return equalsImpl(__StringImpl::select_memcmp(cstr), cstr, cstr ? strlen_P(cstr) : 0); } bool String::equals(const char *str, unsigned int length) const { - return equalsImpl(__StringImpl::select_strncmp(str), str, length); + return equalsImpl(__StringImpl::select_memcmp(str), str, length); } bool String::operator<(const String &rhs) const { @@ -625,7 +645,7 @@ bool String::operator>=(const char *rhs) const { return compareTo(rhs) >= 0; } -bool String::equalsIgnoreCaseImpl(internal_strncasecmp_t impl, const char *str, unsigned int length) const { +bool String::equalsIgnoreCaseImpl(internal_memcasecmp_t impl, const char *str, unsigned int length) const { if (len() != length) return false; if (!length) @@ -636,15 +656,20 @@ bool String::equalsIgnoreCaseImpl(internal_strncasecmp_t impl, const char *str, bool String::equalsIgnoreCase(const String &s) const { if (this == &s) return true; - return equalsIgnoreCaseImpl(strncasecmp, s.buffer(), s.len()); + return equalsIgnoreCaseImpl(__StringImpl::memcasecmp, s.buffer(), s.len()); } bool String::equalsIgnoreCase(const char *cstr) const { - return equalsIgnoreCaseImpl(__StringImpl::select_strncasecmp(cstr), cstr, cstr ? strlen_P(cstr) : 0); + size_t length; + if (cstr) + length = strlen_P(cstr); + else + length = 0; + return equalsIgnoreCase(cstr, length); } bool String::equalsIgnoreCase(const char *str, unsigned int length) const { - return equalsIgnoreCaseImpl(__StringImpl::select_strncasecmp(str), str, length); + return equalsIgnoreCaseImpl(__StringImpl::select_memcasecmp(str), str, length); } unsigned char String::equalsConstantTime(const char *str, unsigned int length) const { @@ -678,7 +703,7 @@ unsigned char String::equalsConstantTime(const String& s) const { return equalsConstantTime(s.buffer(), s.len()); } -bool String::startsWithImpl(internal_strncmp_t impl, const char *str, unsigned int length, unsigned int offset) const { +bool String::startsWithImpl(internal_memcmp_t impl, const char *str, unsigned int length, unsigned int offset) const { if (len() < length) return false; if (offset > (unsigned)(len() - length)) @@ -687,7 +712,7 @@ bool String::startsWithImpl(internal_strncmp_t impl, const char *str, unsigned i } bool String::startsWith(const char *str, unsigned int length, unsigned int offset) const { - return startsWithImpl(__StringImpl::select_strncmp(str), str, length, offset); + return startsWithImpl(__StringImpl::select_memcmp(str), str, length, offset); } bool String::startsWith(const String &prefix) const { @@ -701,33 +726,33 @@ bool String::startsWith(const char *prefix) const { } bool String::startsWith(const String &prefix, unsigned int offset) const { - return startsWithImpl(strncmp, prefix.buffer(), prefix.len(), offset); + return startsWithImpl(memcmp, prefix.buffer(), prefix.len(), offset); } bool String::startsWith(const char *prefix, unsigned int offset) const { if (!prefix) return false; - return startsWithImpl(__StringImpl::select_strncmp(prefix), prefix, strlen_P(prefix), offset); + return startsWithImpl(__StringImpl::select_memcmp(prefix), prefix, strlen_P(prefix), offset); } -bool String::endsWithImpl(internal_strncmp_t impl, const char *str, unsigned int length) const { +bool String::endsWithImpl(internal_memcmp_t impl, const char *str, unsigned int length) const { if (len() < length) return false; return impl(&buffer()[len() - length], str, length) == 0; } bool String::endsWith(const char *suffix, unsigned int length) const { - return endsWithImpl(__StringImpl::select_strncmp(suffix), suffix, length); + return endsWithImpl(__StringImpl::select_memcmp(suffix), suffix, length); } bool String::endsWith(const String &suffix) const { - return endsWithImpl(strncmp, suffix.buffer(), suffix.len()); + return endsWithImpl(memcmp, suffix.buffer(), suffix.len()); } bool String::endsWith(const char *suffix) const { if (!suffix) return false; - return endsWithImpl(__StringImpl::select_strncmp(suffix), suffix, strlen_P(suffix)); + return endsWith(suffix, strlen_P(suffix)); } /*********************************************/ @@ -764,7 +789,7 @@ void String::toCharArray(char *buf, unsigned int bufsize, unsigned int index) co unsigned int n = bufsize - 1; if (n > len() - index) n = len() - index; - strncpy(buf, buffer() + index, n); + memcpy(buf, buffer() + index, n); buf[n] = 0; } @@ -775,31 +800,31 @@ void String::toCharArray(char *buf, unsigned int bufsize, unsigned int index) co int String::indexOf(char ch, unsigned int fromIndex) const { if (fromIndex >= len()) return -1; - const char *temp = strchr(buffer() + fromIndex, ch); - if (temp == NULL) + const auto *temp = memchr(buffer() + fromIndex, ch, len() - fromIndex); + if (temp == nullptr) return -1; - return temp - buffer(); + return static_cast(temp) - buffer(); } -int String::indexOfImpl(internal_strstr_t impl, const char *str, unsigned int length, unsigned int fromIndex) const { +int String::indexOfImpl(internal_memmem_t impl, const char *str, unsigned int length, unsigned int fromIndex) const { if (fromIndex >= len()) return -1; if (length > len()) return -1; - const char *found = impl(buffer() + fromIndex, str); + const void *found = impl(buffer() + fromIndex, length - fromIndex, str, length); if (found == NULL) return -1; - return found - buffer(); + return static_cast(found) - buffer(); } int String::indexOf(const char *cstr, unsigned int fromIndex) const { if (!cstr) return -1; - return indexOfImpl(__StringImpl::select_strstr(cstr), cstr, strlen_P(cstr), fromIndex); + return indexOfImpl(memmem_P, cstr, strlen_P(cstr), fromIndex); } int String::indexOf(const String &str, unsigned int fromIndex) const { - return indexOfImpl(__StringImpl::overloaded_strstr(strstr), str.buffer(), str.len(), fromIndex); + return indexOfImpl(memmem, str.buffer(), str.len(), fromIndex); } int String::lastIndexOf(char ch) const { @@ -814,28 +839,32 @@ int String::lastIndexOf(char ch, unsigned int fromIndex) const { return index; } -int String::lastIndexOfImpl(internal_strstr_t impl, const char *str, unsigned int length, unsigned int fromIndex) const { - if (!len() || !length || length > len()) +int String::lastIndexOfImpl(internal_memmem_t impl, const char *str, unsigned int length, unsigned int fromIndex) const { + const auto this_len = len(); + if (!this_len || !length || length > this_len) return -1; - if (fromIndex >= len()) - fromIndex = len() - 1; + if (fromIndex >= this_len) + fromIndex = this_len - 1; int found = -1; - for (const char *p = buffer(); p <= buffer() + fromIndex; p++) { - p = impl(p, str); + const char *buf = buffer(); + unsigned int left = len(); + for (const char *p = buf; p <= buf + fromIndex; p++) { + p = static_cast(impl(p, left, str, length)); if (!p) break; - if ((unsigned int)(p - buffer()) <= fromIndex) - found = p - buffer(); + left = static_cast(p - buf); + if (left <= fromIndex) + found = p - buf; } return found; } int String::lastIndexOf(const char *str, unsigned int length, unsigned int fromIndex) const { - return lastIndexOfImpl(__StringImpl::select_strstr(str), str, length, fromIndex); + return lastIndexOfImpl(__StringImpl::select_memmem(str), str, length, fromIndex); } int String::lastIndexOf(const String &str) const { - return lastIndexOfImpl(__StringImpl::overloaded_strstr(strstr), str.buffer(), str.len(), len() - str.len()); + return lastIndexOfImpl(memmem, str.buffer(), str.len(), len() - str.len()); } int String::lastIndexOf(const String &str, unsigned int fromIndex) const { @@ -880,43 +909,55 @@ void String::replace(char find, char replace) { } } -void String::replaceImpl(internal_strstr_t impl, const char *find, unsigned int find_len, const char *replace, unsigned int replace_len) { - if (len() == 0 || find_len == 0) +void String::replaceImpl(internal_memmem_t impl, const char *find, unsigned int find_len, const char *replace, unsigned int replace_len) { + const char *readFrom = buffer(); + const char *const readEnd = readFrom + len(); + if ((readFrom == readEnd) || find_len == 0) return; + int diff = replace_len - find_len; - const char *readFrom = buffer(); const char *foundAt; + + auto next_foundAt = [&]() { + foundAt = static_cast(impl(readFrom, readEnd - readFrom, find, find_len)); + return foundAt != nullptr; + }; + + auto next_readFrom = [&]() { + readFrom = foundAt + find_len; + }; + if (diff == 0) { - while ((foundAt = const_cast(impl(readFrom, find))) != NULL) { + while (next_foundAt()) { memmove_P(const_cast(foundAt), replace, replace_len); - readFrom = foundAt + replace_len; + next_readFrom(); } } else if (diff < 0) { - unsigned int remainingLen = len() + 1; char *writeTo = wbuffer(); - while ((foundAt = const_cast(impl(readFrom, find))) != NULL) { + while (next_foundAt()) { unsigned int n = foundAt - readFrom; memmove_P(writeTo, readFrom, n); writeTo += n; + if (replace_len) memmove_P(writeTo, replace, replace_len); writeTo += replace_len; - readFrom = foundAt + find_len; + setLen(len() + diff); - unsigned int currentLen = len(); - remainingLen = currentLen - n - find_len + 1; // avoid strlen(), ref. #5949 - setLen(currentLen + diff); + next_readFrom(); } - memmove_P(writeTo, readFrom, remainingLen); + memmove_P(writeTo, readFrom, readEnd - readFrom); + *(writeTo + (readEnd - readFrom)) = 0; } else { - unsigned int size = len(); // compute size needed for result - while ((foundAt = const_cast(impl(readFrom, find))) != NULL) { - readFrom = foundAt + find_len; + unsigned int tmp = readEnd - readFrom; + unsigned int size = tmp; // precompute size needed for result + while (next_foundAt()) { + next_readFrom(); size += diff; } - if (size == len()) + if (size == tmp) return; if (size > capacity() && !changeBuffer(size)) return; @@ -934,27 +975,37 @@ void String::replaceImpl(internal_strstr_t impl, const char *find, unsigned int } void String::replace(const char *find, unsigned int find_len, const char *replace, unsigned int replace_len) { - this->replaceImpl(__StringImpl::select_strstr(find), find, find_len, replace, replace_len); + this->replaceImpl(__StringImpl::select_memmem(find), find, find_len, replace, replace_len); } + void String::replace(const String &find, const String &replace) { - this->replaceImpl(__StringImpl::overloaded_strstr(strstr), + this->replaceImpl(memmem, find.buffer(), find.len(), replace.buffer(), replace.len()); } + void String::replace(const String& find, const char *replace) { - this->replaceImpl(__StringImpl::overloaded_strstr(strstr), + if (!replace) + return; + this->replaceImpl(memmem, find.buffer(), find.len(), - replace, replace ? strlen_P(replace) : 0); + replace, strlen_P(replace)); } + void String::replace(const char *find, const String &replace) { - this->replaceImpl(__StringImpl::select_strstr(find), - find, find ? strlen_P(find) : 0, + if (!find) + return; + this->replaceImpl(__StringImpl::select_memmem(find), + find, strlen_P(find), replace.buffer(), replace.len()); } + void String::replace(const char *find, const char *replace) { - this->replaceImpl(__StringImpl::select_strstr(find), - find, find ? strlen_P(find) : 0, - replace, replace ? strlen_P(replace) : 0); + if (!find || !replace) + return; + this->replaceImpl(__StringImpl::select_memmem(find), + find, strlen_P(find), + replace, strlen_P(replace)); } void String::remove(unsigned int index, unsigned int count) { diff --git a/cores/esp8266/WString.h b/cores/esp8266/WString.h index e41920f3a5..09ec2e404e 100644 --- a/cores/esp8266/WString.h +++ b/cores/esp8266/WString.h @@ -471,20 +471,23 @@ class String { // rvalue helper void move(String &rhs) noexcept; - // attempt to optimize internal implementations for either RAM of Flash - using internal_strncasecmp_t = int (*)(const char *, const char *, size_t); - using internal_strncmp_t = int (*)(const char *, const char *, size_t); - using internal_strstr_t = char *(*)(const char *, const char *); - - int compareToImpl(internal_strncmp_t, const char *str, unsigned int length) const; - bool equalsImpl(internal_strncmp_t, const char *str, unsigned int length) const; - bool equalsIgnoreCaseImpl(internal_strncasecmp_t, const char *str, unsigned int length) const; - bool startsWithImpl(internal_strncmp_t, const char *str, unsigned int length, unsigned int offset) const; - bool endsWithImpl(internal_strncmp_t, const char *str, unsigned int length) const; - - int indexOfImpl(internal_strstr_t, const char *str, unsigned int length, unsigned int fromIndex) const; - int lastIndexOfImpl(internal_strstr_t, const char *str, unsigned int length, unsigned int fromIndex) const; - void replaceImpl(internal_strstr_t impl, const char *find, unsigned int find_len, const char *replace, unsigned int replace_len); + // internal implementations rely on func wrappers + using internal_memcmp_t = int (*)(const void *, const void *, size_t); + + int compareToImpl(internal_memcmp_t, const char *str, unsigned int length) const; + bool equalsImpl(internal_memcmp_t, const char *str, unsigned int length) const; + bool startsWithImpl(internal_memcmp_t, const char *str, unsigned int length, unsigned int offset) const; + bool endsWithImpl(internal_memcmp_t, const char *str, unsigned int length) const; + + using internal_memcasecmp_t = int (*)(const void *, const void *, size_t); + + bool equalsIgnoreCaseImpl(internal_memcasecmp_t, const char *str, unsigned int length) const; + + using internal_memmem_t = void *(*)(const void *, size_t, const void *, size_t); + + int indexOfImpl(internal_memmem_t, const char *str, unsigned int length, unsigned int fromIndex) const; + int lastIndexOfImpl(internal_memmem_t, const char *str, unsigned int length, unsigned int fromIndex) const; + void replaceImpl(internal_memmem_t impl, const char *find, unsigned int find_len, const char *replace, unsigned int replace_len); friend struct __StringImpl; }; diff --git a/tests/host/core/test_string.cpp b/tests/host/core/test_string.cpp index d542e3b402..fef4825fec 100644 --- a/tests/host/core/test_string.cpp +++ b/tests/host/core/test_string.cpp @@ -54,13 +54,23 @@ TEST_CASE("String::replace", "[core][String]") REQUIRE(str == "The quick brown lis lis lis lis jumped over the canis piger"); str.replace("brown lis lis", "lis"); REQUIRE(str == "The quick lis lis lis jumped over the canis piger"); - str.replace("lis lis", "fox"); - REQUIRE(str == "The quick fox lis jumped over the canis piger"); - str.replace("fox lis jumped", "brown fox"); - REQUIRE(str == "The quick brown fox over the canis piger"); + str.replace("lis", "fox"); + REQUIRE(str == "The quick fox fox fox jumped over the canis piger"); + str.replace("fox fox", "kot"); + REQUIRE(str == "The quick kot fox jumped over the canis piger"); + str.replace("fox jumped", "red felis"); + REQUIRE(str == "The quick kot red felis over the canis piger"); str.replace(" over ", " jumped over "); + REQUIRE(str == "The quick kot red felis jumped over the canis piger"); str.replace("canis piger", "lazy dog"); + REQUIRE(str == "The quick kot red felis jumped over the lazy dog"); str.replace("dog", "dog."); + REQUIRE(str == "The quick kot red felis jumped over the lazy dog."); + str.replace(" kot", ""); + REQUIRE(str == "The quick red felis jumped over the lazy dog."); + str.replace("red", "brown"); + REQUIRE(str == "The quick brown felis jumped over the lazy dog."); + str.replace("felis", "fox"); REQUIRE(str == data); } @@ -198,10 +208,10 @@ TEST_CASE("String concantenation", "[core][String]") TEST_CASE("String comparison", "[core][String]") { - REQUIRE(String("a") < String("b")); // compareTo() reference comparisons + REQUIRE(String("a") < String("b")); // compareTo() reference comparisons REQUIRE(String("1") < String("2")); REQUIRE(String("999") > String("1000")); - String alpha("I like fish!"); // compareTo() + String alpha("I like fish!"); // compareTo() REQUIRE(alpha < "I like tacos!"); REQUIRE(alpha > "I like bacon!"); REQUIRE(alpha > "I like cod!"); From 50956deeb42a787520ee6a0c77e3f41ac3a3e13e Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Tue, 28 Oct 2025 16:36:48 +0300 Subject: [PATCH 17/19] missing file --- tests/host/sys/stdio.h | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 tests/host/sys/stdio.h diff --git a/tests/host/sys/stdio.h b/tests/host/sys/stdio.h new file mode 100644 index 0000000000..250cf6814d --- /dev/null +++ b/tests/host/sys/stdio.h @@ -0,0 +1,3 @@ +#pragma once + +#define vsnprintf_P vsnprintf From 70222c25035acc5c28a3b742356f24797b45211a Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Tue, 28 Oct 2025 17:58:24 +0300 Subject: [PATCH 18/19] fixup replace() for diff>0 incorrectly using lastIndexOf, when should be using deduced search func since there is no reverse indexOf nor lastIndexOf allows end boundary, adjust buffer end pointer manually by swapping it w/ the found one plus, reinit read ptr after possibly changing buffer() location --- cores/esp8266/WString.cpp | 50 +++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/cores/esp8266/WString.cpp b/cores/esp8266/WString.cpp index 5bb0d1a408..23099fcd76 100644 --- a/cores/esp8266/WString.cpp +++ b/cores/esp8266/WString.cpp @@ -911,7 +911,7 @@ void String::replace(char find, char replace) { void String::replaceImpl(internal_memmem_t impl, const char *find, unsigned int find_len, const char *replace, unsigned int replace_len) { const char *readFrom = buffer(); - const char *const readEnd = readFrom + len(); + const char *readEnd = readFrom + len(); if ((readFrom == readEnd) || find_len == 0) return; @@ -961,16 +961,46 @@ void String::replaceImpl(internal_memmem_t impl, const char *find, unsigned int return; if (size > capacity() && !changeBuffer(size)) return; - int index = len() - 1; - while (index >= 0 && (index = lastIndexOf(find, find_len, index)) >= 0) { - readFrom = wbuffer() + index + find_len; - memmove_P(const_cast(readFrom) + diff, readFrom, len() - (readFrom - buffer())); - int newLen = len() + diff; - memmove_P(wbuffer() + index, replace, replace_len); - setLen(newLen); - wbuffer()[newLen] = 0; - index--; + + readFrom = buffer(); + readEnd = readFrom + len(); + + const char *foundFrom = readFrom; + const char *foundEnd = readEnd; + + auto last_found = [impl](const char *hs, size_t hs_len, const char *needle, size_t needle_len) { + const char *last = nullptr; + + const char *current = hs; + const char *end = hs + hs_len; + for (;;) { + current = static_cast(impl(current, end - current, needle, needle_len)); + if (!current) + break; + + last = current; + current += needle_len; + } + + return last; + }; + + for (;;) { + foundAt = last_found(foundFrom, foundEnd - foundFrom, find, find_len); + if (!foundAt) + break; + + readFrom = foundAt + find_len; + memmove_P(const_cast(readFrom) + diff, readFrom, readEnd - readFrom); + memmove_P(const_cast(foundAt), replace, replace_len); + + foundEnd = foundAt; + readEnd += diff; } + + auto newLen = readEnd - buffer(); + setLen(newLen); + wbuffer()[newLen] = 0; } } From 4dd4ae5813c7d1b96a78398240a152d0db18338a Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Tue, 28 Oct 2025 17:59:46 +0300 Subject: [PATCH 19/19] fixup lastIndexOfImpl checking ptr instead of offset similar to replace() use, we already know there is nothing past previously found needle ptr --- cores/esp8266/WString.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/cores/esp8266/WString.cpp b/cores/esp8266/WString.cpp index 23099fcd76..a12942a6d9 100644 --- a/cores/esp8266/WString.cpp +++ b/cores/esp8266/WString.cpp @@ -840,22 +840,23 @@ int String::lastIndexOf(char ch, unsigned int fromIndex) const { } int String::lastIndexOfImpl(internal_memmem_t impl, const char *str, unsigned int length, unsigned int fromIndex) const { - const auto this_len = len(); + const char *buf = buffer(); + const char *const bufEnd = buf + len(); + + const auto this_len = bufEnd - buf; if (!this_len || !length || length > this_len) return -1; if (fromIndex >= this_len) fromIndex = this_len - 1; + int found = -1; - const char *buf = buffer(); - unsigned int left = len(); - for (const char *p = buf; p <= buf + fromIndex; p++) { - p = static_cast(impl(p, left, str, length)); + for (const char *p = buf + fromIndex; p && p < bufEnd; p += length) { + p = static_cast(impl(p, bufEnd - p, str, length)); if (!p) break; - left = static_cast(p - buf); - if (left <= fromIndex) - found = p - buf; + found = p - buf; } + return found; }