From 4bf8e82d413815cd8ef076e7a5c65d0651a0e27a Mon Sep 17 00:00:00 2001 From: Andrei Golubev Date: Mon, 13 Jul 2020 12:26:36 +0300 Subject: [PATCH] Add QArrayDataPointer::freeSpace*() functions Added functions that tell how much free space is available at the beginning and at the end of the storage Updated preconditions of operations to use freeSpace* functions Also, changed casts uint(this->size) to size_t(this->size) Task-number: QTBUG-84320 Change-Id: Iad94c1060a00f62068da9d1327e332a00d4f4109 Reviewed-by: Sona Kurazyan --- src/corelib/tools/qarraydata.cpp | 9 ++--- src/corelib/tools/qarraydata.h | 11 ++++- src/corelib/tools/qarraydataops.h | 40 +++++++++---------- src/corelib/tools/qarraydatapointer.h | 16 +++++++- .../tools/qarraydata/tst_qarraydata.cpp | 39 ++++++++++++++++++ 5 files changed, 88 insertions(+), 27 deletions(-) diff --git a/src/corelib/tools/qarraydata.cpp b/src/corelib/tools/qarraydata.cpp index f46204106d2..cea41dbaff4 100644 --- a/src/corelib/tools/qarraydata.cpp +++ b/src/corelib/tools/qarraydata.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Copyright (C) 2016 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** @@ -191,16 +191,15 @@ void *QArrayData::allocate(QArrayData **dptr, qsizetype objectSize, qsizetype al qsizetype allocSize = calculateBlockSize(capacity, objectSize, headerSize, options); QArrayData *header = allocateData(allocSize, options); - quintptr data = 0; + void *data = nullptr; if (header) { // find where offset should point to so that data() is aligned to alignment bytes - data = (quintptr(header) + sizeof(QArrayData) + alignment - 1) - & ~(alignment - 1); + data = QTypedArrayData::dataStart(header, alignment); header->alloc = qsizetype(capacity); } *dptr = header; - return reinterpret_cast(data); + return data; } QPair diff --git a/src/corelib/tools/qarraydata.h b/src/corelib/tools/qarraydata.h index 0226fa4c925..856a9521ad2 100644 --- a/src/corelib/tools/qarraydata.h +++ b/src/corelib/tools/qarraydata.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Copyright (C) 2019 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** @@ -235,6 +235,15 @@ struct QTypedArrayData static_assert(sizeof(QTypedArrayData) == sizeof(QArrayData)); QArrayData::deallocate(data, sizeof(T), alignof(AlignmentDummy)); } + + static T *dataStart(QArrayData *data, qsizetype alignment) noexcept + { + // Alignment is a power of two + Q_ASSERT(alignment >= qsizetype(alignof(QArrayData)) && !(alignment & (alignment - 1))); + void *start = reinterpret_cast( + (quintptr(data) + sizeof(QArrayData) + alignment - 1) & ~(alignment - 1)); + return static_cast(start); + } }; namespace QtPrivate { diff --git a/src/corelib/tools/qarraydataops.h b/src/corelib/tools/qarraydataops.h index 57440703e5f..c4b24ebd94a 100644 --- a/src/corelib/tools/qarraydataops.h +++ b/src/corelib/tools/qarraydataops.h @@ -233,11 +233,11 @@ public: { Q_ASSERT(this->isMutable()); Q_ASSERT(!this->isShared()); - Q_ASSERT(newSize > uint(this->size)); - Q_ASSERT(newSize <= this->allocatedCapacity()); + Q_ASSERT(newSize > size_t(this->size)); + Q_ASSERT(newSize - this->size <= size_t(this->freeSpaceAtEnd())); ::memset(static_cast(this->end()), 0, (newSize - this->size) * sizeof(T)); - this->size = int(newSize); + this->size = qsizetype(newSize); } template @@ -245,7 +245,7 @@ public: { Q_ASSERT(this->isMutable() || b == e); Q_ASSERT(!this->isShared() || b == e); - Q_ASSERT(std::distance(b, e) >= 0 && size_t(std::distance(b, e)) <= this->allocatedCapacity() - this->size); + Q_ASSERT(std::distance(b, e) >= 0 && qsizetype(std::distance(b, e)) <= this->freeSpaceAtEnd()); T *iter = this->end(); for (; b != e; ++iter, ++b) { @@ -272,7 +272,7 @@ public: Q_ASSERT(!this->isShared()); Q_ASSERT(newSize < size_t(this->size)); - this->size = int(newSize); + this->size = qsizetype(newSize); } void destroyAll() // Call from destructors, ONLY! @@ -291,7 +291,7 @@ public: Q_ASSERT(where >= this->begin() && where <= this->end()); Q_ASSERT(b <= e); Q_ASSERT(e <= where || b > this->end() || where == this->end()); // No overlap or append - Q_ASSERT(size_t(e - b) <= this->allocatedCapacity() - this->size); + Q_ASSERT((e - b) <= this->freeSpaceAtEnd()); ::memmove(static_cast(where + (e - b)), static_cast(where), (static_cast(this->end()) - where) * sizeof(T)); @@ -303,11 +303,11 @@ public: { Q_ASSERT(!this->isShared() || (n == 0 && where == this->end())); Q_ASSERT(where >= this->begin() && where <= this->end()); - Q_ASSERT(this->allocatedCapacity() - this->size >= n); + Q_ASSERT(size_t(this->freeSpaceAtEnd()) >= n); ::memmove(static_cast(where + n), static_cast(where), (static_cast(this->end()) - where) * sizeof(T)); - this->size += int(n); // PODs can't throw on copy + this->size += qsizetype(n); // PODs can't throw on copy while (n--) *where++ = t; } @@ -320,7 +320,7 @@ public: { Q_ASSERT(!this->isShared()); Q_ASSERT(where >= this->begin() && where <= this->end()); - Q_ASSERT(this->allocatedCapacity() - this->size >= 1); + Q_ASSERT(this->freeSpaceAtEnd() >= 1); if (where == this->end()) { new (this->end()) T(std::forward(args)...); @@ -394,13 +394,13 @@ struct QGenericArrayOps { Q_ASSERT(this->isMutable()); Q_ASSERT(!this->isShared()); - Q_ASSERT(newSize > uint(this->size)); - Q_ASSERT(newSize <= this->allocatedCapacity()); + Q_ASSERT(newSize > size_t(this->size)); + Q_ASSERT(newSize - this->size <= size_t(this->freeSpaceAtEnd())); T *const b = this->begin(); do { new (b + this->size) T; - } while (uint(++this->size) != newSize); + } while (size_t(++this->size) != newSize); } template @@ -408,7 +408,7 @@ struct QGenericArrayOps { Q_ASSERT(this->isMutable() || b == e); Q_ASSERT(!this->isShared() || b == e); - Q_ASSERT(std::distance(b, e) >= 0 && size_t(std::distance(b, e)) <= this->allocatedCapacity() - this->size); + Q_ASSERT(std::distance(b, e) >= 0 && qsizetype(std::distance(b, e)) <= this->freeSpaceAtEnd()); T *iter = this->end(); for (; b != e; ++iter, ++b) { @@ -425,7 +425,7 @@ struct QGenericArrayOps Q_ASSERT(this->isMutable() || b == e); Q_ASSERT(!this->isShared() || b == e); Q_ASSERT(b <= e); - Q_ASSERT(size_t(e - b) <= this->allocatedCapacity() - this->size); + Q_ASSERT((e - b) <= this->freeSpaceAtEnd()); typedef typename QArrayExceptionSafetyPrimitives::Constructor CopyConstructor; @@ -454,7 +454,7 @@ struct QGenericArrayOps const T *const b = this->begin(); do { (b + --this->size)->~T(); - } while (uint(this->size) != newSize); + } while (size_t(this->size) != newSize); } void destroyAll() // Call from destructors, ONLY @@ -479,7 +479,7 @@ struct QGenericArrayOps Q_ASSERT(where >= this->begin() && where <= this->end()); Q_ASSERT(b <= e); Q_ASSERT(e <= where || b > this->end() || where == this->end()); // No overlap or append - Q_ASSERT(size_t(e - b) <= this->allocatedCapacity() - this->size); + Q_ASSERT((e - b) <= this->freeSpaceAtEnd()); typedef typename QArrayExceptionSafetyPrimitives::template Destructor Destructor; @@ -527,7 +527,7 @@ struct QGenericArrayOps { Q_ASSERT(!this->isShared() || (n == 0 && where == this->end())); Q_ASSERT(where >= this->begin() && where <= this->end()); - Q_ASSERT(this->allocatedCapacity() - this->size >= n); + Q_ASSERT(size_t(this->freeSpaceAtEnd()) >= n); typedef typename QArrayExceptionSafetyPrimitives::template Destructor Destructor; @@ -578,7 +578,7 @@ struct QGenericArrayOps { Q_ASSERT(!this->isShared()); Q_ASSERT(where >= this->begin() && where <= this->end()); - Q_ASSERT(this->allocatedCapacity() - this->size >= 1); + Q_ASSERT(this->freeSpaceAtEnd() >= 1); createInPlace(this->end(), std::forward(args)...); ++this->size; @@ -651,7 +651,7 @@ struct QMovableArrayOps Q_ASSERT(where >= this->begin() && where <= this->end()); Q_ASSERT(b <= e); Q_ASSERT(e <= where || b > this->end() || where == this->end()); // No overlap or append - Q_ASSERT(size_t(e - b) <= this->allocatedCapacity() - this->size); + Q_ASSERT((e - b) <= this->freeSpaceAtEnd()); typedef typename QArrayExceptionSafetyPrimitives::Displacer ReversibleDisplace; typedef typename QArrayExceptionSafetyPrimitives::Constructor CopyConstructor; @@ -670,7 +670,7 @@ struct QMovableArrayOps { Q_ASSERT(!this->isShared() || (n == 0 && where == this->end())); Q_ASSERT(where >= this->begin() && where <= this->end()); - Q_ASSERT(this->allocatedCapacity() - this->size >= n); + Q_ASSERT(size_t(this->freeSpaceAtEnd()) >= n); typedef typename QArrayExceptionSafetyPrimitives::Displacer ReversibleDisplace; typedef typename QArrayExceptionSafetyPrimitives::Constructor CopyConstructor; diff --git a/src/corelib/tools/qarraydatapointer.h b/src/corelib/tools/qarraydatapointer.h index bb24f3e6f2c..c1d27e4b17a 100644 --- a/src/corelib/tools/qarraydatapointer.h +++ b/src/corelib/tools/qarraydatapointer.h @@ -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 QtCore module of the Qt Toolkit. @@ -194,6 +194,20 @@ public: Data *d_ptr() noexcept { return d; } + qsizetype freeSpaceAtBegin() const noexcept + { + if (d == nullptr) + return 0; + return this->ptr - Data::dataStart(d, alignof(typename Data::AlignmentDummy)); + } + + qsizetype freeSpaceAtEnd() const noexcept + { + if (d == nullptr) + return 0; + return d->constAllocatedCapacity() - freeSpaceAtBegin() - this->size; + } + private: Q_REQUIRED_RESULT QPair clone(QArrayData::ArrayOptions options) const { diff --git a/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp b/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp index 2d17a170f8b..efec562ba96 100644 --- a/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp +++ b/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp @@ -76,6 +76,8 @@ private slots: void variadicLiterals(); void rValueReferences(); void grow(); + void freeSpace_data(); + void freeSpace(); #ifndef QT_NO_EXCEPTIONS void exceptionSafetyPrimitives_constructor(); void exceptionSafetyPrimitives_destructor(); @@ -1814,6 +1816,43 @@ void tst_QArrayData::grow() } } +void tst_QArrayData::freeSpace_data() +{ + QTest::addColumn("allocationOptions"); + QTest::addColumn("n"); + + for (const size_t n : {1, 3, 5, 7, 16, 25}) { + QString suffix = QString::number(n) + QLatin1String("-elements"); + QTest::newRow(qPrintable(QLatin1String("default-alloc-") + suffix)) + << QArrayData::ArrayOptions(QArrayData::DefaultAllocationFlags) << n; + QTest::newRow(qPrintable(QLatin1String("grows-forward-") + suffix)) + << QArrayData::ArrayOptions(QArrayData::GrowsForward) << n; + QTest::newRow(qPrintable(QLatin1String("grows-bidirectional-") + suffix)) + << QArrayData::ArrayOptions(QArrayData::GrowsForward | QArrayData::GrowsBackwards) << n; + } +} + +void tst_QArrayData::freeSpace() +{ + QFETCH(QArrayData::ArrayOptions, allocationOptions); + QFETCH(size_t, n); + const auto testFreeSpace = [] (auto dummy, auto options, size_t n) { + using Type = std::decay_t; + using Data = QTypedArrayData; + using DataPointer = QArrayDataPointer; + Q_UNUSED(dummy); + DataPointer ptr(Data::allocate(n, options)); + const auto alloc = qsizetype(ptr.constAllocatedCapacity()); + QVERIFY(size_t(alloc) >= n); + QCOMPARE(ptr.freeSpaceAtBegin() + ptr.freeSpaceAtEnd(), alloc); + }; + RUN_TEST_FUNC(testFreeSpace, char(0), allocationOptions, n); + RUN_TEST_FUNC(testFreeSpace, char16_t(0), allocationOptions, n); + RUN_TEST_FUNC(testFreeSpace, int(0), allocationOptions, n); + RUN_TEST_FUNC(testFreeSpace, QString(), allocationOptions, n); + RUN_TEST_FUNC(testFreeSpace, CountedObject(), allocationOptions, n); +} + #ifndef QT_NO_EXCEPTIONS struct ThrowingTypeWatcher {