Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qarraydata.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// Copyright (C) 2016 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:significant reason:default
5
6#include <QtCore/qalloc.h>
7#include <QtCore/qarraydata.h>
8#include <QtCore/private/qnumeric_p.h>
9#include <QtCore/private/qtools_p.h>
10#include <QtCore/qmath.h>
11
12#include <QtCore/qbytearray.h> // QBA::value_type
13#include <QtCore/qstring.h> // QString::value_type
14
15#include <stdlib.h>
16
17QT_BEGIN_NAMESPACE
18
19/*
20 * This pair of functions is declared in qtools_p.h and is used by the Qt
21 * containers to allocate memory and grow the memory block during append
22 * operations.
23 *
24 * They take qsizetype parameters and return qsizetype so they will change sizes
25 * according to the pointer width. However, knowing Qt containers store the
26 * container size and element indexes in ints, these functions never return a
27 * size larger than INT_MAX. This is done by casting the element count and
28 * memory block size to int in several comparisons: the check for negative is
29 * very fast on most platforms as the code only needs to check the sign bit.
30 *
31 * These functions return SIZE_MAX on overflow, which can be passed to malloc()
32 * and will surely cause a NULL return (there's no way you can allocate a
33 * memory block the size of your entire VM space).
34 */
35
36/*!
37 \internal
38 \since 5.7
39
40 Returns the memory block size for a container containing \a elementCount
41 elements, each of \a elementSize bytes, plus a header of \a headerSize
42 bytes. That is, this function returns \c
43 {elementCount * elementSize + headerSize}
44
45 but unlike the simple calculation, it checks for overflows during the
46 multiplication and the addition.
47
48 Both \a elementCount and \a headerSize can be zero, but \a elementSize
49 cannot.
50
51 This function returns -1 on overflow or if the memory block size
52 would not fit a qsizetype.
53*/
54qsizetype qCalculateBlockSize(qsizetype elementCount, qsizetype elementSize, qsizetype headerSize) noexcept
55{
56 Q_ASSERT(elementSize);
57
58 size_t bytes;
59 if (Q_UNLIKELY(qMulOverflow(size_t(elementSize), size_t(elementCount), &bytes)) ||
60 Q_UNLIKELY(qAddOverflow(bytes, size_t(headerSize), &bytes)))
61 return -1;
62 if (Q_UNLIKELY(qsizetype(bytes) < 0))
63 return -1;
64
65 return qsizetype(bytes);
66}
67
68/*!
69 \internal
70 \since 5.7
71
72 Returns the memory block size and the number of elements that will fit in
73 that block for a container containing \a elementCount elements, each of \a
74 elementSize bytes, plus a header of \a headerSize bytes. This function
75 assumes the container will grow and pre-allocates a growth factor.
76
77 Both \a elementCount and \a headerSize can be zero, but \a elementSize
78 cannot.
79
80 This function returns -1 on overflow or if the memory block size
81 would not fit a qsizetype.
82
83 \note The memory block may contain up to \a elementSize - 1 bytes more than
84 needed.
85*/
87qCalculateGrowingBlockSize(qsizetype elementCount, qsizetype elementSize, qsizetype headerSize) noexcept
88{
89 CalculateGrowingBlockSizeResult result = {
90 qsizetype(-1), qsizetype(-1)
91 };
92
93 qsizetype bytes = qCalculateBlockSize(elementCount, elementSize, headerSize);
94 if (bytes < 0)
95 return result;
96
97 size_t morebytes = static_cast<size_t>(qNextPowerOfTwo(quint64(bytes)));
98 if (Q_UNLIKELY(qsizetype(morebytes) < 0)) {
99 // grow by half the difference between bytes and morebytes
100 // this slows the growth and avoids trying to allocate exactly
101 // 2G of memory (on 32bit), something that many OSes can't deliver
102 bytes += (morebytes - bytes) / 2;
103 } else {
104 bytes = qsizetype(morebytes);
105 }
106 size_t fittedBytes = QtPrivate::expectedAllocSize(bytes, alignof(std::max_align_t));
107 if (fittedBytes != 0)
108 bytes = fittedBytes;
109
110 result.elementCount = (bytes - headerSize) / elementSize;
111 result.size = result.elementCount * elementSize + headerSize;
112 return result;
113}
114
115using QtPrivate::AlignedQArrayData;
116
117static qsizetype calculateHeaderSize(qsizetype alignment)
118{
119 qsizetype headerSize = sizeof(AlignedQArrayData);
120 const qsizetype headerAlignment = alignof(AlignedQArrayData);
121
122 if (alignment > headerAlignment) {
123 // Allocate extra (alignment - Q_ALIGNOF(AlignedQArrayData)) padding
124 // bytes so we can properly align the data array. This assumes malloc is
125 // able to provide appropriate alignment for the header -- as it should!
126 // Effectively, we allocate one QTypedArrayData<T>::AlignmentDummy.
127 headerSize += alignment - headerAlignment;
128 }
129 Q_ASSERT(headerSize > 0);
130
131 return headerSize;
132}
133
134/*
135 Calculate the byte size for a block of \a capacity objects of size \a
136 objectSize, with a header of size \a headerSize. If the \a option is
137 QArrayData::Grow, the capacity itself adjusted up, preallocating room for
138 more elements to be added later; otherwise, it is an exact calculation.
139
140 Returns a structure containing the size in bytes and elements available.
141*/
143calculateBlockSize(qsizetype capacity, qsizetype objectSize, qsizetype headerSize, QArrayData::AllocationOption option)
144{
145 // Adjust the header size up to account for the trailing null for QString
146 // and QByteArray. This is not checked for overflow because headers sizes
147 // should not be anywhere near the overflow limit.
148 constexpr qsizetype FooterSize = qMax(sizeof(QString::value_type), sizeof(QByteArray::value_type));
149 if (objectSize <= FooterSize)
150 headerSize += FooterSize;
151
152 // allocSize = objectSize * capacity + headerSize, but checked for overflow
153 // plus padded to grow in size
154 if (option == QArrayData::Grow) {
155 return qCalculateGrowingBlockSize(capacity, objectSize, headerSize);
156 } else {
157 return { qCalculateBlockSize(capacity, objectSize, headerSize), capacity };
158 }
159}
160
161static inline void *
162allocateHelper(QArrayData **dptr, qsizetype objectSize, qsizetype alignment, qsizetype capacity,
163 QArrayData::AllocationOption option) noexcept
164{
165 *dptr = nullptr;
166 if (capacity <= 0)
167 return {};
168
169 const qsizetype headerSize = calculateHeaderSize(alignment);
170 Q_ASSERT(headerSize > 0);
171
172 auto blockSize = calculateBlockSize(capacity, objectSize, headerSize, option);
173 capacity = blockSize.elementCount;
174 qsizetype allocSize = blockSize.size;
175 if (Q_UNLIKELY(allocSize < 0)) // handle overflow. cannot allocate reliably
176 return {};
177
178 void *data = nullptr;
179 void *mem = ::malloc(size_t(allocSize));
180 if (Q_LIKELY(mem)) {
181 *dptr = new (mem) QArrayData{1, {}, capacity};
182 // find where offset should point to so that data() is aligned to alignment bytes
183 data = QTypedArrayData<void>::dataStart(*dptr, alignment);
184 }
185
186 return data;
187}
188
189// Generic size and alignment allocation function
190void *QArrayData::allocate(QArrayData **dptr, qsizetype objectSize, qsizetype alignment,
191 qsizetype capacity, AllocationOption option) noexcept
192{
193 Q_ASSERT(dptr);
194 // Alignment is a power of two
195 Q_ASSERT(alignment >= qsizetype(alignof(QArrayData))
196 && !(alignment & (alignment - 1)));
197
198 return allocateHelper(dptr, objectSize, alignment, capacity, option);
199}
200
201// Fixed size and alignment allocation functions
202void *QArrayData::allocate1(QArrayData **dptr, qsizetype capacity, AllocationOption option) noexcept
203{
204 Q_ASSERT(dptr);
205
206 return allocateHelper(dptr, 1, alignof(AlignedQArrayData), capacity, option);
207}
208
209void *QArrayData::allocate2(QArrayData **dptr, qsizetype capacity, AllocationOption option) noexcept
210{
211 Q_ASSERT(dptr);
212
213 return allocateHelper(dptr, 2, alignof(AlignedQArrayData), capacity, option);
214}
215
216std::pair<QArrayData *, void *>
217QArrayData::reallocateUnaligned(QArrayData *data, void *dataPointer,
218 qsizetype objectSize, qsizetype capacity, AllocationOption option) noexcept
219{
220 Q_ASSERT(!data || !data->isShared());
221
222 const qsizetype headerSize = sizeof(AlignedQArrayData);
223 auto r = calculateBlockSize(capacity, objectSize, headerSize, option);
224 qsizetype allocSize = r.size;
225 capacity = r.elementCount;
226 if (Q_UNLIKELY(allocSize < 0))
227 return {};
228
229 const qptrdiff offset = dataPointer
230 ? reinterpret_cast<char *>(dataPointer) - reinterpret_cast<char *>(data)
231 : headerSize;
232 Q_ASSERT(offset > 0);
233 Q_ASSERT(offset <= allocSize); // equals when all free space is at the beginning
234
235 const bool hadData = data;
236 void *mem = ::realloc(data, size_t(allocSize));
237 QArrayData *header = static_cast<QArrayData *>(mem);
238 if (mem) {
239 if (!hadData)
240 header = new (mem) QArrayData{0, {}, {}};
241 header->alloc = capacity;
242 dataPointer = reinterpret_cast<char *>(header) + offset;
243 } else {
244 dataPointer = nullptr;
245 }
246 return {header, dataPointer};
247}
248
249void QArrayData::deallocate(QArrayData *data, qsizetype objectSize,
250 qsizetype alignment) noexcept
251{
252 // Alignment is a power of two
253 Q_ASSERT(alignment >= qsizetype(alignof(QArrayData))
254 && !(alignment & (alignment - 1)));
255
256 const qsizetype capacity = data->alloc;
257 const qsizetype headerSize = calculateHeaderSize(alignment);
258 Q_ASSERT(headerSize > 0);
259 const auto blockSize = calculateBlockSize(capacity, objectSize,
260 headerSize, QArrayData::KeepSize);
261 const qsizetype allocSize = blockSize.size;
262
263 if (Q_LIKELY(allocSize > 0))
264 QtPrivate::sizedFree(data, size_t(allocSize));
265 else // something went wrong, fallback to slow free()
266 free(data);
267}
268
269QT_END_NAMESPACE
static CalculateGrowingBlockSizeResult calculateBlockSize(qsizetype capacity, qsizetype objectSize, qsizetype headerSize, QArrayData::AllocationOption option)
static void * allocateHelper(QArrayData **dptr, qsizetype objectSize, qsizetype alignment, qsizetype capacity, QArrayData::AllocationOption option) noexcept
CalculateGrowingBlockSizeResult qCalculateGrowingBlockSize(qsizetype elementCount, qsizetype elementSize, qsizetype headerSize) noexcept
static qsizetype calculateHeaderSize(qsizetype alignment)