Details
Description
QUrlQuery implements operator== the following way:
bool QUrlQuery::operator ==(const QUrlQuery &other) const { if (d == other.d) return true; if (d && other.d) // keep in sync with qHash(QUrlQuery): return d->valueDelimiter == other.d->valueDelimiter && d->pairDelimiter == other.d->pairDelimiter && d->itemList == other.d->itemList; return false; }
(conventionally it should be non-member to avoid inconsistent argument implicit convertions but that's not the point of this issue)
The implementation doesn't handle a case where one object has a non-null d (pimpl) and one has default-constructed/initialized pimpl. Analogy: 2 QString objects where one has no buffer allocated (isNull() returns true) and one has a buffer containing "" (isEmpty() returns true but isNull() returns false). For QString, operator== still considers such objects equal. QUrlQuery does not handle this case - if exactly one pimpl is null then the function flows to return false; statement.
Reproduction test:
QUrlQuery q1(QUrl(QStringLiteral("https://example.com")).query()); QUrlQuery q2; q2.setQuery(q1.query()); QCOMPARE(q1.toString(), q2.toString()); QCOMPARE(q1.queryPairDelimiter(), q2.queryPairDelimiter()); QCOMPARE(q1.queryValueDelimiter(), q2.queryValueDelimiter()); QCOMPARE(q1.queryItems(), q2.queryItems()); QCOMPARE(q1.query(), q2.query()); // compare class bodies (one will be 0s and one will not) QByteArray a1(reinterpret_cast<const char*>(&q1), sizeof(q1)); QByteArray a2(reinterpret_cast<const char*>(&q2), sizeof(q2)); QCOMPARE(a1, a2); // fails QCOMPARE(q1, q2); // fails