Revision control
Copy as Markdown
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant
#include "qqmldata_p.h"
#include <private/qmetaobject_p.h>
#include <private/qqmlabstractbinding_p.h>
#include <private/qqmlboundsignal_p.h>
#include <private/qqmlcontextdata_p.h>
#include <private/qqmlnotifier_p.h>
#include <QtCore/qtyperevision.h>
#include <private/qthread_p.h>
QT_BEGIN_NAMESPACE
QQmlData::QQmlData(Ownership ownership)
: ownMemory(ownership == OwnsMemory)
, indestructible(true)
, explicitIndestructibleSet(false)
, hasTaintedV4Object(false)
, isQueuedForDeletion(false)
, rootObjectInCreation(false)
, hasInterceptorMetaObject(false)
, hasVMEMetaObject(false)
, hasConstWrapper(false)
, dummy(0)
, bindingBitsArraySize(InlineBindingArraySize)
{
memset(bindingBitsValue, 0, sizeof(bindingBitsValue));
init();
}
QQmlData::~QQmlData() = default;
void QQmlData::destroyed(QAbstractDeclarativeData *d, QObject *o)
{
QQmlData *ddata = static_cast<QQmlData *>(d);
ddata->destroyed(o);
}
class QQmlThreadNotifierProxyObject : public QObject
{
public:
QPointer<QObject> target;
int qt_metacall(QMetaObject::Call, int methodIndex, void **a) override {
if (!target)
return -1;
QMetaMethod method = target->metaObject()->method(methodIndex);
Q_ASSERT(method.methodType() == QMetaMethod::Signal);
int signalIndex = QMetaObjectPrivate::signalIndex(method);
QQmlData *ddata = QQmlData::get(target, false);
QQmlNotifierEndpoint *ep = ddata->notify(signalIndex);
if (ep) QQmlNotifier::emitNotify(ep, a);
delete this;
return -1;
}
};
void QQmlData::signalEmitted(QAbstractDeclarativeData *, QObject *object, int index, void **a)
{
QQmlData *ddata = QQmlData::get(object, false);
if (!ddata) return; // Probably being deleted
// In general, QML only supports QObject's that live on the same thread as the QQmlEngine
// that they're exposed to. However, to make writing "worker objects" that calculate data
// in a separate thread easier, QML allows a QObject that lives in the same thread as the
// QQmlEngine to emit signals from a different thread. These signals are then automatically
// marshalled back onto the QObject's thread and handled by QML from there. This is tested
// by the qqmlecmascript::threadSignal() autotest.
// Relaxed semantics here. If we're on a different thread we might schedule a useless event,
// but that should be rare.
if (!ddata->notifyList.loadRelaxed())
return;
auto objectThreadData = QObjectPrivate::get(object)->threadData.loadRelaxed();
if (QThread::currentThreadId() != objectThreadData->threadId.loadRelaxed()) {
if (!objectThreadData->thread.loadAcquire())
return;
QMetaMethod m = QMetaObjectPrivate::signal(object->metaObject(), index);
const QList<QByteArray> parameterTypes = m.parameterTypes();
QVarLengthArray<const QtPrivate::QMetaTypeInterface *, 16> argTypes;
argTypes.reserve(1 + parameterTypes.size());
argTypes.emplace_back(nullptr); // return type
for (const QByteArray &typeName: parameterTypes) {
QMetaType type;
if (typeName.endsWith('*'))
type = QMetaType(QMetaType::VoidStar);
else
type = QMetaType::fromName(typeName);
if (!type.isValid()) {
qWarning("QObject::connect: Cannot queue arguments of type '%s'\n"
"(Make sure '%s' is registered using qRegisterMetaType().)",
typeName.constData(), typeName.constData());
return;
}
argTypes.emplace_back(type.iface());
}
auto ev = std::make_unique<QQueuedMetaCallEvent>(m.methodIndex(), 0, nullptr, object, index,
argTypes.size(), argTypes.data(), a);
QQmlThreadNotifierProxyObject *mpo = new QQmlThreadNotifierProxyObject;
mpo->target = object;
mpo->moveToThread(objectThreadData->thread.loadAcquire());
QCoreApplication::postEvent(mpo, ev.release());
} else {
QQmlNotifierEndpoint *ep = ddata->notify(index);
if (ep) QQmlNotifier::emitNotify(ep, a);
}
}
int QQmlData::receivers(QAbstractDeclarativeData *d, const QObject *, int index)
{
QQmlData *ddata = static_cast<QQmlData *>(d);
return ddata->endpointCount(index);
}
bool QQmlData::isSignalConnected(QAbstractDeclarativeData *d, const QObject *, int index)
{
QQmlData *ddata = static_cast<QQmlData *>(d);
return ddata->signalHasEndpoint(index);
}
int QQmlData::endpointCount(int index)
{
int count = 0;
QQmlNotifierEndpoint *ep = notify(index);
if (!ep)
return count;
++count;
while (ep->next) {
++count;
ep = ep->next;
}
return count;
}
void QQmlData::markAsDeleted(QObject *o)
{
QVarLengthArray<QObject *> workStack;
workStack.push_back(o);
while (!workStack.isEmpty()) {
auto currentObject = workStack.last();
workStack.pop_back();
QQmlData::setQueuedForDeletion(currentObject);
auto currentObjectPriv = QObjectPrivate::get(currentObject);
for (QObject *child: std::as_const(currentObjectPriv->children))
workStack.push_back(child);
}
}
void QQmlData::setQueuedForDeletion(QObject *object)
{
if (object) {
if (QQmlData *ddata = QQmlData::get(object)) {
if (ddata->ownContext) {
Q_ASSERT(ddata->ownContext.data() == ddata->context);
ddata->ownContext->deepClearContextObject(object);
ddata->ownContext.reset();
ddata->context = nullptr;
}
ddata->isQueuedForDeletion = true;
// Disconnect the notifiers now - during object destruction this would be too late,
// since the disconnect call wouldn't be able to call disconnectNotify(), as it isn't
// possible to get the metaobject anymore.
// Also, there is no point in evaluating bindings in order to set properties on
// half-deleted objects.
ddata->disconnectNotifiers(DeleteNotifyList::No);
}
}
}
void QQmlData::flushPendingBinding(int coreIndex)
{
clearPendingBindingBit(coreIndex);
// Find the binding
QQmlAbstractBinding *b = bindings;
while (b && (b->targetPropertyIndex().coreIndex() != coreIndex ||
b->targetPropertyIndex().hasValueTypeIndex()))
b = b->nextBinding();
if (b && b->targetPropertyIndex().coreIndex() == coreIndex &&
!b->targetPropertyIndex().hasValueTypeIndex())
b->setEnabled(true, QQmlPropertyData::BypassInterceptor |
QQmlPropertyData::DontRemoveBinding);
}
QQmlData::DeferredData::DeferredData() = default;
QQmlData::DeferredData::~DeferredData() = default;
class QQmlDataExtended
{
Q_DISABLE_COPY_MOVE(QQmlDataExtended)
public:
QQmlDataExtended() = default;
~QQmlDataExtended() = default;
QHash<QQmlAttachedPropertiesFunc, QObject *> attachedProperties;
};
void QQmlData::deferData(
int objectIndex, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
const QQmlRefPointer<QQmlContextData> &context, const QString &inlineComponentName)
{
QQmlData::DeferredData *deferData = new QQmlData::DeferredData;
deferData->deferredIdx = objectIndex;
deferData->compilationUnit = compilationUnit;
deferData->context = context;
deferData->inlineComponentName = inlineComponentName;
const QV4::CompiledData::Object *compiledObject = compilationUnit->objectAt(objectIndex);
const QV4::CompiledData::BindingPropertyData *propertyData
= compilationUnit->bindingPropertyDataPerObjectAt(objectIndex);
const QV4::CompiledData::Binding *binding = compiledObject->bindingTable();
for (quint32 i = 0; i < compiledObject->nBindings; ++i, ++binding) {
const QQmlPropertyData *property = propertyData->at(i);
if (compilationUnit->bindingIsDeferred(objectIndex, i))
deferData->bindings.insert(property ? property->coreIndex() : -1, binding);
}
deferredData.append(deferData);
}
void QQmlData::releaseDeferredData()
{
auto it = deferredData.begin();
while (it != deferredData.end()) {
DeferredData *deferData = *it;
if (deferData->bindings.isEmpty()) {
delete deferData;
it = deferredData.erase(it);
} else {
++it;
}
}
}
void QQmlData::addNotify(int index, QQmlNotifierEndpoint *endpoint)
{
// Can only happen on "home" thread. We apply relaxed semantics when loading the atomics.
QQmlNotifyList *list = notifyList.loadRelaxed();
if (!list) {
list = new QQmlNotifyList;
// We don't really care when this change takes effect on other threads. The notifyList can
// only become non-null once in the life time of a QQmlData. It becomes null again when the
// underlying QObject is deleted. At that point any interaction with the QQmlData is UB
// anyway. So, for all intents and purposese, the list becomes non-null once and then stays
// non-null "forever". We can apply relaxed semantics.
notifyList.storeRelaxed(list);
}
Q_ASSERT(!endpoint->isConnected());
index = qMin(index, 0xFFFF - 1);
// Likewise, we don't really care _when_ the change in the connectionMask is propagated to other
// threads. Cross-thread event ordering is inherently nondeterministic. Therefore, when querying
// the conenctionMask in the presence of concurrent modification, any result is correct.
list->connectionMask.storeRelaxed(
list->connectionMask.loadRelaxed() | (1ULL << quint64(index % 64)));
if (index < list->notifiesSize) {
endpoint->next = list->notifies[index];
if (endpoint->next) endpoint->next->prev = &endpoint->next;
endpoint->prev = &list->notifies[index];
list->notifies[index] = endpoint;
} else {
list->maximumTodoIndex = qMax(int(list->maximumTodoIndex), index);
endpoint->next = list->todo;
if (endpoint->next) endpoint->next->prev = &endpoint->next;
endpoint->prev = &list->todo;
list->todo = endpoint;
}
}
void QQmlData::disconnectNotifiers(QQmlData::DeleteNotifyList doDelete)
{
// Can only happen on "home" thread. We apply relaxed semantics when loading the atomics.
if (QQmlNotifyList *list = notifyList.loadRelaxed()) {
while (QQmlNotifierEndpoint *todo = list->todo)
todo->disconnect();
for (int ii = 0; ii < list->notifiesSize; ++ii) {
while (QQmlNotifierEndpoint *ep = list->notifies[ii])
ep->disconnect();
}
free(list->notifies);
if (doDelete == DeleteNotifyList::Yes) {
// We can only get here from QQmlData::destroyed(), and that can only come from the
// the QObject dtor. If you're still sending signals at that point you have UB already
// without any threads. Therefore, it's enough to apply relaxed semantics.
notifyList.storeRelaxed(nullptr);
delete list;
} else {
// We can use relaxed semantics here. The worst thing that can happen is that some
// signal is falsely reported as connected. Signal connectedness across threads
// is not quite deterministic anyway.
list->connectionMask.storeRelaxed(0);
list->maximumTodoIndex = 0;
list->notifiesSize = 0;
list->notifies = nullptr;
}
}
}
QHash<QQmlAttachedPropertiesFunc, QObject *> *QQmlData::attachedProperties() const
{
if (!extendedData) extendedData = new QQmlDataExtended;
return &extendedData->attachedProperties;
}
void QQmlData::removeFromContext()
{
if (nextContextObject)
nextContextObject->prevContextObject = prevContextObject;
if (prevContextObject)
*prevContextObject = nextContextObject;
else if (outerContext && outerContext->ownedObjects() == this)
outerContext->setOwnedObjects(nextContextObject);
nextContextObject = nullptr;
prevContextObject = nullptr;
outerContext = nullptr;
context = nullptr;
}
void QQmlData::clearBindings()
{
if (QQmlAbstractBinding *binding = std::exchange(bindings, nullptr)) {
for (QQmlAbstractBinding *next = binding; next; next = next->nextBinding())
next->setAddedToObject(false);
if (!binding->ref.deref())
delete binding;
}
}
bool QQmlData::clearSignalHandlers()
{
for (QQmlBoundSignal *signalHandler = std::exchange(signalHandlers, nullptr); signalHandler;) {
if (signalHandler->isNotifying()) {
signalHandlers = signalHandler;
return false;
}
QQmlBoundSignal *next = signalHandler->m_nextSignal;
signalHandler->m_prevSignal = nullptr;
signalHandler->m_nextSignal = nullptr;
delete signalHandler;
signalHandler = next;
}
return true;
}
void QQmlData::clear()
{
removeFromContext();
clearBindings();
compilationUnit.reset();
qDeleteAll(std::exchange(deferredData, {}));
if (!clearSignalHandlers())
qFatal("Can't clear QQmlData from signal handler");
BindingBitsType *bits = (bindingBitsArraySize == InlineBindingArraySize)
? bindingBitsValue
: bindingBits;
memset(bits, 0, bindingBitsArraySize * sizeof(BindingBitsType));
propertyCache.reset();
ownContext.reset();
disconnectNotifiers(DeleteNotifyList::No);
delete std::exchange(extendedData, nullptr);
propertyObservers.clear();
lineNumber = 0;
columnNumber = 0;
rootObjectInCreation = false;
hasInterceptorMetaObject = false;
hasVMEMetaObject = false;
cuObjectIndex = -1;
}
void QQmlData::destroyed(QObject *object)
{
removeFromContext();
clearBindings();
compilationUnit.reset();
qDeleteAll(deferredData);
deferredData.clear();
if (!clearSignalHandlers()) {
// The object is being deleted during signal handler evaluation.
// This will cause a crash due to invalid memory access when the
// evaluation has completed.
// Abort with a friendly message instead.
QString locationString;
QQmlBoundSignalExpression *expr = signalHandlers->expression();
if (expr) {
QQmlSourceLocation location = expr->sourceLocation();
if (location.sourceFile.isEmpty())
location.sourceFile = QStringLiteral("<Unknown File>");
locationString.append(location.sourceFile);
locationString.append(QStringLiteral(":%0: ").arg(location.line));
QString source = expr->expression();
if (source.size() > 100) {
source.truncate(96);
source.append(QLatin1String(" ..."));
}
locationString.append(source);
} else {
locationString = QStringLiteral("<Unknown Location>");
}
qFatal("Object %p destroyed while one of its QML signal handlers is in progress.\n"
"Most likely the object was deleted synchronously (use QObject::deleteLater() "
"instead), or the application is running a nested event loop.\n"
"This behavior is NOT supported!\n"
"%s", object, qPrintable(locationString));
}
if (bindingBitsArraySize > InlineBindingArraySize)
free(bindingBits);
if (propertyCache)
propertyCache.reset();
ownContext.reset();
while (guards) {
auto *guard = guards;
guard->setObject(nullptr);
if (guard->objectDestroyed)
guard->objectDestroyed(guard);
}
disconnectNotifiers(DeleteNotifyList::Yes);
if (extendedData)
delete extendedData;
// Dispose the handle.
jsWrapper.clear();
if (ownMemory)
delete this;
else
this->~QQmlData();
}
QQmlData::BindingBitsType *QQmlData::growBits(QObject *obj, int bit)
{
BindingBitsType *bits = (bindingBitsArraySize == InlineBindingArraySize) ? bindingBitsValue : bindingBits;
int props = QQmlMetaObject(obj).propertyCount();
Q_ASSERT(bit < 2 * props);
Q_UNUSED(bit); // .. for Q_NO_DEBUG mode when the assert above expands to empty
uint arraySize = (2 * static_cast<uint>(props) + BitsPerType - 1) / BitsPerType;
Q_ASSERT(arraySize > 1);
Q_ASSERT(arraySize <= 0xffff); // max for bindingBitsArraySize
BindingBitsType *newBits = static_cast<BindingBitsType *>(malloc(arraySize*sizeof(BindingBitsType)));
memcpy(newBits, bits, bindingBitsArraySize * sizeof(BindingBitsType));
memset(newBits + bindingBitsArraySize, 0, sizeof(BindingBitsType) * (arraySize - bindingBitsArraySize));
if (bindingBitsArraySize > InlineBindingArraySize)
free(bits);
bindingBits = newBits;
bits = newBits;
bindingBitsArraySize = arraySize;
return bits;
}
QQmlData *QQmlData::createQQmlData(QObjectPrivate *priv)
{
Q_ASSERT(priv);
Q_ASSERT(!priv->isDeletingChildren);
priv->declarativeData = new QQmlData(OwnsMemory);
return static_cast<QQmlData *>(priv->declarativeData);
}
QQmlPropertyCache::ConstPtr QQmlData::createPropertyCache(QObject *object)
{
QQmlData *ddata = QQmlData::get(object, /*create*/true);
ddata->propertyCache = QQmlMetaType::propertyCache(object, QTypeRevision {});
return ddata->propertyCache;
}
QT_END_NAMESPACE