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