diff --git a/cores/esp8266/WString.cpp b/cores/esp8266/WString.cpp index 1e608c3c92..a12942a6d9 100644 --- a/cores/esp8266/WString.cpp +++ b/cores/esp8266/WString.cpp @@ -27,6 +27,96 @@ #include +/*********************************************/ +/* Actual flash string helpers */ +/*********************************************/ + +struct __StringImpl { + // 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; + } + + // prefer string.h funcs w/ length argument + // avoid string.h cstring funcs, ref. #5949 + + // jump to specific func depending on the src address + // note that similar logic happens in libc too, + // e.g. memmove_P dealing with iram addresses + + inline static String::internal_memmem_t __attribute__((always_inline)) + select_memmem(const char *str) { + return __pgm_expected(str) + ? memmem_P + : memmem; + } + + inline static String::internal_memcmp_t __attribute__((always_inline)) + select_memcmp(const char *str) { + return __pgm_expected(str) + ? memcmp_P + : memcmp; + } + + inline static String::internal_memcasecmp_t __attribute__((always_inline)) + select_memcasecmp(const char *str) { + return __pgm_expected(str) + ? 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 */ +/*********************************************/ + #define OOM_STRING_BORDER_DISPLAY 10 #define OOM_STRING_THRESHOLD_REALLOC_WARN 128 @@ -130,11 +220,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); @@ -181,13 +266,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) @@ -270,24 +355,13 @@ bool String::changeBuffer(unsigned int maxStrLen) { /* Copy and Move */ /*********************************************/ -String &String::copy(const char *cstr, unsigned int length) { - if (!reserve(length)) { - invalidate(); - return *this; - } - setLen(length); - memmove_P(wbuffer(), cstr, length); - wbuffer()[length] = 0; - return *this; -} - -String &String::copy(const __FlashStringHelper *pstr, unsigned int length) { +String &String::copy(const char *str, 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 + memmove_P(wbuffer(), str, length); wbuffer()[length] = 0; return *this; } @@ -316,23 +390,14 @@ 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; } String &String::operator =(char c) { - char buffer[2] { c, '\0' }; - *this = buffer; + copy(&c, 1); return *this; } @@ -343,7 +408,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; @@ -360,24 +425,22 @@ 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) - 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(cstr)); + if (cstr) + return concat(cstr, strlen_P(cstr)); + return false; } bool String::concat(char c) { @@ -420,35 +483,20 @@ 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 */ /*********************************************/ -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; @@ -459,18 +507,15 @@ 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); } String &String::insert(size_t position, const char *other) { - return insert(position, other, strlen(other)); + if (!other) + return *this; + return insert(position, other, strlen_P(other)); } String &String::insert(size_t position, const String &other) { @@ -518,9 +563,10 @@ 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 = lhs ? strlen_P(lhs) : 0; + res.reserve(lhs_len + rhs.length()); + res.concat(lhs, lhs_len); + res.concat(rhs); return res; } @@ -528,84 +574,119 @@ String operator +(const char *lhs, const String &rhs) { /* Comparison */ /*********************************************/ +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) + res = static_cast(len()) - static_cast(length); + return res; +} + int String::compareTo(const String &s) const { - if (!buffer() || !s.buffer()) { - if (s.buffer() && s.len() > 0) - return 0 - *(unsigned char *)s.buffer(); - if (buffer() && len() > 0) - return *(unsigned char *)buffer(); - return 0; - } - return strcmp(buffer(), s.buffer()); + return compareToImpl(memcmp, s.buffer(), s.len()); } -bool String::equals(const String &s2) const { - return (len() == s2.len() && compareTo(s2) == 0); +int String::compareTo(const char *cstr) const { + if (!cstr) + return 1; + return compareToImpl(__StringImpl::select_memcmp(cstr), cstr, strlen_P(cstr)); +} + +int String::compareTo(const char *str, unsigned int length) const { + return compareToImpl(__StringImpl::select_memcmp(str), str, length); +} + +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; + + return same_length + && impl(buffer(), str, length) == 0; +} + +bool String::equals(const String &s) const { + return equalsImpl(memcmp, s.buffer(), s.len()); } bool String::equals(const char *cstr) const { - if (len() == 0) - return (cstr == NULL || *cstr == 0); - if (cstr == NULL) - return buffer()[0] == 0; - return strcmp(buffer(), cstr) == 0; + return equalsImpl(__StringImpl::select_memcmp(cstr), cstr, cstr ? strlen_P(cstr) : 0); } -bool String::equals(const __FlashStringHelper *s) const { - return equals(String(s)); +bool String::equals(const char *str, unsigned int length) const { + return equalsImpl(__StringImpl::select_memcmp(str), str, length); } 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::equalsIgnoreCaseImpl(internal_memcasecmp_t impl, const char *str, unsigned int length) const { + if (len() != length) return false; - if (len() == 0) + if (!length) return true; - const char *p1 = buffer(); - const char *p2 = s2.buffer(); - while (*p1) { - if (tolower(*p1++) != tolower(*p2++)) - return false; - } - return true; + return impl(buffer(), str, length) == 0; +} + +bool String::equalsIgnoreCase(const String &s) const { + if (this == &s) + return true; + return equalsIgnoreCaseImpl(__StringImpl::memcasecmp, s.buffer(), s.len()); } -bool String::equalsIgnoreCase(const __FlashStringHelper *s) const { - return equalsIgnoreCase(String(s)); +bool String::equalsIgnoreCase(const char *cstr) const { + size_t length; + if (cstr) + length = strlen_P(cstr); + else + length = 0; + return equalsIgnoreCase(cstr, length); } -unsigned char String::equalsConstantTime(const String &s2) const { +bool String::equalsIgnoreCase(const char *str, unsigned int length) const { + return equalsIgnoreCaseImpl(__StringImpl::select_memcasecmp(str), str, length); +} + +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() != 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 +699,61 @@ 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::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)) return false; - return startsWith(s2, 0); + return impl(&buffer()[offset], str, length) == 0; } -bool String::startsWith(const char *prefix) const { - return this->startsWith(String(prefix)); +bool String::startsWith(const char *str, unsigned int length, unsigned int offset) const { + return startsWithImpl(__StringImpl::select_memcmp(str), str, length, offset); } -bool String::startsWith(const __FlashStringHelper *prefix) const { - return this->startsWith(String(prefix)); + +bool String::startsWith(const String &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()) +bool String::startsWith(const char *prefix) const { + if (!prefix) return false; - return strncmp(&buffer()[offset], s2.buffer(), s2.len()) == 0; + return startsWith(prefix, 0); } -bool String::startsWith(const __FlashStringHelper *prefix, unsigned int offset) const { - return startsWith(String(prefix), offset); +bool String::startsWith(const String &prefix, unsigned int offset) const { + return startsWithImpl(memcmp, prefix.buffer(), prefix.len(), offset); } -bool String::endsWith(const String &s2) const { - if (len() < s2.len() || !buffer() || !s2.buffer()) +bool String::startsWith(const char *prefix, unsigned int offset) const { + if (!prefix) return false; - return strcmp(&buffer()[len() - s2.len()], s2.buffer()) == 0; + return startsWithImpl(__StringImpl::select_memcmp(prefix), prefix, strlen_P(prefix), offset); } -bool String::endsWith(const char *suffix) const { - return this->endsWith(String(suffix)); +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 __FlashStringHelper *suffix) const { - return this->endsWith(String(suffix)); + +bool String::endsWith(const char *suffix, unsigned int length) const { + return endsWithImpl(__StringImpl::select_memcmp(suffix), suffix, length); } +bool String::endsWith(const String &suffix) const { + return endsWithImpl(memcmp, suffix.buffer(), suffix.len()); +} + +bool String::endsWith(const char *suffix) const { + if (!suffix) + return false; + return endsWith(suffix, strlen_P(suffix)); +} /*********************************************/ /* Character Access */ @@ -666,7 +766,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; } @@ -679,7 +779,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 +789,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); + memcpy(buf, buffer() + index, n); buf[n] = 0; } @@ -700,23 +800,31 @@ void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int ind 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::indexOf(const char *s2, 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; - const char *found = strstr_P(buffer() + fromIndex, s2); + if (length > len()) + return -1; + 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 String &s2, unsigned int fromIndex) const { - return indexOf(s2.c_str(), fromIndex); +int String::indexOf(const char *cstr, unsigned int fromIndex) const { + if (!cstr) + return -1; + return indexOfImpl(memmem_P, cstr, strlen_P(cstr), fromIndex); +} + +int String::indexOf(const String &str, unsigned int fromIndex) const { + return indexOfImpl(memmem, str.buffer(), str.len(), fromIndex); } int String::lastIndexOf(char ch) const { @@ -731,34 +839,50 @@ 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::lastIndexOfImpl(internal_memmem_t impl, const char *str, unsigned int length, unsigned int fromIndex) const { + const char *buf = buffer(); + const char *const bufEnd = buf + len(); -int String::lastIndexOf(const String &s2, unsigned int fromIndex) const { - if (s2.len() == 0 || len() == 0 || s2.len() > len()) + const auto this_len = bufEnd - buf; + 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 = strstr(p, s2.buffer()); + for (const char *p = buf + fromIndex; p && p < bufEnd; p += length) { + p = static_cast(impl(p, bufEnd - p, str, length)); if (!p) break; - if ((unsigned int)(p - buffer()) <= fromIndex) - found = p - buffer(); + found = p - buf; } + return found; } -int String::lastIndexOf(const __FlashStringHelper *str) const { - return lastIndexOf(String(str)); +int String::lastIndexOf(const char *str, unsigned int length, unsigned int fromIndex) const { + return lastIndexOfImpl(__StringImpl::select_memmem(str), str, length, fromIndex); } -int String::lastIndexOf(const __FlashStringHelper *str, unsigned int fromIndex) const { - return lastIndexOf(String(str), fromIndex); +int String::lastIndexOf(const String &str) const { + return lastIndexOfImpl(memmem, str.buffer(), str.len(), len() - str.len()); } +int String::lastIndexOf(const String &str, unsigned int fromIndex) const { + return lastIndexOf(str.buffer(), str.len(), fromIndex); +} + +int String::lastIndexOf(const char *cstr) const { + if (!cstr) + return -1; + return lastIndexOf(cstr, strlen_P(cstr), 0); +} + +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 { if (left > right) { @@ -780,77 +904,140 @@ 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; } } -void String::replace(const String &find, const String &replace) { - 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 *readEnd = readFrom + len(); + if ((readFrom == readEnd) || find_len == 0) return; - int diff = replace.len() - find.len(); - char *readFrom = wbuffer(); - char *foundAt; + + int diff = replace_len - find_len; + 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 = strstr(readFrom, find.buffer())) != NULL) { - memmove_P(foundAt, replace.buffer(), replace.len()); - readFrom = foundAt + replace.len(); + while (next_foundAt()) { + memmove_P(const_cast(foundAt), replace, replace_len); + next_readFrom(); } } else if (diff < 0) { char *writeTo = wbuffer(); - while ((foundAt = strstr(readFrom, find.buffer())) != NULL) { + while (next_foundAt()) { 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(); + + if (replace_len) + memmove_P(writeTo, replace, replace_len); + + writeTo += replace_len; setLen(len() + diff); + + next_readFrom(); } - memmove_P(writeTo, readFrom, strlen(readFrom) + 1); + memmove_P(writeTo, readFrom, readEnd - readFrom); + *(writeTo + (readEnd - readFrom)) = 0; } else { - unsigned int size = len(); // compute size needed for result - while ((foundAt = strstr(readFrom, find.buffer())) != 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; - 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())); - int newLen = len() + diff; - memmove_P(wbuffer() + index, replace.buffer(), 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; } } - -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 char *find, unsigned int find_len, const char *replace, unsigned int replace_len) { + this->replaceImpl(__StringImpl::select_memmem(find), find, find_len, replace, replace_len); } -void String::replace(const char *find, const char *replace) { - this->replace(String(find), String(replace)); + +void String::replace(const String &find, const String &replace) { + this->replaceImpl(memmem, + find.buffer(), find.len(), + replace.buffer(), replace.len()); } -void String::replace(const __FlashStringHelper *find, const char *replace) { - this->replace(String(find), String(replace)); + +void String::replace(const String& find, const char *replace) { + if (!replace) + return; + this->replaceImpl(memmem, + find.buffer(), find.len(), + replace, strlen_P(replace)); } -void String::replace(const __FlashStringHelper *find, const __FlashStringHelper *replace) { - this->replace(String(find), String(replace)); + +void String::replace(const char *find, const String &replace) { + 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) { + 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) { if (index >= len()) { @@ -870,23 +1057,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)) @@ -906,19 +1089,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 1a2aebb298..09ec2e404e 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) { @@ -189,8 +193,10 @@ 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 __FlashStringHelper *str); + bool concat(const char *str, unsigned int length); + bool concat(const __FlashStringHelper *str) { + return concat(reinterpret_cast(str)); + } bool concat(char c); bool concat(unsigned char c); @@ -218,36 +224,87 @@ 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 __FlashStringHelper *s) const; + bool equals(const char *str, unsigned int length) 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 char *str, unsigned int length) const; + bool equalsIgnoreCase(const __FlashStringHelper *s) const { + return equalsIgnoreCase(reinterpret_cast(s)); + } + unsigned char equalsConstantTime(const String &s) const; + unsigned char equalsConstantTime(const char *str, unsigned int length) 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 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 __FlashStringHelper *suffix) const; + bool endsWith(const char *str, unsigned int length) const; + bool endsWith(const __FlashStringHelper *suffix) const { + return endsWith(reinterpret_cast(suffix)); + } // character access char charAt(unsigned int index) const { @@ -256,10 +313,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 +327,23 @@ 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 *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 { + 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 +352,24 @@ 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 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); + 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 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); + } + 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 +428,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)) { @@ -376,17 +453,43 @@ class String { bool changeBuffer(unsigned int maxStrLen); // copy or insert at a specific position - String ©(const char *cstr, unsigned int length); - String ©(const __FlashStringHelper *pstr, unsigned int length); + String ©(const char *str, unsigned int length); + String ©(const __FlashStringHelper *str, unsigned int length) { + return copy(reinterpret_cast(str), length); + } + + 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 __FlashStringHelper *); - 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); + String &insert(size_t position, const __FlashStringHelper *str) { + return insert(position, reinterpret_cast(str)); + } + 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; + + // 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; }; // concatenation (note that it's done using non-method operators to handle both possible type refs) @@ -432,10 +535,7 @@ 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 +// Similarly, allow c-strings and `F(...)` as both lhs and rhs String operator +(const char *lhs, const String &rhs); @@ -444,11 +544,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 reinterpret_cast(lhs) + std::move(rhs); } extern const String emptyString; 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 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 92b37e4279..fef4825fec 100644 --- a/tests/host/core/test_string.cpp +++ b/tests/host/core/test_string.cpp @@ -44,12 +44,34 @@ 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", "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); } TEST_CASE("String(value, base)", "[core][String]") @@ -186,12 +208,19 @@ TEST_CASE("String concantenation", "[core][String]") TEST_CASE("String comparison", "[core][String]") { - String alpha("I like fish!"); + 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!"); 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)); @@ -581,11 +610,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 +629,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) + 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)); } 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; diff --git a/tests/host/sys/pgmspace.h b/tests/host/sys/pgmspace.h index ac60cb15be..f5217a8e93 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 @@ -31,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 @@ -50,38 +53,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/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 diff --git a/tests/host/sys/string.h b/tests/host/sys/string.h new file mode 100644 index 0000000000..286e7ca28f --- /dev/null +++ b/tests/host/sys/string.h @@ -0,0 +1,18 @@ +#pragma once + +#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 +#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