diff options
author | Qt Forward Merge Bot <qt_forward_merge_bot@qt-project.org> | 2018-06-29 01:02:07 +0200 |
---|---|---|
committer | Qt Forward Merge Bot <qt_forward_merge_bot@qt-project.org> | 2018-06-29 01:02:07 +0200 |
commit | dd333172e2ade1db6d0af72b4ed44262c2b4d8c7 (patch) | |
tree | 16301a9b938826cf4b78b751907378d96423c5d0 | |
parent | 3d266d90cb920375bb856844baea290e52355aad (diff) | |
parent | ffeaac704efc9eb85464d0a401d98e28991ec4d3 (diff) |
Merge remote-tracking branch 'origin/5.11' into dev
Change-Id: I800f39ea59efd6049d3226ec27b8ecb3f6e9ae28
-rw-r--r-- | src/qml/doc/src/javascript/resources.qdoc | 27 | ||||
-rw-r--r-- | src/qml/qml/qqmlimport.cpp | 14 | ||||
-rw-r--r-- | src/qml/qml/qqmlmetatype.cpp | 42 | ||||
-rw-r--r-- | src/qml/qml/qqmlmetatype_p.h | 13 | ||||
-rw-r--r-- | src/quick/doc/src/guidelines/qtquick-bestpractices.qdoc | 2 | ||||
-rw-r--r-- | src/quick/doc/src/qtquick.qdoc | 2 | ||||
-rw-r--r-- | src/quick/items/qquickdrag.cpp | 4 | ||||
-rw-r--r-- | src/quick/items/qquickpathview.cpp | 5 | ||||
-rw-r--r-- | src/quick/items/qquicktextcontrol.cpp | 1 | ||||
-rw-r--r-- | tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp | 2 | ||||
-rw-r--r-- | tests/auto/qml/qqmlmetatype/tst_qqmlmetatype.cpp | 14 | ||||
-rw-r--r-- | tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp | 30 | ||||
-rw-r--r-- | tests/auto/quick/qquickpathview/data/objectModelMove.qml | 123 | ||||
-rw-r--r-- | tests/auto/quick/qquickpathview/tst_qquickpathview.cpp | 48 | ||||
-rw-r--r-- | tools/qmlcachegen/qmlcachegen.cpp | 12 |
15 files changed, 278 insertions, 61 deletions
diff --git a/src/qml/doc/src/javascript/resources.qdoc b/src/qml/doc/src/javascript/resources.qdoc index 34b0610f74..60f97c2007 100644 --- a/src/qml/doc/src/javascript/resources.qdoc +++ b/src/qml/doc/src/javascript/resources.qdoc @@ -26,7 +26,7 @@ ****************************************************************************/ /*! \page qtqml-javascript-resources.html -\title Defining JavaScript Resources In QML +\title Defining JavaScript Resources in QML \brief Description of how JavaScript files may be defined for use in QML The program logic for a QML application may be defined in JavaScript. The @@ -60,7 +60,8 @@ An example of a code-behind implementation resource follows: \code // MyButton.qml import QtQuick 2.0 -import "my_button_impl.js" as Logic // a new instance of this JavaScript resource is loaded for each instance of Button.qml +import "my_button_impl.js" as Logic // A new instance of this JavaScript resource + // is loaded for each instance of Button.qml. Rectangle { id: rect @@ -95,13 +96,18 @@ for maintainability and readability. \section1 Shared JavaScript Resources (Libraries) -Some JavaScript files act more like libraries - they provide a set of helper -functions that take input and compute output, but never manipulate QML -component instances directly. +By default, JavaScript files imported from QML share their context with the QML +component. That means the JavaScript files have access to the same QML objects +and can modify them. As a consequence, each import must have a unique copy of +these files. -As it would be wasteful for each QML component instance to have a unique copy of -these libraries, the JavaScript programmer can indicate a particular file is a -shared library through the use of a pragma, as shown in the following example. +\l {Defining JavaScript Resources in QML#Code-Behind Implementation Resource} +{The previous section} covers stateful imports of JavaScript files. However, +some JavaScript files are stateless and act more like reusable libraries, in +the sense that they provide a set of helper functions that do not require +anything from where they were imported from. You can save significant amounts +of memory and speed up the instantiation of QML components if you mark such +libraries with a special pragma, as shown in the following example. \code // factorial.js @@ -141,7 +147,10 @@ For example: \code // Calculator.qml import QtQuick 2.0 -import "factorial.js" as FactorialCalculator // this JavaScript resource is only ever loaded once by the engine, even if multiple instances of Calculator.qml are created +import "factorial.js" as FactorialCalculator // This JavaScript resource is only + // ever loaded once by the engine, + // even if multiple instances of + // Calculator.qml are created. Text { width: 500 diff --git a/src/qml/qml/qqmlimport.cpp b/src/qml/qml/qqmlimport.cpp index 962c92fdb0..1c37894751 100644 --- a/src/qml/qml/qqmlimport.cpp +++ b/src/qml/qml/qqmlimport.cpp @@ -158,6 +158,8 @@ QQmlType fetchOrCreateTypeForUrl(const QString &urlString, const QHashedStringRe // can guarentee it will live long enough to reach qmlregister. QByteArray buf(unqualifiedtype.toString().toUtf8()); + QQmlMetaTypeRegistrationFailureRecorder failureRecorder; + // Register the type. Note that the URI parameters here are empty; for // file type imports, we do not place them in a URI as we don't // necessarily have a good and unique one (picture a library import, @@ -200,9 +202,9 @@ QQmlType fetchOrCreateTypeForUrl(const QString &urlString, const QHashedStringRe // data. if (!ret.isValid()) { if (!errors) // Cannot list errors properly, just quit - qFatal("%s", QQmlMetaType::typeRegistrationFailures().join('\n').toLatin1().constData()); + qFatal("%s", failureRecorder.failures().join('\n').toLatin1().constData()); QQmlError error; - error.setDescription(QQmlMetaType::typeRegistrationFailures().join('\n')); + error.setDescription(failureRecorder.failures().join('\n')); errors->prepend(error); } return ret; @@ -2022,7 +2024,7 @@ bool QQmlImportDatabase::registerPluginTypes(QObject *instance, const QString &b const QByteArray bytes = uri.toUtf8(); const char *moduleId = bytes.constData(); - QStringList registrationFailures; + QQmlMetaTypeRegistrationFailureRecorder failureRecorder; { // Create a scope for QWriteLocker to keep it as narrow as possible, and // to ensure that we release it before the call to initalizeEngine below @@ -2064,14 +2066,12 @@ bool QQmlImportDatabase::registerPluginTypes(QObject *instance, const QString &b } iface->registerTypes(moduleId); - - registrationFailures = QQmlMetaType::typeRegistrationFailures(); QQmlMetaType::setTypeRegistrationNamespace(QString()); } // QWriteLocker lock(QQmlMetaType::typeRegistrationLock()) - if (!registrationFailures.isEmpty()) { + if (!failureRecorder.failures().isEmpty()) { if (errors) { - for (const QString &failure : qAsConst(registrationFailures)) { + for (const QString &failure : failureRecorder.failures()) { QQmlError error; error.setDescription(failure); errors->prepend(error); diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp index 69a8ff034a..b31058ece5 100644 --- a/src/qml/qml/qqmlmetatype.cpp +++ b/src/qml/qml/qqmlmetatype.cpp @@ -114,13 +114,27 @@ struct QQmlMetaTypeData QSet<QString> protectedNamespaces; QString typeRegistrationNamespace; - QStringList typeRegistrationFailures; QHash<int, int> qmlLists; QHash<const QMetaObject *, QQmlPropertyCache *> propertyCaches; QQmlPropertyCache *propertyCache(const QMetaObject *metaObject); QQmlPropertyCache *propertyCache(const QQmlType &type, int minorVersion); + + void startRecordingTypeRegFailures(QStringList *storage) + { typeRegistrationFailures = storage; } + void stopRecordingTypeRegFailures() + { startRecordingTypeRegFailures(nullptr); } + void recordTypeRegFailure(const QString &message) + { + if (typeRegistrationFailures) + typeRegistrationFailures->append(message); + else + qWarning("%s", message.toUtf8().constData()); + } + +private: + QStringList *typeRegistrationFailures = nullptr; }; class QQmlTypeModulePrivate @@ -152,6 +166,16 @@ static uint qHash(const QQmlMetaTypeData::VersionedUri &v) return v.uri.hash() ^ qHash(v.majorVersion); } +QQmlMetaTypeRegistrationFailureRecorder::QQmlMetaTypeRegistrationFailureRecorder() +{ + metaTypeData()->startRecordingTypeRegFailures(&_failures); +} + +QQmlMetaTypeRegistrationFailureRecorder::~QQmlMetaTypeRegistrationFailureRecorder() +{ + metaTypeData()->stopRecordingTypeRegFailures(); +} + QQmlMetaTypeData::QQmlMetaTypeData() { } @@ -1581,7 +1605,7 @@ bool checkRegistration(QQmlType::RegistrationType typeType, QQmlMetaTypeData *da if (!typeName.isEmpty()) { if (typeName.at(0).isLower()) { QString failure(QCoreApplication::translate("qmlRegisterType", "Invalid QML %1 name \"%2\"; type names must begin with an uppercase letter")); - data->typeRegistrationFailures.append(failure.arg(registrationTypeString(typeType)).arg(typeName)); + data->recordTypeRegFailure(failure.arg(registrationTypeString(typeType)).arg(typeName)); return false; } @@ -1589,7 +1613,7 @@ bool checkRegistration(QQmlType::RegistrationType typeType, QQmlMetaTypeData *da for (int ii = 0; ii < typeNameLen; ++ii) { if (!(typeName.at(ii).isLetterOrNumber() || typeName.at(ii) == '_')) { QString failure(QCoreApplication::translate("qmlRegisterType", "Invalid QML %1 name \"%2\"")); - data->typeRegistrationFailures.append(failure.arg(registrationTypeString(typeType)).arg(typeName)); + data->recordTypeRegFailure(failure.arg(registrationTypeString(typeType)).arg(typeName)); return false; } } @@ -1603,7 +1627,7 @@ bool checkRegistration(QQmlType::RegistrationType typeType, QQmlMetaTypeData *da if (data->protectedNamespaces.contains(nameSpace)) { QString failure(QCoreApplication::translate("qmlRegisterType", "Cannot install %1 '%2' into protected namespace '%3'")); - data->typeRegistrationFailures.append(failure.arg(registrationTypeString(typeType)).arg(typeName).arg(nameSpace)); + data->recordTypeRegFailure(failure.arg(registrationTypeString(typeType)).arg(typeName).arg(nameSpace)); return false; } } else if (majorVersion >= 0) { @@ -1614,7 +1638,7 @@ bool checkRegistration(QQmlType::RegistrationType typeType, QQmlMetaTypeData *da if (QQmlTypeModulePrivate::get(qqtm)->locked){ QString failure(QCoreApplication::translate("qmlRegisterType", "Cannot install %1 '%2' into protected module '%3' version '%4'")); - data->typeRegistrationFailures.append(failure.arg(registrationTypeString(typeType)).arg(typeName).arg(nameSpace).arg(majorVersion)); + data->recordTypeRegFailure(failure.arg(registrationTypeString(typeType)).arg(typeName).arg(nameSpace).arg(majorVersion)); return false; } } @@ -1905,14 +1929,6 @@ void QQmlMetaType::setTypeRegistrationNamespace(const QString &uri) QQmlMetaTypeData *data = metaTypeData(); data->typeRegistrationNamespace = uri; - data->typeRegistrationFailures.clear(); -} - -QStringList QQmlMetaType::typeRegistrationFailures() -{ - QQmlMetaTypeData *data = metaTypeData(); - - return data->typeRegistrationFailures; } QMutex *QQmlMetaType::typeRegistrationLock() diff --git a/src/qml/qml/qqmlmetatype_p.h b/src/qml/qml/qqmlmetatype_p.h index 6df439cd7a..4a5e4ba266 100644 --- a/src/qml/qml/qqmlmetatype_p.h +++ b/src/qml/qml/qqmlmetatype_p.h @@ -155,7 +155,6 @@ public: static void protectNamespace(const QString &); static void setTypeRegistrationNamespace(const QString &); - static QStringList typeRegistrationFailures(); static QMutex *typeRegistrationLock(); @@ -361,6 +360,18 @@ private: int m_minor; }; +class Q_AUTOTEST_EXPORT QQmlMetaTypeRegistrationFailureRecorder +{ + QStringList _failures; + +public: + QQmlMetaTypeRegistrationFailureRecorder(); + ~QQmlMetaTypeRegistrationFailureRecorder(); + + QStringList failures() const + { return _failures; } +}; + QT_END_NAMESPACE #endif // QQMLMETATYPE_P_H diff --git a/src/quick/doc/src/guidelines/qtquick-bestpractices.qdoc b/src/quick/doc/src/guidelines/qtquick-bestpractices.qdoc index 30aca38678..f27f87e743 100644 --- a/src/quick/doc/src/guidelines/qtquick-bestpractices.qdoc +++ b/src/quick/doc/src/guidelines/qtquick-bestpractices.qdoc @@ -420,7 +420,7 @@ on the display resolution on offer. \section2 Related Information \list - \li \l{Qt Quick Controls2 - Gallery Example}{Gallery example} + \li \l{Qt Quick Controls 2 - Gallery Example}{Gallery example} \li \l{Qt Quick Controls 2 - Text Editor}{Text Editor example} \li \l{Font Awesome} \li \l{Scalability} diff --git a/src/quick/doc/src/qtquick.qdoc b/src/quick/doc/src/qtquick.qdoc index 4d8da5ed5a..4b843f366b 100644 --- a/src/quick/doc/src/qtquick.qdoc +++ b/src/quick/doc/src/qtquick.qdoc @@ -118,7 +118,7 @@ Additional Qt Quick information: \li \l{Qt Quick Test QML Types}{Tests} - contains types for writing unit test for a QML application \endlist \li \l{Qt Quick Examples and Tutorials} -\li \l{Qt Quick Guidelines} +\li \l{Best Practices for QML and Qt Quick}{Qt Quick Guidelines} \endlist Further information for writing QML applications: diff --git a/src/quick/items/qquickdrag.cpp b/src/quick/items/qquickdrag.cpp index 91f45894a0..f60f3c1ccf 100644 --- a/src/quick/items/qquickdrag.cpp +++ b/src/quick/items/qquickdrag.cpp @@ -41,7 +41,6 @@ #include <private/qguiapplication_p.h> #include <qpa/qplatformintegration.h> -#include <qpa/qplatformdrag.h> #include <private/qquickitem_p.h> #include <QtQuick/private/qquickevents_p_p.h> #include <private/qquickitemchangelistener_p.h> @@ -50,12 +49,13 @@ #include <private/qv4scopedvalue_p.h> #include <QtCore/qmimedata.h> #include <QtQml/qqmlinfo.h> -#include <QtGui/qdrag.h> #include <QtGui/qevent.h> #include <QtGui/qstylehints.h> #include <QtGui/qguiapplication.h> #if QT_CONFIG(draganddrop) +#include <qpa/qplatformdrag.h> +#include <QtGui/qdrag.h> QT_BEGIN_NAMESPACE diff --git a/src/quick/items/qquickpathview.cpp b/src/quick/items/qquickpathview.cpp index 74c8eaa169..879db6284e 100644 --- a/src/quick/items/qquickpathview.cpp +++ b/src/quick/items/qquickpathview.cpp @@ -240,9 +240,13 @@ void QQuickPathViewPrivate::clear() releaseItem(currentItem); currentItem = nullptr; } + for (QQuickItem *p : qAsConst(items)) releaseItem(p); + for (QQuickItem *p : qAsConst(itemCache)) + releaseItem(p); + if (requestedIndex >= 0) { if (model) model->cancel(requestedIndex); @@ -250,6 +254,7 @@ void QQuickPathViewPrivate::clear() } items.clear(); + itemCache.clear(); tl.clear(); } diff --git a/src/quick/items/qquicktextcontrol.cpp b/src/quick/items/qquicktextcontrol.cpp index 1cb12235c1..38ca7283b4 100644 --- a/src/quick/items/qquicktextcontrol.cpp +++ b/src/quick/items/qquicktextcontrol.cpp @@ -47,7 +47,6 @@ #include <qfontmetrics.h> #include <qevent.h> #include <qdebug.h> -#include <qdrag.h> #include <qclipboard.h> #include <qtimer.h> #include <qinputmethod.h> diff --git a/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp b/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp index 6dc54a323e..48eb694167 100644 --- a/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp +++ b/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp @@ -253,7 +253,7 @@ void tst_qmlcachegen::errorOnArgumentsInSignalHandler() QByteArray errorOutput; QVERIFY(!generateCache(testFilePath, &errorOutput)); - QVERIFY2(errorOutput.contains("error: The use of the arguments object in signal handlers is"), errorOutput); + QVERIFY2(errorOutput.contains("error: The use of eval() or the use of the arguments object in signal handlers is"), errorOutput); } void tst_qmlcachegen::aheadOfTimeCompilation() diff --git a/tests/auto/qml/qqmlmetatype/tst_qqmlmetatype.cpp b/tests/auto/qml/qqmlmetatype/tst_qqmlmetatype.cpp index 1d0d353668..e83dac48fb 100644 --- a/tests/auto/qml/qqmlmetatype/tst_qqmlmetatype.cpp +++ b/tests/auto/qml/qqmlmetatype/tst_qqmlmetatype.cpp @@ -227,17 +227,13 @@ void tst_qqmlmetatype::qmlType() void tst_qqmlmetatype::invalidQmlTypeName() { - QStringList currFailures = QQmlMetaType::typeRegistrationFailures(); + QTest::ignoreMessage(QtWarningMsg, "Invalid QML element name \"testtype\"; type names must begin with an uppercase letter"); + QTest::ignoreMessage(QtWarningMsg, "Invalid QML element name \"Test$Type\""); + QTest::ignoreMessage(QtWarningMsg, "Invalid QML element name \"EndingInSlash/\""); + QCOMPARE(qmlRegisterType<TestType>("TestNamespace", 1, 0, "Test$Type"), -1); // should fail due to invalid QML type name. QCOMPARE(qmlRegisterType<TestType>("Test", 1, 0, "EndingInSlash/"), -1); - QStringList nowFailures = QQmlMetaType::typeRegistrationFailures(); - - foreach (const QString &f, currFailures) - nowFailures.removeOne(f); - - QCOMPARE(nowFailures.size(), 2); - QCOMPARE(nowFailures.at(0), QStringLiteral("Invalid QML element name \"Test$Type\"")); - QCOMPARE(nowFailures.at(1), QStringLiteral("Invalid QML element name \"EndingInSlash/\"")); + QCOMPARE(qmlRegisterType<TestType>("Test", 1, 0, "testtype"), -1); } void tst_qqmlmetatype::prettyTypeName() diff --git a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp index b8d5a4d3a3..ba7c85df15 100644 --- a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp +++ b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp @@ -2064,40 +2064,48 @@ void tst_qqmlproperty::floatToStringPrecision_data() QTest::addColumn<QString>("propertyName"); QTest::addColumn<double>("number"); QTest::addColumn<QString>("qtString"); + QTest::addColumn<QString>("alternateQtString"); QTest::addColumn<QString>("jsString"); - QTest::newRow("3.4") << "a" << 3.4 << "3.4" << "3.4"; - QTest::newRow("0.035003945") << "b" << 0.035003945 << "0.035003945" << "0.035003945"; - QTest::newRow("0.0000012345") << "c" << 0.0000012345 << "1.2345e-6" << "0.0000012345"; - QTest::newRow("0.00000012345") << "d" << 0.00000012345 << "1.2345e-7" << "1.2345e-7"; - QTest::newRow("1e20") << "e" << 1e20 << "1e+20" << "100000000000000000000"; - QTest::newRow("1e21") << "f" << 1e21 << "1e+21" << "1e+21"; + QTest::newRow("3.4") << "a" << 3.4 << "3.4" << "3.4" << "3.4"; + QTest::newRow("0.035003945") << "b" << 0.035003945 << "0.035003945" << "0.0035003945" << "0.035003945"; + QTest::newRow("0.0000012345") << "c" << 0.0000012345 << "1.2345e-6" << "1.2345e-06" << "0.0000012345"; + QTest::newRow("0.00000012345") << "d" << 0.00000012345 << "1.2345e-7" << "1.2345e-07" << "1.2345e-7"; + QTest::newRow("1e20") << "e" << 1e20 << "1e+20" << "1e+20" << "100000000000000000000"; + QTest::newRow("1e21") << "f" << 1e21 << "1e+21" << "1e+21" << "1e+21"; } void tst_qqmlproperty::floatToStringPrecision() { QQmlComponent comp(&engine, testFileUrl("floatToStringPrecision.qml")); - QObject *obj = comp.create(); + QScopedPointer<QObject> obj(comp.create()); QVERIFY(obj != nullptr); QFETCH(QString, propertyName); QFETCH(double, number); QFETCH(QString, qtString); + QFETCH(QString, alternateQtString); QFETCH(QString, jsString); QByteArray name = propertyName.toLatin1(); QCOMPARE(obj->property(name).toDouble(), number); - QCOMPARE(obj->property(name).toString(), qtString); + if (obj->property(name).toString() != qtString) { + QCOMPARE(obj->property(name).toString(), alternateQtString); + } else { + QCOMPARE(obj->property(name).toString(), qtString); + } QByteArray name1 = (propertyName + QLatin1Char('1')).toLatin1(); QCOMPARE(obj->property(name1).toDouble(), number); - QCOMPARE(obj->property(name1).toString(), qtString); + if (obj->property(name1).toString() != qtString) { + QCOMPARE(obj->property(name1).toString(), alternateQtString); + } else { + QCOMPARE(obj->property(name1).toString(), qtString); + } QByteArray name2 = (propertyName + QLatin1Char('2')).toLatin1(); QCOMPARE(obj->property(name2).toDouble(), number); QCOMPARE(obj->property(name2).toString(), jsString); - - delete obj; } void tst_qqmlproperty::initTestCase() diff --git a/tests/auto/quick/qquickpathview/data/objectModelMove.qml b/tests/auto/quick/qquickpathview/data/objectModelMove.qml new file mode 100644 index 0000000000..d5fa510d69 --- /dev/null +++ b/tests/auto/quick/qquickpathview/data/objectModelMove.qml @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQml.Models 2.11 +import QtQuick 2.11 + +Item { + id: root + width: 400 + height: 400 + visible: true + + property Item pathViewItem + + function destroyView() { + if (pathViewItem) + pathViewItem.destroy() + } + + function newView() { + pathViewItem = pathViewComponent.createObject(root) + } + + function move() { + objectModel.move(0, 1) + } + + Component { + id: pathViewComponent + + PathView { + id: pathView + objectName: "PathView" + width: 32 * 3 + height: 32 + model: objectModel + + interactive: false + snapMode: PathView.SnapToItem + movementDirection: PathView.Positive + highlightMoveDuration: 100 + + path: Path { + startX: 16 + startY: 16 + PathLine { + x: 16 + (32 * 3) + y: 16 + } + } + } + } + + ObjectModel { + id: objectModel + + Rectangle { + objectName: "red" + width: 32 + height: 32 + color: "red" + } + Rectangle { + objectName: "green" + width: 32 + height: 32 + color: "green" + } + Rectangle { + objectName: "blue" + width: 32 + height: 32 + color: "blue" + } + } +} diff --git a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp index 7a6f302403..7d297d3fa2 100644 --- a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp +++ b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp @@ -144,6 +144,7 @@ private slots: void movementDirection_data(); void movementDirection(); void removePath(); + void objectModelMove(); }; class TestObject : public QObject @@ -2530,6 +2531,53 @@ void tst_QQuickPathView::removePath() QVERIFY(QMetaObject::invokeMethod(pathview, "setPath")); } +/* + Tests that moving items in an ObjectModel and then deleting the view + doesn't cause heap-use-after-free when run through ASAN. + + The test case is based on a Qt Quick Controls 2 test where the issue was + discovered. +*/ +void tst_QQuickPathView::objectModelMove() +{ + QScopedPointer<QQuickView> window(createView()); + window->setSource(testFileUrl("objectModelMove.qml")); + window->show(); + + // Create the view. + QVERIFY(QMetaObject::invokeMethod(window->rootObject(), "newView")); + QPointer<QQuickPathView> pathView = window->rootObject()->property("pathViewItem").value<QQuickPathView*>(); + QVERIFY(pathView); + QCOMPARE(pathView->count(), 3); + pathView->highlightItem()->setObjectName("highlight"); + + // Move an item from index 0 to 1. + QVERIFY(QMetaObject::invokeMethod(window->rootObject(), "move")); + QCOMPARE(pathView->count(), 3); + + // Keep track of the amount of listeners + QVector<QString> itemObjectNames; + itemObjectNames << QLatin1String("red") << QLatin1String("green") << QLatin1String("blue"); + QVector<QQuickItem*> childItems; + for (const QString itemObjectName : qAsConst(itemObjectNames)) { + QQuickItem *childItem = findItem<QQuickItem>(pathView, itemObjectName); + QVERIFY(childItem); + childItems.append(childItem); + } + + // Destroy the view (via destroy()). + QVERIFY(QMetaObject::invokeMethod(window->rootObject(), "destroyView")); + // Ensure that the view has been destroyed. This check is also necessary in order for + // ASAN to complain (it will complain after the test function has finished). + QTRY_VERIFY(pathView.isNull()); + // By this point, all of its cached items should have been released, + // which means none of the items should have any listeners. + for (const auto childItem : qAsConst(childItems)) { + const QQuickItemPrivate *childItemPrivate = QQuickItemPrivate::get(childItem); + QCOMPARE(childItemPrivate->changeListeners.size(), 0); + } +} + QTEST_MAIN(tst_QQuickPathView) #include "tst_qquickpathview.moc" diff --git a/tools/qmlcachegen/qmlcachegen.cpp b/tools/qmlcachegen/qmlcachegen.cpp index ef14819d28..b9a8763c97 100644 --- a/tools/qmlcachegen/qmlcachegen.cpp +++ b/tools/qmlcachegen/qmlcachegen.cpp @@ -148,11 +148,13 @@ static bool checkArgumentsObjectUseInSignalHandlers(const QmlIR::Document &doc, if (compiledFunction->column > 0) error->message += QString::number(compiledFunction->column) + QLatin1Char(':'); - error->message += QLatin1String(" error: The use of the arguments object in signal handlers is\n" - "not supported when compiling qml files ahead of time, because it may be ambiguous if\n" - "any signal parameter is called \"arguments\". Unfortunately we cannot distinguish\n" - "between it being a parameter or the JavaScript arguments object at this point.\n" - "Consider renaming the parameter of the signal if applicable."); + error->message += QLatin1String(" error: The use of eval() or the use of the arguments object in signal handlers is\n" + "not supported when compiling qml files ahead of time. That is because it's ambiguous if \n" + "any signal parameter is called \"arguments\". Similarly the string passed to eval might use\n" + "\"arguments\". Unfortunately we cannot distinguish between it being a parameter or the\n" + "JavaScript arguments object at this point.\n" + "Consider renaming the parameter of the signal if applicable or moving the code into a\n" + "helper function."); return false; } } |