diff options
Diffstat (limited to 'tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp')
-rw-r--r-- | tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp | 375 |
1 files changed, 347 insertions, 28 deletions
diff --git a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp index e9b2218497..cc7edb8bf2 100644 --- a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp +++ b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp @@ -1,11 +1,12 @@ // Copyright (C) 2020 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QObject> #include <QSignalSpy> #include <qtest.h> #include <qproperty.h> #include <private/qproperty_p.h> +#include <private/qobject_p.h> #if __has_include(<source_location>) && __cplusplus >= 202002L && !defined(Q_QDOC) #include <source_location> @@ -28,6 +29,7 @@ class tst_QProperty : public QObject { Q_OBJECT private slots: + void inheritQUntypedPropertyData(); void functorBinding(); void basicDependencies(); void multipleDependencies(); @@ -66,6 +68,7 @@ private slots: void quntypedBindableApi(); void readonlyConstQBindable(); void qobjectBindableManualNotify(); + void qobjectBindableReallocatedBindingStorage(); void qobjectBindableSignalTakingNewValue(); void testNewStuff(); @@ -74,6 +77,7 @@ private slots: void metaProperty(); void modifyObserverListWhileIterating(); + void noDoubleCapture(); void compatPropertyNoDobuleNotification(); void compatPropertySignals(); @@ -87,6 +91,8 @@ private slots: void noDoubleNotification(); void groupedNotifications(); void groupedNotificationConsistency(); + void bindingGroupMovingBindingData(); + void bindingGroupBindingDeleted(); void uninstalledBindingDoesNotEvaluate(); void notify(); @@ -101,8 +107,64 @@ private slots: void notifyAfterAllDepsGone(); void propertyAdaptorBinding(); + void propertyUpdateViaSignaledProperty(); + + void derefFromObserver(); }; +namespace { +template <class T> +constexpr auto isDerivedFromQUntypedPropertyData = std::is_base_of_v<QUntypedPropertyData, T>; + +template <typename Property> +constexpr auto isDerivedFromQUntypedPropertyDataFunc(const Property &property) +{ + Q_UNUSED(property); + return isDerivedFromQUntypedPropertyData<Property>; +} + +template <typename Property> +constexpr auto isDerivedFromQUntypedPropertyDataFunc(Property *property) +{ + Q_UNUSED(property); + return isDerivedFromQUntypedPropertyData<Property>; +} +} // namespace + +void tst_QProperty::inheritQUntypedPropertyData() +{ + class propertyPublic : public QUntypedPropertyData + { + }; + class propertyPrivate : private QUntypedPropertyData + { + }; + + // Compile time test + static_assert(isDerivedFromQUntypedPropertyData<propertyPublic>); + static_assert(isDerivedFromQUntypedPropertyData<propertyPrivate>); + static_assert(isDerivedFromQUntypedPropertyData<QPropertyData<int>>); + static_assert(isDerivedFromQUntypedPropertyData<QProperty<int>>); + + // Run time test + propertyPublic _propertyPublic; + propertyPrivate _propertyPrivate; + QPropertyData<int> qpropertyData; + QProperty<int> qproperty; + std::unique_ptr<propertyPublic> _propertyPublicPtr{ new propertyPublic }; + std::unique_ptr<propertyPrivate> _propertyPrivatePtr{ new propertyPrivate }; + std::unique_ptr<QPropertyData<int>> qpropertyDataPtr{ new QPropertyData<int> }; + std::unique_ptr<QProperty<int>> qpropertyPtr{ new QProperty<int> }; + QVERIFY(isDerivedFromQUntypedPropertyDataFunc(_propertyPublic)); + QVERIFY(isDerivedFromQUntypedPropertyDataFunc(_propertyPrivate)); + QVERIFY(isDerivedFromQUntypedPropertyDataFunc(qpropertyData)); + QVERIFY(isDerivedFromQUntypedPropertyDataFunc(qproperty)); + QVERIFY(isDerivedFromQUntypedPropertyDataFunc(_propertyPublicPtr.get())); + QVERIFY(isDerivedFromQUntypedPropertyDataFunc(_propertyPrivatePtr.get())); + QVERIFY(isDerivedFromQUntypedPropertyDataFunc(qpropertyDataPtr.get())); + QVERIFY(isDerivedFromQUntypedPropertyDataFunc(qpropertyPtr.get())); +} + void tst_QProperty::functorBinding() { QProperty<int> property([]() { return 42; }); @@ -255,6 +317,7 @@ void tst_QProperty::bindingAfterUse() void tst_QProperty::bindingFunctionDtorCalled() { + DtorCounter::counter = 0; DtorCounter dc; { QProperty<int> prop; @@ -1185,6 +1248,23 @@ void tst_QProperty::qobjectBindableManualNotify() QCOMPARE(object.foo(), 1); } + +struct ReallocObject : QObject { + ReallocObject() + { v.setBinding([this] { return x.value() + y.value() + z.value(); }); } + Q_OBJECT_BINDABLE_PROPERTY(ReallocObject, int, v) + Q_OBJECT_BINDABLE_PROPERTY(ReallocObject, int, x) + Q_OBJECT_BINDABLE_PROPERTY(ReallocObject, int, y) + Q_OBJECT_BINDABLE_PROPERTY(ReallocObject, int, z) +}; + +void tst_QProperty::qobjectBindableReallocatedBindingStorage() +{ + ReallocObject object; + object.x = 1; + QCOMPARE(object.v.value(), 1); +} + void tst_QProperty::qobjectBindableSignalTakingNewValue() { // Given an object of type MyQObject, @@ -1472,6 +1552,22 @@ void tst_QProperty::modifyObserverListWhileIterating() } } +void tst_QProperty::noDoubleCapture() +{ + QProperty<long long> size; + size = 3; + QProperty<int> max; + max.setBinding([&size]() -> int { + // each loop run attempts to capture size + for (int i = 0; i < size; ++i) {} + return size.value(); + }); + auto bindingPriv = QPropertyBindingPrivate::get(max.binding()); + QCOMPARE(bindingPriv->dependencyObserverCount, 1U); + size = 4; // should not crash + QCOMPARE(max.value(), 4); +} + class CompatPropertyTester : public QObject { Q_OBJECT @@ -1673,6 +1769,14 @@ class PropertyAdaptorTester : public QObject Q_PROPERTY(int foo1 READ foo WRITE setFoo) signals: + void dummySignal1(); + void dummySignal2(); + void dummySignal3(); + void dummySignal4(); + void dummySignal5(); + void dummySignal6(); + void dummySignal7(); + void dummySignal8(); void fooChanged(int newFoo); public slots: @@ -1701,9 +1805,11 @@ void tst_QProperty::propertyAdaptorBinding() // Check binding of non BINDABLE property PropertyAdaptorTester object; + // set up a dummy connection (needed to verify that the QBindable avoids an out-of-bounds read) + QObject::connect(&object, &PropertyAdaptorTester::dummySignal1, [](){}); + QBindable<int> binding(&object, "foo"); QObject::connect(&object, &PropertyAdaptorTester::fooChanged, &object, &PropertyAdaptorTester::fooHasChanged); - QBindable<int> binding(&object, "foo"); binding.setBinding([&]() { return source + 1; }); QCOMPARE(object.foo(), 6); QCOMPARE(object.fooChangedCount, 1); @@ -1779,13 +1885,14 @@ void tst_QProperty::propertyAdaptorBinding() QCOMPARE(object.fooChangedCount, 7); // Check update group - Qt::beginPropertyUpdateGroup(); - source.setValue(23); - source2.setValue(22); - QCOMPARE(object.foo(), 43); - QCOMPARE(dest1.value(), 43); - QCOMPARE(object.fooChangedCount, 7); - Qt::endPropertyUpdateGroup(); + { + const QScopedPropertyUpdateGroup guard; + source.setValue(23); + source2.setValue(22); + QCOMPARE(object.foo(), 43); + QCOMPARE(dest1.value(), 43); + QCOMPARE(object.fooChangedCount, 7); + } QCOMPARE(object.foo(), 45); QCOMPARE(dest1.value(), 45); QCOMPARE(object.fooChangedCount, 8); @@ -2082,27 +2189,29 @@ void tst_QProperty::groupedNotifications() QCOMPARE(nNotifications, 1); expected = 2; - Qt::beginPropertyUpdateGroup(); - a = 1; - QCOMPARE(b.value(), 0); - QCOMPARE(c.value(), 0); - QCOMPARE(d.value(), 0); - QCOMPARE(nNotifications, 1); - Qt::endPropertyUpdateGroup(); + { + const QScopedPropertyUpdateGroup guard; + a = 1; + QCOMPARE(b.value(), 0); + QCOMPARE(c.value(), 0); + QCOMPARE(d.value(), 0); + QCOMPARE(nNotifications, 1); + } QCOMPARE(b.value(), 1); QCOMPARE(c.value(), 1); QCOMPARE(e.value(), 2); QCOMPARE(nNotifications, 2); expected = 7; - Qt::beginPropertyUpdateGroup(); - a = 2; - d = 3; - QCOMPARE(b.value(), 1); - QCOMPARE(c.value(), 1); - QCOMPARE(d.value(), 3); - QCOMPARE(nNotifications, 2); - Qt::endPropertyUpdateGroup(); + { + const QScopedPropertyUpdateGroup guard; + a = 2; + d = 3; + QCOMPARE(b.value(), 1); + QCOMPARE(c.value(), 1); + QCOMPARE(d.value(), 3); + QCOMPARE(nNotifications, 2); + } QCOMPARE(b.value(), 2); QCOMPARE(c.value(), 2); QCOMPARE(e.value(), 7); @@ -2125,13 +2234,71 @@ void tst_QProperty::groupedNotificationConsistency() j = 1; QVERIFY(!areEqual); // value changed runs before j = 1 - Qt::beginPropertyUpdateGroup(); - i = 2; - j = 2; - Qt::endPropertyUpdateGroup(); + { + const QScopedPropertyUpdateGroup guard; + i = 2; + j = 2; + } QVERIFY(areEqual); // value changed runs after everything has been evaluated } +void tst_QProperty::bindingGroupMovingBindingData() +{ + auto tester = std::make_unique<ClassWithNotifiedProperty>(); + auto testerPriv = QObjectPrivate::get(tester.get()); + + auto dummyNotifier = tester->property.addNotifier([](){}); + auto bindingData = testerPriv->bindingStorage.bindingData(&tester->property); + QVERIFY(bindingData); // we have a notifier, so there should be binding data + + Qt::beginPropertyUpdateGroup(); + auto cleanup = qScopeGuard([](){ Qt::endPropertyUpdateGroup(); }); + tester->property = 42; + QCOMPARE(testerPriv->bindingStorage.bindingData(&tester->property), bindingData); + auto proxyData = QPropertyBindingDataPointer::proxyData(bindingData); + // as we've modified the property, we now should have a proxy for the delayed notification + QVERIFY(proxyData); + // trigger binding data reallocation + std::array<QUntypedPropertyData, 10> propertyDataArray; + for (auto&& data: propertyDataArray) + testerPriv->bindingStorage.bindingData(&data, true); + // binding data has moved + QVERIFY(testerPriv->bindingStorage.bindingData(&tester->property) != bindingData); + bindingData = testerPriv->bindingStorage.bindingData(&tester->property); + // the proxy data has been updated + QCOMPARE(proxyData->originalBindingData, bindingData); + + tester.reset(); + // the property data is gone, proxyData should have been informed + QCOMPARE(proxyData->originalBindingData, nullptr); + QVERIFY(proxyData); +} + +void tst_QProperty::bindingGroupBindingDeleted() +{ + auto deleter = std::make_unique<ClassWithNotifiedProperty>(); + auto toBeDeleted = std::make_unique<ClassWithNotifiedProperty>(); + + bool calledHandler = false; + deleter->property.setBinding([&](){ + int newValue = toBeDeleted->property; + if (newValue == 42) + toBeDeleted.reset(); + return newValue; + }); + auto handler = toBeDeleted->property.onValueChanged([&]() { calledHandler = true; } ); + { + Qt::beginPropertyUpdateGroup(); + auto cleanup = qScopeGuard([](){ Qt::endPropertyUpdateGroup(); }); + QVERIFY(toBeDeleted); + toBeDeleted->property = 42; + // ASAN should not complain here + } + QVERIFY(!toBeDeleted); + // the change notification is sent, even if the binding is deleted during evaluation + QVERIFY(calledHandler); +} + void tst_QProperty::uninstalledBindingDoesNotEvaluate() { QProperty<int> i; @@ -2206,17 +2373,24 @@ void tst_QProperty::selfBindingShouldNotCrash() void tst_QProperty::qpropertyAlias() { +#if QT_DEPRECATED_SINCE(6, 6) + QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED std::unique_ptr<QProperty<int>> i {new QProperty<int>}; QPropertyAlias<int> alias(i.get()); QVERIFY(alias.isValid()); alias.setValue(42); QCOMPARE(i->value(), 42); QProperty<int> j; + bool notifierCalled = false; + auto myNotifier = alias.addNotifier([&](){notifierCalled = true;}); i->setBinding([&]() -> int { return j; }); + QVERIFY(notifierCalled); j.setValue(42); QCOMPARE(alias.value(), 42); i.reset(); QVERIFY(!alias.isValid()); + QT_WARNING_POP +#endif } void tst_QProperty::scheduleNotify() @@ -2256,6 +2430,151 @@ void tst_QProperty::notifyAfterAllDepsGone() QCOMPARE(changeCounter, 2); } +class TestObject : public QObject +{ + Q_OBJECT + Q_PROPERTY(int signaled READ signaled WRITE setSignaled NOTIFY signaledChanged FINAL) + Q_PROPERTY(int bindable1 READ bindable1 WRITE setBindable1 BINDABLE bindable1Bindable NOTIFY bindable1Changed FINAL) + Q_PROPERTY(int bindable2 READ bindable2 WRITE setBindable2 BINDABLE bindable2Bindable NOTIFY bindable2Changed FINAL) + +public: + int signaled() const + { + return m_signaled; + } + + void setSignaled(int newSignaled) + { + if (m_signaled == newSignaled) + return; + m_signaled = newSignaled; + emit signaledChanged(); + } + + int bindable1() const + { + return m_bindable1; + } + + void setBindable1(int newBindable1) + { + if (m_bindable1 == newBindable1) + return; + m_bindable1 = newBindable1; + emit bindable1Changed(); + } + + QBindable<int> bindable1Bindable() + { + return QBindable<int>(&m_bindable1); + } + + int bindable2() const + { + return m_bindable2; + } + + void setBindable2(int newBindable2) + { + if (m_bindable2 == newBindable2) + return; + m_bindable2 = newBindable2; + emit bindable2Changed(); + } + + QBindable<int> bindable2Bindable() + { + return QBindable<int>(&m_bindable2); + } + +signals: + void signaledChanged(); + void bindable1Changed(); + void bindable2Changed(); + +private: + int m_signaled = 0; + Q_OBJECT_COMPAT_PROPERTY(TestObject, int, m_bindable1, &TestObject::setBindable1, &TestObject::bindable1Changed); + Q_OBJECT_COMPAT_PROPERTY(TestObject, int, m_bindable2, &TestObject::setBindable2, &TestObject::bindable2Changed); +}; + +void tst_QProperty::propertyUpdateViaSignaledProperty() +{ + TestObject o; + QProperty<int> rootTrigger; + QProperty<int> signalTrigger; + + o.bindable1Bindable().setBinding([&]() { + return rootTrigger.value(); + }); + + QObject::connect(&o, &TestObject::bindable1Changed, &o, [&]() { + // Signaled changes only once, doesn't actually depend on bindable1. + // In reality, there could be some complicated calculation behind this that changes + // on certain checkpoints, but not on every iteration. + o.setSignaled(40); + }); + + o.bindable2Bindable().setBinding([&]() { + return signalTrigger.value() - o.bindable1(); + }); + + QObject::connect(&o, &TestObject::signaledChanged, &o, [&]() { + signalTrigger.setValue(o.signaled()); + }); + + rootTrigger.setValue(2); + QCOMPARE(o.bindable1(), 2); + QCOMPARE(o.bindable2(), 38); + rootTrigger.setValue(3); + QCOMPARE(o.bindable1(), 3); + QCOMPARE(o.bindable2(), 37); + rootTrigger.setValue(4); + QCOMPARE(o.bindable1(), 4); + QCOMPARE(o.bindable2(), 36); +} + +void tst_QProperty::derefFromObserver() +{ + int triggered = 0; + QProperty<int> source(11); + + DtorCounter::counter = 0; + DtorCounter dc; + + QProperty<int> target([&triggered, &source, dc]() mutable { + dc.shouldIncrement = true; + return ++triggered + source.value(); + }); + QCOMPARE(triggered, 1); + + { + auto propObserver = std::make_unique<QPropertyObserver>(); + QPropertyObserverPointer propObserverPtr { propObserver.get() }; + propObserverPtr.setBindingToNotify(QPropertyBindingPrivate::get(target.binding())); + + QBindingObserverPtr bindingPtr(propObserver.get()); + + QCOMPARE(triggered, 1); + source = 25; + QCOMPARE(triggered, 2); + QCOMPARE(target, 27); + + target.setBinding([]() { return 8; }); + QCOMPARE(target, 8); + + // The QBindingObserverPtr still holds on to the binding. + QCOMPARE(dc.counter, 0); + } + + // The binding is actually gone now. + QCOMPARE(dc.counter, 1); + + source = 26; + QCOMPARE(triggered, 2); + QCOMPARE(target, 8); +} + QTEST_MAIN(tst_QProperty); #undef QT_SOURCE_LOCATION_NAMESPACE |