Add getter and setter for qt_qhash_seed

In some cases it's not possible to use QT_HASH_SEED, specially when
we need to set the environment variable from inside the application,
as dynamically loaded libraries or plugins may create static QHash
instances. That would set qt_qhash_seed to a value different from
-1 and skip the env var value.

For those cases, and when we still want to set qt_qhash_seed, we
provide a way to enforce its value.

Auto-tests accessing qt_qhash_seed directly have been updated
accordingly. Usage in qdoc, uic and rcc has been left as is
for the time being.

Change-Id: I3b35b4fa0223c83b1348a6508641905a2a63266f
Reviewed-by: Lars Knoll <lars.knoll@theqtcompany.com>
bb10
Gabriel de Dietrich 2015-09-21 15:53:49 +02:00
parent 863dfb1541
commit a14fadfb64
6 changed files with 75 additions and 33 deletions

View File

@ -290,6 +290,53 @@ static void qt_initialize_qhash_seed()
}
}
/*! \relates QHash
\since 5.6
Returns the current global QHash seed.
The seed is set in any newly created QHash. See \l{qHash} about how this seed
is being used by QHash.
\sa qSetGlobalQHashSeed
*/
int qGlobalQHashSeed()
{
return qt_qhash_seed.load();
}
/*! \relates QHash
\since 5.6
Sets the global QHash seed.
Manually setting the global QHash seed value should be done only for testing
and debugging purposes, when deterministic and reproducible behavior on a QHash
is needed. We discourage to do it in production code as it can make your
application susceptible to \l{algorithmic complexity attacks}.
The seed is set in any newly created QHash. See \l{qHash} about how this seed
is being used by QHash.
If the environment variable \c QT_HASH_SEED is set, calling this function will
result in a no-op.
Passing the value -1 will reinitialize the global QHash seed to a random value.
\sa qGlobalQHashSeed
*/
void qSetGlobalQHashSeed(int newSeed)
{
if (qEnvironmentVariableIsSet("QT_HASH_SEED"))
return;
if (newSeed == -1) {
int x(qt_create_qhash_seed() & INT_MAX);
qt_qhash_seed.store(x);
} else {
qt_qhash_seed.store(newSeed & INT_MAX);
}
}
/*!
\internal
@ -1132,7 +1179,8 @@ uint qHash(long double key, uint seed) Q_DECL_NOTHROW
where you temporarily need deterministic behavior, for example for debugging or
regression testing. To disable the randomization, define the environment
variable \c QT_HASH_SEED. The contents of that variable, interpreted as a
decimal value, will be used as the seed for qHash().
decimal value, will be used as the seed for qHash(). Alternatively, you can
call the qSetGlobalQHashSeed() function.
\sa QHashIterator, QMutableHashIterator, QMap, QSet
*/

View File

@ -1037,6 +1037,9 @@ Q_INLINE_TEMPLATE int QMultiHash<Key, T>::count(const Key &key, const T &value)
return n;
}
Q_CORE_EXPORT int qGlobalQHashSeed();
Q_CORE_EXPORT void qSetGlobalQHashSeed(int newSeed);
Q_DECLARE_ASSOCIATIVE_ITERATOR(Hash)
Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR(Hash)

View File

@ -50,6 +50,8 @@ private Q_SLOTS:
void qthash();
void range();
void rangeCommutative();
void setGlobalQHashSeed();
};
void tst_QHashFunctions::qhash()
@ -207,5 +209,21 @@ void tst_QHashFunctions::rangeCommutative()
(void)qHashRangeCommutative(hashables, hashables + numHashables);
}
void tst_QHashFunctions::setGlobalQHashSeed()
{
// Setter works as advertised
qSetGlobalQHashSeed(0x10101010);
QCOMPARE(qGlobalQHashSeed(), 0x10101010);
// Creating a new QHash doesn't reset the seed
QHash<QString, int> someHash;
someHash.insert("foo", 42);
QCOMPARE(qGlobalQHashSeed(), 0x10101010);
// Reset works as advertised
qSetGlobalQHashSeed(-1);
QVERIFY(qGlobalQHashSeed() != -1);
}
QTEST_APPLESS_MAIN(tst_QHashFunctions)
#include "tst_qhashfunctions.moc"

View File

@ -970,24 +970,6 @@ void tst_QSet::initializerList()
#endif
}
QT_BEGIN_NAMESPACE
extern Q_CORE_EXPORT QBasicAtomicInt qt_qhash_seed; // from qhash.cpp
QT_END_NAMESPACE
class QtQHashSeedSaver {
int oldSeed, newSeed;
public:
explicit QtQHashSeedSaver(int seed)
: oldSeed(qt_qhash_seed.fetchAndStoreRelaxed(seed)),
newSeed(seed)
{}
~QtQHashSeedSaver()
{
// only restore when no-one else changed the seed in the meantime:
qt_qhash_seed.testAndSetRelaxed(newSeed, oldSeed);
}
};
void tst_QSet::qhash()
{
//
@ -995,14 +977,14 @@ void tst_QSet::qhash()
//
{
// create some deterministic initial state:
const QtQHashSeedSaver seed1(0);
qSetGlobalQHashSeed(0);
QSet<int> s1;
s1.reserve(4);
s1 << 400 << 300 << 200 << 100;
// also change the seed:
const QtQHashSeedSaver seed2(0x10101010);
qSetGlobalQHashSeed(0x10101010);
QSet<int> s2;
s2.reserve(100); // provoke different bucket counts
@ -1049,7 +1031,7 @@ void tst_QSet::intersects()
s1 << 200;
QVERIFY(s1.intersects(s2));
const QtQHashSeedSaver seedSaver(0x10101010);
qSetGlobalQHashSeed(0x10101010);
QSet<int> s3;
s3 << 500;
QVERIFY(!s1.intersects(s3));

View File

@ -64,15 +64,10 @@ private slots:
void properties();
};
QT_BEGIN_NAMESPACE
// Avoid QHash randomization so that the order of the XML attributes is stable
extern Q_CORE_EXPORT QBasicAtomicInt qt_qhash_seed; // from qhash.cpp
QT_END_NAMESPACE
void tst_QDBusXmlParser::initTestCase()
{
// Always initialize the hash seed to 0 to get reliable test results
qt_qhash_seed.store(0);
qSetGlobalQHashSeed(0);
}
void tst_QDBusXmlParser::parsing_data()

View File

@ -259,13 +259,9 @@ void tst_Lancelot::paint(QPaintDevice *device, GraphicsEngine engine, const QStr
QTEST_MAIN(tst_Lancelot)
#undef main
QT_BEGIN_NAMESPACE
extern Q_DECL_IMPORT QBasicAtomicInt qt_qhash_seed; // from qhash.cpp
QT_END_NAMESPACE
int main(int argc, char *argv[])
{
qt_qhash_seed.store(0); // Avoid rendering variations caused by QHash randomization
qSetGlobalQHashSeed(0); // Avoid rendering variations caused by QHash randomization
QBaselineTest::handleCmdLineArgs(&argc, &argv);
return _realmain(argc, argv);