From f6fb118c943ca4e54509b3c4c8aaafcdbb88f031 Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Sat, 5 Jun 2021 20:10:56 +0200 Subject: [PATCH] QProperty: Do not involve semi-destroyed QObjects in bindings Once we're in ~QObject, only methods of QObject are still valid. Notably, no setter of any derived class is still valid. Thus, to be safe we must no longer react to binding changes of those properties. To ensure that this happens for QObjectCompatProperty properties, we explicitly clear the binding storage. Fixes a particles3d example crash. Change-Id: I10d2bfa5e96621ce039d751cffaf3ac41893623e Reviewed-by: Laszlo Agocs Reviewed-by: Ulf Hermann --- src/corelib/kernel/qobject.cpp | 9 ++++++ src/corelib/kernel/qobject_p.h | 2 ++ src/corelib/kernel/qproperty.cpp | 6 ++++ src/corelib/kernel/qproperty.h | 2 ++ .../kernel/qproperty/tst_qproperty.cpp | 30 +++++++++++++++++++ 5 files changed, 49 insertions(+) diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp index f3ea22d25db..fafcd38417e 100644 --- a/src/corelib/kernel/qobject.cpp +++ b/src/corelib/kernel/qobject.cpp @@ -943,6 +943,11 @@ QObject::QObject(QObjectPrivate &dd, QObject *parent) Q_TRACE(QObject_ctor, this); } +void QObjectPrivate::clearBindingStorage() +{ + bindingStorage.clear(); +} + /*! Destroys the object, deleting all its child objects. @@ -973,6 +978,10 @@ QObject::~QObject() d->wasDeleted = true; d->blockSig = 0; // unblock signals so we always emit destroyed() + // If we reached this point, we need to clear the binding data + // as the corresponding properties are no longer useful + d->clearBindingStorage(); + QtSharedPointer::ExternalRefCountData *sharedRefcount = d->sharedRefcount.loadRelaxed(); if (sharedRefcount) { if (sharedRefcount->strongref.loadRelaxed() > 0) { diff --git a/src/corelib/kernel/qobject_p.h b/src/corelib/kernel/qobject_p.h index 4f38796c81c..6d5044e99c8 100644 --- a/src/corelib/kernel/qobject_p.h +++ b/src/corelib/kernel/qobject_p.h @@ -326,6 +326,8 @@ public: QObjectPrivate(int version = QObjectPrivateVersion); virtual ~QObjectPrivate(); void deleteChildren(); + // used to clear binding storage early in ~QObject + void clearBindingStorage(); inline void checkForIncompatibleLibraryVersion(int version) const; diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp index 43fc84159a0..0df73ad6393 100644 --- a/src/corelib/kernel/qproperty.cpp +++ b/src/corelib/kernel/qproperty.cpp @@ -2138,6 +2138,12 @@ QBindingStorage::~QBindingStorage() QBindingStoragePrivate(d).destroy(); } +void QBindingStorage::clear() +{ + QBindingStoragePrivate(d).destroy(); + d = nullptr; +} + // ### Unused, retained for BC with 6.0 void QBindingStorage::maybeUpdateBindingAndRegister_helper(const QUntypedPropertyData *data) const { diff --git a/src/corelib/kernel/qproperty.h b/src/corelib/kernel/qproperty.h index 5deb8fa9b79..9c002f378ff 100644 --- a/src/corelib/kernel/qproperty.h +++ b/src/corelib/kernel/qproperty.h @@ -945,6 +945,7 @@ class Q_CORE_EXPORT QBindingStorage template friend class QObjectCompatProperty; + friend class QObjectPrivate; public: QBindingStorage(); ~QBindingStorage(); @@ -973,6 +974,7 @@ public: return bindingData_helper(data, create); } private: + void clear(); void registerDependency_helper(const QUntypedPropertyData *data) const; // ### Unused, but keep for BC void maybeUpdateBindingAndRegister_helper(const QUntypedPropertyData *data) const; diff --git a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp index d1dc0ceacf9..735d45da2a5 100644 --- a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp +++ b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp @@ -57,6 +57,7 @@ private slots: void basicDependencies(); void multipleDependencies(); void bindingWithDeletedDependency(); + void dependencyChangeDuringDestruction(); void recursiveDependency(); void bindingAfterUse(); void bindingFunctionDtorCalled(); @@ -199,6 +200,35 @@ void tst_QProperty::bindingWithDeletedDependency() QCOMPARE(propertySelector.value(), staticProperty.value()); } +class ChangeDuringDtorTester : public QObject +{ + Q_OBJECT + Q_PROPERTY(int prop READ prop WRITE setProp BINDABLE bindableProp) + +public: + void setProp(int i) { m_prop = i;} + int prop() const { return m_prop; } + QBindable bindableProp() { return &m_prop; } +private: + Q_OBJECT_COMPAT_PROPERTY(ChangeDuringDtorTester, int, m_prop, &ChangeDuringDtorTester::setProp) +}; + +void tst_QProperty::dependencyChangeDuringDestruction() +{ + auto tester = std::make_unique(); + QProperty iprop {42}; + tester->bindableProp().setBinding(Qt::makePropertyBinding(iprop)); + QObject::connect(tester.get(), &QObject::destroyed, [&](){ + iprop = 12; + }); + bool failed = false; + auto handler = tester->bindableProp().onValueChanged([&](){ + failed = true; + }); + tester.reset(); + QVERIFY(!failed); +} + void tst_QProperty::recursiveDependency() { QProperty first(1);