summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/tools/qarraydataops.h20
-rw-r--r--tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp221
2 files changed, 228 insertions, 13 deletions
diff --git a/src/corelib/tools/qarraydataops.h b/src/corelib/tools/qarraydataops.h
index 0230677330..fe71b41d5c 100644
--- a/src/corelib/tools/qarraydataops.h
+++ b/src/corelib/tools/qarraydataops.h
@@ -1039,7 +1039,7 @@ struct QCommonArrayOps : QArrayOpsSelector<T>::Type
using iterator = typename Base::iterator;
using const_iterator = typename Base::const_iterator;
-private:
+protected:
using Self = QCommonArrayOps<T>;
// Tag dispatched helper functions
@@ -1116,6 +1116,11 @@ private:
--start;
}
+ // re-created the range. now there is an initialized memory region
+ // somewhere in the allocated area. if something goes wrong, we must
+ // clean it up, so "freeze" the position for now (cannot commit yet)
+ destroyer.freeze();
+
// step 2. move assign over existing elements in the overlapping
// region (if there's an overlap)
while (e != begin) {
@@ -1124,18 +1129,15 @@ private:
*start = std::move_if_noexcept(*e);
}
- // re-created the range. now there is an initialized memory region
- // somewhere in the allocated area. if something goes wrong, we must
- // clean it up, so "freeze" the position for now (cannot commit yet)
- destroyer.freeze();
-
// step 3. destroy elements in the old range
const qsizetype originalSize = this_->size;
- start = oldRangeEnd; // mind the possible gap, have to re-assign
- while (start != begin) {
+ start = begin; // delete elements in reverse order to prevent any gaps
+ while (start != oldRangeEnd) {
// Exceptions or not, dtor called once per instance
+ if constexpr (std::is_same_v<std::decay_t<GrowthTag>, GrowsForwardTag>)
+ ++this_->ptr;
--this_->size;
- (--start)->~T();
+ (start++)->~T();
}
destroyer.commit();
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)