From d2ae838a7c6a49eb826ddf8de115306d11dd4d30 Mon Sep 17 00:00:00 2001 From: Eirik Aavitsland Date: Tue, 8 Oct 2019 13:47:28 +0200 Subject: [PATCH 01/47] Item views: fix regression causing unexpected widget overlapping A certain geometry adjustment was (practically) introduced in Qt 5.11, and caused very surprising behavior, where item widgets will often overwrite neighbouring cells. This has resulted in a number of bug reports. Since the adjustment has such serious side effects, and does not seem to be relevant any longer for the issue for which it was intended, remove it here. More details: From early Qt 4 times, QStyledItemDelegate would do some automatic expansion of the geometry of editor widgets - but only if the layout was RightToLeft. Hence, the effect of it was rarely seen. QTBUG-37433 did, for Qt 5.10, and complained about it. However, the resulting code change did not remove the adjustment, but instead extended it to apply to the normal LeftToRight layout also. Hence, more users experienced it, and reported it as a regression. Also, now in Qt 5.13, it seems Qt has changed in other ways, and the geometry adjustment no longer seems to help (or indeed make any difference to) the original case in QTBUG-37433. Fixes: QTBUG-78495 Fixes: QTBUG-76011 Fixes: QTBUG-68476 Change-Id: I4a4e873969eb1d89843f98fc63d90371207515d1 Reviewed-by: Paul Olav Tvete --- src/widgets/itemviews/qstyleditemdelegate.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/widgets/itemviews/qstyleditemdelegate.cpp b/src/widgets/itemviews/qstyleditemdelegate.cpp index 22067851cb..702e290da3 100644 --- a/src/widgets/itemviews/qstyleditemdelegate.cpp +++ b/src/widgets/itemviews/qstyleditemdelegate.cpp @@ -514,15 +514,6 @@ void QStyledItemDelegate::updateEditorGeometry(QWidget *editor, QStyle *style = widget ? widget->style() : QApplication::style(); QRect geom = style->subElementRect(QStyle::SE_ItemViewItemText, &opt, widget); - const int delta = qSmartMinSize(editor).width() - geom.width(); - if (delta > 0) { - //we need to widen the geometry - if (editor->layoutDirection() == Qt::RightToLeft) - geom.adjust(-delta, 0, 0, 0); - else - geom.adjust(0, 0, delta, 0); - } - editor->setGeometry(geom); } From 9845c06d1f8c3b30697cdf96b569849ee2372f47 Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Thu, 10 Oct 2019 10:41:33 +0200 Subject: [PATCH 02/47] DTLS auto-test(s) - fix a buggy logic with pending datagrams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: QTBUG-79128 Change-Id: Ifebd5b056541b7732b15b5cf063ad22ab754a64c Reviewed-by: Mårten Nordheim --- tests/auto/network/ssl/qdtls/tst_qdtls.cpp | 2 +- tests/auto/network/ssl/qdtlscookie/tst_qdtlscookie.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/auto/network/ssl/qdtls/tst_qdtls.cpp b/tests/auto/network/ssl/qdtls/tst_qdtls.cpp index 6a94eee389..4dfdf14e5b 100644 --- a/tests/auto/network/ssl/qdtls/tst_qdtls.cpp +++ b/tests/auto/network/ssl/qdtls/tst_qdtls.cpp @@ -1131,7 +1131,7 @@ void tst_QDtls::handshakeReadyRead() QUdpSocket *socket = qobject_cast(sender()); Q_ASSERT(socket); - if (!socket->pendingDatagramSize()) + if (socket->pendingDatagramSize() <= 0) return; const bool isServer = socket == &serverSocket; diff --git a/tests/auto/network/ssl/qdtlscookie/tst_qdtlscookie.cpp b/tests/auto/network/ssl/qdtlscookie/tst_qdtlscookie.cpp index c90e9cb2c8..a273ceaa17 100644 --- a/tests/auto/network/ssl/qdtlscookie/tst_qdtlscookie.cpp +++ b/tests/auto/network/ssl/qdtlscookie/tst_qdtlscookie.cpp @@ -352,7 +352,7 @@ void tst_QDtlsCookie::receiveMessage(QUdpSocket *socket, QByteArray *message, { Q_ASSERT(socket && message); - if (!socket->pendingDatagramSize()) + if (socket->pendingDatagramSize() <= 0) testLoop.enterLoopMSecs(handshakeTimeoutMS); QVERIFY(!testLoop.timeout()); @@ -377,7 +377,7 @@ void tst_QDtlsCookie::serverReadyRead() { Q_ASSERT(clientsToWait); - if (!serverSocket.pendingDatagramSize()) + if (serverSocket.pendingDatagramSize() <= 0) return; QByteArray hello; @@ -410,7 +410,7 @@ void tst_QDtlsCookie::clientReadyRead() QUdpSocket *clientSocket = qobject_cast(sender()); Q_ASSERT(clientSocket); - if (!clientSocket->pendingDatagramSize()) + if (clientSocket->pendingDatagramSize() <= 0) return; QDtls *handshake = nullptr; From 3dbc7596a3b7aca37e80f76288d3ab761ddd247a Mon Sep 17 00:00:00 2001 From: Volker Hilsheimer Date: Tue, 8 Oct 2019 10:56:44 +0200 Subject: [PATCH 03/47] Drag'n'Drop: fix crash when widgets are destroyed during event handling Widgets might be destroyed when handling a dragMoveEvent, in which case the following code will operate on dangling pointers or null pointers. Use a QPointer to watch for the original event receiver to disappear, and add the necessary checks for the objects we deliver events to being null. Change-Id: I4ca2f182540ae21113f4bea4e5c569e983cc58bf Fixes: QTBUG-78907 Reviewed-by: Gatis Paeglis --- src/widgets/kernel/qwidgetwindow.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/widgets/kernel/qwidgetwindow.cpp b/src/widgets/kernel/qwidgetwindow.cpp index fbc71cd0ea..aad505ed29 100644 --- a/src/widgets/kernel/qwidgetwindow.cpp +++ b/src/widgets/kernel/qwidgetwindow.cpp @@ -885,7 +885,7 @@ void QWidgetWindow::handleDragEnterEvent(QDragEnterEvent *event, QWidget *widget void QWidgetWindow::handleDragMoveEvent(QDragMoveEvent *event) { - auto *widget = findDnDTarget(m_widget, event->pos()); + QPointer widget = findDnDTarget(m_widget, event->pos()); if (!widget) { event->ignore(); if (m_dragTarget) { // Send DragLeave to previous @@ -908,14 +908,18 @@ void QWidgetWindow::handleDragMoveEvent(QDragMoveEvent *event) QGuiApplication::forwardEvent(m_dragTarget, &leaveEvent, event); m_dragTarget = nullptr; } - // Send DragEnter to new widget. - handleDragEnterEvent(static_cast(event), widget); - // Handling 'DragEnter' should suffice for the application. - translated.setDropAction(event->dropAction()); - translated.setAccepted(event->isAccepted()); - // The drag enter event is always immediately followed by a drag move event, - // see QDragEnterEvent documentation. - QGuiApplication::forwardEvent(m_dragTarget, &translated, event); + // widget might have been deleted when handling the leaveEvent + if (widget) { + // Send DragEnter to new widget. + handleDragEnterEvent(static_cast(event), widget); + // Handling 'DragEnter' should suffice for the application. + translated.setDropAction(event->dropAction()); + translated.setAccepted(event->isAccepted()); + // The drag enter event is always immediately followed by a drag move event, + // see QDragEnterEvent documentation. + if (m_dragTarget) + QGuiApplication::forwardEvent(m_dragTarget, &translated, event); + } } event->setAccepted(translated.isAccepted()); event->setDropAction(translated.dropAction()); From 472f5331ca8091b191944650d043a288dac7c3e8 Mon Sep 17 00:00:00 2001 From: Joerg Bornemann Date: Mon, 7 Oct 2019 09:15:37 +0200 Subject: [PATCH 04/47] Doc: Fix QMAKE_EXTRA_TARGETS snippet description The snippet uses the Unix touch command, not qmake's touch function. Change-Id: I71d1460447249b8941ce4bdbb494bb419e13b119 Reviewed-by: Oliver Wolff Reviewed-by: Edward Welbourne Reviewed-by: Paul Wicking --- qmake/doc/src/qmake-manual.qdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qmake/doc/src/qmake-manual.qdoc b/qmake/doc/src/qmake-manual.qdoc index da3aa6b248..ed7fe60fdc 100644 --- a/qmake/doc/src/qmake-manual.qdoc +++ b/qmake/doc/src/qmake-manual.qdoc @@ -4690,7 +4690,7 @@ The definitions above define a qmake target called \c mytarget, containing a Makefile target called \c{.buildfile} which in turn is generated with the - \l{touchfunction}{touch()} function. Finally, the + \c touch command. Finally, the \c{.depends} member specifies that \c mytarget depends on \c mytarget2, another target that is defined afterwards. \c mytarget2 is a dummy target. It is only defined to echo some text to the console. From 014d7ac65417ed9b5ffb85cca24d16564ff5005a Mon Sep 17 00:00:00 2001 From: Milian Wolff Date: Thu, 5 Sep 2019 09:58:30 +0200 Subject: [PATCH 05/47] Q{Shared,Weak}Pointer: Reduce overload sets in implicit conversions Only allow implicit conversions when the types involved are compatible. That means, only allow construction and copy assignment when the type X* is convertible to type T*. This is done using SFINAE and the std::is_convertible type trait, which makes the previous QSHAREDPOINTER_VERIFY_AUTO_CAST obsolete. This patch fixes compilation when a function is overloaded with Q{Shared,Weak}Pointer of different, incompatible types. Previously, this resulted in a compilation error due to an ambiguous overload. Change-Id: I069d22f3582e69842f14284d4f27827326597ca2 Fixes: QTBUG-75222 Reviewed-by: Thiago Macieira --- src/corelib/tools/qsharedpointer_impl.h | 51 +++++++----------- .../qsharedpointer/tst_qsharedpointer.cpp | 54 +++++++++++++++++++ 2 files changed, 74 insertions(+), 31 deletions(-) diff --git a/src/corelib/tools/qsharedpointer_impl.h b/src/corelib/tools/qsharedpointer_impl.h index bccf8c5740..f2158436fd 100644 --- a/src/corelib/tools/qsharedpointer_impl.h +++ b/src/corelib/tools/qsharedpointer_impl.h @@ -69,21 +69,6 @@ QT_END_NAMESPACE QT_BEGIN_NAMESPACE - -// Macro QSHAREDPOINTER_VERIFY_AUTO_CAST -// generates a compiler error if the following construct isn't valid: -// T *ptr1; -// X *ptr2 = ptr1; -// -#ifdef QT_NO_DEBUG -# define QSHAREDPOINTER_VERIFY_AUTO_CAST(T, X) qt_noop() -#else - -template inline void qt_sharedpointer_cast_check(T *) { } -# define QSHAREDPOINTER_VERIFY_AUTO_CAST(T, X) \ - qt_sharedpointer_cast_check(static_cast(0)) -#endif - // // forward declarations // @@ -293,6 +278,9 @@ template class QSharedPointer { typedef T *QSharedPointer:: *RestrictedBool; typedef QtSharedPointer::ExternalRefCountData Data; + template + using IfCompatible = typename std::enable_if::value, bool>::type; + public: typedef T Type; typedef T element_type; @@ -316,11 +304,11 @@ public: Q_DECL_CONSTEXPR QSharedPointer(std::nullptr_t) Q_DECL_NOTHROW : value(nullptr), d(nullptr) { } - template + template = true> inline explicit QSharedPointer(X *ptr) : value(ptr) // noexcept { internalConstruct(ptr, QtSharedPointer::NormalDeleter()); } - template + template = true> inline QSharedPointer(X *ptr, Deleter deleter) : value(ptr) // throws { internalConstruct(ptr, deleter); } @@ -349,7 +337,7 @@ public: return *this; } - template + template = true> QSharedPointer(QSharedPointer &&other) Q_DECL_NOTHROW : value(other.value), d(other.d) { @@ -357,7 +345,7 @@ public: other.value = nullptr; } - template + template = true> QSharedPointer &operator=(QSharedPointer &&other) Q_DECL_NOTHROW { QSharedPointer moved(std::move(other)); @@ -367,11 +355,11 @@ public: #endif - template + template = true> QSharedPointer(const QSharedPointer &other) Q_DECL_NOTHROW : value(other.value), d(other.d) { if (d) ref(); } - template + template = true> inline QSharedPointer &operator=(const QSharedPointer &other) { QSharedPointer copy(other); @@ -379,11 +367,11 @@ public: return *this; } - template + template = true> inline QSharedPointer(const QWeakPointer &other) : value(nullptr), d(nullptr) { *this = other; } - template + template = true> inline QSharedPointer &operator=(const QWeakPointer &other) { internalSet(other.d, other.value); return *this; } @@ -553,6 +541,8 @@ class QWeakPointer { typedef T *QWeakPointer:: *RestrictedBool; typedef QtSharedPointer::ExternalRefCountData Data; + template + using IfCompatible = typename std::enable_if::value, bool>::type; public: typedef T element_type; @@ -574,14 +564,14 @@ public: #ifndef QT_NO_QOBJECT // special constructor that is enabled only if X derives from QObject #if QT_DEPRECATED_SINCE(5, 0) - template + template = true> QT_DEPRECATED inline QWeakPointer(X *ptr) : d(ptr ? Data::getAndRef(ptr) : nullptr), value(ptr) { } #endif #endif #if QT_DEPRECATED_SINCE(5, 0) - template + template = true> QT_DEPRECATED inline QWeakPointer &operator=(X *ptr) { return *this = QWeakPointer(ptr); } #endif @@ -619,11 +609,11 @@ public: return *this; } - template + template = true> inline QWeakPointer(const QWeakPointer &o) : d(nullptr), value(nullptr) { *this = o; } - template + template = true> inline QWeakPointer &operator=(const QWeakPointer &o) { // conversion between X and T could require access to the virtual table @@ -640,14 +630,13 @@ public: bool operator!=(const QWeakPointer &o) const Q_DECL_NOTHROW { return !(*this == o); } - template + template = true> inline QWeakPointer(const QSharedPointer &o) : d(nullptr), value(nullptr) { *this = o; } - template + template = true> inline QWeakPointer &operator=(const QSharedPointer &o) { - QSHAREDPOINTER_VERIFY_AUTO_CAST(T, X); // if you get an error in this line, the cast is invalid internalSet(o.d, o.data()); return *this; } @@ -684,7 +673,7 @@ public: { return *this = QWeakPointer(ptr, true); } #ifndef QT_NO_QOBJECT - template + template = true> inline QWeakPointer(X *ptr, bool) : d(ptr ? Data::getAndRef(ptr) : nullptr), value(ptr) { } #endif diff --git a/tests/auto/corelib/tools/qsharedpointer/tst_qsharedpointer.cpp b/tests/auto/corelib/tools/qsharedpointer/tst_qsharedpointer.cpp index e97848fb1c..0fe24ed5cb 100644 --- a/tests/auto/corelib/tools/qsharedpointer/tst_qsharedpointer.cpp +++ b/tests/auto/corelib/tools/qsharedpointer/tst_qsharedpointer.cpp @@ -40,6 +40,7 @@ #include "nontracked.h" #include "wrapper.h" +#include #include #include #include @@ -105,12 +106,15 @@ private slots: void sharedFromThis(); void constructorThrow(); + void overloads(); void threadStressTest_data(); void threadStressTest(); void validConstructs(); void invalidConstructs_data(); void invalidConstructs(); + + // let invalidConstructs be the last test, because it's the slowest; // add new tests above this block public slots: @@ -2250,6 +2254,11 @@ void tst_QSharedPointer::invalidConstructs_data() << &QTest::QExternalTest::tryCompileFail << "QSharedPointer ptr(new Data, [](int *) {});\n"; #endif + + QTest::newRow("incompatible-overload") + << &QTest::QExternalTest::tryCompileFail + << "void foo(QSharedPointer) {}\n" + "void bar() { foo(QSharedPointer()); }\n"; } void tst_QSharedPointer::invalidConstructs() @@ -2748,5 +2757,50 @@ void tst_QSharedPointer::reentrancyWhileDestructing() ReentrancyWhileDestructing::A obj; } +namespace { +struct Base1 {}; +struct Base2 {}; + +struct Child1 : Base1 {}; +struct Child2 : Base2 {}; + +template class SmartPtr> +struct Overloaded +{ + std::array call(const SmartPtr &) + { + return {}; + } + std::array call(const SmartPtr &) + { + return {}; + } + static const Q_CONSTEXPR uint base1Called = sizeof(std::array); + static const Q_CONSTEXPR uint base2Called = sizeof(std::array); + + void test() + { +#define QVERIFY_CALLS(expr, base) Q_STATIC_ASSERT(sizeof(call(expr)) == base##Called) + QVERIFY_CALLS(SmartPtr{}, base1); + QVERIFY_CALLS(SmartPtr{}, base2); + QVERIFY_CALLS(SmartPtr{}, base1); + QVERIFY_CALLS(SmartPtr{}, base2); + QVERIFY_CALLS(SmartPtr{}, base1); + QVERIFY_CALLS(SmartPtr{}, base2); + QVERIFY_CALLS(SmartPtr{}, base1); + QVERIFY_CALLS(SmartPtr{}, base2); +#undef QVERIFY_CALLS + } +}; +} + +void tst_QSharedPointer::overloads() +{ + Overloaded sharedOverloaded; + sharedOverloaded.test(); + Overloaded weakOverloaded; + weakOverloaded.test(); +} + QTEST_MAIN(tst_QSharedPointer) #include "tst_qsharedpointer.moc" From 31012df705c37503be00f0ddc2c323e73fd28067 Mon Sep 17 00:00:00 2001 From: Andre de la Rocha Date: Mon, 2 Sep 2019 16:46:07 +0200 Subject: [PATCH 06/47] Fix QGraphicsScene::update() performance A previous fix has caused a performance degradation while adding a check for avoiding adding duplicated rectangles to the update list. This patch fixes it by using a std::set instead of a QList, avoiding duplication while using an O(log N) operation, instead of the O(N) used before. Fixes: QTBUG-77952 Change-Id: Ifa9fbf110e0bad60ee02a42d91281981fd98ceab Reviewed-by: Volker Hilsheimer --- src/widgets/graphicsview/qgraphicsscene.cpp | 13 ++++++++++--- src/widgets/graphicsview/qgraphicsscene_p.h | 17 ++++++++++++++++- .../qgraphicsscene/tst_qgraphicsscene.cpp | 2 -- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/widgets/graphicsview/qgraphicsscene.cpp b/src/widgets/graphicsview/qgraphicsscene.cpp index 5238ea2f5c..03717d5d1a 100644 --- a/src/widgets/graphicsview/qgraphicsscene.cpp +++ b/src/widgets/graphicsview/qgraphicsscene.cpp @@ -386,7 +386,15 @@ void QGraphicsScenePrivate::_q_emitUpdated() // Notify the changes to anybody interested. QList oldUpdatedRects; - oldUpdatedRects = updateAll ? (QList() << q->sceneRect()) : updatedRects; + if (updateAll) { + oldUpdatedRects << q->sceneRect(); + } else { + // Switch to a ranged constructor in Qt 6... + oldUpdatedRects.reserve(int(updatedRects.size())); + std::copy(updatedRects.cbegin(), updatedRects.cend(), + std::back_inserter(oldUpdatedRects)); + } + updateAll = false; updatedRects.clear(); emit q->changed(oldUpdatedRects); @@ -3219,8 +3227,7 @@ void QGraphicsScene::update(const QRectF &rect) view->d_func()->updateRectF(rect); } } else { - if (!d->updatedRects.contains(rect)) - d->updatedRects << rect; + d->updatedRects.insert(rect); } } diff --git a/src/widgets/graphicsview/qgraphicsscene_p.h b/src/widgets/graphicsview/qgraphicsscene_p.h index a2d13436fc..14bafe6678 100644 --- a/src/widgets/graphicsview/qgraphicsscene_p.h +++ b/src/widgets/graphicsview/qgraphicsscene_p.h @@ -69,6 +69,9 @@ #include #include +#include +#include + QT_REQUIRE_CONFIG(graphicsview); QT_BEGIN_NAMESPACE @@ -122,7 +125,19 @@ public: QRectF growingItemsBoundingRect; void _q_emitUpdated(); - QList updatedRects; + + struct UpdatedRectsCmp + { + bool operator() (const QRectF &a, const QRectF &b) const noexcept + { + return std::make_tuple(a.y(), a.x(), a.height(), a.width()) + < std::make_tuple(b.y(), b.x(), b.height(), b.width()); + } + }; + + // std::set was used here instead of std::unordered_set due to requiring only a comparator and + // showing equivalent performance in empirical measurements within the ranges of interest... + std::set updatedRects; QPainterPath selectionArea; int selectionChanging; diff --git a/tests/auto/widgets/graphicsview/qgraphicsscene/tst_qgraphicsscene.cpp b/tests/auto/widgets/graphicsview/qgraphicsscene/tst_qgraphicsscene.cpp index 46f1d5df5c..4f8741a5aa 100644 --- a/tests/auto/widgets/graphicsview/qgraphicsscene/tst_qgraphicsscene.cpp +++ b/tests/auto/widgets/graphicsview/qgraphicsscene/tst_qgraphicsscene.cpp @@ -3685,8 +3685,6 @@ void tst_QGraphicsScene::changedSignal() qApp->processEvents(); QCOMPARE(cl.changes.size(), 2); QCOMPARE(cl.changes.at(1).size(), 2); - QCOMPARE(cl.changes.at(1).first(), QRectF(0, 0, 10, 10)); - QCOMPARE(cl.changes.at(1).last(), QRectF(20, 0, 10, 10)); QCOMPARE(scene.sceneRect(), QRectF(0, 0, 30, 10)); From 365f70be6ed53bf8b9ba7c5a78d5683e4ff8fe79 Mon Sep 17 00:00:00 2001 From: Nico Vertriest Date: Thu, 10 Oct 2019 14:45:21 +0200 Subject: [PATCH 07/47] Doc: Correct snippet about customizing QMenuBar Task-number: QTBUG-79129 Change-Id: I1f8da3b429ab8543ca1f0b7079d0f50bbeea8eb5 Reviewed-by: Paul Wicking Reviewed-by: Volker Hilsheimer --- src/widgets/doc/snippets/code/doc_src_stylesheet.qdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/doc/snippets/code/doc_src_stylesheet.qdoc b/src/widgets/doc/snippets/code/doc_src_stylesheet.qdoc index 098eaf4717..b0d042566f 100644 --- a/src/widgets/doc/snippets/code/doc_src_stylesheet.qdoc +++ b/src/widgets/doc/snippets/code/doc_src_stylesheet.qdoc @@ -1107,10 +1107,10 @@ QMenu::indicator:exclusive:checked:selected { QMenuBar { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 lightgray, stop:1 darkgray); + spacing: 3px; /* spacing between menu bar items */ } QMenuBar::item { - spacing: 3px; /* spacing between menu bar items */ padding: 1px 4px; background: transparent; border-radius: 4px; From 2e12825b0b4457d709d6d467c84f30ce35336ff3 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Wed, 2 Oct 2019 13:22:39 +0200 Subject: [PATCH 08/47] Doc: Describe initTestCase_data() function and QFETCH_GLOBAL macro Fixes: QTBUG-24342 Change-Id: I8f8f3726c5d31e34af9bfe054572c08fc07e01e0 Reviewed-by: Edward Welbourne --- .../snippets/code/src_qtestlib_qtestcase.cpp | 30 +++++++++++- src/testlib/doc/src/qttestlib-manual.qdoc | 49 ++++++++++++++++--- src/testlib/qtestcase.qdoc | 33 +++++++++++-- 3 files changed, 100 insertions(+), 12 deletions(-) diff --git a/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp b/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp index 202f87af52..5f71828595 100644 --- a/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp +++ b/src/testlib/doc/snippets/code/src_qtestlib_qtestcase.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the documentation of the Qt Toolkit. @@ -296,3 +296,31 @@ QTest::keyClick(myWindow, Qt::Key_Escape, Qt::ShiftModifier, 200); } +//! [30] +void TestQLocale::initTestCase_data() +{ + QTest::addColumn("locale"); + QTest::newRow("C") << QLocale::c(); + QTest::newRow("UKish") << QLocale("en_GB"); + QTest::newRow("USAish") << QLocale(QLocale::English); +} + +void TestQLocale::roundTripInt_data() +{ + QTest::addColumn("number"); + QTest::newRow("one") << 1; + QTest::newRow("two") << 2; + QTest::newRow("ten") << 10; +} +//! [30] + +//! [31] +void TestQLocale::roundTripInt() +{ + QFETCH_GLOBAL(QLocale, locale); + QFETCH(int, number); + bool ok; + QCOMPARE(locale.toInt(locale.toString(number), &ok), number); + QVERIFY(ok); +} +//! [31] diff --git a/src/testlib/doc/src/qttestlib-manual.qdoc b/src/testlib/doc/src/qttestlib-manual.qdoc index 71b4541313..a3644c1623 100644 --- a/src/testlib/doc/src/qttestlib-manual.qdoc +++ b/src/testlib/doc/src/qttestlib-manual.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Copyright (C) 2016 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** @@ -89,12 +89,14 @@ private slot is a test function in your test. QTest::qExec() can be used to execute all test functions in the test object. - In addition, there are four private slots that are \e not treated as test functions. - They will be executed by the testing framework and can be used to initialize and - clean up either the entire test or the current test function. + In addition, you can define the following private slots that are \e not + treated as test functions. When present, they will be executed by the + testing framework and can be used to initialize and clean up either the + entire test or the current test function. \list \li \c{initTestCase()} will be called before the first test function is executed. + \li \c{initTestCase_data()} will be called to create a global test data table. \li \c{cleanupTestCase()} will be called after the last test function was executed. \li \c{init()} will be called before each test function is executed. \li \c{cleanup()} will be called after every test function. @@ -365,6 +367,34 @@ See \l {Chapter 5: Writing a Benchmark}{Writing a Benchmark} in the Qt Test Tutorial for more benchmarking examples. + + \section1 Using Global Test Data + + You can define \c{initTestCase_data()} to set up a global test data table. + Each test is run once for each row in the global test data table. When the + test function itself \l{Chapter 2: Data-driven Testing}{is data-driven}, + it is run for each local data row, for each global data row. So, if there + are \c g rows in the global data table and \c d rows in the test's own + data-table, the number of runs of this test is \c g times \c d. + + Global data is fetched from the table using the \l QFETCH_GLOBAL() macro. + + The following are typical use cases for global test data: + + \list + \li Selecting among the available database backends in QSql tests to run + every test against every database. + \li Doing all networking tests with and without SSL (HTTP versus HTTPS) + and proxying. + \li Testing a timer with a high precision clock and with a coarse one. + \li Selecting whether a parser shall read from a QByteArray or from a + QIODevice. + \endlist + + For example, to test each number provided by \c {roundTripInt_data()} with + each locale provided by \c {initTestCase_data()}: + + \snippet code/src_qtestlib_qtestcase.cpp 31 */ /*! @@ -508,10 +538,9 @@ QTest::newRow() function. Each set of data will become a separate row in the test table. - \l QTest::newRow() takes one argument: a name that will be - associated with the data set. If the test fails, the name will be - used in the test log, referencing the failed data. Then we - stream the data set into the new table row. First an arbitrary + \l QTest::newRow() takes one argument: a name that will be associated + with the data set and used in the test log to identify the data set. + Then we stream the data set into the new table row. First an arbitrary string, and then the expected result of applying the QString::toUpper() function to that string. @@ -543,6 +572,10 @@ \li HELLO \endtable + When data is streamed into the row, each datum is asserted to match + the type of the column whose value it supplies. If any assertion fails, + the test is aborted. + \section1 Rewriting the Test Function Our test function can now be rewritten: diff --git a/src/testlib/qtestcase.qdoc b/src/testlib/qtestcase.qdoc index 2af016304d..f3761cdfa8 100644 --- a/src/testlib/qtestcase.qdoc +++ b/src/testlib/qtestcase.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the documentation of the Qt Toolkit. @@ -220,8 +220,8 @@ \relates QTest The fetch macro creates a local variable named \a name with the type \a type - on the stack. \a name has to match the element name from the test's data. - If no such element exists, the test will assert. + on the stack. The \a name and \a type must match a column from the test's + data table. This is asserted and the test will abort if the assertion fails. Assuming a test has the following data: @@ -239,6 +239,33 @@ by the test framework. The test function must have a _data function. */ +/*! \macro QFETCH_GLOBAL(type, name) + + \relates QTest + + This macro fetches a variable named \a name with the type \a type from + a row in the global data table. The \a name and \a type must match a + column in the global data table. This is asserted and the test will abort + if the assertion fails. + + Assuming a test has the following data: + + \snippet code/src_qtestlib_qtestcase.cpp 30 + + The test's own data is a single number per row. In this case, + \c initTestCase_data() also supplies a locale per row. Therefore, + this test will be run with every combination of locale from the + latter and number from the former. + + \snippet code/src_qtestlib_qtestcase.cpp 31 + + The locale is read from the global data table using QFETCH_GLOBAL(), + and the number is read from the local data table using QFETCH(). + + \note This macro can only be used in test methods of a class with an + \c initTestCase_data() method. +*/ + /*! \macro QWARN(message) \relates QTest From 55427858e3f9d98dc7a75638c54a4fffde6be73a Mon Sep 17 00:00:00 2001 From: Samuli Piippo Date: Wed, 20 Mar 2019 15:39:59 +0200 Subject: [PATCH 09/47] QStandardPaths: Correct handling for XDG_RUNTIME_DIR Always try to create the runtime directory and never change the permissions of an existing directory. Conform to the XDG Base Directory Specification: "If, when attempting to write a file, the destination directory is non-existent an attempt should be made to create it with permission 0700. If the destination directory exists already the permissions should not be changed." Fixes: QTBUG-68338 Change-Id: Iaf854d69225fc46e43abae86232d749e5c247df0 Reviewed-by: David Faure Reviewed-by: Thiago Macieira --- src/corelib/io/qstandardpaths_unix.cpp | 51 ++++++++++--------- .../io/qstandardpaths/tst_qstandardpaths.cpp | 34 ++++++++----- 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/src/corelib/io/qstandardpaths_unix.cpp b/src/corelib/io/qstandardpaths_unix.cpp index 3d4a349c8c..cc656954d9 100644 --- a/src/corelib/io/qstandardpaths_unix.cpp +++ b/src/corelib/io/qstandardpaths_unix.cpp @@ -120,54 +120,59 @@ QString QStandardPaths::writableLocation(StandardLocation type) } case RuntimeLocation: { - const uint myUid = uint(geteuid()); // http://standards.freedesktop.org/basedir-spec/latest/ + const uint myUid = uint(geteuid()); + // since the current user is the owner, set both xxxUser and xxxOwner + const QFile::Permissions wantedPerms = QFile::ReadUser | QFile::WriteUser | QFile::ExeUser + | QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner; QFileInfo fileInfo; QString xdgRuntimeDir = QFile::decodeName(qgetenv("XDG_RUNTIME_DIR")); if (xdgRuntimeDir.isEmpty()) { const QString userName = QFileSystemEngine::resolveUserName(myUid); xdgRuntimeDir = QDir::tempPath() + QLatin1String("/runtime-") + userName; fileInfo.setFile(xdgRuntimeDir); - if (!fileInfo.isDir()) { - if (!QDir().mkdir(xdgRuntimeDir)) { - qWarning("QStandardPaths: error creating runtime directory %s: %s", qPrintable(xdgRuntimeDir), qPrintable(qt_error_string(errno))); - return QString(); - } - } #ifndef Q_OS_WASM qWarning("QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '%s'", qPrintable(xdgRuntimeDir)); #endif } else { fileInfo.setFile(xdgRuntimeDir); - if (!fileInfo.exists()) { - qWarning("QStandardPaths: XDG_RUNTIME_DIR points to non-existing path '%s', " - "please create it with 0700 permissions.", qPrintable(xdgRuntimeDir)); - return QString(); - } + } + if (fileInfo.exists()) { if (!fileInfo.isDir()) { qWarning("QStandardPaths: XDG_RUNTIME_DIR points to '%s' which is not a directory", qPrintable(xdgRuntimeDir)); return QString(); } + } else { + QFileSystemEntry entry(xdgRuntimeDir); + if (!QFileSystemEngine::createDirectory(entry, false)) { + if (errno != EEXIST) { + qWarning("QStandardPaths: error creating runtime directory %s: %s", + qPrintable(xdgRuntimeDir), qPrintable(qt_error_string(errno))); + return QString(); + } + } else { + QSystemError error; + if (!QFileSystemEngine::setPermissions(entry, wantedPerms, error)) { + qWarning("QStandardPaths: could not set correct permissions on runtime directory %s: %s", + qPrintable(xdgRuntimeDir), qPrintable(error.toString())); + return QString(); + } + } } // "The directory MUST be owned by the user" if (fileInfo.ownerId() != myUid) { - qWarning("QStandardPaths: wrong ownership on runtime directory %s, %d instead of %d", qPrintable(xdgRuntimeDir), - fileInfo.ownerId(), myUid); + qWarning("QStandardPaths: wrong ownership on runtime directory %s, %d instead of %d", + qPrintable(xdgRuntimeDir), fileInfo.ownerId(), myUid); return QString(); } // "and he MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700." - // since the current user is the owner, set both xxxUser and xxxOwner - const QFile::Permissions wantedPerms = QFile::ReadUser | QFile::WriteUser | QFile::ExeUser - | QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner; if (fileInfo.permissions() != wantedPerms) { - QFile file(xdgRuntimeDir); - if (!file.setPermissions(wantedPerms)) { - qWarning("QStandardPaths: could not set correct permissions on runtime directory %s: %s", - qPrintable(xdgRuntimeDir), qPrintable(file.errorString())); - return QString(); - } + qWarning("QStandardPaths: wrong permissions on runtime directory %s, %x instead of %x", + qPrintable(xdgRuntimeDir), uint(fileInfo.permissions()), uint(wantedPerms)); + return QString(); } + return xdgRuntimeDir; } default: diff --git a/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp b/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp index 1379c788d1..40023d7fea 100644 --- a/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp +++ b/tests/auto/corelib/io/qstandardpaths/tst_qstandardpaths.cpp @@ -465,16 +465,6 @@ void tst_qstandardpaths::testRuntimeDirectory() #ifdef Q_XDG_PLATFORM const QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); QVERIFY(!runtimeDir.isEmpty()); - - // Check that it can automatically fix permissions - QFile file(runtimeDir); - const QFile::Permissions wantedPerms = QFile::ReadUser | QFile::WriteUser | QFile::ExeUser; - const QFile::Permissions additionalPerms = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner; - QCOMPARE(file.permissions(), wantedPerms | additionalPerms); - QVERIFY(file.setPermissions(wantedPerms | QFile::ExeGroup)); - const QString runtimeDirAgain = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); - QCOMPARE(runtimeDirAgain, runtimeDir); - QCOMPARE(QFile(runtimeDirAgain).permissions(), wantedPerms | additionalPerms); #endif } @@ -516,11 +506,27 @@ void tst_qstandardpaths::testCustomRuntimeDirectory() const QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); QVERIFY2(runtimeDir.isEmpty(), qPrintable(runtimeDir)); - // When $XDG_RUNTIME_DIR points to a non-existing directory, QStandardPaths should warn (QTBUG-48771) - qputenv("XDG_RUNTIME_DIR", "does_not_exist"); - QTest::ignoreMessage(QtWarningMsg, "QStandardPaths: XDG_RUNTIME_DIR points to non-existing path 'does_not_exist', please create it with 0700 permissions."); + // When $XDG_RUNTIME_DIR points to a directory with wrong permissions, QStandardPaths should warn + const QByteArray wrongPermissionFileName = "wrong_permissions"; + QDir::current().mkdir(wrongPermissionFileName); + QFile wrongPermissionFile(wrongPermissionFileName); + const QFile::Permissions wantedPerms = QFile::ReadUser | QFile::WriteUser | QFile::ExeUser; + QVERIFY(wrongPermissionFile.setPermissions(wantedPerms | QFile::ExeGroup)); + + qputenv("XDG_RUNTIME_DIR", wrongPermissionFileName); + QTest::ignoreMessage(QtWarningMsg, + qPrintable(QString::fromLatin1("QStandardPaths: wrong permissions on runtime directory " + wrongPermissionFileName + ", 7710 instead of 7700"))); + const QString wrongPermissionRuntimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); + QVERIFY(wrongPermissionRuntimeDir.isEmpty()); + QDir::current().rmdir(wrongPermissionFileName); + + // When $XDG_RUNTIME_DIR points to a non-existing directory, QStandardPaths should create it first + const QByteArray nonExistingDir = "does_not_exist"; + qputenv("XDG_RUNTIME_DIR", nonExistingDir); const QString nonExistingRuntimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); - QVERIFY2(nonExistingRuntimeDir.isEmpty(), qPrintable(nonExistingRuntimeDir)); + QVERIFY2(!nonExistingRuntimeDir.compare(nonExistingDir), qPrintable(nonExistingRuntimeDir)); + QVERIFY(QDir::current().exists(nonExistingRuntimeDir)); + QDir::current().rmdir(nonExistingRuntimeDir); // When $XDG_RUNTIME_DIR points to a file, QStandardPaths should warn const QString file = QFINDTESTDATA("tst_qstandardpaths.cpp"); From 689405fb5ce5a2ff7446e1d110a7686184270f89 Mon Sep 17 00:00:00 2001 From: Edward Welbourne Date: Thu, 10 Oct 2019 17:17:29 +0200 Subject: [PATCH 10/47] Use arrays rather than assigning literals to char* (warning-fix) Integrity has a hack, to let us link to the library mmap is in, that depends on two extern "C" symbols of type char *; but assigning a string literal to a char * variable as initializer is a const-ness violation (as the Integrity compiler does point out), so change the two variables to be char[] instead of char *, so that the literals populate (and determine the size of) the arrays, instead. Change-Id: Iab34fb378bc0522e14539592ead066f068751ad0 Reviewed-by: Qt CI Bot Reviewed-by: Thiago Macieira --- src/corelib/global/qglobal.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/corelib/global/qglobal.cpp b/src/corelib/global/qglobal.cpp index 30a44aeef8..78ad2a5421 100644 --- a/src/corelib/global/qglobal.cpp +++ b/src/corelib/global/qglobal.cpp @@ -110,8 +110,8 @@ extern "C" { // without full system POSIX. # pragma weak shm_area_password # pragma weak shm_area_name - char *shm_area_password = "dummy"; - char *shm_area_name = "dummy"; + char shm_area_password[] = "dummy"; + char shm_area_name[] = "dummy"; } #endif From 79d7487c89aead1a736f75a5fbf8c154c09924e0 Mon Sep 17 00:00:00 2001 From: Kari Hormi Date: Thu, 10 Oct 2019 17:49:43 +0300 Subject: [PATCH 11/47] Fix cursor not showing in empty block preceding a table When an empty text block precedes a table in QTextEdit, the cursor in the said text block is drawn twice (in order to make sure that the cursor is drawn on top of the table) with inverted colors, resulting in nothing showing up. This commit checks for an empty block before the table and skips the first drawing of the cursor if that's what it finds. Fixes: QTBUG-62919 Change-Id: I828d06e0645007ac42e3f308a35868b4f0db1380 Reviewed-by: Edward Welbourne Reviewed-by: Ville Voutilainen --- src/gui/text/qtextdocumentlayout.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/gui/text/qtextdocumentlayout.cpp b/src/gui/text/qtextdocumentlayout.cpp index cf02e2f9c8..65cafc7bc2 100644 --- a/src/gui/text/qtextdocumentlayout.cpp +++ b/src/gui/text/qtextdocumentlayout.cpp @@ -1336,8 +1336,12 @@ void QTextDocumentLayoutPrivate::drawBlock(const QPointF &offset, QPainter *pain tl->draw(painter, offset, selections, context.clip.isValid() ? (context.clip & clipRect) : clipRect); - if ((context.cursorPosition >= blpos && context.cursorPosition < blpos + bllen) - || (context.cursorPosition < -1 && !tl->preeditAreaText().isEmpty())) { + // if the block is empty and it precedes a table, do not draw the cursor. + // the cursor is drawn later after the table has been drawn so no need + // to draw it here. + if (!isEmptyBlockBeforeTable(frameIteratorForTextPosition(blpos)) + && ((context.cursorPosition >= blpos && context.cursorPosition < blpos + bllen) + || (context.cursorPosition < -1 && !tl->preeditAreaText().isEmpty()))) { int cpos = context.cursorPosition; if (cpos < -1) cpos = tl->preeditAreaPosition() - (cpos + 2); From 3478f6c4474e86f2431c22533dbffefb48af93a9 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Fri, 11 Oct 2019 16:30:33 +0200 Subject: [PATCH 12/47] Doc: Describe using QVERIFY to verify validity of QSignalSpy From https://wiki.qt.io/Writing_Unit_Tests Change-Id: I3186efe30cde465766800aee1f0a530fb80907fb Reviewed-by: Edward Welbourne --- src/testlib/qsignalspy.qdoc | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/testlib/qsignalspy.qdoc b/src/testlib/qsignalspy.qdoc index 3352307d69..39639d0a09 100644 --- a/src/testlib/qsignalspy.qdoc +++ b/src/testlib/qsignalspy.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the documentation of the Qt Toolkit. @@ -48,7 +48,7 @@ \snippet code/doc_src_qsignalspy.cpp 1 - \b {Note:} Non-standard data types need to be registered, using + \note Non-standard data types need to be registered, using the qRegisterMetaType() function, before you can create a QSignalSpy. For example: @@ -57,6 +57,18 @@ To retrieve the instance, you can use qvariant_cast: \snippet code/doc_src_qsignalspy.cpp 3 + + \section1 Verifying Signal Emissions + + The QSignalSpy class provides an elegant mechanism for capturing the list + of signals emitted by an object. However, you should verify its validity + after construction. The constructor does a number of sanity checks, such as + verifying that the signal to be spied upon actually exists. To make the + diagnosis of test failures easier, the results of these checks should be + checked by calling \c QVERIFY(spy.isValid()) before proceeding further with + a test. + + \sa QVERIFY() */ /*! \fn QSignalSpy::QSignalSpy(const QObject *object, const char *signal) From 50481fb909c2bbbc26a193e23783e5b0151168b9 Mon Sep 17 00:00:00 2001 From: Giuseppe D'Angelo Date: Thu, 10 Oct 2019 17:29:17 +0200 Subject: [PATCH 13/47] QEndian: do not use "raw" constexpr Use the Qt's macros instead, since constexpr support may be revoked on certain compilers. Amends d26289ffb43a5fcf34e855db1dfbf42aa03c4f5a. Change-Id: I62354b14b57ae5fcbf3f1186ddb48bcf26535e90 Reviewed-by: Edward Welbourne --- src/corelib/global/qendian.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/corelib/global/qendian.h b/src/corelib/global/qendian.h index 615f523888..5cd9d3160b 100644 --- a/src/corelib/global/qendian.h +++ b/src/corelib/global/qendian.h @@ -327,9 +327,9 @@ public: return pre; } - static constexpr QSpecialInteger max() + static Q_DECL_CONSTEXPR QSpecialInteger max() { return QSpecialInteger(std::numeric_limits::max()); } - static constexpr QSpecialInteger min() + static Q_DECL_CONSTEXPR QSpecialInteger min() { return QSpecialInteger(std::numeric_limits::min()); } }; @@ -373,8 +373,8 @@ public: QLEInteger &operator ++(int); QLEInteger &operator --(int); - static constexpr QLEInteger max(); - static constexpr QLEInteger min(); + static Q_DECL_CONSTEXPR QLEInteger max(); + static Q_DECL_CONSTEXPR QLEInteger min(); }; template @@ -400,8 +400,8 @@ public: QBEInteger &operator ++(int); QBEInteger &operator --(int); - static constexpr QBEInteger max(); - static constexpr QBEInteger min(); + static Q_DECL_CONSTEXPR QBEInteger max(); + static Q_DECL_CONSTEXPR QBEInteger min(); }; #else From 2ec93e29f75e0cb37650255d478b8e3a9da4dc25 Mon Sep 17 00:00:00 2001 From: Andy Shaw Date: Wed, 9 Oct 2019 15:48:03 +0200 Subject: [PATCH 14/47] Cocoa: Update the PPK_PrinterName property if one is explicitly set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a QPrinterInfo is passed in to the QPrinter then it needs to ensure that the underlying session is set up to use the specified printer, otherwise it uses the default one as it has not been changed. Change-Id: I90012223e9831303d02fd3ffc68223dc492ece0c Reviewed-by: Tor Arne Vestbø --- src/plugins/platforms/cocoa/qprintengine_mac.mm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/platforms/cocoa/qprintengine_mac.mm b/src/plugins/platforms/cocoa/qprintengine_mac.mm index 58ad53a6e1..dcb9a85a3c 100644 --- a/src/plugins/platforms/cocoa/qprintengine_mac.mm +++ b/src/plugins/platforms/cocoa/qprintengine_mac.mm @@ -59,6 +59,8 @@ QMacPrintEngine::QMacPrintEngine(QPrinter::PrinterMode mode, const QString &devi QString id = deviceId; if (id.isEmpty()) id = QCocoaPrinterSupport().defaultPrintDeviceId(); + else + setProperty(QPrintEngine::PPK_PrinterName, deviceId); d->m_printDevice.reset(new QCocoaPrintDevice(id)); d->m_pageLayout.setPageSize(d->m_printDevice->defaultPageSize()); d->initialize(); From ed7dd9a6edd7afd8798751c8b1d3ee7802ae253f Mon Sep 17 00:00:00 2001 From: Christian Ehrlicher Date: Sun, 6 Oct 2019 12:56:34 +0200 Subject: [PATCH 15/47] QODBC: Fix crash when a prepared statement is deleted after the db was removed When a prepared statement is still alive after the database was removed with QSqlDatabase::removeDatabase(), the cleanup routine is trying to access the driver which is no longer alive which results in a crash. Fixes: QTBUG-79019 Change-Id: I4630e3b947a12b23ed062f015abc373fc0e246c1 Reviewed-by: Andy Shaw --- src/plugins/sqldrivers/odbc/qsql_odbc.cpp | 10 +++---- .../sql/kernel/qsqlquery/tst_qsqlquery.cpp | 27 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp index 7f98efccba..7709b13cd1 100644 --- a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp +++ b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp @@ -221,18 +221,18 @@ public: int disconnectCount; bool hasSQLFetchScroll; - bool isStmtHandleValid(); + bool isStmtHandleValid() const; void updateStmtHandleState(); }; -bool QODBCResultPrivate::isStmtHandleValid() +bool QODBCResultPrivate::isStmtHandleValid() const { - return disconnectCount == drv_d_func()->disconnectCount; + return drv_d_func() && disconnectCount == drv_d_func()->disconnectCount; } void QODBCResultPrivate::updateStmtHandleState() { - disconnectCount = drv_d_func()->disconnectCount; + disconnectCount = drv_d_func() ? drv_d_func()->disconnectCount : 0; } static QString qWarnODBCHandle(int handleType, SQLHANDLE handle, int *nativeCode = 0) @@ -975,7 +975,7 @@ QODBCResult::QODBCResult(const QODBCDriver *db) QODBCResult::~QODBCResult() { Q_D(QODBCResult); - if (d->hStmt && d->isStmtHandleValid() && driver()->isOpen()) { + if (d->hStmt && d->isStmtHandleValid() && driver() && driver()->isOpen()) { SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt); if (r != SQL_SUCCESS) qSqlWarning(QLatin1String("QODBCDriver: Unable to free statement handle ") diff --git a/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp b/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp index 784d0a70d7..fd7f24f308 100644 --- a/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp +++ b/tests/auto/sql/kernel/qsqlquery/tst_qsqlquery.cpp @@ -196,8 +196,7 @@ private slots: void task_250026_data() { generic_data("QODBC"); } void task_250026(); - void task_205701_data() { generic_data("QMYSQL"); } - void task_205701(); + void crashQueryOnCloseDatabase(); void task_233829_data() { generic_data("QPSQL"); } void task_233829(); @@ -311,6 +310,8 @@ void tst_QSqlQuery::init() void tst_QSqlQuery::cleanup() { + if (QTest::currentTestFunction() == QLatin1String("crashQueryOnCloseDatabase")) + return; QFETCH( QString, dbName ); QSqlDatabase db = QSqlDatabase::database( dbName ); CHECK_DATABASE( db ); @@ -3448,19 +3449,17 @@ void tst_QSqlQuery::task_250026() QCOMPARE( q.value( 0 ).toString().length(), data1026.length() ); } -void tst_QSqlQuery::task_205701() +void tst_QSqlQuery::crashQueryOnCloseDatabase() { - QSqlDatabase qsdb = QSqlDatabase::addDatabase("QMYSQL", "atest"); - qsdb.setHostName("test"); - qsdb.setDatabaseName("test"); - qsdb.setUserName("test"); - qsdb.setPassword("test"); - qsdb.open(); - -// { - QSqlQuery query(qsdb); -// } - QSqlDatabase::removeDatabase("atest"); + for (const auto &dbName : qAsConst(dbs.dbNames)) { + QSqlDatabase clonedDb = QSqlDatabase::cloneDatabase( + QSqlDatabase::database(dbName), "crashTest"); + qDebug() << "Testing crash in sqlquery dtor for driver" << clonedDb.driverName(); + QVERIFY(clonedDb.open()); + QSqlQuery q(clonedDb); + clonedDb.close(); + QSqlDatabase::removeDatabase("crashTest"); + } } #ifdef NOT_READY_YET From 2967510213373fa92db94385e3904196637e8df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Mon, 14 Oct 2019 14:14:01 +0200 Subject: [PATCH 16/47] macOS: Merge [QCocoaApplicationDelegate canQuit] into callsite The logic for handling termination without any event loops has been moved up as an early exit, and the code has been modernized. Change-Id: I202720b9923e35732cffea656e1ce108ef11953d Reviewed-by: Paul Olav Tvete --- .../cocoa/qcocoaapplicationdelegate.mm | 54 +++++++++---------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm index 33a45985a8..01abeb1a0e 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm @@ -140,44 +140,38 @@ QT_USE_NAMESPACE return [[self.dockMenu retain] autorelease]; } -- (BOOL)canQuit -{ - QCloseEvent ev; - QGuiApplication::sendEvent(qGuiApp, &ev); - return ev.isAccepted(); -} - // This function will only be called when NSApp is actually running. - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { if ([reflectionDelegate respondsToSelector:_cmd]) return [reflectionDelegate applicationShouldTerminate:sender]; - if ([self canQuit]) { - if (!startedQuit) { - startedQuit = true; - // Close open windows. This is done in order to deliver de-expose - // events while the event loop is still running. - const QWindowList topLevels = QGuiApplication::topLevelWindows(); - for (int i = 0; i < topLevels.size(); ++i) { - QWindow *topLevelWindow = topLevels.at(i); - // Already closed windows will not have a platform window, skip those - if (topLevelWindow->handle()) - QWindowSystemInterface::handleCloseEvent(topLevelWindow); - } - QWindowSystemInterface::flushWindowSystemEvents(); - - QGuiApplication::exit(0); - startedQuit = false; - } + if (QGuiApplicationPrivate::instance()->threadData->eventLoops.isEmpty()) { + // No event loop is executing. This probably means that Qt is used as a plugin, + // or as a part of a native Cocoa application. In any case it should be fine to + // terminate now. + return NSTerminateNow; } - if (QGuiApplicationPrivate::instance()->threadData->eventLoops.isEmpty()) { - // INVARIANT: No event loop is executing. This probably - // means that Qt is used as a plugin, or as a part of a native - // Cocoa application. In any case it should be fine to - // terminate now: - return NSTerminateNow; + QCloseEvent ev; + QGuiApplication::sendEvent(qGuiApp, &ev); + if (!ev.isAccepted()) + return NSTerminateCancel; + + if (!startedQuit) { + startedQuit = true; + // Close open windows. This is done in order to deliver de-expose + // events while the event loop is still running. + for (QWindow *topLevelWindow : QGuiApplication::topLevelWindows()) { + // Already closed windows will not have a platform window, skip those + if (!topLevelWindow->handle()) + continue; + + QWindowSystemInterface::handleCloseEvent(topLevelWindow); + } + + QGuiApplication::exit(0); + startedQuit = false; } return NSTerminateCancel; From 1b6db1849477be30ef0ca52c288d358b911ea1e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Mon, 14 Oct 2019 15:58:50 +0200 Subject: [PATCH 17/47] Propagate application termination requests through QPA Instead of having each platform plugin deal with application termination in their own weird ways, we now have a QPA API to signal that the system requested the application to terminate. On the QGuiApplication side this results in a Quit event being sent to the application, which triggers the default behavior of closing all app windows, and then finally calling QCoreApplication::quit(). The quit event replaces the misuse of a close event being sent to the application. Close events are documented as being sent to windows. The close events that are sent to individual windows as part of the quit process can be ignored. This will skip the final quit() of the application, and also inform the platform that the quit was not accepted, in case that should be propagated further. In the future the logic for closing windows should be unified between the various approaches in closeAllWindows, shouldQuit, and friends. Change-Id: I0ed7f1c0d3f0bf1a755e1dd4066e1575fc3a28e1 Reviewed-by: Paul Olav Tvete --- src/gui/kernel/qguiapplication.cpp | 23 ++++++++++++++ src/gui/kernel/qguiapplication_p.h | 2 ++ src/gui/kernel/qwindowsysteminterface.cpp | 6 ++++ src/gui/kernel/qwindowsysteminterface.h | 3 ++ src/gui/kernel/qwindowsysteminterface_p.h | 3 +- .../cocoa/qcocoaapplicationdelegate.mm | 31 +++++++------------ .../platforms/haiku/qhaikuapplication.cpp | 5 +-- .../platforms/xcb/qxcbsessionmanager.cpp | 5 +-- src/widgets/kernel/qapplication.cpp | 19 +++++------- 9 files changed, 62 insertions(+), 35 deletions(-) diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp index 13de8af8b5..54f3996b6e 100644 --- a/src/gui/kernel/qguiapplication.cpp +++ b/src/gui/kernel/qguiapplication.cpp @@ -1863,7 +1863,20 @@ bool QGuiApplication::event(QEvent *e) { if(e->type() == QEvent::LanguageChange) { setLayoutDirection(qt_detectRTLLanguage()?Qt::RightToLeft:Qt::LeftToRight); + } else if (e->type() == QEvent::Quit) { + // Close open windows. This is done in order to deliver de-expose + // events while the event loop is still running. + for (QWindow *topLevelWindow : QGuiApplication::topLevelWindows()) { + // Already closed windows will not have a platform window, skip those + if (!topLevelWindow->handle()) + continue; + if (!topLevelWindow->close()) { + e->ignore(); + return true; + } + } } + return QCoreApplication::event(e); } @@ -1940,6 +1953,9 @@ void QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePriv QWindowSystemInterfacePrivate::ApplicationStateChangedEvent * changeEvent = static_cast(e); QGuiApplicationPrivate::setApplicationState(changeEvent->newState, changeEvent->forcePropagate); } break; + case QWindowSystemInterfacePrivate::ApplicationTermination: + QGuiApplicationPrivate::processApplicationTermination(e); + break; case QWindowSystemInterfacePrivate::FlushEvents: { QWindowSystemInterfacePrivate::FlushEventsEvent *flushEventsEvent = static_cast(e); QWindowSystemInterface::deferredFlushWindowSystemEvents(flushEventsEvent->flags); } @@ -3489,6 +3505,13 @@ bool QGuiApplicationPrivate::tryCloseRemainingWindows(QWindowList processedWindo return true; } +void QGuiApplicationPrivate::processApplicationTermination(QWindowSystemInterfacePrivate::WindowSystemEvent *windowSystemEvent) +{ + QEvent event(QEvent::Quit); + QGuiApplication::sendSpontaneousEvent(QGuiApplication::instance(), &event); + windowSystemEvent->eventAccepted = event.isAccepted(); +} + /*! \since 5.2 \fn Qt::ApplicationState QGuiApplication::applicationState() diff --git a/src/gui/kernel/qguiapplication_p.h b/src/gui/kernel/qguiapplication_p.h index e28607bad6..26f65b2f16 100644 --- a/src/gui/kernel/qguiapplication_p.h +++ b/src/gui/kernel/qguiapplication_p.h @@ -141,6 +141,8 @@ public: static void processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent *e); + static void processApplicationTermination(QWindowSystemInterfacePrivate::WindowSystemEvent *e); + static void updateFilteredScreenOrientation(QScreen *screen); static void reportScreenOrientationChange(QScreen *screen); static void processScreenOrientationChange(QWindowSystemInterfacePrivate::ScreenOrientationEvent *e); diff --git a/src/gui/kernel/qwindowsysteminterface.cpp b/src/gui/kernel/qwindowsysteminterface.cpp index 5f61853a6d..4f1056e906 100644 --- a/src/gui/kernel/qwindowsysteminterface.cpp +++ b/src/gui/kernel/qwindowsysteminterface.cpp @@ -285,6 +285,12 @@ QT_DEFINE_QPA_EVENT_HANDLER(void, handleApplicationStateChanged, Qt::Application QWindowSystemInterfacePrivate::handleWindowSystemEvent(e); } +QT_DEFINE_QPA_EVENT_HANDLER(bool, handleApplicationTermination) +{ + auto *e = new QWindowSystemInterfacePrivate::WindowSystemEvent(QWindowSystemInterfacePrivate::ApplicationTermination); + return QWindowSystemInterfacePrivate::handleWindowSystemEvent(e); +} + QWindowSystemInterfacePrivate::GeometryChangeEvent::GeometryChangeEvent(QWindow *window, const QRect &newGeometry) : WindowSystemEvent(GeometryChange) , window(window) diff --git a/src/gui/kernel/qwindowsysteminterface.h b/src/gui/kernel/qwindowsysteminterface.h index 4a0bc858a9..d5a4ad30d8 100644 --- a/src/gui/kernel/qwindowsysteminterface.h +++ b/src/gui/kernel/qwindowsysteminterface.h @@ -215,6 +215,9 @@ public: template static void handleApplicationStateChanged(Qt::ApplicationState newState, bool forcePropagate = false); + template + static bool handleApplicationTermination(); + #if QT_CONFIG(draganddrop) #if QT_DEPRECATED_SINCE(5, 11) QT_DEPRECATED static QPlatformDragQtResponse handleDrag(QWindow *window, const QMimeData *dropData, diff --git a/src/gui/kernel/qwindowsysteminterface_p.h b/src/gui/kernel/qwindowsysteminterface_p.h index 55fd181ef0..6e4bce607e 100644 --- a/src/gui/kernel/qwindowsysteminterface_p.h +++ b/src/gui/kernel/qwindowsysteminterface_p.h @@ -99,7 +99,8 @@ public: ApplicationStateChanged = 0x19, FlushEvents = 0x20, WindowScreenChanged = 0x21, - SafeAreaMarginsChanged = 0x22 + SafeAreaMarginsChanged = 0x22, + ApplicationTermination = 0x23 }; class WindowSystemEvent { diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm index 01abeb1a0e..9b0a6b1b86 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm @@ -88,10 +88,13 @@ #include #include +QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcQpaApplication, "qt.qpa.application"); +QT_END_NAMESPACE + QT_USE_NAMESPACE @implementation QCocoaApplicationDelegate { - bool startedQuit; NSObject *reflectionDelegate; bool inLaunch; } @@ -150,30 +153,20 @@ QT_USE_NAMESPACE // No event loop is executing. This probably means that Qt is used as a plugin, // or as a part of a native Cocoa application. In any case it should be fine to // terminate now. + qCDebug(lcQpaApplication) << "No running event loops, terminating now"; return NSTerminateNow; } - QCloseEvent ev; - QGuiApplication::sendEvent(qGuiApp, &ev); - if (!ev.isAccepted()) + if (!QWindowSystemInterface::handleApplicationTermination()) { + qCDebug(lcQpaApplication) << "Application termination canceled"; return NSTerminateCancel; - - if (!startedQuit) { - startedQuit = true; - // Close open windows. This is done in order to deliver de-expose - // events while the event loop is still running. - for (QWindow *topLevelWindow : QGuiApplication::topLevelWindows()) { - // Already closed windows will not have a platform window, skip those - if (!topLevelWindow->handle()) - continue; - - QWindowSystemInterface::handleCloseEvent(topLevelWindow); - } - - QGuiApplication::exit(0); - startedQuit = false; } + // Even if the application termination was accepted by the application we can't + // return NSTerminateNow, as that would trigger AppKit to ultimately call exit(). + // We need to ensure that the runloop continues spinning so that we can return + // from our own event loop back to main(), and exit from there. + qCDebug(lcQpaApplication) << "Termination accepted, but returning to runloop for exit through main()"; return NSTerminateCancel; } diff --git a/src/plugins/platforms/haiku/qhaikuapplication.cpp b/src/plugins/platforms/haiku/qhaikuapplication.cpp index b75810c453..de4acdfd4a 100644 --- a/src/plugins/platforms/haiku/qhaikuapplication.cpp +++ b/src/plugins/platforms/haiku/qhaikuapplication.cpp @@ -42,6 +42,8 @@ #include #include +#include + #include #include @@ -52,8 +54,7 @@ QHaikuApplication::QHaikuApplication(const char *signature) bool QHaikuApplication::QuitRequested() { - QEvent quitEvent(QEvent::Quit); - QCoreApplication::sendEvent(QCoreApplication::instance(), &quitEvent); + QWindowSystemInterface::handleApplicationTermination(); return true; } diff --git a/src/plugins/platforms/xcb/qxcbsessionmanager.cpp b/src/plugins/platforms/xcb/qxcbsessionmanager.cpp index 2303ccf806..f880d4d722 100644 --- a/src/plugins/platforms/xcb/qxcbsessionmanager.cpp +++ b/src/plugins/platforms/xcb/qxcbsessionmanager.cpp @@ -42,6 +42,8 @@ #ifndef QT_NO_SESSIONMANAGER +#include + #include #include #include @@ -289,8 +291,7 @@ static void sm_dieCallback(SmcConn smcConn, SmPointer /* clientData */) if (smcConn != smcConnection) return; resetSmState(); - QEvent quitEvent(QEvent::Quit); - QGuiApplication::sendEvent(qApp, &quitEvent); + QWindowSystemInterface::handleApplicationTermination(); } static void sm_shutdownCancelledCallback(SmcConn smcConn, SmPointer clientData) diff --git a/src/widgets/kernel/qapplication.cpp b/src/widgets/kernel/qapplication.cpp index 3223781b63..dfa1bc23b1 100644 --- a/src/widgets/kernel/qapplication.cpp +++ b/src/widgets/kernel/qapplication.cpp @@ -1866,22 +1866,19 @@ void QApplication::aboutQt() bool QApplication::event(QEvent *e) { Q_D(QApplication); - if(e->type() == QEvent::Close) { - QCloseEvent *ce = static_cast(e); - ce->accept(); + if (e->type() == QEvent::Quit) { closeAllWindows(); - - const QWidgetList list = topLevelWidgets(); - for (auto *w : list) { + for (auto *w : topLevelWidgets()) { if (w->isVisible() && !(w->windowType() == Qt::Desktop) && !(w->windowType() == Qt::Popup) && (!(w->windowType() == Qt::Dialog) || !w->parentWidget())) { - ce->ignore(); - break; + e->ignore(); + return true; } } - if (ce->isAccepted()) { - return true; - } + // Explicitly call QCoreApplication instead of QGuiApplication so that + // we don't let QGuiApplication close any windows we skipped earlier in + // closeAllWindows(). FIXME: Unify all this close magic through closeAllWindows. + return QCoreApplication::event(e); #ifndef Q_OS_WIN } else if (e->type() == QEvent::LocaleChange) { // on Windows the event propagation is taken care by the From 51edfdd212ed8be611ce6719db7ef454de5b5bcc Mon Sep 17 00:00:00 2001 From: Topi Reinio Date: Thu, 17 Oct 2019 10:24:12 +0200 Subject: [PATCH 18/47] Doc: Prevent QDoc from auto-linking certain words 'macOS' appears in the documentation frequently, and because it resembles a variable/property name, QDoc links each occurrence of it. 'WebChannel' appears as part of a module name, but it's also a QML type - auto-linking each word to QML documentation is confusing. Likewise, there's no need to link 'OpenGL' each time. Explicit links such as \l [QML] WebChannel continue to work. Task-number: QTBUG-79135 Change-Id: I76cc84b0076255e260aa88c244102702a48f35a6 Reviewed-by: Martin Smith --- doc/global/config.qdocconf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/global/config.qdocconf b/doc/global/config.qdocconf index 0b276c400f..16d4e5c63a 100644 --- a/doc/global/config.qdocconf +++ b/doc/global/config.qdocconf @@ -11,6 +11,12 @@ dita.metadata.default.audience = programmer navigation.homepage = index.html navigation.hometitle = "Qt $QT_VER" +#Words to ignore for auto-linking +ignorewords += \ + macOS \ + WebChannel \ + OpenGL + sourcedirs += includes $$BUILDDIR url = http://doc.qt.io/qt-5 From cc4087b18a488ffb02105d31da6f05acad65b728 Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Wed, 16 Oct 2019 15:24:07 +0200 Subject: [PATCH 19/47] Remove mapping from CJK/Latin to Common script in Harfbuzz NG The original commit message says this is to ensure compatibility with the old Harfbuzz, but since OpenType features such as kerning are often matched based on the writing system of the glyphs, it will break kerning (and other OpenType features) for text in these languages in some fonts. Even font that were successfully kerned by the old Harfbuzz are broken. To avoid regressing on finding cursor positions inside ligatures, we need to amend 9f837af9458ea4825b9a8061de444f62d8a7a048. This would enable cursor positions inside ligatures for languages where they are only used for cosmetic purposes, and this was generalized to Common and Greek at the time. This now has to be expanded to include all the writing systems that were previously covered by "Common". [ChangeLog][Text] Fixed kerning error with certain fonts. Fixes: QTBUG-77908 Change-Id: Id261fef05f86841b1533b7d87207c3d17e01e96e Reviewed-by: Simon Hausmann --- src/gui/text/qtextengine.cpp | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp index b37353bf2c..209433dac5 100644 --- a/src/gui/text/qtextengine.cpp +++ b/src/gui/text/qtextengine.cpp @@ -2125,22 +2125,7 @@ void QTextEngine::itemize() const } #if QT_CONFIG(harfbuzz) analysis = scriptAnalysis.data(); - if (qt_useHarfbuzzNG()) { - // ### pretend HB-old behavior for now - for (int i = 0; i < length; ++i) { - switch (analysis[i].script) { - case QChar::Script_Latin: - case QChar::Script_Hiragana: - case QChar::Script_Katakana: - case QChar::Script_Bopomofo: - case QChar::Script_Han: - analysis[i].script = QChar::Script_Common; - break; - default: - break; - } - } - } else { + if (!qt_useHarfbuzzNG()) { for (int i = 0; i < length; ++i) analysis[i].script = hbscript_to_script(script_to_hbscript(analysis[i].script)); } @@ -3619,7 +3604,12 @@ int QTextEngine::positionInLigature(const QScriptItem *si, int end, int clusterLength = 0; if (si->analysis.script != QChar::Script_Common && - si->analysis.script != QChar::Script_Greek) { + si->analysis.script != QChar::Script_Greek && + si->analysis.script != QChar::Script_Latin && + si->analysis.script != QChar::Script_Hiragana && + si->analysis.script != QChar::Script_Katakana && + si->analysis.script != QChar::Script_Bopomofo && + si->analysis.script != QChar::Script_Han) { if (glyph_pos == -1) return si->position + end; else { From 5205510524602bb36d3150001c22c2566259655b Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Mon, 21 Oct 2019 08:12:28 +0200 Subject: [PATCH 20/47] Enable cursor in ligature test on Windows The default font on Windows ("Times" is not found) does not have a ligature for "fi", so the test would not actually be testing what it was supposed to on this platform, and would pass even when the code was buggy. To enable the test on Windows, we select a standard font which has the ligature (Calibri). Change-Id: Ic117cd8e549aa729a0cd68006d7c180c6c89c053 Reviewed-by: Simon Hausmann --- tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp b/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp index a474acd790..2dcca0209e 100644 --- a/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp +++ b/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp @@ -2068,7 +2068,12 @@ void tst_QTextLayout::cursorInLigatureWithMultipleLines() void tst_QTextLayout::xToCursorForLigatures() { +#if defined(Q_OS_WIN32) + QTextLayout layout("fi", QFont("Calibri", 20)); +#else QTextLayout layout("fi", QFont("Times", 20)); +#endif + layout.setCacheEnabled(true); layout.beginLayout(); QTextLine line = layout.createLine(); From d3398207d0384e786f466662fa40c6fb86844fb6 Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Wed, 16 Oct 2019 11:34:29 +0200 Subject: [PATCH 21/47] Revert "Release left button before showing the popup context menu" This partially reverts commit 5e8b16f0e4247cc978b08480450526cfa3b25029. Releasing the mouse button synthetically made it impossible to use tap and hold gestures. When investigating, it seems that other changes have fixed the original issue that 5e8b16f0e4247cc978b08480450526cfa3b25029 was meant to address, so this is no longer needed. [ChangeLog][Android] Fixed regression that made it impossible for an application to use the tap-and-hold gesture. Fixes: QTBUG-72408 Change-Id: I53f687d047a4ad0fdf3c8c96a00ed1b11d09f047 Reviewed-by: BogDan Vatra --- src/plugins/platforms/android/androidjniinput.cpp | 14 -------------- src/plugins/platforms/android/androidjniinput.h | 2 -- .../platforms/android/qandroidinputcontext.cpp | 3 --- 3 files changed, 19 deletions(-) diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp index 9cdc5de0e1..56885f2e23 100644 --- a/src/plugins/platforms/android/androidjniinput.cpp +++ b/src/plugins/platforms/android/androidjniinput.cpp @@ -195,20 +195,6 @@ namespace QtAndroidInput angleDelta); } - void releaseMouse(int x, int y) - { - m_ignoreMouseEvents = true; - QPoint globalPos(x,y); - QWindow *tlw = topLevelWindowAt(globalPos); - QPoint localPos = tlw ? (globalPos-tlw->position()) : globalPos; - - // Release left button - QWindowSystemInterface::handleMouseEvent(tlw, - localPos, - globalPos, - Qt::MouseButtons(Qt::NoButton)); - } - static void longPress(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y) { QAndroidInputContext *inputContext = QAndroidInputContext::androidInputContext(); diff --git a/src/plugins/platforms/android/androidjniinput.h b/src/plugins/platforms/android/androidjniinput.h index 2e2470ae8f..cc3070c4aa 100644 --- a/src/plugins/platforms/android/androidjniinput.h +++ b/src/plugins/platforms/android/androidjniinput.h @@ -61,8 +61,6 @@ namespace QtAndroidInput void updateHandles(int handleCount, QPoint editMenuPos = QPoint(), uint32_t editButtons = 0, QPoint cursor = QPoint(), QPoint anchor = QPoint(), bool rtl = false); bool registerNatives(JNIEnv *env); - - void releaseMouse(int x, int y); } QT_END_NAMESPACE diff --git a/src/plugins/platforms/android/qandroidinputcontext.cpp b/src/plugins/platforms/android/qandroidinputcontext.cpp index 5614d3b04f..e78c317863 100644 --- a/src/plugins/platforms/android/qandroidinputcontext.cpp +++ b/src/plugins/platforms/android/qandroidinputcontext.cpp @@ -827,9 +827,6 @@ void QAndroidInputContext::longPress(int x, int y) focusObjectStopComposing(); - // Release left button, otherwise the following events will cancel the menu popup - QtAndroidInput::releaseMouse(x, y); - const double pixelDensity = QGuiApplication::focusWindow() ? QHighDpiScaling::factor(QGuiApplication::focusWindow()) From d71dbe3e9322430dceed9cb2d3f957fbac2ab715 Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Fri, 11 Oct 2019 09:52:24 +0200 Subject: [PATCH 22/47] docs: clarify usage of css 'qproperty' in stylesheets Clarify that the qproperty properties will only be evaluated once. Fixes: QTBUG-2982 Change-Id: Ie294ced118f740c7378c62c0b5a4924d5628e118 Reviewed-by: Venugopal Shivashankar --- src/widgets/doc/src/widgets-and-layouts/stylesheet.qdoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/widgets/doc/src/widgets-and-layouts/stylesheet.qdoc b/src/widgets/doc/src/widgets-and-layouts/stylesheet.qdoc index 00323eace6..84233e4b62 100644 --- a/src/widgets/doc/src/widgets-and-layouts/stylesheet.qdoc +++ b/src/widgets/doc/src/widgets-and-layouts/stylesheet.qdoc @@ -549,6 +549,10 @@ If the property references an enum declared with Q_ENUMS, you should reference its constants by name, i.e., not their numeric value. + \note Use the qproperty syntax with care, as it modifies the + widget that is being painted. Also, the qproperty syntax is evaluated only + once, which is when the widget is polished by the style. This means that any + attempt to use them in pseudo-states such as QPushButton:hover, will not work. */ /*! From 5288c8dae34ae3a3883f12c8fabeb717e1c5a263 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Sun, 20 Oct 2019 18:06:07 +0200 Subject: [PATCH 23/47] rhi: Enhance swapchain size query docs Inspired by a recent Qt Quick fix. Make sure we make it clear that QRhi-based rendering code should base output size calculations (e.g. for setViewport() and similar) on the pixel size reported from QRhiSwapChain, instead of going to the QWindow. Change-Id: I2fc22972162ccc6307ac07ceb7766c746d5f562a Reviewed-by: Paul Olav Tvete --- src/gui/rhi/qrhi.cpp | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index dabad35688..8ef98d2e42 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -3518,14 +3518,36 @@ QRhiResource::Type QRhiSwapChain::resourceType() const \c{currentPixelSize() != surfacePixelSize()} then the swapchain needs to be resized. + \note Typical rendering logic will call this function to get the output + size when starting to prepare a new frame, and base dependent calculations + (such as, the viewport) on the size returned from this function. + + While in many cases the value is the same as \c{QWindow::size() * + QWindow::devicePixelRatio()}, relying on the QWindow-reported size is not + guaranteed to be correct on all platforms and graphics API implementations. + Using this function is therefore strongly recommended whenever there is a + need to identify the dimensions, in pixels, of the output layer or surface. + + This also has the added benefit of avoiding potential data races when QRhi + is used on a dedicated rendering thread, because the need to call QWindow + functions, that may then access data updated on the main thread, is + avoided. + \sa surfacePixelSize() */ /*! \fn QSize QRhiSwapChain::surfacePixelSize() - \return The size of the window's associated surface or layer. Do not assume - this is the same as QWindow::size() * QWindow::devicePixelRatio(). + \return The size of the window's associated surface or layer. + + \warning Do not assume this is the same as \c{QWindow::size() * + QWindow::devicePixelRatio()}. With some graphics APIs and windowing system + interfaces (for example, Vulkan) there is a theoretical possibility for a + surface to assume a size different from the associated window. To support + these cases, rendering logic must always base size-derived calculations + (such as, viewports) on the size reported from QRhiSwapChain, and never on + the size queried from QWindow. \note Can also be called before buildOrResize(), if at least window() is already set) This in combination with currentPixelSize() allows to detect From aa4b0f5cb7e84046530fbc26581f777506fea658 Mon Sep 17 00:00:00 2001 From: Christian Ehrlicher Date: Sat, 19 Oct 2019 16:39:58 +0200 Subject: [PATCH 24/47] Uic: fix crash when trying to resource icon information Fix a typo introduced in be56db2c49be02fd7083c5a02131462748e29bef to avoid a crash when a pixmap is given for selected on but not for selected off. Fixes: QTBUG-79125 Change-Id: I84072b6b4e8a4d21684be21f5bff1deeaddbba6d Reviewed-by: Friedemann Kleint --- src/tools/uic/cpp/cppwriteinitialization.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/uic/cpp/cppwriteinitialization.cpp b/src/tools/uic/cpp/cppwriteinitialization.cpp index fd5f8c9017..717bff4a51 100644 --- a/src/tools/uic/cpp/cppwriteinitialization.cpp +++ b/src/tools/uic/cpp/cppwriteinitialization.cpp @@ -1680,7 +1680,7 @@ static void writeResourceIcon(QTextStream &output, "Selected", "Off"); } if (i->hasElementSelectedOn()) { - writeIconAddFile(output, indent, iconName, i->elementSelectedOff()->text(), + writeIconAddFile(output, indent, iconName, i->elementSelectedOn()->text(), "Selected", "On"); } } From a9dcd772c032b5cb52f86e6cd898db3d3bc81023 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Fri, 18 Oct 2019 16:11:55 +0200 Subject: [PATCH 25/47] Doc: Replace \b {Note:} with \note Change-Id: I1f6947acec4494c151317e1faf79720dad0da6bb Reviewed-by: Martin Smith --- src/testlib/doc/src/qttestlib-manual.qdoc | 4 +--- src/testlib/qtestcase.qdoc | 24 +++++++++++------------ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/testlib/doc/src/qttestlib-manual.qdoc b/src/testlib/doc/src/qttestlib-manual.qdoc index a3644c1623..ee7767b5a5 100644 --- a/src/testlib/doc/src/qttestlib-manual.qdoc +++ b/src/testlib/doc/src/qttestlib-manual.qdoc @@ -355,15 +355,13 @@ counters can be obtained by running any benchmark executable with the option \c -perfcounterlist. - \list - \li \b Notes: + \note \list \li Using the performance counter may require enabling access to non-privileged applications. \li Devices that do not support high-resolution timers default to one-millisecond granularity. \endlist - \endlist See \l {Chapter 5: Writing a Benchmark}{Writing a Benchmark} in the Qt Test Tutorial for more benchmarking examples. diff --git a/src/testlib/qtestcase.qdoc b/src/testlib/qtestcase.qdoc index f3761cdfa8..9006d7b401 100644 --- a/src/testlib/qtestcase.qdoc +++ b/src/testlib/qtestcase.qdoc @@ -235,7 +235,7 @@ \c aString and \c expected are variables on the stack that are initialized with the current test data. - \b {Note:} This macro can only be used in a test function that is invoked + \note This macro can only be used in a test function that is invoked by the test framework. The test function must have a _data function. */ @@ -282,7 +282,7 @@ This macro can be used to force a test failure. The test stops executing and the failure \a message is appended to the test log. - \b {Note:} This macro can only be used in a test function that is invoked + \note This macro can only be used in a test function that is invoked by the test framework. Example: @@ -359,7 +359,7 @@ \a mode is a \l QTest::TestFailMode and sets whether the test should continue to execute or not. - \b {Note:} This macro can only be used in a test function that is invoked + \note This macro can only be used in a test function that is invoked by the test framework. Example 1: @@ -421,13 +421,13 @@ test has been installed, and regardless of whether the test's build tree is equal to the test's source tree. - \b {Note:} reliable detection of testdata from the source directory requires + \note reliable detection of testdata from the source directory requires either that qmake is used, or the \c{QT_TESTCASE_BUILDDIR} macro is defined to point to the working directory from which the compiler is invoked, or only absolute paths to the source files are passed to the compiler. Otherwise, the absolute path of the source directory cannot be determined. - \b {Note:} For tests that use the \l QTEST_APPLESS_MAIN() macro to generate a + \note For tests that use the \l QTEST_APPLESS_MAIN() macro to generate a \c{main()} function, \c{QFINDTESTDATA} will not attempt to find test data relative to QCoreApplication::applicationDirPath(). In practice, this means that tests using \c{QTEST_APPLESS_MAIN()} will fail to find their test data @@ -449,7 +449,7 @@ Similarly, if qmake is used and the configuration includes \c{QT += gui}, then \c QT_GUI_LIB will be defined automatically. - \b {Note:} On platforms that have keypad navigation enabled by default, + \note On platforms that have keypad navigation enabled by default, this macro will forcefully disable it if \c QT_WIDGETS_LIB is defined. This is done to simplify the usage of key events when writing autotests. If you wish to write a test case that uses keypad navigation, you should enable it either in the @@ -689,7 +689,7 @@ Simulates pressing a \a key with an optional \a modifier on a \a widget. If \a delay is larger than 0, the test will wait for \a delay milliseconds before pressing the key. - \b {Note:} At some point you should release the key using \l keyRelease(). + \note At some point you should release the key using \l keyRelease(). \sa QTest::keyRelease(), QTest::keyClick() */ @@ -701,7 +701,7 @@ If \a delay is larger than 0, the test will wait for \a delay milliseconds before pressing the key. - \b {Note:} At some point you should release the key using \l keyRelease(). + \note At some point you should release the key using \l keyRelease(). \sa QTest::keyRelease(), QTest::keyClick() */ @@ -713,7 +713,7 @@ Simulates pressing a \a key with an optional \a modifier on a \a window. If \a delay is larger than 0, the test will wait for \a delay milliseconds before pressing the key. - \b {Note:} At some point you should release the key using \l keyRelease(). + \note At some point you should release the key using \l keyRelease(). \sa QTest::keyRelease(), QTest::keyClick() */ @@ -726,7 +726,7 @@ If \a delay is larger than 0, the test will wait for \a delay milliseconds before pressing the key. - \b {Note:} At some point you should release the key using \l keyRelease(). + \note At some point you should release the key using \l keyRelease(). \sa QTest::keyRelease(), QTest::keyClick() */ @@ -947,12 +947,12 @@ You can add specializations or overloads of this function to your test to enable verbose output. - \b {Note:} Starting with Qt 5.5, you should prefer to provide a toString() function + \note Starting with Qt 5.5, you should prefer to provide a toString() function in the type's namespace instead of specializing this template. If your code needs to continue to work with the QTestLib from Qt 5.4 or earlier, you need to continue to use specialization. - \b {Note:} The caller of toString() must delete the returned data + \note The caller of toString() must delete the returned data using \c{delete[]}. Your implementation should return a string created with \c{new[]} or qstrdup(). The easiest way to do so is to create a QByteArray or QString and calling QTest::toString() on it From 69a43c6c3e3fcaf10480544c1638f6446ed25d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Nordheim?= Date: Tue, 27 Aug 2019 01:14:39 +0200 Subject: [PATCH 26/47] Schannel refactoring This moves some repeated code into functions (namely readToBuffer and retainExtraData) while also changing how the intermediateBuffer is handled to avoid deallocating and reallocating repeatedly. Change-Id: I49e6cee641f961565051a67123c56b1c8f3c0259 Reviewed-by: Edward Welbourne Reviewed-by: Timur Pocheptsov --- src/network/ssl/qsslsocket_schannel.cpp | 105 ++++++++++++++---------- 1 file changed, 61 insertions(+), 44 deletions(-) diff --git a/src/network/ssl/qsslsocket_schannel.cpp b/src/network/ssl/qsslsocket_schannel.cpp index d7fb080b49..3166cda4c9 100644 --- a/src/network/ssl/qsslsocket_schannel.cpp +++ b/src/network/ssl/qsslsocket_schannel.cpp @@ -431,6 +431,42 @@ QByteArray createAlpnString(const QByteArrayList &nextAllowedProtocols) return alpnString; } #endif // SUPPORTS_ALPN + +qint64 readToBuffer(QByteArray &buffer, QTcpSocket *plainSocket) +{ + Q_ASSERT(plainSocket); + static const qint64 shrinkCutoff = 1024 * 12; + static const qint64 defaultRead = 1024 * 16; + qint64 bytesRead = 0; + + const auto toRead = std::min(defaultRead, plainSocket->bytesAvailable()); + if (toRead > 0) { + const auto bufferSize = buffer.size(); + buffer.reserve(bufferSize + toRead); // avoid growth strategy kicking in + buffer.resize(bufferSize + toRead); + bytesRead = plainSocket->read(buffer.data() + bufferSize, toRead); + buffer.resize(bufferSize + bytesRead); + // In case of excessive memory usage we shrink: + if (buffer.size() < shrinkCutoff && buffer.capacity() > defaultRead) + buffer.shrink_to_fit(); + } + + return bytesRead; +} + +void retainExtraData(QByteArray &buffer, const SecBuffer &secBuffer) +{ + Q_ASSERT(secBuffer.BufferType == SECBUFFER_EXTRA); + if (int(secBuffer.cbBuffer) >= buffer.size()) + return; + +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcSsl, "We got SECBUFFER_EXTRA, will retain %lu bytes", secBuffer.cbBuffer); +#endif + std::move(buffer.end() - secBuffer.cbBuffer, buffer.end(), buffer.begin()); + buffer.resize(secBuffer.cbBuffer); +} + } // anonymous namespace bool QSslSocketPrivate::s_loadRootCertsOnDemand = true; @@ -619,8 +655,8 @@ bool QSslSocketBackendPrivate::acquireCredentialsHandle() nullptr); if (!chainContext) { const QString message = isClient - ? QSslSocket::tr("The certificate provided cannot be used for a client.") - : QSslSocket::tr("The certificate provided cannot be used for a server."); + ? QSslSocket::tr("The certificate provided cannot be used for a client.") + : QSslSocket::tr("The certificate provided cannot be used for a server."); setErrorAndEmit(QAbstractSocket::SocketError::SslInvalidUserDataError, message); return false; } @@ -774,7 +810,7 @@ bool QSslSocketBackendPrivate::acceptContext() Q_ASSERT(mode == QSslSocket::SslServerMode); ULONG contextReq = getContextRequirements(); - intermediateBuffer += plainSocket->read(16384); + readToBuffer(intermediateBuffer, plainSocket); if (intermediateBuffer.isEmpty()) return true; // definitely need more data.. @@ -837,9 +873,9 @@ bool QSslSocketBackendPrivate::acceptContext() // https://docs.microsoft.com/en-us/windows/desktop/secauthn/extra-buffers-returned-by-schannel // inBuffers[1].cbBuffer indicates the amount of bytes _NOT_ processed, the rest need to // be stored. - intermediateBuffer = intermediateBuffer.right(int(inBuffers[1].cbBuffer)); + retainExtraData(intermediateBuffer, inBuffers[1]); } else { /* No 'extra' data, message not incomplete */ - intermediateBuffer.clear(); + intermediateBuffer.resize(0); } if (status != SEC_I_CONTINUE_NEEDED) { @@ -865,11 +901,11 @@ bool QSslSocketBackendPrivate::performHandshake() Q_ASSERT(schannelState == SchannelState::PerformHandshake); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "Bytes available from socket:" << plainSocket->bytesAvailable(); - qCDebug(lcSsl) << "intermediateBuffer size:" << intermediateBuffer.size(); + qCDebug(lcSsl, "Bytes available from socket: %lld", plainSocket->bytesAvailable()); + qCDebug(lcSsl, "intermediateBuffer size: %d", intermediateBuffer.size()); #endif - intermediateBuffer += plainSocket->read(16384); + readToBuffer(intermediateBuffer, plainSocket); if (intermediateBuffer.isEmpty()) return true; // no data, will fail @@ -918,11 +954,10 @@ bool QSslSocketBackendPrivate::performHandshake() // https://docs.microsoft.com/en-us/windows/desktop/secauthn/extra-buffers-returned-by-schannel // inputBuffers[1].cbBuffer indicates the amount of bytes _NOT_ processed, the rest need to // be stored. - intermediateBuffer = intermediateBuffer.right(int(inputBuffers[1].cbBuffer)); - } else { + retainExtraData(intermediateBuffer, inputBuffers[1]); + } else if (status != SEC_E_INCOMPLETE_MESSAGE) { // Clear the buffer if we weren't asked for more data - if (status != SEC_E_INCOMPLETE_MESSAGE) - intermediateBuffer.clear(); + intermediateBuffer.resize(0); } switch (status) { case SEC_E_OK: @@ -1228,8 +1263,7 @@ void QSslSocketBackendPrivate::transmit() fullMessage.resize(inputBuffers[0].cbBuffer + inputBuffers[1].cbBuffer + inputBuffers[2].cbBuffer); const qint64 bytesWritten = plainSocket->write(fullMessage); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "Wrote" << bytesWritten << "of total" - << fullMessage.length() << "bytes"; + qCDebug(lcSsl, "Wrote %lld of total %d bytes", bytesWritten, fullMessage.length()); #endif if (bytesWritten >= 0) { totalBytesWritten += bytesWritten; @@ -1254,36 +1288,24 @@ void QSslSocketBackendPrivate::transmit() int totalRead = 0; bool hadIncompleteData = false; while (!readBufferMaxSize || buffer.size() < readBufferMaxSize) { - QByteArray ciphertext; - if (intermediateBuffer.length()) { + const qint64 bytesRead = readToBuffer(intermediateBuffer, plainSocket); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "Restoring data from intermediateBuffer:" - << intermediateBuffer.length() << "bytes"; + qCDebug(lcSsl, "Read %lld encrypted bytes from the socket", bytesRead); #endif - ciphertext.swap(intermediateBuffer); - } - int initialLength = ciphertext.length(); - ciphertext += plainSocket->read(16384); + if (intermediateBuffer.length() == 0 || (hadIncompleteData && bytesRead == 0)) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "Read" << ciphertext.length() - initialLength - << "encrypted bytes from the socket"; + qCDebug(lcSsl, (hadIncompleteData ? "No new data received, leaving loop!" + : "Nothing to decrypt, leaving loop!")); #endif - if (ciphertext.length() == 0 || (hadIncompleteData && initialLength == ciphertext.length())) { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << (hadIncompleteData ? "No new data received, leaving loop!" - : "Nothing to decrypt, leaving loop!"); -#endif - if (ciphertext.length()) // We have data, it came from intermediateBuffer, swap back - intermediateBuffer.swap(ciphertext); break; } hadIncompleteData = false; #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "Total amount of bytes to decrypt:" << ciphertext.length(); + qCDebug(lcSsl, "Total amount of bytes to decrypt: %d", intermediateBuffer.length()); #endif SecBuffer dataBuffer[4]{ - createSecBuffer(ciphertext, SECBUFFER_DATA), + createSecBuffer(intermediateBuffer, SECBUFFER_DATA), createSecBuffer(nullptr, 0, SECBUFFER_EMPTY), createSecBuffer(nullptr, 0, SECBUFFER_EMPTY), createSecBuffer(nullptr, 0, SECBUFFER_EMPTY) @@ -1299,34 +1321,29 @@ void QSslSocketBackendPrivate::transmit() if (dataBuffer[1].cbBuffer > 0) { // It is always decrypted in-place. // But [0] is the STREAM_HEADER, [1] is the DATA and [2] is the STREAM_TRAILER. - // The pointers in all of those still point into the 'ciphertext' byte array. + // The pointers in all of those still point into 'intermediateBuffer'. buffer.append(static_cast(dataBuffer[1].pvBuffer), dataBuffer[1].cbBuffer); totalRead += dataBuffer[1].cbBuffer; #ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "Decrypted" << dataBuffer[1].cbBuffer - << "bytes. New read buffer size:" << buffer.size(); + qCDebug(lcSsl, "Decrypted %lu bytes. New read buffer size: %d", + dataBuffer[1].cbBuffer, buffer.size()); #endif } if (dataBuffer[3].BufferType == SECBUFFER_EXTRA) { // https://docs.microsoft.com/en-us/windows/desktop/secauthn/extra-buffers-returned-by-schannel // dataBuffer[3].cbBuffer indicates the amount of bytes _NOT_ processed, // the rest need to be stored. -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "We've got excess data, moving it to the intermediate buffer:" - << dataBuffer[3].cbBuffer << "bytes"; -#endif - intermediateBuffer = ciphertext.right(int(dataBuffer[3].cbBuffer)); + retainExtraData(intermediateBuffer, dataBuffer[3]); + } else { + intermediateBuffer.resize(0); } } if (status == SEC_E_INCOMPLETE_MESSAGE) { - // Need more data before we can decrypt.. to the buffer it goes! #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl, "We didn't have enough data to decrypt anything, will try again!"); #endif - Q_ASSERT(intermediateBuffer.isEmpty()); - intermediateBuffer.swap(ciphertext); // We try again, but if we don't get any more data then we leave hadIncompleteData = true; } else if (status == SEC_E_INVALID_HANDLE) { From 559b563d711db0760a51b0dce26536dbc8766a9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Nordheim?= Date: Sat, 7 Sep 2019 02:22:21 +0200 Subject: [PATCH 27/47] Use Schannel's incomplete data guesstimation feature It tells us how many bytes we will need before the call succeeds. It's not accurate but will reduce the amount of calls to their slow functions Change-Id: I82393d5acd68b84c6e6f3377ba40bb1d5c51ca8a Reviewed-by: Edward Welbourne Reviewed-by: Timur Pocheptsov --- src/network/ssl/qsslsocket_schannel.cpp | 32 +++++++++++++++++++++++++ src/network/ssl/qsslsocket_schannel_p.h | 1 + 2 files changed, 33 insertions(+) diff --git a/src/network/ssl/qsslsocket_schannel.cpp b/src/network/ssl/qsslsocket_schannel.cpp index 3166cda4c9..78d807106b 100644 --- a/src/network/ssl/qsslsocket_schannel.cpp +++ b/src/network/ssl/qsslsocket_schannel.cpp @@ -467,6 +467,17 @@ void retainExtraData(QByteArray &buffer, const SecBuffer &secBuffer) buffer.resize(secBuffer.cbBuffer); } +qint64 checkIncompleteData(const SecBuffer &secBuffer) +{ + if (secBuffer.BufferType == SECBUFFER_MISSING) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcSsl, "Need %lu more bytes.", secBuffer.cbBuffer); +#endif + return secBuffer.cbBuffer; +} + return 0; +} + } // anonymous namespace bool QSslSocketPrivate::s_loadRootCertsOnDemand = true; @@ -810,6 +821,10 @@ bool QSslSocketBackendPrivate::acceptContext() Q_ASSERT(mode == QSslSocket::SslServerMode); ULONG contextReq = getContextRequirements(); + if (missingData > plainSocket->bytesAvailable()) + return true; + + missingData = 0; readToBuffer(intermediateBuffer, plainSocket); if (intermediateBuffer.isEmpty()) return true; // definitely need more data.. @@ -866,6 +881,7 @@ bool QSslSocketBackendPrivate::acceptContext() if (status == SEC_E_INCOMPLETE_MESSAGE) { // Need more data + missingData = checkIncompleteData(outBuffers[0]); return true; } @@ -905,6 +921,10 @@ bool QSslSocketBackendPrivate::performHandshake() qCDebug(lcSsl, "intermediateBuffer size: %d", intermediateBuffer.size()); #endif + if (missingData > plainSocket->bytesAvailable()) + return true; + + missingData = 0; readToBuffer(intermediateBuffer, plainSocket); if (intermediateBuffer.isEmpty()) return true; // no data, will fail @@ -990,6 +1010,7 @@ bool QSslSocketBackendPrivate::performHandshake() return true; case SEC_E_INCOMPLETE_MESSAGE: // Simply incomplete, wait for more data + missingData = checkIncompleteData(outBuffers[0]); return true; case SEC_E_ALGORITHM_MISMATCH: setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, @@ -1192,6 +1213,8 @@ void QSslSocketBackendPrivate::reset() connectionEncrypted = false; shutdown = false; renegotiating = false; + + missingData = 0; } void QSslSocketBackendPrivate::startClientEncryption() @@ -1288,6 +1311,14 @@ void QSslSocketBackendPrivate::transmit() int totalRead = 0; bool hadIncompleteData = false; while (!readBufferMaxSize || buffer.size() < readBufferMaxSize) { + if (missingData > plainSocket->bytesAvailable()) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcSsl, "We're still missing %lld bytes, will check later.", missingData); +#endif + break; + } + + missingData = 0; const qint64 bytesRead = readToBuffer(intermediateBuffer, plainSocket); #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl, "Read %lld encrypted bytes from the socket", bytesRead); @@ -1341,6 +1372,7 @@ void QSslSocketBackendPrivate::transmit() } if (status == SEC_E_INCOMPLETE_MESSAGE) { + missingData = checkIncompleteData(dataBuffer[0]); #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl, "We didn't have enough data to decrypt anything, will try again!"); #endif diff --git a/src/network/ssl/qsslsocket_schannel_p.h b/src/network/ssl/qsslsocket_schannel_p.h index 6ab200e1f9..a184deef49 100644 --- a/src/network/ssl/qsslsocket_schannel_p.h +++ b/src/network/ssl/qsslsocket_schannel_p.h @@ -145,6 +145,7 @@ private: const CERT_CONTEXT *localCertContext = nullptr; ULONG contextAttributes = 0; + qint64 missingData = 0; bool renegotiating = false; }; From b61c6164c100defc519b178d73858df59cffc48d Mon Sep 17 00:00:00 2001 From: Hernan Martinez Date: Sat, 12 Oct 2019 02:40:29 -0400 Subject: [PATCH 28/47] QtGui: Disable Windows on ARM64 preprocessor conflict in QtOpenGL The Windows API MemoryBarrier function is actually a macro when _M_ARM64 is defined and it conflicts with the MemoryBarrier method when it's declared and used. Task-number: QTBUG-77388 Change-Id: I762edfc4ca1a44cbe095724de708c7cdad34ae65 Reviewed-by: Laszlo Agocs --- src/gui/opengl/qopenglfunctions_4_2_compatibility.h | 10 ++++++++++ src/gui/opengl/qopenglfunctions_4_2_core.h | 10 ++++++++++ src/gui/opengl/qopenglfunctions_4_3_compatibility.h | 10 ++++++++++ src/gui/opengl/qopenglfunctions_4_3_core.h | 11 +++++++++++ src/gui/opengl/qopenglfunctions_4_4_compatibility.h | 10 ++++++++++ src/gui/opengl/qopenglfunctions_4_4_core.h | 10 ++++++++++ src/gui/opengl/qopenglfunctions_4_5_compatibility.h | 10 ++++++++++ src/gui/opengl/qopenglfunctions_4_5_core.h | 11 +++++++++++ src/gui/opengl/qopenglversionfunctions.h | 10 ++++++++++ src/openglextensions/qopenglextensions.cpp | 9 +++++++++ src/openglextensions/qopenglextensions.h | 10 ++++++++++ 11 files changed, 111 insertions(+) diff --git a/src/gui/opengl/qopenglfunctions_4_2_compatibility.h b/src/gui/opengl/qopenglfunctions_4_2_compatibility.h index 6726d5fc44..a48d581c2d 100644 --- a/src/gui/opengl/qopenglfunctions_4_2_compatibility.h +++ b/src/gui/opengl/qopenglfunctions_4_2_compatibility.h @@ -57,6 +57,12 @@ #include #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE class Q_GUI_EXPORT QOpenGLFunctions_4_2_Compatibility : public QAbstractOpenGLFunctions @@ -5632,6 +5638,10 @@ inline void QOpenGLFunctions_4_2_Compatibility::glVertexAttribI1i(GLuint index, QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL && !QT_OPENGL_ES_2 #endif diff --git a/src/gui/opengl/qopenglfunctions_4_2_core.h b/src/gui/opengl/qopenglfunctions_4_2_core.h index a921329741..5ca98e9808 100644 --- a/src/gui/opengl/qopenglfunctions_4_2_core.h +++ b/src/gui/opengl/qopenglfunctions_4_2_core.h @@ -57,6 +57,12 @@ #include #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE class Q_GUI_EXPORT QOpenGLFunctions_4_2_Core : public QAbstractOpenGLFunctions @@ -3027,6 +3033,10 @@ inline void QOpenGLFunctions_4_2_Core::glDrawArraysInstancedBaseInstance(GLenum QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL && !QT_OPENGL_ES_2 #endif diff --git a/src/gui/opengl/qopenglfunctions_4_3_compatibility.h b/src/gui/opengl/qopenglfunctions_4_3_compatibility.h index b9d4eb1d6f..d969f5b3b4 100644 --- a/src/gui/opengl/qopenglfunctions_4_3_compatibility.h +++ b/src/gui/opengl/qopenglfunctions_4_3_compatibility.h @@ -57,6 +57,12 @@ #include #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE class Q_GUI_EXPORT QOpenGLFunctions_4_3_Compatibility : public QAbstractOpenGLFunctions @@ -5839,6 +5845,10 @@ inline void QOpenGLFunctions_4_3_Compatibility::glVertexAttribI1i(GLuint index, QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL && !QT_OPENGL_ES_2 #endif diff --git a/src/gui/opengl/qopenglfunctions_4_3_core.h b/src/gui/opengl/qopenglfunctions_4_3_core.h index da552d64af..13675caf62 100644 --- a/src/gui/opengl/qopenglfunctions_4_3_core.h +++ b/src/gui/opengl/qopenglfunctions_4_3_core.h @@ -57,6 +57,13 @@ #include #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + + QT_BEGIN_NAMESPACE class Q_GUI_EXPORT QOpenGLFunctions_4_3_Core : public QAbstractOpenGLFunctions @@ -3230,6 +3237,10 @@ inline void QOpenGLFunctions_4_3_Core::glClearBufferData(GLenum target, GLenum i QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL && !QT_OPENGL_ES_2 #endif diff --git a/src/gui/opengl/qopenglfunctions_4_4_compatibility.h b/src/gui/opengl/qopenglfunctions_4_4_compatibility.h index 7a05bd802d..0acab349a1 100644 --- a/src/gui/opengl/qopenglfunctions_4_4_compatibility.h +++ b/src/gui/opengl/qopenglfunctions_4_4_compatibility.h @@ -59,6 +59,12 @@ QT_BEGIN_NAMESPACE +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + class Q_GUI_EXPORT QOpenGLFunctions_4_4_Compatibility : public QAbstractOpenGLFunctions { public: @@ -5961,6 +5967,10 @@ inline void QOpenGLFunctions_4_4_Compatibility::glVertexP2ui(GLenum type, GLuint QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL && !QT_OPENGL_ES_2 #endif diff --git a/src/gui/opengl/qopenglfunctions_4_4_core.h b/src/gui/opengl/qopenglfunctions_4_4_core.h index 6b29a9659b..1ad6f40214 100644 --- a/src/gui/opengl/qopenglfunctions_4_4_core.h +++ b/src/gui/opengl/qopenglfunctions_4_4_core.h @@ -57,6 +57,12 @@ #include #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE class Q_GUI_EXPORT QOpenGLFunctions_4_4_Core : public QAbstractOpenGLFunctions @@ -3415,6 +3421,10 @@ inline void QOpenGLFunctions_4_4_Core::glBufferStorage(GLenum target, GLsizeiptr QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL && !QT_OPENGL_ES_2 #endif diff --git a/src/gui/opengl/qopenglfunctions_4_5_compatibility.h b/src/gui/opengl/qopenglfunctions_4_5_compatibility.h index a809c1c90b..9d9d14548b 100644 --- a/src/gui/opengl/qopenglfunctions_4_5_compatibility.h +++ b/src/gui/opengl/qopenglfunctions_4_5_compatibility.h @@ -57,6 +57,12 @@ #include #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE class Q_GUI_EXPORT QOpenGLFunctions_4_5_Compatibility : public QAbstractOpenGLFunctions @@ -6679,6 +6685,10 @@ inline void QOpenGLFunctions_4_5_Compatibility::glGetnMapdv(GLenum target, GLenu QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL && !QT_OPENGL_ES_2 #endif diff --git a/src/gui/opengl/qopenglfunctions_4_5_core.h b/src/gui/opengl/qopenglfunctions_4_5_core.h index bb1b17f7b1..bf872c628b 100644 --- a/src/gui/opengl/qopenglfunctions_4_5_core.h +++ b/src/gui/opengl/qopenglfunctions_4_5_core.h @@ -57,6 +57,12 @@ #include #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE class Q_GUI_EXPORT QOpenGLFunctions_4_5_Core : public QAbstractOpenGLFunctions @@ -4056,6 +4062,11 @@ inline void QOpenGLFunctions_4_5_Core::glClipControl(GLenum origin, GLenum depth QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + + #endif // QT_NO_OPENGL && !QT_OPENGL_ES_2 #endif diff --git a/src/gui/opengl/qopenglversionfunctions.h b/src/gui/opengl/qopenglversionfunctions.h index f828e5668b..99c8565fbc 100644 --- a/src/gui/opengl/qopenglversionfunctions.h +++ b/src/gui/opengl/qopenglversionfunctions.h @@ -61,6 +61,12 @@ #include #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE class QOpenGLContext; @@ -1897,6 +1903,10 @@ public: QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL #endif diff --git a/src/openglextensions/qopenglextensions.cpp b/src/openglextensions/qopenglextensions.cpp index 6413ae4a78..1660181e97 100644 --- a/src/openglextensions/qopenglextensions.cpp +++ b/src/openglextensions/qopenglextensions.cpp @@ -60,6 +60,12 @@ #include "qopenglextensions.h" #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE QAbstractOpenGLExtension::~QAbstractOpenGLExtension() @@ -7720,3 +7726,6 @@ bool QOpenGLExtension_QCOM_tiled_rendering::initializeOpenGLFunctions() QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif diff --git a/src/openglextensions/qopenglextensions.h b/src/openglextensions/qopenglextensions.h index 439e0e6530..eb473f3699 100644 --- a/src/openglextensions/qopenglextensions.h +++ b/src/openglextensions/qopenglextensions.h @@ -66,6 +66,12 @@ #include +// MemoryBarrier is a macro on some architectures on Windows +#ifdef Q_OS_WIN +#pragma push_macro("MemoryBarrier") +#undef MemoryBarrier +#endif + QT_BEGIN_NAMESPACE class QOpenGLContext; @@ -19473,6 +19479,10 @@ inline void QOpenGLExtension_QCOM_tiled_rendering::glEndTilingQCOM(GLbitfield pr QT_END_NAMESPACE +#ifdef Q_OS_WIN +#pragma pop_macro("MemoryBarrier") +#endif + #endif // QT_NO_OPENGL #endif From 35adb74ddd915831789f0175423660f8e898942e Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 19 Jun 2019 13:43:00 +0200 Subject: [PATCH 29/47] Reimplement JSON support on top of Cbor In turn, deprecate the QJsonDocument methods that deal with JSON binary data. You should use CBOR for data serialization these days. [ChangeLog][Deprecation Notice] The binary JSON representation is deprecated. The CBOR format should be used instead. Fixes: QTBUG-47629 Change-Id: Ic8b92ea36de87815b12307a9d8b1095f07166db8 Reviewed-by: Thiago Macieira --- qmake/Makefile.unix | 10 +- qmake/Makefile.win32 | 3 +- qmake/qmake.pro | 5 +- src/corelib/configure.json | 6 + src/corelib/global/qconfig-bootstrapped.h | 1 + src/corelib/serialization/qbinaryjson.cpp | 415 +++++++++ src/corelib/serialization/qbinaryjson_p.h | 617 +++++++++++++ .../serialization/qbinaryjsonarray.cpp | 137 +++ .../serialization/qbinaryjsonarray_p.h | 98 +++ .../serialization/qbinaryjsonobject.cpp | 149 ++++ .../serialization/qbinaryjsonobject_p.h | 97 ++ .../serialization/qbinaryjsonvalue.cpp | 155 ++++ .../serialization/qbinaryjsonvalue_p.h | 134 +++ src/corelib/serialization/qcborvalue.cpp | 2 + src/corelib/serialization/qcborvalue.h | 4 + src/corelib/serialization/qjson.cpp | 451 ---------- src/corelib/serialization/qjson_p.h | 829 +++--------------- src/corelib/serialization/qjsonarray.cpp | 237 ++--- src/corelib/serialization/qjsonarray.h | 26 +- src/corelib/serialization/qjsoncbor.cpp | 134 +-- src/corelib/serialization/qjsondocument.cpp | 358 ++++---- src/corelib/serialization/qjsondocument.h | 27 +- src/corelib/serialization/qjsonobject.cpp | 320 +++---- src/corelib/serialization/qjsonobject.h | 29 +- src/corelib/serialization/qjsonparser.cpp | 453 ++++------ src/corelib/serialization/qjsonparser_p.h | 51 +- src/corelib/serialization/qjsonvalue.cpp | 291 +++--- src/corelib/serialization/qjsonvalue.h | 54 +- src/corelib/serialization/qjsonwriter.cpp | 75 +- src/corelib/serialization/qjsonwriter_p.h | 4 +- src/corelib/serialization/serialization.pri | 15 +- src/tools/bootstrap/bootstrap.pro | 3 +- 32 files changed, 2843 insertions(+), 2347 deletions(-) create mode 100644 src/corelib/serialization/qbinaryjson.cpp create mode 100644 src/corelib/serialization/qbinaryjson_p.h create mode 100644 src/corelib/serialization/qbinaryjsonarray.cpp create mode 100644 src/corelib/serialization/qbinaryjsonarray_p.h create mode 100644 src/corelib/serialization/qbinaryjsonobject.cpp create mode 100644 src/corelib/serialization/qbinaryjsonobject_p.h create mode 100644 src/corelib/serialization/qbinaryjsonvalue.cpp create mode 100644 src/corelib/serialization/qbinaryjsonvalue_p.h delete mode 100644 src/corelib/serialization/qjson.cpp diff --git a/qmake/Makefile.unix b/qmake/Makefile.unix index 4d4f05e78a..da0fccb834 100644 --- a/qmake/Makefile.unix +++ b/qmake/Makefile.unix @@ -24,7 +24,7 @@ QOBJS = \ qfile.o qfiledevice.o qfileinfo.o qfilesystemengine.o \ qfilesystementry.o qfsfileengine.o qfsfileengine_iterator.o \ qiodevice.o qsettings.o qtemporaryfile.o qtextstream.o \ - qjsonarray.o qjson.o qjsondocument.o qjsonobject.o qjsonparser.o qjsonvalue.o \ + qcborvalue.o qjsoncbor.o qjsonarray.o qjsondocument.o qjsonobject.o qjsonparser.o qjsonvalue.o \ qmetatype.o qsystemerror.o qvariant.o \ quuid.o \ qarraydata.o qbitarray.o qbytearray.o qbytearraylist.o qbytearraymatcher.o \ @@ -96,9 +96,10 @@ DEPEND_SRC = \ $(SOURCE_PATH)/src/corelib/kernel/qsystemerror.cpp \ $(SOURCE_PATH)/src/corelib/kernel/qvariant.cpp \ $(SOURCE_PATH)/src/corelib/plugin/quuid.cpp \ + $(SOURCE_PATH)/src/corelib/serialization/qcborvalue.cpp \ $(SOURCE_PATH)/src/corelib/serialization/qdatastream.cpp \ $(SOURCE_PATH)/src/corelib/serialization/qjsonarray.cpp \ - $(SOURCE_PATH)/src/corelib/serialization/qjson.cpp \ + $(SOURCE_PATH)/src/corelib/serialization/qjsoncbor.cpp \ $(SOURCE_PATH)/src/corelib/serialization/qjsondocument.cpp \ $(SOURCE_PATH)/src/corelib/serialization/qjsonobject.cpp \ $(SOURCE_PATH)/src/corelib/serialization/qjsonparser.cpp \ @@ -466,7 +467,10 @@ qsystemlibrary.o: $(SOURCE_PATH)/src/corelib/plugin/qsystemlibrary.cpp qdatastream.o: $(SOURCE_PATH)/src/corelib/serialization/qdatastream.cpp $(CXX) -c -o $@ $(CXXFLAGS) $< -qjson.o: $(SOURCE_PATH)/src/corelib/serialization/qjson.cpp +qcborvalue.o: $(SOURCE_PATH)/src/corelib/serialization/qcborvalue.cpp + $(CXX) -c -o $@ $(CXXFLAGS) $< + +qjsoncbor.o: $(SOURCE_PATH)/src/corelib/serialization/qjsoncbor.cpp $(CXX) -c -o $@ $(CXXFLAGS) $< qjsondocument.o: $(SOURCE_PATH)/src/corelib/serialization/qjsondocument.cpp diff --git a/qmake/Makefile.win32 b/qmake/Makefile.win32 index 7324817af2..74fb80e337 100644 --- a/qmake/Makefile.win32 +++ b/qmake/Makefile.win32 @@ -118,7 +118,8 @@ QTOBJS= \ qxmlutils.obj \ qnumeric.obj \ qlogging.obj \ - qjson.obj \ + qcborvalue.obj \ + qjsoncbor.obj \ qjsondocument.obj \ qjsonparser.obj \ qjsonarray.obj \ diff --git a/qmake/qmake.pro b/qmake/qmake.pro index a9d8b58da8..42c727b33e 100644 --- a/qmake/qmake.pro +++ b/qmake/qmake.pro @@ -116,6 +116,7 @@ SOURCES += \ qbytearray.cpp \ qbytearraymatcher.cpp \ qcalendar.cpp \ + qcborvalue.cpp \ qcryptographichash.cpp \ qdatetime.cpp \ qdir.cpp \ @@ -131,8 +132,8 @@ SOURCES += \ qgregoriancalendar.cpp \ qhash.cpp \ qiodevice.cpp \ - qjson.cpp \ qjsonarray.cpp \ + qjsoncbor.cpp \ qjsondocument.cpp \ qjsonobject.cpp \ qjsonparser.cpp \ @@ -174,6 +175,8 @@ HEADERS += \ qcalendar.h \ qcalendarbackend_p.h \ qcalendarmath_p.h \ + qcborvalue.h \ + qcborvalue_p.h \ qchar.h \ qcryptographichash.h \ qdatetime.h \ diff --git a/src/corelib/configure.json b/src/corelib/configure.json index b4b7c4eec3..04643aec33 100644 --- a/src/corelib/configure.json +++ b/src/corelib/configure.json @@ -1094,6 +1094,12 @@ Mozilla License) is included. The data is then also used in QNetworkCookieJar::v Note that this is required for plugin loading. Qt GUI needs QPA plugins for basic operation.", "section": "Utilities", "output": [ "publicFeature" ] + }, + "binaryjson": { + "label": "Binary JSON (deprecated)", + "purpose": "Provides support for the deprecated binary JSON format.", + "section": "Utilities", + "output": [ "publicFeature" ] } }, diff --git a/src/corelib/global/qconfig-bootstrapped.h b/src/corelib/global/qconfig-bootstrapped.h index e9383ca68b..81bf6f95ce 100644 --- a/src/corelib/global/qconfig-bootstrapped.h +++ b/src/corelib/global/qconfig-bootstrapped.h @@ -74,6 +74,7 @@ #else # define QT_FEATURE_alloca_malloc_h -1 #endif +#define QT_FEATURE_binaryjson -1 #define QT_FEATURE_cborstream -1 #define QT_CRYPTOGRAPHICHASH_ONLY_SHA1 #define QT_FEATURE_cxx11_random (QT_HAS_INCLUDE() ? 1 : -1) diff --git a/src/corelib/serialization/qbinaryjson.cpp b/src/corelib/serialization/qbinaryjson.cpp new file mode 100644 index 0000000000..3d359f0998 --- /dev/null +++ b/src/corelib/serialization/qbinaryjson.cpp @@ -0,0 +1,415 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbinaryjson_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QBinaryJsonPrivate { + +static Q_CONSTEXPR Base emptyArray = { + { qle_uint(sizeof(Base)) }, + { 0 }, + { qle_uint(0) } +}; + +static Q_CONSTEXPR Base emptyObject = { + { qle_uint(sizeof(Base)) }, + { qToLittleEndian(1U) }, + { qle_uint(0) } +}; + +void MutableData::compact() +{ + Q_STATIC_ASSERT(sizeof(Value) == sizeof(offset)); + + Base *base = header->root(); + int reserve = 0; + if (base->is_object) { + auto *o = static_cast(base); + for (uint i = 0; i < o->length; ++i) + reserve += o->entryAt(i)->usedStorage(o); + } else { + auto *a = static_cast(base); + for (uint i = 0; i < a->length; ++i) + reserve += a->at(i)->usedStorage(a); + } + + uint size = sizeof(Base) + reserve + base->length * sizeof(offset); + uint alloc = sizeof(Header) + size; + auto *h = reinterpret_cast
(malloc(alloc)); + Q_CHECK_PTR(h); + h->tag = QJsonDocument::BinaryFormatTag; + h->version = 1; + Base *b = h->root(); + b->size = size; + b->is_object = header->root()->is_object; + b->length = base->length; + b->tableOffset = reserve + sizeof(Array); + + uint offset = sizeof(Base); + if (b->is_object) { + const auto *o = static_cast(base); + auto *no = static_cast(b); + + for (uint i = 0; i < o->length; ++i) { + no->table()[i] = offset; + + const Entry *e = o->entryAt(i); + Entry *ne = no->entryAt(i); + uint s = e->size(); + memcpy(ne, e, s); + offset += s; + uint dataSize = e->value.usedStorage(o); + if (dataSize) { + memcpy(reinterpret_cast(no) + offset, e->value.data(o), dataSize); + ne->value.value = offset; + offset += dataSize; + } + } + } else { + const auto *a = static_cast(base); + auto *na = static_cast(b); + + for (uint i = 0; i < a->length; ++i) { + const Value *v = a->at(i); + Value *nv = na->at(i); + *nv = *v; + uint dataSize = v->usedStorage(a); + if (dataSize) { + memcpy(reinterpret_cast(na) + offset, v->data(a), dataSize); + nv->value = offset; + offset += dataSize; + } + } + } + Q_ASSERT(offset == uint(b->tableOffset)); + + free(header); + header = h; + this->alloc = alloc; + compactionCounter = 0; +} + +bool ConstData::isValid() const +{ + if (header->tag != QJsonDocument::BinaryFormatTag || header->version != 1U) + return false; + + const Base *root = header->root(); + const uint maxSize = alloc - sizeof(Header); + return root->is_object + ? static_cast(root)->isValid(maxSize) + : static_cast(root)->isValid(maxSize); +} + +QJsonDocument ConstData::toJsonDocument() const +{ + const Base *root = header->root(); + return root->is_object + ? QJsonDocument(static_cast(root)->toJsonObject()) + : QJsonDocument(static_cast(root)->toJsonArray()); +} + +uint Base::reserveSpace(uint dataSize, uint posInTable, uint numItems, bool replace) +{ + Q_ASSERT(posInTable <= length); + if (size + dataSize >= Value::MaxSize) { + qWarning("QJson: Document too large to store in data structure %d %d %d", + uint(size), dataSize, Value::MaxSize); + return 0; + } + + offset off = tableOffset; + // move table to new position + if (replace) { + memmove(reinterpret_cast(table()) + dataSize, table(), length * sizeof(offset)); + } else { + memmove(reinterpret_cast(table() + posInTable + numItems) + dataSize, + table() + posInTable, (length - posInTable) * sizeof(offset)); + memmove(reinterpret_cast(table()) + dataSize, table(), posInTable * sizeof(offset)); + } + tableOffset += dataSize; + for (uint i = 0; i < numItems; ++i) + table()[posInTable + i] = off; + size += dataSize; + if (!replace) { + length += numItems; + size += numItems * sizeof(offset); + } + return off; +} + +uint Object::indexOf(QStringView key, bool *exists) const +{ + uint min = 0; + uint n = length; + while (n > 0) { + uint half = n >> 1; + uint middle = min + half; + if (*entryAt(middle) >= key) { + n = half; + } else { + min = middle + 1; + n -= half + 1; + } + } + if (min < length && *entryAt(min) == key) { + *exists = true; + return min; + } + *exists = false; + return min; +} + +QJsonObject Object::toJsonObject() const +{ + QJsonObject object; + for (uint i = 0; i < length; ++i) { + const Entry *e = entryAt(i); + object.insert(e->key(), e->value.toJsonValue(this)); + } + return object; +} + +bool Object::isValid(uint maxSize) const +{ + if (size > maxSize || tableOffset + length * sizeof(offset) > size) + return false; + + QString lastKey; + for (uint i = 0; i < length; ++i) { + if (table()[i] + sizeof(Entry) >= tableOffset) + return false; + const Entry *e = entryAt(i); + if (!e->isValid(tableOffset - table()[i])) + return false; + const QString key = e->key(); + if (key < lastKey) + return false; + if (!e->value.isValid(this)) + return false; + lastKey = key; + } + return true; +} + +QJsonArray Array::toJsonArray() const +{ + QJsonArray array; + const offset *values = table(); + for (uint i = 0; i < length; ++i) + array.append(reinterpret_cast(values + i)->toJsonValue(this)); + return array; +} + +bool Array::isValid(uint maxSize) const +{ + if (size > maxSize || tableOffset + length * sizeof(offset) > size) + return false; + + const offset *values = table(); + for (uint i = 0; i < length; ++i) { + if (!reinterpret_cast(values + i)->isValid(this)) + return false; + } + return true; +} + +uint Value::usedStorage(const Base *b) const +{ + uint s = 0; + switch (type) { + case QJsonValue::Double: + if (!latinOrIntValue) + s = sizeof(double); + break; + case QJsonValue::String: { + const char *d = data(b); + s = latinOrIntValue + ? (sizeof(ushort) + + qFromLittleEndian(*reinterpret_cast(d))) + : (sizeof(int) + + sizeof(ushort) * qFromLittleEndian(*reinterpret_cast(d))); + break; + } + case QJsonValue::Array: + case QJsonValue::Object: + s = base(b)->size; + break; + case QJsonValue::Null: + case QJsonValue::Bool: + default: + break; + } + return alignedSize(s); +} + +QJsonValue Value::toJsonValue(const Base *b) const +{ + switch (type) { + case QJsonValue::Null: + return QJsonValue(QJsonValue::Null); + case QJsonValue::Bool: + return QJsonValue(toBoolean()); + case QJsonValue::Double: + return QJsonValue(toDouble(b)); + case QJsonValue::String: + return QJsonValue(toString(b)); + case QJsonValue::Array: + return static_cast(base(b))->toJsonArray(); + case QJsonValue::Object: + return static_cast(base(b))->toJsonObject(); + case QJsonValue::Undefined: + return QJsonValue(QJsonValue::Undefined); + } + Q_UNREACHABLE(); + return QJsonValue(QJsonValue::Undefined); +} + +inline bool isValidValueOffset(uint offset, uint tableOffset) +{ + return offset >= sizeof(Base) + && offset + sizeof(uint) <= tableOffset; +} + +bool Value::isValid(const Base *b) const +{ + switch (type) { + case QJsonValue::Null: + case QJsonValue::Bool: + return true; + case QJsonValue::Double: + return latinOrIntValue || isValidValueOffset(value, b->tableOffset); + case QJsonValue::String: + if (!isValidValueOffset(value, b->tableOffset)) + return false; + if (latinOrIntValue) + return asLatin1String(b).isValid(b->tableOffset - value); + return asString(b).isValid(b->tableOffset - value); + case QJsonValue::Array: + return isValidValueOffset(value, b->tableOffset) + && static_cast(base(b))->isValid(b->tableOffset - value); + case QJsonValue::Object: + return isValidValueOffset(value, b->tableOffset) + && static_cast(base(b))->isValid(b->tableOffset - value); + default: + return false; + } +} + +uint Value::requiredStorage(const QBinaryJsonValue &v, bool *compressed) +{ + *compressed = false; + switch (v.type()) { + case QJsonValue::Double: + if (QBinaryJsonPrivate::compressedNumber(v.toDouble()) != INT_MAX) { + *compressed = true; + return 0; + } + return sizeof(double); + case QJsonValue::String: { + QString s = v.toString(); + *compressed = QBinaryJsonPrivate::useCompressed(s); + return QBinaryJsonPrivate::qStringSize(s, *compressed); + } + case QJsonValue::Array: + case QJsonValue::Object: + return v.base ? uint(v.base->size) : sizeof(QBinaryJsonPrivate::Base); + case QJsonValue::Undefined: + case QJsonValue::Null: + case QJsonValue::Bool: + break; + } + return 0; +} + +uint Value::valueToStore(const QBinaryJsonValue &v, uint offset) +{ + switch (v.type()) { + case QJsonValue::Undefined: + case QJsonValue::Null: + break; + case QJsonValue::Bool: + return v.toBool(); + case QJsonValue::Double: { + int c = QBinaryJsonPrivate::compressedNumber(v.toDouble()); + if (c != INT_MAX) + return c; + } + Q_FALLTHROUGH(); + case QJsonValue::String: + case QJsonValue::Array: + case QJsonValue::Object: + return offset; + } + return 0; +} + +void Value::copyData(const QBinaryJsonValue &v, char *dest, bool compressed) +{ + switch (v.type()) { + case QJsonValue::Double: + if (!compressed) + qToLittleEndian(v.toDouble(), dest); + break; + case QJsonValue::String: { + const QString str = v.toString(); + QBinaryJsonPrivate::copyString(dest, str, compressed); + break; + } + case QJsonValue::Array: + case QJsonValue::Object: { + const QBinaryJsonPrivate::Base *b = v.base; + if (!b) + b = (v.type() == QJsonValue::Array ? &emptyArray : &emptyObject); + memcpy(dest, b, b->size); + break; + } + default: + break; + } +} + +} // namespace QBinaryJsonPrivate + +QT_END_NAMESPACE diff --git a/src/corelib/serialization/qbinaryjson_p.h b/src/corelib/serialization/qbinaryjson_p.h new file mode 100644 index 0000000000..132c36f227 --- /dev/null +++ b/src/corelib/serialization/qbinaryjson_p.h @@ -0,0 +1,617 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBINARYJSON_P_H +#define QBINARYJSON_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +#include + +#include + +QT_REQUIRE_CONFIG(binaryjson); + +QT_BEGIN_NAMESPACE + +// in qstring.cpp +void qt_to_latin1_unchecked(uchar *dst, const ushort *uc, qsizetype len); +void qt_from_latin1(ushort *dst, const char *str, size_t size) noexcept; + +/* + This defines a binary data structure for Json data. The data structure is optimised for fast reading + and minimum allocations. The whole data structure can be mmap'ed and used directly. + + In most cases the binary structure is not as space efficient as a utf8 encoded text representation, but + much faster to access. + + The size requirements are: + + String: + Latin1 data: 2 bytes header + string.length() + Full Unicode: 4 bytes header + 2*(string.length()) + + Values: 4 bytes + size of data (size can be 0 for some data) + bool: 0 bytes + double: 8 bytes (0 if integer with less than 27bits) + string: see above + array: size of array + object: size of object + Array: 12 bytes + 4*length + size of Value data + Object: 12 bytes + 8*length + size of Key Strings + size of Value data + + For an example such as + + { // object: 12 + 5*8 = 52 + "firstName": "John", // key 12, value 8 = 20 + "lastName" : "Smith", // key 12, value 8 = 20 + "age" : 25, // key 8, value 0 = 8 + "address" : // key 12, object below = 140 + { // object: 12 + 4*8 + "streetAddress": "21 2nd Street", // key 16, value 16 + "city" : "New York", // key 8, value 12 + "state" : "NY", // key 8, value 4 + "postalCode" : "10021" // key 12, value 8 + }, // object total: 128 + "phoneNumber": // key: 16, value array below = 172 + [ // array: 12 + 2*4 + values below: 156 + { // object 12 + 2*8 + "type" : "home", // key 8, value 8 + "number": "212 555-1234" // key 8, value 16 + }, // object total: 68 + { // object 12 + 2*8 + "type" : "fax", // key 8, value 8 + "number": "646 555-4567" // key 8, value 16 + } // object total: 68 + ] // array total: 156 + } // great total: 412 bytes + + The uncompressed text file used roughly 500 bytes, so in this case we end up using about + the same space as the text representation. + + Other measurements have shown a slightly bigger binary size than a compact text + representation where all possible whitespace was stripped out. +*/ +namespace QBinaryJsonPrivate { + +class Array; +class Object; +class Value; +class Entry; + +template +using q_littleendian = QLEInteger; + +using qle_short = q_littleendian; +using qle_ushort = q_littleendian; +using qle_int = q_littleendian; +using qle_uint = q_littleendian; + +template +using qle_bitfield = QLEIntegerBitfield; + +template +using qle_signedbitfield = QLEIntegerBitfield; + +using offset = qle_uint; + +// round the size up to the next 4 byte boundary +inline uint alignedSize(uint size) { return (size + 3) & ~3; } + +const int MaxLatin1Length = 0x7fff; + +static inline bool useCompressed(QStringView s) +{ + if (s.length() > MaxLatin1Length) + return false; + return QtPrivate::isLatin1(s); +} + +static inline bool useCompressed(QLatin1String s) +{ + return s.size() <= MaxLatin1Length; +} + +static inline uint qStringSize(const QString &string, bool compress) +{ + uint l = 2 + string.size(); + if (!compress) + l *= 2; + return alignedSize(l); +} + +// returns INT_MAX if it can't compress it into 28 bits +static inline int compressedNumber(double d) +{ + // this relies on details of how ieee floats are represented + const int exponent_off = 52; + const quint64 fraction_mask = 0x000fffffffffffffULL; + const quint64 exponent_mask = 0x7ff0000000000000ULL; + + quint64 val; + memcpy (&val, &d, sizeof(double)); + int exp = (int)((val & exponent_mask) >> exponent_off) - 1023; + if (exp < 0 || exp > 25) + return std::numeric_limits::max(); + + quint64 non_int = val & (fraction_mask >> exp); + if (non_int) + return std::numeric_limits::max(); + + bool neg = (val >> 63) != 0; + val &= fraction_mask; + val |= ((quint64)1 << 52); + int res = (int)(val >> (52 - exp)); + return neg ? -res : res; +} + +class Latin1String; + +class String +{ +public: + explicit String(const char *data) : d(reinterpret_cast(data)) {} + + struct Data { + qle_uint length; + qle_ushort utf16[1]; + }; + const Data *d; + + uint byteSize() const { return sizeof(uint) + sizeof(ushort) * d->length; } + bool isValid(uint maxSize) const + { + // Check byteSize() <= maxSize, avoiding integer overflow + return maxSize >= sizeof(uint) + && uint(d->length) <= (maxSize - sizeof(uint)) / sizeof(ushort); + } + + static void copy(char *dest, QStringView str) + { + Data *data = reinterpret_cast(dest); + data->length = str.length(); + qToLittleEndian(str.utf16(), str.length(), data->utf16); + fillTrailingZeros(data); + } + + static void fillTrailingZeros(Data *data) + { + if (data->length & 1) + data->utf16[data->length] = 0; + } + + bool operator ==(QStringView str) const + { + int slen = str.length(); + int l = d->length; + if (slen != l) + return false; + const auto *s = reinterpret_cast(str.utf16()); + const qle_ushort *a = d->utf16; + const ushort *b = s; + while (l-- && *a == *b) + a++,b++; + return (l == -1); + } + + bool operator ==(const String &str) const + { + if (d->length != str.d->length) + return false; + return !memcmp(d->utf16, str.d->utf16, d->length * sizeof(ushort)); + } + + QString toString() const + { +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + return QString(reinterpret_cast(d->utf16), d->length); +#else + const uint l = d->length; + QString str(l, Qt::Uninitialized); + QChar *ch = str.data(); + for (uint i = 0; i < l; ++i) + ch[i] = QChar(d->utf16[i]); + return str; +#endif + } +}; + +class Latin1String +{ +public: + explicit Latin1String(const char *data) : d(reinterpret_cast(data)) {} + + struct Data { + qle_ushort length; + char latin1[1]; + }; + const Data *d; + + uint byteSize() const { return sizeof(ushort) + sizeof(char) * (d->length); } + bool isValid(uint maxSize) const { return byteSize() <= maxSize; } + + static void copy(char *dest, QStringView src) + { + Data *data = reinterpret_cast(dest); + data->length = src.length(); + auto *l = reinterpret_cast(data->latin1); + const auto *uc = reinterpret_cast(src.utf16()); + qt_to_latin1_unchecked(l, uc, data->length); + + for (uint len = data->length; quintptr(l + len) & 0x3; ++len) + l[len] = 0; + } + + QLatin1String toQLatin1String() const noexcept { return QLatin1String(d->latin1, d->length); } + QString toString() const { return QString::fromLatin1(d->latin1, d->length); } +}; + +static inline void copyString(char *dest, QStringView str, bool compress) +{ + if (compress) + Latin1String::copy(dest, str); + else + String::copy(dest, str); +} + +/* + Base is the base class for both Object and Array. Both classes work more or less the same way. + The class starts with a header (defined by the struct below), then followed by data (the data for + values in the Array case and Entry's (see below) for objects. + + After the data a table follows (tableOffset points to it) containing Value objects for Arrays, and + offsets from the beginning of the object to Entry's in the case of Object. + + Entry's in the Object's table are lexicographically sorted by key in the table(). This allows the usage + of a binary search over the keys in an Object. + */ +class Base +{ +public: + qle_uint size; + union { + uint _dummy; + qle_bitfield<0, 1> is_object; + qle_bitfield<1, 31> length; + }; + offset tableOffset; + // content follows here + + bool isObject() const { return !!is_object; } + bool isArray() const { return !isObject(); } + + offset *table() + { + return reinterpret_cast(reinterpret_cast(this) + tableOffset); + } + + const offset *table() const + { + return reinterpret_cast(reinterpret_cast(this) + tableOffset); + } + + uint reserveSpace(uint dataSize, uint posInTable, uint numItems, bool replace); +}; + +class Object : public Base +{ +public: + const Entry *entryAt(uint i) const + { + return reinterpret_cast(reinterpret_cast(this) + table()[i]); + } + + Entry *entryAt(uint i) + { + return reinterpret_cast(reinterpret_cast(this) + table()[i]); + } + + uint indexOf(QStringView key, bool *exists) const; + QJsonObject toJsonObject() const; + bool isValid(uint maxSize) const; +}; + +class Array : public Base +{ +public: + const Value *at(uint i) const { return reinterpret_cast(table() + i); } + Value *at(uint i) { return reinterpret_cast(table() + i); } + + QJsonArray toJsonArray() const; + bool isValid(uint maxSize) const; +}; + +class Value +{ +public: + enum { + MaxSize = (1 << 27) - 1 + }; + union { + uint _dummy; + qle_bitfield<0, 3> type; + qle_bitfield<3, 1> latinOrIntValue; + qle_bitfield<4, 1> latinKey; + qle_bitfield<5, 27> value; + qle_signedbitfield<5, 27> int_value; + }; + + inline const char *data(const Base *b) const + { + return reinterpret_cast(b) + value; + } + + uint usedStorage(const Base *b) const; + + bool toBoolean() const + { + Q_ASSERT(type == QJsonValue::Bool); + return value != 0; + } + + double toDouble(const Base *b) const + { + Q_ASSERT(type == QJsonValue::Double); + if (latinOrIntValue) + return int_value; + + auto i = qFromLittleEndian(reinterpret_cast(b) + value); + double d; + memcpy(&d, &i, sizeof(double)); + return d; + } + + QString toString(const Base *b) const + { + return latinOrIntValue + ? asLatin1String(b).toString() + : asString(b).toString(); + } + + String asString(const Base *b) const + { + Q_ASSERT(type == QJsonValue::String && !latinOrIntValue); + return String(data(b)); + } + + Latin1String asLatin1String(const Base *b) const + { + Q_ASSERT(type == QJsonValue::String && latinOrIntValue); + return Latin1String(data(b)); + } + + const Base *base(const Base *b) const + { + Q_ASSERT(type == QJsonValue::Array || type == QJsonValue::Object); + return reinterpret_cast(data(b)); + } + + QJsonValue toJsonValue(const Base *b) const; + bool isValid(const Base *b) const; + + static uint requiredStorage(const QBinaryJsonValue &v, bool *compressed); + static uint valueToStore(const QBinaryJsonValue &v, uint offset); + static void copyData(const QBinaryJsonValue &v, char *dest, bool compressed); +}; + +class Entry { +public: + Value value; + // key + // value data follows key + + uint size() const + { + uint s = sizeof(Entry); + if (value.latinKey) + s += shallowLatin1Key().byteSize(); + else + s += shallowKey().byteSize(); + return alignedSize(s); + } + + uint usedStorage(Base *b) const + { + return size() + value.usedStorage(b); + } + + String shallowKey() const + { + Q_ASSERT(!value.latinKey); + return String(reinterpret_cast(this) + sizeof(Entry)); + } + + Latin1String shallowLatin1Key() const + { + Q_ASSERT(value.latinKey); + return Latin1String(reinterpret_cast(this) + sizeof(Entry)); + } + + QString key() const + { + return value.latinKey + ? shallowLatin1Key().toString() + : shallowKey().toString(); + } + + bool isValid(uint maxSize) const + { + if (maxSize < sizeof(Entry)) + return false; + maxSize -= sizeof(Entry); + return value.latinKey + ? shallowLatin1Key().isValid(maxSize) + : shallowKey().isValid(maxSize); + } + + bool operator ==(QStringView key) const + { + return value.latinKey + ? (shallowLatin1Key().toQLatin1String() == key) + : (shallowKey() == key); + } + + bool operator >=(QStringView key) const + { + return value.latinKey + ? (shallowLatin1Key().toQLatin1String() >= key) + : (shallowKey().toString() >= key); + } +}; + +class Header { +public: + qle_uint tag; // 'qbjs' + qle_uint version; // 1 + Base *root() { return reinterpret_cast(this + 1); } + const Base *root() const { return reinterpret_cast(this + 1); } +}; + +class ConstData +{ + Q_DISABLE_COPY_MOVE(ConstData) +public: + const uint alloc; + union { + const char *rawData; + const Header *header; + }; + + ConstData(const char *raw, uint a) : alloc(a), rawData(raw) {} + bool isValid() const; + QJsonDocument toJsonDocument() const; +}; + +class MutableData +{ + Q_DISABLE_COPY_MOVE(MutableData) +public: + QAtomicInt ref; + uint alloc; + union { + char *rawData; + Header *header; + }; + uint compactionCounter : 31; + + MutableData(char *raw, uint a) + : alloc(a), rawData(raw), compactionCounter(0) + { + } + + MutableData(uint reserved, QJsonValue::Type valueType) + : rawData(nullptr), compactionCounter(0) + { + Q_ASSERT(valueType == QJsonValue::Array || valueType == QJsonValue::Object); + + alloc = sizeof(Header) + sizeof(Base) + reserved + sizeof(offset); + header = reinterpret_cast
(malloc(alloc)); + Q_CHECK_PTR(header); + header->tag = QJsonDocument::BinaryFormatTag; + header->version = 1; + Base *b = header->root(); + b->size = sizeof(Base); + b->is_object = (valueType == QJsonValue::Object); + b->tableOffset = sizeof(Base); + b->length = 0; + } + + ~MutableData() + { + free(rawData); + } + + MutableData *clone(const Base *b, uint reserve = 0) + { + uint size = sizeof(Header) + b->size; + if (b == header->root() && ref.loadRelaxed() == 1 && alloc >= size + reserve) + return this; + + if (reserve) { + if (reserve < 128) + reserve = 128; + size = qMax(size + reserve, qMin(size *2, uint(Value::MaxSize))); + if (size > Value::MaxSize) { + qWarning("QJson: Document too large to store in data structure"); + return nullptr; + } + } + char *raw = reinterpret_cast(malloc(size)); + Q_CHECK_PTR(raw); + memcpy(raw + sizeof(Header), b, b->size); + auto *h = reinterpret_cast
(raw); + h->tag = QJsonDocument::BinaryFormatTag; + h->version = 1; + auto *d = new MutableData(raw, size); + d->compactionCounter = (b == header->root()) ? compactionCounter : 0; + return d; + } + + char *takeRawData(uint *size) + { + *size = alloc; + char *result = rawData; + rawData = nullptr; + alloc = 0; + return result; + } + + void compact(); +}; + +} // namespace QBinaryJsonPrivate + +Q_DECLARE_TYPEINFO(QBinaryJsonPrivate::Value, Q_PRIMITIVE_TYPE); + +QT_END_NAMESPACE + +#endif // QBINARYJSON_P_H diff --git a/src/corelib/serialization/qbinaryjsonarray.cpp b/src/corelib/serialization/qbinaryjsonarray.cpp new file mode 100644 index 0000000000..68937fe17d --- /dev/null +++ b/src/corelib/serialization/qbinaryjsonarray.cpp @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbinaryjsonarray_p.h" +#include "qbinaryjson_p.h" + +#include + +QT_BEGIN_NAMESPACE + +QBinaryJsonArray::~QBinaryJsonArray() +{ + if (d && !d->ref.deref()) + delete d; +} + +QBinaryJsonArray QBinaryJsonArray::fromJsonArray(const QJsonArray &array) +{ + QBinaryJsonArray binary; + for (const QJsonValue &value : array) + binary.append(QBinaryJsonValue::fromJsonValue(value)); + if (binary.d) // We want to compact it as it is a root item now + binary.d->compactionCounter++; + binary.compact(); + return binary; +} + +void QBinaryJsonArray::append(const QBinaryJsonValue &value) +{ + const uint i = a ? a->length : 0; + + bool compressed; + uint valueSize = QBinaryJsonPrivate::Value::requiredStorage(value, &compressed); + + if (!detach(valueSize + sizeof(QBinaryJsonPrivate::Value))) + return; + + if (!a->length) + a->tableOffset = sizeof(QBinaryJsonPrivate::Array); + + uint valueOffset = a->reserveSpace(valueSize, i, 1, false); + if (!valueOffset) + return; + + QBinaryJsonPrivate::Value *v = a->at(i); + v->type = (value.t == QJsonValue::Undefined ? QJsonValue::Null : value.t); + v->latinOrIntValue = compressed; + v->latinKey = false; + v->value = QBinaryJsonPrivate::Value::valueToStore(value, valueOffset); + if (valueSize) { + QBinaryJsonPrivate::Value::copyData(value, reinterpret_cast(a) + valueOffset, + compressed); + } +} + +char *QBinaryJsonArray::takeRawData(uint *size) +{ + if (d) + return d->takeRawData(size); + *size = 0; + return nullptr; +} + +bool QBinaryJsonArray::detach(uint reserve) +{ + if (!d) { + if (reserve >= QBinaryJsonPrivate::Value::MaxSize) { + qWarning("QBinaryJson: Document too large to store in data structure"); + return false; + } + d = new QBinaryJsonPrivate::MutableData(reserve, QJsonValue::Array); + a = static_cast(d->header->root()); + d->ref.ref(); + return true; + } + if (reserve == 0 && d->ref.loadRelaxed() == 1) + return true; + + QBinaryJsonPrivate::MutableData *x = d->clone(a, reserve); + if (!x) + return false; + x->ref.ref(); + if (!d->ref.deref()) + delete d; + d = x; + a = static_cast(d->header->root()); + return true; +} + +void QBinaryJsonArray::compact() +{ + if (!d || !d->compactionCounter) + return; + + detach(); + d->compact(); + a = static_cast(d->header->root()); +} + +QT_END_NAMESPACE + diff --git a/src/corelib/serialization/qbinaryjsonarray_p.h b/src/corelib/serialization/qbinaryjsonarray_p.h new file mode 100644 index 0000000000..2bb8fed387 --- /dev/null +++ b/src/corelib/serialization/qbinaryjsonarray_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBINARYJSONARRAY_P_H +#define QBINARYJSONARRAY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qbinaryjsonvalue_p.h" + +QT_REQUIRE_CONFIG(binaryjson); + +QT_BEGIN_NAMESPACE + +class QBinaryJsonArray +{ + Q_DISABLE_COPY(QBinaryJsonArray) +public: + QBinaryJsonArray() = default; + ~QBinaryJsonArray(); + + QBinaryJsonArray(QBinaryJsonArray &&other) noexcept + : d(other.d), + a(other.a) + { + other.d = nullptr; + other.a = nullptr; + } + + QBinaryJsonArray &operator =(QBinaryJsonArray &&other) noexcept + { + qSwap(d, other.d); + qSwap(a, other.a); + return *this; + } + + static QBinaryJsonArray fromJsonArray(const QJsonArray &array); + char *takeRawData(uint *size); + +private: + friend class QBinaryJsonValue; + + void append(const QBinaryJsonValue &value); + void compact(); + bool detach(uint reserve = 0); + + QBinaryJsonPrivate::MutableData *d = nullptr; + QBinaryJsonPrivate::Array *a = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QBINARYJSONARRAY_P_H diff --git a/src/corelib/serialization/qbinaryjsonobject.cpp b/src/corelib/serialization/qbinaryjsonobject.cpp new file mode 100644 index 0000000000..3186ab6087 --- /dev/null +++ b/src/corelib/serialization/qbinaryjsonobject.cpp @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbinaryjsonobject_p.h" +#include "qbinaryjson_p.h" + +#include + +QT_BEGIN_NAMESPACE + +QBinaryJsonObject::~QBinaryJsonObject() +{ + if (d && !d->ref.deref()) + delete d; +} + +QBinaryJsonObject QBinaryJsonObject::fromJsonObject(const QJsonObject &object) +{ + QBinaryJsonObject binary; + for (auto it = object.begin(), end = object.end(); it != end; ++it) + binary.insert(it.key(), QBinaryJsonValue::fromJsonValue(it.value())); + if (binary.d) // We want to compact it as it is a root item now + binary.d->compactionCounter++; + binary.compact(); + return binary; +} + +void QBinaryJsonObject::insert(const QString &key, const QBinaryJsonValue &value) +{ + bool latinOrIntValue; + uint valueSize = QBinaryJsonPrivate::Value::requiredStorage(value, &latinOrIntValue); + + bool latinKey = QBinaryJsonPrivate::useCompressed(key); + uint valueOffset = sizeof(QBinaryJsonPrivate::Entry) + + QBinaryJsonPrivate::qStringSize(key, latinKey); + uint requiredSize = valueOffset + valueSize; + + if (!detach(requiredSize + sizeof(QBinaryJsonPrivate::offset))) // offset for the new index entry + return; + + if (!o->length) + o->tableOffset = sizeof(QBinaryJsonPrivate::Object); + + bool keyExists = false; + uint pos = o->indexOf(key, &keyExists); + if (keyExists) + ++d->compactionCounter; + + uint off = o->reserveSpace(requiredSize, pos, 1, keyExists); + if (!off) + return; + + QBinaryJsonPrivate::Entry *e = o->entryAt(pos); + e->value.type = value.t; + e->value.latinKey = latinKey; + e->value.latinOrIntValue = latinOrIntValue; + e->value.value = QBinaryJsonPrivate::Value::valueToStore( + value, reinterpret_cast(e) - reinterpret_cast(o) + valueOffset); + QBinaryJsonPrivate::copyString(reinterpret_cast(e + 1), key, latinKey); + if (valueSize) { + QBinaryJsonPrivate::Value::copyData(value, reinterpret_cast(e) + valueOffset, + latinOrIntValue); + } + + if (d->compactionCounter > 32U && d->compactionCounter >= unsigned(o->length) / 2U) + compact(); +} + +char *QBinaryJsonObject::takeRawData(uint *size) const +{ + if (d) + return d->takeRawData(size); + *size = 0; + return nullptr; +} + +bool QBinaryJsonObject::detach(uint reserve) +{ + if (!d) { + if (reserve >= QBinaryJsonPrivate::Value::MaxSize) { + qWarning("QBinaryJson: Document too large to store in data structure"); + return false; + } + d = new QBinaryJsonPrivate::MutableData(reserve, QJsonValue::Object); + o = static_cast(d->header->root()); + d->ref.ref(); + return true; + } + if (reserve == 0 && d->ref.loadRelaxed() == 1) + return true; + + QBinaryJsonPrivate::MutableData *x = d->clone(o, reserve); + if (!x) + return false; + x->ref.ref(); + if (!d->ref.deref()) + delete d; + d = x; + o = static_cast(d->header->root()); + return true; +} + +void QBinaryJsonObject::compact() +{ + if (!d || !d->compactionCounter) + return; + + detach(); + d->compact(); + o = static_cast(d->header->root()); +} + +QT_END_NAMESPACE diff --git a/src/corelib/serialization/qbinaryjsonobject_p.h b/src/corelib/serialization/qbinaryjsonobject_p.h new file mode 100644 index 0000000000..c0991ac339 --- /dev/null +++ b/src/corelib/serialization/qbinaryjsonobject_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBINARYJSONOBJECT_H +#define QBINARYJSONOBJECT_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qbinaryjsonvalue_p.h" + +QT_REQUIRE_CONFIG(binaryjson); + +QT_BEGIN_NAMESPACE + +class QBinaryJsonObject +{ + Q_DISABLE_COPY(QBinaryJsonObject) +public: + QBinaryJsonObject() = default; + ~QBinaryJsonObject(); + + QBinaryJsonObject(QBinaryJsonObject &&other) noexcept + : d(other.d), o(other.o) + { + other.d = nullptr; + other.o = nullptr; + } + + QBinaryJsonObject &operator =(QBinaryJsonObject &&other) noexcept + { + qSwap(d, other.d); + qSwap(o, other.o); + return *this; + } + + static QBinaryJsonObject fromJsonObject(const QJsonObject &object); + char *takeRawData(uint *size) const; + +private: + friend class QBinaryJsonValue; + + void insert(const QString &key, const QBinaryJsonValue &value); + bool detach(uint reserve = 0); + void compact(); + + QBinaryJsonPrivate::MutableData *d = nullptr; + QBinaryJsonPrivate::Object *o = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QBINARYJSONOBJECT_P_H diff --git a/src/corelib/serialization/qbinaryjsonvalue.cpp b/src/corelib/serialization/qbinaryjsonvalue.cpp new file mode 100644 index 0000000000..5e3a01ad38 --- /dev/null +++ b/src/corelib/serialization/qbinaryjsonvalue.cpp @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbinaryjsonobject_p.h" +#include "qbinaryjsonvalue_p.h" +#include "qbinaryjsonarray_p.h" +#include "qbinaryjson_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +QBinaryJsonValue::QBinaryJsonValue(QBinaryJsonPrivate::MutableData *data, + QBinaryJsonPrivate::Base *parent, + const QBinaryJsonPrivate::Value &v) + : t(QJsonValue::Type(uint(v.type))) +{ + switch (t) { + case QJsonValue::Undefined: + case QJsonValue::Null: + dbl = 0; + break; + case QJsonValue::Bool: + b = v.toBoolean(); + break; + case QJsonValue::Double: + dbl = v.toDouble(parent); + break; + case QJsonValue::String: { + QString s = v.toString(parent); + stringData = s.data_ptr(); + stringData->ref.ref(); + break; + } + case QJsonValue::Array: + case QJsonValue::Object: + d = data; + base = v.base(parent); + break; + } + if (d) + d->ref.ref(); +} + +QBinaryJsonValue::QBinaryJsonValue(QString string) + : stringData(*reinterpret_cast(&string)), t(QJsonValue::String) +{ + stringData->ref.ref(); +} + +QBinaryJsonValue::QBinaryJsonValue(const QBinaryJsonArray &a) + : base(a.a), d(a.d), t(QJsonValue::Array) +{ + if (d) + d->ref.ref(); +} + +QBinaryJsonValue::QBinaryJsonValue(const QBinaryJsonObject &o) + : base(o.o), d(o.d), t(QJsonValue::Object) +{ + if (d) + d->ref.ref(); +} + +QBinaryJsonValue::~QBinaryJsonValue() +{ + if (t == QJsonValue::String && stringData && !stringData->ref.deref()) + free(stringData); + + if (d && !d->ref.deref()) + delete d; +} + +QBinaryJsonValue QBinaryJsonValue::fromJsonValue(const QJsonValue &json) +{ + switch (json.type()) { + case QJsonValue::Bool: + return QBinaryJsonValue(json.toBool()); + case QJsonValue::Double: + return QBinaryJsonValue(json.toDouble()); + case QJsonValue::String: + return QBinaryJsonValue(json.toString()); + case QJsonValue::Array: + return QBinaryJsonArray::fromJsonArray(json.toArray()); + case QJsonValue::Object: + return QBinaryJsonObject::fromJsonObject(json.toObject()); + case QJsonValue::Null: + return QBinaryJsonValue(QJsonValue::Null); + case QJsonValue::Undefined: + return QBinaryJsonValue(QJsonValue::Undefined); + } + Q_UNREACHABLE(); + return QBinaryJsonValue(QJsonValue::Null); +} + +QString QBinaryJsonValue::toString() const +{ + if (t != QJsonValue::String) + return QString(); + stringData->ref.ref(); // the constructor below doesn't add a ref. + QStringDataPtr holder = { stringData }; + return QString(holder); +} + +void QBinaryJsonValue::detach() +{ + if (!d) + return; + + QBinaryJsonPrivate::MutableData *x = d->clone(base); + x->ref.ref(); + if (!d->ref.deref()) + delete d; + d = x; + base = static_cast(d->header->root()); +} + +QT_END_NAMESPACE diff --git a/src/corelib/serialization/qbinaryjsonvalue_p.h b/src/corelib/serialization/qbinaryjsonvalue_p.h new file mode 100644 index 0000000000..498fc62ecd --- /dev/null +++ b/src/corelib/serialization/qbinaryjsonvalue_p.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBINARYJSONVALUE_P_H +#define QBINARYJSONVALUE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +QT_REQUIRE_CONFIG(binaryjson); + +QT_BEGIN_NAMESPACE + +class QBinaryJsonArray; +class QBinaryJsonObject; + +namespace QBinaryJsonPrivate { +class ConstData; +class MutableData; +class Base; +class Value; +class Object; +class Array; +} + +class Q_CORE_EXPORT QBinaryJsonValue +{ + Q_DISABLE_COPY(QBinaryJsonValue) +public: + explicit QBinaryJsonValue(QJsonValue::Type type) : ui(0), t(type) {} + explicit QBinaryJsonValue(bool b) : b(b), t(QJsonValue::Bool) {} + explicit QBinaryJsonValue(double n) : dbl(n), t(QJsonValue::Double) {} + explicit QBinaryJsonValue(QString s); + QBinaryJsonValue(const QBinaryJsonArray &a); + QBinaryJsonValue(const QBinaryJsonObject &o); + + ~QBinaryJsonValue(); + + QBinaryJsonValue(QBinaryJsonValue &&other) noexcept + : ui(other.ui), + d(other.d), + t(other.t) + { + other.ui = 0; + other.d = nullptr; + other.t = QJsonValue::Null; + } + + QBinaryJsonValue &operator =(QBinaryJsonValue &&other) noexcept + { + qSwap(ui, other.ui); + qSwap(d, other.d); + qSwap(t, other.t); + return *this; + } + + static QBinaryJsonValue fromJsonValue(const QJsonValue &json); + QJsonValue::Type type() const { return t; } + bool toBool() const { return (t == QJsonValue::Bool) && b; } + double toDouble() const { return (t == QJsonValue::Double) ? dbl : 0; } + QString toString() const; + +private: + friend class QBinaryJsonPrivate::Value; + friend class QBinaryJsonArray; + friend class QBinaryJsonObject; + + QBinaryJsonValue(QBinaryJsonPrivate::MutableData *d, QBinaryJsonPrivate::Base *parent, + const QBinaryJsonPrivate::Value &v); + + void detach(); + + union { + quint64 ui; + bool b; + double dbl; + QStringData *stringData; + const QBinaryJsonPrivate::Base *base; + }; + QBinaryJsonPrivate::MutableData *d = nullptr; // needed for Objects and Arrays + QJsonValue::Type t = QJsonValue::Null; +}; + +QT_END_NAMESPACE + +#endif // QBINARYJSONVALUE_P_H diff --git a/src/corelib/serialization/qcborvalue.cpp b/src/corelib/serialization/qcborvalue.cpp index 23595f4092..c2f670de06 100644 --- a/src/corelib/serialization/qcborvalue.cpp +++ b/src/corelib/serialization/qcborvalue.cpp @@ -3109,4 +3109,6 @@ QT_END_NAMESPACE #include "qcborarray.cpp" #include "qcbormap.cpp" +#ifndef QT_NO_QOBJECT #include "moc_qcborvalue.cpp" +#endif diff --git a/src/corelib/serialization/qcborvalue.h b/src/corelib/serialization/qcborvalue.h index accd0fae8a..9c613dfcfb 100644 --- a/src/corelib/serialization/qcborvalue.h +++ b/src/corelib/serialization/qcborvalue.h @@ -71,6 +71,8 @@ class QCborStreamReader; class QCborStreamWriter; class QDataStream; +namespace QJsonPrivate { class Value; } + struct QCborParserError { qint64 offset = 0; @@ -301,6 +303,8 @@ public: private: friend class QCborValueRef; friend class QCborContainerPrivate; + friend class QJsonPrivate::Value; + qint64 n = 0; QCborContainerPrivate *container = nullptr; Type t = Undefined; diff --git a/src/corelib/serialization/qjson.cpp b/src/corelib/serialization/qjson.cpp deleted file mode 100644 index 76f1eae1ce..0000000000 --- a/src/corelib/serialization/qjson.cpp +++ /dev/null @@ -1,451 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qjson_p.h" -#include - -QT_BEGIN_NAMESPACE - -namespace QJsonPrivate -{ - -static Q_CONSTEXPR Base emptyArray = { { qle_uint(sizeof(Base)) }, { 0 }, { qle_uint(0) } }; -static Q_CONSTEXPR Base emptyObject = { { qle_uint(sizeof(Base)) }, { qToLittleEndian(1u) }, { qle_uint(0) } }; - -void Data::compact() -{ - Q_ASSERT(sizeof(Value) == sizeof(offset)); - - if (!compactionCounter) - return; - - Base *base = header->root(); - int reserve = 0; - if (base->is_object) { - Object *o = static_cast(base); - for (int i = 0; i < (int)o->length; ++i) - reserve += o->entryAt(i)->usedStorage(o); - } else { - Array *a = static_cast(base); - for (int i = 0; i < (int)a->length; ++i) - reserve += (*a)[i].usedStorage(a); - } - - int size = sizeof(Base) + reserve + base->length*sizeof(offset); - int alloc = sizeof(Header) + size; - Header *h = (Header *) malloc(alloc); - Q_CHECK_PTR(h); - h->tag = QJsonDocument::BinaryFormatTag; - h->version = 1; - Base *b = h->root(); - b->size = size; - b->is_object = header->root()->is_object; - b->length = base->length; - b->tableOffset = reserve + sizeof(Array); - - int offset = sizeof(Base); - if (b->is_object) { - Object *o = static_cast(base); - Object *no = static_cast(b); - - for (int i = 0; i < (int)o->length; ++i) { - no->table()[i] = offset; - - const Entry *e = o->entryAt(i); - Entry *ne = no->entryAt(i); - int s = e->size(); - memcpy(ne, e, s); - offset += s; - int dataSize = e->value.usedStorage(o); - if (dataSize) { - memcpy((char *)no + offset, e->value.data(o), dataSize); - ne->value.value = offset; - offset += dataSize; - } - } - } else { - Array *a = static_cast(base); - Array *na = static_cast(b); - - for (int i = 0; i < (int)a->length; ++i) { - const Value &v = (*a)[i]; - Value &nv = (*na)[i]; - nv = v; - int dataSize = v.usedStorage(a); - if (dataSize) { - memcpy((char *)na + offset, v.data(a), dataSize); - nv.value = offset; - offset += dataSize; - } - } - } - Q_ASSERT(offset == (int)b->tableOffset); - - free(header); - header = h; - this->alloc = alloc; - compactionCounter = 0; -} - -bool Data::valid() const -{ - if (header->tag != QJsonDocument::BinaryFormatTag || header->version != 1u) - return false; - - bool res = false; - Base *root = header->root(); - int maxSize = alloc - sizeof(Header); - if (root->is_object) - res = static_cast(root)->isValid(maxSize); - else - res = static_cast(root)->isValid(maxSize); - - return res; -} - - -int Base::reserveSpace(uint dataSize, int posInTable, uint numItems, bool replace) -{ - Q_ASSERT(posInTable >= 0 && posInTable <= (int)length); - if (size + dataSize >= Value::MaxSize) { - qWarning("QJson: Document too large to store in data structure %d %d %d", (uint)size, dataSize, Value::MaxSize); - return 0; - } - - offset off = tableOffset; - // move table to new position - if (replace) { - memmove((char *)(table()) + dataSize, table(), length*sizeof(offset)); - } else { - memmove((char *)(table() + posInTable + numItems) + dataSize, table() + posInTable, (length - posInTable)*sizeof(offset)); - memmove((char *)(table()) + dataSize, table(), posInTable*sizeof(offset)); - } - tableOffset += dataSize; - for (int i = 0; i < (int)numItems; ++i) - table()[posInTable + i] = off; - size += dataSize; - if (!replace) { - length += numItems; - size += numItems * sizeof(offset); - } - return off; -} - -void Base::removeItems(int pos, int numItems) -{ - Q_ASSERT(pos >= 0 && pos <= (int)length); - if (pos + numItems < (int)length) - memmove(table() + pos, table() + pos + numItems, (length - pos - numItems)*sizeof(offset)); - length -= numItems; -} - -int Object::indexOf(QStringView key, bool *exists) const -{ - int min = 0; - int n = length; - while (n > 0) { - int half = n >> 1; - int middle = min + half; - if (*entryAt(middle) >= key) { - n = half; - } else { - min = middle + 1; - n -= half + 1; - } - } - if (min < (int)length && *entryAt(min) == key) { - *exists = true; - return min; - } - *exists = false; - return min; -} - -int Object::indexOf(QLatin1String key, bool *exists) const -{ - int min = 0; - int n = length; - while (n > 0) { - int half = n >> 1; - int middle = min + half; - if (*entryAt(middle) >= key) { - n = half; - } else { - min = middle + 1; - n -= half + 1; - } - } - if (min < (int)length && *entryAt(min) == key) { - *exists = true; - return min; - } - *exists = false; - return min; -} - -bool Object::isValid(int maxSize) const -{ - if (size > (uint)maxSize || tableOffset + length*sizeof(offset) > size) - return false; - - QString lastKey; - for (uint i = 0; i < length; ++i) { - offset entryOffset = table()[i]; - if (entryOffset + sizeof(Entry) >= tableOffset) - return false; - Entry *e = entryAt(i); - if (!e->isValid(tableOffset - table()[i])) - return false; - QString key = e->key(); - if (key < lastKey) - return false; - if (!e->value.isValid(this)) - return false; - lastKey = key; - } - return true; -} - - - -bool Array::isValid(int maxSize) const -{ - if (size > (uint)maxSize || tableOffset + length*sizeof(offset) > size) - return false; - - for (uint i = 0; i < length; ++i) { - if (!at(i).isValid(this)) - return false; - } - return true; -} - - -bool Entry::operator ==(QStringView key) const -{ - if (value.latinKey) - return (shallowLatin1Key() == key); - else - return (shallowKey() == key); -} - -bool Entry::operator==(QLatin1String key) const -{ - if (value.latinKey) - return shallowLatin1Key() == key; - else - return shallowKey() == QString(key); // ### conversion to QString -} - -bool Entry::operator ==(const Entry &other) const -{ - if (value.latinKey) { - if (other.value.latinKey) - return shallowLatin1Key() == other.shallowLatin1Key(); - return shallowLatin1Key() == other.shallowKey(); - } else if (other.value.latinKey) { - return shallowKey() == other.shallowLatin1Key(); - } - return shallowKey() == other.shallowKey(); -} - -bool Entry::operator >=(const Entry &other) const -{ - if (value.latinKey) { - if (other.value.latinKey) - return shallowLatin1Key() >= other.shallowLatin1Key(); - return shallowLatin1Key() >= other.shallowKey(); - } else if (other.value.latinKey) { - return shallowKey() >= other.shallowLatin1Key(); - } - return shallowKey() >= other.shallowKey(); -} - - -int Value::usedStorage(const Base *b) const -{ - int s = 0; - switch (type) { - case QJsonValue::Double: - if (latinOrIntValue) - break; - s = sizeof(double); - break; - case QJsonValue::String: { - char *d = data(b); - if (latinOrIntValue) - s = sizeof(ushort) + qFromLittleEndian(*(ushort *)d); - else - s = sizeof(int) + sizeof(ushort) * qFromLittleEndian(*(int *)d); - break; - } - case QJsonValue::Array: - case QJsonValue::Object: - s = base(b)->size; - break; - case QJsonValue::Null: - case QJsonValue::Bool: - default: - break; - } - return alignedSize(s); -} - -inline bool isValidValueOffset(uint offset, uint tableOffset) -{ - return offset >= sizeof(Base) - && offset + sizeof(uint) <= tableOffset; -} - -bool Value::isValid(const Base *b) const -{ - switch (type) { - case QJsonValue::Null: - case QJsonValue::Bool: - return true; - case QJsonValue::Double: - return latinOrIntValue || isValidValueOffset(value, b->tableOffset); - case QJsonValue::String: - if (!isValidValueOffset(value, b->tableOffset)) - return false; - if (latinOrIntValue) - return asLatin1String(b).isValid(b->tableOffset - value); - return asString(b).isValid(b->tableOffset - value); - case QJsonValue::Array: - return isValidValueOffset(value, b->tableOffset) - && static_cast(base(b))->isValid(b->tableOffset - value); - case QJsonValue::Object: - return isValidValueOffset(value, b->tableOffset) - && static_cast(base(b))->isValid(b->tableOffset - value); - default: - return false; - } -} - -/*! - \internal - */ -int Value::requiredStorage(QJsonValue &v, bool *compressed) -{ - *compressed = false; - switch (v.t) { - case QJsonValue::Double: - if (QJsonPrivate::compressedNumber(v.dbl) != INT_MAX) { - *compressed = true; - return 0; - } - return sizeof(double); - case QJsonValue::String: { - QString s = v.toString(); - *compressed = QJsonPrivate::useCompressed(s); - return QJsonPrivate::qStringSize(s, *compressed); - } - case QJsonValue::Array: - case QJsonValue::Object: - if (v.d && v.d->compactionCounter) { - v.detach(); - v.d->compact(); - v.base = static_cast(v.d->header->root()); - } - return v.base ? uint(v.base->size) : sizeof(QJsonPrivate::Base); - case QJsonValue::Undefined: - case QJsonValue::Null: - case QJsonValue::Bool: - break; - } - return 0; -} - -/*! - \internal - */ -uint Value::valueToStore(const QJsonValue &v, uint offset) -{ - switch (v.t) { - case QJsonValue::Undefined: - case QJsonValue::Null: - break; - case QJsonValue::Bool: - return v.b; - case QJsonValue::Double: { - int c = QJsonPrivate::compressedNumber(v.dbl); - if (c != INT_MAX) - return c; - } - Q_FALLTHROUGH(); - case QJsonValue::String: - case QJsonValue::Array: - case QJsonValue::Object: - return offset; - } - return 0; -} - -/*! - \internal - */ -void Value::copyData(const QJsonValue &v, char *dest, bool compressed) -{ - switch (v.t) { - case QJsonValue::Double: - if (!compressed) { - qToLittleEndian(v.ui, dest); - } - break; - case QJsonValue::String: { - QString str = v.toString(); - QJsonPrivate::copyString(dest, str, compressed); - break; - } - case QJsonValue::Array: - case QJsonValue::Object: { - const QJsonPrivate::Base *b = v.base; - if (!b) - b = (v.t == QJsonValue::Array ? &emptyArray : &emptyObject); - memcpy(dest, b, b->size); - break; - } - default: - break; - } -} - -} // namespace QJsonPrivate - -QT_END_NAMESPACE diff --git a/src/corelib/serialization/qjson_p.h b/src/corelib/serialization/qjson_p.h index 7978bed7da..d2bbf928d0 100644 --- a/src/corelib/serialization/qjson_p.h +++ b/src/corelib/serialization/qjson_p.h @@ -52,743 +52,170 @@ // We mean it. // -#include #include -#include -#include -#include -#include -#include -#include - -#include "private/qendian_p.h" -#include "private/qsimd_p.h" - -#include -#include -#include +#include +#include QT_BEGIN_NAMESPACE -// in qstring.cpp -void qt_to_latin1_unchecked(uchar *dst, const ushort *uc, qsizetype len); -void qt_from_latin1(ushort *dst, const char *str, size_t size) noexcept; - -/* - This defines a binary data structure for Json data. The data structure is optimised for fast reading - and minimum allocations. The whole data structure can be mmap'ed and used directly. - - In most cases the binary structure is not as space efficient as a utf8 encoded text representation, but - much faster to access. - - The size requirements are: - - String: - Latin1 data: 2 bytes header + string.length() - Full Unicode: 4 bytes header + 2*(string.length()) - - Values: 4 bytes + size of data (size can be 0 for some data) - bool: 0 bytes - double: 8 bytes (0 if integer with less than 27bits) - string: see above - array: size of array - object: size of object - Array: 12 bytes + 4*length + size of Value data - Object: 12 bytes + 8*length + size of Key Strings + size of Value data - - For an example such as - - { // object: 12 + 5*8 = 52 - "firstName": "John", // key 12, value 8 = 20 - "lastName" : "Smith", // key 12, value 8 = 20 - "age" : 25, // key 8, value 0 = 8 - "address" : // key 12, object below = 140 - { // object: 12 + 4*8 - "streetAddress": "21 2nd Street", // key 16, value 16 - "city" : "New York", // key 8, value 12 - "state" : "NY", // key 8, value 4 - "postalCode" : "10021" // key 12, value 8 - }, // object total: 128 - "phoneNumber": // key: 16, value array below = 172 - [ // array: 12 + 2*4 + values below: 156 - { // object 12 + 2*8 - "type" : "home", // key 8, value 8 - "number": "212 555-1234" // key 8, value 16 - }, // object total: 68 - { // object 12 + 2*8 - "type" : "fax", // key 8, value 8 - "number": "646 555-4567" // key 8, value 16 - } // object total: 68 - ] // array total: 156 - } // great total: 412 bytes - - The uncompressed text file used roughly 500 bytes, so in this case we end up using about - the same space as the text representation. - - Other measurements have shown a slightly bigger binary size than a compact text - representation where all possible whitespace was stripped out. -*/ -#define Q_DECLARE_JSONPRIVATE_TYPEINFO(Class, Flags) } Q_DECLARE_TYPEINFO(QJsonPrivate::Class, Flags); namespace QJsonPrivate { namespace QJsonPrivate { -class Array; -class Object; -class Value; -class Entry; - -template -using q_littleendian = QLEInteger; - -typedef q_littleendian qle_short; -typedef q_littleendian qle_ushort; -typedef q_littleendian qle_int; -typedef q_littleendian qle_uint; - -template -using qle_bitfield = QLEIntegerBitfield; - -template -using qle_signedbitfield = QLEIntegerBitfield; - -typedef qle_uint offset; - -// round the size up to the next 4 byte boundary -inline int alignedSize(int size) { return (size + 3) & ~3; } - -const int MaxLatin1Length = 0x7fff; - -static inline bool useCompressed(QStringView s) +template +struct ObjectIterator { - if (s.length() > MaxLatin1Length) - return false; - return QtPrivate::isLatin1(s); -} + using pointer = Element *; -static inline bool useCompressed(QLatin1String s) -{ - return s.size() <= MaxLatin1Length; -} + struct value_type; + struct reference { + reference(Element &ref) : m_key(&ref) {} -template -static inline int qStringSize(T string, bool compress) -{ - int l = 2 + string.size(); - if (!compress) - l *= 2; - return alignedSize(l); -} + reference() = delete; + ~reference() = default; -// returns INT_MAX if it can't compress it into 28 bits -static inline int compressedNumber(double d) -{ - // this relies on details of how ieee floats are represented - const int exponent_off = 52; - const quint64 fraction_mask = 0x000fffffffffffffull; - const quint64 exponent_mask = 0x7ff0000000000000ull; + reference(const reference &other) = default; + reference(reference &&other) = default; - quint64 val; - memcpy (&val, &d, sizeof(double)); - int exp = (int)((val & exponent_mask) >> exponent_off) - 1023; - if (exp < 0 || exp > 25) - return INT_MAX; - - quint64 non_int = val & (fraction_mask >> exp); - if (non_int) - return INT_MAX; - - bool neg = (val >> 63) != 0; - val &= fraction_mask; - val |= ((quint64)1 << 52); - int res = (int)(val >> (52 - exp)); - return neg ? -res : res; -} - -class Latin1String; - -class String -{ -public: - explicit String(const char *data) { d = (Data *)data; } - - struct Data { - qle_uint length; - qle_ushort utf16[1]; - }; - - Data *d; - - int byteSize() const { return sizeof(uint) + sizeof(ushort) * d->length; } - bool isValid(int maxSize) const { - // Check byteSize() <= maxSize, avoiding integer overflow - maxSize -= sizeof(uint); - return maxSize >= 0 && uint(d->length) <= maxSize / sizeof(ushort); - } - - inline String &operator=(QStringView str) - { - d->length = str.length(); - qToLittleEndian(str.utf16(), str.length(), d->utf16); - fillTrailingZeros(); - return *this; - } - - inline String &operator=(QLatin1String str) - { - d->length = str.size(); -#if Q_BYTE_ORDER == Q_BIG_ENDIAN - for (int i = 0; i < str.size(); ++i) - d->utf16[i] = str[i].unicode(); -#else - qt_from_latin1((ushort *)d->utf16, str.data(), str.size()); -#endif - fillTrailingZeros(); - return *this; - } - - void fillTrailingZeros() - { - if (d->length & 1) - d->utf16[d->length] = 0; - } - - inline bool operator ==(QStringView str) const { - int slen = str.length(); - int l = d->length; - if (slen != l) - return false; - const ushort *s = (const ushort *)str.utf16(); - const qle_ushort *a = d->utf16; - const ushort *b = s; - while (l-- && *a == *b) - a++,b++; - return (l == -1); - } - inline bool operator !=(QStringView str) const { - return !operator ==(str); - } - inline bool operator >=(QStringView str) const { - // ### - return toString() >= str; - } - - inline bool operator<(const Latin1String &str) const; - inline bool operator>=(const Latin1String &str) const { return !operator <(str); } - inline bool operator ==(const Latin1String &str) const; - - inline bool operator ==(const String &str) const { - if (d->length != str.d->length) - return false; - return !memcmp(d->utf16, str.d->utf16, d->length*sizeof(ushort)); - } - inline bool operator<(const String &other) const; - inline bool operator >=(const String &other) const { return !(*this < other); } - - inline QString toString() const { -#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - return QString((QChar *)d->utf16, d->length); -#else - int l = d->length; - QString str(l, Qt::Uninitialized); - QChar *ch = str.data(); - for (int i = 0; i < l; ++i) - ch[i] = QChar(d->utf16[i]); - return str; -#endif - } - -}; - -class Latin1String -{ -public: - explicit Latin1String(const char *data) { d = (Data *)data; } - - struct Data { - qle_ushort length; - char latin1[1]; - }; - Data *d; - - int byteSize() const { return sizeof(ushort) + sizeof(char)*(d->length); } - bool isValid(int maxSize) const { - return byteSize() <= maxSize; - } - - inline Latin1String &operator=(QStringView str) - { - int len = d->length = str.length(); - uchar *l = (uchar *)d->latin1; - const ushort *uc = (const ushort *)str.utf16(); - qt_to_latin1_unchecked(l, uc, len); - - fillTrailingZeros(); - return *this; - } - - inline Latin1String &operator=(QLatin1String str) - { - int len = d->length = str.size(); - uchar *l = (uchar *)d->latin1; - memcpy(l, str.data(), len); - - fillTrailingZeros(); - return *this; - } - - void fillTrailingZeros() - { - uchar *l = (uchar *)d->latin1; - for (int len = d->length; (quintptr)(l + len) & 0x3; ++len) - l[len] = 0; - } - - QLatin1String toQLatin1String() const noexcept { - return QLatin1String(d->latin1, d->length); - } - - inline bool operator<(const String &str) const - { - const qle_ushort *uc = (qle_ushort *) str.d->utf16; - if (!uc || *uc == 0) - return false; - - const uchar *c = (uchar *)d->latin1; - const uchar *e = c + qMin((int)d->length, (int)str.d->length); - - while (c < e) { - if (*c != *uc) - break; - ++c; - ++uc; + reference &operator=(const value_type &value); + reference &operator=(const reference &other) + { + if (m_key != other.m_key) { + key() = other.key(); + value() = other.value(); + } + return *this; } - return (c == e ? (int)d->length < (int)str.d->length : *c < *uc); - } - inline bool operator ==(const String &str) const { - return (str == *this); - } - inline bool operator >=(const String &str) const { - return !(*this < str); - } + reference &operator=(reference &&other) + { + key() = other.key(); + value() = other.value(); + return *this; + } - inline QString toString() const { - return QString::fromLatin1(d->latin1, d->length); - } -}; + Element &key() { return *m_key; } + Element &value() { return *(m_key + 1); } -#define DEF_OP(op) \ - inline bool operator op(Latin1String lhs, Latin1String rhs) noexcept \ - { \ - return lhs.toQLatin1String() op rhs.toQLatin1String(); \ - } \ - inline bool operator op(QLatin1String lhs, Latin1String rhs) noexcept \ - { \ - return lhs op rhs.toQLatin1String(); \ - } \ - inline bool operator op(Latin1String lhs, QLatin1String rhs) noexcept \ - { \ - return lhs.toQLatin1String() op rhs; \ - } \ - inline bool operator op(QStringView lhs, Latin1String rhs) noexcept \ - { \ - return lhs op rhs.toQLatin1String(); \ - } \ - inline bool operator op(Latin1String lhs, QStringView rhs) noexcept \ - { \ - return lhs.toQLatin1String() op rhs; \ - } \ - /*end*/ -DEF_OP(==) -DEF_OP(!=) -DEF_OP(< ) -DEF_OP(> ) -DEF_OP(<=) -DEF_OP(>=) -#undef DEF_OP - -inline bool String::operator ==(const Latin1String &str) const -{ - if ((int)d->length != (int)str.d->length) - return false; - const qle_ushort *uc = d->utf16; - const qle_ushort *e = uc + d->length; - const uchar *c = (uchar *)str.d->latin1; - - while (uc < e) { - if (*uc != *c) - return false; - ++uc; - ++c; - } - return true; -} - -inline bool String::operator <(const String &other) const -{ - int alen = d->length; - int blen = other.d->length; - int l = qMin(alen, blen); - qle_ushort *a = d->utf16; - qle_ushort *b = other.d->utf16; - - while (l-- && *a == *b) - a++,b++; - if (l==-1) - return (alen < blen); - return (ushort)*a < (ushort)*b; -} - -inline bool String::operator<(const Latin1String &str) const -{ - const uchar *c = (uchar *) str.d->latin1; - if (!c || *c == 0) - return false; - - const qle_ushort *uc = d->utf16; - const qle_ushort *e = uc + qMin((int)d->length, (int)str.d->length); - - while (uc < e) { - if (*uc != *c) - break; - ++uc; - ++c; - } - return (uc == e ? (int)d->length < (int)str.d->length : (ushort)*uc < *c); - -} - -template -static inline void copyString(char *dest, T str, bool compress) -{ - if (compress) { - Latin1String string(dest); - string = str; - } else { - String string(dest); - string = str; - } -} + const Element &key() const { return *m_key; } + const Element &value() const { return *(m_key + 1); } -/* - Base is the base class for both Object and Array. Both classes work more or less the same way. - The class starts with a header (defined by the struct below), then followed by data (the data for - values in the Array case and Entry's (see below) for objects. - - After the data a table follows (tableOffset points to it) containing Value objects for Arrays, and - offsets from the beginning of the object to Entry's in the case of Object. - - Entry's in the Object's table are lexicographically sorted by key in the table(). This allows the usage - of a binary search over the keys in an Object. - */ -class Base -{ -public: - qle_uint size; - union { - uint _dummy; - qle_bitfield<0, 1> is_object; - qle_bitfield<1, 31> length; + private: + Element *m_key; }; - offset tableOffset; - // content follows here - inline bool isObject() const { return !!is_object; } - inline bool isArray() const { return !isObject(); } + struct value_type { + value_type(reference ref) : m_key(ref.key()), m_value(ref.value()) {} - inline offset *table() const { return (offset *) (((char *) this) + tableOffset); } + Element key() const { return m_key; } + Element value() const { return m_value; } + private: + Element m_key; + Element m_value; + }; - int reserveSpace(uint dataSize, int posInTable, uint numItems, bool replace); - void removeItems(int pos, int numItems); + using difference_type = typename QVector::difference_type; + using iterator_category = std::random_access_iterator_tag; + + ObjectIterator() = default; + ObjectIterator(ElementsIterator it) : it(it) {} + ElementsIterator elementsIterator() { return it; } + + ObjectIterator operator++(int) { ObjectIterator ret(it); it += 2; return ret; } + ObjectIterator &operator++() { it += 2; return *this; } + ObjectIterator &operator+=(difference_type n) { it += 2 * n; return *this; } + + ObjectIterator operator--(int) { ObjectIterator ret(it); it -= 2; return ret; } + ObjectIterator &operator--() { it -= 2; return *this; } + ObjectIterator &operator-=(difference_type n) { it -= 2 * n; return *this; } + + reference operator*() const { return *it; } + reference operator[](int n) const { return it[n * 2]; } + + bool operator<(ObjectIterator other) const { return it < other.it; } + bool operator>(ObjectIterator other) const { return it > other.it; } + bool operator<=(ObjectIterator other) const { return it <= other.it; } + bool operator>=(ObjectIterator other) const { return it >= other.it; } + +private: + ElementsIterator it; }; -class Object : public Base +template +inline ObjectIterator operator+( + ObjectIterator a, + typename ObjectIterator::difference_type n) { -public: - Entry *entryAt(int i) const { - return reinterpret_cast(((char *)this) + table()[i]); - } - int indexOf(QStringView key, bool *exists) const; - int indexOf(QLatin1String key, bool *exists) const; - - bool isValid(int maxSize) const; -}; - - -class Array : public Base + return {a.elementsIterator() + 2 * n}; +} +template +inline ObjectIterator operator+( + int n, ObjectIterator a) { -public: - inline Value at(int i) const; - inline Value &operator [](int i); + return {a.elementsIterator() + 2 * n}; +} +template +inline ObjectIterator operator-( + ObjectIterator a, + typename ObjectIterator::difference_type n) +{ + return {a.elementsIterator() - 2 * n}; +} +template +inline int operator-( + ObjectIterator a, + ObjectIterator b) +{ + return (a.elementsIterator() - b.elementsIterator()) / 2; +} +template +inline bool operator!=( + ObjectIterator a, + ObjectIterator b) +{ + return a.elementsIterator() != b.elementsIterator(); +} +template +inline bool operator==( + ObjectIterator a, + ObjectIterator b) +{ + return a.elementsIterator() == b.elementsIterator(); +} - bool isValid(int maxSize) const; -}; +using KeyIterator = ObjectIterator::iterator>; +using ConstKeyIterator = ObjectIterator::const_iterator>; +template<> +inline KeyIterator::reference &KeyIterator::reference::operator=(const KeyIterator::value_type &value) +{ + *m_key = value.key(); + *(m_key + 1) = value.value(); + return *this; +} + +inline void swap(KeyIterator::reference a, KeyIterator::reference b) +{ + KeyIterator::value_type t = a; + a = b; + b = t; +} class Value { public: - enum { - MaxSize = (1<<27) - 1 - }; - union { - uint _dummy; - qle_bitfield<0, 3> type; - qle_bitfield<3, 1> latinOrIntValue; - qle_bitfield<4, 1> latinKey; - qle_bitfield<5, 27> value; - qle_signedbitfield<5, 27> int_value; - }; + static QCborContainerPrivate *container(const QCborValue &v) { return v.container; } - inline char *data(const Base *b) const { return ((char *)b) + value; } - int usedStorage(const Base *b) const; - - bool toBoolean() const; - double toDouble(const Base *b) const; - QString toString(const Base *b) const; - String asString(const Base *b) const; - Latin1String asLatin1String(const Base *b) const; - Base *base(const Base *b) const; - - bool isValid(const Base *b) const; - - static int requiredStorage(QJsonValue &v, bool *compressed); - static uint valueToStore(const QJsonValue &v, uint offset); - static void copyData(const QJsonValue &v, char *dest, bool compressed); -}; -Q_DECLARE_JSONPRIVATE_TYPEINFO(Value, Q_PRIMITIVE_TYPE) - -inline Value Array::at(int i) const -{ - return *(Value *) (table() + i); -} - -inline Value &Array::operator [](int i) -{ - return *(Value *) (table() + i); -} - - - -class Entry { -public: - Value value; - // key - // value data follows key - - uint size() const { - int s = sizeof(Entry); - if (value.latinKey) - s += shallowLatin1Key().byteSize(); - else - s += shallowKey().byteSize(); - return alignedSize(s); - } - - int usedStorage(Base *b) const { - return size() + value.usedStorage(b); - } - - String shallowKey() const + static QJsonValue fromTrustedCbor(const QCborValue &v) { - Q_ASSERT(!value.latinKey); - return String((const char *)this + sizeof(Entry)); + QJsonValue result; + result.d = v.container; + result.n = v.n; + result.t = v.t; + return result; } - Latin1String shallowLatin1Key() const - { - Q_ASSERT(value.latinKey); - return Latin1String((const char *)this + sizeof(Entry)); - } - QString key() const - { - if (value.latinKey) { - return shallowLatin1Key().toString(); - } - return shallowKey().toString(); - } - - bool isValid(int maxSize) const { - if (maxSize < (int)sizeof(Entry)) - return false; - maxSize -= sizeof(Entry); - if (value.latinKey) - return shallowLatin1Key().isValid(maxSize); - return shallowKey().isValid(maxSize); - } - - bool operator ==(QStringView key) const; - inline bool operator !=(QStringView key) const { return !operator ==(key); } - inline bool operator >=(QStringView key) const; - - bool operator==(QLatin1String key) const; - inline bool operator!=(QLatin1String key) const { return !operator ==(key); } - inline bool operator>=(QLatin1String key) const; - - bool operator ==(const Entry &other) const; - bool operator >=(const Entry &other) const; }; -inline bool Entry::operator >=(QStringView key) const -{ - if (value.latinKey) - return (shallowLatin1Key() >= key); - else - return (shallowKey() >= key); -} - -inline bool Entry::operator >=(QLatin1String key) const -{ - if (value.latinKey) - return shallowLatin1Key() >= key; - else - return shallowKey() >= QString(key); // ### conversion to QString -} - -inline bool operator <(QStringView key, const Entry &e) -{ return e >= key; } - -inline bool operator<(QLatin1String key, const Entry &e) -{ return e >= key; } - - -class Header { -public: - qle_uint tag; // 'qbjs' - qle_uint version; // 1 - Base *root() { return (Base *)(this + 1); } -}; - - -inline bool Value::toBoolean() const -{ - Q_ASSERT(type == QJsonValue::Bool); - return value != 0; -} - -inline double Value::toDouble(const Base *b) const -{ - Q_ASSERT(type == QJsonValue::Double); - if (latinOrIntValue) - return int_value; - - quint64 i = qFromLittleEndian((const uchar *)b + value); - double d; - memcpy(&d, &i, sizeof(double)); - return d; -} - -inline String Value::asString(const Base *b) const -{ - Q_ASSERT(type == QJsonValue::String && !latinOrIntValue); - return String(data(b)); -} - -inline Latin1String Value::asLatin1String(const Base *b) const -{ - Q_ASSERT(type == QJsonValue::String && latinOrIntValue); - return Latin1String(data(b)); -} - -inline QString Value::toString(const Base *b) const -{ - if (latinOrIntValue) - return asLatin1String(b).toString(); - else - return asString(b).toString(); -} - -inline Base *Value::base(const Base *b) const -{ - Q_ASSERT(type == QJsonValue::Array || type == QJsonValue::Object); - return reinterpret_cast(data(b)); -} - -class Data { -public: - enum Validation { - Unchecked, - Validated, - Invalid - }; - - QAtomicInt ref; - int alloc; - union { - char *rawData; - Header *header; - }; - uint compactionCounter : 31; - uint ownsData : 1; - - inline Data(char *raw, int a) - : alloc(a), rawData(raw), compactionCounter(0), ownsData(true) - { - } - inline Data(int reserved, QJsonValue::Type valueType) - : rawData(nullptr), compactionCounter(0), ownsData(true) - { - Q_ASSERT(valueType == QJsonValue::Array || valueType == QJsonValue::Object); - - alloc = sizeof(Header) + sizeof(Base) + reserved + sizeof(offset); - header = (Header *)malloc(alloc); - Q_CHECK_PTR(header); - header->tag = QJsonDocument::BinaryFormatTag; - header->version = 1; - Base *b = header->root(); - b->size = sizeof(Base); - b->is_object = (valueType == QJsonValue::Object); - b->tableOffset = sizeof(Base); - b->length = 0; - } - inline ~Data() - { if (ownsData) free(rawData); } - - uint offsetOf(const void *ptr) const { return (uint)(((char *)ptr - rawData)); } - - QJsonObject toObject(Object *o) const - { - return QJsonObject(const_cast(this), o); - } - - QJsonArray toArray(Array *a) const - { - return QJsonArray(const_cast(this), a); - } - - Data *clone(Base *b, int reserve = 0) - { - int size = sizeof(Header) + b->size; - if (b == header->root() && ref.loadRelaxed() == 1 && alloc >= size + reserve) - return this; - - if (reserve) { - if (reserve < 128) - reserve = 128; - size = qMax(size + reserve, qMin(size *2, (int)Value::MaxSize)); - if (size > Value::MaxSize) { - qWarning("QJson: Document too large to store in data structure"); - return nullptr; - } - } - char *raw = (char *)malloc(size); - Q_CHECK_PTR(raw); - memcpy(raw + sizeof(Header), b, b->size); - Header *h = (Header *)raw; - h->tag = QJsonDocument::BinaryFormatTag; - h->version = 1; - Data *d = new Data(raw, size); - d->compactionCounter = (b == header->root()) ? compactionCounter : 0; - return d; - } - - void compact(); - bool valid() const; - -private: - Q_DISABLE_COPY_MOVE(Data) -}; - -} +} // namespace QJsonPrivate QT_END_NAMESPACE diff --git a/src/corelib/serialization/qjsonarray.cpp b/src/corelib/serialization/qjsonarray.cpp index 9636ac5856..c5d4bf0315 100644 --- a/src/corelib/serialization/qjsonarray.cpp +++ b/src/corelib/serialization/qjsonarray.cpp @@ -40,12 +40,16 @@ #include #include #include +#include #include +#include #include #include +#include +#include + #include "qjsonwriter_p.h" -#include "qjson_p.h" QT_BEGIN_NAMESPACE @@ -131,10 +135,7 @@ QT_BEGIN_NAMESPACE /*! Creates an empty array. */ -QJsonArray::QJsonArray() - : d(nullptr), a(nullptr) -{ -} +QJsonArray::QJsonArray() = default; /*! \fn QJsonArray::QJsonArray(std::initializer_list args) @@ -151,12 +152,10 @@ QJsonArray::QJsonArray() /*! \internal */ -QJsonArray::QJsonArray(QJsonPrivate::Data *data, QJsonPrivate::Array *array) - : d(data), a(array) +QJsonArray::QJsonArray(QCborContainerPrivate *array) + : a(array) { - Q_ASSERT(data); Q_ASSERT(array); - d->ref.ref(); } /*! @@ -168,18 +167,13 @@ QJsonArray::QJsonArray(QJsonPrivate::Data *data, QJsonPrivate::Array *array) */ void QJsonArray::initialize() { - d = nullptr; a = nullptr; } /*! Deletes the array. */ -QJsonArray::~QJsonArray() -{ - if (d && !d->ref.deref()) - delete d; -} +QJsonArray::~QJsonArray() = default; /*! Creates a copy of \a other. @@ -187,12 +181,22 @@ QJsonArray::~QJsonArray() Since QJsonArray is implicitly shared, the copy is shallow as long as the object doesn't get modified. */ +QJsonArray::QJsonArray(std::initializer_list args) +{ + initialize(); + for (const auto & arg : args) + append(arg); +} + QJsonArray::QJsonArray(const QJsonArray &other) { - d = other.d; a = other.a; - if (d) - d->ref.ref(); +} + +QJsonArray::QJsonArray(QJsonArray &&other) noexcept + : a(other.a) +{ + other.a = nullptr; } /*! @@ -200,15 +204,7 @@ QJsonArray::QJsonArray(const QJsonArray &other) */ QJsonArray &QJsonArray::operator =(const QJsonArray &other) { - if (d != other.d) { - if (d && !d->ref.deref()) - delete d; - d = other.d; - if (d) - d->ref.ref(); - } a = other.a; - return *this; } @@ -282,48 +278,7 @@ QJsonArray QJsonArray::fromStringList(const QStringList &list) */ QJsonArray QJsonArray::fromVariantList(const QVariantList &list) { - QJsonArray array; - if (list.isEmpty()) - return array; - - array.detach2(1024); - - QVector values; - values.resize(list.size()); - QJsonPrivate::Value *valueData = values.data(); - uint currentOffset = sizeof(QJsonPrivate::Base); - - for (int i = 0; i < list.size(); ++i) { - QJsonValue val = QJsonValue::fromVariant(list.at(i)); - - bool latinOrIntValue; - int valueSize = QJsonPrivate::Value::requiredStorage(val, &latinOrIntValue); - - if (!array.detach2(valueSize)) - return QJsonArray(); - - QJsonPrivate::Value *v = valueData + i; - v->type = (val.t == QJsonValue::Undefined ? QJsonValue::Null : val.t); - v->latinOrIntValue = latinOrIntValue; - v->latinKey = false; - v->value = QJsonPrivate::Value::valueToStore(val, currentOffset); - if (valueSize) - QJsonPrivate::Value::copyData(val, (char *)array.a + currentOffset, latinOrIntValue); - - currentOffset += valueSize; - array.a->size = currentOffset; - } - - // write table - array.a->tableOffset = currentOffset; - if (!array.detach2(sizeof(QJsonPrivate::offset)*values.size())) - return QJsonArray(); - memcpy(static_cast(array.a->table()), - static_cast(values.constData()), values.size()*sizeof(uint)); - array.a->length = values.size(); - array.a->size = currentOffset + sizeof(QJsonPrivate::offset)*values.size(); - - return array; + return QCborArray::fromVariantList(list).toJsonArray(); } /*! @@ -333,14 +288,7 @@ QJsonArray QJsonArray::fromVariantList(const QVariantList &list) */ QVariantList QJsonArray::toVariantList() const { - QVariantList list; - - if (a) { - list.reserve(a->length); - for (int i = 0; i < (int)a->length; ++i) - list.append(QJsonValue(d, a, a->at(i)).toVariant()); - } - return list; + return QCborArray::fromJsonArray(*this).toVariantList(); } @@ -349,10 +297,7 @@ QVariantList QJsonArray::toVariantList() const */ int QJsonArray::size() const { - if (!d) - return 0; - - return (int)a->length; + return a ? a->elements.size() : 0; } /*! @@ -370,10 +315,7 @@ int QJsonArray::size() const */ bool QJsonArray::isEmpty() const { - if (!d) - return true; - - return !a->length; + return a == nullptr || a->elements.isEmpty(); } /*! @@ -384,10 +326,10 @@ bool QJsonArray::isEmpty() const */ QJsonValue QJsonArray::at(int i) const { - if (!a || i < 0 || i >= (int)a->length) + if (!a || i < 0 || i >= a->elements.size()) return QJsonValue(QJsonValue::Undefined); - return QJsonValue(d, a, a->at(i)); + return QJsonPrivate::Value::fromTrustedCbor(a->valueAt(i)); } /*! @@ -411,7 +353,7 @@ QJsonValue QJsonArray::first() const */ QJsonValue QJsonArray::last() const { - return at(a ? (a->length - 1) : 0); + return at(a ? (a->elements.size() - 1) : 0); } /*! @@ -433,7 +375,7 @@ void QJsonArray::prepend(const QJsonValue &value) */ void QJsonArray::append(const QJsonValue &value) { - insert(a ? (int)a->length : 0, value); + insert(a ? a->elements.size() : 0, value); } /*! @@ -444,14 +386,10 @@ void QJsonArray::append(const QJsonValue &value) */ void QJsonArray::removeAt(int i) { - if (!a || i < 0 || i >= (int)a->length) + if (!a || i < 0 || i >= a->elements.length()) return; - detach2(); - a->removeItems(i, 1); - ++d->compactionCounter; - if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(a->length) / 2u) - compact(); + a->removeAt(i); } /*! \fn void QJsonArray::removeFirst() @@ -484,11 +422,12 @@ void QJsonArray::removeAt(int i) */ QJsonValue QJsonArray::takeAt(int i) { - if (!a || i < 0 || i >= (int)a->length) + if (!a || i < 0 || i >= a->elements.length()) return QJsonValue(QJsonValue::Undefined); - QJsonValue v(d, a, a->at(i)); - removeAt(i); // detaches + detach2(); + const QJsonValue v = QJsonPrivate::Value::fromTrustedCbor(a->extractAt(i)); + a->removeAt(i); return v; } @@ -501,29 +440,14 @@ QJsonValue QJsonArray::takeAt(int i) */ void QJsonArray::insert(int i, const QJsonValue &value) { - Q_ASSERT (i >= 0 && i <= (a ? (int)a->length : 0)); - QJsonValue val = value; + if (a) + detach2(a->elements.length() + 1); + else + a = new QCborContainerPrivate; - bool compressed; - int valueSize = QJsonPrivate::Value::requiredStorage(val, &compressed); - - if (!detach2(valueSize + sizeof(QJsonPrivate::Value))) - return; - - if (!a->length) - a->tableOffset = sizeof(QJsonPrivate::Array); - - int valueOffset = a->reserveSpace(valueSize, i, 1, false); - if (!valueOffset) - return; - - QJsonPrivate::Value &v = (*a)[i]; - v.type = (val.t == QJsonValue::Undefined ? QJsonValue::Null : val.t); - v.latinOrIntValue = compressed; - v.latinKey = false; - v.value = QJsonPrivate::Value::valueToStore(val, valueOffset); - if (valueSize) - QJsonPrivate::Value::copyData(val, (char *)a + valueOffset, compressed); + Q_ASSERT (i >= 0 && i <= a->elements.length()); + a->insertAt(i, value.type() == QJsonValue::Undefined ? QCborValue(nullptr) + : QCborValue::fromJsonValue(value)); } /*! @@ -552,33 +476,9 @@ void QJsonArray::insert(int i, const QJsonValue &value) */ void QJsonArray::replace(int i, const QJsonValue &value) { - Q_ASSERT (a && i >= 0 && i < (int)(a->length)); - QJsonValue val = value; - - bool compressed; - int valueSize = QJsonPrivate::Value::requiredStorage(val, &compressed); - - if (!detach2(valueSize)) - return; - - if (!a->length) - a->tableOffset = sizeof(QJsonPrivate::Array); - - int valueOffset = a->reserveSpace(valueSize, i, 1, true); - if (!valueOffset) - return; - - QJsonPrivate::Value &v = (*a)[i]; - v.type = (val.t == QJsonValue::Undefined ? QJsonValue::Null : val.t); - v.latinOrIntValue = compressed; - v.latinKey = false; - v.value = QJsonPrivate::Value::valueToStore(val, valueOffset); - if (valueSize) - QJsonPrivate::Value::copyData(val, (char *)a + valueOffset, compressed); - - ++d->compactionCounter; - if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(a->length) / 2u) - compact(); + Q_ASSERT (a && i >= 0 && i < a->elements.length()); + detach2(); + a->replaceAt(i, QCborValue::fromJsonValue(value)); } /*! @@ -610,7 +510,7 @@ bool QJsonArray::contains(const QJsonValue &value) const */ QJsonValueRef QJsonArray::operator [](int i) { - Q_ASSERT(a && i >= 0 && i < (int)a->length); + Q_ASSERT(a && i >= 0 && i < a->elements.length()); return QJsonValueRef(this, i); } @@ -633,14 +533,14 @@ bool QJsonArray::operator==(const QJsonArray &other) const return true; if (!a) - return !other.a->length; + return !other.a->elements.length(); if (!other.a) - return !a->length; - if (a->length != other.a->length) + return !a->elements.length(); + if (a->elements.length() != other.a->elements.length()) return false; - for (int i = 0; i < (int)a->length; ++i) { - if (QJsonValue(d, a, a->at(i)) != QJsonValue(other.d, other.a, other.a->at(i))) + for (int i = 0; i < a->elements.length(); ++i) { + if (a->valueAt(i) != other.a->valueAt(i)) return false; } return true; @@ -1216,28 +1116,10 @@ void QJsonArray::detach(uint reserve) */ bool QJsonArray::detach2(uint reserve) { - if (!d) { - if (reserve >= QJsonPrivate::Value::MaxSize) { - qWarning("QJson: Document too large to store in data structure"); - return false; - } - d = new QJsonPrivate::Data(reserve, QJsonValue::Array); - a = static_cast(d->header->root()); - d->ref.ref(); + if (!a) return true; - } - if (reserve == 0 && d->ref.loadRelaxed() == 1) - return true; - - QJsonPrivate::Data *x = d->clone(a, reserve); - if (!x) - return false; - x->ref.ref(); - if (!d->ref.deref()) - delete d; - d = x; - a = static_cast(d->header->root()); - return true; + a = a->detach(a.data(), reserve ? reserve : size()); + return a; } /*! @@ -1245,12 +1127,7 @@ bool QJsonArray::detach2(uint reserve) */ void QJsonArray::compact() { - if (!d || !d->compactionCounter) - return; - - detach2(); - d->compact(); - a = static_cast(d->header->root()); + a->compact(a->elements.size()); } uint qHash(const QJsonArray &array, uint seed) @@ -1267,7 +1144,7 @@ QDebug operator<<(QDebug dbg, const QJsonArray &a) return dbg; } QByteArray json; - QJsonPrivate::Writer::arrayToJson(a.a, json, 0, true); + QJsonPrivate::Writer::arrayToJson(a.a.data(), json, 0, true); dbg.nospace() << "QJsonArray(" << json.constData() // print as utf-8 string without extra quotation marks << ")"; diff --git a/src/corelib/serialization/qjsonarray.h b/src/corelib/serialization/qjsonarray.h index 983a6753b5..22aa996a3e 100644 --- a/src/corelib/serialization/qjsonarray.h +++ b/src/corelib/serialization/qjsonarray.h @@ -42,6 +42,7 @@ #include #include +#include #include QT_BEGIN_NAMESPACE @@ -56,25 +57,14 @@ class Q_CORE_EXPORT QJsonArray public: QJsonArray(); - QJsonArray(std::initializer_list args) - { - initialize(); - for (std::initializer_list::const_iterator i = args.begin(); i != args.end(); ++i) - append(*i); - } + QJsonArray(std::initializer_list args); ~QJsonArray(); QJsonArray(const QJsonArray &other); QJsonArray &operator =(const QJsonArray &other); - QJsonArray(QJsonArray &&other) noexcept - : d(other.d), - a(other.a) - { - other.d = nullptr; - other.a = nullptr; - } + QJsonArray(QJsonArray &&other) noexcept; QJsonArray &operator =(QJsonArray &&other) noexcept { @@ -113,7 +103,6 @@ public: void swap(QJsonArray &other) noexcept { - qSwap(d, other.d); qSwap(a, other.a); } @@ -245,20 +234,21 @@ public: typedef int difference_type; private: - friend class QJsonPrivate::Data; friend class QJsonValue; friend class QJsonDocument; + friend class QCborArray; friend Q_CORE_EXPORT QDebug operator<<(QDebug, const QJsonArray &); - QJsonArray(QJsonPrivate::Data *data, QJsonPrivate::Array *array); + QJsonArray(QCborContainerPrivate *array); void initialize(); void compact(); // ### Qt 6: remove me and merge with detach2 void detach(uint reserve = 0); bool detach2(uint reserve = 0); - QJsonPrivate::Data *d; - QJsonPrivate::Array *a; + // ### Qt 6: remove + void *dead = nullptr; + QExplicitlySharedDataPointer a; }; Q_DECLARE_SHARED_NOT_MOVABLE_UNTIL_QT6(QJsonArray) diff --git a/src/corelib/serialization/qjsoncbor.cpp b/src/corelib/serialization/qjsoncbor.cpp index d6a0c8c48a..7136a163ee 100644 --- a/src/corelib/serialization/qjsoncbor.cpp +++ b/src/corelib/serialization/qjsoncbor.cpp @@ -42,6 +42,10 @@ #include "qcborarray.h" #include "qcbormap.h" + +#include "qjsonarray.h" +#include "qjsonobject.h" +#include "qjsondocument.h" #include "qjson_p.h" #include @@ -149,7 +153,12 @@ static Q_NEVER_INLINE QString makeString(const QCborContainerPrivate *d, qsizety case QCborValue::Array: case QCborValue::Map: +#if defined(QT_JSON_READONLY) || defined(QT_BOOTSTRAPPED) + qFatal("Writing JSON is disabled."); + return QString(); +#else return d->valueAt(idx).toDiagnosticNotation(QCborValue::Compact); +#endif case QCborValue::SimpleType: break; @@ -181,30 +190,11 @@ static Q_NEVER_INLINE QString makeString(const QCborContainerPrivate *d, qsizety return simpleTypeString(e.type); } -static QJsonValue convertToJson(const QCborContainerPrivate *d, qsizetype idx); +QJsonValue qt_convertToJson(QCborContainerPrivate *d, qsizetype idx); -static QJsonArray convertToJsonArray(const QCborContainerPrivate *d) -{ - QJsonArray a; - if (d) { - for (qsizetype idx = 0; idx < d->elements.size(); ++idx) - a.append(convertToJson(d, idx)); - } - return a; -} - -static QJsonObject convertToJsonObject(const QCborContainerPrivate *d) -{ - QJsonObject o; - if (d) { - for (qsizetype idx = 0; idx < d->elements.size(); idx += 2) - o.insert(makeString(d, idx), convertToJson(d, idx + 1)); - } - return o; -} - -static QJsonValue convertExtendedTypeToJson(const QCborContainerPrivate *d) +static QJsonValue convertExtendedTypeToJson(QCborContainerPrivate *d) { +#ifndef QT_BUILD_QMAKE qint64 tag = d->elements.at(0).value; switch (tag) { @@ -225,12 +215,36 @@ static QJsonValue convertExtendedTypeToJson(const QCborContainerPrivate *d) return s; } } +#endif // for all other tags, ignore it and return the converted tagged item - return convertToJson(d, 1); + return qt_convertToJson(d, 1); } -static QJsonValue convertToJson(const QCborContainerPrivate *d, qsizetype idx) +// We need to do this because sub-objects may need conversion. +static QJsonArray convertToJsonArray(QCborContainerPrivate *d) +{ + QJsonArray a; + if (d) { + for (qsizetype idx = 0; idx < d->elements.size(); ++idx) + a.append(qt_convertToJson(d, idx)); + } + return a; +} + +// We need to do this because the keys need to be sorted and converted to strings +// and sub-objects may need recursive conversion. +static QJsonObject convertToJsonObject(QCborContainerPrivate *d) +{ + QJsonObject o; + if (d) { + for (qsizetype idx = 0; idx < d->elements.size(); idx += 2) + o.insert(makeString(d, idx), qt_convertToJson(d, idx + 1)); + } + return o; +} + +QJsonValue qt_convertToJson(QCborContainerPrivate *d, qsizetype idx) { // encoding the container itself if (idx == -QCborValue::Array) @@ -248,7 +262,7 @@ static QJsonValue convertToJson(const QCborContainerPrivate *d, qsizetype idx) const auto &e = d->elements.at(idx); switch (e.type) { case QCborValue::Integer: - return qint64(e.value); + return QJsonPrivate::Value::fromTrustedCbor(e.value); case QCborValue::ByteArray: case QCborValue::String: @@ -264,14 +278,14 @@ static QJsonValue convertToJson(const QCborContainerPrivate *d, qsizetype idx) case QCborValue::RegularExpression: case QCborValue::Uuid: // recurse - return convertToJson(e.flags & Element::IsContainer ? e.container : nullptr, -e.type); + return qt_convertToJson(e.flags & Element::IsContainer ? e.container : nullptr, -e.type); case QCborValue::Null: return QJsonValue(); case QCborValue::Undefined: case QCborValue::Invalid: - return QJsonValue(QJsonValue::Undefined); + return QJsonValue::Undefined; case QCborValue::False: return false; @@ -283,7 +297,7 @@ static QJsonValue convertToJson(const QCborContainerPrivate *d, qsizetype idx) return fpToJson(e.fpvalue()); } - return makeString(d, idx); + return QJsonPrivate::Value::fromTrustedCbor(makeString(d, idx)); } /*! @@ -348,22 +362,22 @@ static QJsonValue convertToJson(const QCborContainerPrivate *d, qsizetype idx) QJsonValue QCborValue::toJsonValue() const { if (container) - return convertToJson(container, n < 0 ? -type() : n); + return qt_convertToJson(container, n < 0 ? -type() : n); // simple values switch (type()) { - case Integer: - return n; - - case Null: - return QJsonValue(); - case False: return false; + case Integer: + return QJsonPrivate::Value::fromTrustedCbor(n); + case True: return true; + case Null: + return QJsonValue(); + case Double: return fpToJson(fp_helper()); @@ -372,12 +386,12 @@ QJsonValue QCborValue::toJsonValue() const case Undefined: case Invalid: - return QJsonValue(QJsonValue::Undefined); + return QJsonValue::Undefined; case ByteArray: case String: // empty strings - return QString(); + return QJsonValue::String; case Array: // empty array @@ -392,16 +406,16 @@ QJsonValue QCborValue::toJsonValue() const case Url: case RegularExpression: case Uuid: - Q_UNREACHABLE(); + // Reachable, but invalid in Json return QJsonValue::Undefined; } - return simpleTypeString(type()); + return QJsonPrivate::Value::fromTrustedCbor(simpleTypeString(type())); } QJsonValue QCborValueRef::toJsonValue() const { - return convertToJson(d, i); + return qt_convertToJson(d, i); } /*! @@ -540,8 +554,10 @@ QVariant QCborValue::toVariant() const case DateTime: return toDateTime(); +#ifndef QT_BOOTSTRAPPED case Url: return toUrl(); +#endif #if QT_CONFIG(regularexpression) case RegularExpression: @@ -597,12 +613,13 @@ QCborValue QCborValue::fromJsonValue(const QJsonValue &v) { switch (v.type()) { case QJsonValue::Bool: - return v.b; + return v.toBool(); case QJsonValue::Double: { qint64 i; - if (convertDoubleTo(v.dbl, &i)) + const double dbl = v.toDouble(); + if (convertDoubleTo(dbl, &i)) return i; - return v.dbl; + return dbl; } case QJsonValue::String: return v.toString(); @@ -710,8 +727,10 @@ QCborValue QCborValue::fromVariant(const QVariant &variant) return variant.toByteArray(); case QVariant::DateTime: return QCborValue(variant.toDateTime()); +#ifndef QT_BOOTSTRAPPED case QVariant::Url: return QCborValue(variant.toUrl()); +#endif case QVariant::Uuid: return QCborValue(variant.toUuid()); case QVariant::List: @@ -824,15 +843,9 @@ QCborArray QCborArray::fromVariantList(const QVariantList &list) */ QCborArray QCborArray::fromJsonArray(const QJsonArray &array) { - QCborArray a; - a.detach(array.size()); - for (const QJsonValue &v : array) { - if (v.isString()) - a.d->append(v.toString()); - else - a.d->append(QCborValue::fromJsonValue(v)); - } - return a; + QCborArray result; + result.d = array.a; + return result; } /*! @@ -944,20 +957,9 @@ QCborMap QCborMap::fromVariantHash(const QVariantHash &hash) */ QCborMap QCborMap::fromJsonObject(const QJsonObject &obj) { - QCborMap m; - m.detach(obj.size()); - QCborContainerPrivate *d = m.d.data(); - - auto it = obj.begin(); - auto end = obj.end(); - for ( ; it != end; ++it) { - d->append(it.key()); - if (it.value().isString()) - d->append(it.value().toString()); - else - d->append(QCborValue::fromJsonValue(it.value())); - } - return m; + QCborMap result; + result.d = obj.o; + return result; } QT_END_NAMESPACE diff --git a/src/corelib/serialization/qjsondocument.cpp b/src/corelib/serialization/qjsondocument.cpp index 8c3818caff..b9b1902f34 100644 --- a/src/corelib/serialization/qjsondocument.cpp +++ b/src/corelib/serialization/qjsondocument.cpp @@ -44,11 +44,22 @@ #include #include #include +#include +#include +#include "qcborvalue_p.h" #include "qjsonwriter_p.h" #include "qjsonparser_p.h" #include "qjson_p.h" #include "qdatastream.h" +#if QT_CONFIG(binaryjson) +#include "qbinaryjson_p.h" +#include "qbinaryjsonobject_p.h" +#include "qbinaryjsonarray_p.h" +#endif + +#include + QT_BEGIN_NAMESPACE /*! \class QJsonDocument @@ -80,6 +91,33 @@ QT_BEGIN_NAMESPACE \sa {JSON Support in Qt}, {JSON Save Game Example} */ + +class QJsonDocumentPrivate +{ + Q_DISABLE_COPY_MOVE(QJsonDocumentPrivate); +public: + QJsonDocumentPrivate() = default; + QJsonDocumentPrivate(QCborValue data) : value(std::move(data)) {} + ~QJsonDocumentPrivate() + { + if (rawData) + free(rawData); + } + + QCborValue value; + char *rawData = nullptr; + uint rawDataSize = 0; + + void clearRawData() + { + if (rawData) { + free(rawData); + rawData = nullptr; + rawDataSize = 0; + } + } +}; + /*! * Constructs an empty and invalid document. */ @@ -109,11 +147,10 @@ QJsonDocument::QJsonDocument(const QJsonArray &array) /*! \internal */ -QJsonDocument::QJsonDocument(QJsonPrivate::Data *data) - : d(data) +QJsonDocument::QJsonDocument(const QCborValue &data) + : d(qt_make_unique(data)) { Q_ASSERT(d); - d->ref.ref(); } /*! @@ -121,20 +158,30 @@ QJsonDocument::QJsonDocument(QJsonPrivate::Data *data) Binary data set with fromRawData is not freed. */ -QJsonDocument::~QJsonDocument() -{ - if (d && !d->ref.deref()) - delete d; -} +QJsonDocument::~QJsonDocument() = default; /*! * Creates a copy of the \a other document. */ QJsonDocument::QJsonDocument(const QJsonDocument &other) { - d = other.d; - if (d) - d->ref.ref(); + if (other.d) { + if (!d) + d = qt_make_unique(); + d->value = other.d->value; + } else { + d.reset(); + } +} + +QJsonDocument::QJsonDocument(QJsonDocument &&other) noexcept + : d(std::move(other.d)) +{ +} + +void QJsonDocument::swap(QJsonDocument &other) noexcept +{ + qSwap(d, other.d); } /*! @@ -143,14 +190,17 @@ QJsonDocument::QJsonDocument(const QJsonDocument &other) */ QJsonDocument &QJsonDocument::operator =(const QJsonDocument &other) { - if (d != other.d) { - if (d && !d->ref.deref()) - delete d; - d = other.d; - if (d) - d->ref.ref(); + if (this != &other) { + if (other.d) { + if (!d) + d = qt_make_unique(); + else + d->clearRawData(); + d->value = other.d->value; + } else { + d.reset(); + } } - return *this; } @@ -187,12 +237,13 @@ QJsonDocument &QJsonDocument::operator =(const QJsonDocument &other) the application. */ +#if QT_CONFIG(binaryjson) /*! Creates a QJsonDocument that uses the first \a size bytes from \a data. It assumes \a data contains a binary encoded JSON document. - The created document does not take ownership of \a data and the caller - has to guarantee that \a data will not be deleted or modified as long as - any QJsonDocument, QJsonObject or QJsonArray still references the data. + The created document does not take ownership of \a data. The data is + copied into a different data structure, and the original data can be + deleted or modified afterwards. \a data has to be aligned to a 4 byte boundary. @@ -202,7 +253,18 @@ QJsonDocument &QJsonDocument::operator =(const QJsonDocument &other) Returns a QJsonDocument representing the data. - \sa rawData(), fromBinaryData(), isNull(), DataValidation + \deprecated in Qt 5.15. The binary JSON encoding is only retained for backwards + compatibility. It is undocumented and restrictive in the maximum size of JSON + documents that can be encoded. Qt JSON types can be converted to Qt CBOR types, + which can in turn be serialized into the CBOR binary format and vice versa. The + CBOR format is a well-defined and less restrictive binary representation for a + superset of JSON. + + \note Before Qt 5.15, the caller had to guarantee that \a data would not be + deleted or modified as long as any QJsonDocument, QJsonObject or QJsonArray + still referenced the data. From Qt 5.15 on, this is not necessary anymore. + + \sa rawData(), fromBinaryData(), isNull(), DataValidation, QCborValue */ QJsonDocument QJsonDocument::fromRawData(const char *data, int size, DataValidation validation) { @@ -211,18 +273,15 @@ QJsonDocument QJsonDocument::fromRawData(const char *data, int size, DataValidat return QJsonDocument(); } - if (size < (int)(sizeof(QJsonPrivate::Header) + sizeof(QJsonPrivate::Base))) + if (size < 0 || uint(size) < sizeof(QBinaryJsonPrivate::Header) + sizeof(QBinaryJsonPrivate::Base)) return QJsonDocument(); - QJsonPrivate::Data *d = new QJsonPrivate::Data((char *)data, size); - d->ownsData = false; + std::unique_ptr binaryData + = qt_make_unique(data, size); - if (validation != BypassValidation && !d->valid()) { - delete d; - return QJsonDocument(); - } - - return QJsonDocument(d); + return (validation == BypassValidation || binaryData->isValid()) + ? binaryData->toJsonDocument() + : QJsonDocument(); } /*! @@ -230,7 +289,16 @@ QJsonDocument QJsonDocument::fromRawData(const char *data, int size, DataValidat \a size will contain the size of the returned data. This method is useful to e.g. stream the JSON document - in it's binary form to a file. + in its binary form to a file. + + \deprecated in Qt 5.15. The binary JSON encoding is only retained for backwards + compatibility. It is undocumented and restrictive in the maximum size of JSON + documents that can be encoded. Qt JSON types can be converted to Qt CBOR types, + which can in turn be serialized into the CBOR binary format and vice versa. The + CBOR format is a well-defined and less restrictive binary representation for a + superset of JSON. + + \sa QCborValue */ const char *QJsonDocument::rawData(int *size) const { @@ -238,7 +306,21 @@ const char *QJsonDocument::rawData(int *size) const *size = 0; return nullptr; } - *size = d->alloc; + + if (!d->rawData) { + if (isObject()) { + QBinaryJsonObject o = QBinaryJsonObject::fromJsonObject(object()); + d->rawData = o.takeRawData(&(d->rawDataSize)); + } else { + QBinaryJsonArray a = QBinaryJsonArray::fromJsonArray(array()); + d->rawData = a.takeRawData(&(d->rawDataSize)); + } + } + + // It would be quite miraculous if not, as we should have hit the 128MB limit then. + Q_ASSERT(d->rawDataSize <= uint(std::numeric_limits::max())); + + *size = d->rawDataSize; return d->rawData; } @@ -249,39 +331,65 @@ const char *QJsonDocument::rawData(int *size) const By default the data is validated. If the \a data is not valid, the method returns a null document. - \sa toBinaryData(), fromRawData(), isNull(), DataValidation + \deprecated in Qt 5.15. The binary JSON encoding is only retained for backwards + compatibility. It is undocumented and restrictive in the maximum size of JSON + documents that can be encoded. Qt JSON types can be converted to Qt CBOR types, + which can in turn be serialized into the CBOR binary format and vice versa. The + CBOR format is a well-defined and less restrictive binary representation for a + superset of JSON. + + \sa toBinaryData(), fromRawData(), isNull(), DataValidation, QCborValue */ QJsonDocument QJsonDocument::fromBinaryData(const QByteArray &data, DataValidation validation) { - if (data.size() < (int)(sizeof(QJsonPrivate::Header) + sizeof(QJsonPrivate::Base))) + if (uint(data.size()) < sizeof(QBinaryJsonPrivate::Header) + sizeof(QBinaryJsonPrivate::Base)) return QJsonDocument(); - QJsonPrivate::Header h; - memcpy(&h, data.constData(), sizeof(QJsonPrivate::Header)); - QJsonPrivate::Base root; - memcpy(&root, data.constData() + sizeof(QJsonPrivate::Header), sizeof(QJsonPrivate::Base)); + QBinaryJsonPrivate::Header h; + memcpy(&h, data.constData(), sizeof(QBinaryJsonPrivate::Header)); + QBinaryJsonPrivate::Base root; + memcpy(&root, data.constData() + sizeof(QBinaryJsonPrivate::Header), + sizeof(QBinaryJsonPrivate::Base)); - // do basic checks here, so we don't try to allocate more memory than we can. - if (h.tag != QJsonDocument::BinaryFormatTag || h.version != 1u || - sizeof(QJsonPrivate::Header) + root.size > (uint)data.size()) + const uint size = sizeof(QBinaryJsonPrivate::Header) + root.size; + if (h.tag != QJsonDocument::BinaryFormatTag || h.version != 1U || size > uint(data.size())) return QJsonDocument(); - const uint size = sizeof(QJsonPrivate::Header) + root.size; - char *raw = (char *)malloc(size); - if (!raw) - return QJsonDocument(); + std::unique_ptr d + = qt_make_unique(data.constData(), size); - memcpy(raw, data.constData(), size); - QJsonPrivate::Data *d = new QJsonPrivate::Data(raw, size); - - if (validation != BypassValidation && !d->valid()) { - delete d; - return QJsonDocument(); - } - - return QJsonDocument(d); + return (validation == BypassValidation || d->isValid()) + ? d->toJsonDocument() + : QJsonDocument(); } +/*! + Returns a binary representation of the document. + + The binary representation is also the native format used internally in Qt, + and is very efficient and fast to convert to and from. + + The binary format can be stored on disk and interchanged with other applications + or computers. fromBinaryData() can be used to convert it back into a + JSON document. + + \deprecated in Qt 5.15. The binary JSON encoding is only retained for backwards + compatibility. It is undocumented and restrictive in the maximum size of JSON + documents that can be encoded. Qt JSON types can be converted to Qt CBOR types, + which can in turn be serialized into the CBOR binary format and vice versa. The + CBOR format is a well-defined and less restrictive binary representation for a + superset of JSON. + + \sa fromBinaryData(), QCborValue + */ +QByteArray QJsonDocument::toBinaryData() const +{ + int size = 0; + const char *raw = rawData(&size); + return QByteArray(raw, size); +} +#endif // QT_CONFIG(binaryjson) + /*! Creates a QJsonDocument from the QVariant \a variant. @@ -293,6 +401,7 @@ QJsonDocument QJsonDocument::fromBinaryData(const QByteArray &data, DataValidati QJsonDocument QJsonDocument::fromVariant(const QVariant &variant) { QJsonDocument doc; + switch (variant.type()) { case QVariant::Map: doc.setObject(QJsonObject::fromVariantMap(variant.toMap())); @@ -304,7 +413,8 @@ QJsonDocument QJsonDocument::fromVariant(const QVariant &variant) doc.setArray(QJsonArray::fromVariantList(variant.toList())); break; case QVariant::StringList: - doc.setArray(QJsonArray::fromStringList(variant.toStringList())); + doc.d = qt_make_unique(); + doc.d->value = QCborArray::fromStringList(variant.toStringList()); break; default: break; @@ -325,10 +435,10 @@ QVariant QJsonDocument::toVariant() const if (!d) return QVariant(); - if (d->header->root()->isArray()) - return QJsonArray(d, static_cast(d->header->root())).toVariantList(); - else - return QJsonObject(d, static_cast(d->header->root())).toVariantMap(); + QCborContainerPrivate *container = QJsonPrivate::Value::container(d->value); + if (d->value.isArray()) + return QJsonArray(container).toVariantList(); + return QJsonObject(container).toVariantMap(); } /*! @@ -370,10 +480,11 @@ QByteArray QJsonDocument::toJson(JsonFormat format) const if (!d) return json; - if (d->header->root()->isArray()) - QJsonPrivate::Writer::arrayToJson(static_cast(d->header->root()), json, 0, (format == Compact)); + const QCborContainerPrivate *container = QJsonPrivate::Value::container(d->value); + if (d->value.isArray()) + QJsonPrivate::Writer::arrayToJson(container, json, 0, (format == Compact)); else - QJsonPrivate::Writer::objectToJson(static_cast(d->header->root()), json, 0, (format == Compact)); + QJsonPrivate::Writer::objectToJson(container, json, 0, (format == Compact)); return json; } @@ -392,7 +503,13 @@ QByteArray QJsonDocument::toJson(JsonFormat format) const QJsonDocument QJsonDocument::fromJson(const QByteArray &json, QJsonParseError *error) { QJsonPrivate::Parser parser(json.constData(), json.length()); - return parser.parse(error); + QJsonDocument result; + const QCborValue val = parser.parse(error); + if (val.isArray() || val.isMap()) { + result.d = qt_make_unique(); + result.d->value = val; + } + return result; } /*! @@ -406,26 +523,6 @@ bool QJsonDocument::isEmpty() const return false; } -/*! - Returns a binary representation of the document. - - The binary representation is also the native format used internally in Qt, - and is very efficient and fast to convert to and from. - - The binary format can be stored on disk and interchanged with other applications - or computers. fromBinaryData() can be used to convert it back into a - JSON document. - - \sa fromBinaryData() - */ -QByteArray QJsonDocument::toBinaryData() const -{ - if (!d || !d->rawData) - return QByteArray(); - - return QByteArray(d->rawData, d->header->root()->size + sizeof(QJsonPrivate::Header)); -} - /*! Returns \c true if the document contains an array. @@ -436,8 +533,7 @@ bool QJsonDocument::isArray() const if (!d) return false; - QJsonPrivate::Header *h = (QJsonPrivate::Header *)d->rawData; - return h->root()->isArray(); + return d->value.isArray(); } /*! @@ -450,8 +546,7 @@ bool QJsonDocument::isObject() const if (!d) return false; - QJsonPrivate::Header *h = (QJsonPrivate::Header *)d->rawData; - return h->root()->isObject(); + return d->value.isMap(); } /*! @@ -464,10 +559,9 @@ bool QJsonDocument::isObject() const */ QJsonObject QJsonDocument::object() const { - if (d) { - QJsonPrivate::Base *b = d->header->root(); - if (b->isObject()) - return QJsonObject(d, static_cast(b)); + if (isObject()) { + if (auto container = QJsonPrivate::Value::container(d->value)) + return QJsonObject(container); } return QJsonObject(); } @@ -482,10 +576,9 @@ QJsonObject QJsonDocument::object() const */ QJsonArray QJsonDocument::array() const { - if (d) { - QJsonPrivate::Base *b = d->header->root(); - if (b->isArray()) - return QJsonArray(d, static_cast(b)); + if (isArray()) { + if (auto container = QJsonPrivate::Value::container(d->value)) + return QJsonArray(container); } return QJsonArray(); } @@ -497,24 +590,12 @@ QJsonArray QJsonDocument::array() const */ void QJsonDocument::setObject(const QJsonObject &object) { - if (d && !d->ref.deref()) - delete d; + if (!d) + d = qt_make_unique(); + else + d->clearRawData(); - d = object.d; - - if (!d) { - d = new QJsonPrivate::Data(0, QJsonValue::Object); - } else if (d->compactionCounter || object.o != d->header->root()) { - QJsonObject o(object); - if (d->compactionCounter) - o.compact(); - else - o.detach2(); - d = o.d; - d->ref.ref(); - return; - } - d->ref.ref(); + d->value = QCborValue::fromJsonValue(object); } /*! @@ -524,24 +605,12 @@ void QJsonDocument::setObject(const QJsonObject &object) */ void QJsonDocument::setArray(const QJsonArray &array) { - if (d && !d->ref.deref()) - delete d; + if (!d) + d = qt_make_unique(); + else + d->clearRawData(); - d = array.d; - - if (!d) { - d = new QJsonPrivate::Data(0, QJsonValue::Array); - } else if (d->compactionCounter || array.a != d->header->root()) { - QJsonArray a(array); - if (d->compactionCounter) - a.compact(); - else - a.detach2(); - d = a.d; - d->ref.ref(); - return; - } - d->ref.ref(); + d->value = QCborValue::fromJsonValue(array); } #if QT_STRINGVIEW_LEVEL < 2 @@ -572,7 +641,7 @@ const QJsonValue QJsonDocument::operator[](QStringView key) const if (!isObject()) return QJsonValue(QJsonValue::Undefined); - return object().value(key); + return QJsonPrivate::Value::fromTrustedCbor(d->value.toMap().value(key)); } /*! @@ -584,7 +653,7 @@ const QJsonValue QJsonDocument::operator[](QLatin1String key) const if (!isObject()) return QJsonValue(QJsonValue::Undefined); - return object().value(key); + return QJsonPrivate::Value::fromTrustedCbor(d->value.toMap().value(key)); } /*! @@ -604,7 +673,7 @@ const QJsonValue QJsonDocument::operator[](int i) const if (!isArray()) return QJsonValue(QJsonValue::Undefined); - return array().at(i); + return QJsonPrivate::Value::fromTrustedCbor(d->value.toArray().at(i)); } /*! @@ -612,21 +681,7 @@ const QJsonValue QJsonDocument::operator[](int i) const */ bool QJsonDocument::operator==(const QJsonDocument &other) const { - if (d == other.d) - return true; - - if (!d || !other.d) - return false; - - if (d->header->root()->isArray() != other.d->header->root()->isArray()) - return false; - - if (d->header->root()->isObject()) - return QJsonObject(d, static_cast(d->header->root())) - == QJsonObject(other.d, static_cast(other.d->header->root())); - else - return QJsonArray(d, static_cast(d->header->root())) - == QJsonArray(other.d, static_cast(other.d->header->root())); + return (!d) ? (!other.d) : (d->value == other.d->value); } /*! @@ -658,10 +713,11 @@ QDebug operator<<(QDebug dbg, const QJsonDocument &o) return dbg; } QByteArray json; - if (o.d->header->root()->isArray()) - QJsonPrivate::Writer::arrayToJson(static_cast(o.d->header->root()), json, 0, true); + const QCborContainerPrivate *container = QJsonPrivate::Value::container(o.d->value); + if (o.d->value.isArray()) + QJsonPrivate::Writer::arrayToJson(container, json, 0, true); else - QJsonPrivate::Writer::objectToJson(static_cast(o.d->header->root()), json, 0, true); + QJsonPrivate::Writer::objectToJson(container, json, 0, true); dbg.nospace() << "QJsonDocument(" << json.constData() // print as utf-8 string without extra quotation marks << ')'; diff --git a/src/corelib/serialization/qjsondocument.h b/src/corelib/serialization/qjsondocument.h index a8e7485f5d..325e47b531 100644 --- a/src/corelib/serialization/qjsondocument.h +++ b/src/corelib/serialization/qjsondocument.h @@ -41,14 +41,16 @@ #define QJSONDOCUMENT_H #include +#include + +#include QT_BEGIN_NAMESPACE class QDebug; +class QCborValue; -namespace QJsonPrivate { - class Parser; -} +namespace QJsonPrivate { class Parser; } struct Q_CORE_EXPORT QJsonParseError { @@ -76,6 +78,7 @@ struct Q_CORE_EXPORT QJsonParseError ParseError error; }; +class QJsonDocumentPrivate; class Q_CORE_EXPORT QJsonDocument { public: @@ -93,11 +96,7 @@ public: QJsonDocument(const QJsonDocument &other); QJsonDocument &operator =(const QJsonDocument &other); - QJsonDocument(QJsonDocument &&other) noexcept - : d(other.d) - { - other.d = nullptr; - } + QJsonDocument(QJsonDocument &&other) noexcept; QJsonDocument &operator =(QJsonDocument &&other) noexcept { @@ -105,21 +104,20 @@ public: return *this; } - void swap(QJsonDocument &other) noexcept - { - qSwap(d, other.d); - } + void swap(QJsonDocument &other) noexcept; enum DataValidation { Validate, BypassValidation }; +#if QT_CONFIG(binaryjson) static QJsonDocument fromRawData(const char *data, int size, DataValidation validation = Validate); const char *rawData(int *size) const; static QJsonDocument fromBinaryData(const QByteArray &data, DataValidation validation = Validate); QByteArray toBinaryData() const; +#endif // QT_CONFIG(binaryjson) static QJsonDocument fromVariant(const QVariant &variant); QVariant toVariant() const; @@ -160,13 +158,12 @@ public: private: friend class QJsonValue; - friend class QJsonPrivate::Data; friend class QJsonPrivate::Parser; friend Q_CORE_EXPORT QDebug operator<<(QDebug, const QJsonDocument &); - QJsonDocument(QJsonPrivate::Data *data); + QJsonDocument(const QCborValue &data); - QJsonPrivate::Data *d; + std::unique_ptr d; }; Q_DECLARE_SHARED_NOT_MOVABLE_UNTIL_QT6(QJsonDocument) diff --git a/src/corelib/serialization/qjsonobject.cpp b/src/corelib/serialization/qjsonobject.cpp index 8b095db915..ae37481f31 100644 --- a/src/corelib/serialization/qjsonobject.cpp +++ b/src/corelib/serialization/qjsonobject.cpp @@ -40,11 +40,17 @@ #include #include #include +#include #include #include #include -#include "qjson_p.h" +#include + +#include #include "qjsonwriter_p.h" +#include "qjson_p.h" + +#include QT_BEGIN_NAMESPACE @@ -109,10 +115,7 @@ QT_BEGIN_NAMESPACE \sa isEmpty() */ -QJsonObject::QJsonObject() - : d(nullptr), o(nullptr) -{ -} +QJsonObject::QJsonObject() = default; /*! \fn QJsonObject::QJsonObject(std::initializer_list > args) @@ -131,12 +134,10 @@ QJsonObject::QJsonObject() /*! \internal */ -QJsonObject::QJsonObject(QJsonPrivate::Data *data, QJsonPrivate::Object *object) - : d(data), o(object) +QJsonObject::QJsonObject(QCborContainerPrivate *object) + : o(object) { - Q_ASSERT(d); Q_ASSERT(o); - d->ref.ref(); } /*! @@ -149,17 +150,19 @@ QJsonObject::QJsonObject(QJsonPrivate::Data *data, QJsonPrivate::Object *object) void QJsonObject::initialize() { - d = nullptr; o = nullptr; } /*! Destroys the object. */ -QJsonObject::~QJsonObject() +QJsonObject::~QJsonObject() = default; + +QJsonObject::QJsonObject(std::initializer_list > args) { - if (d && !d->ref.deref()) - delete d; + initialize(); + for (const auto &arg : args) + insert(arg.first, arg.second); } /*! @@ -170,10 +173,13 @@ QJsonObject::~QJsonObject() */ QJsonObject::QJsonObject(const QJsonObject &other) { - d = other.d; o = other.o; - if (d) - d->ref.ref(); +} + +QJsonObject::QJsonObject(QJsonObject &&other) noexcept + : o(other.o) +{ + other.o = nullptr; } /*! @@ -181,15 +187,7 @@ QJsonObject::QJsonObject(const QJsonObject &other) */ QJsonObject &QJsonObject::operator =(const QJsonObject &other) { - if (d != other.d) { - if (d && !d->ref.deref()) - delete d; - d = other.d; - if (d) - d->ref.ref(); - } o = other.o; - return *this; } @@ -225,55 +223,7 @@ QJsonObject &QJsonObject::operator =(const QJsonObject &other) */ QJsonObject QJsonObject::fromVariantMap(const QVariantMap &map) { - QJsonObject object; - if (map.isEmpty()) - return object; - - object.detach2(1024); - - QVector offsets; - QJsonPrivate::offset currentOffset; - currentOffset = sizeof(QJsonPrivate::Base); - - // the map is already sorted, so we can simply append one entry after the other and - // write the offset table at the end - for (QVariantMap::const_iterator it = map.constBegin(); it != map.constEnd(); ++it) { - QString key = it.key(); - QJsonValue val = QJsonValue::fromVariant(it.value()); - - bool latinOrIntValue; - int valueSize = QJsonPrivate::Value::requiredStorage(val, &latinOrIntValue); - - bool latinKey = QJsonPrivate::useCompressed(key); - int valueOffset = sizeof(QJsonPrivate::Entry) + QJsonPrivate::qStringSize(key, latinKey); - int requiredSize = valueOffset + valueSize; - - if (!object.detach2(requiredSize + sizeof(QJsonPrivate::offset))) // offset for the new index entry - return QJsonObject(); - - QJsonPrivate::Entry *e = reinterpret_cast(reinterpret_cast(object.o) + currentOffset); - e->value.type = val.t; - e->value.latinKey = latinKey; - e->value.latinOrIntValue = latinOrIntValue; - e->value.value = QJsonPrivate::Value::valueToStore(val, (char *)e - (char *)object.o + valueOffset); - QJsonPrivate::copyString((char *)(e + 1), key, latinKey); - if (valueSize) - QJsonPrivate::Value::copyData(val, (char *)e + valueOffset, latinOrIntValue); - - offsets << currentOffset; - currentOffset += requiredSize; - object.o->size = currentOffset; - } - - // write table - object.o->tableOffset = currentOffset; - if (!object.detach2(sizeof(QJsonPrivate::offset)*offsets.size())) - return QJsonObject(); - memcpy(object.o->table(), offsets.constData(), offsets.size()*sizeof(uint)); - object.o->length = offsets.size(); - object.o->size = currentOffset + sizeof(QJsonPrivate::offset)*offsets.size(); - - return object; + return QCborMap::fromVariantMap(map).toJsonObject(); } /*! @@ -285,14 +235,7 @@ QJsonObject QJsonObject::fromVariantMap(const QVariantMap &map) */ QVariantMap QJsonObject::toVariantMap() const { - QVariantMap map; - if (o) { - for (uint i = 0; i < o->length; ++i) { - QJsonPrivate::Entry *e = o->entryAt(i); - map.insert(e->key(), QJsonValue(d, o, e->value).toVariant()); - } - } - return map; + return QCborMap::fromJsonObject(*this).toVariantMap(); } /*! @@ -324,15 +267,7 @@ QJsonObject QJsonObject::fromVariantHash(const QVariantHash &hash) */ QVariantHash QJsonObject::toVariantHash() const { - QVariantHash hash; - if (o) { - hash.reserve(o->length); - for (uint i = 0; i < o->length; ++i) { - QJsonPrivate::Entry *e = o->entryAt(i); - hash.insert(e->key(), QJsonValue(d, o, e->value).toVariant()); - } - } - return hash; + return QCborMap::fromJsonObject(*this).toVariantHash(); } /*! @@ -344,11 +279,9 @@ QStringList QJsonObject::keys() const { QStringList keys; if (o) { - keys.reserve(o->length); - for (uint i = 0; i < o->length; ++i) { - QJsonPrivate::Entry *e = o->entryAt(i); - keys.append(e->key()); - } + keys.reserve(o->elements.length() / 2); + for (int i = 0, end = o->elements.length(); i < end; i += 2) + keys.append(o->stringAt(i)); } return keys; } @@ -358,10 +291,7 @@ QStringList QJsonObject::keys() const */ int QJsonObject::size() const { - if (!d) - return 0; - - return o->length; + return o ? o->elements.length() / 2 : 0; } /*! @@ -371,10 +301,24 @@ int QJsonObject::size() const */ bool QJsonObject::isEmpty() const { - if (!d) - return true; + return !o || o->elements.isEmpty(); +} - return !o->length; +template +static int indexOf(const QExplicitlySharedDataPointer &o, + String key, bool *keyExists) +{ + const auto begin = QJsonPrivate::ConstKeyIterator(o->elements.constBegin()); + const auto end = QJsonPrivate::ConstKeyIterator(o->elements.constEnd()); + + const auto it = std::lower_bound( + begin, end, key, + [&](const QJsonPrivate::ConstKeyIterator::value_type &e, const String &key) { + return o->stringCompareElement(e.key(), key) < 0; + }); + + *keyExists = (it != end) && o->stringEqualsElement((*it).key(), key); + return (it - begin) * 2; } #if QT_STRINGVIEW_LEVEL < 2 @@ -415,14 +359,14 @@ QJsonValue QJsonObject::value(QLatin1String key) const template QJsonValue QJsonObject::valueImpl(T key) const { - if (!d) + if (!o) return QJsonValue(QJsonValue::Undefined); bool keyExists; - int i = o->indexOf(key, &keyExists); + int i = indexOf(o, key, &keyExists); if (!keyExists) return QJsonValue(QJsonValue::Undefined); - return QJsonValue(d, o, o->entryAt(i)->value); + return QJsonPrivate::Value::fromTrustedCbor(o->valueAt(i + 1)); } #if QT_STRINGVIEW_LEVEL < 2 @@ -497,13 +441,16 @@ QJsonValueRef QJsonObject::operator [](QLatin1String key) template QJsonValueRef QJsonObject::atImpl(T key) { + if (!o) + o = new QCborContainerPrivate; + bool keyExists = false; - int index = o ? o->indexOf(key, &keyExists) : 0; + int index = indexOf(o, key, &keyExists); if (!keyExists) { - iterator i = insertAt(index, key, QJsonValue(), false); - index = i.i; + o->insertAt(index, key); + o->insertAt(index + 1, QCborValue::fromJsonValue(QJsonValue())); } - return QJsonValueRef(this, index); + return QJsonValueRef(this, index / 2); } #if QT_STRINGVIEW_LEVEL < 2 @@ -550,12 +497,12 @@ QJsonObject::iterator QJsonObject::insert(QLatin1String key, const QJsonValue &v template QJsonObject::iterator QJsonObject::insertImpl(T key, const QJsonValue &value) { - if (value.t == QJsonValue::Undefined) { + if (value.type() == QJsonValue::Undefined) { remove(key); return end(); } bool keyExists = false; - int pos = o ? o->indexOf(key, &keyExists) : 0; + int pos = o ? indexOf(o, key, &keyExists) : 0; return insertAt(pos, key, value, keyExists); } @@ -565,40 +512,18 @@ QJsonObject::iterator QJsonObject::insertImpl(T key, const QJsonValue &value) template QJsonObject::iterator QJsonObject::insertAt(int pos, T key, const QJsonValue &value, bool keyExists) { - QJsonValue val = value; + if (o) + detach2(o->elements.length() / 2 + (keyExists ? 0 : 1)); + else + o = new QCborContainerPrivate; - bool latinOrIntValue; - int valueSize = QJsonPrivate::Value::requiredStorage(val, &latinOrIntValue); - - bool latinKey = QJsonPrivate::useCompressed(key); - int valueOffset = sizeof(QJsonPrivate::Entry) + QJsonPrivate::qStringSize(key, latinKey); - int requiredSize = valueOffset + valueSize; - - if (!detach2(requiredSize + sizeof(QJsonPrivate::offset))) // offset for the new index entry - return iterator(); - - if (!o->length) - o->tableOffset = sizeof(QJsonPrivate::Object); - - if (keyExists) - ++d->compactionCounter; - - uint off = o->reserveSpace(requiredSize, pos, 1, keyExists); - if (!off) - return end(); - - QJsonPrivate::Entry *e = o->entryAt(pos); - e->value.type = val.t; - e->value.latinKey = latinKey; - e->value.latinOrIntValue = latinOrIntValue; - e->value.value = QJsonPrivate::Value::valueToStore(val, (char *)e - (char *)o + valueOffset); - QJsonPrivate::copyString((char *)(e + 1), key, latinKey); - if (valueSize) - QJsonPrivate::Value::copyData(val, (char *)e + valueOffset, latinOrIntValue); - - compactIfNeeded(); - - return iterator(this, pos); + if (keyExists) { + o->replaceAt(pos + 1, QCborValue::fromJsonValue(value)); + } else { + o->insertAt(pos, key); + o->insertAt(pos + 1, QCborValue::fromJsonValue(value)); + } + return {this, pos / 2}; } #if QT_STRINGVIEW_LEVEL < 2 @@ -637,11 +562,11 @@ void QJsonObject::remove(QLatin1String key) template void QJsonObject::removeImpl(T key) { - if (!d) + if (!o) return; bool keyExists; - int index = o->indexOf(key, &keyExists); + int index = indexOf(o, key, &keyExists); if (!keyExists) return; @@ -692,13 +617,12 @@ QJsonValue QJsonObject::takeImpl(T key) return QJsonValue(QJsonValue::Undefined); bool keyExists; - int index = o->indexOf(key, &keyExists); + int index = indexOf(o, key, &keyExists); if (!keyExists) return QJsonValue(QJsonValue::Undefined); - QJsonValue v(d, o, o->entryAt(index)->value); + const QJsonValue v = QJsonPrivate::Value::fromTrustedCbor(o->extractAt(index + 1)); removeAt(index); - return v; } @@ -742,7 +666,7 @@ bool QJsonObject::containsImpl(T key) const return false; bool keyExists; - o->indexOf(key, &keyExists); + indexOf(o, key, &keyExists); return keyExists; } @@ -755,16 +679,14 @@ bool QJsonObject::operator==(const QJsonObject &other) const return true; if (!o) - return !other.o->length; + return !other.o->elements.length(); if (!other.o) - return !o->length; - if (o->length != other.o->length) + return !o->elements.length(); + if (o->elements.length() != other.o->elements.length()) return false; - for (uint i = 0; i < o->length; ++i) { - QJsonPrivate::Entry *e = o->entryAt(i); - QJsonValue v(d, o, e->value); - if (other.value(e->key()) != v) + for (int i = 0, end = o->elements.length(); i < end; ++i) { + if (o->valueAt(i) != other.o->valueAt(i)) return false; } @@ -788,9 +710,8 @@ bool QJsonObject::operator!=(const QJsonObject &other) const */ QJsonObject::iterator QJsonObject::erase(QJsonObject::iterator it) { - Q_ASSERT(d && d->ref.loadRelaxed() == 1); - if (it.o != this || it.i < 0 || it.i >= (int)o->length) - return iterator(this, o->length); + if (it.o != this || it.i < 0 || it.i >= o->elements.length()) + return {this, o->elements.length()}; int index = it.i; @@ -839,11 +760,11 @@ template QJsonObject::iterator QJsonObject::findImpl(T key) { bool keyExists = false; - int index = o ? o->indexOf(key, &keyExists) : 0; + int index = o ? indexOf(o, key, &keyExists) : 0; if (!keyExists) return end(); detach2(); - return iterator(this, index); + return {this, index / 2}; } #if QT_STRINGVIEW_LEVEL < 2 @@ -904,10 +825,10 @@ template QJsonObject::const_iterator QJsonObject::constFindImpl(T key) const { bool keyExists = false; - int index = o ? o->indexOf(key, &keyExists) : 0; + int index = o ? indexOf(o, key, &keyExists) : 0; if (!keyExists) return end(); - return const_iterator(this, index); + return {this, index / 2}; } /*! \fn int QJsonObject::count() const @@ -1500,28 +1421,10 @@ void QJsonObject::detach(uint reserve) bool QJsonObject::detach2(uint reserve) { - if (!d) { - if (reserve >= QJsonPrivate::Value::MaxSize) { - qWarning("QJson: Document too large to store in data structure"); - return false; - } - d = new QJsonPrivate::Data(reserve, QJsonValue::Object); - o = static_cast(d->header->root()); - d->ref.ref(); + if (!o) return true; - } - if (reserve == 0 && d->ref.loadRelaxed() == 1) - return true; - - QJsonPrivate::Data *x = d->clone(o, reserve); - if (!x) - return false; - x->ref.ref(); - if (!d->ref.deref()) - delete d; - d = x; - o = static_cast(d->header->root()); - return true; + o = QCborContainerPrivate::detach(o.data(), reserve ? reserve * 2 : o->elements.length()); + return o; } /*! @@ -1529,21 +1432,11 @@ bool QJsonObject::detach2(uint reserve) */ void QJsonObject::compact() { - if (!d || !d->compactionCounter) + if (!o) return; detach2(); - d->compact(); - o = static_cast(d->header->root()); -} - -/*! - \internal - */ -void QJsonObject::compactIfNeeded() -{ - if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(o->length) / 2u) - compact(); + o->compact(o->elements.length()); } /*! @@ -1551,10 +1444,8 @@ void QJsonObject::compactIfNeeded() */ QString QJsonObject::keyAt(int i) const { - Q_ASSERT(o && i >= 0 && i < (int)o->length); - - QJsonPrivate::Entry *e = o->entryAt(i); - return e->key(); + Q_ASSERT(o && i >= 0 && i * 2 < o->elements.length()); + return o->stringAt(i * 2); } /*! @@ -1562,11 +1453,9 @@ QString QJsonObject::keyAt(int i) const */ QJsonValue QJsonObject::valueAt(int i) const { - if (!o || i < 0 || i >= (int)o->length) + if (!o || i < 0 || 2 * i + 1 >= o->elements.length()) return QJsonValue(QJsonValue::Undefined); - - QJsonPrivate::Entry *e = o->entryAt(i); - return QJsonValue(d, o, e->value); + return QJsonPrivate::Value::fromTrustedCbor(o->valueAt(2 * i + 1)); } /*! @@ -1574,13 +1463,13 @@ QJsonValue QJsonObject::valueAt(int i) const */ void QJsonObject::setValueAt(int i, const QJsonValue &val) { - Q_ASSERT(o && i >= 0 && i < (int)o->length); - - QJsonPrivate::Entry *e = o->entryAt(i); - if (val.t == QJsonValue::Undefined) - removeAt(i); - else - insertAt(i, e->key(), val, true); + Q_ASSERT(o && i >= 0 && 2 * i + 1 < o->elements.length()); + if (val.isUndefined()) { + o->removeAt(2 * i + 1); + o->removeAt(2 * i); + } else { + o->replaceAt(2 * i + 1, QCborValue::fromJsonValue(val)); + } } /*! @@ -1589,9 +1478,8 @@ void QJsonObject::setValueAt(int i, const QJsonValue &val) void QJsonObject::removeAt(int index) { detach2(); - o->removeItems(index, 1); - ++d->compactionCounter; - compactIfNeeded(); + o->removeAt(index + 1); + o->removeAt(index); } uint qHash(const QJsonObject &object, uint seed) @@ -1614,7 +1502,7 @@ QDebug operator<<(QDebug dbg, const QJsonObject &o) return dbg; } QByteArray json; - QJsonPrivate::Writer::objectToJson(o.o, json, 0, true); + QJsonPrivate::Writer::objectToJson(o.o.data(), json, 0, true); dbg.nospace() << "QJsonObject(" << json.constData() // print as utf-8 string without extra quotation marks << ")"; diff --git a/src/corelib/serialization/qjsonobject.h b/src/corelib/serialization/qjsonobject.h index 53db1e1c08..cfb44c9c55 100644 --- a/src/corelib/serialization/qjsonobject.h +++ b/src/corelib/serialization/qjsonobject.h @@ -43,6 +43,7 @@ #include #include #include +#include #include QT_BEGIN_NAMESPACE @@ -53,29 +54,21 @@ typedef QMap QVariantMap; template class QHash; typedef QHash QVariantHash; +class QCborContainerPrivate; + class Q_CORE_EXPORT QJsonObject { public: QJsonObject(); - QJsonObject(std::initializer_list > args) - { - initialize(); - for (std::initializer_list >::const_iterator i = args.begin(); i != args.end(); ++i) - insert(i->first, i->second); - } + QJsonObject(std::initializer_list > args); ~QJsonObject(); QJsonObject(const QJsonObject &other); QJsonObject &operator =(const QJsonObject &other); - QJsonObject(QJsonObject &&other) noexcept - : d(other.d), o(other.o) - { - other.d = nullptr; - other.o = nullptr; - } + QJsonObject(QJsonObject &&other) noexcept; QJsonObject &operator =(QJsonObject &&other) noexcept { @@ -85,7 +78,6 @@ public: void swap(QJsonObject &other) noexcept { - qSwap(d, other.d); qSwap(o, other.o); } @@ -275,20 +267,18 @@ public: inline bool empty() const { return isEmpty(); } private: - friend class QJsonPrivate::Data; friend class QJsonValue; friend class QJsonDocument; friend class QJsonValueRef; - + friend class QCborMap; friend Q_CORE_EXPORT QDebug operator<<(QDebug, const QJsonObject &); - QJsonObject(QJsonPrivate::Data *data, QJsonPrivate::Object *object); + QJsonObject(QCborContainerPrivate *object); void initialize(); // ### Qt 6: remove me and merge with detach2 void detach(uint reserve = 0); bool detach2(uint reserve = 0); void compact(); - void compactIfNeeded(); template QJsonValue valueImpl(T key) const; template QJsonValueRef atImpl(T key); @@ -305,8 +295,9 @@ private: void removeAt(int i); template iterator insertAt(int i, T key, const QJsonValue &val, bool exists); - QJsonPrivate::Data *d; - QJsonPrivate::Object *o; + // ### Qt 6: remove + void *dead = nullptr; + QExplicitlySharedDataPointer o; }; Q_DECLARE_SHARED_NOT_MOVABLE_UNTIL_QT6(QJsonObject) diff --git a/src/corelib/serialization/qjsonparser.cpp b/src/corelib/serialization/qjsonparser.cpp index cd36bd5a5b..6d0a92e094 100644 --- a/src/corelib/serialization/qjsonparser.cpp +++ b/src/corelib/serialization/qjsonparser.cpp @@ -45,6 +45,8 @@ #include "qjsonparser_p.h" #include "qjson_p.h" #include "private/qutfcodec_p.h" +#include "private/qcborvalue_p.h" +#include "private/qnumeric_p.h" //#define PARSER_DEBUG #ifdef PARSER_DEBUG @@ -197,9 +199,32 @@ QString QJsonParseError::errorString() const using namespace QJsonPrivate; +class StashedContainer +{ + Q_DISABLE_COPY_MOVE(StashedContainer) +public: + StashedContainer(QExplicitlySharedDataPointer *container, + QCborValue::Type type) + : type(type), stashed(std::move(*container)), current(container) + { + } + + ~StashedContainer() + { + stashed->append(QCborContainerPrivate::makeValue(type, -1, current->take(), + QCborContainerPrivate::MoveContainer)); + *current = std::move(stashed); + } + +private: + QCborValue::Type type; + QExplicitlySharedDataPointer stashed; + QExplicitlySharedDataPointer *current; +}; + Parser::Parser(const char *json, int length) - : head(json), json(json), data(nullptr) - , dataLength(0), current(0), nestingLevel(0) + : head(json), json(json) + , nestingLevel(0) , lastError(QJsonParseError::NoError) { end = json + length; @@ -297,34 +322,30 @@ char Parser::nextToken() /* JSON-text = object / array */ -QJsonDocument Parser::parse(QJsonParseError *error) +QCborValue Parser::parse(QJsonParseError *error) { #ifdef PARSER_DEBUG indent = 0; qDebug(">>>>> parser begin"); #endif - // allocate some space - dataLength = qMax(end - json, (ptrdiff_t) 256); - data = (char *)malloc(dataLength); - Q_CHECK_PTR(data); - - // fill in Header data - QJsonPrivate::Header *h = (QJsonPrivate::Header *)data; - h->tag = QJsonDocument::BinaryFormatTag; - h->version = 1u; - - current = sizeof(QJsonPrivate::Header); - eatBOM(); char token = nextToken(); + QCborValue data; + DEBUG << Qt::hex << (uint)token; if (token == BeginArray) { + container = new QCborContainerPrivate; if (!parseArray()) goto error; + data = QCborContainerPrivate::makeValue(QCborValue::Array, -1, container.take(), + QCborContainerPrivate::MoveContainer); } else if (token == BeginObject) { + container = new QCborContainerPrivate; if (!parseObject()) goto error; + data = QCborContainerPrivate::makeValue(QCborValue::Map, -1, container.take(), + QCborContainerPrivate::MoveContainer); } else { lastError = QJsonParseError::IllegalValue; goto error; @@ -342,44 +363,95 @@ QJsonDocument Parser::parse(QJsonParseError *error) error->offset = 0; error->error = QJsonParseError::NoError; } - QJsonPrivate::Data *d = new QJsonPrivate::Data(data, current); - return QJsonDocument(d); + + return data; } error: #ifdef PARSER_DEBUG qDebug(">>>>> parser error"); #endif + container.reset(); if (error) { error->offset = json - head; error->error = lastError; } - free(data); - return QJsonDocument(); + return QCborValue(); } -void Parser::ParsedObject::insert(uint offset) { - const QJsonPrivate::Entry *newEntry = reinterpret_cast(parser->data + objectPosition + offset); - int min = 0; - int n = offsets.size(); - while (n > 0) { - int half = n >> 1; - int middle = min + half; - if (*entryAt(middle) >= *newEntry) { - n = half; - } else { - min = middle + 1; - n -= half + 1; + +static void sortContainer(QCborContainerPrivate *container) +{ + using Forward = QJsonPrivate::KeyIterator; + using Reverse = std::reverse_iterator; + using Value = Forward::value_type; + + auto compare = [container](const Value &a, const Value &b) + { + const auto &aKey = a.key(); + const auto &bKey = b.key(); + + Q_ASSERT(aKey.flags & QtCbor::Element::HasByteData); + Q_ASSERT(bKey.flags & QtCbor::Element::HasByteData); + + const QtCbor::ByteData *aData = container->byteData(aKey); + const QtCbor::ByteData *bData = container->byteData(bKey); + + if (!aData) + return bData ? -1 : 0; + if (!bData) + return 1; + + // If StringIsAscii is set, we can use either the UTF-8 or the latin1 comparison + // for the string as ASCII is a subset of both. If nothing is set, that means UTF-8. + + // We are currently missing an efficient comparison between UTF-8 and UTF-16 strings. + // Therefore, we need to convert the UTF-8 string if we encounter such a case. + + if (aKey.flags & QtCbor::Element::StringIsAscii) { + if (bKey.flags & QtCbor::Element::StringIsAscii) + return QtPrivate::compareStrings(aData->asLatin1(), bData->asLatin1()); + if (bKey.flags & QtCbor::Element::StringIsUtf16) + return QtPrivate::compareStrings(aData->asLatin1(), bData->asStringView()); + + return QCborContainerPrivate::compareUtf8(aData, bData->asLatin1()); } - } - if (min < offsets.size() && *entryAt(min) == *newEntry) { - offsets[min] = offset; - } else { - offsets.insert(min, offset); - } + + if (aKey.flags & QtCbor::Element::StringIsUtf16) { + if (bKey.flags & QtCbor::Element::StringIsAscii) + return QtPrivate::compareStrings(aData->asStringView(), bData->asLatin1()); + if (bKey.flags & QtCbor::Element::StringIsUtf16) + return QtPrivate::compareStrings(aData->asStringView(), bData->asStringView()); + + // Nasty case. a is UTF-16 and b is UTF-8 + return QtPrivate::compareStrings(aData->asStringView(), bData->toUtf8String()); + } + + if (bKey.flags & QtCbor::Element::StringIsAscii) + return QCborContainerPrivate::compareUtf8(aData, bData->asLatin1()); + + // Nasty case. a is UTF-8 and b is UTF-16 + if (bKey.flags & QtCbor::Element::StringIsUtf16) + return QtPrivate::compareStrings(aData->toUtf8String(), bData->asStringView()); + + return QCborContainerPrivate::compareUtf8(aData, bData->asLatin1()); + }; + + std::sort(Forward(container->elements.begin()), Forward(container->elements.end()), + [&compare](const Value &a, const Value &b) { return compare(a, b) < 0; }); + + // We need to retain the _last_ value for any duplicate keys. Therefore the reverse dance here. + auto it = std::unique(Reverse(container->elements.end()), Reverse(container->elements.begin()), + [&compare](const Value &a, const Value &b) { + return compare(a, b) == 0; + }).base().elementsIterator(); + + // The erase from beginning is expensive but hopefully rare. + container->elements.erase(container->elements.begin(), it); } + /* object = begin-object [ member *( value-separator member ) ] end-object @@ -392,19 +464,14 @@ bool Parser::parseObject() return false; } - int objectOffset = reserveSpace(sizeof(QJsonPrivate::Object)); - if (objectOffset < 0) - return false; - BEGIN << "parseObject pos=" << objectOffset << current << json; - - ParsedObject parsedObject(this, objectOffset); + BEGIN << "parseObject" << json; char token = nextToken(); while (token == Quote) { - int off = current - objectOffset; - if (!parseMember(objectOffset)) + if (!container) + container = new QCborContainerPrivate; + if (!parseMember()) return false; - parsedObject.insert(off); token = nextToken(); if (token != ValueSeparator) break; @@ -421,50 +488,23 @@ bool Parser::parseObject() return false; } - DEBUG << "numEntries" << parsedObject.offsets.size(); - int table = objectOffset; - // finalize the object - if (parsedObject.offsets.size()) { - int tableSize = parsedObject.offsets.size()*sizeof(uint); - table = reserveSpace(tableSize); - if (table < 0) - return false; - -#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN - memcpy(data + table, parsedObject.offsets.constData(), tableSize); -#else - offset *o = (offset *)(data + table); - for (int i = 0; i < parsedObject.offsets.size(); ++i) - o[i] = parsedObject.offsets[i]; - -#endif - } - - QJsonPrivate::Object *o = (QJsonPrivate::Object *)(data + objectOffset); - o->tableOffset = table - objectOffset; - o->size = current - objectOffset; - o->is_object = true; - o->length = parsedObject.offsets.size(); - - DEBUG << "current=" << current; END; --nestingLevel; + + if (container) + sortContainer(container.data()); return true; } /* member = string name-separator value */ -bool Parser::parseMember(int baseOffset) +bool Parser::parseMember() { - int entryOffset = reserveSpace(sizeof(QJsonPrivate::Entry)); - if (entryOffset < 0) - return false; - BEGIN << "parseMember pos=" << entryOffset; + BEGIN << "parseMember"; - bool latin1; - if (!parseString(&latin1)) + if (!parseString()) return false; char token = nextToken(); if (token != NameSeparator) { @@ -475,56 +515,13 @@ bool Parser::parseMember(int baseOffset) lastError = QJsonParseError::UnterminatedObject; return false; } - QJsonPrivate::Value val; - if (!parseValue(&val, baseOffset)) + if (!parseValue()) return false; - // finalize the entry - QJsonPrivate::Entry *e = (QJsonPrivate::Entry *)(data + entryOffset); - e->value = val; - e->value.latinKey = latin1; - END; return true; } -namespace { - struct ValueArray { - static const int prealloc = 128; - ValueArray() : data(stackValues), alloc(prealloc), size(0) {} - ~ValueArray() { if (data != stackValues) free(data); } - - inline bool grow() { - alloc *= 2; - if (data == stackValues) { - QJsonPrivate::Value *newValues = static_cast(malloc(alloc*sizeof(QJsonPrivate::Value))); - if (!newValues) - return false; - memcpy(newValues, data, size*sizeof(QJsonPrivate::Value)); - data = newValues; - } else { - void *newValues = realloc(data, alloc * sizeof(QJsonPrivate::Value)); - if (!newValues) - return false; - data = static_cast(newValues); - } - return true; - } - bool append(const QJsonPrivate::Value &v) { - if (alloc == size && !grow()) - return false; - data[size] = v; - ++size; - return true; - } - - QJsonPrivate::Value stackValues[prealloc]; - QJsonPrivate::Value *data; - int alloc; - int size; - }; -} - /* array = begin-array [ value *( value-separator value ) ] end-array */ @@ -537,12 +534,6 @@ bool Parser::parseArray() return false; } - int arrayOffset = reserveSpace(sizeof(QJsonPrivate::Array)); - if (arrayOffset < 0) - return false; - - ValueArray values; - if (!eatSpace()) { lastError = QJsonParseError::UnterminatedArray; return false; @@ -555,13 +546,10 @@ bool Parser::parseArray() lastError = QJsonParseError::UnterminatedArray; return false; } - QJsonPrivate::Value val; - if (!parseValue(&val, arrayOffset)) + if (!container) + container = new QCborContainerPrivate; + if (!parseValue()) return false; - if (!values.append(val)) { - lastError = QJsonParseError::DocumentTooLarge; - return false; - } char token = nextToken(); if (token == EndArray) break; @@ -575,27 +563,11 @@ bool Parser::parseArray() } } - DEBUG << "size =" << values.size; - int table = arrayOffset; - // finalize the object - if (values.size) { - int tableSize = values.size*sizeof(QJsonPrivate::Value); - table = reserveSpace(tableSize); - if (table < 0) - return false; - memcpy(data + table, values.data, tableSize); - } - - QJsonPrivate::Array *a = (QJsonPrivate::Array *)(data + arrayOffset); - a->tableOffset = table - arrayOffset; - a->size = current - arrayOffset; - a->is_object = false; - a->length = values.size; - - DEBUG << "current=" << current; + DEBUG << "size =" << (container ? container->elements.length() : 0); END; --nestingLevel; + return true; } @@ -604,10 +576,9 @@ value = false / null / true / object / array / number / string */ -bool Parser::parseValue(QJsonPrivate::Value *val, int baseOffset) +bool Parser::parseValue() { BEGIN << "parse Value" << json; - val->_dummy = 0; switch (*json++) { case 'n': @@ -618,7 +589,7 @@ bool Parser::parseValue(QJsonPrivate::Value *val, int baseOffset) if (*json++ == 'u' && *json++ == 'l' && *json++ == 'l') { - val->type = QJsonValue::Null; + container->append(QCborValue(QCborValue::Null)); DEBUG << "value: null"; END; return true; @@ -633,8 +604,7 @@ bool Parser::parseValue(QJsonPrivate::Value *val, int baseOffset) if (*json++ == 'r' && *json++ == 'u' && *json++ == 'e') { - val->type = QJsonValue::Bool; - val->value = true; + container->append(QCborValue(true)); DEBUG << "value: true"; END; return true; @@ -650,8 +620,7 @@ bool Parser::parseValue(QJsonPrivate::Value *val, int baseOffset) *json++ == 'l' && *json++ == 's' && *json++ == 'e') { - val->type = QJsonValue::Bool; - val->value = false; + container->append(QCborValue(false)); DEBUG << "value: false"; END; return true; @@ -659,44 +628,28 @@ bool Parser::parseValue(QJsonPrivate::Value *val, int baseOffset) lastError = QJsonParseError::IllegalValue; return false; case Quote: { - val->type = QJsonValue::String; - if (current - baseOffset >= Value::MaxSize) { - lastError = QJsonParseError::DocumentTooLarge; + if (!parseString()) return false; - } - val->value = current - baseOffset; - bool latin1; - if (!parseString(&latin1)) - return false; - val->latinOrIntValue = latin1; DEBUG << "value: string"; END; return true; } - case BeginArray: - val->type = QJsonValue::Array; - if (current - baseOffset >= Value::MaxSize) { - lastError = QJsonParseError::DocumentTooLarge; - return false; - } - val->value = current - baseOffset; + case BeginArray: { + StashedContainer stashedContainer(&container, QCborValue::Array); if (!parseArray()) return false; DEBUG << "value: array"; END; return true; - case BeginObject: - val->type = QJsonValue::Object; - if (current - baseOffset >= Value::MaxSize) { - lastError = QJsonParseError::DocumentTooLarge; - return false; - } - val->value = current - baseOffset; + } + case BeginObject: { + StashedContainer stashedContainer(&container, QCborValue::Map); if (!parseObject()) return false; DEBUG << "value: object"; END; return true; + } case ValueSeparator: // Essentially missing value, but after a colon, not after a comma // like the other MissingObject errors. @@ -708,7 +661,7 @@ bool Parser::parseValue(QJsonPrivate::Value *val, int baseOffset) return false; default: --json; - if (!parseNumber(val, baseOffset)) + if (!parseNumber()) return false; DEBUG << "value: number"; END; @@ -735,10 +688,9 @@ bool Parser::parseValue(QJsonPrivate::Value *val, int baseOffset) */ -bool Parser::parseNumber(QJsonPrivate::Value *val, int baseOffset) +bool Parser::parseNumber() { BEGIN << "parseNumber" << json; - val->type = QJsonValue::Double; const char *start = json; bool isInt = true; @@ -778,42 +730,32 @@ bool Parser::parseNumber(QJsonPrivate::Value *val, int baseOffset) return false; } - QByteArray number(start, json - start); + const QByteArray number = QByteArray::fromRawData(start, json - start); DEBUG << "numberstring" << number; if (isInt) { bool ok; - int n = number.toInt(&ok); - if (ok && n < (1<<25) && n > -(1<<25)) { - val->int_value = n; - val->latinOrIntValue = true; + qlonglong n = number.toLongLong(&ok); + if (ok) { + container->append(QCborValue(n)); END; return true; } } bool ok; - union { - quint64 ui; - double d; - }; - d = number.toDouble(&ok); + double d = number.toDouble(&ok); if (!ok) { lastError = QJsonParseError::IllegalNumber; return false; } - int pos = reserveSpace(sizeof(double)); - if (pos < 0) - return false; - qToLittleEndian(ui, data + pos); - if (current - baseOffset >= Value::MaxSize) { - lastError = QJsonParseError::DocumentTooLarge; - return false; - } - val->value = pos - baseOffset; - val->latinOrIntValue = false; + qint64 n; + if (convertDoubleTo(d, &n)) + container->append(QCborValue(n)); + else + container->append(QCborValue(d)); END; return true; @@ -902,58 +844,45 @@ static inline bool scanEscapeSequence(const char *&json, const char *end, uint * static inline bool scanUtf8Char(const char *&json, const char *end, uint *result) { - const uchar *&src = reinterpret_cast(json); - const uchar *uend = reinterpret_cast(end); - uchar b = *src++; - int res = QUtf8Functions::fromUtf8(b, result, src, uend); - if (res < 0) { - // decoding error, backtrack the character we read above - --json; + const auto *usrc = reinterpret_cast(json); + const auto *uend = reinterpret_cast(end); + const uchar b = *usrc++; + int res = QUtf8Functions::fromUtf8(b, result, usrc, uend); + if (res < 0) return false; - } + json = reinterpret_cast(usrc); return true; } -bool Parser::parseString(bool *latin1) +bool Parser::parseString() { - *latin1 = true; - const char *start = json; - int outStart = current; - // try to write out a latin1 string + // try to parse a utf-8 string without escape sequences, and note whether it's 7bit ASCII. - int stringPos = reserveSpace(2); - if (stringPos < 0) - return false; - - BEGIN << "parse string stringPos=" << stringPos << json; + BEGIN << "parse string" << json; + bool isUtf8 = true; + bool isAscii = true; while (json < end) { uint ch = 0; if (*json == '"') break; - else if (*json == '\\') { - if (!scanEscapeSequence(json, end, &ch)) { - lastError = QJsonParseError::IllegalEscapeSequence; - return false; - } - } else { - if (!scanUtf8Char(json, end, &ch)) { - lastError = QJsonParseError::IllegalUTF8String; - return false; - } - } - // bail out if the string is not pure latin1 or too long to hold as a latin1string (which has only 16 bit for the length) - if (ch > 0xff || json - start >= 0x8000) { - *latin1 = false; + if (*json == '\\') { + isAscii = false; + // If we find escape sequences, we store UTF-16 as there are some + // escape sequences which are hard to represent in UTF-8. + // (plain "\\ud800" for example) + isUtf8 = false; break; } - int pos = reserveSpace(1); - if (pos < 0) + if (!scanUtf8Char(json, end, &ch)) { + lastError = QJsonParseError::IllegalUTF8String; return false; - DEBUG << " " << ch << (char)ch; - data[pos] = (uchar)ch; + } + if (ch > 0x7f) + isAscii = false; + DEBUG << " " << ch << char(ch); } ++json; DEBUG << "end of string"; @@ -962,25 +891,20 @@ bool Parser::parseString(bool *latin1) return false; } - // no unicode string, we are done - if (*latin1) { - // write string length - *(QJsonPrivate::qle_ushort *)(data + stringPos) = ushort(current - outStart - sizeof(ushort)); - int pos = reserveSpace((4 - current) & 3); - if (pos < 0) - return false; - while (pos & 3) - data[pos++] = 0; + // no escape sequences, we are done + if (isUtf8) { + container->appendByteData(start, json - start - 1, QCborValue::String, + isAscii ? QtCbor::Element::StringIsAscii + : QtCbor::Element::ValueFlags {}); END; return true; } - *latin1 = false; - DEBUG << "not latin"; + DEBUG << "has escape sequences"; json = start; - current = outStart + sizeof(int); + QString ucs4; while (json < end) { uint ch = 0; if (*json == '"') @@ -997,16 +921,10 @@ bool Parser::parseString(bool *latin1) } } if (QChar::requiresSurrogates(ch)) { - int pos = reserveSpace(4); - if (pos < 0) - return false; - *(QJsonPrivate::qle_ushort *)(data + pos) = QChar::highSurrogate(ch); - *(QJsonPrivate::qle_ushort *)(data + pos + 2) = QChar::lowSurrogate(ch); + ucs4.append(QChar::highSurrogate(ch)); + ucs4.append(QChar::lowSurrogate(ch)); } else { - int pos = reserveSpace(2); - if (pos < 0) - return false; - *(QJsonPrivate::qle_ushort *)(data + pos) = (ushort)ch; + ucs4.append(QChar(ushort(ch))); } } ++json; @@ -1016,13 +934,8 @@ bool Parser::parseString(bool *latin1) return false; } - // write string length - *(QJsonPrivate::qle_int *)(data + stringPos) = (current - outStart - sizeof(int))/2; - int pos = reserveSpace((4 - current) & 3); - if (pos < 0) - return false; - while (pos & 3) - data[pos++] = 0; + container->appendByteData(reinterpret_cast(ucs4.utf16()), ucs4.size() * 2, + QCborValue::String, QtCbor::Element::StringIsUtf16); END; return true; } diff --git a/src/corelib/serialization/qjsonparser_p.h b/src/corelib/serialization/qjsonparser_p.h index 379256847f..14d9705447 100644 --- a/src/corelib/serialization/qjsonparser_p.h +++ b/src/corelib/serialization/qjsonparser_p.h @@ -52,8 +52,8 @@ // #include -#include -#include +#include +#include QT_BEGIN_NAMESPACE @@ -64,25 +64,7 @@ class Parser public: Parser(const char *json, int length); - QJsonDocument parse(QJsonParseError *error); - - class ParsedObject - { - public: - ParsedObject(Parser *p, int pos) : parser(p), objectPosition(pos) { - offsets.reserve(64); - } - void insert(uint offset); - - Parser *parser; - int objectPosition; - QVector offsets; - - inline QJsonPrivate::Entry *entryAt(int i) const { - return reinterpret_cast(parser->data + objectPosition + offsets[i]); - } - }; - + QCborValue parse(QJsonParseError *error); private: inline void eatBOM(); @@ -91,34 +73,17 @@ private: bool parseObject(); bool parseArray(); - bool parseMember(int baseOffset); - bool parseString(bool *latin1); - bool parseValue(QJsonPrivate::Value *val, int baseOffset); - bool parseNumber(QJsonPrivate::Value *val, int baseOffset); + bool parseMember(); + bool parseString(); + bool parseValue(); + bool parseNumber(); const char *head; const char *json; const char *end; - char *data; - int dataLength; - int current; int nestingLevel; QJsonParseError::ParseError lastError; - - inline int reserveSpace(int space) { - if (current + space >= dataLength) { - dataLength = 2*dataLength + space; - char *newData = (char *)realloc(data, dataLength); - if (!newData) { - lastError = QJsonParseError::DocumentTooLarge; - return -1; - } - data = newData; - } - int pos = current; - current += space; - return pos; - } + QExplicitlySharedDataPointer container; }; } diff --git a/src/corelib/serialization/qjsonvalue.cpp b/src/corelib/serialization/qjsonvalue.cpp index 5f07a6a03e..033e438580 100644 --- a/src/corelib/serialization/qjsonvalue.cpp +++ b/src/corelib/serialization/qjsonvalue.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -47,10 +48,11 @@ #include #include "qdatastream.h" -#ifndef QT_BOOTSTRAPPED -# include -# include -#endif +#include +#include + +#include +#include #include "qjson_p.h" @@ -112,70 +114,61 @@ QT_BEGIN_NAMESPACE The default is to create a Null value. */ QJsonValue::QJsonValue(Type type) - : ui(0), d(nullptr), t(type) + : d(nullptr), t(QCborValue::Undefined) { -} - -/*! - \internal - */ -QJsonValue::QJsonValue(QJsonPrivate::Data *data, QJsonPrivate::Base *base, const QJsonPrivate::Value &v) - : d(nullptr) -{ - t = (Type)(uint)v.type; - switch (t) { - case Undefined: + switch (type) { case Null: - dbl = 0; + t = QCborValue::Null; break; case Bool: - b = v.toBoolean(); + t = QCborValue::False; break; case Double: - dbl = v.toDouble(base); + t = QCborValue::Double; break; - case String: { - QString s = v.toString(base); - stringData = s.data_ptr(); - stringData->ref.ref(); + case String: + t = QCborValue::String; break; - } case Array: + t = QCborValue::Array; + break; case Object: - d = data; - this->base = v.base(base); + t = QCborValue::Map; + break; + case Undefined: break; } - if (d) - d->ref.ref(); } /*! Creates a value of type Bool, with value \a b. */ QJsonValue::QJsonValue(bool b) - : d(nullptr), t(Bool) + : t(b ? QCborValue::True : QCborValue::False) { - this->b = b; } /*! Creates a value of type Double, with value \a n. */ -QJsonValue::QJsonValue(double n) - : d(nullptr), t(Double) +QJsonValue::QJsonValue(double v) + : d(nullptr) { - this->dbl = n; + if (convertDoubleTo(v, &n)) { + t = QCborValue::Integer; + } else { + memcpy(&n, &v, sizeof(n)); + t = QCborValue::Double; + } } /*! \overload Creates a value of type Double, with value \a n. */ -QJsonValue::QJsonValue(int n) - : d(nullptr), t(Double) +QJsonValue::QJsonValue(int v) + : n(v), t(QCborValue::Integer) { - this->dbl = n; } /*! @@ -184,19 +177,17 @@ QJsonValue::QJsonValue(int n) NOTE: the integer limits for IEEE 754 double precision data is 2^53 (-9007199254740992 to +9007199254740992). If you pass in values outside this range expect a loss of precision to occur. */ -QJsonValue::QJsonValue(qint64 n) - : d(nullptr), t(Double) +QJsonValue::QJsonValue(qint64 v) + : n(v), t(QCborValue::Integer) { - this->dbl = double(n); } /*! Creates a value of type String, with value \a s. */ QJsonValue::QJsonValue(const QString &s) - : d(nullptr), t(String) + : QJsonValue(QJsonPrivate::Value::fromTrustedCbor(s)) { - stringDataFromQStringHelper(s); } /*! @@ -211,71 +202,50 @@ QJsonValue::QJsonValue(const QString &s) \since 5.3 */ +// ### Qt6: remove void QJsonValue::stringDataFromQStringHelper(const QString &string) { - stringData = *(QStringData **)(&string); - stringData->ref.ref(); + *this = QJsonValue(string); } /*! Creates a value of type String, with value \a s. */ QJsonValue::QJsonValue(QLatin1String s) - : d(nullptr), t(String) + : QJsonValue(QJsonPrivate::Value::fromTrustedCbor(s)) { - // ### FIXME: Avoid creating the temp QString below - QString str(s); - stringDataFromQStringHelper(str); } /*! Creates a value of type Array, with value \a a. */ QJsonValue::QJsonValue(const QJsonArray &a) - : d(a.d), t(Array) + : n(-1), d(a.a), t(QCborValue::Array) { - base = a.a; - if (d) - d->ref.ref(); } /*! Creates a value of type Object, with value \a o. */ QJsonValue::QJsonValue(const QJsonObject &o) - : d(o.d), t(Object) + : n(-1), d(o.o), t(QCborValue::Map) { - base = o.o; - if (d) - d->ref.ref(); } /*! Destroys the value. */ -QJsonValue::~QJsonValue() -{ - if (t == String && stringData && !stringData->ref.deref()) - free(stringData); - - if (d && !d->ref.deref()) - delete d; -} +QJsonValue::~QJsonValue() = default; /*! Creates a copy of \a other. */ QJsonValue::QJsonValue(const QJsonValue &other) { + n = other.n; t = other.t; d = other.d; - ui = other.ui; - if (d) - d->ref.ref(); - - if (t == String && stringData) - stringData->ref.ref(); } /*! @@ -288,6 +258,23 @@ QJsonValue &QJsonValue::operator =(const QJsonValue &other) return *this; } +QJsonValue::QJsonValue(QJsonValue &&other) noexcept : + n(other.n), + d(other.d), + t(other.t) +{ + other.n = 0; + other.d = nullptr; + other.t = QCborValue::Null; +} + +void QJsonValue::swap(QJsonValue &other) noexcept +{ + qSwap(n, other.n); + qSwap(d, other.d); + qSwap(t, other.t); +} + /*! \fn QJsonValue::QJsonValue(QJsonValue &&other) \since 5.10 @@ -528,23 +515,27 @@ QJsonValue QJsonValue::fromVariant(const QVariant &variant) QVariant QJsonValue::toVariant() const { switch (t) { - case Bool: - return b; - case Double: - return dbl; - case String: + case QCborValue::True: + return true; + case QCborValue::False: + return false; + case QCborValue::Integer: + case QCborValue::Double: + return toDouble(); + case QCborValue::String: return toString(); - case Array: + case QCborValue::Array: return d ? - QJsonArray(d, static_cast(base)).toVariantList() : + QJsonArray(d.data()).toVariantList() : QVariantList(); - case Object: + case QCborValue::Map: return d ? - QJsonObject(d, static_cast(base)).toVariantMap() : + QJsonObject(d.data()).toVariantMap() : QVariantMap(); - case Null: + case QCborValue::Null: return QVariant::fromValue(nullptr); - case Undefined: + case QCborValue::Undefined: + default: break; } return QVariant(); @@ -573,7 +564,25 @@ QVariant QJsonValue::toVariant() const */ QJsonValue::Type QJsonValue::type() const { - return t; + switch (t) { + case QCborValue::Null: + return QJsonValue::Null; + case QCborValue::True: + case QCborValue::False: + return QJsonValue::Bool; + case QCborValue::Double: + case QCborValue::Integer: + return QJsonValue::Double; + case QCborValue::String: + return QJsonValue::String; + case QCborValue::Array: + return QJsonValue::Array; + case QCborValue::Map: + return QJsonValue::Object; + case QCborValue::Undefined: + default: + return QJsonValue::Undefined; + } } /*! @@ -583,9 +592,14 @@ QJsonValue::Type QJsonValue::type() const */ bool QJsonValue::toBool(bool defaultValue) const { - if (t != Bool) + switch (t) { + case QCborValue::True: + return true; + case QCborValue::False: + return false; + default: return defaultValue; - return b; + } } /*! @@ -597,9 +611,20 @@ bool QJsonValue::toBool(bool defaultValue) const */ int QJsonValue::toInt(int defaultValue) const { - if (t == Double && int(dbl) == dbl) - return int(dbl); - return defaultValue; + switch (t) { + case QCborValue::Double: { + const double dbl = toDouble(); + int dblInt; + convertDoubleTo(dbl, &dblInt); + return dbl == dblInt ? dblInt : defaultValue; + } + case QCborValue::Integer: + return (n <= qint64(std::numeric_limits::max()) + && n >= qint64(std::numeric_limits::min())) + ? n : defaultValue; + default: + return defaultValue; + } } /*! @@ -609,9 +634,17 @@ int QJsonValue::toInt(int defaultValue) const */ double QJsonValue::toDouble(double defaultValue) const { - if (t != Double) + switch (t) { + case QCborValue::Double: { + double d; + memcpy(&d, &n, sizeof(d)); + return d; + } + case QCborValue::Integer: + return n; + default: return defaultValue; - return dbl; + } } /*! @@ -621,11 +654,7 @@ double QJsonValue::toDouble(double defaultValue) const */ QString QJsonValue::toString(const QString &defaultValue) const { - if (t != String) - return defaultValue; - stringData->ref.ref(); // the constructor below doesn't add a ref. - QStringDataPtr holder = { stringData }; - return QString(holder); + return (t == QCborValue::String && d) ? d->stringAt(n) : defaultValue; } /*! @@ -637,11 +666,7 @@ QString QJsonValue::toString(const QString &defaultValue) const */ QString QJsonValue::toString() const { - if (t != String) - return QString(); - stringData->ref.ref(); // the constructor below doesn't add a ref. - QStringDataPtr holder = { stringData }; - return QString(holder); + return (t == QCborValue::String && d) ? d->stringAt(n) : QString(); } /*! @@ -651,10 +676,10 @@ QString QJsonValue::toString() const */ QJsonArray QJsonValue::toArray(const QJsonArray &defaultValue) const { - if (!d || t != Array) + if (t != QCborValue::Array || n >= 0 || !d) return defaultValue; - return QJsonArray(d, static_cast(base)); + return QJsonArray(d.data()); } /*! @@ -676,10 +701,10 @@ QJsonArray QJsonValue::toArray() const */ QJsonObject QJsonValue::toObject(const QJsonObject &defaultValue) const { - if (!d || t != Object) + if (t != QCborValue::Map || n >= 0 || !d) return defaultValue; - return QJsonObject(d, static_cast(base)); + return QJsonObject(d.data()); } /*! @@ -766,33 +791,31 @@ bool QJsonValue::operator==(const QJsonValue &other) const return false; switch (t) { - case Undefined: - case Null: + case QCborValue::Undefined: + case QCborValue::Null: + case QCborValue::True: + case QCborValue::False: break; - case Bool: - return b == other.b; - case Double: - return dbl == other.dbl; - case String: + case QCborValue::Double: + return toDouble() == other.toDouble(); + case QCborValue::Integer: + return n == other.n; + case QCborValue::String: return toString() == other.toString(); - case Array: - if (base == other.base) - return true; - if (!base) - return !other.base->length; - if (!other.base) - return !base->length; - return QJsonArray(d, static_cast(base)) - == QJsonArray(other.d, static_cast(other.base)); - case Object: - if (base == other.base) - return true; - if (!base) - return !other.base->length; - if (!other.base) - return !base->length; - return QJsonObject(d, static_cast(base)) - == QJsonObject(other.d, static_cast(other.base)); + case QCborValue::Array: + if (!d) + return !other.d || other.d->elements.length() == 0; + if (!other.d) + return d->elements.length() == 0; + return QJsonArray(d.data()) == QJsonArray(other.d.data()); + case QCborValue::Map: + if (!d) + return !other.d || other.d->elements.length() == 0; + if (!other.d) + return d->elements.length() == 0; + return QJsonObject(d.data()) == QJsonObject(other.d.data()); + default: + return false; } return true; } @@ -810,15 +833,7 @@ bool QJsonValue::operator!=(const QJsonValue &other) const */ void QJsonValue::detach() { - if (!d) - return; - - QJsonPrivate::Data *x = d->clone(base); - x->ref.ref(); - if (!d->ref.deref()) - delete d; - d = x; - base = static_cast(d->header->root()); + d.detach(); } @@ -914,7 +929,7 @@ uint qHash(const QJsonValue &value, uint seed) QDebug operator<<(QDebug dbg, const QJsonValue &o) { QDebugStateSaver saver(dbg); - switch (o.t) { + switch (o.type()) { case QJsonValue::Undefined: dbg << "QJsonValue(undefined)"; break; @@ -948,7 +963,7 @@ QDebug operator<<(QDebug dbg, const QJsonValue &o) #ifndef QT_NO_DATASTREAM QDataStream &operator<<(QDataStream &stream, const QJsonValue &v) { - quint8 type = v.t; + quint8 type = v.type(); stream << type; switch (type) { case QJsonValue::Undefined: diff --git a/src/corelib/serialization/qjsonvalue.h b/src/corelib/serialization/qjsonvalue.h index 8ade18509b..5adcd64176 100644 --- a/src/corelib/serialization/qjsonvalue.h +++ b/src/corelib/serialization/qjsonvalue.h @@ -42,22 +42,18 @@ #include #include +#include +#include QT_BEGIN_NAMESPACE -class QDebug; class QVariant; class QJsonArray; class QJsonObject; +class QCborContainerPrivate; namespace QJsonPrivate { - class Data; - class Base; - class Object; - class Header; - class Array; - class Value; - class Entry; +class Value; } class Q_CORE_EXPORT QJsonValue @@ -77,12 +73,12 @@ public: QJsonValue(bool b); QJsonValue(double n); QJsonValue(int n); - QJsonValue(qint64 n); + QJsonValue(qint64 v); QJsonValue(const QString &s); QJsonValue(QLatin1String s); #ifndef QT_NO_CAST_FROM_ASCII inline QT_ASCII_CAST_WARN QJsonValue(const char *s) - : d(nullptr), t(String) { stringDataFromQStringHelper(QString::fromUtf8(s)); } + : QJsonValue(QString::fromUtf8(s)) {} #endif QJsonValue(const QJsonArray &a); QJsonValue(const QJsonObject &o); @@ -92,15 +88,7 @@ public: QJsonValue(const QJsonValue &other); QJsonValue &operator =(const QJsonValue &other); - QJsonValue(QJsonValue &&other) noexcept - : ui(other.ui), - d(other.d), - t(other.t) - { - other.ui = 0; - other.d = nullptr; - other.t = Null; - } + QJsonValue(QJsonValue &&other) noexcept; QJsonValue &operator =(QJsonValue &&other) noexcept { @@ -108,12 +96,7 @@ public: return *this; } - void swap(QJsonValue &other) noexcept - { - qSwap(ui, other.ui); - qSwap(d, other.d); - qSwap(t, other.t); - } + void swap(QJsonValue &other) noexcept; static QJsonValue fromVariant(const QVariant &variant); QVariant toVariant() const; @@ -149,7 +132,7 @@ public: private: // avoid implicit conversions from char * to bool - inline QJsonValue(const void *) {} + QJsonValue(const void *) = delete; friend class QJsonPrivate::Value; friend class QJsonArray; friend class QJsonObject; @@ -157,20 +140,19 @@ private: friend Q_CORE_EXPORT QDebug operator<<(QDebug, const QJsonValue &); friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QJsonValue &); - QJsonValue(QJsonPrivate::Data *d, QJsonPrivate::Base *b, const QJsonPrivate::Value& v); + // ### Qt6: Remove this. void stringDataFromQStringHelper(const QString &string); void detach(); - union { - quint64 ui; - bool b; - double dbl; - QStringData *stringData; - QJsonPrivate::Base *base; - }; - QJsonPrivate::Data *d; // needed for Objects and Arrays - Type t; + // ### Qt6: change to an actual QCborValue + qint64 n = 0; + QExplicitlySharedDataPointer d; // needed for Objects, Arrays, Strings + QCborValue::Type t; + + // Assert binary compatibility with pre-5.15 QJsonValue + Q_STATIC_ASSERT(sizeof(QExplicitlySharedDataPointer) == sizeof(void *)); + Q_STATIC_ASSERT(sizeof(QCborValue::Type) == sizeof(QJsonValue::Type)); }; class Q_CORE_EXPORT QJsonValueRef diff --git a/src/corelib/serialization/qjsonwriter.cpp b/src/corelib/serialization/qjsonwriter.cpp index 012d3bea86..627d1bbd62 100644 --- a/src/corelib/serialization/qjsonwriter.cpp +++ b/src/corelib/serialization/qjsonwriter.cpp @@ -44,13 +44,14 @@ #include "qjson_p.h" #include "private/qutfcodec_p.h" #include +#include QT_BEGIN_NAMESPACE using namespace QJsonPrivate; -static void objectContentToJson(const QJsonPrivate::Object *o, QByteArray &json, int indent, bool compact); -static void arrayContentToJson(const QJsonPrivate::Array *a, QByteArray &json, int indent, bool compact); +static void objectContentToJson(const QCborContainerPrivate *o, QByteArray &json, int indent, bool compact); +static void arrayContentToJson(const QCborContainerPrivate *a, QByteArray &json, int indent, bool compact); static inline uchar hexdig(uint u) { @@ -126,16 +127,20 @@ static QByteArray escapedString(const QString &s) return ba; } -static void valueToJson(const QJsonPrivate::Base *b, const QJsonPrivate::Value &v, QByteArray &json, int indent, bool compact) +static void valueToJson(const QCborValue &v, QByteArray &json, int indent, bool compact) { - QJsonValue::Type type = (QJsonValue::Type)(uint)v.type; + QCborValue::Type type = v.type(); switch (type) { - case QJsonValue::Bool: - json += v.toBoolean() ? "true" : "false"; + case QCborValue::True: + json += "true"; break; - case QJsonValue::Double: { - const double d = v.toDouble(b); - if (qIsFinite(d)) { // +2 to format to ensure the expected precision + case QCborValue::False: + json += "false"; + break; + case QCborValue::Integer: + case QCborValue::Double: { + const double d = v.toDouble(); + if (qIsFinite(d)) { quint64 absInt; json += QByteArray::number(d, convertDoubleTo(std::abs(d), &absInt) ? 'f' : 'g', QLocale::FloatingPointShortest); @@ -144,42 +149,44 @@ static void valueToJson(const QJsonPrivate::Base *b, const QJsonPrivate::Value & } break; } - case QJsonValue::String: + case QCborValue::String: json += '"'; - json += escapedString(v.toString(b)); + json += escapedString(v.toString()); json += '"'; break; - case QJsonValue::Array: + case QCborValue::Array: json += compact ? "[" : "[\n"; - arrayContentToJson(static_cast(v.base(b)), json, indent + (compact ? 0 : 1), compact); + arrayContentToJson( + QJsonPrivate::Value::container(v), json, indent + (compact ? 0 : 1), compact); json += QByteArray(4*indent, ' '); json += ']'; break; - case QJsonValue::Object: + case QCborValue::Map: json += compact ? "{" : "{\n"; - objectContentToJson(static_cast(v.base(b)), json, indent + (compact ? 0 : 1), compact); + objectContentToJson( + QJsonPrivate::Value::container(v), json, indent + (compact ? 0 : 1), compact); json += QByteArray(4*indent, ' '); json += '}'; break; - case QJsonValue::Null: + case QCborValue::Null: default: json += "null"; } } -static void arrayContentToJson(const QJsonPrivate::Array *a, QByteArray &json, int indent, bool compact) +static void arrayContentToJson(const QCborContainerPrivate *a, QByteArray &json, int indent, bool compact) { - if (!a || !a->length) + if (!a || a->elements.empty()) return; QByteArray indentString(4*indent, ' '); - uint i = 0; - while (1) { + qsizetype i = 0; + while (true) { json += indentString; - valueToJson(a, a->at(i), json, indent, compact); + valueToJson(a->valueAt(i), json, indent, compact); - if (++i == a->length) { + if (++i == a->elements.size()) { if (!compact) json += '\n'; break; @@ -190,23 +197,23 @@ static void arrayContentToJson(const QJsonPrivate::Array *a, QByteArray &json, i } -static void objectContentToJson(const QJsonPrivate::Object *o, QByteArray &json, int indent, bool compact) +static void objectContentToJson(const QCborContainerPrivate *o, QByteArray &json, int indent, bool compact) { - if (!o || !o->length) + if (!o || o->elements.empty()) return; QByteArray indentString(4*indent, ' '); - uint i = 0; - while (1) { - QJsonPrivate::Entry *e = o->entryAt(i); + qsizetype i = 0; + while (true) { + QCborValue e = o->valueAt(i); json += indentString; json += '"'; - json += escapedString(e->key()); + json += escapedString(o->valueAt(i).toString()); json += compact ? "\":" : "\": "; - valueToJson(o, e->value, json, indent, compact); + valueToJson(o->valueAt(i + 1), json, indent, compact); - if (++i == o->length) { + if ((i += 2) == o->elements.size()) { if (!compact) json += '\n'; break; @@ -216,18 +223,18 @@ static void objectContentToJson(const QJsonPrivate::Object *o, QByteArray &json, } } -void Writer::objectToJson(const QJsonPrivate::Object *o, QByteArray &json, int indent, bool compact) +void Writer::objectToJson(const QCborContainerPrivate *o, QByteArray &json, int indent, bool compact) { - json.reserve(json.size() + (o ? (int)o->size : 16)); + json.reserve(json.size() + (o ? (int)o->elements.size() : 16)); json += compact ? "{" : "{\n"; objectContentToJson(o, json, indent + (compact ? 0 : 1), compact); json += QByteArray(4*indent, ' '); json += compact ? "}" : "}\n"; } -void Writer::arrayToJson(const QJsonPrivate::Array *a, QByteArray &json, int indent, bool compact) +void Writer::arrayToJson(const QCborContainerPrivate *a, QByteArray &json, int indent, bool compact) { - json.reserve(json.size() + (a ? (int)a->size : 16)); + json.reserve(json.size() + (a ? (int)a->elements.size() : 16)); json += compact ? "[" : "[\n"; arrayContentToJson(a, json, indent + (compact ? 0 : 1), compact); json += QByteArray(4*indent, ' '); diff --git a/src/corelib/serialization/qjsonwriter_p.h b/src/corelib/serialization/qjsonwriter_p.h index 76a8460449..8c263bb7c3 100644 --- a/src/corelib/serialization/qjsonwriter_p.h +++ b/src/corelib/serialization/qjsonwriter_p.h @@ -62,8 +62,8 @@ namespace QJsonPrivate class Writer { public: - static void objectToJson(const QJsonPrivate::Object *o, QByteArray &json, int indent, bool compact = false); - static void arrayToJson(const QJsonPrivate::Array *a, QByteArray &json, int indent, bool compact = false); + static void objectToJson(const QCborContainerPrivate *o, QByteArray &json, int indent, bool compact = false); + static void arrayToJson(const QCborContainerPrivate *a, QByteArray &json, int indent, bool compact = false); }; } diff --git a/src/corelib/serialization/serialization.pri b/src/corelib/serialization/serialization.pri index 5310fddd67..7407e20d9e 100644 --- a/src/corelib/serialization/serialization.pri +++ b/src/corelib/serialization/serialization.pri @@ -25,7 +25,6 @@ SOURCES += \ serialization/qcbordiagnostic.cpp \ serialization/qcborvalue.cpp \ serialization/qdatastream.cpp \ - serialization/qjson.cpp \ serialization/qjsoncbor.cpp \ serialization/qjsondocument.cpp \ serialization/qjsonobject.cpp \ @@ -45,6 +44,20 @@ qtConfig(cborstream): { serialization/qcborstream.h } +qtConfig(binaryjson): { + HEADERS += \ + serialization/qbinaryjson_p.h \ + serialization/qbinaryjsonarray_p.h \ + serialization/qbinaryjsonobject_p.h \ + serialization/qbinaryjsonvalue_p.h + + SOURCES += \ + serialization/qbinaryjson.cpp \ + serialization/qbinaryjsonarray.cpp \ + serialization/qbinaryjsonobject.cpp \ + serialization/qbinaryjsonvalue.cpp \ +} + false: SOURCES += \ serialization/qcborarray.cpp \ serialization/qcbormap.cpp diff --git a/src/tools/bootstrap/bootstrap.pro b/src/tools/bootstrap/bootstrap.pro index 9863ff5e69..6230cc081d 100644 --- a/src/tools/bootstrap/bootstrap.pro +++ b/src/tools/bootstrap/bootstrap.pro @@ -63,8 +63,9 @@ SOURCES += \ ../../corelib/kernel/qsharedmemory.cpp \ ../../corelib/kernel/qsystemsemaphore.cpp \ ../../corelib/plugin/quuid.cpp \ + ../../corelib/serialization/qcborvalue.cpp \ ../../corelib/serialization/qdatastream.cpp \ - ../../corelib/serialization/qjson.cpp \ + ../../corelib/serialization/qjsoncbor.cpp \ ../../corelib/serialization/qjsondocument.cpp \ ../../corelib/serialization/qjsonobject.cpp \ ../../corelib/serialization/qjsonarray.cpp \ From 03717be7885d84783bc8ea32a65e42e4970f59d6 Mon Sep 17 00:00:00 2001 From: Eirik Aavitsland Date: Wed, 23 Oct 2019 13:35:13 +0200 Subject: [PATCH 30/47] Fix: confusion in QImage paintEngine creation on shared images During the creation of a raster paint engine in QImage::paintEngine(), the QImage will be detached. At least old gcc versions would get confused so that the newly created paintengine would end up in the old QImage copy insteads of the newly detached one. Work around by dropping the temporary engine pointer. Fixes: QTBUG-79383 Change-Id: I27b1f24312269bc2bcc641dc4334397a92e3bfbb Reviewed-by: Allan Sandfeld Jensen --- src/gui/image/qimage.cpp | 6 +++--- tests/auto/gui/image/qimage/tst_qimage.cpp | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp index dda407181a..2779b97fbd 100644 --- a/src/gui/image/qimage.cpp +++ b/src/gui/image/qimage.cpp @@ -4145,11 +4145,11 @@ QPaintEngine *QImage::paintEngine() const if (!d->paintEngine) { QPaintDevice *paintDevice = const_cast(this); - QPaintEngine *paintEngine = 0; QPlatformIntegration *platformIntegration = QGuiApplicationPrivate::platformIntegration(); if (platformIntegration) - paintEngine = platformIntegration->createImagePaintEngine(paintDevice); - d->paintEngine = paintEngine ? paintEngine : new QRasterPaintEngine(paintDevice); + d->paintEngine = platformIntegration->createImagePaintEngine(paintDevice); + if (!d->paintEngine) + d->paintEngine = new QRasterPaintEngine(paintDevice); } return d->paintEngine; diff --git a/tests/auto/gui/image/qimage/tst_qimage.cpp b/tests/auto/gui/image/qimage/tst_qimage.cpp index b84aa52465..2a9b92ed35 100644 --- a/tests/auto/gui/image/qimage/tst_qimage.cpp +++ b/tests/auto/gui/image/qimage/tst_qimage.cpp @@ -2132,6 +2132,12 @@ void tst_QImage::paintEngine() QCOMPARE(engine, img.paintEngine()); QCOMPARE(img, expected); + + { + QImage img1(16, 16, QImage::Format_ARGB32); + QImage img2 = img1; + QVERIFY(img2.paintEngine()); + } } void tst_QImage::setAlphaChannelWhilePainting() From 4514b67d7c31c95e7f753192b74fb15cbe4b033a Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 22 Oct 2019 10:50:19 +0200 Subject: [PATCH 31/47] moc: When generating metatypes, allow empty MOC_DIR We don't want the metatypes to be generated into the file system root in that case. Change-Id: I91bab20fa498de0f2918d8ee5b2f230cc0610aae Reviewed-by: Simon Hausmann --- mkspecs/features/metatypes.prf | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/mkspecs/features/metatypes.prf b/mkspecs/features/metatypes.prf index a0a548eeb2..64387458ac 100644 --- a/mkspecs/features/metatypes.prf +++ b/mkspecs/features/metatypes.prf @@ -2,17 +2,25 @@ qtPrepareTool(MOC_COLLECT_JSON, moc) QMAKE_MOC_OPTIONS += --output-json +MOC_JSON_H_BASE = $${QMAKE_H_MOD_MOC} +MOC_JSON_CPP_BASE = $${QMAKE_CPP_MOD_MOC} + +!isEmpty(MOC_DIR) { + MOC_JSON_H_BASE = $$MOC_DIR/$${MOC_JSON_H_BASE} + MOC_JSON_CPP_BASE = $$MOC_DIR/$${MOC_JSON_CPP_BASE} +} + moc_json_header.input = HEADERS -moc_json_header.output = $$MOC_DIR/$${QMAKE_H_MOD_MOC}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)}.json +moc_json_header.output = $${MOC_JSON_H_BASE}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)}.json moc_json_header.CONFIG = no_link moc_verify -moc_json_header.depends = $$MOC_DIR/$${QMAKE_H_MOD_MOC}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)} +moc_json_header.depends = $${MOC_JSON_H_BASE}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)} moc_json_header.commands = $$escape_expand(\\n) # force creation of rule moc_json_header.variable_out = MOC_JSON_FILES moc_json_source.input = SOURCES -moc_json_source.output = $$MOC_DIR/$${QMAKE_CPP_MOD_MOC}${QMAKE_FILE_BASE}$${QMAKE_EXT_CPP_MOC}.json +moc_json_source.output = $${MOC_JSON_CPP_BASE}${QMAKE_FILE_BASE}$${QMAKE_EXT_CPP_MOC}.json moc_json_source.CONFIG = no_link moc_verify -moc_json_source.depends = $$MOC_DIR/$${QMAKE_CPP_MOD_MOC}${QMAKE_FILE_BASE}$${QMAKE_EXT_CPP_MOC} +moc_json_source.depends = $${MOC_JSON_CPP_BASE}${QMAKE_FILE_BASE}$${QMAKE_EXT_CPP_MOC} moc_json_source.commands = $$escape_expand(\\n) # force creation of rule moc_json_source.variable_out = MOC_JSON_FILES From 6326d1df39fc68e47d1cd34d6cd8b021bdd66045 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Tue, 22 Oct 2019 10:47:35 +0200 Subject: [PATCH 32/47] Store a native resource binding map in QShader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deserializer remains compatible with .qsb files without this additional section. Task-number: QTBUG-79368 Change-Id: I03e2a634febbd88da7f6a4369f104855ea31e3af Reviewed-by: Christian Strømme --- src/gui/rhi/qshader.cpp | 144 +++++++++++++++++++++++++++++++++----- src/gui/rhi/qshader_p.h | 5 ++ src/gui/rhi/qshader_p_p.h | 4 +- 3 files changed, 135 insertions(+), 18 deletions(-) diff --git a/src/gui/rhi/qshader.cpp b/src/gui/rhi/qshader.cpp index 6a2c596557..c22b029dc8 100644 --- a/src/gui/rhi/qshader.cpp +++ b/src/gui/rhi/qshader.cpp @@ -214,7 +214,8 @@ QT_BEGIN_NAMESPACE QShader, it indicates no shader code was found for the requested key. */ -static const int QSB_VERSION = 1; +static const int QSB_VERSION = 2; +static const int QSB_VERSION_WITHOUT_BINDINGS = 1; /*! Constructs a new, empty (and thus invalid) QShader instance. @@ -345,6 +346,14 @@ void QShader::removeShader(const QShaderKey &key) d->shaders.erase(it); } +static void writeShaderKey(QDataStream *ds, const QShaderKey &k) +{ + *ds << k.source(); + *ds << k.sourceVersion().version(); + *ds << k.sourceVersion().flags(); + *ds << k.sourceVariant(); +} + /*! \return a serialized binary version of all the data held by the QShader, suitable for writing to files or other I/O devices. @@ -365,18 +374,42 @@ QByteArray QShader::serialized() const ds << d->shaders.count(); for (auto it = d->shaders.cbegin(), itEnd = d->shaders.cend(); it != itEnd; ++it) { const QShaderKey &k(it.key()); - ds << k.source(); - ds << k.sourceVersion().version(); - ds << k.sourceVersion().flags(); - ds << k.sourceVariant(); + writeShaderKey(&ds, k); const QShaderCode &shader(d->shaders.value(k)); ds << shader.shader(); ds << shader.entryPoint(); } + ds << d->bindings.count(); + for (auto it = d->bindings.cbegin(), itEnd = d->bindings.cend(); it != itEnd; ++it) { + const QShaderKey &k(it.key()); + writeShaderKey(&ds, k); + const NativeResourceBindingMap &map(it.value()); + ds << map.count(); + for (auto mapIt = map.cbegin(), mapItEnd = map.cend(); mapIt != mapItEnd; ++mapIt) { + ds << mapIt.key(); + ds << mapIt.value().first; + ds << mapIt.value().second; + } + } return qCompress(buf.buffer()); } +static void readShaderKey(QDataStream *ds, QShaderKey *k) +{ + int intVal; + *ds >> intVal; + k->setSource(QShader::Source(intVal)); + QShaderVersion ver; + *ds >> intVal; + ver.setVersion(intVal); + *ds >> intVal; + ver.setFlags(QShaderVersion::Flags(intVal)); + k->setSourceVersion(ver); + *ds >> intVal; + k->setSourceVariant(QShader::Variant(intVal)); +} + /*! Creates a new QShader instance from the given \a data. @@ -396,8 +429,11 @@ QShader QShader::fromSerialized(const QByteArray &data) Q_ASSERT(d->ref.loadRelaxed() == 1); // must be detached int intVal; ds >> intVal; - if (intVal != QSB_VERSION) + const int qsbVersion = intVal; + if (qsbVersion != QSB_VERSION && qsbVersion != QSB_VERSION_WITHOUT_BINDINGS) { + qWarning("Attempted to deserialize QShader with unknown version %d.", qsbVersion); return QShader(); + } ds >> intVal; d->stage = Stage(intVal); @@ -408,16 +444,7 @@ QShader QShader::fromSerialized(const QByteArray &data) ds >> count; for (int i = 0; i < count; ++i) { QShaderKey k; - ds >> intVal; - k.setSource(Source(intVal)); - QShaderVersion ver; - ds >> intVal; - ver.setVersion(intVal); - ds >> intVal; - ver.setFlags(QShaderVersion::Flags(intVal)); - k.setSourceVersion(ver); - ds >> intVal; - k.setSourceVariant(Variant(intVal)); + readShaderKey(&ds, &k); QShaderCode shader; QByteArray s; ds >> s; @@ -427,6 +454,27 @@ QShader QShader::fromSerialized(const QByteArray &data) d->shaders[k] = shader; } + if (qsbVersion != QSB_VERSION_WITHOUT_BINDINGS) { + ds >> count; + for (int i = 0; i < count; ++i) { + QShaderKey k; + readShaderKey(&ds, &k); + NativeResourceBindingMap map; + int mapSize; + ds >> mapSize; + for (int b = 0; b < mapSize; ++b) { + int binding; + ds >> binding; + int firstNativeBinding; + ds >> firstNativeBinding; + int secondNativeBinding; + ds >> secondNativeBinding; + map.insert(binding, { firstNativeBinding, secondNativeBinding }); + } + d->bindings.insert(k, map); + } + } + return bs; } @@ -460,7 +508,7 @@ bool operator==(const QShader &lhs, const QShader &rhs) Q_DECL_NOTHROW { return lhs.d->stage == rhs.d->stage && lhs.d->shaders == rhs.d->shaders; - // do not bother with desc, if the shader code is the same, the description must match too + // do not bother with desc and bindings, if the shader code is the same, the description must match too } /*! @@ -586,4 +634,66 @@ QDebug operator<<(QDebug dbg, const QShaderVersion &v) } #endif // QT_NO_DEBUG_STREAM +/*! + \typedef QShader::NativeResourceBindingMap + + Synonym for QHash>. + + The resource binding model QRhi assumes is based on SPIR-V. This means that + uniform buffers, storage buffers, combined image samplers, and storage + images share a common binding point space. The binding numbers in + QShaderDescription and QRhiShaderResourceBinding are expected to match the + \c binding layout qualifier in the Vulkan-compatible GLSL shader. + + Graphics APIs other than Vulkan may use a resource binding model that is + not fully compatible with this. In addition, the generator of the shader + code translated from SPIR-V may choose not to take the SPIR-V binding + qualifiers into account, for various reasons. (this is the case with the + Metal backend of SPIRV-Cross, for example). + + Therefore, a QShader may expose an additional map that describes what the + native binding point for a given SPIR-V binding is. The QRhi backends are + expected to use this map automatically, as appropriate. The value is a + pair, because combined image samplers may map to two native resources (a + texture and a sampler) in some shading languages. In that case the second + value refers to the sampler. +*/ + +/*! + \return the native binding map for \a key or null if no extra mapping is + available, or is not applicable. + */ +const QShader::NativeResourceBindingMap *QShader::nativeResourceBindingMap(const QShaderKey &key) const +{ + auto it = d->bindings.constFind(key); + if (it == d->bindings.cend()) + return nullptr; + + return &it.value(); +} + +/*! + Stores the given native resource binding \a map associated with \a key. + + \sa nativeResourceBindingMap() + */ +void QShader::setResourceBindingMap(const QShaderKey &key, const NativeResourceBindingMap &map) +{ + detach(); + d->bindings[key] = map; +} + +/*! + Removes the native resource binding map for \a key. + */ +void QShader::removeResourceBindingMap(const QShaderKey &key) +{ + auto it = d->bindings.find(key); + if (it == d->bindings.end()) + return; + + detach(); + d->bindings.erase(it); +} + QT_END_NAMESPACE diff --git a/src/gui/rhi/qshader_p.h b/src/gui/rhi/qshader_p.h index 243842a95a..4b561b6fa9 100644 --- a/src/gui/rhi/qshader_p.h +++ b/src/gui/rhi/qshader_p.h @@ -149,6 +149,11 @@ public: QByteArray serialized() const; static QShader fromSerialized(const QByteArray &data); + using NativeResourceBindingMap = QHash >; // binding -> native_binding[, native_binding] + const NativeResourceBindingMap *nativeResourceBindingMap(const QShaderKey &key) const; + void setResourceBindingMap(const QShaderKey &key, const NativeResourceBindingMap &map); + void removeResourceBindingMap(const QShaderKey &key); + private: QShaderPrivate *d; friend struct QShaderPrivate; diff --git a/src/gui/rhi/qshader_p_p.h b/src/gui/rhi/qshader_p_p.h index 6473590e95..4535e01491 100644 --- a/src/gui/rhi/qshader_p_p.h +++ b/src/gui/rhi/qshader_p_p.h @@ -66,7 +66,8 @@ struct Q_GUI_EXPORT QShaderPrivate : ref(1), stage(other->stage), desc(other->desc), - shaders(other->shaders) + shaders(other->shaders), + bindings(other->bindings) { } @@ -77,6 +78,7 @@ struct Q_GUI_EXPORT QShaderPrivate QShader::Stage stage = QShader::VertexStage; QShaderDescription desc; QHash shaders; + QHash bindings; }; QT_END_NAMESPACE From cc631c15a00f26fa8f6aa703fd7cf9e715be8f3c Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Tue, 22 Oct 2019 12:37:34 +0200 Subject: [PATCH 33/47] rhi: metal: Remap resource bindings based on the QShader table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ...when available. Fall back to the QRhi (i.e. SPIR-V) binding point otherwise (which becomes unsafe once shadertools bumps its SPIRV-Cross snapshot, but is fine for existing .qsb files) Task-number: QTBUG-79368 Change-Id: I2d452fdd4efb484867732c358171a800d3261dcd Reviewed-by: Christian Strømme --- src/gui/rhi/qrhimetal.mm | 97 ++++++++++++++++++++++++++----------- src/gui/rhi/qrhimetal_p_p.h | 7 ++- 2 files changed, 73 insertions(+), 31 deletions(-) diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index 5f14d917b8..b2d56b43af 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -35,8 +35,6 @@ ****************************************************************************/ #include "qrhimetal_p_p.h" -#include "qshader_p.h" -#include "qshaderdescription_p.h" #include #include #include @@ -143,8 +141,10 @@ struct QMetalShader id lib = nil; id func = nil; std::array localSize; + QShader::NativeResourceBindingMap nativeResourceBindingMap; void release() { + nativeResourceBindingMap.clear(); [lib release]; lib = nil; [func release]; @@ -164,7 +164,7 @@ struct QRhiMetalData const QRhiDepthStencilClearValue &depthStencilClearValue, int colorAttCount); id createMetalLib(const QShader &shader, QShader::Variant shaderVariant, - QString *error, QByteArray *entryPoint); + QString *error, QByteArray *entryPoint, QShaderKey *activeKey); id createMSLShaderFunction(id lib, const QByteArray &entryPoint); struct DeferredReleaseEntry { @@ -653,18 +653,39 @@ QRhiShaderResourceBindings *QRhiMetal::createShaderResourceBindings() return new QMetalShaderResourceBindings(this); } -void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD, +enum class BindingType { + Buffer, + Texture, + Sampler +}; + +static inline int mapBinding(int binding, + int stageIndex, + const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[], + BindingType type) +{ + const QShader::NativeResourceBindingMap *map = nativeResourceBindingMaps[stageIndex]; + if (map) { + auto it = map->constFind(binding); + if (it != map->cend()) + return type == BindingType::Sampler ? it->second : it->first; + } + return binding; +} + +void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, + QMetalCommandBuffer *cbD, int dynamicOffsetCount, const QRhiCommandBuffer::DynamicOffset *dynamicOffsets, - bool offsetOnlyChange) + bool offsetOnlyChange, + const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES]) { - static const int KNOWN_STAGES = 3; struct { QRhiBatchedBindings > buffers; QRhiBatchedBindings bufferOffsets; QRhiBatchedBindings > textures; QRhiBatchedBindings > samplers; - } res[KNOWN_STAGES]; + } res[SUPPORTED_STAGES]; for (const QRhiShaderResourceBinding &binding : qAsConst(srbD->sortedBindings)) { const QRhiShaderResourceBinding::Data *b = binding.data(); @@ -682,15 +703,15 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD } } if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { - res[0].buffers.feed(b->binding, mtlbuf); + res[0].buffers.feed(mapBinding(b->binding, 0, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); res[0].bufferOffsets.feed(b->binding, offset); } if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - res[1].buffers.feed(b->binding, mtlbuf); + res[1].buffers.feed(mapBinding(b->binding, 1, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); res[1].bufferOffsets.feed(b->binding, offset); } if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - res[2].buffers.feed(b->binding, mtlbuf); + res[2].buffers.feed(mapBinding(b->binding, 2, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); res[2].bufferOffsets.feed(b->binding, offset); } } @@ -700,16 +721,16 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex); QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler); if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { - res[0].textures.feed(b->binding, texD->d->tex); - res[0].samplers.feed(b->binding, samplerD->d->samplerState); + res[0].textures.feed(mapBinding(b->binding, 0, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex); + res[0].samplers.feed(mapBinding(b->binding, 0, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState); } if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - res[1].textures.feed(b->binding, texD->d->tex); - res[1].samplers.feed(b->binding, samplerD->d->samplerState); + res[1].textures.feed(mapBinding(b->binding, 1, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex); + res[1].samplers.feed(mapBinding(b->binding, 1, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState); } if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - res[2].textures.feed(b->binding, texD->d->tex); - res[2].samplers.feed(b->binding, samplerD->d->samplerState); + res[2].textures.feed(mapBinding(b->binding, 2, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex); + res[2].samplers.feed(mapBinding(b->binding, 2, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState); } } break; @@ -722,11 +743,11 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex); id t = texD->d->viewForLevel(b->u.simage.level); if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) - res[0].textures.feed(b->binding, t); + res[0].textures.feed(mapBinding(b->binding, 0, nativeResourceBindingMaps, BindingType::Texture), t); if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) - res[1].textures.feed(b->binding, t); + res[1].textures.feed(mapBinding(b->binding, 1, nativeResourceBindingMaps, BindingType::Texture), t); if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) - res[2].textures.feed(b->binding, t); + res[2].textures.feed(mapBinding(b->binding, 2, nativeResourceBindingMaps, BindingType::Texture), t); } break; case QRhiShaderResourceBinding::BufferLoad: @@ -739,15 +760,15 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD id mtlbuf = bufD->d->buf[0]; uint offset = uint(b->u.sbuf.offset); if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { - res[0].buffers.feed(b->binding, mtlbuf); + res[0].buffers.feed(mapBinding(b->binding, 0, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); res[0].bufferOffsets.feed(b->binding, offset); } if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - res[1].buffers.feed(b->binding, mtlbuf); + res[1].buffers.feed(mapBinding(b->binding, 1, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); res[1].bufferOffsets.feed(b->binding, offset); } if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - res[2].buffers.feed(b->binding, mtlbuf); + res[2].buffers.feed(mapBinding(b->binding, 2, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); res[2].bufferOffsets.feed(b->binding, offset); } } @@ -758,7 +779,7 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD } } - for (int idx = 0; idx < KNOWN_STAGES; ++idx) { + for (int idx = 0; idx < SUPPORTED_STAGES; ++idx) { res[idx].buffers.finish(); res[idx].bufferOffsets.finish(); @@ -973,18 +994,22 @@ void QRhiMetal::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBind // dynamic uniform buffer offsets always trigger a rebind if (hasDynamicOffsetInSrb || resNeedsRebind || srbChanged || srbRebuilt) { + const QShader::NativeResourceBindingMap *resBindMaps[SUPPORTED_STAGES] = { nullptr, nullptr, nullptr }; if (gfxPsD) { cbD->currentGraphicsSrb = srb; cbD->currentComputeSrb = nullptr; + resBindMaps[0] = &gfxPsD->d->vs.nativeResourceBindingMap; + resBindMaps[1] = &gfxPsD->d->fs.nativeResourceBindingMap; } else { cbD->currentGraphicsSrb = nullptr; cbD->currentComputeSrb = srb; + resBindMaps[2] = &compPsD->d->cs.nativeResourceBindingMap; } cbD->currentSrbGeneration = srbD->generation; cbD->currentResSlot = resSlot; const bool offsetOnlyChange = hasDynamicOffsetInSrb && !resNeedsRebind && !srbChanged && !srbRebuilt; - enqueueShaderResourceBindings(srbD, cbD, dynamicOffsetCount, dynamicOffsets, offsetOnlyChange); + enqueueShaderResourceBindings(srbD, cbD, dynamicOffsetCount, dynamicOffsets, offsetOnlyChange, resBindMaps); } } @@ -3081,9 +3106,10 @@ static inline MTLCullMode toMetalCullMode(QRhiGraphicsPipeline::CullMode c) } id QRhiMetalData::createMetalLib(const QShader &shader, QShader::Variant shaderVariant, - QString *error, QByteArray *entryPoint) + QString *error, QByteArray *entryPoint, QShaderKey *activeKey) { - QShaderCode mtllib = shader.shader({ QShader::MetalLibShader, 12, shaderVariant }); + QShaderKey key = { QShader::MetalLibShader, 12, shaderVariant }; + QShaderCode mtllib = shader.shader(key); if (!mtllib.shader().isEmpty()) { dispatch_data_t data = dispatch_data_create(mtllib.shader().constData(), size_t(mtllib.shader().size()), @@ -3094,6 +3120,7 @@ id QRhiMetalData::createMetalLib(const QShader &shader, QShader::Var dispatch_release(data); if (!err) { *entryPoint = mtllib.entryPoint(); + *activeKey = key; return lib; } else { const QString msg = QString::fromNSString(err.localizedDescription); @@ -3101,7 +3128,8 @@ id QRhiMetalData::createMetalLib(const QShader &shader, QShader::Var } } - QShaderCode mslSource = shader.shader({ QShader::MslShader, 12, shaderVariant }); + key = { QShader::MslShader, 12, shaderVariant }; + QShaderCode mslSource = shader.shader(key); if (mslSource.shader().isEmpty()) { qWarning() << "No MSL 1.2 code found in baked shader" << shader; return nil; @@ -3122,6 +3150,7 @@ id QRhiMetalData::createMetalLib(const QShader &shader, QShader::Var } *entryPoint = mslSource.entryPoint(); + *activeKey = key; return lib; } @@ -3195,9 +3224,12 @@ bool QMetalGraphicsPipeline::build() break; } } else { + const QShader shader = shaderStage.shader(); QString error; QByteArray entryPoint; - id lib = rhiD->d->createMetalLib(shaderStage.shader(), shaderStage.shaderVariant(), &error, &entryPoint); + QShaderKey activeKey; + id lib = rhiD->d->createMetalLib(shader, shaderStage.shaderVariant(), + &error, &entryPoint, &activeKey); if (!lib) { qWarning("MSL shader compilation failed: %s", qPrintable(error)); return false; @@ -3218,6 +3250,8 @@ bool QMetalGraphicsPipeline::build() case QRhiShaderStage::Vertex: d->vs.lib = lib; d->vs.func = func; + if (const QShader::NativeResourceBindingMap *map = shader.nativeResourceBindingMap(activeKey)) + d->vs.nativeResourceBindingMap = *map; rhiD->d->shaderCache.insert(shaderStage, d->vs); [d->vs.lib retain]; [d->vs.func retain]; @@ -3226,6 +3260,8 @@ bool QMetalGraphicsPipeline::build() case QRhiShaderStage::Fragment: d->fs.lib = lib; d->fs.func = func; + if (const QShader::NativeResourceBindingMap *map = shader.nativeResourceBindingMap(activeKey)) + d->fs.nativeResourceBindingMap = *map; rhiD->d->shaderCache.insert(shaderStage, d->fs); [d->fs.lib retain]; [d->fs.func retain]; @@ -3360,8 +3396,9 @@ bool QMetalComputePipeline::build() const QShader shader = m_shaderStage.shader(); QString error; QByteArray entryPoint; + QShaderKey activeKey; id lib = rhiD->d->createMetalLib(shader, m_shaderStage.shaderVariant(), - &error, &entryPoint); + &error, &entryPoint, &activeKey); if (!lib) { qWarning("MSL shader compilation failed: %s", qPrintable(error)); return false; @@ -3375,6 +3412,8 @@ bool QMetalComputePipeline::build() d->cs.lib = lib; d->cs.func = func; d->cs.localSize = shader.description().computeShaderLocalSize(); + if (const QShader::NativeResourceBindingMap *map = shader.nativeResourceBindingMap(activeKey)) + d->cs.nativeResourceBindingMap = *map; if (rhiD->d->shaderCache.count() >= QRhiMetal::MAX_SHADER_CACHE_ENTRIES) { for (QMetalShader &s : rhiD->d->shaderCache) diff --git a/src/gui/rhi/qrhimetal_p_p.h b/src/gui/rhi/qrhimetal_p_p.h index 688fec8147..2be86db5c8 100644 --- a/src/gui/rhi/qrhimetal_p_p.h +++ b/src/gui/rhi/qrhimetal_p_p.h @@ -433,10 +433,13 @@ public: qsizetype *curOfs); void enqueueResourceUpdates(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates); void executeBufferHostWritesForCurrentFrame(QMetalBuffer *bufD); - void enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, QMetalCommandBuffer *cbD, + static const int SUPPORTED_STAGES = 3; + void enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD, + QMetalCommandBuffer *cbD, int dynamicOffsetCount, const QRhiCommandBuffer::DynamicOffset *dynamicOffsets, - bool offsetOnlyChange); + bool offsetOnlyChange, + const QShader::NativeResourceBindingMap *nativeResourceBindingMaps[SUPPORTED_STAGES]); int effectiveSampleCount(int sampleCount) const; bool importedDevice = false; From 4106275a7fc9ab3abe2fa2ca0b107a3e96e36ca0 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Wed, 23 Oct 2019 10:21:24 +0200 Subject: [PATCH 34/47] rhi: metal: Fix and clean up committing resource bindings Make it readable by using names instead of mere indices for the stages. There is an important fix in there as well: when in a render pass, only resource for VERTEX and FRAGMENT are taken into account, while in a compute pass those are skipped. This ensures that we do not send messages to a nil or invalid MTLRender/ComputeCommandEncoder. (nil would not be an error but the other is fatal) Task-number: QTBUG-79447 Change-Id: Ibef108cb7c82b5b0fdd2a299cd89fbebe8c3606a Reviewed-by: Paul Olav Tvete --- src/gui/rhi/qrhimetal.mm | 96 +++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 45 deletions(-) diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index b2d56b43af..131b2da802 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -686,6 +686,7 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD QRhiBatchedBindings > textures; QRhiBatchedBindings > samplers; } res[SUPPORTED_STAGES]; + enum { VERTEX = 0, FRAGMENT = 1, COMPUTE = 2 }; for (const QRhiShaderResourceBinding &binding : qAsConst(srbD->sortedBindings)) { const QRhiShaderResourceBinding::Data *b = binding.data(); @@ -703,16 +704,16 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD } } if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { - res[0].buffers.feed(mapBinding(b->binding, 0, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); - res[0].bufferOffsets.feed(b->binding, offset); + res[VERTEX].buffers.feed(mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); + res[VERTEX].bufferOffsets.feed(b->binding, offset); } if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - res[1].buffers.feed(mapBinding(b->binding, 1, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); - res[1].bufferOffsets.feed(b->binding, offset); + res[FRAGMENT].buffers.feed(mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); + res[FRAGMENT].bufferOffsets.feed(b->binding, offset); } if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - res[2].buffers.feed(mapBinding(b->binding, 2, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); - res[2].bufferOffsets.feed(b->binding, offset); + res[COMPUTE].buffers.feed(mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); + res[COMPUTE].bufferOffsets.feed(b->binding, offset); } } break; @@ -721,16 +722,16 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.stex.tex); QMetalSampler *samplerD = QRHI_RES(QMetalSampler, b->u.stex.sampler); if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { - res[0].textures.feed(mapBinding(b->binding, 0, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex); - res[0].samplers.feed(mapBinding(b->binding, 0, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState); + res[VERTEX].textures.feed(mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex); + res[VERTEX].samplers.feed(mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState); } if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - res[1].textures.feed(mapBinding(b->binding, 1, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex); - res[1].samplers.feed(mapBinding(b->binding, 1, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState); + res[FRAGMENT].textures.feed(mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex); + res[FRAGMENT].samplers.feed(mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState); } if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - res[2].textures.feed(mapBinding(b->binding, 2, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex); - res[2].samplers.feed(mapBinding(b->binding, 2, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState); + res[COMPUTE].textures.feed(mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture), texD->d->tex); + res[COMPUTE].samplers.feed(mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Sampler), samplerD->d->samplerState); } } break; @@ -743,11 +744,11 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD QMetalTexture *texD = QRHI_RES(QMetalTexture, b->u.simage.tex); id t = texD->d->viewForLevel(b->u.simage.level); if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) - res[0].textures.feed(mapBinding(b->binding, 0, nativeResourceBindingMaps, BindingType::Texture), t); + res[VERTEX].textures.feed(mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Texture), t); if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) - res[1].textures.feed(mapBinding(b->binding, 1, nativeResourceBindingMaps, BindingType::Texture), t); + res[FRAGMENT].textures.feed(mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Texture), t); if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) - res[2].textures.feed(mapBinding(b->binding, 2, nativeResourceBindingMaps, BindingType::Texture), t); + res[COMPUTE].textures.feed(mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Texture), t); } break; case QRhiShaderResourceBinding::BufferLoad: @@ -760,16 +761,16 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD id mtlbuf = bufD->d->buf[0]; uint offset = uint(b->u.sbuf.offset); if (b->stage.testFlag(QRhiShaderResourceBinding::VertexStage)) { - res[0].buffers.feed(mapBinding(b->binding, 0, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); - res[0].bufferOffsets.feed(b->binding, offset); + res[VERTEX].buffers.feed(mapBinding(b->binding, VERTEX, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); + res[VERTEX].bufferOffsets.feed(b->binding, offset); } if (b->stage.testFlag(QRhiShaderResourceBinding::FragmentStage)) { - res[1].buffers.feed(mapBinding(b->binding, 1, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); - res[1].bufferOffsets.feed(b->binding, offset); + res[FRAGMENT].buffers.feed(mapBinding(b->binding, FRAGMENT, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); + res[FRAGMENT].bufferOffsets.feed(b->binding, offset); } if (b->stage.testFlag(QRhiShaderResourceBinding::ComputeStage)) { - res[2].buffers.feed(mapBinding(b->binding, 2, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); - res[2].bufferOffsets.feed(b->binding, offset); + res[COMPUTE].buffers.feed(mapBinding(b->binding, COMPUTE, nativeResourceBindingMaps, BindingType::Buffer), mtlbuf); + res[COMPUTE].bufferOffsets.feed(b->binding, offset); } } break; @@ -779,25 +780,30 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD } } - for (int idx = 0; idx < SUPPORTED_STAGES; ++idx) { - res[idx].buffers.finish(); - res[idx].bufferOffsets.finish(); + for (int stage = 0; stage < SUPPORTED_STAGES; ++stage) { + if (cbD->recordingPass != QMetalCommandBuffer::RenderPass && (stage == VERTEX || stage == FRAGMENT)) + continue; + if (cbD->recordingPass != QMetalCommandBuffer::ComputePass && stage == COMPUTE) + continue; - for (int i = 0, ie = res[idx].buffers.batches.count(); i != ie; ++i) { - const auto &bufferBatch(res[idx].buffers.batches[i]); - const auto &offsetBatch(res[idx].bufferOffsets.batches[i]); - switch (idx) { - case 0: + res[stage].buffers.finish(); + res[stage].bufferOffsets.finish(); + + for (int i = 0, ie = res[stage].buffers.batches.count(); i != ie; ++i) { + const auto &bufferBatch(res[stage].buffers.batches[i]); + const auto &offsetBatch(res[stage].bufferOffsets.batches[i]); + switch (stage) { + case VERTEX: [cbD->d->currentRenderPassEncoder setVertexBuffers: bufferBatch.resources.constData() offsets: offsetBatch.resources.constData() withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))]; break; - case 1: + case FRAGMENT: [cbD->d->currentRenderPassEncoder setFragmentBuffers: bufferBatch.resources.constData() offsets: offsetBatch.resources.constData() withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))]; break; - case 2: + case COMPUTE: [cbD->d->currentComputePassEncoder setBuffers: bufferBatch.resources.constData() offsets: offsetBatch.resources.constData() withRange: NSMakeRange(bufferBatch.startBinding, NSUInteger(bufferBatch.resources.count()))]; @@ -811,21 +817,21 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD if (offsetOnlyChange) continue; - res[idx].textures.finish(); - res[idx].samplers.finish(); + res[stage].textures.finish(); + res[stage].samplers.finish(); - for (int i = 0, ie = res[idx].textures.batches.count(); i != ie; ++i) { - const auto &batch(res[idx].textures.batches[i]); - switch (idx) { - case 0: + for (int i = 0, ie = res[stage].textures.batches.count(); i != ie; ++i) { + const auto &batch(res[stage].textures.batches[i]); + switch (stage) { + case VERTEX: [cbD->d->currentRenderPassEncoder setVertexTextures: batch.resources.constData() withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))]; break; - case 1: + case FRAGMENT: [cbD->d->currentRenderPassEncoder setFragmentTextures: batch.resources.constData() withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))]; break; - case 2: + case COMPUTE: [cbD->d->currentComputePassEncoder setTextures: batch.resources.constData() withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))]; break; @@ -834,18 +840,18 @@ void QRhiMetal::enqueueShaderResourceBindings(QMetalShaderResourceBindings *srbD break; } } - for (int i = 0, ie = res[idx].samplers.batches.count(); i != ie; ++i) { - const auto &batch(res[idx].samplers.batches[i]); - switch (idx) { - case 0: + for (int i = 0, ie = res[stage].samplers.batches.count(); i != ie; ++i) { + const auto &batch(res[stage].samplers.batches[i]); + switch (stage) { + case VERTEX: [cbD->d->currentRenderPassEncoder setVertexSamplerStates: batch.resources.constData() withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))]; break; - case 1: + case FRAGMENT: [cbD->d->currentRenderPassEncoder setFragmentSamplerStates: batch.resources.constData() withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))]; break; - case 2: + case COMPUTE: [cbD->d->currentComputePassEncoder setSamplerStates: batch.resources.constData() withRange: NSMakeRange(batch.startBinding, NSUInteger(batch.resources.count()))]; break; From c46eec096f30b69c1b683f8417d27e0234590bfa Mon Sep 17 00:00:00 2001 From: Robert Loehning Date: Fri, 18 Oct 2019 16:44:55 +0200 Subject: [PATCH 35/47] QStateMachine: Don't scream at the user Change-Id: I171606d10985bc7338b0f24ceb142fc0d88e7932 Reviewed-by: Thiago Macieira Reviewed-by: Leena Miettinen --- src/corelib/statemachine/qstatemachine.cpp | 4 ++-- .../statemachine/qstatemachine/tst_qstatemachine.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/corelib/statemachine/qstatemachine.cpp b/src/corelib/statemachine/qstatemachine.cpp index 945e36968f..a97700e5d0 100644 --- a/src/corelib/statemachine/qstatemachine.cpp +++ b/src/corelib/statemachine/qstatemachine.cpp @@ -1498,7 +1498,7 @@ void QStateMachinePrivate::setError(QStateMachine::Error errorCode, QAbstractSta case QStateMachine::StateMachineChildModeSetToParallelError: Q_ASSERT(currentContext != nullptr); - errorString = QStateMachine::tr("Child mode of state machine '%1' is not 'ExclusiveStates'!") + errorString = QStateMachine::tr("Child mode of state machine '%1' is not 'ExclusiveStates'.") .arg(currentContext->objectName()); break; @@ -2469,7 +2469,7 @@ QStateMachine::QStateMachine(QObject *parent) and \a parent. \warning Do not set the \a childMode to anything else than \l{ExclusiveStates}, otherwise the - state machine is invalid, and might work incorrectly! + state machine is invalid, and might work incorrectly. */ QStateMachine::QStateMachine(QState::ChildMode childMode, QObject *parent) : QState(*new QStateMachinePrivate, /*parentState=*/0) diff --git a/tests/auto/corelib/statemachine/qstatemachine/tst_qstatemachine.cpp b/tests/auto/corelib/statemachine/qstatemachine/tst_qstatemachine.cpp index 72a6f0360d..4b13ac45cc 100644 --- a/tests/auto/corelib/statemachine/qstatemachine/tst_qstatemachine.cpp +++ b/tests/auto/corelib/statemachine/qstatemachine/tst_qstatemachine.cpp @@ -338,7 +338,7 @@ void tst_QStateMachine::transitionToRootState() machine.postEvent(new QEvent(QEvent::User)); QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: " - "Child mode of state machine 'machine' is not 'ExclusiveStates'!"); + "Child mode of state machine 'machine' is not 'ExclusiveStates'."); QCoreApplication::processEvents(); QVERIFY(machine.configuration().isEmpty()); QVERIFY(!machine.isRunning()); @@ -1064,7 +1064,7 @@ void tst_QStateMachine::transitionToStateNotInGraph() machine.start(); QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: " - "Child mode of state machine '' is not 'ExclusiveStates'!"); + "Child mode of state machine '' is not 'ExclusiveStates'."); QCoreApplication::processEvents(); QCOMPARE(machine.isRunning(), false); @@ -2103,7 +2103,7 @@ void tst_QStateMachine::parallelRootState() QVERIFY(finishedSpy.isValid()); machine.start(); QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: " - "Child mode of state machine '' is not 'ExclusiveStates'!"); + "Child mode of state machine '' is not 'ExclusiveStates'."); QTRY_COMPARE(startedSpy.count(), 1); QCOMPARE(machine.configuration().size(), 4); QVERIFY(machine.configuration().contains(s1)); @@ -3316,7 +3316,7 @@ void tst_QStateMachine::targetStateWithNoParent() machine.start(); QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: " - "Child mode of state machine '' is not 'ExclusiveStates'!"); + "Child mode of state machine '' is not 'ExclusiveStates'."); TEST_ACTIVE_CHANGED(s1, 2); QTRY_COMPARE(startedSpy.count(), 1); QCOMPARE(machine.isRunning(), false); From 86a0f1cfd7942e31d1462f74769eef5a5cf3c9ab Mon Sep 17 00:00:00 2001 From: BogDan Vatra Date: Tue, 22 Oct 2019 12:41:26 +0300 Subject: [PATCH 36/47] Add build-id flag This flag is needed by LLDB & simple perf tools to locate the right binary. Change-Id: Iffa1b0678663cfb9d1d699da5ad6fe672863918c Reviewed-by: Eskil Abrahamsen Blomfeldt --- mkspecs/android-clang/qmake.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mkspecs/android-clang/qmake.conf b/mkspecs/android-clang/qmake.conf index 81609c3962..ec6c765799 100644 --- a/mkspecs/android-clang/qmake.conf +++ b/mkspecs/android-clang/qmake.conf @@ -70,8 +70,8 @@ QMAKE_CFLAGS_THREAD = -D_REENTRANT QMAKE_CFLAGS_HIDESYMS = -fvisibility=hidden QMAKE_CFLAGS_NEON = -mfpu=neon -QMAKE_LFLAGS_APP = -Wl,--no-undefined -Wl,-z,noexecstack -shared -QMAKE_LFLAGS_SHLIB = -Wl,--no-undefined -Wl,-z,noexecstack -shared +QMAKE_LFLAGS_APP = -Wl,--build-id=sha1 -Wl,--no-undefined -Wl,-z,noexecstack -shared +QMAKE_LFLAGS_SHLIB = -Wl,--build-id=sha1 -Wl,--no-undefined -Wl,-z,noexecstack -shared QMAKE_LFLAGS_PLUGIN = $$QMAKE_LFLAGS_SHLIB QMAKE_LFLAGS_NOUNDEF = -Wl,--no-undefined QMAKE_LFLAGS_RPATH = -Wl,-rpath= From 444e947aadc82e2aeb72fa5b3e6a352ff02441f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Wed, 23 Oct 2019 15:33:46 +0200 Subject: [PATCH 37/47] macOS: Improve handling of wantsBestResolutionOpenGLSurface We were disabling wantsBestResolutionOpenGLSurface whenever we detected the Apple software renderer, but this isn't needed when layer-backed, and did in fact result in the exact same visual result as the bug the code was working around -- only rendering to a quarter of the viewport. We now apply the workaround only when software rendering is combined with surface-backed views. The logic has also been improved to not rely on string comparison to look for the software renderer, but instead uses the renderer ID that the context provides. Since tweaking the wantsBestResolutionOpenGLSurface is only relevant when using a window for GL rendering the logic has been moved into QCocoaGLContext. Change-Id: I021aaefbb7a9782bc8ee3c9703da246510326d50 Reviewed-by: Timur Pocheptsov --- src/plugins/platforms/cocoa/qcocoaglcontext.h | 4 +- .../platforms/cocoa/qcocoaglcontext.mm | 46 +++++++++++-------- .../platforms/cocoa/qnsview_drawing.mm | 11 ----- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.h b/src/plugins/platforms/cocoa/qcocoaglcontext.h index bb309c0713..4210a4ed3f 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.h +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.h @@ -50,6 +50,8 @@ QT_BEGIN_NAMESPACE +class QCocoaWindow; + class QCocoaGLContext : public QPlatformOpenGLContext { public: @@ -76,12 +78,12 @@ private: static NSOpenGLPixelFormat *pixelFormatForSurfaceFormat(const QSurfaceFormat &format); bool setDrawable(QPlatformSurface *surface); + void prepareDrawable(QCocoaWindow *platformWindow); void updateSurfaceFormat(); NSOpenGLContext *m_context = nil; NSOpenGLContext *m_shareContext = nil; QSurfaceFormat m_format; - bool m_didCheckForSoftwareContext = false; QVarLengthArray m_updateObservers; QAtomicInt m_needsUpdate = false; }; diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm index ba7d12ce30..76a210d0b6 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -368,23 +368,6 @@ bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface) [m_context makeCurrentContext]; if (surface->surface()->surfaceClass() == QSurface::Window) { - // Disable high-resolution surfaces when using the software renderer, which has the - // problem that the system silently falls back to a to using a low-resolution buffer - // when a high-resolution buffer is requested. This is not detectable using the NSWindow - // convertSizeToBacking and backingScaleFactor APIs. A typical result of this is that Qt - // will display a quarter of the window content when running in a virtual machine. - if (!m_didCheckForSoftwareContext) { - // FIXME: This ensures we check only once per context, - // but the context may be used for multiple surfaces. - m_didCheckForSoftwareContext = true; - - const GLubyte* renderer = glGetString(GL_RENDERER); - if (qstrcmp((const char *)renderer, "Apple Software Renderer") == 0) { - NSView *view = static_cast(surface)->m_view; - [view setWantsBestResolutionOpenGLSurface:NO]; - } - } - if (m_needsUpdate.fetchAndStoreRelaxed(false)) update(); } @@ -413,11 +396,14 @@ bool QCocoaGLContext::setDrawable(QPlatformSurface *surface) } Q_ASSERT(surface->surface()->surfaceClass() == QSurface::Window); - QNSView *view = qnsview_cast(static_cast(surface)->view()); + auto *cocoaWindow = static_cast(surface); + QNSView *view = qnsview_cast(cocoaWindow->view()); if (view == m_context.view) return true; + prepareDrawable(cocoaWindow); + // Setting the drawable may happen on a separate thread as a result of // a call to makeCurrent, so we need to set up the observers before we // associate the view with the context. That way we will guarantee that @@ -460,6 +446,30 @@ bool QCocoaGLContext::setDrawable(QPlatformSurface *surface) return true; } +void QCocoaGLContext::prepareDrawable(QCocoaWindow *platformWindow) +{ + // We generally want high-DPI GL surfaces, unless the user has explicitly disabled them + bool prefersBestResolutionOpenGLSurface = qt_mac_resolveOption(YES, + platformWindow->window(), "_q_mac_wantsBestResolutionOpenGLSurface", + "QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE"); + + auto *view = platformWindow->view(); + + // The only case we have to opt out ourselves is when using the Apple software renderer + // in combination with surface-backed views, as these together do not support high-DPI. + if (prefersBestResolutionOpenGLSurface) { + int rendererID = 0; + [m_context getValues:&rendererID forParameter:NSOpenGLContextParameterCurrentRendererID]; + bool isSoftwareRenderer = (rendererID & kCGLRendererIDMatchingMask) == kCGLRendererGenericFloatID; + if (isSoftwareRenderer && !view.layer) { + qCInfo(lcQpaOpenGLContext) << "Disabling high resolution GL surface due to software renderer"; + prefersBestResolutionOpenGLSurface = false; + } + } + + view.wantsBestResolutionOpenGLSurface = prefersBestResolutionOpenGLSurface; +} + // NSOpenGLContext is not re-entrant. Even when using separate contexts per thread, // view, and window, calls into the API will still deadlock. For more information // see https://openradar.appspot.com/37064579 diff --git a/src/plugins/platforms/cocoa/qnsview_drawing.mm b/src/plugins/platforms/cocoa/qnsview_drawing.mm index eb9286519d..2fd63fad67 100644 --- a/src/plugins/platforms/cocoa/qnsview_drawing.mm +++ b/src/plugins/platforms/cocoa/qnsview_drawing.mm @@ -44,17 +44,6 @@ - (void)initDrawing { [self updateLayerBacking]; - - // Enable high-DPI OpenGL for retina displays. Enabling has the side - // effect that Cocoa will start calling glViewport(0, 0, width, height), - // overriding any glViewport calls in application code. This is usually not a - // problem, except if the application wants to have a "custom" viewport. - // (like the hellogl example) - if (m_platformWindow->window()->supportsOpenGL()) { - self.wantsBestResolutionOpenGLSurface = qt_mac_resolveOption(YES, m_platformWindow->window(), - "_q_mac_wantsBestResolutionOpenGLSurface", "QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE"); - // See also QCocoaGLContext::makeCurrent for software renderer workarounds. - } } - (BOOL)isOpaque From 7c9ffe3e46bca3cfdec6fd1db3da4c96a6d5acd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Wed, 23 Oct 2019 14:16:24 +0200 Subject: [PATCH 38/47] rhi: Use Q_GLOBAL_STATIC for QRhiGles2 GL program cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Defers initialization until actually needed. Change-Id: Idb09dbad0dfa602949d381ee61565d9050e77e7c Reviewed-by: Laszlo Agocs Reviewed-by: Tor Arne Vestbø --- src/gui/rhi/qrhigles2.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index dec28cac9b..abee843a74 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -2961,7 +2961,7 @@ bool QRhiGles2::isProgramBinaryDiskCacheEnabled() const return checker.get(ctx)->isSupported(); } -static QOpenGLProgramBinaryCache qrhi_programBinaryCache; +Q_GLOBAL_STATIC(QOpenGLProgramBinaryCache, qrhi_programBinaryCache); static inline QShader::Stage toShaderStage(QRhiShaderStage::Type type) { @@ -2995,7 +2995,7 @@ QRhiGles2::DiskCacheResult QRhiGles2::tryLoadFromDiskCache(const QRhiShaderStage } diskCacheKey = binaryProgram.cacheKey(); - if (qrhi_programBinaryCache.load(diskCacheKey, program)) { + if (qrhi_programBinaryCache()->load(diskCacheKey, program)) { qCDebug(lcOpenGLProgramDiskCache, "Program binary received from cache, program %u, key %s", program, diskCacheKey.constData()); result = QRhiGles2::DiskCacheHit; @@ -3013,7 +3013,7 @@ void QRhiGles2::trySaveToDiskCache(GLuint program, const QByteArray &cacheKey) if (isProgramBinaryDiskCacheEnabled()) { qCDebug(lcOpenGLProgramDiskCache, "Saving program binary, program %u, key %s", program, cacheKey.constData()); - qrhi_programBinaryCache.save(cacheKey, program); + qrhi_programBinaryCache()->save(cacheKey, program); } } From f39230fcac4de01f26945bde16c3a10c5ac74afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Fri, 18 Oct 2019 16:23:48 +0200 Subject: [PATCH 39/47] macOS: Skip NSOpenGLContext flush if window exposed size is out of sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some clients such as QOpenGLWidget will end up drawing and flushing during the resize event, which for GL will result in an immediate update on the screen. The problem is that the underlying Core Animation layer, and the window's frame, has not been visually updated yet to the new size, so we end up drawing "ahead" of what the window server is showing the user. Ideally we'd be able to present the GL drawing in a transaction, in sync with the drawing of the window frame, but this API is only available for CAMetalLayer and CAEAGLLayer. As a workaround we detect when the exposed size is out of sync with the window geometry, and skip the flush until the exposed size has caught up. We know this will happen eventually as AppKit will always ask us to display after a resize. Change-Id: I1739ac8878b3fc6820a55dd017ddd170fd5f55d6 Fixes: QTBUG-79139 Reviewed-by: Morten Johan Sørvig --- src/plugins/platforms/cocoa/qcocoaglcontext.mm | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm index 76a210d0b6..7452d53579 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -501,6 +501,21 @@ void QCocoaGLContext::swapBuffers(QPlatformSurface *surface) return; } + if (m_context.view.layer) { + // Flushing an NSOpenGLContext will hit the screen immediately, ignoring + // any Core Animation transactions in place. This may result in major + // visual artifacts if the flush happens out of sync with the size + // of the layer, view, and window reflected by other parts of the UI, + // e.g. if the application flushes in the resize event or a timer during + // window resizing, instead of in the expose event. + auto *cocoaWindow = static_cast(surface); + if (cocoaWindow->geometry().size() != cocoaWindow->m_exposedRect.size()) { + qCInfo(lcQpaOpenGLContext) << "Window exposed size does not match geometry (yet)." + << "Skipping flush to avoid visual artifacts."; + return; + } + } + QMutexLocker locker(&s_reentrancyMutex); [m_context flushBuffer]; } From 312793f28eaa1eddd8ffd15f42f51c31ecd1c4ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Wed, 23 Oct 2019 13:15:07 +0200 Subject: [PATCH 40/47] macOS: Respect Qt::AA_UseSoftwareOpenGL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: Ia83e8e9e571e4f46d2a8d810c376015552755457 Reviewed-by: Morten Johan Sørvig --- src/plugins/platforms/cocoa/qcocoaglcontext.mm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm index 7452d53579..900f0608c8 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -223,6 +223,12 @@ NSOpenGLPixelFormat *QCocoaGLContext::pixelFormatForSurfaceFormat(const QSurface attrs << NSOpenGLPFAAllowOfflineRenderers; } + if (qGuiApp->testAttribute(Qt::AA_UseSoftwareOpenGL)) { + // kCGLRendererGenericFloatID is the modern software renderer on macOS, + // as opposed to kCGLRendererGenericID, which is deprecated. + attrs << NSOpenGLPFARendererID << kCGLRendererGenericFloatID; + } + // FIXME: Pull this information out of the NSView QByteArray useLayer = qgetenv("QT_MAC_WANTS_LAYER"); if (!useLayer.isEmpty() && useLayer.toInt() > 0) { From f2edc6cb3a12063005c77aae7946f4a07d3bd30c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Wed, 23 Oct 2019 13:21:32 +0200 Subject: [PATCH 41/47] macOS: Don't set NSOpenGLPFANoRecovery for layer-backed views MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Apple software renderer is perfectly capable of being used when compositing CA layers. Change-Id: I3b78ff61a79869ecdb7bd431388041f2c124472e Reviewed-by: Morten Johan Sørvig --- src/plugins/platforms/cocoa/qcocoaglcontext.mm | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm index 900f0608c8..b312e033cd 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -229,14 +229,6 @@ NSOpenGLPixelFormat *QCocoaGLContext::pixelFormatForSurfaceFormat(const QSurface attrs << NSOpenGLPFARendererID << kCGLRendererGenericFloatID; } - // FIXME: Pull this information out of the NSView - QByteArray useLayer = qgetenv("QT_MAC_WANTS_LAYER"); - if (!useLayer.isEmpty() && useLayer.toInt() > 0) { - // Disable the software rendering fallback. This makes compositing - // OpenGL and raster NSViews using Core Animation layers possible. - attrs << NSOpenGLPFANoRecovery; - } - attrs << 0; // 0-terminate array return [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs.constData()]; } From 9ab043b6889739ce495c395281774ee2db706dab Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Wed, 4 Sep 2019 15:26:10 +0200 Subject: [PATCH 42/47] QFileSystemEngine: Consistently check for invalid file names stat() and friends expect a null-terminated C string. There is no way to generate anything useful from a string that has null bytes in the middle. It's important to catch this early, as otherwise, for example, a QDir::exists() on such a path can return true, as the path is silently truncated. Extend the checks for empty file names to windows and add checks for null bytes. Change-Id: Ie9794c3a7c4fd57f9a66bdbbab8b45a08b6f9170 Reviewed-by: Thiago Macieira --- src/corelib/io/qfilesystemengine_p.h | 30 +++++++++++++ src/corelib/io/qfilesystemengine_unix.cpp | 52 +++++++++-------------- src/corelib/io/qfilesystemengine_win.cpp | 27 +++++++++++- tests/auto/corelib/io/qfile/tst_qfile.cpp | 4 ++ 4 files changed, 79 insertions(+), 34 deletions(-) diff --git a/src/corelib/io/qfilesystemengine_p.h b/src/corelib/io/qfilesystemengine_p.h index e44837747c..ecfdc03743 100644 --- a/src/corelib/io/qfilesystemengine_p.h +++ b/src/corelib/io/qfilesystemengine_p.h @@ -58,6 +58,36 @@ QT_BEGIN_NAMESPACE +#define Q_RETURN_ON_INVALID_FILENAME(message, result) \ + { \ + QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC).warning(message); \ + errno = EINVAL; \ + return (result); \ + } + +inline bool qIsFilenameBroken(const QByteArray &name) +{ + return name.contains('\0'); +} + +inline bool qIsFilenameBroken(const QString &name) +{ + return name.contains(QLatin1Char('\0')); +} + +inline bool qIsFilenameBroken(const QFileSystemEntry &entry) +{ + return qIsFilenameBroken(entry.nativeFilePath()); +} + +#define Q_CHECK_FILE_NAME(name, result) \ + do { \ + if (Q_UNLIKELY((name).isEmpty())) \ + Q_RETURN_ON_INVALID_FILENAME("Empty filename passed to function", (result)); \ + if (Q_UNLIKELY(qIsFilenameBroken(name))) \ + Q_RETURN_ON_INVALID_FILENAME("Broken filename passed to function", (result)); \ + } while (false) + class QFileSystemEngine { public: diff --git a/src/corelib/io/qfilesystemengine_unix.cpp b/src/corelib/io/qfilesystemengine_unix.cpp index 74865fe31f..c3abec8989 100644 --- a/src/corelib/io/qfilesystemengine_unix.cpp +++ b/src/corelib/io/qfilesystemengine_unix.cpp @@ -118,13 +118,6 @@ enum { #endif }; -#define emptyFileEntryWarning() emptyFileEntryWarning_(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC) -static void emptyFileEntryWarning_(const char *file, int line, const char *function) -{ - QMessageLogger(file, line, function).warning("Empty filename passed to function"); - errno = EINVAL; -} - #if defined(Q_OS_DARWIN) static inline bool hasResourcePropertyFlag(const QFileSystemMetaData &data, const QFileSystemEntry &entry, @@ -625,8 +618,7 @@ void QFileSystemMetaData::fillFromDirEnt(const QT_DIRENT &entry) //static QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data) { - if (Q_UNLIKELY(link.isEmpty())) - return emptyFileEntryWarning(), link; + Q_CHECK_FILE_NAME(link, link); QByteArray s = qt_readlink(link.nativeFilePath().constData()); if (s.length() > 0) { @@ -685,10 +677,7 @@ QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, //static QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data) { - if (Q_UNLIKELY(entry.isEmpty())) - return emptyFileEntryWarning(), entry; - if (entry.isRoot()) - return entry; + Q_CHECK_FILE_NAME(entry, entry); #if !defined(Q_OS_MAC) && !defined(Q_OS_QNX) && !defined(Q_OS_ANDROID) && !defined(Q_OS_HAIKU) && _POSIX_VERSION < 200809L // realpath(X,0) is not supported @@ -738,8 +727,8 @@ QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, //static QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry) { - if (Q_UNLIKELY(entry.isEmpty())) - return emptyFileEntryWarning(), entry; + Q_CHECK_FILE_NAME(entry, entry); + if (entry.isAbsolute() && entry.isClean()) return entry; @@ -773,8 +762,7 @@ QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry) //static QByteArray QFileSystemEngine::id(const QFileSystemEntry &entry) { - if (Q_UNLIKELY(entry.isEmpty())) - return emptyFileEntryWarning(), QByteArray(); + Q_CHECK_FILE_NAME(entry, QByteArray()); QT_STATBUF statResult; if (QT_STAT(entry.nativeFilePath().constData(), &statResult)) { @@ -887,8 +875,7 @@ QString QFileSystemEngine::bundleName(const QFileSystemEntry &entry) bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data, QFileSystemMetaData::MetaDataFlags what) { - if (Q_UNLIKELY(entry.isEmpty())) - return emptyFileEntryWarning(), false; + Q_CHECK_FILE_NAME(entry, false); #if defined(Q_OS_DARWIN) if (what & QFileSystemMetaData::BundleType) { @@ -1157,8 +1144,7 @@ static bool createDirectoryWithParents(const QByteArray &nativeName, bool should bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents) { QString dirName = entry.filePath(); - if (Q_UNLIKELY(dirName.isEmpty())) - return emptyFileEntryWarning(), false; + Q_CHECK_FILE_NAME(dirName, false); // Darwin doesn't support trailing /'s, so remove for everyone while (dirName.size() > 1 && dirName.endsWith(QLatin1Char('/'))) @@ -1177,8 +1163,7 @@ bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool crea //static bool QFileSystemEngine::removeDirectory(const QFileSystemEntry &entry, bool removeEmptyParents) { - if (Q_UNLIKELY(entry.isEmpty())) - return emptyFileEntryWarning(), false; + Q_CHECK_FILE_NAME(entry, false); if (removeEmptyParents) { QString dirName = QDir::cleanPath(entry.filePath()); @@ -1203,8 +1188,9 @@ bool QFileSystemEngine::removeDirectory(const QFileSystemEntry &entry, bool remo //static bool QFileSystemEngine::createLink(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) { - if (Q_UNLIKELY(source.isEmpty() || target.isEmpty())) - return emptyFileEntryWarning(), false; + Q_CHECK_FILE_NAME(source, false); + Q_CHECK_FILE_NAME(target, false); + if (::symlink(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0) return true; error = QSystemError(errno, QSystemError::StandardLibraryError); @@ -1233,8 +1219,9 @@ bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSy { QFileSystemEntry::NativePath srcPath = source.nativeFilePath(); QFileSystemEntry::NativePath tgtPath = target.nativeFilePath(); - if (Q_UNLIKELY(srcPath.isEmpty() || tgtPath.isEmpty())) - return emptyFileEntryWarning(), false; + + Q_CHECK_FILE_NAME(srcPath, false); + Q_CHECK_FILE_NAME(tgtPath, false); #if defined(RENAME_NOREPLACE) && QT_CONFIG(renameat2) if (renameat2(AT_FDCWD, srcPath, AT_FDCWD, tgtPath, RENAME_NOREPLACE) == 0) @@ -1302,8 +1289,9 @@ bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSy //static bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) { - if (Q_UNLIKELY(source.isEmpty() || target.isEmpty())) - return emptyFileEntryWarning(), false; + Q_CHECK_FILE_NAME(source, false); + Q_CHECK_FILE_NAME(target, false); + if (::rename(source.nativeFilePath().constData(), target.nativeFilePath().constData()) == 0) return true; error = QSystemError(errno, QSystemError::StandardLibraryError); @@ -1313,8 +1301,7 @@ bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source, cons //static bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError &error) { - if (Q_UNLIKELY(entry.isEmpty())) - return emptyFileEntryWarning(), false; + Q_CHECK_FILE_NAME(entry, false); if (unlink(entry.nativeFilePath().constData()) == 0) return true; error = QSystemError(errno, QSystemError::StandardLibraryError); @@ -1349,8 +1336,7 @@ static mode_t toMode_t(QFile::Permissions permissions) //static bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry, QFile::Permissions permissions, QSystemError &error, QFileSystemMetaData *data) { - if (Q_UNLIKELY(entry.isEmpty())) - return emptyFileEntryWarning(), false; + Q_CHECK_FILE_NAME(entry, false); mode_t mode = toMode_t(permissions); bool success = ::chmod(entry.nativeFilePath().constData(), mode) == 0; diff --git a/src/corelib/io/qfilesystemengine_win.cpp b/src/corelib/io/qfilesystemengine_win.cpp index 279918b812..ae29190848 100644 --- a/src/corelib/io/qfilesystemengine_win.cpp +++ b/src/corelib/io/qfilesystemengine_win.cpp @@ -461,7 +461,9 @@ void QFileSystemEngine::clearWinStatData(QFileSystemMetaData &data) QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, QFileSystemMetaData &data) { - if (data.missingFlags(QFileSystemMetaData::LinkType)) + Q_CHECK_FILE_NAME(link, link); + + if (data.missingFlags(QFileSystemMetaData::LinkType)) QFileSystemEngine::fillMetaData(link, data, QFileSystemMetaData::LinkType); QString target; @@ -480,6 +482,8 @@ QFileSystemEntry QFileSystemEngine::getLinkTarget(const QFileSystemEntry &link, //static QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, QFileSystemMetaData &data) { + Q_CHECK_FILE_NAME(entry, entry); + if (data.missingFlags(QFileSystemMetaData::ExistsAttribute)) QFileSystemEngine::fillMetaData(entry, data, QFileSystemMetaData::ExistsAttribute); @@ -492,6 +496,8 @@ QFileSystemEntry QFileSystemEngine::canonicalName(const QFileSystemEntry &entry, //static QString QFileSystemEngine::nativeAbsoluteFilePath(const QString &path) { + Q_CHECK_FILE_NAME(path, QString()); + // can be //server or //server/share QString absPath; QVarLengthArray buf(qMax(MAX_PATH, path.size() + 1)); @@ -527,6 +533,8 @@ QString QFileSystemEngine::nativeAbsoluteFilePath(const QString &path) //static QFileSystemEntry QFileSystemEngine::absoluteName(const QFileSystemEntry &entry) { + Q_CHECK_FILE_NAME(entry, entry); + QString ret; if (!entry.isRelative()) { @@ -609,6 +617,8 @@ QByteArray fileIdWin8(HANDLE handle) //static QByteArray QFileSystemEngine::id(const QFileSystemEntry &entry) { + Q_CHECK_FILE_NAME(entry, QByteArray()); + QByteArray result; #ifndef Q_OS_WINRT @@ -999,6 +1009,7 @@ static bool isDirPath(const QString &dirPath, bool *existed); bool QFileSystemEngine::fillMetaData(const QFileSystemEntry &entry, QFileSystemMetaData &data, QFileSystemMetaData::MetaDataFlags what) { + Q_CHECK_FILE_NAME(entry, false); what |= QFileSystemMetaData::WinLnkType | QFileSystemMetaData::WinStatFlags; data.entryFlags &= ~what; @@ -1116,6 +1127,8 @@ static bool isDirPath(const QString &dirPath, bool *existed) bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool createParents) { QString dirName = entry.filePath(); + Q_CHECK_FILE_NAME(dirName, false); + if (createParents) { dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName)); // We spefically search for / so \ would break it.. @@ -1177,6 +1190,8 @@ bool QFileSystemEngine::createDirectory(const QFileSystemEntry &entry, bool crea bool QFileSystemEngine::removeDirectory(const QFileSystemEntry &entry, bool removeEmptyParents) { QString dirName = entry.filePath(); + Q_CHECK_FILE_NAME(dirName, false); + if (removeEmptyParents) { dirName = QDir::toNativeSeparators(QDir::cleanPath(dirName)); for (int oldslash = 0, slash=dirName.length(); slash > 0; oldslash = slash) { @@ -1381,6 +1396,9 @@ bool QFileSystemEngine::copyFile(const QFileSystemEntry &source, const QFileSyst //static bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) { + Q_CHECK_FILE_NAME(source, false); + Q_CHECK_FILE_NAME(target, false); + #ifndef Q_OS_WINRT bool ret = ::MoveFile((wchar_t*)source.nativeFilePath().utf16(), (wchar_t*)target.nativeFilePath().utf16()) != 0; @@ -1396,6 +1414,9 @@ bool QFileSystemEngine::renameFile(const QFileSystemEntry &source, const QFileSy //static bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source, const QFileSystemEntry &target, QSystemError &error) { + Q_CHECK_FILE_NAME(source, false); + Q_CHECK_FILE_NAME(target, false); + bool ret = ::MoveFileEx(reinterpret_cast(source.nativeFilePath().utf16()), reinterpret_cast(target.nativeFilePath().utf16()), MOVEFILE_REPLACE_EXISTING) != 0; @@ -1407,6 +1428,8 @@ bool QFileSystemEngine::renameOverwriteFile(const QFileSystemEntry &source, cons //static bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError &error) { + Q_CHECK_FILE_NAME(entry, false); + bool ret = ::DeleteFile((wchar_t*)entry.nativeFilePath().utf16()) != 0; if(!ret) error = QSystemError(::GetLastError(), QSystemError::NativeError); @@ -1417,6 +1440,8 @@ bool QFileSystemEngine::removeFile(const QFileSystemEntry &entry, QSystemError & bool QFileSystemEngine::setPermissions(const QFileSystemEntry &entry, QFile::Permissions permissions, QSystemError &error, QFileSystemMetaData *data) { + Q_CHECK_FILE_NAME(entry, false); + Q_UNUSED(data); int mode = 0; diff --git a/tests/auto/corelib/io/qfile/tst_qfile.cpp b/tests/auto/corelib/io/qfile/tst_qfile.cpp index 4f010f37c2..b8ae95dd93 100644 --- a/tests/auto/corelib/io/qfile/tst_qfile.cpp +++ b/tests/auto/corelib/io/qfile/tst_qfile.cpp @@ -550,6 +550,10 @@ void tst_QFile::exists() QFile unc(uncPath); QVERIFY2(unc.exists(), msgFileDoesNotExist(uncPath).constData()); #endif + + QTest::ignoreMessage(QtWarningMsg, "Broken filename passed to function"); + QVERIFY(!QFile::exists(QDir::currentPath() + QLatin1Char('/') + + QChar(QChar::Null) + QLatin1String("x/y"))); } void tst_QFile::open_data() From fcbf15c97bac91a889eccaa0cfad10093fd052f0 Mon Sep 17 00:00:00 2001 From: Alexander Volkov Date: Wed, 23 Oct 2019 16:53:00 +0300 Subject: [PATCH 43/47] QKeySequence: Add missing names for multimedia keys Task-number: QTBUG-40030 Change-Id: Ib34bcbf42d6dd1206209c2d76444fd8c777278fe Reviewed-by: Shawn Rutledge --- src/gui/kernel/qkeysequence.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gui/kernel/qkeysequence.cpp b/src/gui/kernel/qkeysequence.cpp index 2a86b340af..e1244e1006 100644 --- a/src/gui/kernel/qkeysequence.cpp +++ b/src/gui/kernel/qkeysequence.cpp @@ -492,6 +492,8 @@ static const struct { { Qt::Key_LaunchD, QT_TRANSLATE_NOOP("QShortcut", "Launch (D)") }, { Qt::Key_LaunchE, QT_TRANSLATE_NOOP("QShortcut", "Launch (E)") }, { Qt::Key_LaunchF, QT_TRANSLATE_NOOP("QShortcut", "Launch (F)") }, + { Qt::Key_LaunchG, QT_TRANSLATE_NOOP("QShortcut", "Launch (G)") }, + { Qt::Key_LaunchH, QT_TRANSLATE_NOOP("QShortcut", "Launch (H)") }, { Qt::Key_MonBrightnessUp, QT_TRANSLATE_NOOP("QShortcut", "Monitor Brightness Up") }, { Qt::Key_MonBrightnessDown, QT_TRANSLATE_NOOP("QShortcut", "Monitor Brightness Down") }, { Qt::Key_KeyboardLightOnOff, QT_TRANSLATE_NOOP("QShortcut", "Keyboard Light On/Off") }, @@ -518,9 +520,11 @@ static const struct { { Qt::Key_Book, QT_TRANSLATE_NOOP("QShortcut", "Book") }, { Qt::Key_CD, QT_TRANSLATE_NOOP("QShortcut", "CD") }, { Qt::Key_Calculator, QT_TRANSLATE_NOOP("QShortcut", "Calculator") }, + { Qt::Key_Calendar, QT_TRANSLATE_NOOP("QShortcut", "Calendar") }, { Qt::Key_Clear, QT_TRANSLATE_NOOP("QShortcut", "Clear") }, { Qt::Key_ClearGrab, QT_TRANSLATE_NOOP("QShortcut", "Clear Grab") }, { Qt::Key_Close, QT_TRANSLATE_NOOP("QShortcut", "Close") }, + { Qt::Key_ContrastAdjust, QT_TRANSLATE_NOOP("QShortcut", "Adjust contrast") }, { Qt::Key_Copy, QT_TRANSLATE_NOOP("QShortcut", "Copy") }, { Qt::Key_Cut, QT_TRANSLATE_NOOP("QShortcut", "Cut") }, { Qt::Key_Display, QT_TRANSLATE_NOOP("QShortcut", "Display") }, @@ -534,6 +538,7 @@ static const struct { { Qt::Key_LogOff, QT_TRANSLATE_NOOP("QShortcut", "Logoff") }, { Qt::Key_Market, QT_TRANSLATE_NOOP("QShortcut", "Market") }, { Qt::Key_Meeting, QT_TRANSLATE_NOOP("QShortcut", "Meeting") }, + { Qt::Key_Memo, QT_TRANSLATE_NOOP("QShortcut", "Memo") }, { Qt::Key_MenuKB, QT_TRANSLATE_NOOP("QShortcut", "Keyboard Menu") }, { Qt::Key_MenuPB, QT_TRANSLATE_NOOP("QShortcut", "Menu PB") }, { Qt::Key_MySites, QT_TRANSLATE_NOOP("QShortcut", "My Sites") }, @@ -554,6 +559,7 @@ static const struct { { Qt::Key_Support, QT_TRANSLATE_NOOP("QShortcut", "Support") }, { Qt::Key_TaskPane, QT_TRANSLATE_NOOP("QShortcut", "Task Panel") }, { Qt::Key_Terminal, QT_TRANSLATE_NOOP("QShortcut", "Terminal") }, + { Qt::Key_ToDoList, QT_TRANSLATE_NOOP("QShortcut", "To-do list") }, { Qt::Key_Tools, QT_TRANSLATE_NOOP("QShortcut", "Tools") }, { Qt::Key_Travel, QT_TRANSLATE_NOOP("QShortcut", "Travel") }, { Qt::Key_Video, QT_TRANSLATE_NOOP("QShortcut", "Video") }, From 5edf34848a51c7678031aeb9576b8f3b7b5fceab Mon Sep 17 00:00:00 2001 From: Christian Ehrlicher Date: Sat, 19 Oct 2019 19:18:55 +0200 Subject: [PATCH 44/47] QTableView: properly deselect row when column 0 is hidden/not visible When the first column is hidden or not visible in the current viewport, it is not possible to deselect the current row. Fix it by passing the correct column to QItemSelectionModel::selectedRows() when testing if the current index is selected. Fixes: QTBUG-79092 Change-Id: I9d8082d2b29ad2f799156aee910c6ff6e3217771 Reviewed-by: David Faure Reviewed-by: Friedemann Kleint --- src/widgets/itemviews/qtableview.cpp | 2 +- .../itemviews/qtableview/tst_qtableview.cpp | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/widgets/itemviews/qtableview.cpp b/src/widgets/itemviews/qtableview.cpp index c50156bbce..11c5be10fd 100644 --- a/src/widgets/itemviews/qtableview.cpp +++ b/src/widgets/itemviews/qtableview.cpp @@ -3331,7 +3331,7 @@ void QTableViewPrivate::selectRow(int row, bool anchor) if (q->selectionMode() != QTableView::SingleSelection && command.testFlag(QItemSelectionModel::Toggle)) { if (anchor) - ctrlDragSelectionFlag = verticalHeader->selectionModel()->selectedRows().contains(index) + ctrlDragSelectionFlag = verticalHeader->selectionModel()->selectedRows(column).contains(index) ? QItemSelectionModel::Deselect : QItemSelectionModel::Select; command &= ~QItemSelectionModel::Toggle; command |= ctrlDragSelectionFlag; diff --git a/tests/auto/widgets/itemviews/qtableview/tst_qtableview.cpp b/tests/auto/widgets/itemviews/qtableview/tst_qtableview.cpp index 09990ab70a..3e0d2539b4 100644 --- a/tests/auto/widgets/itemviews/qtableview/tst_qtableview.cpp +++ b/tests/auto/widgets/itemviews/qtableview/tst_qtableview.cpp @@ -418,6 +418,7 @@ private slots: void taskQTBUG_10169_sizeHintForRow(); void taskQTBUG_30653_doItemsLayout(); void taskQTBUG_50171_selectRowAfterSwapColumns(); + void deselectRow(); #if QT_CONFIG(wheelevent) void mouseWheel_data(); @@ -4492,6 +4493,31 @@ void tst_QTableView::taskQTBUG_50171_selectRowAfterSwapColumns() } } +class DeselectTableWidget : public QTableWidget +{ +public: + using QTableWidget::QTableWidget; + QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex &, + const QEvent * = nullptr) const override + { + return QItemSelectionModel::Toggle; + } +}; + +void tst_QTableView::deselectRow() +{ + DeselectTableWidget tw(20, 20); + tw.show(); + QVERIFY(QTest::qWaitForWindowExposed(&tw)); + tw.hideColumn(0); + QVERIFY(tw.isColumnHidden(0)); + tw.selectRow(1); + QVERIFY(tw.selectionModel()->isRowSelected(1, QModelIndex())); + tw.selectRow(1); + // QTBUG-79092 - deselection was not possible when column 0 was hidden + QVERIFY(!tw.selectionModel()->isRowSelected(1, QModelIndex())); +} + // This has nothing to do with QTableView, but it's convenient to reuse the QtTestTableModel #if QT_CONFIG(textmarkdownwriter) From 31cbda4833aaea6cead58a9a3281059c27393433 Mon Sep 17 00:00:00 2001 From: Christian Ehrlicher Date: Mon, 21 Oct 2019 20:58:40 +0200 Subject: [PATCH 45/47] QItemSelectionModel: make parent an optional parameter Make parent an optional parameter to be consistent with the rest of the API. Change-Id: I5942fee69ca33bd87d4d14b6919d2b8d5b60c0b4 Reviewed-by: Friedemann Kleint Reviewed-by: David Faure --- src/corelib/itemmodels/qitemselectionmodel.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/corelib/itemmodels/qitemselectionmodel.h b/src/corelib/itemmodels/qitemselectionmodel.h index 3c3f9fb1ac..5820695592 100644 --- a/src/corelib/itemmodels/qitemselectionmodel.h +++ b/src/corelib/itemmodels/qitemselectionmodel.h @@ -171,11 +171,11 @@ public: QModelIndex currentIndex() const; Q_INVOKABLE bool isSelected(const QModelIndex &index) const; - Q_INVOKABLE bool isRowSelected(int row, const QModelIndex &parent) const; - Q_INVOKABLE bool isColumnSelected(int column, const QModelIndex &parent) const; + Q_INVOKABLE bool isRowSelected(int row, const QModelIndex &parent = QModelIndex()) const; + Q_INVOKABLE bool isColumnSelected(int column, const QModelIndex &parent = QModelIndex()) const; - Q_INVOKABLE bool rowIntersectsSelection(int row, const QModelIndex &parent) const; - Q_INVOKABLE bool columnIntersectsSelection(int column, const QModelIndex &parent) const; + Q_INVOKABLE bool rowIntersectsSelection(int row, const QModelIndex &parent = QModelIndex()) const; + Q_INVOKABLE bool columnIntersectsSelection(int column, const QModelIndex &parent = QModelIndex()) const; bool hasSelection() const; From 9ae0fa4d4977ef1eb28eff2f95b429e9455ba748 Mon Sep 17 00:00:00 2001 From: Christian Ehrlicher Date: Sat, 19 Oct 2019 18:01:08 +0200 Subject: [PATCH 46/47] cleanup QSortFilterProxyModel tests Cleanup QSortFilterProxyModel tests: - adjust includes - use nullptr - use override - avoid unneeded casts - use new signal/slot syntax Change-Id: I05f7c18cd2642bc8251bf1560803e7635684d24d Reviewed-by: Friedemann Kleint --- .../tst_qsortfilterproxymodel.cpp | 532 ++++++++---------- .../tst_qsortfilterproxymodel.h | 23 +- .../tst_qsortfilterproxymodel_recursive.cpp | 5 +- 3 files changed, 249 insertions(+), 311 deletions(-) diff --git a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.cpp b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.cpp index 624187349b..7f9a996136 100644 --- a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.cpp +++ b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.cpp @@ -26,25 +26,29 @@ ** ****************************************************************************/ -#include #include "tst_qsortfilterproxymodel.h" #include "dynamictreemodel.h" -#include -#include -#include -#include -#include - -#include +#include +#include +#include +#include +#include +#include +#include +#include Q_LOGGING_CATEGORY(lcItemModels, "qt.corelib.tests.itemmodels") +using IntPair = QPair; +using IntList = QVector; +using IntPairList = QVector; + // Testing get/set functions void tst_QSortFilterProxyModel::getSetCheck() { QSortFilterProxyModel obj1; - QCOMPARE(obj1.sourceModel(), (QAbstractItemModel *)0); + QCOMPARE(obj1.sourceModel(), nullptr); // int QSortFilterProxyModel::filterKeyColumn() // void QSortFilterProxyModel::setFilterKeyColumn(int) obj1.setFilterKeyColumn(0); @@ -55,15 +59,12 @@ void tst_QSortFilterProxyModel::getSetCheck() QCOMPARE(INT_MAX, obj1.filterKeyColumn()); } -tst_QSortFilterProxyModel::tst_QSortFilterProxyModel() - : m_model(0), m_proxy(0) -{ - qRegisterMetaType(); -} - +Q_DECLARE_METATYPE(Qt::MatchFlag) void tst_QSortFilterProxyModel::initTestCase() { + qRegisterMetaType(); qRegisterMetaType >(); + qRegisterMetaType(); m_model = new QStandardItemModel(0, 1); m_proxy = new QSortFilterProxyModel(); m_proxy->setSourceModel(m_model); @@ -97,13 +98,13 @@ void tst_QSortFilterProxyModel::cleanup() void tst_QSortFilterProxyModel::sort_data() { - QTest::addColumn("sortOrder"); - QTest::addColumn("sortCaseSensitivity"); + QTest::addColumn("sortOrder"); + QTest::addColumn("sortCaseSensitivity"); QTest::addColumn("initial"); QTest::addColumn("expected"); - QTest::newRow("flat descending") << static_cast(Qt::DescendingOrder) - << int(Qt::CaseSensitive) + QTest::newRow("flat descending") << Qt::DescendingOrder + << Qt::CaseSensitive << (QStringList() << "delta" << "yankee" @@ -158,8 +159,8 @@ void tst_QSortFilterProxyModel::sort_data() << "charlie" << "bravo" << "alpha"); - QTest::newRow("flat ascending") << static_cast(Qt::AscendingOrder) - << int(Qt::CaseSensitive) + QTest::newRow("flat ascending") << Qt::AscendingOrder + << Qt::CaseSensitive << (QStringList() << "delta" << "yankee" @@ -214,14 +215,14 @@ void tst_QSortFilterProxyModel::sort_data() << "xray" << "yankee" << "zulu"); - QTest::newRow("case insensitive") << static_cast(Qt::AscendingOrder) - << int(Qt::CaseInsensitive) + QTest::newRow("case insensitive") << Qt::AscendingOrder + << Qt::CaseInsensitive << (QStringList() << "alpha" << "BETA" << "Gamma" << "delta") << (QStringList() << "alpha" << "BETA" << "delta" << "Gamma"); - QTest::newRow("case sensitive") << static_cast(Qt::AscendingOrder) - << int(Qt::CaseSensitive) + QTest::newRow("case sensitive") << Qt::AscendingOrder + << Qt::CaseSensitive << (QStringList() << "alpha" << "BETA" << "Gamma" << "delta") << (QStringList() @@ -230,13 +231,13 @@ void tst_QSortFilterProxyModel::sort_data() QStringList list; for (int i = 10000; i < 20000; ++i) list.append(QStringLiteral("Number: ") + QString::number(i)); - QTest::newRow("large set ascending") << static_cast(Qt::AscendingOrder) << int(Qt::CaseSensitive) << list << list; + QTest::newRow("large set ascending") << Qt::AscendingOrder << Qt::CaseSensitive << list << list; } void tst_QSortFilterProxyModel::sort() { - QFETCH(int, sortOrder); - QFETCH(int, sortCaseSensitivity); + QFETCH(Qt::SortOrder, sortOrder); + QFETCH(Qt::CaseSensitivity, sortCaseSensitivity); QFETCH(QStringList, initial); QFETCH(QStringList, expected); @@ -259,8 +260,8 @@ void tst_QSortFilterProxyModel::sort() } // sort - m_proxy->sort(0, static_cast(sortOrder)); - m_proxy->setSortCaseSensitivity(static_cast(sortCaseSensitivity)); + m_proxy->sort(0, sortOrder); + m_proxy->setSortCaseSensitivity(sortCaseSensitivity); // make sure the model is unchanged for (int row = 0; row < m_model->rowCount(QModelIndex()); ++row) { @@ -285,24 +286,24 @@ void tst_QSortFilterProxyModel::sort() void tst_QSortFilterProxyModel::sortHierarchy_data() { - QTest::addColumn("sortOrder"); + QTest::addColumn("sortOrder"); QTest::addColumn("initial"); QTest::addColumn("expected"); QTest::newRow("flat ascending") - << static_cast(Qt::AscendingOrder) + << Qt::AscendingOrder << (QStringList() << "c" << "f" << "d" << "e" << "a" << "b") << (QStringList() << "a" << "b" << "c" << "d" << "e" << "f"); QTest::newRow("simple hierarchy") - << static_cast(Qt::AscendingOrder) + << Qt::AscendingOrder << (QStringList() << "a" << "<" << "b" << "<" << "c" << ">" << ">") << (QStringList() << "a" << "<" << "b" << "<" << "c" << ">" << ">"); QTest::newRow("hierarchical ascending") - << static_cast(Qt::AscendingOrder) + << Qt::AscendingOrder << (QStringList() << "c" << "<" @@ -365,14 +366,14 @@ void tst_QSortFilterProxyModel::sortHierarchy_data() void tst_QSortFilterProxyModel::sortHierarchy() { - QFETCH(int, sortOrder); + QFETCH(Qt::SortOrder, sortOrder); QFETCH(QStringList, initial); QFETCH(QStringList, expected); buildHierarchy(initial, m_model); checkHierarchy(initial, m_model); checkHierarchy(initial, m_proxy); - m_proxy->sort(0, static_cast(sortOrder)); + m_proxy->sort(0, sortOrder); checkHierarchy(initial, m_model); checkHierarchy(expected, m_proxy); } @@ -872,13 +873,13 @@ class MyFilteredColumnProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - MyFilteredColumnProxyModel(FilterType filterType, QObject *parent = 0) : + MyFilteredColumnProxyModel(FilterType filterType, QObject *parent = nullptr) : QSortFilterProxyModel(parent), m_filterType(filterType) { } protected: - bool filterAcceptsColumn(int sourceColumn, const QModelIndex &) const + bool filterAcceptsColumn(int sourceColumn, const QModelIndex &) const override { QString key = sourceModel()->headerData(sourceColumn, Qt::Horizontal).toString(); bool result = false; @@ -1369,10 +1370,11 @@ void tst_QSortFilterProxyModel::setupFilter(QSortFilterProxyModel *model, const class TestModel: public QAbstractTableModel { + Q_OBJECT public: - int rowCount(const QModelIndex &) const { return 10000; } - int columnCount(const QModelIndex &) const { return 1; } - QVariant data(const QModelIndex &index, int role) const + int rowCount(const QModelIndex &) const override { return 10000; } + int columnCount(const QModelIndex &) const override { return 1; } + QVariant data(const QModelIndex &index, int role) const override { if (role != Qt::DisplayRole) return QVariant(); @@ -1402,7 +1404,7 @@ void tst_QSortFilterProxyModel::insertAfterSelect() view.setModel(&filter); view.show(); QModelIndex firstIndex = filter.mapFromSource(model.index(0, 0, QModelIndex())); - QCOMPARE(firstIndex.model(), (const QAbstractItemModel *)view.model()); + QCOMPARE(firstIndex.model(), view.model()); QVERIFY(firstIndex.isValid()); int itemOffset = view.visualRect(firstIndex).width() / 2; QPoint p(itemOffset, 1); @@ -1423,7 +1425,7 @@ void tst_QSortFilterProxyModel::removeAfterSelect() view.setModel(&filter); view.show(); QModelIndex firstIndex = filter.mapFromSource(model.index(0, 0, QModelIndex())); - QCOMPARE(firstIndex.model(), (const QAbstractItemModel *)view.model()); + QCOMPARE(firstIndex.model(), view.model()); QVERIFY(firstIndex.isValid()); int itemOffset = view.visualRect(firstIndex).width() / 2; QPoint p(itemOffset, 1); @@ -1488,8 +1490,8 @@ void tst_QSortFilterProxyModel::changeSourceLayout() QSortFilterProxyModel proxy; proxy.setSourceModel(&model); - QList persistentSourceIndexes; - QList persistentProxyIndexes; + QVector persistentSourceIndexes; + QVector persistentProxyIndexes; for (int row = 0; row < model.rowCount(); ++row) { persistentSourceIndexes.append(model.index(row, 0)); persistentProxyIndexes.append(proxy.index(row, 0)); @@ -1634,7 +1636,7 @@ void tst_QSortFilterProxyModel::removeSourceRows() QCOMPARE(aboutToRemoveSpy.count(), expectedRemovedProxyIntervals.count()); for (int i = 0; i < aboutToRemoveSpy.count(); ++i) { - QList args = aboutToRemoveSpy.at(i); + const auto &args = aboutToRemoveSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), expectedRemovedProxyIntervals.at(i).first); @@ -1642,7 +1644,7 @@ void tst_QSortFilterProxyModel::removeSourceRows() } QCOMPARE(removeSpy.count(), expectedRemovedProxyIntervals.count()); for (int i = 0; i < removeSpy.count(); ++i) { - QList args = removeSpy.at(i); + const auto &args = removeSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), expectedRemovedProxyIntervals.at(i).first); @@ -1664,14 +1666,14 @@ void tst_QSortFilterProxyModel::insertSourceRows_data() QTest::addColumn("sourceItems"); QTest::addColumn("start"); QTest::addColumn("newItems"); - QTest::addColumn("sortOrder"); + QTest::addColumn("sortOrder"); QTest::addColumn("proxyItems"); QTest::newRow("insert (1)") << (QStringList() << "c" << "b") // sourceItems << 1 // start << (QStringList() << "a") // newItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << (QStringList() << "a" << "b" << "c") // proxyItems ; @@ -1679,7 +1681,7 @@ void tst_QSortFilterProxyModel::insertSourceRows_data() << (QStringList() << "d" << "b" << "c") // sourceItems << 3 // start << (QStringList() << "a") // newItems - << static_cast(Qt::DescendingOrder) // sortOrder + << Qt::DescendingOrder // sortOrder << (QStringList() << "d" << "c" << "b" << "a") // proxyItems ; } @@ -1691,7 +1693,7 @@ void tst_QSortFilterProxyModel::insertSourceRows() QFETCH(QStringList, sourceItems); QFETCH(int, start); QFETCH(QStringList, newItems); - QFETCH(int, sortOrder); + QFETCH(Qt::SortOrder, sortOrder); QFETCH(QStringList, proxyItems); QStandardItemModel model; @@ -1707,7 +1709,7 @@ void tst_QSortFilterProxyModel::insertSourceRows() model.setData(index, sourceItems.at(i), Qt::DisplayRole); } - proxy.sort(0, static_cast(sortOrder)); + proxy.sort(0, sortOrder); (void)proxy.rowCount(QModelIndex()); // force mapping model.insertRows(start, newItems.size(), QModelIndex()); @@ -1727,7 +1729,7 @@ void tst_QSortFilterProxyModel::insertSourceRows() void tst_QSortFilterProxyModel::changeFilter_data() { QTest::addColumn("sourceItems"); - QTest::addColumn("sortOrder"); + QTest::addColumn("sortOrder"); QTest::addColumn("initialFilter"); QTest::addColumn("initialRemoveIntervals"); QTest::addColumn("initialProxyItems"); @@ -1738,7 +1740,7 @@ void tst_QSortFilterProxyModel::changeFilter_data() QTest::newRow("filter (1)") << (QStringList() << "a" << "b" << "c" << "d" << "e" << "f") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "a|b|c" // initialFilter << (IntPairList() << IntPair(3, 5)) // initialRemoveIntervals << (QStringList() << "a" << "b" << "c") // initialProxyItems @@ -1750,7 +1752,7 @@ void tst_QSortFilterProxyModel::changeFilter_data() QTest::newRow("filter (2)") << (QStringList() << "a" << "b" << "c" << "d" << "e" << "f") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "a|c|e" // initialFilter << (IntPairList() << IntPair(5, 5) << IntPair(3, 3) << IntPair(1, 1)) // initialRemoveIntervals << (QStringList() << "a" << "c" << "e") // initialProxyItems @@ -1762,7 +1764,7 @@ void tst_QSortFilterProxyModel::changeFilter_data() QTest::newRow("filter (3)") << (QStringList() << "a" << "b" << "c") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "a" // initialFilter << (IntPairList() << IntPair(1, 2)) // initialRemoveIntervals << (QStringList() << "a") // initialProxyItems @@ -1777,7 +1779,7 @@ void tst_QSortFilterProxyModel::changeFilter_data() void tst_QSortFilterProxyModel::changeFilter() { QFETCH(QStringList, sourceItems); - QFETCH(int, sortOrder); + QFETCH(Qt::SortOrder, sortOrder); QFETCH(QString, initialFilter); QFETCH(IntPairList, initialRemoveIntervals); QFETCH(QStringList, initialProxyItems); @@ -1798,7 +1800,7 @@ void tst_QSortFilterProxyModel::changeFilter() model.setData(index, sourceItems.at(i), Qt::DisplayRole); } - proxy.sort(0, static_cast(sortOrder)); + proxy.sort(0, sortOrder); (void)proxy.rowCount(QModelIndex()); // force mapping QSignalSpy initialRemoveSpy(&proxy, &QSortFilterProxyModel::rowsRemoved); @@ -1812,7 +1814,7 @@ void tst_QSortFilterProxyModel::changeFilter() QCOMPARE(initialRemoveSpy.count(), initialRemoveIntervals.count()); QCOMPARE(initialInsertSpy.count(), 0); for (int i = 0; i < initialRemoveSpy.count(); ++i) { - QList args = initialRemoveSpy.at(i); + const auto &args = initialRemoveSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), initialRemoveIntervals.at(i).first); @@ -1835,7 +1837,7 @@ void tst_QSortFilterProxyModel::changeFilter() QCOMPARE(finalRemoveSpy.count(), finalRemoveIntervals.count()); for (int i = 0; i < finalRemoveSpy.count(); ++i) { - QList args = finalRemoveSpy.at(i); + const auto &args = finalRemoveSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), finalRemoveIntervals.at(i).first); @@ -1844,7 +1846,7 @@ void tst_QSortFilterProxyModel::changeFilter() QCOMPARE(finalInsertSpy.count(), insertIntervals.count()); for (int i = 0; i < finalInsertSpy.count(); ++i) { - QList args = finalInsertSpy.at(i); + const auto &args = finalInsertSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), insertIntervals.at(i).first); @@ -1861,7 +1863,7 @@ void tst_QSortFilterProxyModel::changeFilter() void tst_QSortFilterProxyModel::changeSourceData_data() { QTest::addColumn("sourceItems"); - QTest::addColumn("sortOrder"); + QTest::addColumn("sortOrder"); QTest::addColumn("filter"); QTest::addColumn("expectedInitialProxyItems"); QTest::addColumn("dynamic"); @@ -1875,7 +1877,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() QTest::newRow("move_to_end_ascending") << (QStringList() << "c" << "b" << "a") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "" // filter << (QStringList() << "a" << "b" << "c") // expectedInitialProxyItems << true // dynamic @@ -1890,7 +1892,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() QTest::newRow("move_to_end_descending") << (QStringList() << "b" << "c" << "z") // sourceItems - << static_cast(Qt::DescendingOrder) // sortOrder + << Qt::DescendingOrder // sortOrder << "" // filter << (QStringList() << "z" << "c" << "b") // expectedInitialProxyItems << true // dynamic @@ -1905,7 +1907,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() QTest::newRow("no_op_change") << (QStringList() << "a" << "b") // sourceItems - << static_cast(Qt::DescendingOrder) // sortOrder + << Qt::DescendingOrder // sortOrder << "" // filter << (QStringList() << "b" << "a") // expectedInitialProxyItems << true // dynamic @@ -1920,7 +1922,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() QTest::newRow("no_effect_on_filtering") << (QStringList() << "a" << "b") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "" // filter << (QStringList() << "a" << "b") // expectedInitialProxyItems << true // dynamic @@ -1935,7 +1937,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() QTest::newRow("filtered_out_value_stays_out") << (QStringList() << "a" << "b" << "c" << "d") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "a|c" // filter << (QStringList() << "a" << "c") // expectedInitialProxyItems << true // dynamic @@ -1950,7 +1952,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() QTest::newRow("filtered_out_now_matches") << (QStringList() << "a" << "b" << "c" << "d") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "a|c|x" // filter << (QStringList() << "a" << "c") // expectedInitialProxyItems << true // dynamic @@ -1965,7 +1967,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() QTest::newRow("value_is_now_filtered_out") << (QStringList() << "a" << "b" << "c" << "d") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "a|c" // filter << (QStringList() << "a" << "c") // expectedInitialProxyItems << true // dynamic @@ -1980,7 +1982,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() QTest::newRow("non_dynamic_filter_does_not_update_sort") << (QStringList() << "c" << "b" << "a") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "" // filter << (QStringList() << "a" << "b" << "c") // expectedInitialProxyItems << false // dynamic @@ -1997,7 +1999,7 @@ void tst_QSortFilterProxyModel::changeSourceData_data() void tst_QSortFilterProxyModel::changeSourceData() { QFETCH(QStringList, sourceItems); - QFETCH(int, sortOrder); + QFETCH(Qt::SortOrder, sortOrder); QFETCH(QString, filter); QFETCH(QStringList, expectedInitialProxyItems); QFETCH(bool, dynamic); @@ -2022,7 +2024,7 @@ void tst_QSortFilterProxyModel::changeSourceData() model.setData(index, sourceItems.at(i), Qt::DisplayRole); } - proxy.sort(0, static_cast(sortOrder)); + proxy.sort(0, sortOrder); (void)proxy.rowCount(QModelIndex()); // force mapping setupFilter(&proxy, filter); @@ -2050,7 +2052,7 @@ void tst_QSortFilterProxyModel::changeSourceData() QCOMPARE(removeSpy.count(), removeIntervals.count()); for (int i = 0; i < removeSpy.count(); ++i) { - QList args = removeSpy.at(i); + const auto &args = removeSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), removeIntervals.at(i).first); @@ -2059,7 +2061,7 @@ void tst_QSortFilterProxyModel::changeSourceData() QCOMPARE(insertSpy.count(), insertIntervals.count()); for (int i = 0; i < insertSpy.count(); ++i) { - QList args = insertSpy.at(i); + const auto &args = insertSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), insertIntervals.at(i).first); @@ -2107,8 +2109,7 @@ void tst_QSortFilterProxyModel::changeSourceDataKeepsStableSorting_qtbug1548() // doesn't alter the sorting. In this case, we sort on the DisplayRole, // and play with other roles. - static const QStringList rows - = QStringList() << "a" << "b" << "b" << "b" << "c" << "c" << "x"; + const QStringList rows({"a", "b", "b", "b", "c", "c", "x"}); // Build a table of pairs (string, #row) in each row QStandardItemModel model(0, 2); @@ -2119,11 +2120,7 @@ void tst_QSortFilterProxyModel::changeSourceDataKeepsStableSorting_qtbug1548() column0->setCheckState(Qt::Unchecked); QStandardItem *column1 = new QStandardItem(QString::number(rowNumber)); - - const QList row - = QList() << column0 << column1; - - model.appendRow(row); + model.appendRow({column0, column1}); } checkSortedTableModel(&model, rows); @@ -2201,15 +2198,12 @@ void tst_QSortFilterProxyModel::sortFilterRole() proxy.setSourceModel(&model); model.insertColumns(0, 1); - QList > sourceItems; - sourceItems = QList >() - << QPair("b", 3) - << QPair("c", 2) - << QPair("a", 1); + const QVector> + sourceItems({QPair("b", 3), + QPair("c", 2), + QPair("a", 1)}); - QList orderedItems; - orderedItems = QList() - << 2 << 1; + const QVector orderedItems({2, 1}); model.insertRows(0, sourceItems.count()); for (int i = 0; i < sourceItems.count(); ++i) { @@ -2273,70 +2267,70 @@ void tst_QSortFilterProxyModel::selectionFilteredOut() void tst_QSortFilterProxyModel::match_data() { QTest::addColumn("sourceItems"); - QTest::addColumn("sortOrder"); + QTest::addColumn("sortOrder"); QTest::addColumn("filter"); QTest::addColumn("proxyStartRow"); QTest::addColumn("what"); - QTest::addColumn("matchFlags"); + QTest::addColumn("matchFlags"); QTest::addColumn("expectedProxyItems"); QTest::newRow("1") << (QStringList() << "a") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "" // filter << 0 // proxyStartRow << "a" // what - << static_cast(Qt::MatchExactly) // matchFlags + << Qt::MatchExactly // matchFlags << (IntList() << 0); // expectedProxyItems QTest::newRow("2") << (QStringList() << "a" << "b") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "" // filter << 0 // proxyStartRow << "b" // what - << static_cast(Qt::MatchExactly) // matchFlags + << Qt::MatchExactly // matchFlags << (IntList() << 1); // expectedProxyItems QTest::newRow("3") << (QStringList() << "a" << "b") // sourceItems - << static_cast(Qt::DescendingOrder) // sortOrder + << Qt::DescendingOrder // sortOrder << "" // filter << 0 // proxyStartRow << "a" // what - << static_cast(Qt::MatchExactly) // matchFlags + << Qt::MatchExactly // matchFlags << (IntList() << 1); // expectedProxyItems QTest::newRow("4") << (QStringList() << "b" << "d" << "a" << "c") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "" // filter << 1 // proxyStartRow << "a" // what - << static_cast(Qt::MatchExactly) // matchFlags + << Qt::MatchExactly // matchFlags << IntList(); // expectedProxyItems QTest::newRow("5") << (QStringList() << "b" << "d" << "a" << "c") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "a|b" // filter << 0 // proxyStartRow << "c" // what - << static_cast(Qt::MatchExactly) // matchFlags + << Qt::MatchExactly // matchFlags << IntList(); // expectedProxyItems QTest::newRow("6") << (QStringList() << "b" << "d" << "a" << "c") // sourceItems - << static_cast(Qt::DescendingOrder) // sortOrder + << Qt::DescendingOrder // sortOrder << "a|b" // filter << 0 // proxyStartRow << "b" // what - << static_cast(Qt::MatchExactly) // matchFlags + << Qt::MatchExactly // matchFlags << (IntList() << 0); // expectedProxyItems } void tst_QSortFilterProxyModel::match() { QFETCH(QStringList, sourceItems); - QFETCH(int, sortOrder); + QFETCH(Qt::SortOrder, sortOrder); QFETCH(QString, filter); QFETCH(int, proxyStartRow); QFETCH(QString, what); - QFETCH(int, matchFlags); + QFETCH(Qt::MatchFlag, matchFlags); QFETCH(IntList, expectedProxyItems); QStandardItemModel model; @@ -2351,13 +2345,13 @@ void tst_QSortFilterProxyModel::match() model.setData(index, sourceItems.at(i), Qt::DisplayRole); } - proxy.sort(0, static_cast(sortOrder)); + proxy.sort(0, sortOrder); setupFilter(&proxy, filter); QModelIndex startIndex = proxy.index(proxyStartRow, 0); QModelIndexList indexes = proxy.match(startIndex, Qt::DisplayRole, what, expectedProxyItems.count(), - Qt::MatchFlags(matchFlags)); + matchFlags); QCOMPARE(indexes.count(), expectedProxyItems.count()); for (int i = 0; i < indexes.count(); ++i) QCOMPARE(indexes.at(i).row(), expectedProxyItems.at(i)); @@ -2480,8 +2474,9 @@ void tst_QSortFilterProxyModel::invalidateMappedChildren() class EvenOddFilterModel : public QSortFilterProxyModel { + Q_OBJECT public: - virtual bool filterAcceptsRow(int srcRow, const QModelIndex& srcParent) const + bool filterAcceptsRow(int srcRow, const QModelIndex &srcParent) const override { if (srcParent.isValid()) return (srcParent.row() % 2) ^ !(srcRow % 2); @@ -2572,9 +2567,9 @@ void tst_QSortFilterProxyModel::sourceModelDeletion() { QStandardItemModel model; proxyModel.setSourceModel(&model); - QCOMPARE(proxyModel.sourceModel(), static_cast(&model)); + QCOMPARE(proxyModel.sourceModel(), &model); } - QCOMPARE(proxyModel.sourceModel(), static_cast(0)); + QCOMPARE(proxyModel.sourceModel(), nullptr); } void tst_QSortFilterProxyModel::sortColumnTracking1() @@ -2830,8 +2825,9 @@ void tst_QSortFilterProxyModel::dynamicSorting() class QtTestModel: public QAbstractItemModel { + Q_OBJECT public: - QtTestModel(int _rows, int _cols, QObject *parent = 0) + QtTestModel(int _rows, int _cols, QObject *parent = nullptr) : QAbstractItemModel(parent) , rows(_rows) , cols(_cols) @@ -2839,12 +2835,12 @@ public: { } - bool canFetchMore(const QModelIndex &idx) const + bool canFetchMore(const QModelIndex &idx) const override { return !fetched.contains(idx); } - void fetchMore(const QModelIndex &idx) + void fetchMore(const QModelIndex &idx) override { if (fetched.contains(idx)) return; @@ -2853,23 +2849,24 @@ public: endInsertRows(); } - bool hasChildren(const QModelIndex & = QModelIndex()) const + bool hasChildren(const QModelIndex &parent = QModelIndex()) const override { + Q_UNUSED(parent) return true; } - int rowCount(const QModelIndex& parent = QModelIndex()) const + int rowCount(const QModelIndex& parent = QModelIndex()) const override { return fetched.contains(parent) ? rows : 0; } - int columnCount(const QModelIndex& parent = QModelIndex()) const + int columnCount(const QModelIndex& parent = QModelIndex()) const override { - Q_UNUSED(parent); + Q_UNUSED(parent) return cols; } - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { if (row < 0 || column < 0 || column >= cols || row >= rows) { return QModelIndex(); @@ -2879,14 +2876,14 @@ public: return i; } - QModelIndex parent(const QModelIndex &index) const + QModelIndex parent(const QModelIndex &index) const override { if (!parentHash.contains(index)) return QModelIndex(); return parentHash[index]; } - QVariant data(const QModelIndex &idx, int role) const + QVariant data(const QModelIndex &idx, int role) const override { if (!idx.isValid()) return QVariant(); @@ -3099,10 +3096,8 @@ void tst_QSortFilterProxyModel::appearsAndSort() { class PModel : public QSortFilterProxyModel { - public: - PModel() : mVisible(false) {}; protected: - bool filterAcceptsRow(int, const QModelIndex &) const + bool filterAcceptsRow(int, const QModelIndex &) const override { return mVisible; } @@ -3114,13 +3109,11 @@ void tst_QSortFilterProxyModel::appearsAndSort() invalidate(); } private: - bool mVisible; + bool mVisible = false; } proxyModel; QStringListModel sourceModel; - QStringList list; - list << "b" << "a" << "c"; - sourceModel.setStringList(list); + sourceModel.setStringList({"b", "a", "c"}); proxyModel.setSourceModel(&sourceModel); proxyModel.setDynamicSortFilter(true); @@ -3195,43 +3188,40 @@ void tst_QSortFilterProxyModel::unnecessaryDynamicSorting() } } -class SelectionProxyModel : QAbstractProxyModel +class SelectionProxyModel : public QAbstractProxyModel { Q_OBJECT public: - SelectionProxyModel() - : QAbstractProxyModel(), selectionModel(0) - { - } - - QModelIndex mapFromSource(QModelIndex const&) const + QModelIndex mapFromSource(QModelIndex const&) const override { return QModelIndex(); } - QModelIndex mapToSource(QModelIndex const&) const + QModelIndex mapToSource(QModelIndex const&) const override { return QModelIndex(); } - QModelIndex index(int, int, const QModelIndex&) const + QModelIndex index(int, int, const QModelIndex&) const override { return QModelIndex(); } - QModelIndex parent(const QModelIndex&) const + QModelIndex parent(const QModelIndex&) const override { return QModelIndex(); } - int rowCount(const QModelIndex&) const + int rowCount(const QModelIndex&) const override { return 0; } - int columnCount(const QModelIndex&) const + int columnCount(const QModelIndex&) const override { return 0; } - void setSourceModel( QAbstractItemModel *sourceModel ) + void setSourceModel(QAbstractItemModel *sourceModel) override { beginResetModel(); - disconnect( sourceModel, SIGNAL(modelAboutToBeReset()), this, SLOT(sourceModelAboutToBeReset()) ); + disconnect(sourceModel, &QAbstractItemModel::modelAboutToBeReset, + this, &SelectionProxyModel::sourceModelAboutToBeReset); QAbstractProxyModel::setSourceModel( sourceModel ); - connect( sourceModel, SIGNAL(modelAboutToBeReset()), this, SLOT(sourceModelAboutToBeReset()) ); + connect(sourceModel, &QAbstractItemModel::modelAboutToBeReset, + this, &SelectionProxyModel::sourceModelAboutToBeReset); endResetModel(); } - void setSelectionModel( QItemSelectionModel *_selectionModel ) + void setSelectionModel(QItemSelectionModel *_selectionModel) { selectionModel = _selectionModel; } @@ -3249,7 +3239,7 @@ private slots: } private: - QItemSelectionModel *selectionModel; + QItemSelectionModel *selectionModel = nullptr; }; void tst_QSortFilterProxyModel::testMultipleProxiesWithSelection() @@ -3275,7 +3265,7 @@ void tst_QSortFilterProxyModel::testMultipleProxiesWithSelection() selectionModel.select( proxy2.index( 0, 0 ), QItemSelectionModel::Select ); // trick the proxy into emitting begin/end reset signals. - proxy.setSourceModel(0); + proxy.setSourceModel(nullptr); } static bool isValid(const QItemSelection &selection) @@ -3321,7 +3311,7 @@ class Model10287 : public QStandardItemModel Q_OBJECT public: - Model10287(QObject *parent = 0) + Model10287(QObject *parent = nullptr) : QStandardItemModel(0, 1, parent) { parentItem = new QStandardItem("parent"); @@ -3352,7 +3342,7 @@ class Proxy10287 : public QSortFilterProxyModel Q_OBJECT public: - Proxy10287(QAbstractItemModel *model, QObject *parent = 0) + Proxy10287(QAbstractItemModel *model, QObject *parent = nullptr) : QSortFilterProxyModel(parent) { setSourceModel(model); @@ -3360,7 +3350,7 @@ public: } protected: - virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { // Filter based on UserRole in model QModelIndex i = sourceModel()->index(source_row, 0, source_parent); @@ -3380,13 +3370,9 @@ class FilteredColumnProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - FilteredColumnProxyModel(QObject *parent = 0) - : QSortFilterProxyModel(parent) - { - } - + using QSortFilterProxyModel::QSortFilterProxyModel; protected: - bool filterAcceptsColumn(int column, const QModelIndex & /* source_parent */) const + bool filterAcceptsColumn(int column, const QModelIndex &) const override { return column % 2 != 0; } @@ -3413,12 +3399,7 @@ class ChangableHeaderData : public QStringListModel { Q_OBJECT public: - explicit ChangableHeaderData(QObject *parent = 0) - : QStringListModel(parent) - { - - } - + using QStringListModel::QStringListModel; void emitHeaderDataChanged() { headerDataChanged(Qt::Vertical, 0, rowCount() - 1); @@ -3461,7 +3442,7 @@ void tst_QSortFilterProxyModel::resetInvalidate() struct Proxy : QSortFilterProxyModel { QString pattern; - virtual bool filterAcceptsRow(int source_row, const QModelIndex&) const + bool filterAcceptsRow(int source_row, const QModelIndex&) const override { return sourceModel()->data(sourceModel()->index(source_row, 0)).toString().contains(pattern); } @@ -3479,17 +3460,16 @@ void tst_QSortFilterProxyModel::resetInvalidate() } }; - QStringListModel sourceModel(QStringList() << "Poisson" << "Vache" << "Brebis" - << "Elephant" << "Cochon" << "Serpent" - << "Mouton" << "Ecureuil" << "Mouche"); + QStringListModel sourceModel({"Poisson", "Vache", "Brebis", + "Elephant", "Cochon", "Serpent", + "Mouton", "Ecureuil", "Mouche"}); Proxy proxy; proxy.pattern = QString::fromLatin1("n"); proxy.setSourceModel(&sourceModel); QCOMPARE(proxy.rowCount(), 5); - for (int i = 0; i < proxy.rowCount(); i++) { + for (int i = 0; i < proxy.rowCount(); i++) QVERIFY(proxy.data(proxy.index(i,0)).toString().contains('n')); - } proxy.pattern = QString::fromLatin1("o"); proxy.notifyChange(test); @@ -3511,13 +3491,13 @@ class CustomDataProxy : public QSortFilterProxyModel Q_OBJECT public: - CustomDataProxy(QObject *parent = 0) + CustomDataProxy(QObject *parent = nullptr) : QSortFilterProxyModel(parent) { setDynamicSortFilter(true); } - void setSourceModel(QAbstractItemModel *sourceModel) + void setSourceModel(QAbstractItemModel *sourceModel) override { // It would be possible to use only the modelReset signal of the source model to clear // the data in *this, however, this requires that the slot is connected @@ -3534,16 +3514,13 @@ public: for (int i = 0; i < sourceModel->rowCount(); ++i) { if (sourceModel->index(i, 0).data().toString().endsWith(QLatin1Char('y'))) - { m_backgroundColours.insert(i, Qt::blue); - } else if (sourceModel->index(i, 0).data().toString().endsWith(QLatin1Char('r'))) - { + else if (sourceModel->index(i, 0).data().toString().endsWith(QLatin1Char('r'))) m_backgroundColours.insert(i, Qt::red); - } } } - QVariant data(const QModelIndex &index, int role) const + QVariant data(const QModelIndex &index, int role) const override { if (role != Qt::BackgroundRole) return QSortFilterProxyModel::data(index, role); @@ -3564,12 +3541,14 @@ class ModelObserver : public QObject { Q_OBJECT public: - ModelObserver(QAbstractItemModel *model, QObject *parent = 0) - : QObject(parent), m_model(model) - { - connect(m_model, SIGNAL(modelAboutToBeReset()), SLOT(modelAboutToBeReset())); - connect(m_model, SIGNAL(modelReset()), SLOT(modelReset())); - } + ModelObserver(QAbstractItemModel *model, QObject *parent = nullptr) + : QObject(parent), m_model(model) + { + connect(m_model, &QAbstractItemModel::modelAboutToBeReset, + this, &ModelObserver::modelAboutToBeReset); + connect(m_model, &QAbstractItemModel::modelReset, + this, &ModelObserver::modelReset); + } public slots: void modelAboutToBeReset() @@ -3580,7 +3559,7 @@ public slots: QColor color = m_model->index(i, 0).data(Qt::BackgroundRole).value(); if (color == Qt::blue) ++blues; - if (color == Qt::red) + else if (color == Qt::red) ++reds; } QCOMPARE(blues, 11); @@ -3595,7 +3574,7 @@ public slots: QColor color = m_model->index(i, 0).data(Qt::BackgroundRole).value(); if (color == Qt::blue) ++blues; - if (color == Qt::red) + else if (color == Qt::red) ++reds; } QCOMPARE(reds, 0); @@ -3609,26 +3588,25 @@ private: void tst_QSortFilterProxyModel::testResetInternalData() { - - QStringListModel model(QStringList() << "Monday" - << "Tuesday" - << "Wednesday" - << "Thursday" - << "Friday" - << "January" - << "February" - << "March" - << "April" - << "May" - << "Saturday" - << "June" - << "Sunday" - << "July" - << "August" - << "September" - << "October" - << "November" - << "December"); + QStringListModel model({"Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "January", + "February", + "March", + "April", + "May", + "Saturday", + "June", + "Sunday", + "July", + "August", + "September", + "October", + "November", + "December"}); CustomDataProxy proxy; proxy.setSourceModel(&model); @@ -3636,7 +3614,7 @@ void tst_QSortFilterProxyModel::testResetInternalData() ModelObserver observer(&proxy); // Cause the source model to reset. - model.setStringList(QStringList() << "Spam" << "Eggs"); + model.setStringList({"Spam", "Eggs"}); } @@ -3752,13 +3730,17 @@ class SignalArgumentChecker : public QObject { Q_OBJECT public: - SignalArgumentChecker(QAbstractItemModel *model, QAbstractProxyModel *proxy, QObject *parent = 0) - : QObject(parent), m_proxy(proxy) + SignalArgumentChecker(QAbstractItemModel *model, QAbstractProxyModel *proxy, QObject *parent = nullptr) + : QObject(parent), m_proxy(proxy) { - connect(model, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int))); - connect(model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(rowsMoved(QModelIndex,int,int,QModelIndex,int))); - connect(proxy, SIGNAL(layoutAboutToBeChanged(QList)), SLOT(layoutAboutToBeChanged(QList))); - connect(proxy, SIGNAL(layoutChanged(QList)), SLOT(layoutChanged(QList))); + connect(model, &QAbstractItemModel::rowsAboutToBeMoved, + this, &SignalArgumentChecker::rowsAboutToBeMoved); + connect(model, &QAbstractItemModel::rowsMoved, + this, &SignalArgumentChecker::rowsMoved); + connect(proxy, &QAbstractProxyModel::layoutAboutToBeChanged, + this, &SignalArgumentChecker::layoutAboutToBeChanged); + connect(proxy, &QAbstractProxyModel::layoutChanged, + this, &SignalArgumentChecker::layoutChanged); } private slots: @@ -3881,7 +3863,7 @@ void tst_QSortFilterProxyModel::moveSourceRows() QVERIFY(filterBothAfterParentLayoutSpy.isValid()); { - ModelMoveCommand moveCommand(&model, 0); + ModelMoveCommand moveCommand(&model, nullptr); moveCommand.setAncestorRowNumbers(QList() << 2); moveCommand.setDestAncestors(QList() << 5); moveCommand.setStartRow(3); @@ -3923,13 +3905,7 @@ class FilterProxy : public QSortFilterProxyModel { Q_OBJECT public: - FilterProxy(QObject *parent = 0) - : QSortFilterProxyModel(parent), - mode(false) - { - - } - + using QSortFilterProxyModel::QSortFilterProxyModel; public slots: void setMode(bool on) { @@ -3938,25 +3914,23 @@ public slots: } protected: - virtual bool filterAcceptsRow ( int source_row, const QModelIndex & source_parent ) const + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { if (mode) { - if (!source_parent.isValid()) { + if (!source_parent.isValid()) return true; - } else { + else return (source_row % 2) != 0; - } } else { - if (!source_parent.isValid()) { + if (!source_parent.isValid()) return source_row >= 2 && source_row < 10; - } else { + else return true; - } } } private: - bool mode; + bool mode = false; }; void tst_QSortFilterProxyModel::hierarchyFilterInvalidation() @@ -3989,13 +3963,7 @@ class FilterProxy2 : public QSortFilterProxyModel { Q_OBJECT public: - FilterProxy2(QObject *parent = 0) - : QSortFilterProxyModel(parent), - mode(false) - { - - } - + using QSortFilterProxyModel::QSortFilterProxyModel; public slots: void setMode(bool on) { @@ -4004,21 +3972,17 @@ public slots: } protected: - virtual bool filterAcceptsRow ( int source_row, const QModelIndex & source_parent ) const + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { - if (source_parent.isValid()) { + if (source_parent.isValid()) return true; - } else { - if (0 == source_row) { - return true; - } else { - return !mode; - } - } + if (0 == source_row) + return true; + return !mode; } private: - bool mode; + bool mode = false; }; void tst_QSortFilterProxyModel::simpleFilterInvalidation() @@ -4047,22 +4011,21 @@ class CustomRoleNameModel : public QAbstractListModel { Q_OBJECT public: - CustomRoleNameModel(QObject *parent = 0) : QAbstractListModel(parent) {} - - QVariant data(const QModelIndex &index, int role) const + using QAbstractListModel::QAbstractListModel; + QVariant data(const QModelIndex &index, int role) const override { - Q_UNUSED(index); - Q_UNUSED(role); + Q_UNUSED(index) + Q_UNUSED(role) return QVariant(); } - int rowCount(const QModelIndex &parent = QModelIndex()) const + int rowCount(const QModelIndex &parent = QModelIndex()) const override { - Q_UNUSED(parent); + Q_UNUSED(parent) return 0; } - QHash roleNames() const + QHash roleNames() const override { QHash rn = QAbstractListModel::roleNames(); rn[Qt::UserRole + 1] = "custom"; @@ -4089,8 +4052,7 @@ class DropOnOddRows : public QAbstractListModel { Q_OBJECT public: - DropOnOddRows(QObject *parent = 0) : QAbstractListModel(parent) {} - + using QAbstractListModel::QAbstractListModel; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { if (role == Qt::DisplayRole) @@ -4100,15 +4062,15 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override { - Q_UNUSED(parent); + Q_UNUSED(parent) return 10; } bool canDropMimeData(const QMimeData *, Qt::DropAction, int row, int column, const QModelIndex &parent) const override { - Q_UNUSED(row); - Q_UNUSED(column); + Q_UNUSED(row) + Q_UNUSED(column) return parent.row() % 2 == 0; } }; @@ -4117,12 +4079,7 @@ class SourceAssertion : public QSortFilterProxyModel { Q_OBJECT public: - explicit SourceAssertion(QObject *parent = 0) - : QSortFilterProxyModel(parent) - { - - } - + using QSortFilterProxyModel::QSortFilterProxyModel; QModelIndex mapToSource(const QModelIndex &proxyIndex) const override { Q_ASSERT(sourceModel()); @@ -4150,9 +4107,11 @@ void tst_QSortFilterProxyModel::noMapAfterSourceDelete() // QTBUG-39549, test whether canDropMimeData(), dropMimeData() are proxied as well // by invoking them on a QSortFilterProxyModel proxying a QStandardItemModel that allows drops // on row #1, filtering for that row. -class DropTestModel : public QStandardItemModel { +class DropTestModel : public QStandardItemModel +{ + Q_OBJECT public: - explicit DropTestModel(QObject *parent = 0) : QStandardItemModel(0, 1, parent) + explicit DropTestModel(QObject *parent = nullptr) : QStandardItemModel(0, 1, parent) { appendRow(new QStandardItem(QStringLiteral("Row0"))); appendRow(new QStandardItem(QStringLiteral("Row1"))); @@ -4173,11 +4132,12 @@ void tst_QSortFilterProxyModel::forwardDropApi() model.setSourceModel(new DropTestModel(&model)); model.setFilterFixedString(QStringLiteral("Row1")); QCOMPARE(model.rowCount(), 1); - QVERIFY(model.canDropMimeData(0, Qt::CopyAction, 0, 0, QModelIndex())); - QVERIFY(model.dropMimeData(0, Qt::CopyAction, 0, 0, QModelIndex())); + QVERIFY(model.canDropMimeData(nullptr, Qt::CopyAction, 0, 0, QModelIndex())); + QVERIFY(model.dropMimeData(nullptr, Qt::CopyAction, 0, 0, QModelIndex())); } -static QString rowTexts(QAbstractItemModel *model) { +static QString rowTexts(QAbstractItemModel *model) +{ QString str; for (int row = 0 ; row < model->rowCount(); ++row) str += model->index(row, 0).data().toString(); @@ -4199,7 +4159,7 @@ void tst_QSortFilterProxyModel::canDropMimeData() // the proxy should correctly map canDropMimeData to the source model, // i.e. accept drops on the first 5 rows and refuse drops on the next 5. for (int row = 0; row < proxy.rowCount(); ++row) - QCOMPARE(proxy.canDropMimeData(0, Qt::CopyAction, -1, -1, proxy.index(row, 0)), row < 5); + QCOMPARE(proxy.canDropMimeData(nullptr, Qt::CopyAction, -1, -1, proxy.index(row, 0)), row < 5); } void tst_QSortFilterProxyModel::resortingDoesNotBreakTreeModels() @@ -4239,12 +4199,7 @@ void tst_QSortFilterProxyModel::resortingDoesNotBreakTreeModels() void tst_QSortFilterProxyModel::filterHint() { // test that a filtering model does not emit layoutChanged with a hint - QStringListModel model(QStringList() << "one" - << "two" - << "three" - << "four" - << "five" - << "six"); + QStringListModel model({"one", "two", "three", "four", "five", "six"}); QSortFilterProxyModel proxy1; proxy1.setSourceModel(&model); proxy1.setSortRole(Qt::DisplayRole); @@ -4296,8 +4251,7 @@ class StepTreeModel : public QAbstractItemModel { Q_OBJECT public: - StepTreeModel(QObject * parent = 0) - : QAbstractItemModel(parent), m_depth(0) {} + using QAbstractItemModel::QAbstractItemModel; int columnCount(const QModelIndex& = QModelIndex()) const override { return 1; } @@ -4338,7 +4292,7 @@ public: void setDepth(quintptr depth) { - int parentIdWithLayoutChange = (m_depth < depth) ? m_depth : depth; + quintptr parentIdWithLayoutChange = (m_depth < depth) ? m_depth : depth; QList parentsOfLayoutChange; parentsOfLayoutChange.push_back(createIndex(0, 0, parentIdWithLayoutChange)); @@ -4370,7 +4324,7 @@ private: } private: - quintptr m_depth; + quintptr m_depth = 0; }; void tst_QSortFilterProxyModel::sourceLayoutChangeLeavesValidPersistentIndexes() @@ -4439,7 +4393,7 @@ void tst_QSortFilterProxyModel::rowMoveLeavesValidPersistentIndexes() QPersistentModelIndex persistentIndex = proxy1.match(proxy1.index(0, 0), Qt::DisplayRole, "2", 1, Qt::MatchRecursive).first(); - ModelMoveCommand moveCommand(&model, 0); + ModelMoveCommand moveCommand(&model, nullptr); moveCommand.setAncestorRowNumbers(QList{0, 0, 0, 0}); moveCommand.setStartRow(0); moveCommand.setEndRow(0); @@ -4578,7 +4532,7 @@ void tst_QSortFilterProxyModel::emitLayoutChangedOnlyIfSortingChanged() void tst_QSortFilterProxyModel::removeIntervals_data() { QTest::addColumn("sourceItems"); - QTest::addColumn("sortOrder"); + QTest::addColumn("sortOrder"); QTest::addColumn("filter"); QTest::addColumn("replacementSourceItems"); QTest::addColumn("expectedRemovedProxyIntervals"); @@ -4588,7 +4542,7 @@ void tst_QSortFilterProxyModel::removeIntervals_data() << (QStringList() << "a" << "b" << "c") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "[^x]" // filter << (QStringList() << "x" << "x" @@ -4601,7 +4555,7 @@ void tst_QSortFilterProxyModel::removeIntervals_data() << (QStringList() << "a" << "b" << "c") // sourceItems - << static_cast(Qt::DescendingOrder) // sortOrder + << Qt::DescendingOrder // sortOrder << "[^x]" // filter << (QStringList() << "x" << "x" @@ -4614,7 +4568,7 @@ void tst_QSortFilterProxyModel::removeIntervals_data() << (QStringList() << "a" << "b" << "c") // sourceItems - << static_cast(Qt::AscendingOrder) // sortOrder + << Qt::AscendingOrder // sortOrder << "[^x]" // filter << (QStringList() << "x" << "b" @@ -4627,7 +4581,7 @@ void tst_QSortFilterProxyModel::removeIntervals_data() << (QStringList() << "a" << "b" << "c") // sourceItems - << static_cast(Qt::DescendingOrder) // sortOrder + << Qt::DescendingOrder // sortOrder << "[^x]" // filter << (QStringList() << "x" << "b" @@ -4640,7 +4594,7 @@ void tst_QSortFilterProxyModel::removeIntervals_data() void tst_QSortFilterProxyModel::removeIntervals() { QFETCH(QStringList, sourceItems); - QFETCH(int, sortOrder); + QFETCH(Qt::SortOrder, sortOrder); QFETCH(QString, filter); QFETCH(QStringList, replacementSourceItems); QFETCH(IntPairList, expectedRemovedProxyIntervals); @@ -4659,9 +4613,7 @@ void tst_QSortFilterProxyModel::removeIntervals() } proxy.setDynamicSortFilter(true); - - if (sortOrder != -1) - proxy.sort(0, static_cast(sortOrder)); + proxy.sort(0, sortOrder); if (!filter.isEmpty()) setupFilter(&proxy, filter); @@ -4681,7 +4633,7 @@ void tst_QSortFilterProxyModel::removeIntervals() QCOMPARE(aboutToRemoveSpy.count(), expectedRemovedProxyIntervals.count()); for (int i = 0; i < aboutToRemoveSpy.count(); ++i) { - QList args = aboutToRemoveSpy.at(i); + const auto &args = aboutToRemoveSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), expectedRemovedProxyIntervals.at(i).first); @@ -4689,7 +4641,7 @@ void tst_QSortFilterProxyModel::removeIntervals() } QCOMPARE(removeSpy.count(), expectedRemovedProxyIntervals.count()); for (int i = 0; i < removeSpy.count(); ++i) { - QList args = removeSpy.at(i); + const auto &args = removeSpy.at(i); QCOMPARE(args.at(1).type(), QVariant::Int); QCOMPARE(args.at(2).type(), QVariant::Int); QCOMPARE(args.at(1).toInt(), expectedRemovedProxyIntervals.at(i).first); @@ -4838,8 +4790,8 @@ void tst_QSortFilterProxyModel::filterAndInsertColumn_data() void tst_QSortFilterProxyModel::filterAndInsertColumn() { - class ColumnFilterProxy : public QSortFilterProxyModel { - Q_DISABLE_COPY(ColumnFilterProxy) + class ColumnFilterProxy : public QSortFilterProxyModel + { ColumnFilterMode filerMode; public: ColumnFilterProxy(ColumnFilterMode mode) diff --git a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.h b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.h index 8ae97165b8..71662bda07 100644 --- a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.h +++ b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_common/tst_qsortfilterproxymodel.h @@ -29,19 +29,10 @@ #ifndef TST_QSORTFILTERPROXYMODEL_H #define TST_QSORTFILTERPROXYMODEL_H -#include #include "dynamictreemodel.h" - -#include -#include -#include -#include - -#include - -typedef QList IntList; -typedef QPair IntPair; -typedef QList IntPairList; +#include +#include +#include enum class FilterType { RegExp, @@ -53,10 +44,6 @@ Q_DECLARE_METATYPE(QList) class tst_QSortFilterProxyModel : public QObject { Q_OBJECT - -public: - tst_QSortFilterProxyModel(); - public slots: void initTestCase(); void cleanupTestCase(); @@ -181,8 +168,8 @@ protected: FilterType m_filterType; private: - QStandardItemModel *m_model; - QSortFilterProxyModel *m_proxy; + QStandardItemModel *m_model = nullptr; + QSortFilterProxyModel *m_proxy = nullptr; }; Q_DECLARE_METATYPE(QAbstractItemModel::LayoutChangeHint) diff --git a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/tst_qsortfilterproxymodel_recursive.cpp b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/tst_qsortfilterproxymodel_recursive.cpp index 7cae554963..dbafedbb5d 100644 --- a/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/tst_qsortfilterproxymodel_recursive.cpp +++ b/tests/auto/corelib/itemmodels/qsortfilterproxymodel_recursive/tst_qsortfilterproxymodel_recursive.cpp @@ -28,9 +28,8 @@ #include #include - -#include -#include +#include +#include Q_DECLARE_METATYPE(QModelIndex) From 9d504e1150e4bd49d34aba5cee938d1fd9f7e80c Mon Sep 17 00:00:00 2001 From: Albert Astals Cid Date: Fri, 11 Oct 2019 17:31:22 +0200 Subject: [PATCH 47/47] QSqlTableModel::record: Use the const & we're given QSqlTableModelPrivate::ModifiedRow::rec returns a const & so use it instead doing a copy. QSqlRecord is cheap to copy constructor but not having to do it is faster Change-Id: Iad6e79fcdcdf380ce681fe9426436f8cb98be553 Reviewed-by: Andy Shaw --- src/sql/models/qsqltablemodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sql/models/qsqltablemodel.cpp b/src/sql/models/qsqltablemodel.cpp index 4bc9a8c2f8..ba8a6dea36 100644 --- a/src/sql/models/qsqltablemodel.cpp +++ b/src/sql/models/qsqltablemodel.cpp @@ -1369,7 +1369,7 @@ QSqlRecord QSqlTableModel::record(int row) const // get generated flags from the cache const QSqlTableModelPrivate::ModifiedRow mrow = d->cache.value(row); if (mrow.op() != QSqlTableModelPrivate::None) { - const QSqlRecord crec = mrow.rec(); + const QSqlRecord &crec = mrow.rec(); for (int i = 0, cnt = rec.count(); i < cnt; ++i) rec.setGenerated(i, crec.isGenerated(i)); }