QVarLengthArray: fix size update on failed append()

If the in-place constructor throws, the old code had already updated
the container's size(). Fix by delaying the update to after the
in-place construction.

[ChangeLog][QtCore][QVarLengthArray] Fixed a bug whereby a failed
append() would leave the container with an inconsistent size().

Pick-to: 6.2 5.15
Change-Id: Ief1e668d945149bd8ba96c8af1398baaa7876880
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Marc Mutz 2021-12-07 17:46:26 +01:00
parent 0f88e79ed8
commit d4a88e4ea4
2 changed files with 50 additions and 6 deletions

View File

@ -228,20 +228,19 @@ public:
if (size() == capacity()) { // i.e. size() != 0 if (size() == capacity()) { // i.e. size() != 0
T copy(t); T copy(t);
reallocate(size(), size() << 1); reallocate(size(), size() << 1);
const qsizetype idx = s++; new (end()) T(std::move(copy));
new (data() + idx) T(std::move(copy));
} else { } else {
const qsizetype idx = s++; new (end()) T(t);
new (data() + idx) T(t);
} }
++s;
} }
void append(T &&t) void append(T &&t)
{ {
if (size() == capacity()) if (size() == capacity())
reallocate(size(), size() << 1); reallocate(size(), size() << 1);
const qsizetype idx = s++; new (end()) T(std::move(t));
new (data() + idx) T(std::move(t)); ++s;
} }
void append(const T *buf, qsizetype size); void append(const T *buf, qsizetype size);

View File

@ -30,6 +30,7 @@
#include <qvarlengtharray.h> #include <qvarlengtharray.h>
#include <qvariant.h> #include <qvariant.h>
#include <qscopeguard.h> #include <qscopeguard.h>
#include <qscopedvaluerollback.h>
#include <memory> #include <memory>
@ -85,6 +86,7 @@ private slots:
void removeLast(); void removeLast();
void oldTests(); void oldTests();
void appendCausingRealloc(); void appendCausingRealloc();
void appendIsStronglyExceptionSafe();
void resize(); void resize();
void realloc(); void realloc();
void iterators(); void iterators();
@ -402,6 +404,49 @@ void tst_QVarLengthArray::appendCausingRealloc()
d.append(i); d.append(i);
} }
void tst_QVarLengthArray::appendIsStronglyExceptionSafe()
{
#ifdef QT_NO_EXCEPTIONS
QSKIP("This test requires exception support enabled in the compiler.");
#else
static bool throwOnCopyNow = false;
static bool throwOnMoveNow = false;
struct Thrower {
Thrower() = default;
Thrower(const Thrower &)
{
if (throwOnCopyNow)
throw 1;
}
Thrower &operator=(const Thrower &) = default;
Thrower(Thrower &&)
{
if (throwOnMoveNow)
throw 1;
}
Thrower &operator=(Thrower &&) = default;
~Thrower() = default;
};
{
// ### TODO: QVLA isn't exception-safe when throwing during reallocation,
// ### so check with size() < capacity() for now
QVarLengthArray<Thrower, 2> vla(1);
{
Thrower t;
const QScopedValueRollback rb(throwOnCopyNow, true);
QVERIFY_THROWS_EXCEPTION(int, vla.push_back(t));
QCOMPARE(vla.size(), 1);
}
{
const QScopedValueRollback rb(throwOnMoveNow, true);
QVERIFY_THROWS_EXCEPTION(int, vla.push_back({}));
QCOMPARE(vla.size(), 1);
}
}
#endif
}
void tst_QVarLengthArray::resize() void tst_QVarLengthArray::resize()
{ {
// Empty Movable // Empty Movable