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