From 042b118cafc68c677e9eafa7f718a025a34940da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Wed, 29 Apr 2020 19:14:06 +0200 Subject: [PATCH 1/6] macOS: Avoid modifying hash while iterating it QCocoaTouch sneakily updated the list of active touches in the destructor, causing problems when we switched from Q_FOREACH to ranged-for. Fixes: QTBUG-83876 Change-Id: If7ec9e9116b7eb581580b461ae12ad70c739906d Reviewed-by: Simon Hausmann --- src/plugins/platforms/cocoa/qmultitouch_mac.mm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/platforms/cocoa/qmultitouch_mac.mm b/src/plugins/platforms/cocoa/qmultitouch_mac.mm index 95256657fe..ac2317b217 100644 --- a/src/plugins/platforms/cocoa/qmultitouch_mac.mm +++ b/src/plugins/platforms/cocoa/qmultitouch_mac.mm @@ -184,7 +184,10 @@ QCocoaTouch::getCurrentTouchPointList(NSEvent *event, bool acceptSingleTouch) if (_touchCount != _currentTouches.size()) { // Remove all instances, and basically start from scratch: touchPoints.clear(); - for (QCocoaTouch *qcocoaTouch : _currentTouches) { + // Deleting touch points will remove them from current touches, + // so we make a copy of the touches before iterating them. + const auto currentTouchesSnapshot = _currentTouches; + for (QCocoaTouch *qcocoaTouch : currentTouchesSnapshot) { if (!_updateInternalStateOnly) { qcocoaTouch->_touchPoint.state = Qt::TouchPointReleased; touchPoints.insert(qcocoaTouch->_touchPoint.id, qcocoaTouch->_touchPoint); From 1eeabc6652220ff263eaa63872cd52c1693cbf69 Mon Sep 17 00:00:00 2001 From: Andy Shaw Date: Thu, 30 Apr 2020 09:39:16 +0200 Subject: [PATCH 2/6] Don't output a warning that can trigger before Qt has fully initialized If a warning is outputted before Qt has had a chance to initialize the logging rules then it will cause it to hang as it will be stuck trying to initialize but can't continue because it needs to output the warning which triggered during that initialization. Therefore the warning should not be outputted to avoid this from happening. Change-Id: I202752c377bf69def2f7fb76be71b78d00ad3fd5 Reviewed-by: Volker Hilsheimer --- src/corelib/text/qlocale.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/corelib/text/qlocale.cpp b/src/corelib/text/qlocale.cpp index 60bfac62ab..e74b825774 100644 --- a/src/corelib/text/qlocale.cpp +++ b/src/corelib/text/qlocale.cpp @@ -763,8 +763,6 @@ static void updateSystemPrivate() const ushort group = res.toString().at(0).unicode(); if (group != globalLocaleData.m_decimal) globalLocaleData.m_group = group; - else if (group == globalLocaleData.m_group) - qWarning("System-supplied decimal and grouping character are both 0x%hx", group); } res = sys_locale->query(QSystemLocale::ZeroDigit, QVariant()); From 52a2505672cbb1ca8b5b32f7bc1259485c65483b Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Wed, 15 Apr 2020 13:58:33 -0300 Subject: [PATCH 3/6] QCborValue: avoid signed integer oveflows when decoding time_t QDateTime::fromSecsSinceEpoch() multiplies by 1000 but does not check for overflow. That means we must do so in QCborValue validation. We can't use mul_overflow on 32-bit platforms, so we do a compare- and-branch there. For 64-bit platforms, we prefer to do the multiplication with checked overflow, as the common case is that it will not overflow and we'll need the multiplication anyway. Change-Id: Ibdc95e9af7bd456a94ecfffd16060cba6f1c86b8 Reviewed-by: Edward Welbourne --- src/corelib/serialization/qcborvalue.cpp | 23 +++- .../qcborvalue/tst_qcborvalue.cpp | 109 ++++++++++++++++++ 2 files changed, 128 insertions(+), 4 deletions(-) diff --git a/src/corelib/serialization/qcborvalue.cpp b/src/corelib/serialization/qcborvalue.cpp index a3729b4ef9..f5cccf1be1 100644 --- a/src/corelib/serialization/qcborvalue.cpp +++ b/src/corelib/serialization/qcborvalue.cpp @@ -788,10 +788,25 @@ static QCborValue::Type convertToExtendedType(QCborContainerPrivate *d) // The data is supposed to be US-ASCII. If it isn't (contains UTF-8), // QDateTime::fromString will fail anyway. dt = QDateTime::fromString(b->asLatin1(), Qt::ISODateWithMs); - } else if (tag == qint64(QCborKnownTags::UnixTime_t) && e.type == QCborValue::Integer) { - dt = QDateTime::fromSecsSinceEpoch(e.value, Qt::UTC); - } else if (tag == qint64(QCborKnownTags::UnixTime_t) && e.type == QCborValue::Double) { - dt = QDateTime::fromMSecsSinceEpoch(qint64(e.fpvalue() * 1000), Qt::UTC); + } else if (tag == qint64(QCborKnownTags::UnixTime_t)) { + qint64 msecs; + 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 + // 32-bit architectures. + ok = !mul_overflow(e.value, qint64(1000), &msecs); +#else + static const qint64 Limit = std::numeric_limits::max() / 1000; + ok = (e.value > -Limit && e.value < Limit); + if (ok) + msecs = e.value * 1000; +#endif + } else if (e.type == QCborValue::Double) { + ok = convertDoubleTo(round(e.fpvalue() * 1000), &msecs); + } + if (ok) + dt = QDateTime::fromMSecsSinceEpoch(msecs, Qt::UTC); } if (dt.isValid()) { QByteArray text = dt.toString(Qt::ISODateWithMs).toLatin1(); diff --git a/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp b/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp index 64321c11fa..0b3046fbdc 100644 --- a/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp +++ b/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp @@ -106,6 +106,8 @@ private slots: void fromCborStreamReaderIODevice(); void validation_data(); void validation(); + void extendedTypeValidation_data(); + void extendedTypeValidation(); void hugeDeviceValidation_data(); void hugeDeviceValidation(); void recursionLimit_data(); @@ -118,6 +120,68 @@ private slots: void streamVariantSerialization(); }; +namespace SimpleEncodeToCbor { +inline size_t lengthOf(int) +{ + return 1; // encode as byte +} + +template inline size_t lengthOf(T) +{ + return sizeof(T); +} + +static void encodeOneAt(char *ptr, int v) +{ + // encode as byte + *ptr = char(v); +} + +template +static typename std::enable_if::value>::type +encodeOneAt(char *ptr, T v) +{ + qToBigEndian(v, ptr); +} + +template +static typename std::enable_if::value || + std::is_same::value>::type +encodeOneAt(char *ptr, T v) +{ + typename QIntegerForSizeof::Unsigned u; + memcpy(&u, &v, sizeof(u)); + qToBigEndian(u, ptr); +} + +static char *encodeAt(char *ptr) +{ + return ptr; +} + +template +static char *encodeAt(char *ptr, Arg0 a0, Args... a) +{ + encodeOneAt(ptr, a0); + return encodeAt(ptr + lengthOf(a0), a...); +} + +} // namespace SimpleEncodetoCbor + +template +static QByteArray encode(Args... a) +{ + // this would be much easier with C++17 fold expressions... + using namespace SimpleEncodeToCbor; + using namespace std; + size_t lengths[] = { lengthOf(a)... }; + size_t total = accumulate(begin(lengths), end(lengths), size_t(0), plus{}); + QByteArray result(QByteArray::size_type(total), Qt::Uninitialized); + char *ptr = result.data(); + encodeAt(ptr, a...); + return result; +} + // Get the validation data from TinyCBOR (see src/3rdparty/tinycbor/tests/parser/data.cpp) #include "data.cpp" @@ -1882,6 +1946,51 @@ void tst_QCborValue::validation() } } +void tst_QCborValue::extendedTypeValidation_data() +{ + QTest::addColumn("data"); + QTest::addColumn("expected"); + + // QDateTime currently stores time in milliseconds, so make sure + // we don't overflow + { + quint64 limit = std::numeric_limits::max() / 1000; + QTest::newRow("UnixTime_t:integer-overflow-positive") + << encode(0xc1, 0x1b, limit + 1) + << QCborValue(QCborKnownTags::UnixTime_t, qint64(limit) + 1); + QTest::newRow("UnixTime_t:integer-overflow-negative") + << encode(0xc1, 0x3b, limit) + << QCborValue(QCborKnownTags::UnixTime_t, -qint64(limit) - 1); + + double fplimit = std::numeric_limits::min() / (-1000.); // 2^63 ms + QTest::newRow("UnixTime_t:fp-overflow-positive") + << encode(0xc1, 0xfb, fplimit) + << QCborValue(QCborKnownTags::UnixTime_t, fplimit); + QTest::newRow("UnixTime_t:fp-overflow-negative") + << encode(0xc1, 0xfb, -fplimit) + << QCborValue(QCborKnownTags::UnixTime_t, -fplimit); + } +} + +void tst_QCborValue::extendedTypeValidation() +{ + QFETCH(QByteArray, data); + QFETCH(QCborValue, expected); + + QCborParserError error; + QCborValue decoded = QCborValue::fromCbor(data, &error); + QVERIFY2(error.error == QCborError(), qPrintable(error.errorString())); + QCOMPARE(error.offset, data.size()); + QCOMPARE(decoded, expected); + + QByteArray encoded = decoded.toCbor(); +#if QT_VERSION < QT_VERSION_CHECK(6,0,0) + // behavior change, see qdatetime.cpp:fromIsoTimeString + QEXPECT_FAIL("DateTime:Null-at-19", "QDateTime parsing fixed, but only in 6.0", Abort); +#endif + QCOMPARE(encoded, data); +} + void tst_QCborValue::hugeDeviceValidation_data() { addValidationHugeDevice(MaxByteArraySize + 1, MaxStringSize + 1); From 2a53017df488b3cedbfbf5a56bf105b9fc5392fa Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Wed, 15 Apr 2020 14:00:05 -0300 Subject: [PATCH 4/6] QCborValue: add an extra check against producing invalid ISO dates By QCborValue design, we store the textual representation in ISO format, equivalent of CBOR tag 0, which isn't allowed to have negative years or beyond year 10000. Change-Id: Ibdc95e9af7bd456a94ecfffd16060ccff359c296 Reviewed-by: Ulf Hermann --- src/corelib/serialization/qcborvalue.cpp | 10 ++++++---- .../serialization/qcborvalue/tst_qcborvalue.cpp | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/corelib/serialization/qcborvalue.cpp b/src/corelib/serialization/qcborvalue.cpp index f5cccf1be1..30bfa367ed 100644 --- a/src/corelib/serialization/qcborvalue.cpp +++ b/src/corelib/serialization/qcborvalue.cpp @@ -810,10 +810,12 @@ static QCborValue::Type convertToExtendedType(QCborContainerPrivate *d) } if (dt.isValid()) { QByteArray text = dt.toString(Qt::ISODateWithMs).toLatin1(); - replaceByteData(text, text.size(), Element::StringIsAscii); - e.type = QCborValue::String; - d->elements[0].value = qint64(QCborKnownTags::DateTimeString); - return QCborValue::DateTime; + if (!text.isEmpty()) { + replaceByteData(text, text.size(), Element::StringIsAscii); + e.type = QCborValue::String; + d->elements[0].value = qint64(QCborKnownTags::DateTimeString); + return QCborValue::DateTime; + } } break; } diff --git a/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp b/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp index 0b3046fbdc..d035ca4ee5 100644 --- a/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp +++ b/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp @@ -1970,6 +1970,21 @@ void tst_QCborValue::extendedTypeValidation_data() << encode(0xc1, 0xfb, -fplimit) << QCborValue(QCborKnownTags::UnixTime_t, -fplimit); } + + // But in fact, QCborValue stores date/times as their ISO textual + // representation, which means it can't represent dates before year 1 or + // after year 9999. + { + QDateTime dt(QDate(-1, 1, 1), QTime(0, 0), Qt::UTC); + QTest::newRow("UnixTime_t:negative-year") + << encode(0xc1, 0x3b, quint64(-dt.toSecsSinceEpoch()) - 1) + << QCborValue(QCborKnownTags::UnixTime_t, dt.toSecsSinceEpoch()); + + dt.setDate(QDate(10000, 1, 1)); + QTest::newRow("UnixTime_t:year10k") + << encode(0xc1, 0x1b, quint64(dt.toSecsSinceEpoch())) + << QCborValue(QCborKnownTags::UnixTime_t, dt.toSecsSinceEpoch()); + } } void tst_QCborValue::extendedTypeValidation() From 8366c06d46f63b12b88abaddb0ff7a6b6dda75a4 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Wed, 15 Apr 2020 15:13:14 -0300 Subject: [PATCH 5/6] QCborValue: add tests of parsing invalid ISO date-time strings We rely on QDateTime::fromString being proper, so this is not extensive testing. Change-Id: Ibdc95e9af7bd456a94ecfffd160610cdac5d62e1 Reviewed-by: Thiago Macieira Reviewed-by: Edward Welbourne --- .../qcborvalue/tst_qcborvalue.cpp | 76 ++++++++++++++++++- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp b/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp index d035ca4ee5..e8acd29bbc 100644 --- a/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp +++ b/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp @@ -126,20 +126,36 @@ inline size_t lengthOf(int) return 1; // encode as byte } +template inline size_t lengthOf(const char (&)[N]) +{ + return N - 1; +} + + +inline size_t lengthOf(const char *str) +{ + return strlen(str); +} + template inline size_t lengthOf(T) { return sizeof(T); } -static void encodeOneAt(char *ptr, int v) +static void encodeOneAt(char *ptr, int v, size_t) { // encode as byte *ptr = char(v); } +static void encodeOneAt(char *ptr, const char *v, size_t size) +{ + memcpy(ptr, v, size); +} + template static typename std::enable_if::value>::type -encodeOneAt(char *ptr, T v) +encodeOneAt(char *ptr, T v, size_t) { qToBigEndian(v, ptr); } @@ -147,7 +163,7 @@ encodeOneAt(char *ptr, T v) template static typename std::enable_if::value || std::is_same::value>::type -encodeOneAt(char *ptr, T v) +encodeOneAt(char *ptr, T v, size_t) { typename QIntegerForSizeof::Unsigned u; memcpy(&u, &v, sizeof(u)); @@ -162,7 +178,7 @@ static char *encodeAt(char *ptr) template static char *encodeAt(char *ptr, Arg0 a0, Args... a) { - encodeOneAt(ptr, a0); + encodeOneAt(ptr, a0, lengthOf(a0)); return encodeAt(ptr + lengthOf(a0), a...); } @@ -1778,6 +1794,15 @@ void tst_QCborValue::fromCbor_data() QTest::newRow("DateTime:NoMilli") << QCborValue(QDateTime::fromSecsSinceEpoch(1515565477, Qt::UTC)) << raw("\xc0\x74" "2018-01-10T06:24:37Z"); + // date-only is only permitted local time + QTest::newRow("DateTime:NoTime:Local") << QCborValue(QDateTime(QDate(2020, 4, 15), QTime(0, 0), Qt::LocalTime)) + << raw("\xc0\x6a" "2020-04-15"); + QTest::newRow("DateTime:24:00:00") << QCborValue(QDateTime(QDate(2020, 4, 16), QTime(0, 0), Qt::UTC)) + << raw("\xc0\x74" "2020-04-15T24:00:00Z"); + QTest::newRow("DateTime:+00:00") << QCborValue(QDateTime::fromMSecsSinceEpoch(1515565477125, Qt::UTC)) + << raw("\xc0\x78\x1d" "2018-01-10T06:24:37.125+00:00"); + QTest::newRow("DateTime:+01:00") << QCborValue(QDateTime::fromMSecsSinceEpoch(1515565477125, Qt::OffsetFromUTC, 60*60)) + << raw("\xc0\x78\x1d" "2018-01-10T07:24:37.125+01:00"); QTest::newRow("UnixTime_t:Integer") << QCborValue(QDateTime::fromSecsSinceEpoch(1515565477, Qt::UTC)) << raw("\xc1\x1a\x5a\x55\xb1\xa5"); QTest::newRow("UnixTime_t:Double") << QCborValue(QDateTime::fromMSecsSinceEpoch(1515565477125, Qt::UTC)) @@ -1985,6 +2010,49 @@ void tst_QCborValue::extendedTypeValidation_data() << encode(0xc1, 0x1b, quint64(dt.toSecsSinceEpoch())) << QCborValue(QCborKnownTags::UnixTime_t, dt.toSecsSinceEpoch()); } + + // Invalid ISO date/time strings + { + auto add = [](const char *tag, const char *str) { + QByteArray raw; + if (strlen(str) < 0x18) + raw = encode(0xc0, 0x60 + int(strlen(str)), str); + else + raw = encode(0xc0, 0x78, quint8(strlen(str)), str); + QTest::addRow("DateTime:%s", tag) + << raw << QCborValue(QCborKnownTags::DateTimeString, QString(str)); + }; + // tst_QDateTime::fromStringDateFormat has more tests + add("junk", "jjj"); + add("zoned-date-only", "2020-04-15Z"); + add("month-13", "2020-13-01T00:00:00Z"); + add("negative-month", "2020--1-01T00:00:00Z"); + add("jan-32", "2020-01-32T00:00:00Z"); + add("apr-31", "2020-04-31T00:00:00Z"); + add("feb-30", "2020-02-30T00:00:00Z"); + add("feb-29-nonleap", "2021-02-29T00:00:00Z"); + add("negative-day", "2020-01--1T00:00:00Z"); + add("bad-separator", "2020-04-15j13:30:59Z"); + add("hour-25", "2020-04-15T25:00:00Z"); + add("negative-hour", "2020-04-15T-1:00:00Z"); + add("minute-60", "2020-04-15T23:60:00Z"); + add("negative-minute", "2020-04-15T23:-1:00Z"); + add("second-60", "2020-04-15T23:59:60Z"); // not a leap second + add("negative-second", "2020-04-15T23:59:-1Z"); + add("negative-milli", "2020-04-15T23.59:59.-1Z"); + + // walking null + char dt[] = "2020-04-15T17:33:32.125Z"; + quint8 len = strlen(dt); + for (int i = 0; i < int(len); ++i) { + char c = '\0'; + qSwap(c, dt[i]); + QTest::addRow("DateTime:Null-at-%d", i) + << encode(0xc0, 0x78, len) + QByteArray(dt, len) + << QCborValue(QCborKnownTags::DateTimeString, QLatin1String(dt, len)); + qSwap(c, dt[i]); + } + } } void tst_QCborValue::extendedTypeValidation() From 821e71fded090d815b5cd396057ac9823874fe1f Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Wed, 15 Apr 2020 15:16:06 -0300 Subject: [PATCH 6/6] QCborValue: check parsing of invalid URL QUrl will reject invalid URLs for us, so we don't get normalization. The original junk should be retrievable, of course. Change-Id: Ibdc95e9af7bd456a94ecfffd160610f5b2c8e1a2 Reviewed-by: Ulf Hermann Reviewed-by: Edward Welbourne --- src/corelib/serialization/qcborvalue.cpp | 8 +++++--- .../corelib/serialization/qcborvalue/tst_qcborvalue.cpp | 8 ++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/corelib/serialization/qcborvalue.cpp b/src/corelib/serialization/qcborvalue.cpp index 30bfa367ed..3bca15d562 100644 --- a/src/corelib/serialization/qcborvalue.cpp +++ b/src/corelib/serialization/qcborvalue.cpp @@ -827,9 +827,11 @@ static QCborValue::Type convertToExtendedType(QCborContainerPrivate *d) // normalize to a short (decoded) form, so as to save space QUrl url(e.flags & Element::StringIsUtf16 ? b->asQStringRaw() : - b->toUtf8String()); - QByteArray encoded = url.toString(QUrl::DecodeReserved).toUtf8(); - replaceByteData(encoded, encoded.size(), {}); + b->toUtf8String(), QUrl::StrictMode); + if (url.isValid()) { + QByteArray encoded = url.toString(QUrl::DecodeReserved).toUtf8(); + replaceByteData(encoded, encoded.size(), {}); + } } return QCborValue::Url; } diff --git a/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp b/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp index e8acd29bbc..9c1341e252 100644 --- a/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp +++ b/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp @@ -2053,6 +2053,14 @@ void tst_QCborValue::extendedTypeValidation_data() qSwap(c, dt[i]); } } + + // Improperly-encoded URLs + { + const char badurl[] = "%zz"; + QTest::newRow("Url:Invalid") + << encode(0xd8, int(QCborKnownTags::Url), 0x60 + int(strlen(badurl)), badurl) + << QCborValue(QCborKnownTags::Url, QLatin1String(badurl)); + } } void tst_QCborValue::extendedTypeValidation()