QML: Do not leak memory if QQmlData is manipulated from ctor

This creates another QQmlData object on the heap, which is rather
wasteful. However, we cannot really avoid it since it's all too easy to
trigger an operation like this.

If it happens, use the QQmlData it has created, and ignore the memory
we've allocated inline. While we're at it, make the memory ownership a
required argument to the ctor.

Pick-to: 6.5 6.6
Fixes: QTBUG-114186
Change-Id: I0568768c9ec13c94db79bb162c9eeb76f75f2a55
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Ulf Hermann 2023-06-07 13:21:09 +02:00
parent 9240878b2e
commit a3389d6bf2
7 changed files with 61 additions and 11 deletions

View File

@ -55,7 +55,9 @@ struct Binding;
class Q_QML_PRIVATE_EXPORT QQmlData : public QAbstractDeclarativeData
{
public:
QQmlData();
enum Ownership { DoesNotOwnMemory, OwnsMemory };
QQmlData(Ownership ownership);
~QQmlData();
static inline void init() {
@ -295,7 +297,7 @@ private:
Q_NEVER_INLINE BindingBitsType *growBits(QObject *obj, int bit);
Q_DISABLE_COPY(QQmlData);
Q_DISABLE_COPY_MOVE(QQmlData);
};
bool QQmlData::wasDeleted(const QObjectPrivate *priv)

View File

@ -255,8 +255,8 @@ void QQmlPrivate::qdeclarativeelement_destructor(QObject *o)
}
}
QQmlData::QQmlData()
: ownMemory(true), indestructible(true), explicitIndestructibleSet(false),
QQmlData::QQmlData(Ownership ownership)
: ownMemory(ownership == OwnsMemory), indestructible(true), explicitIndestructibleSet(false),
hasTaintedV4Object(false), isQueuedForDeletion(false), rootObjectInCreation(false),
hasInterceptorMetaObject(false), hasVMEMetaObject(false), hasConstWrapper(false),
bindingBitsArraySize(InlineBindingArraySize), notifyList(nullptr),
@ -1525,7 +1525,7 @@ QQmlData *QQmlData::createQQmlData(QObjectPrivate *priv)
{
Q_ASSERT(priv);
Q_ASSERT(!priv->isDeletingChildren);
priv->declarativeData = new QQmlData;
priv->declarativeData = new QQmlData(OwnsMemory);
return static_cast<QQmlData *>(priv->declarativeData);
}

View File

@ -491,11 +491,10 @@ QObject *QQmlType::createWithQQmlData() const
auto instance = create(&ddataMemory, sizeof(QQmlData));
if (!instance)
return nullptr;
QQmlData *ddata = new (ddataMemory) QQmlData;
ddata->ownMemory = false;
QObjectPrivate* p = QObjectPrivate::get(instance);
Q_ASSERT(!p->isDeletingChildren);
p->declarativeData = ddata;
if (!p->declarativeData)
p->declarativeData = new (ddataMemory) QQmlData(QQmlData::DoesNotOwnMemory);
return instance;
}

View File

@ -319,9 +319,12 @@ QObject *ListModel::getOrCreateModelObject(QQmlListModel *model, int elementInde
void *memory = operator new(sizeof(QObject) + sizeof(QQmlData));
void *ddataMemory = ((char *)memory) + sizeof(QObject);
e->m_objectCache = new (memory) QObject;
QQmlData *ddata = new (ddataMemory) QQmlData;
ddata->ownMemory = false;
QObjectPrivate::get(e->m_objectCache)->declarativeData = ddata;
const QAbstractDeclarativeData *old = std::exchange(
QObjectPrivate::get(e->m_objectCache)->declarativeData,
new (ddataMemory) QQmlData(QQmlData::DoesNotOwnMemory));
Q_ASSERT(!old); // QObject should really not manipulate QQmlData
(void)new ModelNodeMetaObject(e->m_objectCache, model, elementIndex);
}
return e->m_objectCache;

View File

@ -159,6 +159,7 @@ void registerTypes()
qmlRegisterTypesAndRevisions<Greeter>("QmlOtherThis", 1);
qmlRegisterTypesAndRevisions<BirthdayParty>("People", 1);
qmlRegisterTypesAndRevisions<AttachedInCtor>("Test", 1);
}
QVariant myCustomVariantTypeConverter(const QString &data)

View File

@ -18,6 +18,8 @@
#include <QtQml/qqmlpropertyvaluesource.h>
#include <QtQml/qqmlscriptstring.h>
#include <QtQml/qqmlproperty.h>
#include <private/qqmlcomponentattached_p.h>
#include <private/qqmlcustomparser_p.h>
QVariant myCustomVariantTypeConverter(const QString &data);
@ -2625,6 +2627,32 @@ public:
}
};
class Attachment : public QObject {
Q_OBJECT
public:
Attachment(QObject *parent = nullptr) : QObject(parent) {}
};
class AttachedInCtor : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_ATTACHED(Attachment)
public:
AttachedInCtor(QObject *parent = nullptr)
: QObject(parent)
{
attached = qmlAttachedPropertiesObject<AttachedInCtor>(this, true);
}
static Attachment *qmlAttachedProperties(QObject *object) {
return new Attachment(object);
}
QObject *attached = nullptr;
};
class BirthdayParty : public QObject
{
Q_OBJECT

View File

@ -420,6 +420,8 @@ private slots:
void variantObjectList();
void jitExceptions();
void attachedInCtor();
private:
QQmlEngine engine;
QStringList defaultImportPathList;
@ -8076,6 +8078,21 @@ void tst_qqmllanguage::jitExceptions()
QVERIFY(!o.isNull());
}
void tst_qqmllanguage::attachedInCtor()
{
QQmlEngine e;
QQmlComponent c(&e);
c.setData(R"(
import Test
AttachedInCtor {}
)", QUrl());
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QScopedPointer<QObject> o(c.create());
AttachedInCtor *a = qobject_cast<AttachedInCtor *>(o.data());
QVERIFY(a->attached);
QCOMPARE(a->attached, qmlAttachedPropertiesObject<AttachedInCtor>(a, false));
}
QTEST_MAIN(tst_qqmllanguage)
#include "tst_qqmllanguage.moc"