diff --git a/src/corelib/tools/qarraydataops.h b/src/corelib/tools/qarraydataops.h index bc5f3ac52a6..369a8ae5742 100644 --- a/src/corelib/tools/qarraydataops.h +++ b/src/corelib/tools/qarraydataops.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Copyright (C) 2016 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** @@ -46,6 +46,8 @@ #include #include +#include +#include QT_BEGIN_NAMESPACE @@ -53,6 +55,166 @@ template struct QArrayDataPointer; namespace QtPrivate { +/*! + \internal + + This class provides basic building blocks to do reversible operations. This + in turn allows to reason about exception safety under certain conditions. + + This class is not part of the Qt API. It exists for the convenience of other + Qt classes. This class may change from version to version without notice, + or even be removed. + + We mean it. + */ +template +struct QArrayExceptionSafetyPrimitives +{ + using parameter_type = typename QArrayDataPointer::parameter_type; + using iterator = typename QArrayDataPointer::iterator; + + // Constructs a range of elements at the specified position. If an exception + // is thrown during construction, already constructed elements are + // destroyed. By design, only one function (create/copy/clone/move) and only + // once is supposed to be called per class instance. + struct Constructor + { + T *const where; + size_t n = 0; + + template + using iterator_copy_value = decltype(*std::declval()); + template + using iterator_move_value = decltype(std::move(*std::declval())); + + Constructor(T *w) noexcept : where(w) {} + qsizetype create(size_t size) noexcept(std::is_nothrow_default_constructible_v) + { + n = 0; + while (n != size) { + new (where + n) T; + ++n; + } + return qsizetype(std::exchange(n, 0)); + } + template + qsizetype copy(ForwardIt first, ForwardIt last) + noexcept(std::is_nothrow_constructible_v>) + { + n = 0; + for (; first != last; ++first) { + new (where + n) T(*first); + ++n; + } + return qsizetype(std::exchange(n, 0)); + } + qsizetype clone(size_t size, parameter_type t) + noexcept(std::is_nothrow_constructible_v) + { + n = 0; + while (n != size) { + new (where + n) T(t); + ++n; + } + return qsizetype(std::exchange(n, 0)); + } + template + qsizetype move(ForwardIt first, ForwardIt last) + noexcept(std::is_nothrow_constructible_v>) + { + n = 0; + for (; first != last; ++first) { + new (where + n) T(std::move(*first)); + ++n; + } + return qsizetype(std::exchange(n, 0)); + } + ~Constructor() noexcept(std::is_nothrow_destructible_v) + { + while (n) + where[--n].~T(); + } + }; + + // Watches passed iterator. Unless commit() is called, all the elements that + // the watched iterator passes through are deleted at the end of object + // lifetime. + // + // Requirements: when not at starting position, the iterator is expected to + // point to a valid object (to initialized memory) + template + struct Destructor + { + It *iter; + It end; + + Destructor(It &it) noexcept(std::is_nothrow_copy_constructible_v) + : iter(std::addressof(it)), end(it) + { } + void commit() noexcept + { + iter = std::addressof(end); + } + ~Destructor() noexcept(std::is_nothrow_destructible_v) + { + // Step is either 1 or -1 and depends on the interposition of *iter + // and end. Note that *iter is expected to point to a valid object + // (see the logic below). + for (const int step = *iter < end ? 1 : -1; *iter != end; std::advance(*iter, step)) + (*iter)->~T(); + } + }; + + // Moves the data range in memory by the specified amount. Unless commit() + // is called, the data is moved back to the original place at the end of + // object lifetime. + struct Displacer + { + T *const begin; + T *const end; + qsizetype displace; + + static_assert(QTypeInfoQuery::isRelocatable, "Type must be relocatable"); + + Displacer(T *start, T *finish, qsizetype diff) noexcept + : begin(start), end(finish), displace(diff) + { + ::memmove(static_cast(begin + displace), static_cast(begin), + (end - begin) * sizeof(T)); + } + void commit() noexcept { displace = 0; } + ~Displacer() noexcept + { + if (displace) + ::memmove(static_cast(begin), static_cast(begin + displace), + (end - begin) * sizeof(T)); + } + }; + + // Watches passed iterator. Moves the data range (defined as a start + // iterator and a length) to the new starting position at the end of object + // lifetime. + struct Mover + { + T *&destination; + const T *const source; + size_t n; + qsizetype &size; + + static_assert(QTypeInfoQuery::isRelocatable, "Type must be relocatable"); + + Mover(T *&start, size_t length, qsizetype &sz) noexcept + : destination(start), source(start), n(length), size(sz) + { } + ~Mover() noexcept + { + ::memmove(static_cast(destination), static_cast(source), + n * sizeof(T)); + size -= source > destination ? source - destination : destination - source; + } + }; +}; + QT_WARNING_PUSH #if defined(Q_CC_GNU) && Q_CC_GNU >= 700 QT_WARNING_DISABLE_GCC("-Wstringop-overflow") @@ -265,35 +427,13 @@ struct QGenericArrayOps Q_ASSERT(b <= e); Q_ASSERT(size_t(e - b) <= this->allocatedCapacity() - this->size); + typedef typename QArrayExceptionSafetyPrimitives::Constructor CopyConstructor; + // Provides strong exception safety guarantee, // provided T::~T() nothrow - struct CopyConstructor - { - CopyConstructor(T *w) : where(w) {} - - void copy(T *src, const T *const srcEnd) - { - n = 0; - for (; src != srcEnd; ++src) { - new (where + n) T(std::move(*src)); - ++n; - } - n = 0; - } - - ~CopyConstructor() - { - while (n) - where[--n].~T(); - } - - T *const where; - size_t n; - } copier(this->end()); - - copier.copy(b, e); - this->size += (e - b); + CopyConstructor copier(this->end()); + this->size += copier.move(b, e); } void copyAppend(size_t n, parameter_type t) @@ -341,6 +481,8 @@ struct QGenericArrayOps Q_ASSERT(e <= where || b > this->end() || where == this->end()); // No overlap or append Q_ASSERT(size_t(e - b) <= this->allocatedCapacity() - this->size); + typedef typename QArrayExceptionSafetyPrimitives::template Destructor Destructor; + // Array may be truncated at where in case of exceptions T *const end = this->end(); @@ -349,28 +491,7 @@ struct QGenericArrayOps const T *const step1End = where + qMax(e - b, end - where); - struct Destructor - { - Destructor(T *&it) - : iter(&it) - , end(it) - { - } - - void commit() - { - iter = &end; - } - - ~Destructor() - { - for (; *iter != end; --*iter) - (*iter)->~T(); - } - - T **iter; - T *end; - } destroyer(writeIter); + Destructor destroyer(writeIter); // Construct new elements in array while (writeIter != step1End) { @@ -404,6 +525,8 @@ struct QGenericArrayOps Q_ASSERT(where >= this->begin() && where <= this->end()); Q_ASSERT(this->allocatedCapacity() - this->size >= n); + typedef typename QArrayExceptionSafetyPrimitives::template Destructor Destructor; + // Array may be truncated at where in case of exceptions T *const end = this->end(); const T *readIter = end; @@ -411,28 +534,7 @@ struct QGenericArrayOps const T *const step1End = where + qMax(n, end - where); - struct Destructor - { - Destructor(T *&it) - : iter(&it) - , end(it) - { - } - - void commit() - { - iter = &end; - } - - ~Destructor() - { - for (; *iter != end; --*iter) - (*iter)->~T(); - } - - T **iter; - T *end; - } destroyer(writeIter); + Destructor destroyer(writeIter); // Construct new elements in array while (writeIter != step1End) { @@ -542,62 +644,17 @@ struct QMovableArrayOps Q_ASSERT(e <= where || b > this->end() || where == this->end()); // No overlap or append Q_ASSERT(size_t(e - b) <= this->allocatedCapacity() - this->size); + typedef typename QArrayExceptionSafetyPrimitives::Displacer ReversibleDisplace; + typedef typename QArrayExceptionSafetyPrimitives::Constructor CopyConstructor; + // Provides strong exception safety guarantee, // provided T::~T() nothrow - struct ReversibleDisplace - { - ReversibleDisplace(T *start, T *finish, size_t diff) - : begin(start) - , end(finish) - , displace(diff) - { - ::memmove(static_cast(begin + displace), static_cast(begin), - (end - begin) * sizeof(T)); - } - - void commit() { displace = 0; } - - ~ReversibleDisplace() - { - if (displace) - ::memmove(static_cast(begin), static_cast(begin + displace), - (end - begin) * sizeof(T)); - } - - T *const begin; - T *const end; - size_t displace; - - } displace(where, this->end(), size_t(e - b)); - - struct CopyConstructor - { - CopyConstructor(T *w) : where(w) {} - - void copy(const T *src, const T *const srcEnd) - { - n = 0; - for (; src != srcEnd; ++src) { - new (where + n) T(*src); - ++n; - } - n = 0; - } - - ~CopyConstructor() - { - while (n) - where[--n].~T(); - } - - T *const where; - size_t n; - } copier(where); - - copier.copy(b, e); + ReversibleDisplace displace(where, this->end(), e - b); + CopyConstructor copier(where); + const auto copiedSize = copier.copy(b, e); displace.commit(); - this->size += (e - b); + this->size += copiedSize; } void insert(T *where, size_t n, parameter_type t) @@ -606,62 +663,17 @@ struct QMovableArrayOps Q_ASSERT(where >= this->begin() && where <= this->end()); Q_ASSERT(this->allocatedCapacity() - this->size >= n); + typedef typename QArrayExceptionSafetyPrimitives::Displacer ReversibleDisplace; + typedef typename QArrayExceptionSafetyPrimitives::Constructor CopyConstructor; + // Provides strong exception safety guarantee, // provided T::~T() nothrow - struct ReversibleDisplace - { - ReversibleDisplace(T *start, T *finish, size_t diff) - : begin(start) - , end(finish) - , displace(diff) - { - ::memmove(static_cast(begin + displace), static_cast(begin), - (end - begin) * sizeof(T)); - } - - void commit() { displace = 0; } - - ~ReversibleDisplace() - { - if (displace) - ::memmove(static_cast(begin), static_cast(begin + displace), - (end - begin) * sizeof(T)); - } - - T *const begin; - T *const end; - size_t displace; - - } displace(where, this->end(), n); - - struct CopyConstructor - { - CopyConstructor(T *w) : where(w) {} - - void copy(size_t count, parameter_type proto) - { - n = 0; - while (count--) { - new (where + n) T(proto); - ++n; - } - n = 0; - } - - ~CopyConstructor() - { - while (n) - where[--n].~T(); - } - - T *const where; - size_t n; - } copier(where); - - copier.copy(n, t); + ReversibleDisplace displace(where, this->end(), qsizetype(n)); + CopyConstructor copier(where); + const auto copiedSize = copier.clone(n, t); displace.commit(); - this->size += int(n); + this->size += copiedSize; } // use moving insert @@ -674,27 +686,9 @@ struct QMovableArrayOps Q_ASSERT(b >= this->begin() && b < this->end()); Q_ASSERT(e > this->begin() && e <= this->end()); - struct Mover - { - Mover(T *&start, const T *finish, qsizetype &sz) - : destination(start) - , source(start) - , n(finish - start) - , size(sz) - { - } + typedef typename QArrayExceptionSafetyPrimitives::Mover Mover; - ~Mover() - { - ::memmove(static_cast(destination), static_cast(source), n * sizeof(T)); - size -= (source - destination); - } - - T *&destination; - const T *const source; - size_t n; - qsizetype &size; - } mover(e, this->end(), this->size); + Mover mover(e, static_cast(this->end()) - e, this->size); // destroy the elements we're erasing do { diff --git a/tests/auto/corelib/tools/qarraydata/CMakeLists.txt b/tests/auto/corelib/tools/qarraydata/CMakeLists.txt index 94c97b83275..fac007f3b52 100644 --- a/tests/auto/corelib/tools/qarraydata/CMakeLists.txt +++ b/tests/auto/corelib/tools/qarraydata/CMakeLists.txt @@ -5,6 +5,7 @@ ##################################################################### qt_add_test(tst_qarraydata + EXCEPTIONS SOURCES simplevector.h tst_qarraydata.cpp diff --git a/tests/auto/corelib/tools/qarraydata/qarraydata.pro b/tests/auto/corelib/tools/qarraydata/qarraydata.pro index ee3faa9ad7b..36708dfd59c 100644 --- a/tests/auto/corelib/tools/qarraydata/qarraydata.pro +++ b/tests/auto/corelib/tools/qarraydata/qarraydata.pro @@ -2,4 +2,4 @@ TARGET = tst_qarraydata SOURCES += $$PWD/tst_qarraydata.cpp HEADERS += $$PWD/simplevector.h QT = core testlib -CONFIG += testcase +CONFIG += testcase exceptions diff --git a/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp b/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp index 3f96d7e782a..2d17a170f8b 100644 --- a/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp +++ b/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. @@ -37,6 +37,7 @@ #include #include #include +#include // A wrapper for a test function. Calls a function, if it fails, reports failure #define RUN_TEST_FUNC(test, ...) \ @@ -75,6 +76,12 @@ private slots: void variadicLiterals(); void rValueReferences(); void grow(); +#ifndef QT_NO_EXCEPTIONS + void exceptionSafetyPrimitives_constructor(); + void exceptionSafetyPrimitives_destructor(); + void exceptionSafetyPrimitives_mover(); + void exceptionSafetyPrimitives_displacer(); +#endif }; template const T &const_(const T &t) { return t; } @@ -1807,5 +1814,608 @@ void tst_QArrayData::grow() } } +#ifndef QT_NO_EXCEPTIONS +struct ThrowingTypeWatcher +{ + std::vector destroyedIds; + bool watch = false; + void destroyed(int id) + { + if (watch) + destroyedIds.push_back(id); + } +}; +ThrowingTypeWatcher& throwingTypeWatcher() { static ThrowingTypeWatcher global; return global; } + +struct ThrowingType +{ + static unsigned int throwOnce; + static constexpr char throwString[] = "Requested to throw"; + void checkThrow() { + // deferred throw + if (throwOnce > 0) { + --throwOnce; + if (throwOnce == 0) { + throw std::runtime_error(throwString); + } + } + return; + } + int id = 0; + + ThrowingType(int val = 0) : id(val) + { + checkThrow(); + } + ThrowingType(const ThrowingType &other) : id(other.id) + { + checkThrow(); + } + ThrowingType& operator=(const ThrowingType &other) + { + id = other.id; + checkThrow(); + return *this; + } + ~ThrowingType() + { + throwingTypeWatcher().destroyed(id); // notify global watcher + id = -1; + } +}; +unsigned int ThrowingType::throwOnce = 0; +bool operator==(const ThrowingType &a, const ThrowingType &b) { + return a.id == b.id; +} +QT_BEGIN_NAMESPACE +Q_DECLARE_TYPEINFO(ThrowingType, Q_RELOCATABLE_TYPE); +QT_END_NAMESPACE + +template // T must be constructible from a single int parameter +static QArrayDataPointer createDataPointer(qsizetype capacity, qsizetype initSize) +{ + QArrayDataPointer adp(QTypedArrayData::allocate(capacity)); + adp->appendInitialize(initSize); + // assign unique values + int i = 0; + std::generate(adp.begin(), adp.end(), [&i] () { return T(i++); }); + return adp; +} + +void tst_QArrayData::exceptionSafetyPrimitives_constructor() +{ + using Prims = QtPrivate::QArrayExceptionSafetyPrimitives; + using Constructor = typename Prims::Constructor; + + struct WatcherScope + { + WatcherScope() { throwingTypeWatcher().watch = true; } + ~WatcherScope() + { + throwingTypeWatcher().watch = false; + throwingTypeWatcher().destroyedIds.clear(); + } + }; + + const auto doConstruction = [] (auto &dataPointer, auto where, auto op) { + Constructor ctor(where); + dataPointer.size += op(ctor); + }; + + // empty ranges + { + auto data = createDataPointer(20, 10); + const auto originalSize = data.size; + const std::array emptyRange{}; + + doConstruction(data, data.end(), [] (Constructor &ctor) { return ctor.create(0); }); + QCOMPARE(data.size, originalSize); + + doConstruction(data, data.end(), [&emptyRange] (Constructor &ctor) { + return ctor.copy(emptyRange.begin(), emptyRange.end()); + }); + QCOMPARE(data.size, originalSize); + + doConstruction(data, data.end(), [] (Constructor &ctor) { + return ctor.clone(0, ThrowingType(42)); + }); + QCOMPARE(data.size, originalSize); + + doConstruction(data, data.end(), [emptyRange] (Constructor &ctor) mutable { + return ctor.move(emptyRange.begin(), emptyRange.end()); + }); + QCOMPARE(data.size, originalSize); + } + + // successful create + { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + reference->appendInitialize(reference.size + 1); + + doConstruction(data, data.end(), [] (Constructor &ctor) { return ctor.create(1); }); + + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i], reference.data()[i]); + } + + // successful copy + { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + const std::array source = { + ThrowingType(42), ThrowingType(43), ThrowingType(44) + }; + reference->copyAppend(source.begin(), source.end()); + + doConstruction(data, data.end(), [&source] (Constructor &ctor) { + return ctor.copy(source.begin(), source.end()); + }); + + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i], reference.data()[i]); + + reference->copyAppend(2, source[0]); + + doConstruction(data, data.end(), [&source] (Constructor &ctor) { + return ctor.clone(2, source[0]); + }); + + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i], reference.data()[i]); + } + + // successful move + { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + const std::array source = { + ThrowingType(42), ThrowingType(43), ThrowingType(44) + }; + reference->copyAppend(source.begin(), source.end()); + + doConstruction(data, data.end(), [source] (Constructor &ctor) mutable { + return ctor.move(source.begin(), source.end()); + }); + + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i], reference.data()[i]); + } + + // failed create + { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + + for (uint throwOnNthConstruction : {1, 3}) { + WatcherScope scope; Q_UNUSED(scope); + try { + ThrowingType::throwOnce = throwOnNthConstruction; + doConstruction(data, data.end(), [] (Constructor &ctor) { + return ctor.create(5); + }); + } catch (const std::runtime_error &e) { + QCOMPARE(std::string(e.what()), ThrowingType::throwString); + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i], reference.data()[i]); + QCOMPARE(throwingTypeWatcher().destroyedIds.size(), (throwOnNthConstruction - 1)); + for (auto id : throwingTypeWatcher().destroyedIds) + QCOMPARE(id, 0); + } + } + } + + // failed copy + { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + const std::array source = { + ThrowingType(42), ThrowingType(43), ThrowingType(44), ThrowingType(170) + }; + + // copy range + for (uint throwOnNthConstruction : {1, 3}) { + WatcherScope scope; Q_UNUSED(scope); + try { + ThrowingType::throwOnce = throwOnNthConstruction; + doConstruction(data, data.end(), [&source] (Constructor &ctor) { + return ctor.copy(source.begin(), source.end()); + }); + } catch (const std::runtime_error &e) { + QCOMPARE(std::string(e.what()), ThrowingType::throwString); + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i], reference.data()[i]); + const auto destroyedSize = throwingTypeWatcher().destroyedIds.size(); + QCOMPARE(destroyedSize, (throwOnNthConstruction - 1)); + for (size_t i = 0; i < destroyedSize; ++i) + QCOMPARE(throwingTypeWatcher().destroyedIds[i], source[destroyedSize - i - 1]); + } + } + + // copy value + for (uint throwOnNthConstruction : {1, 3}) { + const ThrowingType value(512); + QVERIFY(QArrayDataPointer::pass_parameter_by_value == false); + WatcherScope scope; Q_UNUSED(scope); + try { + ThrowingType::throwOnce = throwOnNthConstruction; + doConstruction(data, data.end(), [&source, &value] (Constructor &ctor) { + return ctor.clone(5, value); + }); + } catch (const std::runtime_error &e) { + QCOMPARE(std::string(e.what()), ThrowingType::throwString); + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i], reference.data()[i]); + QCOMPARE(throwingTypeWatcher().destroyedIds.size(), (throwOnNthConstruction - 1)); + for (auto id : throwingTypeWatcher().destroyedIds) + QCOMPARE(id, 512); + } + } + } + + // failed move + { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + const std::array source = { + ThrowingType(42), ThrowingType(43), ThrowingType(44), ThrowingType(170) + }; + + for (uint throwOnNthConstruction : {1, 3}) { + WatcherScope scope; Q_UNUSED(scope); + try { + ThrowingType::throwOnce = throwOnNthConstruction; + doConstruction(data, data.end(), [source] (Constructor &ctor) mutable { + return ctor.move(source.begin(), source.end()); + }); + } catch (const std::runtime_error &e) { + QCOMPARE(std::string(e.what()), ThrowingType::throwString); + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i], reference.data()[i]); + const auto destroyedSize = throwingTypeWatcher().destroyedIds.size(); + QCOMPARE(destroyedSize, (throwOnNthConstruction - 1)); + for (size_t i = 0; i < destroyedSize; ++i) + QCOMPARE(throwingTypeWatcher().destroyedIds[i], source[destroyedSize - i - 1]); + } + } + } +} + +void tst_QArrayData::exceptionSafetyPrimitives_destructor() +{ + using Prims = QtPrivate::QArrayExceptionSafetyPrimitives; + using Destructor = typename Prims::Destructor<>; + + struct WatcherScope + { + WatcherScope() { throwingTypeWatcher().watch = true; } + ~WatcherScope() + { + throwingTypeWatcher().watch = false; + throwingTypeWatcher().destroyedIds.clear(); + } + }; + + // successful operation with no rollback, elements added from left to right + { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + reference->insert(reference.end(), 2, ThrowingType(42)); + + WatcherScope scope; Q_UNUSED(scope); + { + auto where = data.end() - 1; + Destructor destroyer(where); + for (int i = 0; i < 2; ++i) { + new (where + 1) ThrowingType(42); + ++where; + ++data.size; + } + destroyer.commit(); + } + + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i], reference.data()[i]); + QVERIFY(throwingTypeWatcher().destroyedIds.size() == 0); + } + + // failed operation with rollback, elements added from left to right + { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + + WatcherScope scope; Q_UNUSED(scope); + try { + auto where = data.end() - 1; + Destructor destroyer(where); + for (int i = 0; i < 2; ++i) { + new (where + 1) ThrowingType(42 + i); + ++where; + ThrowingType::throwOnce = 1; + } + QFAIL("Unreachable line!"); + destroyer.commit(); + } catch (const std::runtime_error &e) { + QCOMPARE(std::string(e.what()), ThrowingType::throwString); + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i], reference.data()[i]); + QVERIFY(throwingTypeWatcher().destroyedIds.size() == 1); + QCOMPARE(throwingTypeWatcher().destroyedIds[0], 42); + } + } + + // successful operation with no rollback, elements added from right to left + { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + reference->erase(reference.begin(), reference.begin() + 2); + reference->insert(reference.begin(), 2, ThrowingType(42)); + + data.begin()->~ThrowingType(); + data.begin()->~ThrowingType(); + data.size -= 2; + WatcherScope scope; Q_UNUSED(scope); + { + auto where = data.begin() + 2; // Note: not updated data ptr, so begin + 2 + Destructor destroyer(where); + for (int i = 0; i < 2; ++i) { + new (where - 1) ThrowingType(42); + --where; + ++data.size; + } + destroyer.commit(); + } + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i], reference.data()[i]); + QVERIFY(throwingTypeWatcher().destroyedIds.size() == 0); + } + + // failed operation with rollback, elements added from right to left + { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + reference->erase(reference.begin(), reference.begin() + 2); + + data.begin()->~ThrowingType(); + data.begin()->~ThrowingType(); + data.size -= 2; + WatcherScope scope; Q_UNUSED(scope); + try { + auto where = data.begin() + 2; // Note: not updated data ptr, so begin + 2 + Destructor destroyer(where); + for (int i = 0; i < 2; ++i) { + new (where - 1) ThrowingType(42 + i); + --where; + ThrowingType::throwOnce = 1; + } + QFAIL("Unreachable line!"); + destroyer.commit(); + } catch (const std::runtime_error &e) { + QCOMPARE(std::string(e.what()), ThrowingType::throwString); + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i + 2], reference.data()[i]); + QVERIFY(throwingTypeWatcher().destroyedIds.size() == 1); + QCOMPARE(throwingTypeWatcher().destroyedIds[0], 42); + } + } + + // extra: the very first operation throws - destructor has to do nothing, + // since nothing is properly constructed + { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + + WatcherScope scope; Q_UNUSED(scope); + try { + auto where = data.end() - 1; + Destructor destroyer(where); + ThrowingType::throwOnce = 1; + new (where + 1) ThrowingType(42); + ++where; + QFAIL("Unreachable line!"); + destroyer.commit(); + } catch (const std::runtime_error &e) { + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i], reference.data()[i]); + QVERIFY(throwingTypeWatcher().destroyedIds.size() == 0); + } + } + + // extra: special case when iterator is intentionally out of bounds: this is + // to cover the case when we work on the uninitialized memory region instead + // of being near the border + { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + reference->erase(reference.begin(), reference.begin() + 2); + + data.begin()->~ThrowingType(); + data.begin()->~ThrowingType(); + data.size -= 2; + WatcherScope scope; Q_UNUSED(scope); + try { + auto where = data.begin() - 1; // Note: intentionally out of range + Destructor destroyer(where); + for (int i = 0; i < 2; ++i) { + new (where + 1) ThrowingType(42); + ++where; + ThrowingType::throwOnce = 1; + } + QFAIL("Unreachable line!"); + destroyer.commit(); + } catch (const std::runtime_error &e) { + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i + 2], reference.data()[i]); + QVERIFY(throwingTypeWatcher().destroyedIds.size() == 1); + QVERIFY(throwingTypeWatcher().destroyedIds[0] == 42); + } + } +} + +void tst_QArrayData::exceptionSafetyPrimitives_mover() +{ + QVERIFY(QTypeInfo::isRelocatable); + using Prims = QtPrivate::QArrayExceptionSafetyPrimitives; + using Mover = typename Prims::Mover; + + const auto testMoveLeft = [] (size_t posB, size_t posE) { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + + ThrowingType *b = data.begin() + posB; + ThrowingType *e = data.begin() + posE; + const auto originalSize = data.size; + const auto length = std::distance(b, e); + { + Mover mover(e, static_cast(data.end()) - e, data.size); + while (e != b) + (--e)->~ThrowingType(); + } + QCOMPARE(data.size + length, originalSize); + qsizetype i = 0; + for (; i < std::distance(static_cast(data.begin()), b); ++i) + QCOMPARE(data.data()[i], reference.data()[i]); + for (; i < data.size; ++i) + QCOMPARE(data.data()[i], reference.data()[i + length]); + }; + + const auto testMoveRight = [] (size_t posB, size_t posE) { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + + ThrowingType *begin = data.begin(); + ThrowingType *b = data.begin() + posB; + ThrowingType *e = data.begin() + posE; + const auto originalSize = data.size; + const auto length = std::distance(b, e); + { + Mover mover(begin, b - static_cast(data.begin()), data.size); + while (b != e) { + ++begin; + (b++)->~ThrowingType(); + } + } + QCOMPARE(data.size + length, originalSize); + + // restore original data size + { + for (qsizetype i = 0; i < length; ++i) { + new (static_cast(data.begin() + i)) ThrowingType(42); + ++data.size; + } + } + + qsizetype i = length; + for (; i < std::distance(static_cast(data.begin()), e); ++i) + QCOMPARE(data.data()[i], reference.data()[i - length]); + for (; i < data.size; ++i) + QCOMPARE(data.data()[i], reference.data()[i]); + }; + + // normal move left + RUN_TEST_FUNC(testMoveLeft, 2, 4); + // no move left + RUN_TEST_FUNC(testMoveLeft, 2, 2); + // normal move right + RUN_TEST_FUNC(testMoveRight, 3, 5); + // no move right + RUN_TEST_FUNC(testMoveRight, 4, 4); +} + +void tst_QArrayData::exceptionSafetyPrimitives_displacer() +{ + QVERIFY(QTypeInfo::isRelocatable); + using Prims = QtPrivate::QArrayExceptionSafetyPrimitives; + const auto doDisplace = [] (auto &dataPointer, auto start, auto finish, qsizetype diff) { + typename Prims::Displacer displace(start, finish, diff); + new (start) ThrowingType(42); + ++dataPointer.size; + displace.commit(); + }; + + // successful operation with displace to the right + { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + reference->insert(reference.end() - 1, 1, ThrowingType(42)); + + auto where = data.end() - 1; + doDisplace(data, where, data.end(), 1); + + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i], reference.data()[i]); + } + + // failed operation with displace to the right + { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + try { + ThrowingType::throwOnce = 1; + doDisplace(data, data.end() - 1, data.end(), 1); + QFAIL("Unreachable line!"); + } catch (const std::exception &e) { + QCOMPARE(std::string(e.what()), ThrowingType::throwString); + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i], reference.data()[i]); + } + } + + // successful operation with displace to the left + { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + reference.data()[0] = reference.data()[1]; + reference.data()[1] = ThrowingType(42); + + data.begin()->~ThrowingType(); // free space at begin + --data.size; + auto where = data.begin() + 1; + doDisplace(data, where, where + 1, -1); + + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i], reference.data()[i]); + } + + // failed operation with displace to the left + { + auto data = createDataPointer(20, 10); + auto reference = createDataPointer(20, 10); + reference->erase(reference.begin(), reference.begin() + 1); + + try { + data.begin()->~ThrowingType(); // free space at begin + --data.size; + ThrowingType::throwOnce = 1; + auto where = data.begin() + 1; + doDisplace(data, where, where + 1, -1); + QFAIL("Unreachable line!"); + } catch (const std::exception &e) { + QCOMPARE(std::string(e.what()), ThrowingType::throwString); + QCOMPARE(data.size, reference.size); + for (qsizetype i = 0; i < data.size; ++i) + QCOMPARE(data.data()[i + 1], reference.data()[i]); + } + } +} +#endif // QT_NO_EXCEPTIONS + QTEST_APPLESS_MAIN(tst_QArrayData) #include "tst_qarraydata.moc"