summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrei Golubev <andrei.golubev@qt.io>2021-04-24 01:34:40 +0200
committerAndrei Golubev <andrei.golubev@qt.io>2021-04-26 17:07:15 +0200
commit65d0f6829cc124f6d0d4003a17bedcb74dddf33b (patch)
treef2853e667bb8d04db4c0afcc23c9d2835752802f
parenta0253f5f0249024580050e4ec22d50cb139ef8d9 (diff)
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 Pick-to: dev 6.0 6.1 Change-Id: I353ed46a31c5c77cd0c5fcacd3dfce46e5cf3e67 Reviewed-by: Lars Knoll <lars.knoll@qt.io>
-rw-r--r--tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp281
1 files changed, 281 insertions, 0 deletions
diff --git a/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp b/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp
index e29527cb00..856aa4b20f 100644
--- a/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp
+++ b/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp
@@ -37,6 +37,7 @@
#include <tuple>
#include <algorithm>
#include <vector>
+#include <set>
#include <stdexcept>
#include <functional>
#include <memory>
@@ -84,6 +85,10 @@ private slots:
void dataPointerAllocate();
void selfEmplaceBackwards();
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; }
@@ -2276,5 +2281,281 @@ void tst_QArrayData::selfEmplaceForward()
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)
#include "tst_qarraydata.moc"