QStringBuilder: handle freeSpaceAtBegin() in op+=

Amends 9b320edb53.

The above commit made the mistake of relying on the 30yr+ old
fundamental relation

    size() - capacity() == freeSpaceAtEnd()

which, however, Qt 6's prepend()-optimization (freeSpaceAtBegin())
broke. Because of that, while size() - capacity() may be large enough
to hold the new data, if freeSpaceAtBegin() > 0, then freeSpaceAtEnd()
may not.

Fix by inspecting freeSpaceAtEnd() instead of capacity(). The
following reserve() call is unaffected, since it internally already
adds freeSpaceAtBegin() to the requested size, which is why the
unconditional reserve() in 9b320edb535a0fbe118933d2e983b73f90c32685^
worked while 9b320edb535a0fbe118933d2e983b73f90c32685's capacity()
check did not.

Fixes: QTBUG-99330
Pick-to: 6.2 6.3
Change-Id: I520f36216011423f97a24484263acd40d8b1fa43
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Topi Reiniö <topi.reinio@qt.io>
Reviewed-by: Sona Kurazyan <sona.kurazyan@qt.io>
This commit is contained in:
Marc Mutz 2021-12-20 21:52:45 +01:00
parent 1efae2dcad
commit 11409f4c02
2 changed files with 45 additions and 2 deletions

View File

@ -450,7 +450,7 @@ QByteArray &appendToByteArray(QByteArray &a, const QStringBuilder<A, B> &b, char
// append 8-bit data to a byte array
qsizetype len = a.size() + QConcatenable< QStringBuilder<A, B> >::size(b);
a.detach(); // a detach() in a.data() could reset a.capacity() to a.size()
if (len > a.capacity())
if (len > a.data_ptr().freeSpaceAtEnd()) // capacity() was broken when prepend()-optimization landed
a.reserve(qMax(len, 2 * a.capacity()));
char *it = a.data() + a.size();
QConcatenable< QStringBuilder<A, B> >::appendTo(b, it);
@ -479,7 +479,7 @@ QString &operator+=(QString &a, const QStringBuilder<A, B> &b)
{
qsizetype len = a.size() + QConcatenable< QStringBuilder<A, B> >::size(b);
a.detach(); // a detach() in a.data() could reset a.capacity() to a.size()
if (len > a.capacity())
if (len > a.data_ptr().freeSpaceAtEnd()) // capacity() was broken when prepend()-optimization landed
a.reserve(qMax(len, 2 * a.capacity()));
QChar *it = a.data() + a.size();
QConcatenable< QStringBuilder<A, B> >::appendTo(b, it);

View File

@ -73,6 +73,36 @@ template <> QByteArray toQByteArray(char * const &p) { return p; }
template <size_t N> QByteArray toQByteArray(const char (&a)[N]) { return a; }
template <> QByteArray toQByteArray(const char &c) { return QByteArray(&c, 1); }
template <typename String, typename Separator>
void checkItWorksWithFreeSpaceAtBegin(const String &chunk, const Separator &separator)
{
// GIVEN: a String with freeSpaceAtBegin() and less than chunk.size() freeSpaceAtEnd()
String str;
int prepends = 0;
const int max_prepends = 10;
while (str.data_ptr().freeSpaceAtBegin() < chunk.size() && prepends++ < max_prepends)
str.prepend(chunk);
QVERIFY(prepends < max_prepends);
int appends = 0;
const int max_appends = 100;
while (str.data_ptr().freeSpaceAtEnd() >= chunk.size() && appends++ < max_appends)
str.append(chunk);
QVERIFY(appends < max_appends);
QVERIFY(str.capacity() - str.size() >= chunk.size());
QVERIFY(str.data_ptr().freeSpaceAtEnd() < chunk.size());
// WHEN: adding a QStringBuilder expression which exceeds freeSpaceAtEnd()
str += separator P chunk;
// THEN: it doesn't crash (QTBUG-99330)
const String expected = chunk.repeated(prepends + appends) + separator + chunk;
QCOMPARE(str, expected);
}
void runScenario()
{
// this code is latin1. TODO: replace it with the utf8 block below, once
@ -329,6 +359,16 @@ void runScenario()
QCOMPARE(str2, str2_e);
}
checkItWorksWithFreeSpaceAtBegin(QString::fromUtf8(UTF8_LITERAL),
#ifdef QT_NO_CAST_FROM_ASCII
QLatin1String("1234")
#else
"1234"
#endif
);
if (QTest::currentTestFailed())
return;
//operator QByteArray +=
{
QByteArray ba = UTF8_LITERAL;
@ -350,4 +390,7 @@ void runScenario()
QCOMPARE(byteArray, "multipart/mixed; boundary=\"oooooooooooooooooooooooooooooo\"");
}
checkItWorksWithFreeSpaceAtBegin(QByteArray(UTF8_LITERAL), "1234");
if (QTest::currentTestFailed())
return;
}