Skip to content

Commit 4a69cd7

Browse files
committed
Restrict comparison of variants
Comparing two variants will not try to convert the types of the variant anymore. Exceptions are when both types are numeric types or one type is numeric and the other one a QString. The exceptions are there to keep compatibility with C++ and to not completely break QSettings (which needs automatic conversions from QString to numeric types). [ChangeLog][Important Behavior Changes] Comparing two variants in Qt 6 will not try attempt any type conversions before comparing the variants anymore. Instead variants of different type will not compare equal, with two exceptions: If both types are numeric types they will get compared according to C++ type promotion rules. If one type is a QString and the other type a numeric type, a conversion from the string to the numeric tpye will be attempted. Fixes: QTBUG-84636 Change-Id: I0cdd0b7259a525a41679fb6761f1e37e1d5b257f Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
1 parent 50c96c1 commit 4a69cd7

File tree

3 files changed

+118
-62
lines changed

3 files changed

+118
-62
lines changed

src/corelib/kernel/qvariant.cpp

Lines changed: 82 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,28 @@ static qulonglong qMetaTypeUNumber(const QVariant::Private *d)
174174
return 0;
175175
}
176176

177-
static qlonglong qConvertToNumber(const QVariant::Private *d, bool *ok)
177+
static qlonglong qConvertToNumber(const QVariant::Private *d, bool *ok, bool allowStringToBool = false)
178178
{
179179
*ok = true;
180180

181181
switch (uint(d->type().id())) {
182-
case QMetaType::QString:
183-
return v_cast<QString>(d)->toLongLong(ok);
182+
case QMetaType::QString: {
183+
const QString *s = v_cast<QString>(d);
184+
qlonglong l = s->toLongLong(ok);
185+
if (*ok)
186+
return l;
187+
if (allowStringToBool) {
188+
if (*s == QLatin1String("false") || *s == QLatin1String("0")) {
189+
*ok = true;
190+
return 0;
191+
}
192+
if (*s == QLatin1String("true") || *s == QLatin1String("1")) {
193+
*ok = true;
194+
return 1;
195+
}
196+
}
197+
return 0;
198+
}
184199
case QMetaType::QChar:
185200
return v_cast<QChar>(d)->unicode();
186201
case QMetaType::QByteArray:
@@ -238,6 +253,8 @@ static qreal qConvertToRealNumber(const QVariant::Private *d, bool *ok)
238253
{
239254
*ok = true;
240255
switch (uint(d->type().id())) {
256+
case QMetaType::QString:
257+
return v_cast<QString>(d)->toDouble(ok);
241258
case QMetaType::Double:
242259
return qreal(d->data.d);
243260
case QMetaType::Float:
@@ -3727,21 +3744,45 @@ bool QVariant::convert(const int type, void *ptr) const
37273744
equal; otherwise returns \c false.
37283745
37293746
QVariant uses the equality operator of the type() it contains to
3730-
check for equality. QVariant will try to convert() \a v if its
3731-
type is not the same as this variant's type. See canConvert() for
3732-
a list of possible conversions.
3747+
check for equality.
3748+
3749+
Variants of different types will always compare as not equal with a few
3750+
exceptions:
3751+
3752+
\list
3753+
\i If both types are numeric types (integers and floatins point numbers)
3754+
Qt will compare those types using standard C++ type promotion rules.
3755+
\i If one type is numeric and the other one a QString, Qt will try to
3756+
convert the QString to a matching numeric type and if successful compare
3757+
those.
3758+
\endlist
37333759
*/
37343760

37353761
/*!
37363762
\fn bool QVariant::operator!=(const QVariant &v) const
37373763
37383764
Compares this QVariant with \a v and returns \c true if they are not
37393765
equal; otherwise returns \c false.
3766+
3767+
QVariant uses the equality operator of the type() it contains to
3768+
check for equality.
3769+
3770+
Variants of different types will always compare as not equal with a few
3771+
exceptions:
3772+
3773+
\list
3774+
\i If both types are numeric types (integers and floatins point numbers)
3775+
Qt will compare those types using standard C++ type promotion rules.
3776+
\i If one type is numeric and the other one a QString, Qt will try to
3777+
convert the QString to a matching numeric type and if successful compare
3778+
those.
3779+
\endlist
37403780
*/
37413781

37423782
static bool qIsNumericType(uint tp)
37433783
{
37443784
static const qulonglong numericTypeBits =
3785+
Q_UINT64_C(1) << QMetaType::QString |
37453786
Q_UINT64_C(1) << QMetaType::Bool |
37463787
Q_UINT64_C(1) << QMetaType::Double |
37473788
Q_UINT64_C(1) << QMetaType::Float |
@@ -3789,6 +3830,10 @@ static int numericTypePromotion(uint t1, uint t2)
37893830
Q_ASSERT(qIsNumericType(t1));
37903831
Q_ASSERT(qIsNumericType(t2));
37913832

3833+
if ((t1 == QMetaType::Bool && t2 == QMetaType::QString) ||
3834+
(t2 == QMetaType::Bool && t1 == QMetaType::QString))
3835+
return QMetaType::Bool;
3836+
37923837
// C++ integral ranks: (4.13 Integer conversion rank [conv.rank])
37933838
// bool < signed char < short < int < long < long long
37943839
// unsigneds have the same rank as their signed counterparts
@@ -3830,53 +3875,59 @@ static int numericTypePromotion(uint t1, uint t2)
38303875
return QMetaType::Int;
38313876
}
38323877

3833-
static int integralCompare(uint promotedType, const QVariant::Private *d1, const QVariant::Private *d2)
3878+
static bool integralEquals(uint promotedType, const QVariant::Private *d1, const QVariant::Private *d2)
38343879
{
38353880
// use toLongLong to retrieve the data, it gets us all the bits
38363881
bool ok;
3837-
qlonglong l1 = qConvertToNumber(d1, &ok);
3838-
Q_ASSERT(ok);
3882+
qlonglong l1 = qConvertToNumber(d1, &ok, promotedType == QMetaType::Bool);
3883+
if (!ok)
3884+
return false;
38393885

3840-
qlonglong l2 = qConvertToNumber(d2, &ok);
3841-
Q_ASSERT(ok);
3886+
qlonglong l2 = qConvertToNumber(d2, &ok, promotedType == QMetaType::Bool);
3887+
if (!ok)
3888+
return false;
38423889

3890+
if (promotedType == QMetaType::Bool)
3891+
return bool(l1) == bool(l2);
38433892
if (promotedType == QMetaType::Int)
3844-
return int(l1) < int(l2) ? -1 : int(l1) == int(l2) ? 0 : 1;
3893+
return int(l1) == int(l2);
38453894
if (promotedType == QMetaType::UInt)
3846-
return uint(l1) < uint(l2) ? -1 : uint(l1) == uint(l2) ? 0 : 1;
3895+
return uint(l1) == uint(l2);
38473896
if (promotedType == QMetaType::LongLong)
3848-
return l1 < l2 ? -1 : l1 == l2 ? 0 : 1;
3897+
return l1 == l2;
38493898
if (promotedType == QMetaType::ULongLong)
3850-
return qulonglong(l1) < qulonglong(l2) ? -1 : qulonglong(l1) == qulonglong(l2) ? 0 : 1;
3899+
return qulonglong(l1) == qulonglong(l2);
38513900

38523901
Q_UNREACHABLE();
38533902
return 0;
38543903
}
38553904

3856-
static int numericCompare(const QVariant::Private *d1, const QVariant::Private *d2)
3905+
static bool numericEquals(const QVariant::Private *d1, const QVariant::Private *d2)
38573906
{
38583907
uint promotedType = numericTypePromotion(d1->type().id(), d2->type().id());
38593908
if (promotedType != QMetaType::QReal)
3860-
return integralCompare(promotedType, d1, d2);
3909+
return integralEquals(promotedType, d1, d2);
38613910

38623911
// qreal comparisons
38633912
bool ok;
38643913
qreal r1 = qConvertToRealNumber(d1, &ok);
3865-
Q_ASSERT(ok);
3914+
if (!ok)
3915+
return false;
38663916
qreal r2 = qConvertToRealNumber(d2, &ok);
3867-
Q_ASSERT(ok);
3917+
if (!ok)
3918+
return false;
38683919
if (r1 == r2)
3869-
return 0;
3920+
return true;
38703921

38713922
// only do fuzzy comparisons for finite, non-zero numbers
38723923
int c1 = qFpClassify(r1);
38733924
int c2 = qFpClassify(r2);
38743925
if ((c1 == FP_NORMAL || c1 == FP_SUBNORMAL) && (c2 == FP_NORMAL || c2 == FP_SUBNORMAL)) {
38753926
if (qFuzzyCompare(r1, r2))
3876-
return 0;
3927+
return true;
38773928
}
38783929

3879-
return r1 < r2 ? -1 : 1;
3930+
return false;
38803931
}
38813932

38823933
/*!
@@ -3885,26 +3936,19 @@ static int numericCompare(const QVariant::Private *d1, const QVariant::Private *
38853936
bool QVariant::equals(const QVariant &v) const
38863937
{
38873938
auto metatype = d.type();
3888-
// try numerics first, with C++ type promotion rules (no conversion)
3889-
if (qIsNumericType(metatype.id()) && qIsNumericType(v.d.type().id()))
3890-
return numericCompare(&d, &v.d) == 0;
3891-
3892-
QVariant v1 = *this;
3893-
QVariant v2 = v;
3894-
if (v2.canConvert(v1.d.type().id())) {
3895-
if (!v2.convert(v1.d.type().id()))
3896-
return false;
3897-
} else {
3898-
// try the opposite conversion, it might work
3899-
qSwap(v1, v2);
3900-
if (!v2.convert(v1.d.type().id()))
3901-
return false;
3939+
3940+
if (metatype != v.metaType()) {
3941+
// try numeric comparisons, with C++ type promotion rules (no conversion)
3942+
if (qIsNumericType(metatype.id()) && qIsNumericType(v.d.type().id()))
3943+
return numericEquals(&d, &v.d);
3944+
return false;
39023945
}
3903-
metatype = v1.metaType();
3946+
39043947
// For historical reasons: QVariant() == QVariant()
39053948
if (!metatype.isValid())
39063949
return true;
3907-
return metatype.equals(QT_PREPEND_NAMESPACE(constData(v1.d)), QT_PREPEND_NAMESPACE(constData(v2.d)));
3950+
3951+
return metatype.equals(QT_PREPEND_NAMESPACE(constData(d)), QT_PREPEND_NAMESPACE(constData(v.d)));
39083952
}
39093953

39103954
/*!

tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,38 +1491,38 @@ void tst_QVariant::operator_eq_eq_data()
14911491

14921492
QTest::newRow( "double_int" ) << QVariant(42.0) << QVariant(42) << true;
14931493
QTest::newRow( "float_int" ) << QVariant(42.f) << QVariant(42) << true;
1494-
QTest::newRow( "mInt_mIntString" ) << mInt << mIntString << true;
1495-
QTest::newRow( "mIntString_mInt" ) << mIntString << mInt << true;
1494+
QTest::newRow( "mInt_mIntString" ) << mInt << mIntString << false;
1495+
QTest::newRow( "mIntString_mInt" ) << mIntString << mInt << false;
14961496
QTest::newRow( "mInt_mIntQString" ) << mInt << mIntQString << true;
14971497
QTest::newRow( "mIntQString_mInt" ) << mIntQString << mInt << true;
14981498

1499-
QTest::newRow( "mUInt_mUIntString" ) << mUInt << mUIntString << true;
1500-
QTest::newRow( "mUIntString_mUInt" ) << mUIntString << mUInt << true;
1499+
QTest::newRow( "mUInt_mUIntString" ) << mUInt << mUIntString << false;
1500+
QTest::newRow( "mUIntString_mUInt" ) << mUIntString << mUInt << false;
15011501
QTest::newRow( "mUInt_mUIntQString" ) << mUInt << mUIntQString << true;
15021502
QTest::newRow( "mUIntQString_mUInt" ) << mUIntQString << mUInt << true;
15031503

1504-
QTest::newRow( "mDouble_mDoubleString" ) << mDouble << mDoubleString << true;
1505-
QTest::newRow( "mDoubleString_mDouble" ) << mDoubleString << mDouble << true;
1504+
QTest::newRow( "mDouble_mDoubleString" ) << mDouble << mDoubleString << false;
1505+
QTest::newRow( "mDoubleString_mDouble" ) << mDoubleString << mDouble << false;
15061506
QTest::newRow( "mDouble_mDoubleQString" ) << mDouble << mDoubleQString << true;
15071507
QTest::newRow( "mDoubleQString_mDouble" ) << mDoubleQString << mDouble << true;
15081508

1509-
QTest::newRow( "mFloat_mFloatString" ) << mFloat << mFloatString << true;
1510-
QTest::newRow( "mFloatString_mFloat" ) << mFloatString << mFloat << true;
1509+
QTest::newRow( "mFloat_mFloatString" ) << mFloat << mFloatString << false;
1510+
QTest::newRow( "mFloatString_mFloat" ) << mFloatString << mFloat << false;
15111511
QTest::newRow( "mFloat_mFloatQString" ) << mFloat << mFloatQString << true;
15121512
QTest::newRow( "mFloatQString_mFloat" ) << mFloatQString << mFloat << true;
15131513

1514-
QTest::newRow( "mLongLong_mLongLongString" ) << mLongLong << mLongLongString << true;
1515-
QTest::newRow( "mLongLongString_mLongLong" ) << mLongLongString << mLongLong << true;
1514+
QTest::newRow( "mLongLong_mLongLongString" ) << mLongLong << mLongLongString << false;
1515+
QTest::newRow( "mLongLongString_mLongLong" ) << mLongLongString << mLongLong << false;
15161516
QTest::newRow( "mLongLong_mLongLongQString" ) << mLongLong << mLongLongQString << true;
15171517
QTest::newRow( "mLongLongQString_mLongLong" ) << mLongLongQString << mLongLong << true;
15181518

1519-
QTest::newRow( "mULongLong_mULongLongString" ) << mULongLong << mULongLongString << true;
1520-
QTest::newRow( "mULongLongString_mULongLong" ) << mULongLongString << mULongLong << true;
1519+
QTest::newRow( "mULongLong_mULongLongString" ) << mULongLong << mULongLongString << false;
1520+
QTest::newRow( "mULongLongString_mULongLong" ) << mULongLongString << mULongLong << false;
15211521
QTest::newRow( "mULongLong_mULongLongQString" ) << mULongLong << mULongLongQString << true;
15221522
QTest::newRow( "mULongLongQString_mULongLong" ) << mULongLongQString << mULongLong << true;
15231523

1524-
QTest::newRow( "mBool_mBoolString" ) << mBool << mBoolString << true;
1525-
QTest::newRow( "mBoolString_mBool" ) << mBoolString << mBool << true;
1524+
QTest::newRow( "mBool_mBoolString" ) << mBool << mBoolString << false;
1525+
QTest::newRow( "mBoolString_mBool" ) << mBoolString << mBool << false;
15261526
QTest::newRow( "mBool_mBoolQString" ) << mBool << mBoolQString << true;
15271527
QTest::newRow( "mBoolQString_mBool" ) << mBoolQString << mBool << true;
15281528

@@ -1531,16 +1531,16 @@ void tst_QVariant::operator_eq_eq_data()
15311531
QTest::newRow("char_char") << QVariant(QChar('a')) << QVariant(QChar('a')) << true;
15321532
QTest::newRow("char_char2") << QVariant(QChar('a')) << QVariant(QChar('b')) << false;
15331533

1534-
QTest::newRow("invalidConversion") << QVariant(QString("bubu")) << QVariant(0) << false;
1535-
QTest::newRow("invalidConversionR") << QVariant(0) << QVariant(QString("bubu")) << false;
1534+
QTest::newRow("invalidConversion") << QVariant(QString("bubu")) << QVariant() << false;
1535+
QTest::newRow("invalidConversionR") << QVariant() << QVariant(QString("bubu")) << false;
15361536
// ### many other combinations missing
15371537

15381538
{
15391539
QUuid uuid(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
1540-
QTest::newRow("uuidstring") << QVariant(uuid) << QVariant(uuid.toString()) << true;
1541-
QTest::newRow("stringuuid") << QVariant(uuid.toString()) << QVariant(uuid) << true;
1542-
QTest::newRow("uuidbytearray") << QVariant(uuid) << QVariant(uuid.toByteArray()) << true;
1543-
QTest::newRow("bytearrayuuid") << QVariant(uuid.toByteArray()) << QVariant(uuid) << true;
1540+
QTest::newRow("uuidstring") << QVariant(uuid) << QVariant(uuid.toString()) << false;
1541+
QTest::newRow("stringuuid") << QVariant(uuid.toString()) << QVariant(uuid) << false;
1542+
QTest::newRow("uuidbytearray") << QVariant(uuid) << QVariant(uuid.toByteArray()) << false;
1543+
QTest::newRow("bytearrayuuid") << QVariant(uuid.toByteArray()) << QVariant(uuid) << false;
15441544
}
15451545

15461546
{
@@ -2234,7 +2234,9 @@ void tst_QVariant::variantMap()
22342234
QVariant v3 = QVariant(QMetaType::type("QMap<QString, QVariant>"), &map);
22352235
QCOMPARE(qvariant_cast<QVariantMap>(v3).value("test").toInt(), 42);
22362236

2237-
QCOMPARE(v, QVariant(v.toHash()));
2237+
QHash<QString, QVariant> hash;
2238+
hash["test"] = 42;
2239+
QCOMPARE(hash, v.toHash());
22382240
}
22392241

22402242
void tst_QVariant::variantHash()
@@ -2257,7 +2259,9 @@ void tst_QVariant::variantHash()
22572259
QVariant v3 = QVariant(QMetaType::type("QHash<QString, QVariant>"), &hash);
22582260
QCOMPARE(qvariant_cast<QVariantHash>(v3).value("test").toInt(), 42);
22592261

2260-
QCOMPARE(v, QVariant(v.toMap()));
2262+
QMap<QString, QVariant> map;
2263+
map["test"] = 42;
2264+
QCOMPARE(map, v.toMap());
22612265
}
22622266

22632267
class CustomQObject : public QObject {

tests/auto/corelib/serialization/json/tst_qtjson.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,7 +1361,7 @@ void tst_QtJson::fromVariant_data()
13611361
jsonObject["null"] = QJsonValue::Null;
13621362
jsonObject["default"] = QJsonValue();
13631363

1364-
QTest::newRow("default") << QVariant() << QJsonValue(QJsonValue::Null);
1364+
QTest::newRow("default") << QVariant() << QJsonValue();
13651365
QTest::newRow("nullptr") << QVariant::fromValue(nullptr) << QJsonValue(QJsonValue::Null);
13661366
QTest::newRow("bool") << QVariant(boolValue) << QJsonValue(boolValue);
13671367
QTest::newRow("int") << QVariant(intValue) << QJsonValue(intValue);
@@ -1391,6 +1391,14 @@ static QVariant normalizedVariant(const QVariant &v)
13911391
out << normalizedVariant(v);
13921392
return out;
13931393
}
1394+
case QMetaType::QStringList: {
1395+
const QStringList in = v.toStringList();
1396+
QVariantList out;
1397+
out.reserve(in.size());
1398+
for (const QString &v : in)
1399+
out << v;
1400+
return out;
1401+
}
13941402
case QMetaType::QVariantMap: {
13951403
const QVariantMap in = v.toMap();
13961404
QVariantMap out;
@@ -1400,7 +1408,7 @@ static QVariant normalizedVariant(const QVariant &v)
14001408
}
14011409
case QMetaType::QVariantHash: {
14021410
const QVariantHash in = v.toHash();
1403-
QVariantHash out;
1411+
QVariantMap out;
14041412
for (auto it = in.begin(); it != in.end(); ++it)
14051413
out.insert(it.key(), normalizedVariant(it.value()));
14061414
return out;

0 commit comments

Comments
 (0)