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
qqmldata.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant
4
5#include "qqmldata_p.h"
6
7#include <private/qmetaobject_p.h>
8#include <private/qqmlabstractbinding_p.h>
9#include <private/qqmlboundsignal_p.h>
10#include <private/qqmlcontextdata_p.h>
11#include <private/qqmlnotifier_p.h>
12
13#include <QtCore/qtyperevision.h>
14#include <private/qthread_p.h>
15
17
18QQmlData::QQmlData(Ownership ownership)
19 : ownMemory(ownership == OwnsMemory)
20 , indestructible(true)
21 , explicitIndestructibleSet(false)
22 , hasTaintedV4Object(false)
23 , isQueuedForDeletion(false)
24 , rootObjectInCreation(false)
25 , hasInterceptorMetaObject(false)
26 , hasVMEMetaObject(false)
27 , hasConstWrapper(false)
28 , dummy(0)
29 , bindingBitsArraySize(InlineBindingArraySize)
30{
31 memset(bindingBitsValue, 0, sizeof(bindingBitsValue));
32 init();
33}
34
35QQmlData::~QQmlData() = default;
36
37void QQmlData::destroyed(QAbstractDeclarativeData *d, QObject *o)
38{
39 QQmlData *ddata = static_cast<QQmlData *>(d);
40 ddata->destroyed(o);
41}
42
44{
45public:
47
48 int qt_metacall(QMetaObject::Call, int methodIndex, void **a) override {
49 if (!target)
50 return -1;
51
52 QMetaMethod method = target->metaObject()->method(methodIndex);
53 Q_ASSERT(method.methodType() == QMetaMethod::Signal);
54 int signalIndex = QMetaObjectPrivate::signalIndex(method);
55 QQmlData *ddata = QQmlData::get(target, false);
56 QQmlNotifierEndpoint *ep = ddata->notify(signalIndex);
57 if (ep) QQmlNotifier::emitNotify(ep, a);
58
59 delete this;
60
61 return -1;
62 }
63};
64
65void QQmlData::signalEmitted(QAbstractDeclarativeData *, QObject *object, int index, void **a)
66{
67 QQmlData *ddata = QQmlData::get(object, false);
68 if (!ddata) return; // Probably being deleted
69
70 // In general, QML only supports QObject's that live on the same thread as the QQmlEngine
71 // that they're exposed to. However, to make writing "worker objects" that calculate data
72 // in a separate thread easier, QML allows a QObject that lives in the same thread as the
73 // QQmlEngine to emit signals from a different thread. These signals are then automatically
74 // marshalled back onto the QObject's thread and handled by QML from there. This is tested
75 // by the qqmlecmascript::threadSignal() autotest.
76
77 // Relaxed semantics here. If we're on a different thread we might schedule a useless event,
78 // but that should be rare.
79 if (!ddata->notifyList.loadRelaxed())
80 return;
81
82 auto objectThreadData = QObjectPrivate::get(object)->threadData.loadRelaxed();
83 if (QThread::currentThreadId() != objectThreadData->threadId.loadRelaxed()) {
84 if (!objectThreadData->thread.loadAcquire())
85 return;
86
87 QMetaMethod m = QMetaObjectPrivate::signal(object->metaObject(), index);
88 const QList<QByteArray> parameterTypes = m.parameterTypes();
89
90 QVarLengthArray<const QtPrivate::QMetaTypeInterface *, 16> argTypes;
91 argTypes.reserve(1 + parameterTypes.size());
92 argTypes.emplace_back(nullptr); // return type
93 for (const QByteArray &typeName: parameterTypes) {
94 QMetaType type;
95 if (typeName.endsWith('*'))
96 type = QMetaType(QMetaType::VoidStar);
97 else
98 type = QMetaType::fromName(typeName);
99
100 if (!type.isValid()) {
101 qWarning("QObject::connect: Cannot queue arguments of type '%s'\n"
102 "(Make sure '%s' is registered using qRegisterMetaType().)",
103 typeName.constData(), typeName.constData());
104 return;
105 }
106
107 argTypes.emplace_back(type.iface());
108 }
109
110 auto ev = std::make_unique<QQueuedMetaCallEvent>(m.methodIndex(), 0, nullptr, object, index,
111 argTypes.size(), argTypes.data(), a);
112
113 QQmlThreadNotifierProxyObject *mpo = new QQmlThreadNotifierProxyObject;
114 mpo->target = object;
115 mpo->moveToThread(objectThreadData->thread.loadAcquire());
116 QCoreApplication::postEvent(mpo, ev.release());
117
118 } else {
119 QQmlNotifierEndpoint *ep = ddata->notify(index);
120 if (ep) QQmlNotifier::emitNotify(ep, a);
121 }
122}
123
124int QQmlData::receivers(QAbstractDeclarativeData *d, const QObject *, int index)
125{
126 QQmlData *ddata = static_cast<QQmlData *>(d);
127 return ddata->endpointCount(index);
128}
129
130bool QQmlData::isSignalConnected(QAbstractDeclarativeData *d, const QObject *, int index)
131{
132 QQmlData *ddata = static_cast<QQmlData *>(d);
133 return ddata->signalHasEndpoint(index);
134}
135
136int QQmlData::endpointCount(int index)
137{
138 int count = 0;
139 QQmlNotifierEndpoint *ep = notify(index);
140 if (!ep)
141 return count;
142 ++count;
143 while (ep->next) {
144 ++count;
145 ep = ep->next;
146 }
147 return count;
148}
149
150void QQmlData::markAsDeleted(QObject *o)
151{
152 QVarLengthArray<QObject *> workStack;
153 workStack.push_back(o);
154 while (!workStack.isEmpty()) {
155 auto currentObject = workStack.last();
156 workStack.pop_back();
157 QQmlData::setQueuedForDeletion(currentObject);
158 auto currentObjectPriv = QObjectPrivate::get(currentObject);
159 for (QObject *child: std::as_const(currentObjectPriv->children))
160 workStack.push_back(child);
161 }
162}
163
164void QQmlData::setQueuedForDeletion(QObject *object)
165{
166 if (object) {
167 if (QQmlData *ddata = QQmlData::get(object)) {
168 if (ddata->ownContext) {
169 Q_ASSERT(ddata->ownContext.data() == ddata->context);
170 ddata->ownContext->deepClearContextObject(object);
171 ddata->ownContext.reset();
172 ddata->context = nullptr;
173 }
174 ddata->isQueuedForDeletion = true;
175
176 // Disconnect the notifiers now - during object destruction this would be too late,
177 // since the disconnect call wouldn't be able to call disconnectNotify(), as it isn't
178 // possible to get the metaobject anymore.
179 // Also, there is no point in evaluating bindings in order to set properties on
180 // half-deleted objects.
181 ddata->disconnectNotifiers(DeleteNotifyList::No);
182 }
183 }
184}
185
186void QQmlData::flushPendingBinding(int coreIndex)
187{
188 clearPendingBindingBit(coreIndex);
189
190 // Find the binding
191 QQmlAbstractBinding *b = bindings;
192 while (b && (b->targetPropertyIndex().coreIndex() != coreIndex ||
193 b->targetPropertyIndex().hasValueTypeIndex()))
194 b = b->nextBinding();
195
196 if (b && b->targetPropertyIndex().coreIndex() == coreIndex &&
197 !b->targetPropertyIndex().hasValueTypeIndex())
198 b->setEnabled(true, QQmlPropertyData::BypassInterceptor |
199 QQmlPropertyData::DontRemoveBinding);
200}
201
202QQmlData::DeferredData::DeferredData() = default;
203QQmlData::DeferredData::~DeferredData() = default;
204
214
215void QQmlData::NotifyList::layout(QQmlNotifierEndpoint *endpoint)
216{
217 // Add a temporary sentinel at beginning of list. This will be overwritten
218 // when the end point is inserted into the notifies further down.
219 endpoint->prev = nullptr;
220
221 while (endpoint->next) {
222 Q_ASSERT(reinterpret_cast<QQmlNotifierEndpoint *>(endpoint->next->prev) == endpoint);
223 endpoint = endpoint->next;
224 }
225
226 while (endpoint) {
227 QQmlNotifierEndpoint *ep = (QQmlNotifierEndpoint *) endpoint->prev;
228
229 int index = endpoint->sourceSignal;
230 index = qMin(index, 0xFFFF - 1);
231
232 endpoint->next = notifies[index];
233 if (endpoint->next) endpoint->next->prev = &endpoint->next;
234 endpoint->prev = &notifies[index];
235 notifies[index] = endpoint;
236
237 endpoint = ep;
238 }
239}
240
241void QQmlData::NotifyList::layout()
242{
243 Q_ASSERT(maximumTodoIndex >= notifiesSize);
244
245 if (todo) {
246 QQmlNotifierEndpoint **old = notifies;
247 const int reallocSize = (maximumTodoIndex + 1) * sizeof(QQmlNotifierEndpoint*);
248 notifies = (QQmlNotifierEndpoint**)realloc(notifies, reallocSize);
249 const int memsetSize = (maximumTodoIndex - notifiesSize + 1) *
250 sizeof(QQmlNotifierEndpoint*);
251 memset(notifies + notifiesSize, 0, memsetSize);
252
253 if (notifies != old) {
254 for (int ii = 0; ii < notifiesSize; ++ii)
255 if (notifies[ii])
256 notifies[ii]->prev = &notifies[ii];
257 }
258
259 notifiesSize = maximumTodoIndex + 1;
260
261 layout(todo);
262 }
263
264 maximumTodoIndex = 0;
265 todo = nullptr;
266}
267
268void QQmlData::deferData(
269 int objectIndex, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
270 const QQmlRefPointer<QQmlContextData> &context, const QString &inlineComponentName)
271{
272 QQmlData::DeferredData *deferData = new QQmlData::DeferredData;
273 deferData->deferredIdx = objectIndex;
274 deferData->compilationUnit = compilationUnit;
275 deferData->context = context;
276 deferData->inlineComponentName = inlineComponentName;
277
278 const QV4::CompiledData::Object *compiledObject = compilationUnit->objectAt(objectIndex);
279 const QV4::CompiledData::BindingPropertyData *propertyData
280 = compilationUnit->bindingPropertyDataPerObjectAt(objectIndex);
281
282 const QV4::CompiledData::Binding *binding = compiledObject->bindingTable();
283 for (quint32 i = 0; i < compiledObject->nBindings; ++i, ++binding) {
284 const QQmlPropertyData *property = propertyData->at(i);
285 if (binding->hasFlag(QV4::CompiledData::Binding::IsDeferredBinding))
286 deferData->bindings.insert(property ? property->coreIndex() : -1, binding);
287 }
288
289 deferredData.append(deferData);
290}
291
292void QQmlData::releaseDeferredData()
293{
294 auto it = deferredData.begin();
295 while (it != deferredData.end()) {
296 DeferredData *deferData = *it;
297 if (deferData->bindings.isEmpty()) {
298 delete deferData;
299 it = deferredData.erase(it);
300 } else {
301 ++it;
302 }
303 }
304}
305
306void QQmlData::addNotify(int index, QQmlNotifierEndpoint *endpoint)
307{
308 // Can only happen on "home" thread. We apply relaxed semantics when loading the atomics.
309
310 NotifyList *list = notifyList.loadRelaxed();
311
312 if (!list) {
313 list = new NotifyList;
314 // We don't really care when this change takes effect on other threads. The notifyList can
315 // only become non-null once in the life time of a QQmlData. It becomes null again when the
316 // underlying QObject is deleted. At that point any interaction with the QQmlData is UB
317 // anyway. So, for all intents and purposese, the list becomes non-null once and then stays
318 // non-null "forever". We can apply relaxed semantics.
319 notifyList.storeRelaxed(list);
320 }
321
322 Q_ASSERT(!endpoint->isConnected());
323
324 index = qMin(index, 0xFFFF - 1);
325
326 // Likewise, we don't really care _when_ the change in the connectionMask is propagated to other
327 // threads. Cross-thread event ordering is inherently nondeterministic. Therefore, when querying
328 // the conenctionMask in the presence of concurrent modification, any result is correct.
329 list->connectionMask.storeRelaxed(
330 list->connectionMask.loadRelaxed() | (1ULL << quint64(index % 64)));
331
332 if (index < list->notifiesSize) {
333 endpoint->next = list->notifies[index];
334 if (endpoint->next) endpoint->next->prev = &endpoint->next;
335 endpoint->prev = &list->notifies[index];
336 list->notifies[index] = endpoint;
337 } else {
338 list->maximumTodoIndex = qMax(int(list->maximumTodoIndex), index);
339
340 endpoint->next = list->todo;
341 if (endpoint->next) endpoint->next->prev = &endpoint->next;
342 endpoint->prev = &list->todo;
343 list->todo = endpoint;
344 }
345}
346
347void QQmlData::disconnectNotifiers(QQmlData::DeleteNotifyList doDelete)
348{
349 // Can only happen on "home" thread. We apply relaxed semantics when loading the atomics.
350 if (NotifyList *list = notifyList.loadRelaxed()) {
351 while (QQmlNotifierEndpoint *todo = list->todo)
352 todo->disconnect();
353 for (int ii = 0; ii < list->notifiesSize; ++ii) {
354 while (QQmlNotifierEndpoint *ep = list->notifies[ii])
355 ep->disconnect();
356 }
357 free(list->notifies);
358
359 if (doDelete == DeleteNotifyList::Yes) {
360 // We can only get here from QQmlData::destroyed(), and that can only come from the
361 // the QObject dtor. If you're still sending signals at that point you have UB already
362 // without any threads. Therefore, it's enough to apply relaxed semantics.
363 notifyList.storeRelaxed(nullptr);
364 delete list;
365 } else {
366 // We can use relaxed semantics here. The worst thing that can happen is that some
367 // signal is falsely reported as connected. Signal connectedness across threads
368 // is not quite deterministic anyway.
369 list->connectionMask.storeRelaxed(0);
370 list->maximumTodoIndex = 0;
371 list->notifiesSize = 0;
372 list->notifies = nullptr;
373
374 }
375 }
376}
377
378QHash<QQmlAttachedPropertiesFunc, QObject *> *QQmlData::attachedProperties() const
379{
380 if (!extendedData) extendedData = new QQmlDataExtended;
381 return &extendedData->attachedProperties;
382}
383
384void QQmlData::removeFromContext()
385{
386 if (nextContextObject)
387 nextContextObject->prevContextObject = prevContextObject;
388 if (prevContextObject)
389 *prevContextObject = nextContextObject;
390 else if (outerContext && outerContext->ownedObjects() == this)
391 outerContext->setOwnedObjects(nextContextObject);
392
393 nextContextObject = nullptr;
394 prevContextObject = nullptr;
395 outerContext = nullptr;
396}
397
398void QQmlData::clearBindings()
399{
400 if (QQmlAbstractBinding *binding = std::exchange(bindings, nullptr)) {
401 for (QQmlAbstractBinding *next = binding; next; next = next->nextBinding())
402 next->setAddedToObject(false);
403 if (!binding->ref.deref())
404 delete binding;
405 }
406}
407
408bool QQmlData::clearSignalHandlers()
409{
410 for (QQmlBoundSignal *signalHandler = std::exchange(signalHandlers, nullptr); signalHandler;) {
411 if (signalHandler->isNotifying()) {
412 signalHandlers = signalHandler;
413 return false;
414 }
415
416 QQmlBoundSignal *next = signalHandler->m_nextSignal;
417 signalHandler->m_prevSignal = nullptr;
418 signalHandler->m_nextSignal = nullptr;
419 delete signalHandler;
420 signalHandler = next;
421 }
422
423 return true;
424}
425
426void QQmlData::destroyed(QObject *object)
427{
428 removeFromContext();
429 clearBindings();
430
431 compilationUnit.reset();
432 qDeleteAll(deferredData);
433 deferredData.clear();
434
435 if (!clearSignalHandlers()) {
436 // The object is being deleted during signal handler evaluation.
437 // This will cause a crash due to invalid memory access when the
438 // evaluation has completed.
439 // Abort with a friendly message instead.
440 QString locationString;
441 QQmlBoundSignalExpression *expr = signalHandlers->expression();
442 if (expr) {
443 QQmlSourceLocation location = expr->sourceLocation();
444 if (location.sourceFile.isEmpty())
445 location.sourceFile = QStringLiteral("<Unknown File>");
446 locationString.append(location.sourceFile);
447 locationString.append(QStringLiteral(":%0: ").arg(location.line));
448 QString source = expr->expression();
449 if (source.size() > 100) {
450 source.truncate(96);
451 source.append(QLatin1String(" ..."));
452 }
453 locationString.append(source);
454 } else {
455 locationString = QStringLiteral("<Unknown Location>");
456 }
457 qFatal("Object %p destroyed while one of its QML signal handlers is in progress.\n"
458 "Most likely the object was deleted synchronously (use QObject::deleteLater() "
459 "instead), or the application is running a nested event loop.\n"
460 "This behavior is NOT supported!\n"
461 "%s", object, qPrintable(locationString));
462 }
463
464 if (bindingBitsArraySize > InlineBindingArraySize)
465 free(bindingBits);
466
467 if (propertyCache)
468 propertyCache.reset();
469
470 ownContext.reset();
471
472 while (guards) {
473 auto *guard = guards;
474 guard->setObject(nullptr);
475 if (guard->objectDestroyed)
476 guard->objectDestroyed(guard);
477 }
478
479 disconnectNotifiers(DeleteNotifyList::Yes);
480
481 if (extendedData)
482 delete extendedData;
483
484 // Dispose the handle.
485 jsWrapper.clear();
486
487 if (ownMemory)
488 delete this;
489 else
490 this->~QQmlData();
491}
492
493QQmlData::BindingBitsType *QQmlData::growBits(QObject *obj, int bit)
494{
495 BindingBitsType *bits = (bindingBitsArraySize == InlineBindingArraySize) ? bindingBitsValue : bindingBits;
496 int props = QQmlMetaObject(obj).propertyCount();
497 Q_ASSERT(bit < 2 * props);
498 Q_UNUSED(bit); // .. for Q_NO_DEBUG mode when the assert above expands to empty
499
500 uint arraySize = (2 * static_cast<uint>(props) + BitsPerType - 1) / BitsPerType;
501 Q_ASSERT(arraySize > 1);
502 Q_ASSERT(arraySize <= 0xffff); // max for bindingBitsArraySize
503
504 BindingBitsType *newBits = static_cast<BindingBitsType *>(malloc(arraySize*sizeof(BindingBitsType)));
505 memcpy(newBits, bits, bindingBitsArraySize * sizeof(BindingBitsType));
506 memset(newBits + bindingBitsArraySize, 0, sizeof(BindingBitsType) * (arraySize - bindingBitsArraySize));
507
508 if (bindingBitsArraySize > InlineBindingArraySize)
509 free(bits);
510 bindingBits = newBits;
511 bits = newBits;
512 bindingBitsArraySize = arraySize;
513 return bits;
514}
515
516QQmlData *QQmlData::createQQmlData(QObjectPrivate *priv)
517{
518 Q_ASSERT(priv);
519 Q_ASSERT(!priv->isDeletingChildren);
520 priv->declarativeData = new QQmlData(OwnsMemory);
521 return static_cast<QQmlData *>(priv->declarativeData);
522}
523
524QQmlPropertyCache::ConstPtr QQmlData::createPropertyCache(QObject *object)
525{
526 QQmlData *ddata = QQmlData::get(object, /*create*/true);
527 ddata->propertyCache = QQmlMetaType::propertyCache(object, QTypeRevision {});
528 return ddata->propertyCache;
529}
530
531QT_END_NAMESPACE
QHash< QQmlAttachedPropertiesFunc, QObject * > attachedProperties
Definition qqmldata.cpp:212
~QQmlDataExtended()=default
QPointer< QObject > target
Definition qqmldata.cpp:46
int qt_metacall(QMetaObject::Call, int methodIndex, void **a) override
Definition qqmldata.cpp:48
Combined button and popup list for selecting options.