Containers: add max_size()

One more method for STL compatibility.
This one is particularly subtle as it's required by the
`reservable-container` concept:

https://eel.is/c++draft/ranges#range.utility.conv.general-3

Without this concept, ranges::to won't reserve() before copying the
elements (out of a sized range which isn't a common_range).

Implementation notes: there were already a couple of constants denoting
the maximum QByteArray and QString size. Centralize that implementation
in QTypedArrayData, so that QList can use it too.

The maximum allocation size (private constant) needs a even more central
place so that even QVLA can use it. Lacking anything better, I've put it
in qcontainerfwd.h.

Since our containers aren't allocator-aware, I can make max_size() a
static member, and replace the existing constants throughout the rest of
qtbase. (I can't kill them yet as they're used by other submodules.)

[ChangeLog][QtCore][QList] Added max_size().

[ChangeLog][QtCore][QString] Added max_size().

[ChangeLog][QtCore][QByteArray] Added max_size().

[ChangeLog][QtCore][QVarLengthArray] Added max_size().

Change-Id: I176142e31b998f4f787c96333894b8f6653eb70d
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
bb10
Giuseppe D'Angelo 2024-02-19 13:05:54 +01:00
parent 476d2a7392
commit 7ce6920aac
20 changed files with 96 additions and 39 deletions

View File

@ -9,7 +9,6 @@
#include "qfile.h"
#include "qstringlist.h"
#include "qdir.h"
#include "private/qbytearray_p.h"
#include "private/qtools_p.h"
#include <algorithm>
@ -89,9 +88,9 @@ static void checkWarnMessage(const QIODevice *device, const char *function, cons
#define CHECK_MAXBYTEARRAYSIZE(function) \
do { \
if (maxSize >= MaxByteArraySize) { \
if (maxSize >= QByteArray::max_size()) { \
checkWarnMessage(this, #function, "maxSize argument exceeds QByteArray size limit"); \
maxSize = MaxByteArraySize - 1; \
maxSize = QByteArray::max_size() - 1; \
} \
} while (0)
@ -1243,7 +1242,7 @@ QByteArray QIODevice::readAll()
: d->buffer.size());
qint64 readResult;
do {
if (readBytes + readChunkSize >= MaxByteArraySize) {
if (readBytes + readChunkSize >= QByteArray::max_size()) {
// If resize would fail, don't read more, return what we have.
break;
}
@ -1257,8 +1256,8 @@ QByteArray QIODevice::readAll()
} else {
// Read it all in one go.
readBytes -= d->pos;
if (readBytes >= MaxByteArraySize)
readBytes = MaxByteArraySize;
if (readBytes >= QByteArray::max_size())
readBytes = QByteArray::max_size();
result.resize(readBytes);
readBytes = d->read(result.data(), readBytes);
}
@ -1449,7 +1448,7 @@ QByteArray QIODevice::readLine(qint64 maxSize)
qint64 readBytes = 0;
if (maxSize == 0) {
// Size is unknown, read incrementally.
maxSize = MaxByteArraySize - 1;
maxSize = QByteArray::max_size() - 1;
// The first iteration needs to leave an extra byte for the terminating null
result.resize(1);

View File

@ -6,7 +6,6 @@
#define CBOR_NO_ENCODER_API
#include <private/qcborcommon_p.h>
#include <private/qbytearray_p.h>
#include <private/qnumeric_p.h>
#include <private/qstringconverter_p.h>
#include <qiodevice.h>
@ -1752,7 +1751,7 @@ QCborStreamReaderPrivate::readStringChunk_byte(ReadStringChunk params, qsizetype
// the distinction between DataTooLarge and OOM is mostly for
// compatibility with Qt 5; in Qt 6, we could consider everything
// to be OOM.
handleError(newSize > MaxByteArraySize ? CborErrorDataTooLarge: CborErrorOutOfMemory);
handleError(newSize > QByteArray::max_size() ? CborErrorDataTooLarge: CborErrorOutOfMemory);
return -1;
}
@ -1791,7 +1790,7 @@ QCborStreamReaderPrivate::readStringChunk_unicode(ReadStringChunk params, qsizet
// conversion uses the same number of words or less.
qsizetype currentSize = params.string->size();
size_t newSize = size_t(utf8len) + size_t(currentSize); // can't overflow
if (utf8len > MaxStringSize || qsizetype(newSize) < 0) {
if (utf8len > QString::max_size() || qsizetype(newSize) < 0) {
handleError(CborErrorDataTooLarge);
return -1;
}

View File

@ -19,7 +19,6 @@
#include <qlocale.h>
#include <qdatetime.h>
#include <qtimezone.h>
#include <private/qbytearray_p.h>
#include <private/qnumeric_p.h>
#include <private/qsimd_p.h>
@ -1616,7 +1615,7 @@ void QCborContainerPrivate::decodeStringFromCbor(QCborStreamReader &reader)
// add space for aligned ByteData (this can't overflow)
offset += sizeof(QtCbor::ByteData) + alignof(QtCbor::ByteData);
offset &= ~(alignof(QtCbor::ByteData) - 1);
if (offset > size_t(MaxByteArraySize)) {
if (offset > size_t(QByteArray::max_size())) {
// overflow
setErrorInReader(reader, { QCborError::DataTooLarge });
return;
@ -1629,9 +1628,9 @@ void QCborContainerPrivate::decodeStringFromCbor(QCborStreamReader &reader)
// so capa how much we allocate
newCapacity = offset + MaxMemoryIncrement - EstimatedOverhead;
}
if (newCapacity > size_t(MaxByteArraySize)) {
if (newCapacity > size_t(QByteArray::max_size())) {
// this may cause an allocation failure
newCapacity = MaxByteArraySize;
newCapacity = QByteArray::max_size();
}
if (newCapacity > size_t(data.capacity()))
data.reserve(newCapacity);
@ -1682,7 +1681,7 @@ void QCborContainerPrivate::decodeStringFromCbor(QCborStreamReader &reader)
// check that this UTF-8 text string can be loaded onto a QString
if (e.type == QCborValue::String) {
if (Q_UNLIKELY(b->len > MaxStringSize)) {
if (Q_UNLIKELY(b->len > QString::max_size())) {
setErrorInReader(reader, { QCborError::DataTooLarge });
status = QCborStreamReader::Error;
}

View File

@ -14,7 +14,6 @@
#include "private/qsimd_p.h"
#include "qstringalgorithms_p.h"
#include "qscopedpointer.h"
#include "qbytearray_p.h"
#include "qstringconverter_p.h"
#include <qdatastream.h>
#include <qmath.h>
@ -780,7 +779,7 @@ QByteArray qUncompress(const uchar* data, qsizetype nbytes)
return QByteArray();
}
constexpr auto MaxDecompressedSize = size_t(MaxByteArraySize);
constexpr auto MaxDecompressedSize = size_t(QByteArray::max_size());
if constexpr (MaxDecompressedSize < std::numeric_limits<CompressSizeHint_t>::max()) {
if (expectedSize > MaxDecompressedSize)
return tooMuchData(ZLibOp::Decompression);
@ -1389,6 +1388,15 @@ QByteArray &QByteArray::operator=(const char *str)
\sa isEmpty(), resize()
*/
/*! \fn qsizetype QByteArray::max_size()
\since 6.8
This function is provided for STL compatibility.
It returns the maximum number of elements that the byte array can
theoretically hold. In practice, the number can be much smaller,
limited by the amount of memory available to the system.
*/
/*! \fn bool QByteArray::isEmpty() const
Returns \c true if the byte array has size 0; otherwise returns \c false.

View File

@ -519,6 +519,11 @@ public:
void shrink_to_fit() { squeeze(); }
iterator erase(const_iterator first, const_iterator last);
inline iterator erase(const_iterator it) { return erase(it, it + 1); }
static constexpr qsizetype max_size() noexcept
{
// -1 to deal with the NUL terminator
return Data::max_size() - 1;
}
static QByteArray fromStdString(const std::string &s);
std::string toStdString() const;

View File

@ -16,13 +16,12 @@
//
#include <QtCore/qbytearray.h>
#include "private/qtools_p.h"
QT_BEGIN_NAMESPACE
// -1 because of the terminating NUL
constexpr qsizetype MaxByteArraySize = MaxAllocSize - sizeof(std::remove_pointer<QByteArray::DataPointer>::type) - 1;
constexpr qsizetype MaxStringSize = (MaxAllocSize - sizeof(std::remove_pointer<QByteArray::DataPointer>::type)) / 2 - 1;
constexpr qsizetype MaxByteArraySize = QtPrivate::MaxAllocSize - sizeof(std::remove_pointer<QByteArray::DataPointer>::type) - 1;
constexpr qsizetype MaxStringSize = (QtPrivate::MaxAllocSize - sizeof(std::remove_pointer<QByteArray::DataPointer>::type)) / 2 - 1;
QT_END_NAMESPACE

View File

@ -6261,6 +6261,16 @@ QString& QString::fill(QChar ch, qsizetype size)
\sa isEmpty(), resize()
*/
/*!
\fn qsizetype QString::max_size()
\since 6.8
This function is provided for STL compatibility.
It returns the maximum number of elements that the string can
theoretically hold. In practice, the number can be much smaller,
limited by the amount of memory available to the system.
*/
/*! \fn bool QString::isNull() const
Returns \c true if this string is null; otherwise returns \c false.

View File

@ -941,6 +941,11 @@ public:
void shrink_to_fit() { squeeze(); }
iterator erase(const_iterator first, const_iterator last);
inline iterator erase(const_iterator it) { return erase(it, it + 1); }
static constexpr qsizetype max_size() noexcept
{
// -1 to deal with the NUL terminator
return Data::max_size() - 1;
}
static inline QString fromStdString(const std::string &s);
inline std::string toStdString() const;

View File

@ -8,6 +8,7 @@
#include <QtCore/qpair.h>
#include <QtCore/qatomic.h>
#include <QtCore/qflags.h>
#include <QtCore/qcontainerfwd.h>
#include <string.h>
QT_BEGIN_NAMESPACE
@ -169,6 +170,12 @@ struct QTypedArrayData
(quintptr(data) + sizeof(QArrayData) + alignment - 1) & ~(alignment - 1));
return static_cast<T *>(start);
}
constexpr static qsizetype max_size() noexcept
{
// -1 to deal with the pointer one-past-the-end
return (QtPrivate::MaxAllocSize - sizeof(QtPrivate::AlignedQArrayData) - 1) / sizeof(T);
}
};
namespace QtPrivate {

View File

@ -14,6 +14,7 @@
// std headers can unfortunately not be forward declared
#include <cstddef> // std::size_t
#include <utility>
#include <limits>
QT_BEGIN_NAMESPACE
@ -52,6 +53,12 @@ using QVariantMap = QMap<QString, QVariant>;
using QVariantHash = QHash<QString, QVariant>;
using QVariantPair = std::pair<QVariant, QVariant>;
namespace QtPrivate
{
[[maybe_unused]]
constexpr qsizetype MaxAllocSize = (std::numeric_limits<qsizetype>::max)();
}
QT_END_NAMESPACE
#endif // QCONTAINERFWD_H

View File

@ -689,6 +689,10 @@ public:
inline reference back() { return last(); }
inline const_reference back() const noexcept { return last(); }
void shrink_to_fit() { squeeze(); }
static qsizetype max_size() noexcept
{
return Data::max_size();
}
// comfort
QList<T> &operator+=(const QList<T> &l) { append(l); return *this; }

View File

@ -1329,6 +1329,15 @@
returns \c false.
*/
/*! \fn template <typename T> qsizetype QList<T>::max_size()
\since 6.8
This function is provided for STL compatibility.
It returns the maximum number of elements that the list can
theoretically hold. In practice, the number can be much smaller,
limited by the amount of memory available to the system.
*/
/*! \fn template <typename T> QList<T> &QList<T>::operator+=(const QList<T> &other)
Appends the items of the \a other list to this list and

View File

@ -3,7 +3,6 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "private/qringbuffer_p.h"
#include "private/qbytearray_p.h"
#include <type_traits>
@ -91,7 +90,7 @@ void QRingBuffer::free(qint64 bytes)
clear(); // try to minify/squeeze us
}
} else {
Q_ASSERT(bytes < MaxByteArraySize);
Q_ASSERT(bytes < QByteArray::max_size());
chunk.advance(bytes);
bufferSize -= bytes;
}
@ -106,7 +105,7 @@ void QRingBuffer::free(qint64 bytes)
char *QRingBuffer::reserve(qint64 bytes)
{
Q_ASSERT(bytes > 0 && bytes < MaxByteArraySize);
Q_ASSERT(bytes > 0 && bytes < QByteArray::max_size());
const qsizetype chunkSize = qMax(qint64(basicBlockSize), bytes);
qsizetype tail = 0;
@ -136,7 +135,7 @@ char *QRingBuffer::reserve(qint64 bytes)
*/
char *QRingBuffer::reserveFront(qint64 bytes)
{
Q_ASSERT(bytes > 0 && bytes < MaxByteArraySize);
Q_ASSERT(bytes > 0 && bytes < QByteArray::max_size());
const qsizetype chunkSize = qMax(qint64(basicBlockSize), bytes);
if (bufferSize == 0) {
@ -182,7 +181,7 @@ void QRingBuffer::chop(qint64 bytes)
clear(); // try to minify/squeeze us
}
} else {
Q_ASSERT(bytes < MaxByteArraySize);
Q_ASSERT(bytes < QByteArray::max_size());
chunk.grow(-bytes);
bufferSize -= bytes;
}

View File

@ -115,9 +115,6 @@ constexpr inline int qt_lencmp(qsizetype lhs, qsizetype rhs) noexcept
} // namespace QtMiscUtils
// We typically need an extra bit for qNextPowerOfTwo when determining the next allocation size.
constexpr qsizetype MaxAllocSize = (std::numeric_limits<qsizetype>::max)();
struct CalculateGrowingBlockSizeResult
{
qsizetype size;

View File

@ -183,6 +183,12 @@ public:
iterator erase(const_iterator begin, const_iterator end);
iterator erase(const_iterator pos) { return erase(pos, pos + 1); }
static constexpr qsizetype max_size() noexcept
{
// -1 to deal with the pointer one-past-the-end
return (QtPrivate::MaxAllocSize / sizeof(T)) - 1;
}
size_t hash(size_t seed) const noexcept(QtPrivate::QNothrowHashable_v<T>)
{
return qHashRange(begin(), end(), seed);
@ -397,6 +403,7 @@ public:
}
#ifdef Q_QDOC
inline qsizetype size() const { return this->s; }
static constexpr qsizetype max_size() noexcept { return QVLABase<T>::max_size(); }
#endif
using Base::size;
inline qsizetype count() const { return size(); }

View File

@ -140,6 +140,15 @@
\sa isEmpty(), resize()
*/
/*! \fn template<class T, qsizetype Prealloc> qsizetype QVarLengthArray<T, Prealloc>::max_size()
\since 6.8
This function is provided for STL compatibility.
It returns the maximum number of elements that the array can
theoretically hold. In practice, the number can be much smaller,
limited by the amount of memory available to the system.
*/
/*! \fn template<class T, qsizetype Prealloc> T& QVarLengthArray<T, Prealloc>::first()
Returns a reference to the first item in the array. The array must

View File

@ -3,7 +3,6 @@
#include "qdecompresshelper_p.h"
#include <QtCore/private/qbytearray_p.h>
#include <QtCore/qiodevice.h>
#include <QtCore/qcoreapplication.h>

View File

@ -83,7 +83,6 @@
#include "qsctpsocket_p.h"
#include "qabstractsocketengine_p.h"
#include "private/qbytearray_p.h"
#ifdef QSCTPSOCKET_DEBUG
#include <qdebug.h>
@ -133,7 +132,7 @@ bool QSctpSocketPrivate::canReadNotification()
bytesToRead = 4096;
}
Q_ASSERT((datagramSize + qsizetype(bytesToRead)) < MaxByteArraySize);
Q_ASSERT((datagramSize + qsizetype(bytesToRead)) < QByteArray::max_size());
incomingDatagram.resize(datagramSize + int(bytesToRead));
#if defined (QSCTPSOCKET_DEBUG)

View File

@ -5,8 +5,6 @@
#include <QTest>
#include <QBuffer>
#include <QtCore/private/qbytearray_p.h>
class tst_QCborStreamReader : public QObject
{
Q_OBJECT
@ -920,7 +918,7 @@ void tst_QCborStreamReader::validation_data()
// Add QCborStreamReader-specific limitations due to use of QByteArray and
// QString, which are allocated by QArrayData::allocate().
const qsizetype MaxInvalid = std::numeric_limits<QByteArray::size_type>::max();
const qsizetype MinInvalid = MaxByteArraySize + 1;
const qsizetype MinInvalid = QByteArray::max_size() + 1;
addValidationColumns();
addValidationData(MinInvalid);
@ -997,7 +995,7 @@ void tst_QCborStreamReader::validation()
void tst_QCborStreamReader::hugeDeviceValidation_data()
{
addValidationHugeDevice(MaxByteArraySize + 1, MaxStringSize + 1);
addValidationHugeDevice(QByteArray::max_size() + 1, QString::max_size() + 1);
}
void tst_QCborStreamReader::hugeDeviceValidation()

View File

@ -11,8 +11,6 @@
#include <QtEndian>
#include <QTimeZone>
#include <QtCore/private/qbytearray_p.h>
Q_DECLARE_METATYPE(QCborKnownTags)
Q_DECLARE_METATYPE(QCborValue)
Q_DECLARE_METATYPE(QCborValue::EncodingOptions)
@ -2221,7 +2219,7 @@ void tst_QCborValue::validation_data()
// Add QCborStreamReader-specific limitations due to use of QByteArray and
// QString, which are allocated by QArrayData::allocate().
const qsizetype MaxInvalid = std::numeric_limits<QByteArray::size_type>::max();
const qsizetype MinInvalid = MaxByteArraySize + 1 - sizeof(QByteArray::size_type);
const qsizetype MinInvalid = QByteArray::max_size() + 1 - sizeof(QByteArray::size_type);
addValidationColumns();
addValidationData(MinInvalid);
addValidationLargeData(MinInvalid, MaxInvalid);
@ -2394,7 +2392,7 @@ void tst_QCborValue::hugeDeviceValidation_data()
{
// because QCborValue will attempt to retain the original string in UTF-8,
// the size which it can't store is actually the byte array size
addValidationHugeDevice(MaxByteArraySize + 1, MaxByteArraySize + 1);
addValidationHugeDevice(QByteArray::max_size() + 1, QByteArray::max_size() + 1);
}
void tst_QCborValue::hugeDeviceValidation()