diff options
author | MÃ¥rten Nordheim <marten.nordheim@qt.io> | 2021-10-26 15:04:19 +0200 |
---|---|---|
committer | Marc Mutz <marc.mutz@qt.io> | 2021-11-25 08:52:39 +0000 |
commit | 8f8775adf3c4fbba1bd3c120a228351d46f50127 (patch) | |
tree | 52655d992735025a9d0d1c421838f5a7c67a7f00 /tests/auto | |
parent | a92619d950fdbf803cdc8c8ca8e75c1c82abb23f (diff) |
QHash: fix thread race around references and detaching
If we detach from a shared hash while holding a reference to a key from
said shared hash then there is no guarantee for how long the reference
is valid (given a multi-thread environment).
Pick-to: 6.2
Change-Id: Ifb610753d24faca63e2c0eb8836c78d55a229001
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
Diffstat (limited to 'tests/auto')
-rw-r--r-- | tests/auto/corelib/tools/qhash/tst_qhash.cpp | 56 |
1 files changed, 56 insertions, 0 deletions
diff --git a/tests/auto/corelib/tools/qhash/tst_qhash.cpp b/tests/auto/corelib/tools/qhash/tst_qhash.cpp index 7e297c0119..7cc4d3d0f0 100644 --- a/tests/auto/corelib/tools/qhash/tst_qhash.cpp +++ b/tests/auto/corelib/tools/qhash/tst_qhash.cpp @@ -36,6 +36,8 @@ #include <unordered_set> #include <string> +#include <qsemaphore.h> + class tst_QHash : public QObject { Q_OBJECT @@ -97,6 +99,8 @@ private slots: void reserveShared(); void QTBUG98265(); + + void detachAndReferences(); }; struct IdentityTracker { @@ -2627,5 +2631,57 @@ void tst_QHash::QTBUG98265() QVERIFY(a != b); } + +/* + Calling functions which take a const-ref argument for a key with a reference + to a key inside the hash itself should keep the key valid as long as it is + needed. If not users may get hard-to-debug races where CoW should've + shielded them. +*/ +void tst_QHash::detachAndReferences() +{ +#if !QT_CONFIG(cxx11_future) + QSKIP("This test requires cxx11_future") +#else + // Repeat a few times because it's not a guarantee + for (int i = 0; i < 50; ++i) { + QHash<char, char> hash; + hash.insert('a', 'a'); + hash.insert('b', 'a'); + hash.insert('c', 'a'); + hash.insert('d', 'a'); + hash.insert('e', 'a'); + hash.insert('f', 'a'); + hash.insert('g', 'a'); + + QSemaphore sem; + QSemaphore sem2; + std::thread th([&sem, &sem2, hash]() mutable { + sem.release(); + sem2.acquire(); + hash.reserve(100); // [2]: ...then this rehashes directly, without detaching + }); + + // The key is a reference to an entry in the hash. If we were already + // detached then no problem occurs! The problem happens because _after_ + // we detach but before using the key the other thread resizes and + // rehashes, leaving our const-ref dangling. + auto it = hash.constBegin(); + const auto &key = it.key(); // [3]: leaving our const-refs dangling + auto kCopy = key; + const auto &value = it.value(); + auto vCopy = value; + sem2.release(); + sem.acquire(); + hash.insert(key, value); // [1]: this detaches first... + + th.join(); + QCOMPARE(hash.size(), 7); + QVERIFY(hash.contains(kCopy)); + QCOMPARE(hash.value(kCopy), vCopy); + } +#endif +} + QTEST_APPLESS_MAIN(tst_QHash) #include "tst_qhash.moc" |