summaryrefslogtreecommitdiffstats
path: root/tests/auto
diff options
context:
space:
mode:
authorMÃ¥rten Nordheim <marten.nordheim@qt.io>2021-10-26 15:04:19 +0200
committerMarc Mutz <marc.mutz@qt.io>2021-11-25 08:52:39 +0000
commit8f8775adf3c4fbba1bd3c120a228351d46f50127 (patch)
tree52655d992735025a9d0d1c421838f5a7c67a7f00 /tests/auto
parenta92619d950fdbf803cdc8c8ca8e75c1c82abb23f (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.cpp56
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"