QHash security fix (1.5/2): qHash two arguments overload support
Algorithmic complexity attacks against hash tables have been known since 2003 (cf. [1, 2]), and they have been left unpatched for years until the 2011 attacks [3] against many libraries / (reference) implementations of programming languages. This patch adds a qHash overload taking two arguments: the value to be hashed, and a uint to be used as a seed for the hash function itself (support the global QHash seed was added in a previous patch). The seed itself is not used just yet; instead, 0 is passed. Compatibility with the one-argument qHash(T) implementation is kept through a catch-all template. [1] http://www.cs.rice.edu/~scrosby/hash/CrosbyWallach_UsenixSec2003.pdf [2] http://perldoc.perl.org/perlsec.html#Algorithmic-Complexity-Attacks [3] http://www.ocert.org/advisories/ocert-2011-003.html Task-number: QTBUG-23529 Change-Id: I1d0a84899476d134db455418c8043a349a7e5317 Reviewed-by: João Abecasis <joao.abecasis@nokia.com>
This commit is contained in:
parent
fb20f9c2da
commit
9a77171ccc
4
dist/changes-5.0.0
vendored
4
dist/changes-5.0.0
vendored
@ -346,6 +346,10 @@ QtCore
|
|||||||
* QEvent::AccessibilityPrepare, AccessibilityHelp and AccessibilityDescription removed:
|
* QEvent::AccessibilityPrepare, AccessibilityHelp and AccessibilityDescription removed:
|
||||||
* The enum values simply didn't make sense in the first place and should simply be dropped.
|
* The enum values simply didn't make sense in the first place and should simply be dropped.
|
||||||
|
|
||||||
|
* [QTBUG-23529] QHash is now more resilient to a family of denial of service
|
||||||
|
attacks exploiting algorithmic complexity, by supporting two-arguments overloads
|
||||||
|
of the qHash() hashing function.
|
||||||
|
|
||||||
QtGui
|
QtGui
|
||||||
-----
|
-----
|
||||||
* Accessibility has been refactored. The hierachy of accessible objects is implemented via
|
* Accessibility has been refactored. The hierachy of accessible objects is implemented via
|
||||||
|
@ -155,9 +155,9 @@ inline bool operator==(const Employee &e1, const Employee &e2)
|
|||||||
&& e1.dateOfBirth() == e2.dateOfBirth();
|
&& e1.dateOfBirth() == e2.dateOfBirth();
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint qHash(const Employee &key)
|
inline uint qHash(const Employee &key, uint seed)
|
||||||
{
|
{
|
||||||
return qHash(key.name()) ^ key.dateOfBirth().day();
|
return qHash(key.name(), seed) ^ key.dateOfBirth().day();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // EMPLOYEE_H
|
#endif // EMPLOYEE_H
|
||||||
|
@ -54,7 +54,7 @@ class Q_CORE_EXPORT QBitArray
|
|||||||
{
|
{
|
||||||
friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QBitArray &);
|
friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QBitArray &);
|
||||||
friend Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QBitArray &);
|
friend Q_CORE_EXPORT QDataStream &operator>>(QDataStream &, QBitArray &);
|
||||||
friend Q_CORE_EXPORT uint qHash(const QBitArray &key);
|
friend Q_CORE_EXPORT uint qHash(const QBitArray &key, uint seed);
|
||||||
QByteArray d;
|
QByteArray d;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -84,9 +84,9 @@ QT_BEGIN_NAMESPACE
|
|||||||
"a", "aa", "aaa", "aaaa", ...
|
"a", "aa", "aaa", "aaaa", ...
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static uint hash(const uchar *p, int n)
|
static uint hash(const uchar *p, int n, uint seed)
|
||||||
{
|
{
|
||||||
uint h = 0;
|
uint h = seed;
|
||||||
|
|
||||||
while (n--) {
|
while (n--) {
|
||||||
h = (h << 4) + *p++;
|
h = (h << 4) + *p++;
|
||||||
@ -96,9 +96,9 @@ static uint hash(const uchar *p, int n)
|
|||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint hash(const QChar *p, int n)
|
static uint hash(const QChar *p, int n, uint seed)
|
||||||
{
|
{
|
||||||
uint h = 0;
|
uint h = seed;
|
||||||
|
|
||||||
while (n--) {
|
while (n--) {
|
||||||
h = (h << 4) + (*p++).unicode();
|
h = (h << 4) + (*p++).unicode();
|
||||||
@ -108,25 +108,25 @@ static uint hash(const QChar *p, int n)
|
|||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint qHash(const QByteArray &key)
|
uint qHash(const QByteArray &key, uint seed)
|
||||||
{
|
{
|
||||||
return hash(reinterpret_cast<const uchar *>(key.constData()), key.size());
|
return hash(reinterpret_cast<const uchar *>(key.constData()), key.size(), seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint qHash(const QString &key)
|
uint qHash(const QString &key, uint seed)
|
||||||
{
|
{
|
||||||
return hash(key.unicode(), key.size());
|
return hash(key.unicode(), key.size(), seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint qHash(const QStringRef &key)
|
uint qHash(const QStringRef &key, uint seed)
|
||||||
{
|
{
|
||||||
return hash(key.unicode(), key.size());
|
return hash(key.unicode(), key.size(), seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint qHash(const QBitArray &bitArray)
|
uint qHash(const QBitArray &bitArray, uint seed)
|
||||||
{
|
{
|
||||||
int m = bitArray.d.size() - 1;
|
int m = bitArray.d.size() - 1;
|
||||||
uint result = hash(reinterpret_cast<const uchar *>(bitArray.d.constData()), qMax(0, m));
|
uint result = hash(reinterpret_cast<const uchar *>(bitArray.d.constData()), qMax(0, m), seed);
|
||||||
|
|
||||||
// deal with the last 0 to 7 bits manually, because we can't trust that
|
// deal with the last 0 to 7 bits manually, because we can't trust that
|
||||||
// the padding is initialized to 0 in bitArray.d
|
// the padding is initialized to 0 in bitArray.d
|
||||||
@ -614,17 +614,16 @@ void QHashData::checkSanity()
|
|||||||
Returns the hash value for the \a key.
|
Returns the hash value for the \a key.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*! \fn uint qHash(const QByteArray &key)
|
/*! \fn uint qHash(const QByteArray &key, uint seed = 0)
|
||||||
\fn uint qHash(const QBitArray &key)
|
\fn uint qHash(const QBitArray &key, uint seed = 0)
|
||||||
|
\fn uint qHash(const QString &key, uint seed = 0)
|
||||||
|
\fn uint qHash(const QStringRef &key, uint seed = 0)
|
||||||
|
|
||||||
\relates QHash
|
\relates QHash
|
||||||
|
\since 5.0
|
||||||
|
|
||||||
Returns the hash value for the \a key.
|
Returns the hash value for the \a key, using \a seed to
|
||||||
*/
|
seed the calculation.
|
||||||
|
|
||||||
/*! \fn uint qHash(const QString &key)
|
|
||||||
\relates QHash
|
|
||||||
|
|
||||||
Returns the hash value for the \a key.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*! \fn uint qHash(const T *key)
|
/*! \fn uint qHash(const T *key)
|
||||||
@ -656,8 +655,7 @@ void QHashData::checkSanity()
|
|||||||
key. With QHash, the items are arbitrarily ordered.
|
key. With QHash, the items are arbitrarily ordered.
|
||||||
\li The key type of a QMap must provide operator<(). The key
|
\li The key type of a QMap must provide operator<(). The key
|
||||||
type of a QHash must provide operator==() and a global
|
type of a QHash must provide operator==() and a global
|
||||||
hash function called qHash() (see the related non-member
|
hash function called qHash() (see \l{qHash}).
|
||||||
functions).
|
|
||||||
\endlist
|
\endlist
|
||||||
|
|
||||||
Here's an example QHash with QString keys and \c int values:
|
Here's an example QHash with QString keys and \c int values:
|
||||||
@ -702,6 +700,15 @@ void QHashData::checkSanity()
|
|||||||
To avoid this problem, replace \c hash[i] with \c hash.value(i)
|
To avoid this problem, replace \c hash[i] with \c hash.value(i)
|
||||||
in the code above.
|
in the code above.
|
||||||
|
|
||||||
|
Internally, QHash uses a hash table to perform lookups. Unlike Qt
|
||||||
|
3's \c QDict class, which needed to be initialized with a prime
|
||||||
|
number, QHash's hash table automatically grows and shrinks to
|
||||||
|
provide fast lookups without wasting too much memory. You can
|
||||||
|
still control the size of the hash table by calling reserve() if
|
||||||
|
you already know approximately how many items the QHash will
|
||||||
|
contain, but this isn't necessary to obtain good performance. You
|
||||||
|
can also call capacity() to retrieve the hash table's size.
|
||||||
|
|
||||||
If you want to navigate through all the (key, value) pairs stored
|
If you want to navigate through all the (key, value) pairs stored
|
||||||
in a QHash, you can use an iterator. QHash provides both
|
in a QHash, you can use an iterator. QHash provides both
|
||||||
\l{Java-style iterators} (QHashIterator and QMutableHashIterator)
|
\l{Java-style iterators} (QHashIterator and QMutableHashIterator)
|
||||||
@ -751,21 +758,15 @@ void QHashData::checkSanity()
|
|||||||
|
|
||||||
QHash's key and value data types must be \l{assignable data
|
QHash's key and value data types must be \l{assignable data
|
||||||
types}. You cannot, for example, store a QWidget as a value;
|
types}. You cannot, for example, store a QWidget as a value;
|
||||||
instead, store a QWidget *. In addition, QHash's key type must
|
instead, store a QWidget *.
|
||||||
provide operator==(), and there must also be a global qHash()
|
|
||||||
function that returns a hash value for an argument of the key's
|
|
||||||
type.
|
|
||||||
|
|
||||||
Here's a list of the C++ and Qt types that can serve as keys in a
|
\target qHash
|
||||||
QHash: any integer type (char, unsigned long, etc.), any pointer
|
\section2 The qHash() hashing function
|
||||||
type, QChar, QString, and QByteArray. For all of these, the \c
|
|
||||||
<QHash> header defines a qHash() function that computes an
|
|
||||||
adequate hash value. If you want to use other types as the key,
|
|
||||||
make sure that you provide operator==() and a qHash()
|
|
||||||
implementation.
|
|
||||||
|
|
||||||
Example:
|
A QHash's key type has additional requirements other than being an
|
||||||
\snippet doc/src/snippets/code/src_corelib_tools_qhash.cpp 13
|
assignable data type: it must provide operator==(), and there must also be
|
||||||
|
a global qHash() function that returns a hash value for an argument of the
|
||||||
|
key's type.
|
||||||
|
|
||||||
The qHash() function computes a numeric value based on a key. It
|
The qHash() function computes a numeric value based on a key. It
|
||||||
can use any algorithm imaginable, as long as it always returns
|
can use any algorithm imaginable, as long as it always returns
|
||||||
@ -775,19 +776,56 @@ void QHashData::checkSanity()
|
|||||||
attempt to return different hash values for different keys to the
|
attempt to return different hash values for different keys to the
|
||||||
largest extent possible.
|
largest extent possible.
|
||||||
|
|
||||||
|
For a key type \c{K}, the qHash function must have one of these signatures:
|
||||||
|
|
||||||
|
\code
|
||||||
|
uint qHash(K key);
|
||||||
|
uint qHash(const K &key);
|
||||||
|
|
||||||
|
uint qHash(K key, uint seed);
|
||||||
|
uint qHash(const K &key, uint seed);
|
||||||
|
\endcode
|
||||||
|
|
||||||
|
The two-arguments overloads take an unsigned integer that should be used to
|
||||||
|
seed the calculation of the hash function. This seed is provided by QHash
|
||||||
|
in order to prevent a family of \l{algorithmic complexity attacks}. If both
|
||||||
|
a one-argument and a two-arguments overload are defined for a key type,
|
||||||
|
the latter is used by QHash (note that you can simply define a
|
||||||
|
two-arguments version, and use a default value for the seed parameter).
|
||||||
|
|
||||||
|
Here's a partial list of the C++ and Qt types that can serve as keys in a
|
||||||
|
QHash: any integer type (char, unsigned long, etc.), any pointer type,
|
||||||
|
QChar, QString, and QByteArray. For all of these, the \c <QHash> header
|
||||||
|
defines a qHash() function that computes an adequate hash value. Many other
|
||||||
|
Qt classes also declare a qHash overload for their type; please refer to
|
||||||
|
the documentation of each class.
|
||||||
|
|
||||||
|
If you want to use other types as the key, make sure that you provide
|
||||||
|
operator==() and a qHash() implementation.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
\snippet doc/src/snippets/code/src_corelib_tools_qhash.cpp 13
|
||||||
|
|
||||||
In the example above, we've relied on Qt's global qHash(const
|
In the example above, we've relied on Qt's global qHash(const
|
||||||
QString &) to give us a hash value for the employee's name, and
|
QString &, uint) to give us a hash value for the employee's name, and
|
||||||
XOR'ed this with the day they were born to help produce unique
|
XOR'ed this with the day they were born to help produce unique
|
||||||
hashes for people with the same name.
|
hashes for people with the same name.
|
||||||
|
|
||||||
Internally, QHash uses a hash table to perform lookups. Unlike Qt
|
\section2 Algorithmic complexity attacks
|
||||||
3's \c QDict class, which needed to be initialized with a prime
|
|
||||||
number, QHash's hash table automatically grows and shrinks to
|
All hash tables are vulnerable to a particular class of denial of service
|
||||||
provide fast lookups without wasting too much memory. You can
|
attacks, in which the attacker carefully pre-computes a set of different
|
||||||
still control the size of the hash table by calling reserve() if
|
keys that are going to be hashed in the same bucket of a hash table (or
|
||||||
you already know approximately how many items the QHash will
|
even have the very same hash value). The attack aims at getting the
|
||||||
contain, but this isn't necessary to obtain good performance. You
|
worst-case algorithmic behavior (O(n) instead of amortized O(1), see
|
||||||
can also call capacity() to retrieve the hash table's size.
|
\l{Algorithmic Complexity} for the details) when the data is fed into the
|
||||||
|
table.
|
||||||
|
|
||||||
|
In order to avoid this worst-case behavior, the calculation of the hash
|
||||||
|
value done by qHash() can be salted by a random seed, that nullifies the
|
||||||
|
attack's extent. This seed is automatically generated by QHash once per
|
||||||
|
process, and then passed by QHash as the second argument of the
|
||||||
|
two-arguments overload of the qHash() function.
|
||||||
|
|
||||||
\sa QHashIterator, QMutableHashIterator, QMap, QSet
|
\sa QHashIterator, QMutableHashIterator, QMap, QSet
|
||||||
*/
|
*/
|
||||||
|
@ -84,10 +84,10 @@ inline uint qHash(quint64 key)
|
|||||||
}
|
}
|
||||||
inline uint qHash(qint64 key) { return qHash(quint64(key)); }
|
inline uint qHash(qint64 key) { return qHash(quint64(key)); }
|
||||||
inline uint qHash(QChar key) { return qHash(key.unicode()); }
|
inline uint qHash(QChar key) { return qHash(key.unicode()); }
|
||||||
Q_CORE_EXPORT uint qHash(const QByteArray &key);
|
Q_CORE_EXPORT uint qHash(const QByteArray &key, uint seed = 0);
|
||||||
Q_CORE_EXPORT uint qHash(const QString &key);
|
Q_CORE_EXPORT uint qHash(const QString &key, uint seed = 0);
|
||||||
Q_CORE_EXPORT uint qHash(const QStringRef &key);
|
Q_CORE_EXPORT uint qHash(const QStringRef &key, uint seed = 0);
|
||||||
Q_CORE_EXPORT uint qHash(const QBitArray &key);
|
Q_CORE_EXPORT uint qHash(const QBitArray &key, uint seed = 0);
|
||||||
|
|
||||||
#if defined(Q_CC_MSVC)
|
#if defined(Q_CC_MSVC)
|
||||||
#pragma warning( push )
|
#pragma warning( push )
|
||||||
@ -108,6 +108,8 @@ template <typename T1, typename T2> inline uint qHash(const QPair<T1, T2> &key)
|
|||||||
return ((h1 << 16) | (h1 >> 16)) ^ h2;
|
return ((h1 << 16) | (h1 >> 16)) ^ h2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T> inline uint qHash(const T &t, uint) { return qHash(t); }
|
||||||
|
|
||||||
struct Q_CORE_EXPORT QHashData
|
struct Q_CORE_EXPORT QHashData
|
||||||
{
|
{
|
||||||
struct Node {
|
struct Node {
|
||||||
@ -857,7 +859,7 @@ Q_OUTOFLINE_TEMPLATE typename QHash<Key, T>::Node **QHash<Key, T>::findNode(cons
|
|||||||
uint h = 0;
|
uint h = 0;
|
||||||
|
|
||||||
if (d->numBuckets || ahp) {
|
if (d->numBuckets || ahp) {
|
||||||
h = qHash(akey);
|
h = qHash(akey, 0);
|
||||||
if (ahp)
|
if (ahp)
|
||||||
*ahp = h;
|
*ahp = h;
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
#include <QtCore/qvariant.h>
|
#include <QtCore/qvariant.h>
|
||||||
#include <QtCore/qstring.h>
|
#include <QtCore/qstring.h>
|
||||||
#include <QtDBus/qdbusmacros.h>
|
#include <QtDBus/qdbusmacros.h>
|
||||||
|
#include <QtCore/qhash.h>
|
||||||
|
|
||||||
#ifndef QT_NO_DBUS
|
#ifndef QT_NO_DBUS
|
||||||
|
|
||||||
@ -55,9 +56,6 @@ QT_BEGIN_HEADER
|
|||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
|
||||||
// defined in qhash.cpp
|
|
||||||
Q_CORE_EXPORT uint qHash(const QString &key);
|
|
||||||
|
|
||||||
class Q_DBUS_EXPORT QDBusObjectPath
|
class Q_DBUS_EXPORT QDBusObjectPath
|
||||||
{
|
{
|
||||||
QString m_path;
|
QString m_path;
|
||||||
|
@ -73,6 +73,7 @@ private slots:
|
|||||||
void noNeedlessRehashes();
|
void noNeedlessRehashes();
|
||||||
|
|
||||||
void const_shared_null();
|
void const_shared_null();
|
||||||
|
void twoArguments_qHash();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Foo {
|
struct Foo {
|
||||||
@ -1203,5 +1204,101 @@ void tst_QHash::const_shared_null()
|
|||||||
QVERIFY(!hash2.isDetached());
|
QVERIFY(!hash2.isDetached());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This gets set to != 0 in wrong qHash overloads
|
||||||
|
static int wrongqHashOverload = 0;
|
||||||
|
|
||||||
|
struct OneArgumentQHashStruct1 {};
|
||||||
|
bool operator==(const OneArgumentQHashStruct1 &, const OneArgumentQHashStruct1 &) { return false; }
|
||||||
|
uint qHash(OneArgumentQHashStruct1) { return 0; }
|
||||||
|
|
||||||
|
struct OneArgumentQHashStruct2 {};
|
||||||
|
bool operator==(const OneArgumentQHashStruct2 &, const OneArgumentQHashStruct2 &) { return false; }
|
||||||
|
uint qHash(const OneArgumentQHashStruct2 &) { return 0; }
|
||||||
|
|
||||||
|
struct OneArgumentQHashStruct3 {};
|
||||||
|
bool operator==(const OneArgumentQHashStruct3 &, const OneArgumentQHashStruct3 &) { return false; }
|
||||||
|
uint qHash(OneArgumentQHashStruct3) { return 0; }
|
||||||
|
uint qHash(OneArgumentQHashStruct3 &, uint) { wrongqHashOverload = 1; return 0; }
|
||||||
|
|
||||||
|
struct OneArgumentQHashStruct4 {};
|
||||||
|
bool operator==(const OneArgumentQHashStruct4 &, const OneArgumentQHashStruct4 &) { return false; }
|
||||||
|
uint qHash(const OneArgumentQHashStruct4 &) { return 0; }
|
||||||
|
uint qHash(OneArgumentQHashStruct4 &, uint) { wrongqHashOverload = 1; return 0; }
|
||||||
|
|
||||||
|
|
||||||
|
struct TwoArgumentsQHashStruct1 {};
|
||||||
|
bool operator==(const TwoArgumentsQHashStruct1 &, const TwoArgumentsQHashStruct1 &) { return false; }
|
||||||
|
uint qHash(const TwoArgumentsQHashStruct1 &) { wrongqHashOverload = 1; return 0; }
|
||||||
|
uint qHash(const TwoArgumentsQHashStruct1 &, uint) { return 0; }
|
||||||
|
|
||||||
|
struct TwoArgumentsQHashStruct2 {};
|
||||||
|
bool operator==(const TwoArgumentsQHashStruct2 &, const TwoArgumentsQHashStruct2 &) { return false; }
|
||||||
|
uint qHash(TwoArgumentsQHashStruct2) { wrongqHashOverload = 1; return 0; }
|
||||||
|
uint qHash(const TwoArgumentsQHashStruct2 &, uint) { return 0; }
|
||||||
|
|
||||||
|
struct TwoArgumentsQHashStruct3 {};
|
||||||
|
bool operator==(const TwoArgumentsQHashStruct3 &, const TwoArgumentsQHashStruct3 &) { return false; }
|
||||||
|
uint qHash(const TwoArgumentsQHashStruct3 &) { wrongqHashOverload = 1; return 0; }
|
||||||
|
uint qHash(TwoArgumentsQHashStruct3, uint) { return 0; }
|
||||||
|
|
||||||
|
struct TwoArgumentsQHashStruct4 {};
|
||||||
|
bool operator==(const TwoArgumentsQHashStruct4 &, const TwoArgumentsQHashStruct4 &) { return false; }
|
||||||
|
uint qHash(TwoArgumentsQHashStruct4) { wrongqHashOverload = 1; return 0; }
|
||||||
|
uint qHash(TwoArgumentsQHashStruct4, uint) { return 0; }
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\internal
|
||||||
|
|
||||||
|
Check that QHash picks up the right overload.
|
||||||
|
The best one, for a type T, is the two-args version of qHash:
|
||||||
|
either uint qHash(T, uint) or uint qHash(const T &, uint).
|
||||||
|
|
||||||
|
If neither of these exists, then one between
|
||||||
|
uint qHash(T) or uint qHash(const T &) must exist
|
||||||
|
(and it gets selected instead).
|
||||||
|
*/
|
||||||
|
void tst_QHash::twoArguments_qHash()
|
||||||
|
{
|
||||||
|
QHash<OneArgumentQHashStruct1, int> oneArgHash1;
|
||||||
|
OneArgumentQHashStruct1 oneArgObject1;
|
||||||
|
oneArgHash1[oneArgObject1] = 1;
|
||||||
|
QCOMPARE(wrongqHashOverload, 0);
|
||||||
|
|
||||||
|
QHash<OneArgumentQHashStruct2, int> oneArgHash2;
|
||||||
|
OneArgumentQHashStruct2 oneArgObject2;
|
||||||
|
oneArgHash2[oneArgObject2] = 1;
|
||||||
|
QCOMPARE(wrongqHashOverload, 0);
|
||||||
|
|
||||||
|
QHash<OneArgumentQHashStruct3, int> oneArgHash3;
|
||||||
|
OneArgumentQHashStruct3 oneArgObject3;
|
||||||
|
oneArgHash3[oneArgObject3] = 1;
|
||||||
|
QCOMPARE(wrongqHashOverload, 0);
|
||||||
|
|
||||||
|
QHash<OneArgumentQHashStruct4, int> oneArgHash4;
|
||||||
|
OneArgumentQHashStruct4 oneArgObject4;
|
||||||
|
oneArgHash4[oneArgObject4] = 1;
|
||||||
|
QCOMPARE(wrongqHashOverload, 0);
|
||||||
|
|
||||||
|
QHash<TwoArgumentsQHashStruct1, int> twoArgsHash1;
|
||||||
|
TwoArgumentsQHashStruct1 twoArgsObject1;
|
||||||
|
twoArgsHash1[twoArgsObject1] = 1;
|
||||||
|
QCOMPARE(wrongqHashOverload, 0);
|
||||||
|
|
||||||
|
QHash<TwoArgumentsQHashStruct2, int> twoArgsHash2;
|
||||||
|
TwoArgumentsQHashStruct2 twoArgsObject2;
|
||||||
|
twoArgsHash2[twoArgsObject2] = 1;
|
||||||
|
QCOMPARE(wrongqHashOverload, 0);
|
||||||
|
|
||||||
|
QHash<TwoArgumentsQHashStruct3, int> twoArgsHash3;
|
||||||
|
TwoArgumentsQHashStruct3 twoArgsObject3;
|
||||||
|
twoArgsHash3[twoArgsObject3] = 1;
|
||||||
|
QCOMPARE(wrongqHashOverload, 0);
|
||||||
|
|
||||||
|
QHash<TwoArgumentsQHashStruct4, int> twoArgsHash4;
|
||||||
|
TwoArgumentsQHashStruct4 twoArgsObject4;
|
||||||
|
twoArgsHash4[twoArgsObject4] = 1;
|
||||||
|
QCOMPARE(wrongqHashOverload, 0);
|
||||||
|
}
|
||||||
|
|
||||||
QTEST_APPLESS_MAIN(tst_QHash)
|
QTEST_APPLESS_MAIN(tst_QHash)
|
||||||
#include "tst_qhash.moc"
|
#include "tst_qhash.moc"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user