From 2f3cd3b1a8e57bcf9db2b9e9af01cfd3ad141108 Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Thu, 1 Oct 2020 09:20:34 +0200 Subject: Handle notifier list modification during iteration As propertyobservers can execute arbitrarily complex code, they can also modify the obsever list in multiple ways. To protect against list corruption resulting from this, we introduce a protection scheme which makes the list resilient against modification. A detailed description of the scheme can be found as a comment in QPropertyObserverPointer::notify. Task-number: QTBUG-87153 Change-Id: I9bb49e457165ddc1e4c8bbdf3d3c9fbf5ff27e94 Reviewed-by: Qt CI Bot Reviewed-by: Lars Knoll --- .../corelib/kernel/qproperty/tst_qproperty.cpp | 53 ++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'tests/auto/corelib/kernel/qproperty') diff --git a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp index 344ced9101..55ef3b12cb 100644 --- a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp +++ b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp @@ -76,6 +76,8 @@ private slots: void compatBindings(); void metaProperty(); void aliasOnMetaProperty(); + + void modifyObserverListWhileIterating(); }; void tst_QProperty::functorBinding() @@ -1248,6 +1250,57 @@ void tst_QProperty::aliasOnMetaProperty() QCOMPARE(alias.value(), 100); } +void tst_QProperty::modifyObserverListWhileIterating() +{ + struct DestructingObserver : QPropertyObserver { + DestructingObserver() : QPropertyObserver([](QPropertyObserver *self, QUntypedPropertyData *) { + auto This = static_cast(self); + (*This)(); + }), m_target(this){} + void operator()() { + (*counter)++; + std::destroy_at(m_target); + } + DestructingObserver *m_target; + int *counter = nullptr; + }; + union ObserverOrUninit { + DestructingObserver observer = {}; + char* memory; + ~ObserverOrUninit() {} + }; + { + // observer deletes itself while running the notification + // while explicitly calling the destructor is rather unusual + // it is completely plausible for this to happen because the object to which a + // propertyobserver belongs has been destroyed + ObserverOrUninit data; + int counter = 0; + data.observer.counter = &counter; + QProperty prop; + QUntypedBindable bindableProp(&prop); + bindableProp.observe(&data.observer); + prop = 42; // should not crash + QCOMPARE(counter, 1); + } + { + // observer deletes the next observer in the list + ObserverOrUninit data1; + ObserverOrUninit data2; + QProperty prop; + QUntypedBindable bindableProp(&prop); + bindableProp.observe(&data1.observer); + bindableProp.observe(&data2.observer); + int counter = 0; + data1.observer.m_target = &data2.observer; + data1.observer.counter = &counter; + data2.observer.m_target = &data1.observer; + data2.observer.counter = &counter; + prop = 42; // should not crash + QCOMPARE(counter, 1); // only one trigger should run as the other has been deleted + } +} + QTEST_MAIN(tst_QProperty); #include "tst_qproperty.moc" -- cgit v1.2.3