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:
parent
01a03a02f9
commit
56f1208f9e
@ -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 {
|
||||
|
@ -5,6 +5,7 @@
|
||||
#####################################################################
|
||||
|
||||
qt_add_test(tst_qarraydata
|
||||
EXCEPTIONS
|
||||
SOURCES
|
||||
simplevector.h
|
||||
tst_qarraydata.cpp
|
||||
|
@ -2,4 +2,4 @@ TARGET = tst_qarraydata
|
||||
SOURCES += $$PWD/tst_qarraydata.cpp
|
||||
HEADERS += $$PWD/simplevector.h
|
||||
QT = core testlib
|
||||
CONFIG += testcase
|
||||
CONFIG += testcase exceptions
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user