Add tests for QtPrivate::q_relocate_overlap_n
Test the relocation logic through the QADP::relocate() method which basically calls q_relocate_overlap_n inside and then ensures that the data pointers are in good state Running these locally in fact revealed a bug in the implementation, so these tests are definitely good to have Task-number: QTBUG-93019 Change-Id: I353ed46a31c5c77cd0c5fcacd3dfce46e5cf3e67 Reviewed-by: Lars Knoll <lars.knoll@qt.io> (cherry picked from commit 65d0f6829cc124f6d0d4003a17bedcb74dddf33b) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
parent
9c1e58803c
commit
4eca69b86a
@ -37,6 +37,7 @@
|
|||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -84,6 +85,10 @@ private slots:
|
|||||||
void dataPointerAllocate();
|
void dataPointerAllocate();
|
||||||
void selfEmplaceBackwards();
|
void selfEmplaceBackwards();
|
||||||
void selfEmplaceForward();
|
void selfEmplaceForward();
|
||||||
|
#ifndef QT_NO_EXCEPTIONS
|
||||||
|
void relocateWithExceptions_data();
|
||||||
|
void relocateWithExceptions();
|
||||||
|
#endif // QT_NO_EXCEPTIONS
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T> const T &const_(const T &t) { return t; }
|
template <class T> const T &const_(const T &t) { return t; }
|
||||||
@ -2276,5 +2281,281 @@ void tst_QArrayData::selfEmplaceForward()
|
|||||||
RUN_TEST_FUNC(testSelfEmplace, MyComplexQString(), 4, complexObjs);
|
RUN_TEST_FUNC(testSelfEmplace, MyComplexQString(), 4, complexObjs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef QT_NO_EXCEPTIONS
|
||||||
|
struct ThrowingTypeWatcher
|
||||||
|
{
|
||||||
|
std::vector<void *> destroyedAddrs;
|
||||||
|
bool watch = false;
|
||||||
|
|
||||||
|
void destroyed(void *addr)
|
||||||
|
{
|
||||||
|
if (watch)
|
||||||
|
destroyedAddrs.push_back(addr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ThrowingTypeWatcher &throwingTypeWatcher()
|
||||||
|
{
|
||||||
|
static ThrowingTypeWatcher global;
|
||||||
|
return global;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ThrowingType
|
||||||
|
{
|
||||||
|
static unsigned int throwOnce;
|
||||||
|
static constexpr char throwString[] = "Requested to throw";
|
||||||
|
enum MoveCase {
|
||||||
|
MoveRightNoOverlap,
|
||||||
|
MoveRightOverlap,
|
||||||
|
MoveLeftNoOverlap,
|
||||||
|
MoveLeftOverlap,
|
||||||
|
};
|
||||||
|
enum ThrowCase {
|
||||||
|
NoThrow,
|
||||||
|
ThrowInUninitializedRegion,
|
||||||
|
ThrowInOverlapRegion,
|
||||||
|
};
|
||||||
|
|
||||||
|
// reinforce basic checkers with std::shared_ptr which happens to signal
|
||||||
|
// very explicitly about use-after-free and so on under ASan
|
||||||
|
std::shared_ptr<int> doubleFreeHelper = std::shared_ptr<int>(new int(42));
|
||||||
|
int id = 0;
|
||||||
|
|
||||||
|
void checkThrow()
|
||||||
|
{
|
||||||
|
// deferred throw
|
||||||
|
if (throwOnce > 0) {
|
||||||
|
--throwOnce;
|
||||||
|
if (throwOnce == 0) {
|
||||||
|
throw std::runtime_error(throwString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void copy(const ThrowingType &other) noexcept(false)
|
||||||
|
{
|
||||||
|
doubleFreeHelper = other.doubleFreeHelper;
|
||||||
|
id = other.id;
|
||||||
|
checkThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
ThrowingType(int val = 0) noexcept(false) : id(val) { checkThrow(); }
|
||||||
|
ThrowingType(const ThrowingType &other) noexcept(false) { copy(other); }
|
||||||
|
ThrowingType &operator=(const ThrowingType &other) noexcept(false)
|
||||||
|
{
|
||||||
|
copy(other);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ThrowingType(ThrowingType &&other) noexcept(false) { copy(other); }
|
||||||
|
ThrowingType &operator=(ThrowingType &&other) noexcept(false)
|
||||||
|
{
|
||||||
|
copy(other);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
~ThrowingType() noexcept(true)
|
||||||
|
{
|
||||||
|
throwingTypeWatcher().destroyed(this); // notify global watcher
|
||||||
|
id = -1;
|
||||||
|
// if we're in dtor but use_count is 0, it's double free
|
||||||
|
QVERIFY(doubleFreeHelper.use_count() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator==(const ThrowingType &a, const ThrowingType &b) { return a.id == b.id; }
|
||||||
|
};
|
||||||
|
|
||||||
|
unsigned int ThrowingType::throwOnce = 0;
|
||||||
|
static_assert(!QTypeInfo<ThrowingType>::isRelocatable);
|
||||||
|
|
||||||
|
void tst_QArrayData::relocateWithExceptions_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<ThrowingType::MoveCase>("moveCase");
|
||||||
|
QTest::addColumn<ThrowingType::ThrowCase>("throwCase");
|
||||||
|
// Not throwing
|
||||||
|
QTest::newRow("no-throw-move-right-no-overlap")
|
||||||
|
<< ThrowingType::MoveRightNoOverlap << ThrowingType::NoThrow;
|
||||||
|
QTest::newRow("no-throw-move-right-overlap")
|
||||||
|
<< ThrowingType::MoveRightOverlap << ThrowingType::NoThrow;
|
||||||
|
QTest::newRow("no-throw-move-left-no-overlap")
|
||||||
|
<< ThrowingType::MoveLeftNoOverlap << ThrowingType::NoThrow;
|
||||||
|
QTest::newRow("no-throw-move-left-overlap")
|
||||||
|
<< ThrowingType::MoveLeftOverlap << ThrowingType::NoThrow;
|
||||||
|
// Throwing in uninitialized region
|
||||||
|
QTest::newRow("throw-in-uninit-region-move-right-no-overlap")
|
||||||
|
<< ThrowingType::MoveRightNoOverlap << ThrowingType::ThrowInUninitializedRegion;
|
||||||
|
QTest::newRow("throw-in-uninit-region-move-right-overlap")
|
||||||
|
<< ThrowingType::MoveRightOverlap << ThrowingType::ThrowInUninitializedRegion;
|
||||||
|
QTest::newRow("throw-in-uninit-region-move-left-no-overlap")
|
||||||
|
<< ThrowingType::MoveLeftNoOverlap << ThrowingType::ThrowInUninitializedRegion;
|
||||||
|
QTest::newRow("throw-in-uninit-region-move-left-overlap")
|
||||||
|
<< ThrowingType::MoveLeftOverlap << ThrowingType::ThrowInUninitializedRegion;
|
||||||
|
// Throwing in overlap region
|
||||||
|
QTest::newRow("throw-in-overlap-region-move-right-overlap")
|
||||||
|
<< ThrowingType::MoveRightOverlap << ThrowingType::ThrowInOverlapRegion;
|
||||||
|
QTest::newRow("throw-in-overlap-region-move-left-overlap")
|
||||||
|
<< ThrowingType::MoveLeftOverlap << ThrowingType::ThrowInOverlapRegion;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tst_QArrayData::relocateWithExceptions()
|
||||||
|
{
|
||||||
|
// Assume that non-throwing moves perform correctly. Otherwise, all previous
|
||||||
|
// tests would've failed. Test only what happens when exceptions are thrown.
|
||||||
|
QFETCH(ThrowingType::MoveCase, moveCase);
|
||||||
|
QFETCH(ThrowingType::ThrowCase, throwCase);
|
||||||
|
|
||||||
|
struct ThrowingTypeLeakChecker
|
||||||
|
{
|
||||||
|
ThrowingType::MoveCase moveCase;
|
||||||
|
ThrowingType::ThrowCase throwCase;
|
||||||
|
size_t containerSize = 0;
|
||||||
|
|
||||||
|
ThrowingTypeLeakChecker(ThrowingType::MoveCase mc, ThrowingType::ThrowCase tc)
|
||||||
|
: moveCase(mc), throwCase(tc)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void start(qsizetype size)
|
||||||
|
{
|
||||||
|
containerSize = size_t(size);
|
||||||
|
throwingTypeWatcher().watch = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
~ThrowingTypeLeakChecker()
|
||||||
|
{
|
||||||
|
const size_t destroyedElementsCount = throwingTypeWatcher().destroyedAddrs.size();
|
||||||
|
const size_t destroyedElementsUniqueCount =
|
||||||
|
std::set<void *>(throwingTypeWatcher().destroyedAddrs.begin(),
|
||||||
|
throwingTypeWatcher().destroyedAddrs.end())
|
||||||
|
.size();
|
||||||
|
|
||||||
|
// reset the global watcher first and only then verify things
|
||||||
|
throwingTypeWatcher().watch = false;
|
||||||
|
throwingTypeWatcher().destroyedAddrs.clear();
|
||||||
|
|
||||||
|
size_t deletedByRelocate = 0;
|
||||||
|
switch (throwCase) {
|
||||||
|
case ThrowingType::NoThrow:
|
||||||
|
// if no overlap, N elements from old range. otherwise, N - 1
|
||||||
|
// elements from old range
|
||||||
|
if (moveCase == ThrowingType::MoveLeftNoOverlap
|
||||||
|
|| moveCase == ThrowingType::MoveRightNoOverlap) {
|
||||||
|
deletedByRelocate = containerSize;
|
||||||
|
} else {
|
||||||
|
deletedByRelocate = containerSize - 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ThrowingType::ThrowInUninitializedRegion:
|
||||||
|
// 1 relocated element from uninitialized region
|
||||||
|
deletedByRelocate = 1u;
|
||||||
|
break;
|
||||||
|
case ThrowingType::ThrowInOverlapRegion:
|
||||||
|
// 2 relocated elements from uninitialized region
|
||||||
|
deletedByRelocate = 2u;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
QFAIL("Unknown throwCase");
|
||||||
|
}
|
||||||
|
|
||||||
|
QCOMPARE(destroyedElementsCount, deletedByRelocate + containerSize);
|
||||||
|
QCOMPARE(destroyedElementsUniqueCount, destroyedElementsCount);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto setDeferredThrow = [throwCase]() {
|
||||||
|
switch (throwCase) {
|
||||||
|
case ThrowingType::NoThrow:
|
||||||
|
break; // do nothing
|
||||||
|
case ThrowingType::ThrowInUninitializedRegion:
|
||||||
|
ThrowingType::throwOnce = 2;
|
||||||
|
break;
|
||||||
|
case ThrowingType::ThrowInOverlapRegion:
|
||||||
|
ThrowingType::throwOnce = 3;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
QFAIL("Unknown throwCase");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto createDataPointer = [](qsizetype capacity, qsizetype initSize) {
|
||||||
|
QArrayDataPointer<ThrowingType> qadp(QTypedArrayData<ThrowingType>::allocate(capacity));
|
||||||
|
qadp->appendInitialize(initSize);
|
||||||
|
int i = 0;
|
||||||
|
std::generate(qadp.begin(), qadp.end(), [&i]() { return ThrowingType(i++); });
|
||||||
|
return qadp;
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (moveCase) {
|
||||||
|
case ThrowingType::MoveRightNoOverlap: {
|
||||||
|
ThrowingTypeLeakChecker watch(moveCase, throwCase);
|
||||||
|
auto storage = createDataPointer(20, 3);
|
||||||
|
QVERIFY(storage.freeSpaceAtEnd() > 3);
|
||||||
|
|
||||||
|
watch.start(storage.size);
|
||||||
|
try {
|
||||||
|
setDeferredThrow();
|
||||||
|
storage->relocate(4);
|
||||||
|
if (throwCase != ThrowingType::NoThrow)
|
||||||
|
QFAIL("Unreachable line!");
|
||||||
|
} catch (const std::runtime_error &e) {
|
||||||
|
QCOMPARE(std::string(e.what()), ThrowingType::throwString);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ThrowingType::MoveRightOverlap: {
|
||||||
|
ThrowingTypeLeakChecker watch(moveCase, throwCase);
|
||||||
|
auto storage = createDataPointer(20, 3);
|
||||||
|
QVERIFY(storage.freeSpaceAtEnd() > 3);
|
||||||
|
|
||||||
|
watch.start(storage.size);
|
||||||
|
try {
|
||||||
|
setDeferredThrow();
|
||||||
|
storage->relocate(2);
|
||||||
|
if (throwCase != ThrowingType::NoThrow)
|
||||||
|
QFAIL("Unreachable line!");
|
||||||
|
} catch (const std::runtime_error &e) {
|
||||||
|
QCOMPARE(std::string(e.what()), ThrowingType::throwString);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ThrowingType::MoveLeftNoOverlap: {
|
||||||
|
ThrowingTypeLeakChecker watch(moveCase, throwCase);
|
||||||
|
auto storage = createDataPointer(20, 2);
|
||||||
|
storage->insert(0, 1, ThrowingType(42));
|
||||||
|
QVERIFY(storage.freeSpaceAtBegin() > 3);
|
||||||
|
|
||||||
|
watch.start(storage.size);
|
||||||
|
try {
|
||||||
|
setDeferredThrow();
|
||||||
|
storage->relocate(-4);
|
||||||
|
if (throwCase != ThrowingType::NoThrow)
|
||||||
|
QFAIL("Unreachable line!");
|
||||||
|
} catch (const std::runtime_error &e) {
|
||||||
|
QCOMPARE(std::string(e.what()), ThrowingType::throwString);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ThrowingType::MoveLeftOverlap: {
|
||||||
|
ThrowingTypeLeakChecker watch(moveCase, throwCase);
|
||||||
|
auto storage = createDataPointer(20, 2);
|
||||||
|
storage->insert(0, 1, ThrowingType(42));
|
||||||
|
QVERIFY(storage.freeSpaceAtBegin() > 3);
|
||||||
|
|
||||||
|
watch.start(storage.size);
|
||||||
|
try {
|
||||||
|
setDeferredThrow();
|
||||||
|
storage->relocate(-2);
|
||||||
|
if (throwCase != ThrowingType::NoThrow)
|
||||||
|
QFAIL("Unreachable line!");
|
||||||
|
} catch (const std::runtime_error &e) {
|
||||||
|
QCOMPARE(std::string(e.what()), ThrowingType::throwString);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
QFAIL("Unknown ThrowingType::MoveCase");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif // QT_NO_EXCEPTIONS
|
||||||
|
|
||||||
QTEST_APPLESS_MAIN(tst_QArrayData)
|
QTEST_APPLESS_MAIN(tst_QArrayData)
|
||||||
#include "tst_qarraydata.moc"
|
#include "tst_qarraydata.moc"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user