From f83f4badce46b5572fa4c85ba2c3420a19d28450 Mon Sep 17 00:00:00 2001 From: Andrei Golubev Date: Fri, 15 Jan 2021 09:42:19 +0100 Subject: Add new special QObjectPrivate::{connect, disconnect} for QML Original QML-specific connection mechanism ignores the receiver argument and uses sender as receiver. This causes uncontrollable memory growth in certain cases as connections on receiver persist even after receiver is destroyed New connect() with receiver parameter uses underlying API correctly, disconnect is provided for the symmetry (not sure it's really needed) Task-number: QTBUG-86368 Change-Id: I4580d75b617cb2c4dfb971a4dfb8e943e325572b Reviewed-by: Fabian Kosmale Reviewed-by: Thiago Macieira (cherry picked from commit 6f520abdab7120789800208dde837b3836f762cc) Reviewed-by: Qt Cherry-pick Bot --- src/corelib/kernel/qobject.cpp | 49 +++++++++++++++++++++-- src/corelib/kernel/qobject_p.h | 6 +++ tests/auto/corelib/kernel/qobject/tst_qobject.cpp | 28 +++++++++++++ 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp index 68082910dc..3eda1b384e 100644 --- a/src/corelib/kernel/qobject.cpp +++ b/src/corelib/kernel/qobject.cpp @@ -5062,11 +5062,32 @@ bool QObject::disconnectImpl(const QObject *sender, void **signal, const QObject /*! \internal - Used by QML to connect a signal by index to a slot implemented in JavaScript (wrapped in a custom QSlotObjectBase subclass). + Used by QML to connect a signal by index to a slot implemented in JavaScript + (wrapped in a custom QSlotObjectBase subclass). + + This version of connect assumes that sender and receiver are the same object. The signal_index is an index relative to the number of methods. */ QMetaObject::Connection QObjectPrivate::connect(const QObject *sender, int signal_index, QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type) +{ + return QObjectPrivate::connect(sender, signal_index, sender, slotObj, type); +} + +/*! + \internal + Used by QML to connect a signal by index to a slot implemented in JavaScript + (wrapped in a custom QSlotObjectBase subclass). + + This is an overload that should be used when \a sender and \a receiver are + different objects. + + The signal_index is an index relative to the number of methods. + */ +QMetaObject::Connection QObjectPrivate::connect(const QObject *sender, int signal_index, + const QObject *receiver, + QtPrivate::QSlotObjectBase *slotObj, + Qt::ConnectionType type) { if (!sender) { qCWarning(lcConnect, "QObject::connect: invalid nullptr parameter"); @@ -5077,7 +5098,8 @@ QMetaObject::Connection QObjectPrivate::connect(const QObject *sender, int signa const QMetaObject *senderMetaObject = sender->metaObject(); signal_index = methodIndexToSignalIndex(&senderMetaObject, signal_index); - return QObjectPrivate::connectImpl(sender, signal_index, sender, /*slot*/nullptr, slotObj, type, /*types*/nullptr, senderMetaObject); + return QObjectPrivate::connectImpl(sender, signal_index, receiver, /*slot*/ nullptr, slotObj, + type, /*types*/ nullptr, senderMetaObject); } /*! @@ -5085,13 +5107,34 @@ QMetaObject::Connection QObjectPrivate::connect(const QObject *sender, int signa Used by QML to disconnect a signal by index that's connected to a slot implemented in JavaScript (wrapped in a custom QSlotObjectBase subclass) In the QML case the slot is not a pointer to a pointer to the function to disconnect, but instead it is a pointer to an array of internal values required for the disconnect. + + This version of disconnect assumes that sender and receiver are the same object. */ bool QObjectPrivate::disconnect(const QObject *sender, int signal_index, void **slot) +{ + return QObjectPrivate::disconnect(sender, signal_index, sender, slot); +} + +/*! + \internal + + Used by QML to disconnect a signal by index that's connected to a slot + implemented in JavaScript (wrapped in a custom QSlotObjectBase subclass) In the + QML case the slot is not a pointer to a pointer to the function to disconnect, + but instead it is a pointer to an array of internal values required for the + disconnect. + + This is an overload that should be used when \a sender and \a receiver are + different objects. + */ +bool QObjectPrivate::disconnect(const QObject *sender, int signal_index, const QObject *receiver, + void **slot) { const QMetaObject *senderMetaObject = sender->metaObject(); signal_index = methodIndexToSignalIndex(&senderMetaObject, signal_index); - return QMetaObjectPrivate::disconnect(sender, signal_index, senderMetaObject, sender, -1, slot); + return QMetaObjectPrivate::disconnect(sender, signal_index, senderMetaObject, receiver, -1, + slot); } /*! diff --git a/src/corelib/kernel/qobject_p.h b/src/corelib/kernel/qobject_p.h index a9626a8ab0..4483e2b6ce 100644 --- a/src/corelib/kernel/qobject_p.h +++ b/src/corelib/kernel/qobject_p.h @@ -353,7 +353,13 @@ public: QtPrivate::QSlotObjectBase *slotObj, int type, const int *types, const QMetaObject *senderMetaObject); static QMetaObject::Connection connect(const QObject *sender, int signal_index, QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type); + static QMetaObject::Connection connect(const QObject *sender, int signal_index, + const QObject *receiver, + QtPrivate::QSlotObjectBase *slotObj, + Qt::ConnectionType type); static bool disconnect(const QObject *sender, int signal_index, void **slot); + static bool disconnect(const QObject *sender, int signal_index, const QObject *receiver, + void **slot); static bool disconnect(Connection *c); void ensureConnectionData() diff --git a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp index 5cf1f0e50f..68d90987e1 100644 --- a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp +++ b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp @@ -149,6 +149,7 @@ private slots: void connectBase(); void connectWarnings(); void qmlConnect(); + void qmlConnectToQObjectReceiver(); void exceptions(); void noDeclarativeParentChangedOnDestruction(); void deleteLaterInAboutToBlockHandler(); @@ -6787,6 +6788,33 @@ void tst_QObject::qmlConnect() #endif } +void tst_QObject::qmlConnectToQObjectReceiver() +{ +#ifdef QT_BUILD_INTERNAL + SenderObject sender; + QScopedPointer receiver(new QObject); + QmlReceiver *slotObject = new QmlReceiver; + slotObject->magic = slotObject; + slotObject->ref(); // extra ref so that slot object is not implicitly deleted + + QVERIFY(QObjectPrivate::connect(&sender, sender.metaObject()->indexOfSignal("signal1()"), + receiver.get(), slotObject, Qt::AutoConnection)); + + QCOMPARE(slotObject->callCount, 0); + sender.emitSignal1(); + QCOMPARE(slotObject->callCount, 1); + + receiver.reset(); // this should disconnect the slotObject + + sender.emitSignal1(); + QCOMPARE(slotObject->callCount, 1); + + slotObject->destroyIfLastRef(); +#else + QSKIP("Needs QT_BUILD_INTERNAL"); +#endif +} + #ifndef QT_NO_EXCEPTIONS class ObjectException : public std::exception { }; -- cgit v1.2.3