Separate exception safety primitives from operations

Refactored certain bits of qarraydataops.h: picked exception-related
building blocks and put them into one place, (somewhat) documented
the usage, added tests

Personally, the existing code seemed rather complicated to analyze
(and do mental experiments for corner cases), especially when staring
at the whole thing for a while or "returning back" from some other work
and I still have my doubts that everything works correctly. Testing the
building blocks that are used should:
a) increase trust into existing code (provided the usage is correct)
b) give more use cases of how to use the building blocks, which in turn
   would allow to compare and contrast tests vs implementation

Task-number: QTBUG-84320
Change-Id: I313a1d1817577507fe07a5b9b7d2c90b0969b490
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Andrei Golubev 2020-07-09 17:53:48 +03:00
parent 01a03a02f9
commit 56f1208f9e
4 changed files with 802 additions and 197 deletions

View File

@ -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 <new>
#include <string.h>
#include <utility>
#include <iterator>
QT_BEGIN_NAMESPACE
@ -53,6 +55,166 @@ template <class T> 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<typename T>
struct QArrayExceptionSafetyPrimitives
{
using parameter_type = typename QArrayDataPointer<T>::parameter_type;
using iterator = typename QArrayDataPointer<T>::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<typename It>
using iterator_copy_value = decltype(*std::declval<It>());
template<typename It>
using iterator_move_value = decltype(std::move(*std::declval<It>()));
Constructor(T *w) noexcept : where(w) {}
qsizetype create(size_t size) noexcept(std::is_nothrow_default_constructible_v<T>)
{
n = 0;
while (n != size) {
new (where + n) T;
++n;
}
return qsizetype(std::exchange(n, 0));
}
template<typename ForwardIt>
qsizetype copy(ForwardIt first, ForwardIt last)
noexcept(std::is_nothrow_constructible_v<T, iterator_copy_value<ForwardIt>>)
{
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<T, parameter_type>)
{
n = 0;
while (n != size) {
new (where + n) T(t);
++n;
}
return qsizetype(std::exchange(n, 0));
}
template<typename ForwardIt>
qsizetype move(ForwardIt first, ForwardIt last)
noexcept(std::is_nothrow_constructible_v<T, iterator_move_value<ForwardIt>>)
{
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<T>)
{
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<typename It = iterator>
struct Destructor
{
It *iter;
It end;
Destructor(It &it) noexcept(std::is_nothrow_copy_constructible_v<It>)
: iter(std::addressof(it)), end(it)
{ }
void commit() noexcept
{
iter = std::addressof(end);
}
~Destructor() noexcept(std::is_nothrow_destructible_v<T>)
{
// 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<T>::isRelocatable, "Type must be relocatable");
Displacer(T *start, T *finish, qsizetype diff) noexcept
: begin(start), end(finish), displace(diff)
{
::memmove(static_cast<void *>(begin + displace), static_cast<void *>(begin),
(end - begin) * sizeof(T));
}
void commit() noexcept { displace = 0; }
~Displacer() noexcept
{
if (displace)
::memmove(static_cast<void *>(begin), static_cast<void *>(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<T>::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<void *>(destination), static_cast<const void *>(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<T>::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<T>::template Destructor<T *> 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<T>::template Destructor<T *> 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<size_t>(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<T>::Displacer ReversibleDisplace;
typedef typename QArrayExceptionSafetyPrimitives<T>::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<void *>(begin + displace), static_cast<void *>(begin),
(end - begin) * sizeof(T));
}
void commit() { displace = 0; }
~ReversibleDisplace()
{
if (displace)
::memmove(static_cast<void *>(begin), static_cast<void *>(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<T>::Displacer ReversibleDisplace;
typedef typename QArrayExceptionSafetyPrimitives<T>::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<void *>(begin + displace), static_cast<void *>(begin),
(end - begin) * sizeof(T));
}
void commit() { displace = 0; }
~ReversibleDisplace()
{
if (displace)
::memmove(static_cast<void *>(begin), static_cast<void *>(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<T>::Mover Mover;
~Mover()
{
::memmove(static_cast<void *>(destination), static_cast<const void *>(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<const T *>(this->end()) - e, this->size);
// destroy the elements we're erasing
do {

View File

@ -5,6 +5,7 @@
#####################################################################
qt_add_test(tst_qarraydata
EXCEPTIONS
SOURCES
simplevector.h
tst_qarraydata.cpp

View File

@ -2,4 +2,4 @@ TARGET = tst_qarraydata
SOURCES += $$PWD/tst_qarraydata.cpp
HEADERS += $$PWD/simplevector.h
QT = core testlib
CONFIG += testcase
CONFIG += testcase exceptions

View File

@ -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 <tuple>
#include <algorithm>
#include <vector>
#include <stdexcept>
// 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 <class T> const T &const_(const T &t) { return t; }
@ -1807,5 +1814,608 @@ void tst_QArrayData::grow()
}
}
#ifndef QT_NO_EXCEPTIONS
struct ThrowingTypeWatcher
{
std::vector<int> 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<typename T> // T must be constructible from a single int parameter
static QArrayDataPointer<T> createDataPointer(qsizetype capacity, qsizetype initSize)
{
QArrayDataPointer<T> adp(QTypedArrayData<T>::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<ThrowingType>;
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<ThrowingType>(20, 10);
const auto originalSize = data.size;
const std::array<ThrowingType, 0> 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<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(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<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(20, 10);
const std::array<ThrowingType, 3> 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<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(20, 10);
const std::array<ThrowingType, 3> 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<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(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<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(20, 10);
const std::array<ThrowingType, 4> 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<ThrowingType>::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<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(20, 10);
const std::array<ThrowingType, 4> 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<ThrowingType>;
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<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(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<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(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<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(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<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(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<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(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<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(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<ThrowingType>::isRelocatable);
using Prims = QtPrivate::QArrayExceptionSafetyPrimitives<ThrowingType>;
using Mover = typename Prims::Mover;
const auto testMoveLeft = [] (size_t posB, size_t posE) {
auto data = createDataPointer<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(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<ThrowingType *>(data.end()) - e, data.size);
while (e != b)
(--e)->~ThrowingType();
}
QCOMPARE(data.size + length, originalSize);
qsizetype i = 0;
for (; i < std::distance(static_cast<ThrowingType *>(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<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(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<ThrowingType *>(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<ThrowingType *>(data.begin() + i)) ThrowingType(42);
++data.size;
}
}
qsizetype i = length;
for (; i < std::distance(static_cast<ThrowingType *>(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<ThrowingType>::isRelocatable);
using Prims = QtPrivate::QArrayExceptionSafetyPrimitives<ThrowingType>;
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<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(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<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(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<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(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<ThrowingType>(20, 10);
auto reference = createDataPointer<ThrowingType>(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"