diff options
author | Qt Forward Merge Bot <qt_forward_merge_bot@qt-project.org> | 2020-01-09 01:00:43 +0100 |
---|---|---|
committer | Fabian Kosmale <fabian.kosmale@qt.io> | 2020-01-09 07:24:26 +0000 |
commit | ba10b0b9ed93be007fcb156710ef6081000e3ae3 (patch) | |
tree | ea17c625900b83d5955cb4a2db1587a5f07e2fb4 /tests/auto | |
parent | 653c25d48298fb747cf6f3b012816855c51d4260 (diff) | |
parent | 1798d20ded699837f7b3afe0bb340617af266518 (diff) |
Merge remote-tracking branch 'origin/5.14' into 5.15
Conflicts:
src/particles/qquickitemparticle.cpp
src/qmlmodels/qqmladaptormodel.cpp
tests/auto/particles/qquickitemparticle/tst_qquickitemparticle.cpp
Change-Id: Ibd8fbb91da6893a09f4ffe61ad0b95d8149bbc87
Diffstat (limited to 'tests/auto')
11 files changed, 450 insertions, 0 deletions
diff --git a/tests/auto/particles/qquickitemparticle/data/loader.qml b/tests/auto/particles/qquickitemparticle/data/loader.qml new file mode 100644 index 0000000000..beac7a0410 --- /dev/null +++ b/tests/auto/particles/qquickitemparticle/data/loader.qml @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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:GPL-EXCEPT$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick.Particles 2.0 + +Rectangle { + color: "black" + width: 320 + height: 320 + + Component { + id: component + + ParticleSystem { + id: sys + objectName: "system" + anchors.fill: parent + running: visible + + ItemParticle { + delegate: Image { source: "../../shared/star.png" } + } + + Emitter { + //0,0 position + size: 32 + emitRate: 10 + lifeSpan: 150000 + } + } + } + + Loader { + id: loader + objectName: "loader" + sourceComponent: component + anchors.fill: parent + } +} diff --git a/tests/auto/particles/qquickitemparticle/tst_qquickitemparticle.cpp b/tests/auto/particles/qquickitemparticle/tst_qquickitemparticle.cpp index 0087c74a9c..28ebbb3c05 100644 --- a/tests/auto/particles/qquickitemparticle/tst_qquickitemparticle.cpp +++ b/tests/auto/particles/qquickitemparticle/tst_qquickitemparticle.cpp @@ -46,6 +46,8 @@ private slots: void test_deletion(); void test_noDeletion(); void test_takeGive(); + void test_noCrashOnReset(); + void test_noLeakWhenDeleted(); }; void tst_qquickitemparticle::initTestCase() @@ -120,6 +122,41 @@ void tst_qquickitemparticle::test_takeGive() delete view; } +void tst_qquickitemparticle::test_noCrashOnReset() +{ + QQuickView* view = createView(testFileUrl("basic.qml"), 600); + QQuickParticleSystem* system = view->rootObject()->findChild<QQuickParticleSystem*>("system"); + + for (int i = 0; i < 10; ++i) { + ensureAnimTime(16, system->m_animation); + system->reset(); + } + + delete view; +} + +void tst_qquickitemparticle::test_noLeakWhenDeleted() +{ + QQuickView* view = createView(testFileUrl("loader.qml"), 500); + QQuickParticleSystem* system = view->rootObject()->findChild<QQuickParticleSystem*>("system"); + ensureAnimTime(100, system->m_animation); + + auto particles = qAsConst(system->groupData[0]->data); + QVERIFY(!particles.isEmpty()); + + QQuickParticleData* firstParticleData = particles.first(); + QPointer<QQuickItem> firstParticleDelegate = firstParticleData->delegate; + QVERIFY(!firstParticleDelegate.isNull()); + + QQuickItem* loader = view->rootObject()->findChild<QQuickItem*>("loader"); + loader->setProperty("active", false); //This should destroy the ParticleSystem, ItemParticle and Emitter + + QTest::qWait(1); //Process events to make sure the loader is properly unloaded + QVERIFY(firstParticleDelegate.isNull()); //Delegates should be deleted + + delete view; +} + QTEST_MAIN(tst_qquickitemparticle); #include "tst_qquickitemparticle.moc" diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index f1ff396d4f..66a526fda8 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -252,6 +252,17 @@ private slots: void interrupt(); void triggerBackwardJumpWithDestructuring(); + void arrayConcatOnSparseArray(); + void sortSparseArray(); + void compileBrokenRegexp(); + void sortNonStringArray(); + void iterateInvalidProxy(); + void applyOnHugeArray(); + + void tostringRecursionCheck(); + void arrayIncludesWithLargeArray(); + void printCircularArray(); + void typedArraySet(); public: Q_INVOKABLE QJSValue throwingCppMethod1(); @@ -4961,6 +4972,177 @@ void tst_QJSEngine::triggerBackwardJumpWithDestructuring() QVERIFY(!value.isError()); } +void tst_QJSEngine::arrayConcatOnSparseArray() +{ + QJSEngine engine; + engine.installExtensions(QJSEngine::GarbageCollectionExtension); + const auto value = engine.evaluate( + "(function() {\n" + " const v4 = [1,2,3];\n" + " const v7 = [4,5];\n" + " v7.length = 1337;\n" + " const v9 = v4.concat(v7);\n" + " gc();\n" + " return v9;\n" + "})();"); + QCOMPARE(value.property("length").toInt(), 1340); + for (int i = 0; i < 5; ++i) + QCOMPARE(value.property(i).toInt(), i + 1); + for (int i = 5; i < 1340; ++i) + QVERIFY(value.property(i).isUndefined()); +} + +void tst_QJSEngine::sortSparseArray() +{ + QJSEngine engine; + engine.installExtensions(QJSEngine::ConsoleExtension); + const auto value = engine.evaluate( + "(function() {\n" + " var sparse = [0];\n" + " sparse = Object.defineProperty(sparse, \"10\", " + " {get: ()=>{return 2}, set: ()=>{return 2}} );\n" + " return Array.prototype.sort.call(sparse, ()=>{});\n" + "})();"); + + QCOMPARE(value.property("length").toInt(), 11); + QVERIFY(value.property(0).isNumber()); + QCOMPARE(value.property(0).toInt(), 0); + QVERIFY(value.property(1).isNumber()); + QCOMPARE(value.property(1).toInt(), 2); + QVERIFY(value.property(10).isUndefined()); +} + +void tst_QJSEngine::compileBrokenRegexp() +{ + QJSEngine engine; + const auto value = engine.evaluate( + "(function() {" + "var ret = new RegExp(Array(4097).join(" + " String.fromCharCode(58)) + Array(4097).join(String.fromCharCode(480)) " + " + Array(65537).join(String.fromCharCode(5307)));" + "return RegExp.prototype.compile.call(ret, 'a','b');" + "})();" + ); + + QVERIFY(value.isError()); + QCOMPARE(value.toString(), "SyntaxError: Invalid flags supplied to RegExp constructor"); +} + +void tst_QJSEngine::tostringRecursionCheck() +{ + QJSEngine engine; + auto value = engine.evaluate(R"js( + var a = {}; + var b = new Array(1337); + function main() { + var ret = a.toLocaleString; + b[1] = ret; + Array = {}; + Object.toString = b[1]; + var ret = String.prototype.lastIndexOf.call({}, b[1]); + var ret = String.prototype.charAt.call(Function, Object); + } + main(); + )js"); + QVERIFY(value.isError()); + QCOMPARE(value.toString(), QLatin1String("RangeError: Maximum call stack size exceeded.")); +} + +void tst_QJSEngine::arrayIncludesWithLargeArray() +{ + QJSEngine engine; + auto value = engine.evaluate(R"js( + let arr = new Array(10000000) + arr.includes(42) + )js"); + QVERIFY(value.isBool()); + QCOMPARE(value.toBool(), false); +} + +void tst_QJSEngine::printCircularArray() +{ + QJSEngine engine; + engine.installExtensions(QJSEngine::ConsoleExtension); + QTest::ignoreMessage(QtMsgType::QtDebugMsg, "[[Circular]]"); + auto value = engine.evaluate(R"js( + let v1 = [] + v1.push(v1) + console.log(v1) + )js"); +} + +void tst_QJSEngine::sortNonStringArray() +{ + QJSEngine engine; + const auto value = engine.evaluate( + "const v4 = [Symbol.iterator, 1];" + "const v5 = v4.sort();" + ); + QVERIFY(value.isError()); + QCOMPARE(value.toString(), "TypeError: Cannot convert a symbol to a string."); +} + +void tst_QJSEngine::iterateInvalidProxy() +{ + QJSEngine engine; + const auto value = engine.evaluate( + "const v1 = new Proxy(Reflect, Reflect);" + "for (const v2 in v1) {}" + "const v3 = { getOwnPropertyDescriptor: eval, getPrototypeOf: eval };" + "const v4 = new Proxy(v3, v3);" + "for (const v5 in v4) {}" + ); + QVERIFY(value.isError()); + QCOMPARE(value.toString(), "TypeError: Type error"); +} + +void tst_QJSEngine::applyOnHugeArray() +{ + QJSEngine engine; + const auto value = engine.evaluate( + "var a = new Array(10);" + "a[536870912] = Function;" + "Function.apply('aaaaaaaa', a);" + ); + QVERIFY(value.isError()); + QCOMPARE(value.toString(), "RangeError: Array too large for apply()."); +} + +void tst_QJSEngine::typedArraySet() +{ + QJSEngine engine; + const auto value = engine.evaluate( + "(function() {" + " var length = 0xffffffe;" + " var offset = 0xfffffff0;" + " var e1;" + " var e2;" + " try {" + " var source1 = new Int8Array(length);" + " var target1 = new Int8Array(length);" + " target1.set(source1, offset);" + " } catch (intError) {" + " e1 = intError;" + " }" + " try {" + " var source2 = new Array(length);" + " var target2 = new Int8Array(length);" + " target2.set(source2, offset);" + " } catch (arrayError) {" + " e2 = arrayError;" + " }" + " return [e1, e2];" + "})();" + ); + + QVERIFY(value.isArray()); + for (int i = 0; i < 2; ++i) { + const auto error = value.property(i); + QVERIFY(error.isError()); + QCOMPARE(error.toString(), "RangeError: TypedArray.set: out of range"); + } +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc" diff --git a/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp b/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp index 37d0ea4dea..95f554776f 100644 --- a/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp +++ b/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp @@ -2708,6 +2708,70 @@ void tst_QJSValue::nestedObjectToVariant() QCOMPARE(o.toVariant(), expected); } +static int instanceCount = 0; + +struct MyType +{ + MyType(int n = 0, const char *t=nullptr): number(n), text(t) + { + ++instanceCount; + } + MyType(const MyType &other) + : number(other.number), text(other.text) + { + ++instanceCount; + } + ~MyType() + { + --instanceCount; + } + int number; + const char *text; +}; + +Q_DECLARE_METATYPE(MyType) +Q_DECLARE_METATYPE(MyType*) + +void tst_QJSValue::jsvalueArrayToSequenceType() +{ + QCOMPARE(instanceCount, 0); + { + QJSEngine eng {}; + auto testObject = eng.newObject(); + testObject.setProperty("test", 42); + testObject.setProperty("mytypeobject", eng.toScriptValue(QVariant::fromValue(MyType {42, "hello"}))); + auto array = eng.newArray(4); + array.setProperty(0, QLatin1String("Hello World")); + array.setProperty(1, 42); + array.setProperty(2, QJSValue(QJSValue::UndefinedValue)); + array.setProperty(3, testObject); + auto asVariant = QVariant::fromValue(array); + QVERIFY(asVariant.canConvert<QVariantList>()); + auto asIterable = asVariant.value<QSequentialIterable>(); + for (auto it = asIterable.begin(); it != asIterable.end(); ++it) { + Q_UNUSED(*it) + } + int i = 0; + for (QVariant myVariant: asIterable) { + QCOMPARE(myVariant.isValid(), i != 2); + ++i; + } + QVERIFY(asIterable.at(2).value<QVariant>().isNull()); + QCOMPARE(asIterable.at(3).value<QVariantMap>().find("mytypeobject")->value<MyType>().number, 42); + QCOMPARE(asIterable.at(0).value<QVariant>().toString(), QLatin1String("Hello World")); + auto it1 = asIterable.begin(); + auto it2 = asIterable.begin(); + QCOMPARE((*it1).value<QVariant>().toString(), (*it2).value<QVariant>().toString()); + QCOMPARE((*it1).value<QVariant>().toString(), QLatin1String("Hello World")); + ++it2; + QCOMPARE((*it1).value<QVariant>().toString(), QLatin1String("Hello World")); + QCOMPARE((*it2).value<QVariant>().toInt(), 42); + } + // tests need to be done after engine has been destroyed, else it will hold a reference until + // the gc decides to collect it + QCOMPARE(instanceCount, 0); +} + void tst_QJSValue::deleteFromDifferentThread() { #if !QT_CONFIG(thread) diff --git a/tests/auto/qml/qjsvalue/tst_qjsvalue.h b/tests/auto/qml/qjsvalue/tst_qjsvalue.h index f704169d43..d85b9a0552 100644 --- a/tests/auto/qml/qjsvalue/tst_qjsvalue.h +++ b/tests/auto/qml/qjsvalue/tst_qjsvalue.h @@ -142,6 +142,8 @@ private slots: void nestedObjectToVariant_data(); void nestedObjectToVariant(); + void jsvalueArrayToSequenceType(); + void deleteFromDifferentThread(); private: diff --git a/tests/auto/qml/qqmlapplicationengine/data/invalid.qml b/tests/auto/qml/qqmlapplicationengine/data/invalid.qml new file mode 100644 index 0000000000..5939a69a7a --- /dev/null +++ b/tests/auto/qml/qqmlapplicationengine/data/invalid.qml @@ -0,0 +1,5 @@ +import QtQml 2.12 + +QtObject { + JUST_SOME_INVALID_PROPERTY: 0 +} diff --git a/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp b/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp index 0f5eea8b95..5e855efe1a 100644 --- a/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp +++ b/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp @@ -30,6 +30,7 @@ #include <QQmlApplicationEngine> #include <QScopedPointer> #include <QSignalSpy> +#include <QRegularExpression> #if QT_CONFIG(process) #include <QProcess> #endif @@ -53,6 +54,7 @@ private slots: void loadTranslation_data(); void loadTranslation(); void setInitialProperties(); + void failureToLoadTriggersWarningSignal(); private: QString buildDir; @@ -293,6 +295,20 @@ void tst_qqmlapplicationengine::setInitialProperties() } } +Q_DECLARE_METATYPE(QList<QQmlError>) // for signalspy below + +void tst_qqmlapplicationengine::failureToLoadTriggersWarningSignal() +{ + auto url = testFileUrl("invalid.qml"); + qRegisterMetaType<QList<QQmlError>>(); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QQmlApplicationEngine failed to load component"); + QTest::ignoreMessage(QtMsgType::QtWarningMsg, QRegularExpression(url.toString() + QLatin1Char('*'))); + QQmlApplicationEngine test; + QSignalSpy warningObserver(&test, &QQmlApplicationEngine::warnings); + test.load(url); + QTRY_COMPARE(warningObserver.count(), 1); +} + QTEST_MAIN(tst_qqmlapplicationengine) #include "tst_qqmlapplicationengine.moc" diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index 46297b3c16..ec0db16114 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -381,6 +381,8 @@ private slots: void semicolonAfterProperty(); void hugeStack(); + void gcCrashRegressionTest(); + private: // static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter); static void verifyContextLifetime(QQmlContextData *ctxt); @@ -9213,6 +9215,53 @@ void tst_qqmlecmascript::hugeStack() QCOMPARE(qvariant_cast<QJSValue>(huge).property(QLatin1String("length")).toInt(), 33059); } +void tst_qqmlecmascript::gcCrashRegressionTest() +{ + const QString qmljs = QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmljs"; + if (!QFile::exists(qmljs)) { + QSKIP("Tets requires qmljs"); + } + QProcess process; + + QTemporaryFile infile; + QVERIFY(infile.open()); + infile.write(R"js( + function i_want_to_break_free() { + var n = 400; + var m = 10; + var regex = new RegExp("(ab)".repeat(n), "g"); // g flag to trigger the vulnerable path + var part = "ab".repeat(n); // matches have to be at least size 2 to prevent interning + var s = (part + "|").repeat(m); + var cnt = 0; + var ary = []; + s.replace(regex, function() { + for (var i = 1; i < arguments.length-2; ++i) { + if (typeof arguments[i] !== 'string') { + i_am_free = arguments[i]; + throw "success"; + } + ary[cnt++] = arguments[i]; // root everything to force GC + } + return "x"; + }); + } + try { i_want_to_break_free(); } catch (e) {console.log("hi") } + console.log(typeof(i_am_free)); // will print "object" + )js"); + infile.close(); + + QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); + environment.insert("QV4_GC_MAX_STACK_SIZE", "32768"); + + process.setProcessEnvironment(environment); + process.start(qmljs, QStringList({infile.fileName()})); + QVERIFY(process.waitForStarted()); + const qint64 pid = process.processId(); + QVERIFY(pid != 0); + QVERIFY(process.waitForFinished()); + QCOMPARE(process.exitCode(), 0); +} + QTEST_MAIN(tst_qqmlecmascript) #include "tst_qqmlecmascript.moc" diff --git a/tests/auto/qml/qquickworkerscript/data/doRequest.mjs b/tests/auto/qml/qquickworkerscript/data/doRequest.mjs new file mode 100644 index 0000000000..d607c3400d --- /dev/null +++ b/tests/auto/qml/qquickworkerscript/data/doRequest.mjs @@ -0,0 +1,6 @@ +WorkerScript.onMessage = function(message) +{ + var req = new XMLHttpRequest(); + req.open("GET", message.url, true); + req.send(); +}; diff --git a/tests/auto/qml/qquickworkerscript/data/xmlHttpRequest.qml b/tests/auto/qml/qquickworkerscript/data/xmlHttpRequest.qml new file mode 100644 index 0000000000..42136d78f0 --- /dev/null +++ b/tests/auto/qml/qquickworkerscript/data/xmlHttpRequest.qml @@ -0,0 +1,16 @@ +import QtQuick 2.14 + +Rectangle +{ + width: 100 + height: 100 + + WorkerScript + { + source: "doRequest.mjs" + Component.onCompleted: + { + sendMessage({"url": "https://example.com"}); + } + } +} diff --git a/tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp b/tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp index bb4c9a7c1e..2f79f7157f 100644 --- a/tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp +++ b/tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp @@ -60,6 +60,7 @@ private slots: void script_function(); void script_var(); void stressDispose(); + void xmlHttpRequest(); private: void waitForEchoMessage(QQuickWorkerScript *worker) { @@ -359,6 +360,13 @@ void tst_QQuickWorkerScript::stressDispose() } } +void tst_QQuickWorkerScript::xmlHttpRequest() +{ + QQmlComponent component(&m_engine, testFileUrl("xmlHttpRequest.qml")); + QScopedPointer<QObject> root{component.create()}; // should not crash + QVERIFY(root); +} + QTEST_MAIN(tst_QQuickWorkerScript) #include "tst_qquickworkerscript.moc" |