diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp index e2c5c7bff38..f4c026235a5 100644 --- a/src/corelib/kernel/qproperty.cpp +++ b/src/corelib/kernel/qproperty.cpp @@ -250,6 +250,10 @@ QUntypedPropertyBinding QPropertyBindingData::setBinding(const QUntypedPropertyB if (auto *existingBinding = d.bindingPtr()) { if (existingBinding == newBinding.data()) return QUntypedPropertyBinding(static_cast(oldBinding.data())); + if (existingBinding->isEagerlyUpdating()) { + existingBinding->setError({QPropertyBindingError::BindingLoop, QStringLiteral("Binding set during binding evaluation!")}); + return QUntypedPropertyBinding(static_cast(oldBinding.data())); + } oldBinding = QPropertyBindingPrivatePtr(existingBinding); observer = static_cast(oldBinding.data())->takeObservers(); static_cast(oldBinding.data())->unlinkAndDeref(); @@ -269,9 +273,11 @@ QUntypedPropertyBinding QPropertyBindingData::setBinding(const QUntypedPropertyB newBindingRaw->prependObserver(observer); newBindingRaw->setStaticObserver(staticObserverCallback, guardCallback); if (newBindingRaw->requiresEagerEvaluation()) { + newBindingRaw->setEagerlyUpdating(true); auto changed = newBindingRaw->evaluateIfDirtyAndReturnTrueIfValueChanged(propertyDataPtr); if (changed) observer.notify(newBindingRaw, propertyDataPtr, /*alreadyKnownToHaveChanged=*/true); + newBindingRaw->setEagerlyUpdating(false); } } else if (observer) { d.setObservers(observer.ptr); diff --git a/src/corelib/kernel/qproperty_p.h b/src/corelib/kernel/qproperty_p.h index d5feef10080..13bd40fad7d 100644 --- a/src/corelib/kernel/qproperty_p.h +++ b/src/corelib/kernel/qproperty_p.h @@ -226,6 +226,9 @@ public: // public because the auto-tests access it, too. size_t dependencyObserverCount = 0; + bool isEagerlyUpdating() {return eagerlyUpdating;} + void setEagerlyUpdating(bool b) {eagerlyUpdating = b;} + QPropertyBindingPrivate(QMetaType metaType, const QtPrivate::BindingFunctionVTable *vtable, const QPropertyBindingSourceLocation &location, bool isQQmlPropertyBinding=false) : hasBindingWrapper(false) diff --git a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp index 5ae92e89819..ca2de478cff 100644 --- a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp +++ b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp @@ -462,6 +462,7 @@ class BindingLoopTester : public QObject eagerData2.setBinding(Qt::makePropertyBinding([&](){ return eagerData.value() + 1; } ) ); i->setValue(42); } + BindingLoopTester() {} int eagerProp() {return eagerData.value();} void setEagerProp(int i) { eagerData = i; } @@ -495,11 +496,21 @@ void tst_QProperty::bindingLoop() QCOMPARE(secondProp.binding().error().type(), QPropertyBindingError::BindingLoop); - QProperty i; - BindingLoopTester tester(&i); - QCOMPARE(tester.bindableEagerProp().binding().error().type(), QPropertyBindingError::BindingLoop); - QEXPECT_FAIL("", "Only the first property in a dependency cycle is set to the error state", Continue); - QCOMPARE(tester.bindableEagerProp2().binding().error().type(), QPropertyBindingError::BindingLoop); + { + QProperty i; + BindingLoopTester tester(&i); + QCOMPARE(tester.bindableEagerProp().binding().error().type(), QPropertyBindingError::BindingLoop); + QCOMPARE(tester.bindableEagerProp2().binding().error().type(), QPropertyBindingError::BindingLoop); + } + { + BindingLoopTester tester; + auto handler = tester.bindableEagerProp().onValueChanged([&]() { + tester.bindableEagerProp().setBinding([](){return 42;}); + }); + tester.bindableEagerProp().setBinding([]() {return 42;}); + QCOMPARE(tester.bindableEagerProp().binding().error().type(), QPropertyBindingError::BindingLoop); + QCOMPARE(tester.bindableEagerProp().binding().error().description(), "Binding set during binding evaluation!"); + } } class ReallocTester : public QObject