Fix crash when using String::move on empty string (#10938) (#10945)

Fixes: #10938
Keep allocated memory when rhs fits

Use case: Appending to a String with pre-allocated memory (e.g. from `reserve()`)
No need to move 0-termination char in String::move
Simplify calls to String::copy

A lot of the same checks were done before calling `copy()` which should be done in the `copy()` function itself.
String::copy() Should not copy more than given length
Fix potential out of range in String::concat

There is no prerequisite the given array has to be a 0-terminated char array.
So we should only copy the length that has been given.

The `setLen()` function will make sure the internal string is 0-terminated.
So no need to dangerously assume there will be 1 more byte to copy
Allow String::concat(const String &s) with s.buffer() == nullptr

When constructing a String object, the internal buffer is a nullptr.
However concatenating this to another String would return `false` while this is perfectly fine to do.
This commit is contained in:
TD-er 2025-02-13 13:14:05 +01:00 committed by GitHub
parent 6fb55a7f68
commit 5488d5d23f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -226,11 +226,11 @@ bool String::changeBuffer(unsigned int maxStrLen) {
/*********************************************/ /*********************************************/
String &String::copy(const char *cstr, unsigned int length) { String &String::copy(const char *cstr, unsigned int length) {
if (!reserve(length)) { if (cstr == nullptr || !reserve(length)) {
invalidate(); invalidate();
return *this; return *this;
} }
memmove(wbuffer(), cstr, length + 1); memmove(wbuffer(), cstr, length);
setLen(length); setLen(length);
return *this; return *this;
} }
@ -239,17 +239,20 @@ String &String::copy(const char *cstr, unsigned int length) {
void String::move(String &rhs) { void String::move(String &rhs) {
if (buffer()) { if (buffer()) {
if (capacity() >= rhs.len()) { if (capacity() >= rhs.len()) {
memmove(wbuffer(), rhs.buffer(), rhs.length() + 1); // Use case: When 'reserve()' was called and the first
// assignment/append is the return value of a function.
if (rhs.len() && rhs.buffer()) {
memmove(wbuffer(), rhs.buffer(), rhs.length());
}
setLen(rhs.len()); setLen(rhs.len());
rhs.invalidate(); rhs.invalidate();
return; return;
} else { }
if (!isSSO()) { if (!isSSO()) {
free(wbuffer()); free(wbuffer());
setBuffer(nullptr); setBuffer(nullptr);
} }
} }
}
if (rhs.isSSO()) { if (rhs.isSSO()) {
setSSO(true); setSSO(true);
memmove(sso.buff, rhs.sso.buff, sizeof(sso.buff)); memmove(sso.buff, rhs.sso.buff, sizeof(sso.buff));
@ -259,10 +262,7 @@ void String::move(String &rhs) {
} }
setCapacity(rhs.capacity()); setCapacity(rhs.capacity());
setLen(rhs.len()); setLen(rhs.len());
rhs.setSSO(false); rhs.init();
rhs.setCapacity(0);
rhs.setBuffer(nullptr);
rhs.setLen(0);
} }
#endif #endif
@ -270,12 +270,7 @@ String &String::operator=(const String &rhs) {
if (this == &rhs) { if (this == &rhs) {
return *this; return *this;
} }
if (rhs.buffer()) { return copy(rhs.buffer(), rhs.len());
copy(rhs.buffer(), rhs.len());
} else {
invalidate();
}
return *this;
} }
#ifdef __GXX_EXPERIMENTAL_CXX0X__ #ifdef __GXX_EXPERIMENTAL_CXX0X__
@ -295,12 +290,7 @@ String &String::operator=(StringSumHelper &&rval) {
#endif #endif
String &String::operator=(const char *cstr) { String &String::operator=(const char *cstr) {
if (cstr) { return copy(cstr, strlen(cstr));
copy(cstr, strlen(cstr));
} else {
invalidate();
}
return *this;
} }
/*********************************************/ /*********************************************/
@ -311,23 +301,21 @@ bool String::concat(const String &s) {
// Special case if we're concatting ourself (s += s;) since we may end up // 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 // realloc'ing the buffer and moving s.buffer in the method called
if (&s == this) { if (&s == this) {
unsigned int newlen = 2 * len();
if (!s.buffer()) {
return false;
}
if (s.len() == 0) { if (s.len() == 0) {
return true; return true;
} }
if (!s.buffer()) {
return false;
}
unsigned int newlen = 2 * len();
if (!reserve(newlen)) { if (!reserve(newlen)) {
return false; return false;
} }
memmove(wbuffer() + len(), buffer(), len()); memmove(wbuffer() + len(), buffer(), len());
setLen(newlen); setLen(newlen);
wbuffer()[len()] = 0;
return true; return true;
} else {
return concat(s.buffer(), s.len());
} }
return concat(s.buffer(), s.len());
} }
bool String::concat(const char *cstr, unsigned int length) { bool String::concat(const char *cstr, unsigned int length) {
@ -343,10 +331,10 @@ bool String::concat(const char *cstr, unsigned int length) {
} }
if (cstr >= wbuffer() && cstr < wbuffer() + len()) { if (cstr >= wbuffer() && cstr < wbuffer() + len()) {
// compatible with SSO in ram #6155 (case "x += x.c_str()") // compatible with SSO in ram #6155 (case "x += x.c_str()")
memmove(wbuffer() + len(), cstr, length + 1); memmove(wbuffer() + len(), cstr, length);
} else { } else {
// compatible with source in flash #6367 // compatible with source in flash #6367
memcpy_P(wbuffer() + len(), cstr, length + 1); memcpy_P(wbuffer() + len(), cstr, length);
} }
setLen(newlen); setLen(newlen);
return true; return true;