Support GrowsBackwards in QByteArray

Updated main QByteArray operations to support prepend-optimization path

There are still many things to consider especially algorithms that use
QByteArray::data() or do raw memory operations (e.g. memcpy) regardless
of the underlying memory layout, which was somewhat valid before but
will likely break now given free space > 0 at the beginning of the byte
array memory. Looked at existing cases in the scope of QByteArray, they
seem to be OK. Hopefully, CI would find missed violations if any

Task-number: QTBUG-84320
Change-Id: I7990cda165b8e77a397e41df4e145468e7a86be0
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Andrei Golubev 2020-08-10 11:30:50 +02:00
parent 7f3c0d8a05
commit 4b9ec00534
2 changed files with 92 additions and 57 deletions

View File

@ -1129,8 +1129,9 @@ QByteArray &QByteArray::operator=(const char *str)
} else {
const qsizetype len = qsizetype(strlen(str));
const size_t fullLen = size_t(len) + 1;
if (d->needsDetach() || fullLen > d->allocatedCapacity()
|| (len < size() && fullLen < (d->allocatedCapacity() >> 1)))
const auto capacityAtEnd = d->allocatedCapacity() - d.freeSpaceAtBegin();
if (d->needsDetach() || fullLen > capacityAtEnd
|| (len < size() && fullLen < (capacityAtEnd >> 1)))
reallocData(fullLen, d->detachFlags());
memcpy(d.data(), str, fullLen); // include null terminator
d.size = len;
@ -1613,7 +1614,8 @@ void QByteArray::resize(qsizetype size)
if (size < 0)
size = 0;
if (d->needsDetach() || size > capacity())
const auto capacityAtEnd = capacity() - d.freeSpaceAtBegin();
if (d->needsDetach() || size > capacityAtEnd)
reallocData(size_t(size) + 1u, d->detachFlags() | Data::GrowsForward);
d.size = size;
if (d->allocatedCapacity())
@ -1640,7 +1642,12 @@ QByteArray &QByteArray::fill(char ch, qsizetype size)
void QByteArray::reallocData(size_t alloc, Data::ArrayOptions options)
{
if (d->needsDetach()) {
// there's a case of slow reallocate path where we need to memmove the data
// before a call to ::realloc(), meaning that there's an extra "heavy"
// operation. just prefer ::malloc() branch in this case
const bool slowReallocatePath = d.freeSpaceAtBegin() > 0;
if (d->needsDetach() || slowReallocatePath) {
DataPointer dd(Data::allocate(alloc, options), qMin(qsizetype(alloc) - 1, d.size));
if (dd.size > 0)
::memcpy(dd.data(), d.data(), dd.size);
@ -1651,6 +1658,19 @@ void QByteArray::reallocData(size_t alloc, Data::ArrayOptions options)
}
}
void QByteArray::reallocGrowData(size_t alloc, Data::ArrayOptions options)
{
if (d->needsDetach()) {
const auto newSize = qMin(qsizetype(alloc) - 1, d.size);
DataPointer dd(DataPointer::allocateGrow(d, alloc, newSize, options));
dd->copyAppend(d.data(), d.data() + newSize);
dd.data()[dd.size] = 0;
d = dd;
} else {
d->reallocate(alloc, options);
}
}
void QByteArray::expand(qsizetype i)
{
resize(qMax(i + 1, size()));
@ -1731,11 +1751,10 @@ QByteArray &QByteArray::prepend(const char *str)
QByteArray &QByteArray::prepend(const char *str, qsizetype len)
{
if (str) {
if (d->needsDetach() || size() + len > capacity())
reallocData(size_t(size() + len) + 1u, d->detachFlags() | Data::GrowsForward);
memmove(d.data()+len, d.data(), d.size);
memcpy(d.data(), str, len);
d.size += len;
const bool shouldGrow = d->shouldGrowBeforeInsert(d.begin(), len);
if (d->needsDetach() || size() + len > capacity() || shouldGrow)
reallocGrowData(size_t(size() + len) + 1u, d->detachFlags() | Data::GrowsBackwards);
d->insert(d.begin(), str, str + len);
d.data()[d.size] = '\0';
}
return *this;
@ -1757,11 +1776,10 @@ QByteArray &QByteArray::prepend(const char *str, qsizetype len)
QByteArray &QByteArray::prepend(char ch)
{
if (d->needsDetach() || size() + 1 > capacity())
reallocData(size_t(size()) + 2u, d->detachFlags() | Data::GrowsForward);
memmove(d.data()+1, d.data(), d.size);
d.data()[0] = ch;
++d.size;
const bool shouldGrow = d->shouldGrowBeforeInsert(d.begin(), 1);
if (d->needsDetach() || size() + 1 > capacity() || shouldGrow)
reallocGrowData(size_t(size()) + 2u, d->detachFlags() | Data::GrowsBackwards);
d->insert(d.begin(), 1, ch);
d.data()[d.size] = '\0';
return *this;
}
@ -1795,10 +1813,10 @@ QByteArray &QByteArray::append(const QByteArray &ba)
if (size() == 0 && ba.d.isMutable()) {
*this = ba;
} else if (ba.size() != 0) {
if (d->needsDetach() || size() + ba.size() > capacity())
reallocData(size_t(size() + ba.size()) + 1u, d->detachFlags() | Data::GrowsForward);
memcpy(d.data() + d.size, ba.data(), ba.size());
d.size += ba.size();
const bool shouldGrow = d->shouldGrowBeforeInsert(d.end(), ba.size());
if (d->needsDetach() || size() + ba.size() > capacity() || shouldGrow)
reallocGrowData(size_t(size() + ba.size()) + 1u, d->detachFlags() | Data::GrowsForward);
d->copyAppend(ba.data(), ba.data() + ba.size());
d.data()[d.size] = '\0';
}
return *this;
@ -1814,10 +1832,11 @@ QByteArray& QByteArray::append(const char *str)
{
if (str) {
const qsizetype len = qsizetype(strlen(str));
if (d->needsDetach() || size() + len > capacity())
reallocData(size_t(size() + len) + 1u, d->detachFlags() | Data::GrowsForward);
memcpy(d.data() + d.size, str, len + 1); // include null terminator
d.size += len;
const bool shouldGrow = d->shouldGrowBeforeInsert(d.end(), len);
if (d->needsDetach() || size() + len > capacity() || shouldGrow)
reallocGrowData(size_t(size() + len) + 1u, d->detachFlags() | Data::GrowsForward);
d->copyAppend(str, str + len);
d.data()[d.size] = '\0';
}
return *this;
}
@ -1842,10 +1861,10 @@ QByteArray &QByteArray::append(const char *str, qsizetype len)
if (len < 0)
len = qstrlen(str);
if (str && len) {
if (d->needsDetach() || size() + len > capacity())
reallocData(size_t(size() + len) + 1u, d->detachFlags() | Data::GrowsForward);
memcpy(d.data() + d.size, str, len);
d.size += len;
const bool shouldGrow = d->shouldGrowBeforeInsert(d.end(), len);
if (d->needsDetach() || size() + len > capacity() || shouldGrow)
reallocGrowData(size_t(size() + len) + 1u, d->detachFlags() | Data::GrowsForward);
d->copyAppend(str, str + len);
d.data()[d.size] = '\0';
}
return *this;
@ -1870,9 +1889,10 @@ QByteArray &QByteArray::append(const char *str, qsizetype len)
QByteArray& QByteArray::append(char ch)
{
if (d->needsDetach() || size() + 1 > capacity())
reallocData(size_t(size()) + 2u, d->detachFlags() | Data::GrowsForward);
d.data()[d.size++] = ch;
const bool shouldGrow = d->shouldGrowBeforeInsert(d.end(), 1);
if (d->needsDetach() || size() + 1 > capacity() || shouldGrow)
reallocGrowData(size_t(size()) + 2u, d->detachFlags() | Data::GrowsForward);
d->copyAppend(1, ch);
d.data()[d.size] = '\0';
return *this;
}
@ -1885,18 +1905,7 @@ QByteArray& QByteArray::append(char ch)
static inline QByteArray &qbytearray_insert(QByteArray *ba,
qsizetype pos, const char *arr, qsizetype len)
{
if (pos < 0 || len <= 0 || arr == nullptr)
return *ba;
qsizetype oldsize = ba->size();
ba->resize(qMax(pos, oldsize) + len);
char *dst = ba->data();
if (pos > oldsize)
::memset(dst + oldsize, 0x20, pos - oldsize);
else
::memmove(dst + pos + len, dst + pos, oldsize - pos);
memcpy(dst + pos, arr, len);
return *ba;
return ba->insert(pos, arr, len);
}
/*!
@ -1943,7 +1952,27 @@ QByteArray &QByteArray::insert(qsizetype i, const char *str)
QByteArray &QByteArray::insert(qsizetype i, const char *str, qsizetype len)
{
return qbytearray_insert(this, i, str, len);
if (i < 0 || str == nullptr || len <= 0)
return *this;
const auto oldSize = size();
const auto newSize = qMax(i, oldSize) + len;
const bool shouldGrow = d->shouldGrowBeforeInsert(d.begin() + qMin(i, oldSize), len);
// ### optimize me
if (d->needsDetach() || newSize > capacity() || shouldGrow) {
auto flags = d->detachFlags() | Data::GrowsForward;
if (i <= newSize / 4) // using QList's policy
flags |= Data::GrowsBackwards;
reallocGrowData(newSize + 1, flags);
}
if (i > oldSize) // set spaces in the uninitialized gap
d->copyAppend(i - oldSize, 0x20);
d->insert(d.begin() + i, str, str + len);
d.data()[d.size] = '\0';
return *this;
}
/*!
@ -1974,14 +2003,23 @@ QByteArray &QByteArray::insert(qsizetype i, qsizetype count, char ch)
if (i < 0 || count <= 0)
return *this;
qsizetype oldsize = size();
resize(qMax(i, oldsize) + count);
char *dst = d.data();
if (i > oldsize)
::memset(dst + oldsize, 0x20, i - oldsize);
else if (i < oldsize)
::memmove(dst + i + count, dst + i, oldsize - i);
::memset(dst + i, ch, count);
const auto oldSize = size();
const auto newSize = qMax(i, oldSize) + count;
const bool shouldGrow = d->shouldGrowBeforeInsert(d.begin() + qMin(i, oldSize), count);
// ### optimize me
if (d->needsDetach() || newSize > capacity() || shouldGrow) {
auto flags = d->detachFlags() | Data::GrowsForward;
if (i <= newSize / 4) // using QList's policy
flags |= Data::GrowsBackwards;
reallocGrowData(newSize + 1, flags);
}
if (i > oldSize) // set spaces in the uninitialized gap
d->copyAppend(i - oldSize, 0x20);
d->insert(d.begin() + i, count, ch);
d.data()[d.size] = '\0';
return *this;
}
@ -2001,15 +2039,11 @@ QByteArray &QByteArray::insert(qsizetype i, qsizetype count, char ch)
QByteArray &QByteArray::remove(qsizetype pos, qsizetype len)
{
if (len <= 0 || size_t(pos) >= size_t(size()))
if (len <= 0 || pos < 0 || size_t(pos) >= size_t(size()))
return *this;
detach();
if (len >= size() - pos) {
resize(pos);
} else {
memmove(d.data() + pos, d.data() + pos + len, size() - pos - len);
resize(size() - len);
}
d->erase(d.begin() + pos, d.begin() + qMin(pos + len, size()));
d.data()[d.size] = '\0';
return *this;
}

View File

@ -426,6 +426,7 @@ public:
private:
void reallocData(size_t alloc, Data::ArrayOptions options);
void reallocGrowData(size_t alloc, Data::ArrayOptions options);
void expand(qsizetype i);
QByteArray nulTerminated() const;