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
Pick-to: 5.15 6.0
Change-Id: I4580d75b617cb2c4dfb971a4dfb8e943e325572b
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Andrei Golubev 2021-01-15 09:42:19 +01:00
parent 5a0e5521e4
commit 6f520abdab
3 changed files with 80 additions and 3 deletions

View File

@ -5061,11 +5061,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");
@ -5076,7 +5097,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);
}
/*!
@ -5084,13 +5106,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);
}
/*!

View File

@ -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()

View File

@ -152,6 +152,7 @@ private slots:
void connectBase();
void connectWarnings();
void qmlConnect();
void qmlConnectToQObjectReceiver();
void exceptions();
void noDeclarativeParentChangedOnDestruction();
void deleteLaterInAboutToBlockHandler();
@ -6790,6 +6791,33 @@ void tst_QObject::qmlConnect()
#endif
}
void tst_QObject::qmlConnectToQObjectReceiver()
{
#ifdef QT_BUILD_INTERNAL
SenderObject sender;
QScopedPointer<QObject> 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 { };