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/qml | |
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/qml')
9 files changed, 348 insertions, 0 deletions
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" |