diff options
Diffstat (limited to 'src/corelib/kernel/qobject.cpp')
-rw-r--r-- | src/corelib/kernel/qobject.cpp | 89 |
1 files changed, 67 insertions, 22 deletions
diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp index 9385419015..4fccf8dd56 100644 --- a/src/corelib/kernel/qobject.cpp +++ b/src/corelib/kernel/qobject.cpp @@ -71,6 +71,7 @@ #include <qtcore_tracepoints_p.h> #include <new> +#include <mutex> #include <ctype.h> #include <limits.h> @@ -390,8 +391,14 @@ void QObjectPrivate::ConnectionData::removeConnection(QObjectPrivate::Connection Q_ASSERT(c != orphaned.loadRelaxed()); // add c to orphanedConnections - c->nextInOrphanList = orphaned.loadRelaxed(); - orphaned.storeRelaxed(c); + Connection *o = nullptr; + /* No ABA issue here: When adding a node, we only care about the list head, it doesn't + * matter if the tail changes. + */ + do { + o = orphaned.loadRelaxed(); + c->nextInOrphanList = o; + } while (!orphaned.testAndSetRelease(o, c)); #ifndef QT_NO_DEBUG found = false; @@ -406,21 +413,32 @@ void QObjectPrivate::ConnectionData::removeConnection(QObjectPrivate::Connection } -void QObjectPrivate::ConnectionData::cleanOrphanedConnectionsImpl(QObject *sender) +void QObjectPrivate::ConnectionData::cleanOrphanedConnectionsImpl(QObject *sender, LockPolicy lockPolicy) { + QBasicMutex *senderMutex = signalSlotLock(sender); ConnectionOrSignalVector *c = nullptr; { - QBasicMutexLocker l(signalSlotLock(sender)); + std::unique_lock<QBasicMutex> lock(*senderMutex, std::defer_lock_t{}); + if (lockPolicy == NeedToLock) + lock.lock(); if (ref.loadAcquire() > 1) return; // Since ref == 1, no activate() is in process since we locked the mutex. That implies, // that nothing can reference the orphaned connection objects anymore and they can // be safely deleted - c = orphaned.loadRelaxed(); - orphaned.storeRelaxed(nullptr); + c = orphaned.fetchAndStoreRelaxed(nullptr); + } + if (c) { + // Deleting c might run arbitrary user code, so we must not hold the lock + if (lockPolicy == AlreadyLockedAndTemporarilyReleasingLock) { + senderMutex->unlock(); + deleteOrphaned(c); + senderMutex->lock(); + } else { + deleteOrphaned(c); + } } - deleteOrphaned(c); } void QObjectPrivate::ConnectionData::deleteOrphaned(QObjectPrivate::ConnectionOrSignalVector *o) @@ -992,7 +1010,7 @@ QObject::~QObject() emit destroyed(this); } - if (d->declarativeData) { + if (!d->isDeletingChildren && d->declarativeData) { if (static_cast<QAbstractDeclarativeDataImpl*>(d->declarativeData)->ownedByQml1) { if (QAbstractDeclarativeData::destroyed_qml1) QAbstractDeclarativeData::destroyed_qml1(d->declarativeData, this); @@ -1022,7 +1040,7 @@ QObject::~QObject() QBasicMutex *m = signalSlotLock(c->receiver.loadRelaxed()); bool needToUnlock = QOrderedMutexLocker::relock(signalSlotMutex, m); - if (c->receiver.loadAcquire()) { + if (c == connectionList.first.loadAcquire() && c->receiver.loadAcquire()) { cd->removeConnection(c); Q_ASSERT(connectionList.first.loadRelaxed() != c); } @@ -1060,14 +1078,29 @@ QObject::~QObject() } senderData->removeConnection(node); + /* + When we unlock, another thread has the chance to delete/modify sender data. + Thus we need to call cleanOrphanedConnections before unlocking. We use the + variant of the function which assumes that the lock is already held to avoid + a deadlock. + We need to hold m, the sender lock. Considering that we might execute arbitrary user + code, we should already release the signalSlotMutex here – unless they are the same. + */ + const bool locksAreTheSame = signalSlotMutex == m; + if (!locksAreTheSame) + locker.unlock(); + senderData->cleanOrphanedConnections( + sender, + QObjectPrivate::ConnectionData::AlreadyLockedAndTemporarilyReleasingLock + ); if (needToUnlock) m->unlock(); - if (slotObj) { + if (locksAreTheSame) // otherwise already unlocked locker.unlock(); + if (slotObj) slotObj->destroyIfLastRef(); - locker.relock(); - } + locker.relock(); } // invalidate all connections on the object and make sure @@ -1547,7 +1580,7 @@ void QObject::moveToThread(QThread *targetThread) QThreadData *currentData = QThreadData::current(); QThreadData *targetData = targetThread ? QThreadData::get2(targetThread) : nullptr; - QThreadData *thisThreadData = d->threadData.loadRelaxed(); + QThreadData *thisThreadData = d->threadData.loadAcquire(); if (!thisThreadData->thread.loadAcquire() && currentData == targetData) { // one exception to the rule: we allow moving objects with no thread affinity to the current thread currentData = d->threadData; @@ -2292,7 +2325,7 @@ void QObject::removeEventFilter(QObject *obj) event loop was still running: the Qt event loop will delete those objects as soon as the new nested event loop starts. - \b{Note:} It is safe to call this function more than once; when the + \note It is safe to call this function more than once; when the first deferred deletion event is delivered, any pending events for the object are removed from the event queue. @@ -2300,6 +2333,10 @@ void QObject::removeEventFilter(QObject *obj) */ void QObject::deleteLater() { +#ifdef QT_DEBUG + if (qApp == this) + qWarning("You are deferring the delete of QCoreApplication, this may not work as expected."); +#endif QCoreApplication::postEvent(this, new QDeferredDeleteEvent()); } @@ -2423,6 +2460,7 @@ static bool check_method_code(int code, const QObject *object, return true; } +Q_DECL_COLD_FUNCTION static void err_method_notfound(const QObject *object, const char *method, const char *func) { @@ -2444,6 +2482,7 @@ static void err_method_notfound(const QObject *object, } +Q_DECL_COLD_FUNCTION static void err_info_about_objects(const char * func, const QObject * sender, const QObject * receiver) @@ -2583,7 +2622,7 @@ int QObject::receivers(const char *signal) const if (!d->isSignalConnected(signal_index)) return receivers; - if (d->declarativeData && QAbstractDeclarativeData::receivers) { + if (!d->isDeletingChildren && d->declarativeData && QAbstractDeclarativeData::receivers) { receivers += QAbstractDeclarativeData::receivers(d->declarativeData, this, signal_index); } @@ -4175,7 +4214,7 @@ void QObject::dumpObjectTree() /*! Dumps a tree of children to the debug output. - \note before Qt 5.9, this function was not const. + \note Before Qt 5.9, this function was not const. \sa dumpObjectInfo() */ @@ -4206,7 +4245,7 @@ void QObject::dumpObjectInfo() Dumps information about signal connections, etc. for this object to the debug output. - \note before Qt 5.9, this function was not const. + \note Before Qt 5.9, this function was not const. \sa dumpObjectTree() */ @@ -5106,12 +5145,18 @@ bool QObject::disconnect(const QMetaObject::Connection &connection) connections = QObjectPrivate::get(c->sender)->connections.loadRelaxed(); Q_ASSERT(connections); connections->removeConnection(c); - } - connections->cleanOrphanedConnections(c->sender); - - c->sender->disconnectNotify(QMetaObjectPrivate::signal(c->sender->metaObject(), - c->signal_index)); + c->sender->disconnectNotify(QMetaObjectPrivate::signal(c->sender->metaObject(), c->signal_index)); + // We must not hold the receiver mutex, else we risk dead-locking; we also only need the sender mutex + // It is however vital to hold the senderMutex before calling cleanOrphanedConnections, as otherwise + // another thread might modify/delete the connection + if (receiverMutex != senderMutex) { + receiverMutex->unlock(); + } + connections->cleanOrphanedConnections(c->sender, QObjectPrivate::ConnectionData::AlreadyLockedAndTemporarilyReleasingLock); + senderMutex->unlock(); // now both sender and receiver mutex have been manually unlocked + locker.dismiss(); // so we dismiss the QOrderedMutexLocker + } const_cast<QMetaObject::Connection &>(connection).d_ptr = nullptr; c->deref(); // has been removed from the QMetaObject::Connection object |