summaryrefslogtreecommitdiffstats
path: root/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp')
-rw-r--r--tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp221
1 files changed, 217 insertions, 4 deletions
diff --git a/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp b/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp
index 7eaa388118..27d4fd87d6 100644
--- a/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp
+++ b/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp
@@ -39,6 +39,7 @@
#include <vector>
#include <stdexcept>
#include <functional>
+#include <memory>
// A wrapper for a test function. Calls a function, if it fails, reports failure
#define RUN_TEST_FUNC(test, ...) \
@@ -88,6 +89,8 @@ private slots:
void exceptionSafetyPrimitives_destructor();
void exceptionSafetyPrimitives_mover();
void exceptionSafetyPrimitives_displacer();
+ void moveNonPod_data();
+ void moveNonPod();
#endif
};
@@ -2207,7 +2210,9 @@ ThrowingTypeWatcher& throwingTypeWatcher() { static ThrowingTypeWatcher global;
struct ThrowingType
{
static unsigned int throwOnce;
+ static unsigned int throwOnceInDtor;
static constexpr char throwString[] = "Requested to throw";
+ static constexpr char throwStringDtor[] = "Requested to throw in dtor";
void checkThrow() {
// deferred throw
if (throwOnce > 0) {
@@ -2220,27 +2225,36 @@ struct ThrowingType
}
int id = 0;
- ThrowingType(int val = 0) : id(val)
+ ThrowingType(int val = 0) noexcept(false) : id(val)
{
checkThrow();
}
- ThrowingType(const ThrowingType &other) : id(other.id)
+ ThrowingType(const ThrowingType &other) noexcept(false) : id(other.id)
{
checkThrow();
}
- ThrowingType& operator=(const ThrowingType &other)
+ ThrowingType& operator=(const ThrowingType &other) noexcept(false)
{
id = other.id;
checkThrow();
return *this;
}
- ~ThrowingType()
+ ~ThrowingType() noexcept(false)
{
throwingTypeWatcher().destroyed(id); // notify global watcher
id = -1;
+
+ // deferred throw
+ if (throwOnceInDtor > 0) {
+ --throwOnceInDtor;
+ if (throwOnceInDtor == 0) {
+ throw std::runtime_error(throwStringDtor);
+ }
+ }
}
};
unsigned int ThrowingType::throwOnce = 0;
+unsigned int ThrowingType::throwOnceInDtor = 0;
bool operator==(const ThrowingType &a, const ThrowingType &b) {
return a.id == b.id;
}
@@ -2815,6 +2829,205 @@ void tst_QArrayData::exceptionSafetyPrimitives_displacer()
}
}
}
+
+struct GenericThrowingType
+{
+ std::shared_ptr<int> data = std::shared_ptr<int>(new int(42)); // helper for double free
+ ThrowingType throwingData = ThrowingType(0);
+ GenericThrowingType(int id = 0) : throwingData(id)
+ {
+ QVERIFY(data.use_count() > 0);
+ }
+
+ ~GenericThrowingType()
+ {
+ // if we're in dtor but use_count is 0, it's double free
+ QVERIFY(data.use_count() > 0);
+ }
+
+ enum MoveCase {
+ MoveRightNoOverlap = 0,
+ MoveRightOverlap = 1,
+ MoveLeftNoOverlap = 2,
+ MoveLeftOverlap = 3,
+ };
+
+ enum ThrowCase {
+ ThrowInDtor = 0,
+ ThrowInUninitializedRegion = 1,
+ ThrowInOverlapRegion = 2,
+ };
+};
+
+struct PublicGenericMoveOps : QtPrivate::QCommonArrayOps<GenericThrowingType>
+{
+ template<typename GrowthTag>
+ void public_moveInGrowthDirection(GrowthTag tag, qsizetype futureGrowth)
+ {
+ static_assert(!QTypeInfo<GenericThrowingType>::isRelocatable);
+ using MoveOps = QtPrivate::QCommonArrayOps<GenericThrowingType>::GenericMoveOps;
+ MoveOps::moveInGrowthDirection(tag, this, futureGrowth);
+ }
+};
+
+void tst_QArrayData::moveNonPod_data()
+{
+ QTest::addColumn<GenericThrowingType::MoveCase>("moveCase");
+ QTest::addColumn<GenericThrowingType::ThrowCase>("throwCase");
+
+ // Throwing in dtor
+ QTest::newRow("throw-in-dtor-move-right-no-overlap")
+ << GenericThrowingType::MoveRightNoOverlap << GenericThrowingType::ThrowInDtor;
+ QTest::newRow("throw-in-dtor-move-right-overlap")
+ << GenericThrowingType::MoveRightOverlap << GenericThrowingType::ThrowInDtor;
+ QTest::newRow("throw-in-dtor-move-left-no-overlap")
+ << GenericThrowingType::MoveLeftNoOverlap << GenericThrowingType::ThrowInDtor;
+ QTest::newRow("throw-in-dtor-move-left-overlap")
+ << GenericThrowingType::MoveLeftOverlap << GenericThrowingType::ThrowInDtor;
+
+ // Throwing in uninitialized region
+ QTest::newRow("throw-in-uninit-region-move-right-no-overlap")
+ << GenericThrowingType::MoveRightNoOverlap
+ << GenericThrowingType::ThrowInUninitializedRegion;
+ QTest::newRow("throw-in-uninit-region-move-right-overlap")
+ << GenericThrowingType::MoveRightOverlap << GenericThrowingType::ThrowInUninitializedRegion;
+ QTest::newRow("throw-in-uninit-region-move-left-no-overlap")
+ << GenericThrowingType::MoveLeftNoOverlap
+ << GenericThrowingType::ThrowInUninitializedRegion;
+ QTest::newRow("throw-in-uninit-region-move-left-overlap")
+ << GenericThrowingType::MoveLeftOverlap << GenericThrowingType::ThrowInUninitializedRegion;
+
+ // Throwing in overlap region
+ QTest::newRow("throw-in-overlap-region-move-right-overlap")
+ << GenericThrowingType::MoveRightOverlap << GenericThrowingType::ThrowInOverlapRegion;
+ QTest::newRow("throw-in-overlap-region-move-left-overlap")
+ << GenericThrowingType::MoveLeftOverlap << GenericThrowingType::ThrowInOverlapRegion;
+}
+
+void tst_QArrayData::moveNonPod()
+{
+ // Assume that non-throwing moves perform correctly. Otherwise, all previous
+ // tests would've failed. Test only what happens when exceptions are thrown.
+
+ QFETCH(GenericThrowingType::MoveCase, moveCase);
+ QFETCH(GenericThrowingType::ThrowCase, throwCase);
+
+ struct WatcherScope
+ {
+ WatcherScope() { throwingTypeWatcher().watch = true; }
+ ~WatcherScope()
+ {
+ throwingTypeWatcher().watch = false;
+ throwingTypeWatcher().destroyedIds.clear();
+ }
+ };
+
+ const auto cast = [] (auto &dataPointer) {
+ return static_cast<PublicGenericMoveOps*>(std::addressof(dataPointer));
+ };
+
+ const auto setThrowingFlag = [throwCase] () {
+ switch (throwCase) {
+ case GenericThrowingType::ThrowInDtor: ThrowingType::throwOnceInDtor = 2; break;
+ case GenericThrowingType::ThrowInUninitializedRegion: ThrowingType::throwOnce = 2; break;
+ case GenericThrowingType::ThrowInOverlapRegion: ThrowingType::throwOnce = 3; break;
+ default: QFAIL("Unknown throwCase");
+ }
+ };
+
+ const auto checkExceptionText = [throwCase] (const char *what) {
+ if (throwCase == GenericThrowingType::ThrowInDtor) {
+ QCOMPARE(std::string(what), ThrowingType::throwStringDtor);
+ } else {
+ QCOMPARE(std::string(what), ThrowingType::throwString);
+ }
+ };
+
+ const auto checkNoMemoryLeaks = [throwCase] (size_t extraDestroyedElements = 0) {
+ const size_t destroyedElementsCount = throwingTypeWatcher().destroyedIds.size();
+ switch (throwCase) {
+ case GenericThrowingType::ThrowInDtor:
+ // 2 elements from uinitialized region + 2 elements from old range + extra if no overlap
+ QCOMPARE(destroyedElementsCount, 2u + 2u + extraDestroyedElements);
+ break;
+ case GenericThrowingType::ThrowInUninitializedRegion:
+ // always 1 element from uninitialized region
+ QCOMPARE(destroyedElementsCount, 1u);
+ break;
+ case GenericThrowingType::ThrowInOverlapRegion:
+ // always 2 elements from uninitialized region
+ QCOMPARE(destroyedElementsCount, 2u);
+ break;
+ default: QFAIL("Unknown throwCase");
+ }
+ };
+
+ switch (moveCase) {
+ case GenericThrowingType::MoveRightNoOverlap : { // moving right without overlap
+ auto storage = createDataPointer<GenericThrowingType>(20, 3);
+ QVERIFY(storage.freeSpaceAtEnd() > 3);
+
+ WatcherScope scope; Q_UNUSED(scope);
+ try {
+ setThrowingFlag();
+ cast(storage)->public_moveInGrowthDirection(QtPrivate::GrowsForwardTag{}, 4);
+ QFAIL("Unreachable line!");
+ } catch (const std::runtime_error &e) {
+ checkExceptionText(e.what());
+ }
+ checkNoMemoryLeaks(1);
+ break;
+ }
+ case GenericThrowingType::MoveRightOverlap: { // moving right with overlap
+ auto storage = createDataPointer<GenericThrowingType>(20, 3);
+ QVERIFY(storage.freeSpaceAtEnd() > 3);
+
+ WatcherScope scope; Q_UNUSED(scope);
+ try {
+ setThrowingFlag();
+ cast(storage)->public_moveInGrowthDirection(QtPrivate::GrowsForwardTag{}, 2);
+ QFAIL("Unreachable line!");
+ } catch (const std::runtime_error &e) {
+ checkExceptionText(e.what());
+ }
+ checkNoMemoryLeaks();
+ break;
+ }
+ case GenericThrowingType::MoveLeftNoOverlap: { // moving left without overlap
+ auto storage = createDataPointer<GenericThrowingType>(20, 2);
+ storage->insert(storage.begin(), 1, GenericThrowingType(42));
+ QVERIFY(storage.freeSpaceAtBegin() > 3);
+
+ WatcherScope scope; Q_UNUSED(scope);
+ try {
+ setThrowingFlag();
+ cast(storage)->public_moveInGrowthDirection(QtPrivate::GrowsBackwardsTag{}, 4);
+ QFAIL("Unreachable line!");
+ } catch (const std::runtime_error &e) {
+ checkExceptionText(e.what());
+ }
+ checkNoMemoryLeaks(1);
+ break;
+ }
+ case GenericThrowingType::MoveLeftOverlap: {
+ auto storage = createDataPointer<GenericThrowingType>(20, 2);
+ storage->insert(storage.begin(), 1, GenericThrowingType(42));
+ QVERIFY(storage.freeSpaceAtBegin() > 3);
+
+ WatcherScope scope; Q_UNUSED(scope);
+ try {
+ setThrowingFlag();
+ cast(storage)->public_moveInGrowthDirection(QtPrivate::GrowsBackwardsTag{}, 2);
+ QFAIL("Unreachable line!");
+ } catch (const std::runtime_error &e) {
+ checkExceptionText(e.what());
+ }
+ checkNoMemoryLeaks();
+ break;
+ }
+ default: QFAIL("Unknown moveCase");
+ };
+}
#endif // QT_NO_EXCEPTIONS
QTEST_APPLESS_MAIN(tst_QArrayData)