summaryrefslogtreecommitdiffstats
path: root/src/corelib/serialization/qcborvalue.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/serialization/qcborvalue.cpp')
-rw-r--r--src/corelib/serialization/qcborvalue.cpp548
1 files changed, 427 insertions, 121 deletions
diff --git a/src/corelib/serialization/qcborvalue.cpp b/src/corelib/serialization/qcborvalue.cpp
index a236899136..cd0b842111 100644
--- a/src/corelib/serialization/qcborvalue.cpp
+++ b/src/corelib/serialization/qcborvalue.cpp
@@ -17,7 +17,8 @@
#include <qendian.h>
#include <qlocale.h>
-#include <private/qbytearray_p.h>
+#include <qdatetime.h>
+#include <qtimezone.h>
#include <private/qnumeric_p.h>
#include <private/qsimd_p.h>
@@ -25,15 +26,33 @@
QT_BEGIN_NAMESPACE
+// Worst case memory allocation for a corrupt stream: 256 MB for 32-bit, 1 GB for 64-bit
+static constexpr quint64 MaxAcceptableMemoryUse = (sizeof(void*) == 4 ? 256 : 1024) * 1024 * 1024;
+
+// Internal limits to ensure we don't blow up the memory when parsing a corrupt
+// (possibly crafted to exploit) CBOR stream. The recursion impacts both the
+// maps/arrays we'll open when parsing and the thread's stack, as the parser is
+// itself recursive. If someone really needs more than 1024 layers of nesting,
+// they probably have a weird use-case for which custom parsing and
+// serialisation code would make sense. The limit on element count is the
+// preallocated limit: if the stream does actually have more elements, we will
+// grow the container.
+Q_DECL_UNUSED static constexpr int MaximumRecursionDepth = 1024;
+Q_DECL_UNUSED static constexpr quint64 MaximumPreallocatedElementCount =
+ MaxAcceptableMemoryUse / MaximumRecursionDepth / sizeof(QtCbor::Element) - 1;
+
/*!
\class QCborValue
\inmodule QtCore
\ingroup cbor
+ \ingroup qtserialization
\reentrant
\since 5.12
\brief The QCborValue class encapsulates a value in CBOR.
+ \compares strong
+
This class can be used to hold one of the many types available in CBOR.
CBOR is the Concise Binary Object Representation, a very compact form of
binary data encoding that is a superset of JSON. It was created by the IETF
@@ -173,8 +192,9 @@ QT_BEGIN_NAMESPACE
array or map it refers to will be modified with the new value. In all other
aspects, its API is identical to QCborValue.
- \sa QCborArray, QCborMap, QCborStreamReader, QCborStreamWriter
- QJsonValue, QJsonDocument
+ \sa QCborArray, QCborMap, QCborStreamReader, QCborStreamWriter,
+ QJsonValue, QJsonDocument, {Serialization Converter}, {Saving and Loading a Game}
+ {Parsing and displaying CBOR data}
*/
/*!
@@ -385,7 +405,7 @@ QT_BEGIN_NAMESPACE
/*!
\fn QCborValue::QCborValue(QCborSimpleType st)
- Creates a QCborValue of simple type \a st. The type can later later be retrieved
+ Creates a QCborValue of simple type \a st. The type can later be retrieved
using toSimpleType() as well as isSimpleType(st).
CBOR simple types are types that do not have any associated value, like
@@ -768,9 +788,9 @@ static QCborValue::Type convertToExtendedType(QCborContainerPrivate *d)
bool ok = false;
if (e.type == QCborValue::Integer) {
#if QT_POINTER_SIZE == 8
- // we don't have a fast 64-bit mul_overflow implementation on
+ // we don't have a fast 64-bit qMulOverflow implementation on
// 32-bit architectures.
- ok = !mul_overflow(e.value, qint64(1000), &msecs);
+ ok = !qMulOverflow(e.value, qint64(1000), &msecs);
#else
static const qint64 Limit = std::numeric_limits<qint64>::max() / 1000;
ok = (e.value > -Limit && e.value < Limit);
@@ -781,7 +801,7 @@ static QCborValue::Type convertToExtendedType(QCborContainerPrivate *d)
ok = convertDoubleTo(round(e.fpvalue() * 1000), &msecs);
}
if (ok)
- dt = QDateTime::fromMSecsSinceEpoch(msecs, Qt::UTC);
+ dt = QDateTime::fromMSecsSinceEpoch(msecs, QTimeZone::UTC);
}
if (dt.isValid()) {
QByteArray text = dt.toString(Qt::ISODateWithMs).toLatin1();
@@ -870,7 +890,7 @@ static void writeDoubleToCbor(QCborStreamWriter &writer, double d, QCborValue::E
// no data loss, we could use float
#ifndef QT_BOOTSTRAPPED
if ((opt & QCborValue::UseFloat16) == QCborValue::UseFloat16) {
- qfloat16 f16 = f;
+ qfloat16 f16 = qfloat16(f);
if (f16 == f)
return writer.append(f16);
}
@@ -884,12 +904,12 @@ static void writeDoubleToCbor(QCborStreamWriter &writer, double d, QCborValue::E
}
#endif // QT_CONFIG(cborstreamwriter)
-static inline int typeOrder(Element e1, Element e2)
+static inline int typeOrder(QCborValue::Type e1, QCborValue::Type e2)
{
- auto comparable = [](Element e) {
- if (e.type >= 0x10000) // see QCborValue::isTag_helper()
+ auto comparable = [](QCborValue::Type type) {
+ if (type >= 0x10000) // see QCborValue::isTag_helper()
return QCborValue::Tag;
- return e.type;
+ return type;
};
return comparable(e1) - comparable(e2);
}
@@ -903,14 +923,24 @@ QCborContainerPrivate::~QCborContainerPrivate()
}
}
-void QCborContainerPrivate::compact(qsizetype reserved)
+void QCborContainerPrivate::compact()
{
if (usedData > data.size() / 2)
return;
// 50% savings if we recreate the byte data
- // ### TBD
- Q_UNUSED(reserved);
+ QByteArray newData;
+ QByteArray::size_type newUsedData = 0;
+ // Compact only elements that have byte data.
+ // Nested containers will be compacted when their data changes.
+ for (auto &e : elements) {
+ if (e.flags & Element::HasByteData) {
+ if (const ByteData *b = byteData(e))
+ e.value = addByteDataImpl(newData, newUsedData, b->byte(), b->len);
+ }
+ }
+ data = newData;
+ usedData = newUsedData;
}
QCborContainerPrivate *QCborContainerPrivate::clone(QCborContainerPrivate *d, qsizetype reserved)
@@ -918,12 +948,17 @@ QCborContainerPrivate *QCborContainerPrivate::clone(QCborContainerPrivate *d, qs
if (!d) {
d = new QCborContainerPrivate;
} else {
- d = new QCborContainerPrivate(*d);
+ // in case QList::reserve throws
+ QExplicitlySharedDataPointer u(new QCborContainerPrivate(*d));
if (reserved >= 0) {
- d->elements.reserve(reserved);
- d->compact(reserved);
+ u->elements.reserve(reserved);
+ u->compact();
}
- for (auto &e : qAsConst(d->elements)) {
+
+ d = u.take();
+ d->ref.storeRelaxed(0);
+
+ for (auto &e : std::as_const(d->elements)) {
if (e.flags & Element::IsContainer)
e.container->ref.ref();
}
@@ -949,7 +984,7 @@ QCborContainerPrivate *QCborContainerPrivate::grow(QCborContainerPrivate *d, qsi
Q_ASSERT(index >= 0);
d = detach(d, index + 1);
Q_ASSERT(d);
- int j = d->elements.size();
+ qsizetype j = d->elements.size();
while (j++ < index)
d->append(Undefined());
return d;
@@ -989,10 +1024,23 @@ void QCborContainerPrivate::replaceAt_complex(Element &e, const QCborValue &valu
// Copy string data, if any
if (const ByteData *b = value.container->byteData(value.n)) {
- if (this == value.container)
- e.value = addByteData(b->toByteArray(), b->len);
- else
+ const auto flags = e.flags;
+ // The element e has an invalid e.value, because it is copied from
+ // value. It means that calling compact() will trigger an assertion
+ // or just silently corrupt the data.
+ // Temporarily unset the Element::HasByteData flag in order to skip
+ // the element e in the call to compact().
+ e.flags = e.flags & ~Element::HasByteData;
+ if (this == value.container) {
+ const QByteArray valueData = b->toByteArray();
+ compact();
+ e.value = addByteData(valueData, valueData.size());
+ } else {
+ compact();
e.value = addByteData(b->byte(), b->len);
+ }
+ // restore the flags
+ e.flags = flags;
}
if (disp == MoveContainer)
@@ -1017,6 +1065,12 @@ Q_NEVER_INLINE void QCborContainerPrivate::appendAsciiString(QStringView s)
qt_to_latin1_unchecked(l, s.utf16(), len);
}
+void QCborContainerPrivate::appendNonAsciiString(QStringView s)
+{
+ appendByteData(reinterpret_cast<const char *>(s.utf16()), s.size() * 2,
+ QCborValue::String, QtCbor::Element::StringIsUtf16);
+}
+
QCborValue QCborContainerPrivate::extractAt_complex(Element e)
{
// create a new container for the returned value, containing the byte data
@@ -1029,7 +1083,7 @@ QCborValue QCborContainerPrivate::extractAt_complex(Element e)
// make a shallow copy of the byte data
container->appendByteData(b->byte(), b->len, e.type, e.flags);
usedData -= b->len + qsizetype(sizeof(*b));
- compact(elements.size());
+ compact();
} else {
// just share with the original byte data
container->data = data;
@@ -1040,9 +1094,127 @@ QCborValue QCborContainerPrivate::extractAt_complex(Element e)
return makeValue(e.type, 0, container);
}
+// Similar to QStringIterator::next() but returns malformed surrogate pair
+// itself when one is detected, and returns the length in UTF-8.
+static auto nextUtf32Character(const char16_t *&ptr, const char16_t *end) noexcept
+{
+ Q_ASSERT(ptr != end);
+ struct R {
+ char32_t c;
+ qsizetype len = 1; // in UTF-8 code units (bytes)
+ } r = { *ptr++ };
+
+ if (r.c < 0x0800) {
+ if (r.c >= 0x0080)
+ ++r.len;
+ } else if (!QChar::isHighSurrogate(r.c) || ptr == end) {
+ r.len += 2;
+ } else {
+ r.len += 3;
+ r.c = QChar::surrogateToUcs4(r.c, *ptr++);
+ }
+
+ return r;
+}
+
+static qsizetype stringLengthInUtf8(const char16_t *ptr, const char16_t *end) noexcept
+{
+ qsizetype len = 0;
+ while (ptr < end)
+ len += nextUtf32Character(ptr, end).len;
+ return len;
+}
+
+static int compareStringsInUtf8(QStringView lhs, QStringView rhs, Comparison mode) noexcept
+{
+ if (mode == Comparison::ForEquality)
+ return lhs == rhs ? 0 : 1;
+
+ // The UTF-16 length is *usually* comparable, but not always. There are
+ // pathological cases where they can be wrong, so we need to compare as if
+ // we were doing it in UTF-8. That includes the case of UTF-16 surrogate
+ // pairs, because qstring.cpp sorts them before U+E000-U+FFFF.
+ int diff = 0;
+ qsizetype len1 = 0;
+ qsizetype len2 = 0;
+ const char16_t *src1 = lhs.utf16();
+ const char16_t *src2 = rhs.utf16();
+ const char16_t *end1 = src1 + lhs.size();
+ const char16_t *end2 = src2 + rhs.size();
+
+ // first, scan until we find a difference (if any)
+ do {
+ auto r1 = nextUtf32Character(src1, end1);
+ auto r2 = nextUtf32Character(src2, end2);
+ len1 += r1.len;
+ len2 += r2.len;
+ diff = int(r1.c) - int(r2.c); // no underflow due to limited range
+ } while (src1 < end1 && src2 < end2 && diff == 0);
+
+ // compute the full length past this first difference
+ len1 += stringLengthInUtf8(src1, end1);
+ len2 += stringLengthInUtf8(src2, end2);
+ if (len1 == len2)
+ return diff;
+ return len1 < len2 ? -1 : 1;
+}
+
+static int compareStringsInUtf8(QUtf8StringView lhs, QStringView rhs, Comparison mode) noexcept
+{
+ // CBOR requires that the shortest of the two strings be sorted first, so
+ // we have to calculate the UTF-8 length of the UTF-16 string while
+ // comparing. Unlike the UTF-32 comparison above, we convert the UTF-16
+ // string to UTF-8 so we only need to decode one string.
+
+ const qsizetype len1 = lhs.size();
+ const auto src1 = reinterpret_cast<const uchar *>(lhs.data());
+ const char16_t *src2 = rhs.utf16();
+ const char16_t *const end2 = src2 + rhs.size();
+
+ // Compare the two strings until we find a difference.
+ int diff = 0;
+ qptrdiff idx1 = 0;
+ qsizetype len2 = 0;
+ do {
+ uchar utf8[4]; // longest possible Unicode character in UTF-8
+ uchar *ptr = utf8;
+ char16_t uc = *src2++;
+ int r = QUtf8Functions::toUtf8<QUtf8BaseTraits>(uc, ptr, src2, end2);
+ Q_UNUSED(r); // ignore failure to encode proper UTF-16 surrogates
+
+ qptrdiff n = ptr - utf8;
+ len2 += n;
+ if (len1 - idx1 < n)
+ return -1; // lhs is definitely shorter
+ diff = memcmp(src1 + idx1, utf8, n);
+ idx1 += n;
+ } while (diff == 0 && idx1 < len1 && src2 < end2);
+
+ if (mode == Comparison::ForEquality && diff)
+ return diff;
+ if ((idx1 == len1) != (src2 == end2)) {
+ // One of the strings ended earlier than the other
+ return idx1 == len1 ? -1 : 1;
+ }
+
+ // We found a difference and neither string ended, so continue calculating
+ // the UTF-8 length of rhs.
+ len2 += stringLengthInUtf8(src2, end2);
+
+ if (len1 != len2)
+ return len1 < len2 ? -1 : 1;
+ return diff;
+}
+
+static int compareStringsInUtf8(QStringView lhs, QUtf8StringView rhs, Comparison mode) noexcept
+{
+ return -compareStringsInUtf8(rhs, lhs, mode);
+}
+
QT_WARNING_DISABLE_MSVC(4146) // unary minus operator applied to unsigned type, result still unsigned
-static int compareContainer(const QCborContainerPrivate *c1, const QCborContainerPrivate *c2);
-static int compareElementNoData(const Element &e1, const Element &e2)
+static int compareContainer(const QCborContainerPrivate *c1, const QCborContainerPrivate *c2,
+ Comparison mode) noexcept;
+static int compareElementNoData(const Element &e1, const Element &e2) noexcept
{
Q_ASSERT(e1.type == e2.type);
@@ -1086,15 +1258,16 @@ static int compareElementNoData(const Element &e1, const Element &e2)
}
static int compareElementRecursive(const QCborContainerPrivate *c1, const Element &e1,
- const QCborContainerPrivate *c2, const Element &e2)
+ const QCborContainerPrivate *c2, const Element &e2,
+ Comparison mode) noexcept
{
- int cmp = typeOrder(e1, e2);
+ int cmp = typeOrder(e1.type, e2.type);
if (cmp != 0)
return cmp;
if ((e1.flags & Element::IsContainer) || (e2.flags & Element::IsContainer))
return compareContainer(e1.flags & Element::IsContainer ? e1.container : nullptr,
- e2.flags & Element::IsContainer ? e2.container : nullptr);
+ e2.flags & Element::IsContainer ? e2.container : nullptr, mode);
// string data?
const ByteData *b1 = c1 ? c1->byteData(e1) : nullptr;
@@ -1102,11 +1275,6 @@ static int compareElementRecursive(const QCborContainerPrivate *c1, const Elemen
if (b1 || b2) {
auto len1 = b1 ? b1->len : 0;
auto len2 = b2 ? b2->len : 0;
-
- if (e1.flags & Element::StringIsUtf16)
- len1 /= 2;
- if (e2.flags & Element::StringIsUtf16)
- len2 /= 2;
if (len1 == 0 || len2 == 0)
return len1 < len2 ? -1 : len1 == len2 ? 0 : 1;
@@ -1115,58 +1283,37 @@ static int compareElementRecursive(const QCborContainerPrivate *c1, const Elemen
Q_ASSERT(b2);
// Officially with CBOR, we sort first the string with the shortest
- // UTF-8 length. The length of an ASCII string is the same as its UTF-8
- // and UTF-16 ones, but the UTF-8 length of a string is bigger than the
- // UTF-16 equivalent. Combinations are:
- // 1) UTF-16 and UTF-16
- // 2) UTF-16 and UTF-8 <=== this is the problem case
- // 3) UTF-16 and US-ASCII
- // 4) UTF-8 and UTF-8
- // 5) UTF-8 and US-ASCII
- // 6) US-ASCII and US-ASCII
- if ((e1.flags & Element::StringIsUtf16) && (e2.flags & Element::StringIsUtf16)) {
- // Case 1: both UTF-16, so lengths are comparable.
- // (we can't use memcmp in little-endian machines)
- if (len1 == len2)
- return QtPrivate::compareStrings(b1->asStringView(), b2->asStringView());
- return len1 < len2 ? -1 : 1;
- }
+ // UTF-8 length. Since US-ASCII is just a subset of UTF-8, its length
+ // is the UTF-8 length. But the UTF-16 length may not be directly
+ // comparable.
+ if ((e1.flags & Element::StringIsUtf16) && (e2.flags & Element::StringIsUtf16))
+ return compareStringsInUtf8(b1->asStringView(), b2->asStringView(), mode);
if (!(e1.flags & Element::StringIsUtf16) && !(e2.flags & Element::StringIsUtf16)) {
- // Cases 4, 5 and 6: neither is UTF-16, so lengths are comparable too
+ // Neither is UTF-16, so lengths are comparable too
// (this case includes byte arrays too)
- if (len1 == len2)
+ if (len1 == len2) {
+ if (mode == Comparison::ForEquality) {
+ // GCC optimizes this to __memcmpeq(); Clang to bcmp()
+ return memcmp(b1->byte(), b2->byte(), size_t(len1)) == 0 ? 0 : 1;
+ }
return memcmp(b1->byte(), b2->byte(), size_t(len1));
+ }
return len1 < len2 ? -1 : 1;
}
- if (!(e1.flags & Element::StringIsAscii) || !(e2.flags & Element::StringIsAscii)) {
- // Case 2: one of them is UTF-8 and the other is UTF-16, so lengths
- // are NOT comparable. We need to convert to UTF-16 first...
- // (we can't use QUtf8::compareUtf8 because we need to compare lengths)
- auto string = [](const Element &e, const ByteData *b) {
- return e.flags & Element::StringIsUtf16 ? b->asQStringRaw() : b->toUtf8String();
- };
-
- QString s1 = string(e1, b1);
- QString s2 = string(e2, b2);
- if (s1.size() == s2.size())
- return s1.compare(s2);
- return s1.size() < s2.size() ? -1 : 1;
- }
-
- // Case 3 (UTF-16 and US-ASCII) remains, so lengths are comparable again
- if (len1 != len2)
- return len1 < len2 ? -1 : 1;
+ // Only one is UTF-16
if (e1.flags & Element::StringIsUtf16)
- return QtPrivate::compareStrings(b1->asStringView(), b2->asLatin1());
- return QtPrivate::compareStrings(b1->asLatin1(), b2->asStringView());
+ return compareStringsInUtf8(b1->asStringView(), b2->asUtf8StringView(), mode);
+ else
+ return compareStringsInUtf8(b1->asUtf8StringView(), b2->asStringView(), mode);
}
return compareElementNoData(e1, e2);
}
-static int compareContainer(const QCborContainerPrivate *c1, const QCborContainerPrivate *c2)
+static int compareContainer(const QCborContainerPrivate *c1, const QCborContainerPrivate *c2,
+ Comparison mode) noexcept
{
auto len1 = c1 ? c1->elements.size() : 0;
auto len2 = c2 ? c2->elements.size() : 0;
@@ -1178,7 +1325,7 @@ static int compareContainer(const QCborContainerPrivate *c1, const QCborContaine
for (qsizetype i = 0; i < len1; ++i) {
const Element &e1 = c1->elements.at(i);
const Element &e2 = c2->elements.at(i);
- int cmp = QCborContainerPrivate::compareElement_helper(c1, e1, c2, e2);
+ int cmp = compareElementRecursive(c1, e1, c2, e2, mode);
if (cmp)
return cmp;
}
@@ -1187,15 +1334,16 @@ static int compareContainer(const QCborContainerPrivate *c1, const QCborContaine
}
inline int QCborContainerPrivate::compareElement_helper(const QCborContainerPrivate *c1, Element e1,
- const QCborContainerPrivate *c2, Element e2)
+ const QCborContainerPrivate *c2, Element e2,
+ Comparison mode) noexcept
{
- return compareElementRecursive(c1, e1, c2, e2);
+ return compareElementRecursive(c1, e1, c2, e2, mode);
}
/*!
- \fn bool QCborValue::operator==(const QCborValue &other) const
+ \fn bool QCborValue::operator==(const QCborValue &lhs, const QCborValue &rhs)
- Compares this value and \a other, and returns true if they hold the same
+ Compares \a lhs and \a rhs, and returns true if they hold the same
contents, false otherwise. If each QCborValue contains an array or map, the
comparison is recursive to elements contained in them.
@@ -1206,9 +1354,9 @@ inline int QCborContainerPrivate::compareElement_helper(const QCborContainerPriv
*/
/*!
- \fn bool QCborValue::operator!=(const QCborValue &other) const
+ \fn bool QCborValue::operator!=(const QCborValue &lhs, const QCborValue &rhs)
- Compares this value and \a other, and returns true if contents differ,
+ Compares \a lhs and \a rhs, and returns true if contents differ,
false otherwise. If each QCborValue contains an array or map, the comparison
is recursive to elements contained in them.
@@ -1217,12 +1365,20 @@ inline int QCborContainerPrivate::compareElement_helper(const QCborContainerPriv
\sa compare(), QCborValue::operator==(), QCborMap::operator==(),
operator==(), operator<()
*/
+bool comparesEqual(const QCborValue &lhs,
+ const QCborValue &rhs) noexcept
+{
+ Element e1 = QCborContainerPrivate::elementFromValue(lhs);
+ Element e2 = QCborContainerPrivate::elementFromValue(rhs);
+ return compareElementRecursive(lhs.container, e1, rhs.container, e2,
+ Comparison::ForEquality) == 0;
+}
/*!
- \fn bool QCborValue::operator<(const QCborValue &other) const
+ \fn bool QCborValue::operator<(const QCborValue &lhs, const QCborValue &rhs)
- Compares this value and \a other, and returns true if this value should be
- sorted before \a other, false otherwise. If each QCborValue contains an
+ Compares \a lhs and \a rhs, and returns true if \a lhs should be
+ sorted before \a rhs, false otherwise. If each QCborValue contains an
array or map, the comparison is recursive to elements contained in them.
For more information on CBOR sorting order, see QCborValue::compare().
@@ -1232,6 +1388,47 @@ inline int QCborContainerPrivate::compareElement_helper(const QCborContainerPriv
*/
/*!
+ \fn bool QCborValue::operator<=(const QCborValue &lhs, const QCborValue &rhs)
+
+ Compares \a lhs and \a rhs, and returns true if \a lhs should be
+ sorted before \a rhs or is being equal to \a rhs, false otherwise.
+ If each QCborValue contains an array or map, the comparison is recursive
+ to elements contained in them.
+
+ For more information on CBOR sorting order, see QCborValue::compare().
+
+ \sa compare(), QCborValue::operator<(), QCborMap::operator==(),
+ operator==(), operator!=()
+*/
+
+/*!
+ \fn bool QCborValue::operator>(const QCborValue &lhs, const QCborValue &rhs)
+
+ Compares \a lhs and \a rhs, and returns true if \a lhs should be
+ sorted after \a rhs, false otherwise. If each QCborValue contains an
+ array or map, the comparison is recursive to elements contained in them.
+
+ For more information on CBOR sorting order, see QCborValue::compare().
+
+ \sa compare(), QCborValue::operator>=(), QCborMap::operator==(),
+ operator==(), operator!=()
+*/
+
+/*!
+ \fn bool QCborValue::operator>=(const QCborValue &lhs, const QCborValue &rhs)
+
+ Compares \a lhs and \a rhs, and returns true if \a lhs should be
+ sorted after \a rhs or is being equal to \a rhs, false otherwise.
+ If each QCborValue contains an array or map, the comparison is recursive
+ to elements contained in them.
+
+ For more information on CBOR sorting order, see QCborValue::compare().
+
+ \sa compare(), QCborValue::operator>(), QCborMap::operator==(),
+ operator==(), operator!=()
+*/
+
+/*!
Compares this value and \a other, and returns an integer that indicates
whether this value should be sorted prior to (if the result is negative) or
after \a other (if the result is positive). If this function returns 0, the
@@ -1296,17 +1493,59 @@ int QCborValue::compare(const QCborValue &other) const
{
Element e1 = QCborContainerPrivate::elementFromValue(*this);
Element e2 = QCborContainerPrivate::elementFromValue(other);
- return compareElementRecursive(container, e1, other.container, e2);
+ return compareElementRecursive(container, e1, other.container, e2, Comparison::ForOrdering);
+}
+
+bool comparesEqual(const QCborArray &lhs, const QCborArray &rhs) noexcept
+{
+ return compareContainer(lhs.d.constData(), rhs.d.constData(), Comparison::ForEquality) == 0;
}
int QCborArray::compare(const QCborArray &other) const noexcept
{
- return compareContainer(d.data(), other.d.data());
+ return compareContainer(d.data(), other.d.data(), Comparison::ForOrdering);
+}
+
+bool QCborArray::comparesEqual_helper(const QCborArray &lhs, const QCborValue &rhs) noexcept
+{
+ if (typeOrder(QCborValue::Array, rhs.type()))
+ return false;
+ return compareContainer(lhs.d.constData(), rhs.container, Comparison::ForEquality) == 0;
+}
+
+Qt::strong_ordering
+QCborArray::compareThreeWay_helper(const QCborArray &lhs, const QCborValue &rhs) noexcept
+{
+ int c = typeOrder(QCborValue::Array, rhs.type());
+ if (c == 0)
+ c = compareContainer(lhs.d.constData(), rhs.container, Comparison::ForOrdering);
+ return Qt::compareThreeWay(c, 0);
+}
+
+bool comparesEqual(const QCborMap &lhs, const QCborMap &rhs) noexcept
+{
+ return compareContainer(lhs.d.constData(), rhs.d.constData(), Comparison::ForEquality) == 0;
}
int QCborMap::compare(const QCborMap &other) const noexcept
{
- return compareContainer(d.data(), other.d.data());
+ return compareContainer(d.data(), other.d.data(), Comparison::ForOrdering);
+}
+
+bool QCborMap::comparesEqual_helper(const QCborMap &lhs, const QCborValue &rhs) noexcept
+{
+ if (typeOrder(QCborValue::Map, rhs.type()))
+ return false;
+ return compareContainer(lhs.d.constData(), rhs.container, Comparison::ForEquality) == 0;
+}
+
+Qt::strong_ordering
+QCborMap::compareThreeWay_helper(const QCborMap &lhs, const QCborValue &rhs) noexcept
+{
+ int c = typeOrder(QCborValue::Map, rhs.type());
+ if (c == 0)
+ c = compareContainer(lhs.d.constData(), rhs.container, Comparison::ForOrdering);
+ return Qt::compareThreeWay(c, 0);
}
#if QT_CONFIG(cborstreamwriter)
@@ -1458,6 +1697,19 @@ static Element decodeBasicValueFromCbor(QCborStreamReader &reader)
return e;
}
+// Clamp allocation to avoid crashing due to corrupt stream. This also
+// ensures we never overflow qsizetype. The returned length is doubled for Map
+// entries to account for key-value pairs.
+static qsizetype clampedContainerLength(const QCborStreamReader &reader)
+{
+ if (!reader.isLengthKnown())
+ return 0;
+ int mapShift = reader.isMap() ? 1 : 0;
+ quint64 shiftedMaxElements = MaximumPreallocatedElementCount >> mapShift;
+ qsizetype len = qsizetype(qMin(reader.length(), shiftedMaxElements));
+ return len << mapShift;
+}
+
static inline QCborContainerPrivate *createContainerFromCbor(QCborStreamReader &reader, int remainingRecursionDepth)
{
if (Q_UNLIKELY(remainingRecursionDepth == 0)) {
@@ -1466,33 +1718,27 @@ static inline QCborContainerPrivate *createContainerFromCbor(QCborStreamReader &
}
QCborContainerPrivate *d = nullptr;
- int mapShift = reader.isMap() ? 1 : 0;
- if (reader.isLengthKnown()) {
- quint64 len = reader.length();
-
- // Clamp allocation to 1M elements (avoids crashing due to corrupt
- // stream or loss of precision when converting from quint64 to
- // QList::size_type).
- len = qMin(len, quint64(1024 * 1024 - 1));
- if (len) {
- d = new QCborContainerPrivate;
- d->ref.storeRelaxed(1);
- d->elements.reserve(qsizetype(len) << mapShift);
- }
- } else {
- d = new QCborContainerPrivate;
- d->ref.storeRelaxed(1);
+ {
+ // in case QList::reserve throws
+ QExplicitlySharedDataPointer u(new QCborContainerPrivate);
+ if (qsizetype len = clampedContainerLength(reader))
+ u->elements.reserve(len);
+ d = u.take();
}
reader.enterContainer();
- if (reader.lastError() != QCborError::NoError)
+ if (reader.lastError() != QCborError::NoError) {
+ d->elements.clear();
return d;
+ }
while (reader.hasNext() && reader.lastError() == QCborError::NoError)
d->decodeValueFromCbor(reader, remainingRecursionDepth - 1);
if (reader.lastError() == QCborError::NoError)
reader.leaveContainer();
+ else
+ d->elements.squeeze();
return d;
}
@@ -1562,7 +1808,7 @@ void QCborContainerPrivate::decodeStringFromCbor(QCborStreamReader &reader)
// add space for aligned ByteData (this can't overflow)
offset += sizeof(QtCbor::ByteData) + alignof(QtCbor::ByteData);
offset &= ~(alignof(QtCbor::ByteData) - 1);
- if (offset > size_t(MaxByteArraySize)) {
+ if (offset > size_t(QByteArray::max_size())) {
// overflow
setErrorInReader(reader, { QCborError::DataTooLarge });
return;
@@ -1575,9 +1821,9 @@ void QCborContainerPrivate::decodeStringFromCbor(QCborStreamReader &reader)
// so capa how much we allocate
newCapacity = offset + MaxMemoryIncrement - EstimatedOverhead;
}
- if (newCapacity > size_t(MaxByteArraySize)) {
+ if (newCapacity > size_t(QByteArray::max_size())) {
// this may cause an allocation failure
- newCapacity = MaxByteArraySize;
+ newCapacity = QByteArray::max_size();
}
if (newCapacity > size_t(data.capacity()))
data.reserve(newCapacity);
@@ -1628,7 +1874,7 @@ void QCborContainerPrivate::decodeStringFromCbor(QCborStreamReader &reader)
// check that this UTF-8 text string can be loaded onto a QString
if (e.type == QCborValue::String) {
- if (Q_UNLIKELY(b->len > MaxStringSize)) {
+ if (Q_UNLIKELY(b->len > QString::max_size())) {
setErrorInReader(reader, { QCborError::DataTooLarge });
status = QCborStreamReader::Error;
}
@@ -1688,7 +1934,6 @@ QCborValue::QCborValue(const QByteArray &ba)
container->ref.storeRelaxed(1);
}
-#if QT_STRINGVIEW_LEVEL < 2
/*!
Creates a QCborValue with string value \a s. The value can later be
retrieved using toString().
@@ -1696,7 +1941,6 @@ QCborValue::QCborValue(const QByteArray &ba)
\sa toString(), isString(), isByteArray()
*/
QCborValue::QCborValue(const QString &s) : QCborValue(qToStringViewIgnoringNull(s)) {}
-#endif
/*!
Creates a QCborValue with string value \a s. The value can later be
@@ -1714,8 +1958,8 @@ QCborValue::QCborValue(QStringView s)
/*!
\overload
- Creates a QCborValue with string value \a s. The value can later be
- retrieved using toString().
+ Creates a QCborValue with the Latin-1 string viewed by \a s.
+ The value can later be retrieved using toString().
\sa toString(), isString(), isByteArray()
*/
@@ -1780,7 +2024,7 @@ QCborValue::QCborValue(QCborTag tag, const QCborValue &tv)
/*!
Copies the contents of \a other into this object.
*/
-QCborValue::QCborValue(const QCborValue &other)
+QCborValue::QCborValue(const QCborValue &other) noexcept
: n(other.n), container(other.container), t(other.t)
{
if (container)
@@ -1874,7 +2118,7 @@ void QCborValue::dispose()
/*!
Replaces the contents of this QCborObject with a copy of \a other.
*/
-QCborValue &QCborValue::operator=(const QCborValue &other)
+QCborValue &QCborValue::operator=(const QCborValue &other) noexcept
{
n = other.n;
assignContainer(container, other.container);
@@ -2219,12 +2463,6 @@ static void convertArrayToMap(QCborContainerPrivate *&array)
for (qsizetype i = 0; i < size; ++i)
dst[i * 2] = { i, QCborValue::Integer };
- // only do this last portion if we're not modifying in-place
- for (qsizetype i = 0; src != dst && i < size; ++i) {
- if (dst[i * 2 + 1].flags & QtCbor::Element::IsContainer)
- dst[i * 2 + 1].container->ref.ref();
- }
-
// update reference counts
assignContainer(array, map);
}
@@ -2344,8 +2582,6 @@ QCborValueRef QCborValue::operator[](qint64 key)
}
#if QT_CONFIG(cborstreamreader)
-enum { MaximumRecursionDepth = 1024 };
-
/*!
Decodes one item from the CBOR stream found in \a reader and returns the
equivalent representation. This function is recursive: if the item is a map
@@ -2651,6 +2887,76 @@ QString QCborValueConstRef::concreteString(QCborValueConstRef self, const QStrin
return self.d->stringAt(self.i);
}
+bool
+QCborValueConstRef::comparesEqual_helper(QCborValueConstRef lhs, QCborValueConstRef rhs) noexcept
+{
+ QtCbor::Element e1 = lhs.d->elements.at(lhs.i);
+ QtCbor::Element e2 = rhs.d->elements.at(rhs.i);
+ return compareElementRecursive(lhs.d, e1, rhs.d, e2, Comparison::ForEquality) == 0;
+}
+
+Qt::strong_ordering
+QCborValueConstRef::compareThreeWay_helper(QCborValueConstRef lhs, QCborValueConstRef rhs) noexcept
+{
+ QtCbor::Element e1 = lhs.d->elements.at(lhs.i);
+ QtCbor::Element e2 = rhs.d->elements.at(rhs.i);
+ int c = compareElementRecursive(lhs.d, e1, rhs.d, e2, Comparison::ForOrdering);
+ return Qt::compareThreeWay(c, 0);
+}
+
+bool
+QCborValueConstRef::comparesEqual_helper(QCborValueConstRef lhs, const QCborValue &rhs) noexcept
+{
+ QtCbor::Element e1 = lhs.d->elements.at(lhs.i);
+ QtCbor::Element e2 = QCborContainerPrivate::elementFromValue(rhs);
+ return compareElementRecursive(lhs.d, e1, rhs.container, e2, Comparison::ForEquality) == 0;
+}
+
+Qt::strong_ordering
+QCborValueConstRef::compareThreeWay_helper(QCborValueConstRef lhs, const QCborValue &rhs) noexcept
+{
+ QtCbor::Element e1 = lhs.d->elements.at(lhs.i);
+ QtCbor::Element e2 = QCborContainerPrivate::elementFromValue(rhs);
+ int c = compareElementRecursive(lhs.d, e1, rhs.container, e2, Comparison::ForOrdering);
+ return Qt::compareThreeWay(c, 0);
+}
+
+bool QCborArray::comparesEqual_helper(const QCborArray &lhs, QCborValueConstRef rhs) noexcept
+{
+ QtCbor::Element e2 = rhs.d->elements.at(rhs.i);
+ if (typeOrder(QCborValue::Array, e2.type))
+ return false;
+ return compareContainer(lhs.d.constData(), e2.container, Comparison::ForEquality) == 0;
+}
+
+Qt::strong_ordering
+QCborArray::compareThreeWay_helper(const QCborArray &lhs, QCborValueConstRef rhs) noexcept
+{
+ QtCbor::Element e2 = rhs.d->elements.at(rhs.i);
+ int c = typeOrder(QCborValue::Array, e2.type);
+ if (c == 0)
+ c = compareContainer(lhs.d.constData(), e2.container, Comparison::ForOrdering);
+ return Qt::compareThreeWay(c, 0);
+}
+
+bool QCborMap::comparesEqual_helper(const QCborMap &lhs, QCborValueConstRef rhs) noexcept
+{
+ QtCbor::Element e2 = rhs.d->elements.at(rhs.i);
+ if (typeOrder(QCborValue::Array, e2.type))
+ return false;
+ return compareContainer(lhs.d.constData(), e2.container, Comparison::ForEquality) == 0;
+}
+
+Qt::strong_ordering
+QCborMap::compareThreeWay_helper(const QCborMap &lhs, QCborValueConstRef rhs) noexcept
+{
+ QtCbor::Element e2 = rhs.d->elements.at(rhs.i);
+ int c = typeOrder(QCborValue::Map, e2.type);
+ if (c == 0)
+ c = compareContainer(lhs.d.constData(), e2.container, Comparison::ForOrdering);
+ return Qt::compareThreeWay(c, 0);
+}
+
QCborValue QCborValueConstRef::concrete(QCborValueConstRef self) noexcept
{
return self.d->valueAt(self.i);