diff options
Diffstat (limited to 'tests/auto/qscriptengine/tst_qscriptengine.cpp')
-rw-r--r-- | tests/auto/qscriptengine/tst_qscriptengine.cpp | 907 |
1 files changed, 563 insertions, 344 deletions
diff --git a/tests/auto/qscriptengine/tst_qscriptengine.cpp b/tests/auto/qscriptengine/tst_qscriptengine.cpp index c4c53e9..2d7feee 100644 --- a/tests/auto/qscriptengine/tst_qscriptengine.cpp +++ b/tests/auto/qscriptengine/tst_qscriptengine.cpp @@ -93,7 +93,8 @@ private slots: void constructWithParent(); void currentContext(); void pushPopContext(); - void getSetDefaultPrototype(); + void getSetDefaultPrototype_int(); + void getSetDefaultPrototype_customType(); void newFunction(); void newFunctionWithArg(); void newFunctionWithProto(); @@ -109,7 +110,9 @@ private slots: void newVariant_promoteNonObject(); void newVariant_promoteNonQScriptObject(); void newRegExp(); + void jsRegExp(); void newDate(); + void jsParseDate(); void newQObject(); void newQObject_ownership(); void newQObject_promoteObject(); @@ -121,6 +124,7 @@ private slots: void newActivationObject(); void getSetGlobalObject(); void globalObjectProperties(); + void globalObjectProperties_enumerate(); void createGlobalObjectProperty(); void globalObjectGetterSetterProperty(); void customGlobalObjectWithPrototype(); @@ -136,7 +140,14 @@ private slots: void nestedEvaluate(); void uncaughtException(); void errorMessage_QT679(); - void valueConversion(); + void valueConversion_basic(); + void valueConversion_customType(); + void valueConversion_sequence(); + void valueConversion_QVariant(); + void valueConversion_hooliganTask248802(); + void valueConversion_basic2(); + void valueConversion_dateTime(); + void valueConversion_regExp(); void qScriptValueFromValue_noEngine(); void importExtension(); void infiniteRecursion(); @@ -152,32 +163,51 @@ private slots: void numberParsing_data(); void numberParsing(); void automaticSemicolonInsertion(); + void abortEvaluation_notEvaluating(); void abortEvaluation(); + void abortEvaluation_tryCatch(); + void abortEvaluation_fromNative(); void abortEvaluation_QTBUG9433(); - void isEvaluating(); + void isEvaluating_notEvaluating(); + void isEvaluating_fromNative(); + void isEvaluating_fromEvent(); void printFunctionWithCustomHandler(); void printThrowsException(); void errorConstructors(); - void argumentsProperty(); - void numberClass(); - void forInStatement(); - void functionExpression(); + void argumentsProperty_globalContext(); + void argumentsProperty_JS(); + void argumentsProperty_evaluateInNativeFunction(); + void jsNumberClass(); + void jsForInStatement_simple(); + void jsForInStatement_prototypeProperties(); + void jsForInStatement_mutateWhileIterating(); + void jsForInStatement_arrays(); + void jsForInStatement_nullAndUndefined(); + void jsFunctionDeclarationAsStatement(); void stringObjects(); + void jsStringPrototypeReplaceBugs(); void getterSetterThisObject_global(); void getterSetterThisObject_plain(); void getterSetterThisObject_prototypeChain(); void getterSetterThisObject_activation(); - void continueInSwitch(); - void readOnlyPrototypeProperty(); + void jsContinueInSwitch(); + void jsShadowReadOnlyPrototypeProperty(); void toObject(); - void reservedWords_data(); - void reservedWords(); - void futureReservedWords_data(); - void futureReservedWords(); - void throwInsideWithStatement(); - void getSetAgent(); - void reentrancy(); - void incDecNonObjectProperty(); + void jsReservedWords_data(); + void jsReservedWords(); + void jsFutureReservedWords_data(); + void jsFutureReservedWords(); + void jsThrowInsideWithStatement(); + void getSetAgent_ownership(); + void getSetAgent_deleteAgent(); + void getSetAgent_differentEngine(); + void reentrancy_stringHandles(); + void reentrancy_processEventsInterval(); + void reentrancy_typeConversion(); + void reentrancy_globalObjectProperties(); + void reentrancy_Array(); + void reentrancy_objectCreation(); + void jsIncDecNonObjectProperty(); void installTranslatorFunctions_data(); void installTranslatorFunctions(); void translateScript_data(); @@ -195,6 +225,7 @@ private slots: void translateScriptUnicode(); void translateScriptUnicodeIdBased_data(); void translateScriptUnicodeIdBased(); + void translateFromBuiltinCallback(); void functionScopes(); void nativeFunctionScopes(); void evaluateProgram(); @@ -314,8 +345,8 @@ void tst_QScriptEngine::newFunction() QScriptValue prot = fun.property("prototype", QScriptValue::ResolveLocal); QVERIFY(prot.isObject()); QVERIFY(prot.property("constructor").strictlyEquals(fun)); - QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable); - QCOMPARE(prot.propertyFlags("constructor"), QScriptValue::Undeletable | QScriptValue::SkipInEnumeration); + QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable | QScriptValue::SkipInEnumeration); + QCOMPARE(prot.propertyFlags("constructor"), QScriptValue::SkipInEnumeration); } // prototype should be Function.prototype QCOMPARE(fun.prototype().isValid(), true); @@ -339,8 +370,8 @@ void tst_QScriptEngine::newFunctionWithArg() QScriptValue prot = fun.property("prototype", QScriptValue::ResolveLocal); QVERIFY(prot.isObject()); QVERIFY(prot.property("constructor").strictlyEquals(fun)); - QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable); - QCOMPARE(prot.propertyFlags("constructor"), QScriptValue::Undeletable | QScriptValue::SkipInEnumeration); + QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable | QScriptValue::SkipInEnumeration); + QCOMPARE(prot.propertyFlags("constructor"), QScriptValue::SkipInEnumeration); } // prototype should be Function.prototype QCOMPARE(fun.prototype().isValid(), true); @@ -367,10 +398,9 @@ void tst_QScriptEngine::newFunctionWithProto() QCOMPARE(fun.prototype().strictlyEquals(eng.evaluate("Function.prototype")), true); // public prototype should be the one we passed QCOMPARE(fun.property("prototype").strictlyEquals(proto), true); - QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable); + QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable | QScriptValue::SkipInEnumeration); QCOMPARE(proto.property("constructor").strictlyEquals(fun), true); - QCOMPARE(proto.propertyFlags("constructor"), - QScriptValue::Undeletable | QScriptValue::SkipInEnumeration); + QCOMPARE(proto.propertyFlags("constructor"), QScriptValue::SkipInEnumeration); QCOMPARE(fun.call().isNull(), true); QCOMPARE(fun.construct().isObject(), true); @@ -440,10 +470,10 @@ void tst_QScriptEngine::newArray_HooliganTask233836() { QScriptEngine eng; { + // According to ECMA-262, this should cause a RangeError. QScriptValue ret = eng.evaluate("a = new Array(4294967295); a.push('foo')"); - QVERIFY(ret.isNumber()); - QCOMPARE(ret.toInt32(), 0); - QCOMPARE(eng.evaluate("a[4294967295]").toString(), QString::fromLatin1("foo")); + QEXPECT_FAIL("", "ECMA compliance bug in Array.prototype.push: https://bugs.webkit.org/show_bug.cgi?id=55033", Continue); + QVERIFY(ret.isError() && ret.toString().contains(QLatin1String("RangeError"))); } { QScriptValue ret = eng.newArray(0xFFFFFFFF); @@ -609,54 +639,66 @@ void tst_QScriptEngine::newRegExp() QCOMPARE(rexp.toRegExp().pattern(), QRegExp("foo").pattern()); } - { - QScriptValue r = eng.evaluate("/foo/gim"); - QVERIFY(r.isRegExp()); - QCOMPARE(r.toString(), QString::fromLatin1("/foo/gim")); +} - QScriptValue rxCtor = eng.globalObject().property("RegExp"); - QScriptValue r2 = rxCtor.call(QScriptValue(), QScriptValueList() << r); - QVERIFY(r2.isRegExp()); - QVERIFY(r2.strictlyEquals(r)); +void tst_QScriptEngine::jsRegExp() +{ + // See ECMA-262 Section 15.10, "RegExp Objects". + // These should really be JS-only tests, as they test the implementation's + // ECMA-compliance, not the C++ API. Compliance should already be covered + // by the Mozilla tests (qscriptjstestsuite). + // We can consider updating the expected results of this test if the + // RegExp implementation changes. + + QScriptEngine eng; + QScriptValue r = eng.evaluate("/foo/gim"); + QVERIFY(r.isRegExp()); + QCOMPARE(r.toString(), QString::fromLatin1("/foo/gim")); - QScriptValue r3 = rxCtor.call(QScriptValue(), QScriptValueList() << r << "gim"); - QVERIFY(r3.isError()); - QCOMPARE(r3.toString(), QString::fromLatin1("TypeError: Cannot supply flags when constructing one RegExp from another.")); + QScriptValue rxCtor = eng.globalObject().property("RegExp"); + QScriptValue r2 = rxCtor.call(QScriptValue(), QScriptValueList() << r); + QVERIFY(r2.isRegExp()); + QVERIFY(r2.strictlyEquals(r)); - QScriptValue r4 = rxCtor.call(QScriptValue(), QScriptValueList() << "foo" << "gim"); - QVERIFY(r4.isRegExp()); + QScriptValue r3 = rxCtor.call(QScriptValue(), QScriptValueList() << r << "gim"); + QVERIFY(r3.isError()); + QVERIFY(r3.toString().contains(QString::fromLatin1("TypeError"))); // Cannot supply flags when constructing one RegExp from another - QScriptValue r5 = rxCtor.construct(QScriptValueList() << r); - QVERIFY(r5.isRegExp()); - QCOMPARE(r5.toString(), QString::fromLatin1("/foo/gim")); - // In JSC, constructing a RegExp from another produces the same identical object. - // This is different from SpiderMonkey and old back-end. - QVERIFY(r5.strictlyEquals(r)); + QScriptValue r4 = rxCtor.call(QScriptValue(), QScriptValueList() << "foo" << "gim"); + QVERIFY(r4.isRegExp()); - QScriptValue r6 = rxCtor.construct(QScriptValueList() << "foo" << "bar"); - QVERIFY(r6.isError()); - QCOMPARE(r6.toString(), QString::fromLatin1("SyntaxError: Invalid regular expression: invalid regular expression flag")); + QScriptValue r5 = rxCtor.construct(QScriptValueList() << r); + QVERIFY(r5.isRegExp()); + QCOMPARE(r5.toString(), QString::fromLatin1("/foo/gim")); + // In JSC, constructing a RegExp from another produces the same identical object. + // This is different from SpiderMonkey and old back-end. + QEXPECT_FAIL("", "RegExp copy-constructor should return a new object: https://bugs.webkit.org/show_bug.cgi?id=55040", Continue); + QVERIFY(!r5.strictlyEquals(r)); - QScriptValue r7 = eng.evaluate("/foo/gimp"); - QVERIFY(r7.isError()); - QCOMPARE(r7.toString(), QString::fromLatin1("SyntaxError: Invalid regular expression: invalid regular expression flag")); + QScriptValue r6 = rxCtor.construct(QScriptValueList() << "foo" << "bar"); + QVERIFY(r6.isError()); + QVERIFY(r6.toString().contains(QString::fromLatin1("SyntaxError"))); // Invalid regular expression flag - QScriptValue r8 = eng.evaluate("/foo/migmigmig"); - QVERIFY(r8.isRegExp()); - QCOMPARE(r8.toString(), QString::fromLatin1("/foo/gim")); + QScriptValue r7 = eng.evaluate("/foo/gimp"); + QVERIFY(r7.isError()); + QVERIFY(r7.toString().contains(QString::fromLatin1("SyntaxError"))); // Invalid regular expression flag - QScriptValue r9 = rxCtor.construct(); - QVERIFY(r9.isRegExp()); - QCOMPARE(r9.toString(), QString::fromLatin1("/(?:)/")); + // JSC doesn't complain about duplicate flags. + QScriptValue r8 = eng.evaluate("/foo/migmigmig"); + QVERIFY(r8.isRegExp()); + QCOMPARE(r8.toString(), QString::fromLatin1("/foo/gim")); - QScriptValue r10 = rxCtor.construct(QScriptValueList() << "" << "gim"); - QVERIFY(r10.isRegExp()); - QCOMPARE(r10.toString(), QString::fromLatin1("/(?:)/gim")); + QScriptValue r9 = rxCtor.construct(); + QVERIFY(r9.isRegExp()); + QCOMPARE(r9.toString(), QString::fromLatin1("/(?:)/")); - QScriptValue r11 = rxCtor.construct(QScriptValueList() << "{1.*}" << "g"); - QVERIFY(r11.isRegExp()); - QCOMPARE(r11.toString(), QString::fromLatin1("/{1.*}/g")); - } + QScriptValue r10 = rxCtor.construct(QScriptValueList() << "" << "gim"); + QVERIFY(r10.isRegExp()); + QCOMPARE(r10.toString(), QString::fromLatin1("/(?:)/gim")); + + QScriptValue r11 = rxCtor.construct(QScriptValueList() << "{1.*}" << "g"); + QVERIFY(r11.isRegExp()); + QCOMPARE(r11.toString(), QString::fromLatin1("/{1.*}/g")); } void tst_QScriptEngine::newDate() @@ -695,7 +737,11 @@ void tst_QScriptEngine::newDate() // toDateTime() result should be in local time QCOMPARE(date.toDateTime(), dt.toLocalTime()); } +} +void tst_QScriptEngine::jsParseDate() +{ + QScriptEngine eng; // Date.parse() should return NaN when it fails { QScriptValue ret = eng.evaluate("Date.parse()"); @@ -1196,6 +1242,8 @@ static QScriptValue getSetFoo(QScriptContext *ctx, QScriptEngine *) void tst_QScriptEngine::globalObjectProperties() { + // See ECMA-262 Section 15.1, "The Global Object". + QScriptEngine eng; QScriptValue global = eng.globalObject(); @@ -1269,10 +1317,15 @@ void tst_QScriptEngine::globalObjectProperties() QCOMPARE(global.propertyFlags("URIError"), QScriptValue::SkipInEnumeration); QVERIFY(global.property("Math").isObject()); QVERIFY(!global.property("Math").isFunction()); - QEXPECT_FAIL("", "[ECMA compliance] JSC sets DontDelete flag for Math object", Continue); + QEXPECT_FAIL("", "[ECMA compliance] JSC sets DontDelete flag for Math object: https://bugs.webkit.org/show_bug.cgi?id=55034", Continue); QCOMPARE(global.propertyFlags("Math"), QScriptValue::SkipInEnumeration); +} + +void tst_QScriptEngine::globalObjectProperties_enumerate() +{ + QScriptEngine eng; + QScriptValue global = eng.globalObject(); - // enumeration QSet<QString> expectedNames; expectedNames << "isNaN" @@ -1500,7 +1553,7 @@ void tst_QScriptEngine::globalObjectWithCustomPrototype() { QScriptValue ret = engine.evaluate("this.__proto__ = { 'a': 123 }; a"); QVERIFY(ret.isNumber()); - QEXPECT_FAIL("", "QTBUG-9737", Continue); + QEXPECT_FAIL("", "QTBUG-9737: Prototype change in JS not reflected on C++ side", Continue); QVERIFY(ret.strictlyEquals(global.property("a"))); } } @@ -1510,7 +1563,9 @@ void tst_QScriptEngine::builtinFunctionNames_data() QTest::addColumn<QString>("expression"); QTest::addColumn<QString>("expectedName"); - QTest::newRow("print") << QString("print") << QString("print"); + // See ECMA-262 Chapter 15, "Standard Built-in ECMAScript Objects". + + QTest::newRow("print") << QString("print") << QString("print"); // QtScript extension. QTest::newRow("parseInt") << QString("parseInt") << QString("parseInt"); QTest::newRow("parseFloat") << QString("parseFloat") << QString("parseFloat"); QTest::newRow("isNaN") << QString("isNaN") << QString("isNaN"); @@ -1521,8 +1576,8 @@ void tst_QScriptEngine::builtinFunctionNames_data() QTest::newRow("encodeURIComponent") << QString("encodeURIComponent") << QString("encodeURIComponent"); QTest::newRow("escape") << QString("escape") << QString("escape"); QTest::newRow("unescape") << QString("unescape") << QString("unescape"); - QTest::newRow("version") << QString("version") << QString("version"); - QTest::newRow("gc") << QString("gc") << QString("gc"); + QTest::newRow("version") << QString("version") << QString("version"); // QtScript extension. + QTest::newRow("gc") << QString("gc") << QString("gc"); // QtScript extension. QTest::newRow("Array") << QString("Array") << QString("Array"); QTest::newRow("Array.prototype.toString") << QString("Array.prototype.toString") << QString("toString"); @@ -1673,6 +1728,7 @@ void tst_QScriptEngine::builtinFunctionNames() QFETCH(QString, expression); QFETCH(QString, expectedName); QScriptEngine eng; + // The "name" property is actually non-standard, but JSC supports it. QScriptValue ret = eng.evaluate(QString::fromLatin1("%0.name").arg(expression)); QVERIFY(ret.isString()); QCOMPARE(ret.toString(), expectedName); @@ -1894,22 +1950,24 @@ static QScriptValue eval_nested(QScriptContext *ctx, QScriptEngine *eng) return result; } +// Tests that nested evaluate uses the "this" that was passed. void tst_QScriptEngine::nestedEvaluate() { QScriptEngine eng; QScriptValue fun = eng.newFunction(eval_nested); eng.globalObject().setProperty("fun", fun); + // From JS function call { QScriptValue result = eng.evaluate("o = { id:'foo'}; o.fun = fun; o.fun()"); QCOMPARE(result.property("local_bar").toString(), QString("local")); QCOMPARE(result.property("thisObjectIdBefore").toString(), QString("foo")); QCOMPARE(result.property("thisObjectIdAfter").toString(), QString("foo")); QCOMPARE(result.property("evaluatedThisObjectId").toString(), QString("foo")); - QScriptValue bar = eng.evaluate("bar"); + QScriptValue bar = eng.evaluate("bar"); // Was introduced in local scope. QVERIFY(bar.isError()); - QCOMPARE(bar.toString(), QString::fromLatin1("ReferenceError: Can't find variable: bar")); + QVERIFY(bar.toString().contains(QString::fromLatin1("ReferenceError"))); } - + // From QScriptValue::call() { QScriptValue result = fun.call(eng.evaluate("p = { id:'foo' }") , QScriptValueList() ); QCOMPARE(result.property("local_bar").toString(), QString("local")); @@ -1918,7 +1976,7 @@ void tst_QScriptEngine::nestedEvaluate() QCOMPARE(result.property("evaluatedThisObjectId").toString(), QString("foo")); QScriptValue bar = eng.evaluate("bar"); QVERIFY(bar.isError()); - QCOMPARE(bar.toString(), QString::fromLatin1("ReferenceError: Can't find variable: bar")); + QVERIFY(bar.toString().contains(QString::fromLatin1("ReferenceError"))); } } @@ -1985,6 +2043,7 @@ void tst_QScriptEngine::errorMessage_QT679() engine.globalObject().setProperty("foo", 15); QScriptValue error = engine.evaluate("'hello world';\nfoo.bar.blah"); QVERIFY(error.isError()); + // The exact message is back-end specific and subject to change. QCOMPARE(error.toString(), QString::fromLatin1("TypeError: Result of expression 'foo.bar' [undefined] is not an object.")); } @@ -1997,37 +2056,40 @@ public: Q_DECLARE_METATYPE(Foo) Q_DECLARE_METATYPE(Foo*) -void tst_QScriptEngine::getSetDefaultPrototype() +void tst_QScriptEngine::getSetDefaultPrototype_int() { QScriptEngine eng; - { - QScriptValue object = eng.newObject(); - QCOMPARE(eng.defaultPrototype(qMetaTypeId<int>()).isValid(), false); - eng.setDefaultPrototype(qMetaTypeId<int>(), object); - QCOMPARE(eng.defaultPrototype(qMetaTypeId<int>()).strictlyEquals(object), true); - QScriptValue value = eng.newVariant(int(123)); - QCOMPARE(value.prototype().isObject(), true); - QCOMPARE(value.prototype().strictlyEquals(object), true); - eng.setDefaultPrototype(qMetaTypeId<int>(), QScriptValue()); - QCOMPARE(eng.defaultPrototype(qMetaTypeId<int>()).isValid(), false); - QScriptValue value2 = eng.newVariant(int(123)); - QCOMPARE(value2.prototype().strictlyEquals(object), false); - } - { - QScriptValue object = eng.newObject(); - QCOMPARE(eng.defaultPrototype(qMetaTypeId<Foo>()).isValid(), false); - eng.setDefaultPrototype(qMetaTypeId<Foo>(), object); - QCOMPARE(eng.defaultPrototype(qMetaTypeId<Foo>()).strictlyEquals(object), true); - QScriptValue value = eng.newVariant(qVariantFromValue(Foo())); - QCOMPARE(value.prototype().isObject(), true); - QCOMPARE(value.prototype().strictlyEquals(object), true); + QScriptValue object = eng.newObject(); + QCOMPARE(eng.defaultPrototype(qMetaTypeId<int>()).isValid(), false); + eng.setDefaultPrototype(qMetaTypeId<int>(), object); + QCOMPARE(eng.defaultPrototype(qMetaTypeId<int>()).strictlyEquals(object), true); + QScriptValue value = eng.newVariant(int(123)); + QCOMPARE(value.prototype().isObject(), true); + QCOMPARE(value.prototype().strictlyEquals(object), true); - eng.setDefaultPrototype(qMetaTypeId<Foo>(), QScriptValue()); - QCOMPARE(eng.defaultPrototype(qMetaTypeId<Foo>()).isValid(), false); - QScriptValue value2 = eng.newVariant(qVariantFromValue(Foo())); - QCOMPARE(value2.prototype().strictlyEquals(object), false); - } + eng.setDefaultPrototype(qMetaTypeId<int>(), QScriptValue()); + QCOMPARE(eng.defaultPrototype(qMetaTypeId<int>()).isValid(), false); + QScriptValue value2 = eng.newVariant(int(123)); + QCOMPARE(value2.prototype().strictlyEquals(object), false); +} + +void tst_QScriptEngine::getSetDefaultPrototype_customType() +{ + QScriptEngine eng; + + QScriptValue object = eng.newObject(); + QCOMPARE(eng.defaultPrototype(qMetaTypeId<Foo>()).isValid(), false); + eng.setDefaultPrototype(qMetaTypeId<Foo>(), object); + QCOMPARE(eng.defaultPrototype(qMetaTypeId<Foo>()).strictlyEquals(object), true); + QScriptValue value = eng.newVariant(qVariantFromValue(Foo())); + QCOMPARE(value.prototype().isObject(), true); + QCOMPARE(value.prototype().strictlyEquals(object), true); + + eng.setDefaultPrototype(qMetaTypeId<Foo>(), QScriptValue()); + QCOMPARE(eng.defaultPrototype(qMetaTypeId<Foo>()).isValid(), false); + QScriptValue value2 = eng.newVariant(qVariantFromValue(Foo())); + QCOMPARE(value2.prototype().strictlyEquals(object), false); } static QScriptValue fooToScriptValue(QScriptEngine *eng, const Foo &foo) @@ -2061,7 +2123,7 @@ Q_DECLARE_METATYPE(QStack<int>) Q_DECLARE_METATYPE(QQueue<char>) Q_DECLARE_METATYPE(QLinkedList<QStack<int> >) -void tst_QScriptEngine::valueConversion() +void tst_QScriptEngine::valueConversion_basic() { QScriptEngine eng; { @@ -2123,7 +2185,11 @@ void tst_QScriptEngine::valueConversion() QCOMPARE(qScriptValueToValue<QChar>(code), c); QCOMPARE(qScriptValueToValue<QChar>(qScriptValueFromValue(&eng, c)), c); } +} +void tst_QScriptEngine::valueConversion_customType() +{ + QScriptEngine eng; { // a type that we don't have built-in conversion of // (it's stored as a variant) @@ -2169,7 +2235,11 @@ void tst_QScriptEngine::valueConversion() QVERIFY(fooVal2.property("x").strictlyEquals(QScriptValue(&eng, 56))); QVERIFY(fooVal2.property("y").strictlyEquals(QScriptValue(&eng, 78))); } +} +void tst_QScriptEngine::valueConversion_sequence() +{ + QScriptEngine eng; qScriptRegisterSequenceMetaType<QLinkedList<QString> >(&eng); { @@ -2248,7 +2318,11 @@ void tst_QScriptEngine::valueConversion() QCOMPARE(qscriptvalue_cast<QObjectList>(val), lst); } +} +void tst_QScriptEngine::valueConversion_QVariant() +{ + QScriptEngine eng; // qScriptValueFromValue() should be "smart" when the argument is a QVariant { QScriptValue val = qScriptValueFromValue(&eng, QVariant()); @@ -2325,7 +2399,12 @@ void tst_QScriptEngine::valueConversion() QCOMPARE(val.toVariant(), var); } - // task 248802 + QCOMPARE(qscriptvalue_cast<QVariant>(QScriptValue(123)), QVariant(123)); +} + +void tst_QScriptEngine::valueConversion_hooliganTask248802() +{ + QScriptEngine eng; qScriptRegisterMetaType<Foo>(&eng, fooToScriptValueV2, fooFromScriptValueV2); { QScriptValue num(&eng, 123); @@ -2343,6 +2422,11 @@ void tst_QScriptEngine::valueConversion() QCOMPARE(foo.x, 123); } +} + +void tst_QScriptEngine::valueConversion_basic2() +{ + QScriptEngine eng; // more built-in types { QScriptValue val = qScriptValueFromValue(&eng, uint(123)); @@ -2379,6 +2463,11 @@ void tst_QScriptEngine::valueConversion() QVERIFY(val.isNumber()); QCOMPARE(val.toInt32(), 123); } +} + +void tst_QScriptEngine::valueConversion_dateTime() +{ + QScriptEngine eng; { QDateTime in = QDateTime::currentDateTime(); QScriptValue val = qScriptValueFromValue(&eng, in); @@ -2391,6 +2480,11 @@ void tst_QScriptEngine::valueConversion() QVERIFY(val.isDate()); QCOMPARE(val.toDateTime().date(), in); } +} + +void tst_QScriptEngine::valueConversion_regExp() +{ + QScriptEngine eng; { QRegExp in = QRegExp("foo"); QScriptValue val = qScriptValueFromValue(&eng, in); @@ -2416,8 +2510,6 @@ void tst_QScriptEngine::valueConversion() QEXPECT_FAIL("", "QTBUG-6136: JSC-based back-end doesn't preserve QRegExp::minimal (always false)", Continue); QCOMPARE(val.toRegExp().isMinimal(), in.isMinimal()); } - - QCOMPARE(qscriptvalue_cast<QVariant>(QScriptValue(123)), QVariant(123)); } void tst_QScriptEngine::qScriptValueFromValue_noEngine() @@ -2522,7 +2614,7 @@ void tst_QScriptEngine::importExtension() QEXPECT_FAIL("", "JSC throws syntax error eagerly", Continue); QCOMPARE(eng.uncaughtExceptionLineNumber(), 4); QVERIFY(ret.isError()); - QCOMPARE(ret.property("message").toString(), QLatin1String("Parse error")); + QVERIFY(ret.toString().contains(QLatin1String("SyntaxError"))); } QStringList imp = eng.importedExtensions(); QCOMPARE(imp.size(), 2); @@ -2548,6 +2640,9 @@ static QScriptValue recurse2(QScriptContext *ctx, QScriptEngine *eng) void tst_QScriptEngine::infiniteRecursion() { + // Infinite recursion in JS should cause the VM to throw an error + // when the JS stack is exhausted. + // The exact error is back-end specific and subject to change. const QString stackOverflowError = QString::fromLatin1("RangeError: Maximum call stack size exceeded."); QScriptEngine eng; { @@ -2610,6 +2705,8 @@ void tst_QScriptEngine::castWithPrototypeChain() baz2.b = 456; QScriptValue baz2Value = qScriptValueFromValue(&eng, &baz2); { + // qscriptvalue_cast() does magic; if the QScriptValue contains + // t of type T, and the target type is T*, &t is returned. Baz *pbaz = qscriptvalue_cast<Baz*>(baz2Value); QVERIFY(pbaz != 0); QCOMPARE(pbaz->b, baz2.b); @@ -2632,6 +2729,13 @@ void tst_QScriptEngine::castWithPrototypeChain() } // establish chain -- now casting should work + // Why? because qscriptvalue_cast() does magic again. + // It the instance itself is not of type T, qscriptvalue_cast() + // searches the prototype chain for T, and if it finds one, it infers + // that the instance can also be casted to that type. This cast is + // _not_ safe and thus relies on the developer doing the right thing. + // This is an undocumented feature to enable qscriptvalue_cast() to + // be used by prototype functions to cast the JS this-object to C++. bazProto.setPrototype(barProto); { @@ -2740,6 +2844,9 @@ void tst_QScriptEngine::collectGarbage() void tst_QScriptEngine::reportAdditionalMemoryCost() { QScriptEngine eng; + // There isn't any reliable way to test whether calling + // this function affects garbage collection responsiveness; + // the best we can do is call it with a few different values. for (int x = 0; x < 1000; ++x) { eng.reportAdditionalMemoryCost(0); eng.reportAdditionalMemoryCost(10); @@ -2757,6 +2864,8 @@ void tst_QScriptEngine::reportAdditionalMemoryCost() void tst_QScriptEngine::gcWithNestedDataStructure() { + // The GC must be able to traverse deeply nested objects, otherwise this + // test would crash. QScriptEngine eng; eng.evaluate( "function makeList(size)" @@ -2778,6 +2887,7 @@ void tst_QScriptEngine::gcWithNestedDataStructure() if (x == 1) eng.evaluate("gc()"); QScriptValue l = head; + // Make sure all the nodes are still alive. for (int i = 0; i < 200; ++i) { QCOMPARE(l.property("data").toString(), QString::number(i)); l = l.property("next"); @@ -2807,6 +2917,8 @@ void tst_QScriptEngine::processEventsWhileRunning() if (x == 0) eng.pushContext(); + // This is running for a silly amount of time just to make sure + // the script doesn't finish before event processing is triggered. QString script = QString::fromLatin1( "var end = Number(new Date()) + 2000;" "var x = 0;" @@ -3012,13 +3124,15 @@ void tst_QScriptEngine::numberParsing() } // see ECMA-262, section 7.9 +// This is testing ECMA compliance, not our C++ API, but it's important that +// the back-end is conformant in this regard. void tst_QScriptEngine::automaticSemicolonInsertion() { QScriptEngine eng; { QScriptValue ret = eng.evaluate("{ 1 2 } 3"); QVERIFY(ret.isError()); - QCOMPARE(ret.toString(), QString::fromLatin1("SyntaxError: Parse error")); + QVERIFY(ret.toString().contains("SyntaxError")); } { QScriptValue ret = eng.evaluate("{ 1\n2 } 3"); @@ -3028,7 +3142,7 @@ void tst_QScriptEngine::automaticSemicolonInsertion() { QScriptValue ret = eng.evaluate("for (a; b\n)"); QVERIFY(ret.isError()); - QCOMPARE(ret.toString(), QString::fromLatin1("SyntaxError: Parse error")); + QVERIFY(ret.toString().contains("SyntaxError")); } { QScriptValue ret = eng.evaluate("(function() { return\n1 + 2 })()"); @@ -3043,7 +3157,7 @@ void tst_QScriptEngine::automaticSemicolonInsertion() { QScriptValue ret = eng.evaluate("if (a > b)\nelse c = d"); QVERIFY(ret.isError()); - QCOMPARE(ret.toString(), QString::fromLatin1("SyntaxError: Parse error")); + QVERIFY(ret.toString().contains("SyntaxError")); } { eng.evaluate("function c() { return { foo: function() { return 5; } } }"); @@ -3055,7 +3169,7 @@ void tst_QScriptEngine::automaticSemicolonInsertion() { QScriptValue ret = eng.evaluate("throw\n1"); QVERIFY(ret.isError()); - QCOMPARE(ret.toString(), QString::fromLatin1("SyntaxError: Parse error")); + QVERIFY(ret.toString().contains("SyntaxError")); } { QScriptValue ret = eng.evaluate("a = Number(1)\n++a"); @@ -3257,7 +3371,7 @@ static QScriptValue myFunctionAbortingEvaluation(QScriptContext *, QScriptEngine return eng->nullValue(); // should be ignored } -void tst_QScriptEngine::abortEvaluation() +void tst_QScriptEngine::abortEvaluation_notEvaluating() { QScriptEngine eng; @@ -3270,7 +3384,11 @@ void tst_QScriptEngine::abortEvaluation() QVERIFY(ret.isString()); QCOMPARE(ret.toString(), QString::fromLatin1("ciao")); } +} +void tst_QScriptEngine::abortEvaluation() +{ + QScriptEngine eng; EventReceiver3 receiver(&eng); eng.setProcessEventsInterval(100); @@ -3301,6 +3419,13 @@ void tst_QScriptEngine::abortEvaluation() } } +} + +void tst_QScriptEngine::abortEvaluation_tryCatch() +{ + QScriptEngine eng; + EventReceiver3 receiver(&eng); + eng.setProcessEventsInterval(100); // scripts cannot intercept the abortion with try/catch for (int y = 0; y < 4; ++y) { QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1))); @@ -3334,13 +3459,15 @@ void tst_QScriptEngine::abortEvaluation() break; } } +} - { - QScriptValue fun = eng.newFunction(myFunctionAbortingEvaluation); - eng.globalObject().setProperty("myFunctionAbortingEvaluation", fun); - QScriptValue ret = eng.evaluate("myFunctionAbortingEvaluation()"); - QVERIFY(!ret.isValid()); - } +void tst_QScriptEngine::abortEvaluation_fromNative() +{ + QScriptEngine eng; + QScriptValue fun = eng.newFunction(myFunctionAbortingEvaluation); + eng.globalObject().setProperty("myFunctionAbortingEvaluation", fun); + QScriptValue ret = eng.evaluate("myFunctionAbortingEvaluation()"); + QVERIFY(!ret.isValid()); } class ThreadedEngine : public QThread { @@ -3407,7 +3534,7 @@ public: bool wasEvaluating; }; -void tst_QScriptEngine::isEvaluating() +void tst_QScriptEngine::isEvaluating_notEvaluating() { QScriptEngine eng; @@ -3419,30 +3546,34 @@ void tst_QScriptEngine::isEvaluating() QVERIFY(!eng.isEvaluating()); eng.evaluate("0 = 0"); QVERIFY(!eng.isEvaluating()); +} - { - QScriptValue fun = eng.newFunction(myFunctionReturningIsEvaluating); - eng.globalObject().setProperty("myFunctionReturningIsEvaluating", fun); - QScriptValue ret = eng.evaluate("myFunctionReturningIsEvaluating()"); - QVERIFY(ret.isBoolean()); - QVERIFY(ret.toBoolean()); - } +void tst_QScriptEngine::isEvaluating_fromNative() +{ + QScriptEngine eng; + QScriptValue fun = eng.newFunction(myFunctionReturningIsEvaluating); + eng.globalObject().setProperty("myFunctionReturningIsEvaluating", fun); + QScriptValue ret = eng.evaluate("myFunctionReturningIsEvaluating()"); + QVERIFY(ret.isBoolean()); + QVERIFY(ret.toBoolean()); +} - { - EventReceiver4 receiver(&eng); - QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1))); +void tst_QScriptEngine::isEvaluating_fromEvent() +{ + QScriptEngine eng; + EventReceiver4 receiver(&eng); + QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1))); - QString script = QString::fromLatin1( - "var end = Number(new Date()) + 1000;" - "var x = 0;" - "while (Number(new Date()) < end) {" - " ++x;" - "}"); + QString script = QString::fromLatin1( + "var end = Number(new Date()) + 1000;" + "var x = 0;" + "while (Number(new Date()) < end) {" + " ++x;" + "}"); - eng.setProcessEventsInterval(100); - eng.evaluate(script); - QVERIFY(receiver.wasEvaluating); - } + eng.setProcessEventsInterval(100); + eng.evaluate(script); + QVERIFY(receiver.wasEvaluating); } static QtMsgType theMessageType; @@ -3456,24 +3587,33 @@ static void myMsgHandler(QtMsgType type, const char *msg) void tst_QScriptEngine::printFunctionWithCustomHandler() { + // The built-in print() function passes the output to Qt's message + // handler. By installing a custom message handler, the output can be + // redirected without changing the print() function itself. + // This behavior is not documented. QScriptEngine eng; QtMsgHandler oldHandler = qInstallMsgHandler(myMsgHandler); QVERIFY(eng.globalObject().property("print").isFunction()); + theMessageType = QtSystemMsg; QVERIFY(theMessage.isEmpty()); QVERIFY(eng.evaluate("print('test')").isUndefined()); QCOMPARE(theMessageType, QtDebugMsg); QCOMPARE(theMessage, QString::fromLatin1("test")); + theMessageType = QtSystemMsg; theMessage.clear(); QVERIFY(eng.evaluate("print(3, true, 'little pigs')").isUndefined()); QCOMPARE(theMessageType, QtDebugMsg); QCOMPARE(theMessage, QString::fromLatin1("3 true little pigs")); + qInstallMsgHandler(oldHandler); } void tst_QScriptEngine::printThrowsException() { + // If an argument to print() causes an exception to be thrown when + // it's converted to a string, print() should propagate the exception. QScriptEngine eng; QScriptValue ret = eng.evaluate("print({ toString: function() { throw 'foo'; } });"); QVERIFY(eng.hasUncaughtException()); @@ -3506,34 +3646,30 @@ void tst_QScriptEngine::errorConstructors() } } -static QScriptValue argumentsProperty_fun(QScriptContext *, QScriptEngine *eng) +void tst_QScriptEngine::argumentsProperty_globalContext() { - eng->evaluate("var a = arguments[0];"); - eng->evaluate("arguments[0] = 200;"); - return eng->evaluate("a + arguments[0]"); + QScriptEngine eng; + { + // Unlike function calls, the global context doesn't have an + // arguments property. + QScriptValue ret = eng.evaluate("arguments"); + QVERIFY(ret.isError()); + QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError"))); + } + eng.evaluate("arguments = 10"); + { + QScriptValue ret = eng.evaluate("arguments"); + QVERIFY(ret.isNumber()); + QCOMPARE(ret.toInt32(), 10); + } + QVERIFY(eng.evaluate("delete arguments").toBoolean()); + QVERIFY(!eng.globalObject().property("arguments").isValid()); } - -void tst_QScriptEngine::argumentsProperty() +void tst_QScriptEngine::argumentsProperty_JS() { { QScriptEngine eng; - { - QScriptValue ret = eng.evaluate("arguments"); - QVERIFY(ret.isError()); - QCOMPARE(ret.toString(), QString::fromLatin1("ReferenceError: Can't find variable: arguments")); - } - eng.evaluate("arguments = 10"); - { - QScriptValue ret = eng.evaluate("arguments"); - QVERIFY(ret.isNumber()); - QCOMPARE(ret.toInt32(), 10); - } - QVERIFY(eng.evaluate("delete arguments").toBoolean()); - QVERIFY(!eng.globalObject().property("arguments").isValid()); - } - { - QScriptEngine eng; eng.evaluate("o = { arguments: 123 }"); QScriptValue ret = eng.evaluate("with (o) { arguments; }"); QVERIFY(ret.isNumber()); @@ -3542,25 +3678,40 @@ void tst_QScriptEngine::argumentsProperty() { QScriptEngine eng; QVERIFY(!eng.globalObject().property("arguments").isValid()); + // This is testing ECMA-262 compliance. In function calls, "arguments" + // appears like a local variable, and it can be replaced. QScriptValue ret = eng.evaluate("(function() { arguments = 456; return arguments; })()"); QVERIFY(ret.isNumber()); QCOMPARE(ret.toInt32(), 456); QVERIFY(!eng.globalObject().property("arguments").isValid()); } +} - { - QScriptEngine eng; - QScriptValue fun = eng.newFunction(argumentsProperty_fun); - eng.globalObject().setProperty("fun", eng.newFunction(argumentsProperty_fun)); - QScriptValue result = eng.evaluate("fun(18)"); - QVERIFY(result.isNumber()); - QCOMPARE(result.toInt32(), 218); - } +static QScriptValue argumentsProperty_fun(QScriptContext *, QScriptEngine *eng) +{ + // Since evaluate() is done in the current context, "arguments" should + // refer to currentContext()->argumentsObject(). + // This is for consistency with the built-in JS eval() function. + eng->evaluate("var a = arguments[0];"); + eng->evaluate("arguments[0] = 200;"); + return eng->evaluate("a + arguments[0]"); } -void tst_QScriptEngine::numberClass() +void tst_QScriptEngine::argumentsProperty_evaluateInNativeFunction() { QScriptEngine eng; + QScriptValue fun = eng.newFunction(argumentsProperty_fun); + eng.globalObject().setProperty("fun", eng.newFunction(argumentsProperty_fun)); + QScriptValue result = eng.evaluate("fun(18)"); + QVERIFY(result.isNumber()); + QCOMPARE(result.toInt32(), 200+18); +} + +void tst_QScriptEngine::jsNumberClass() +{ + // See ECMA-262 Section 15.7, "Number Objects". + + QScriptEngine eng; QScriptValue ctor = eng.globalObject().property("Number"); QVERIFY(ctor.property("length").isNumber()); @@ -3669,7 +3820,7 @@ void tst_QScriptEngine::numberClass() } } -void tst_QScriptEngine::forInStatement() +void tst_QScriptEngine::jsForInStatement_simple() { QScriptEngine eng; { @@ -3692,8 +3843,11 @@ void tst_QScriptEngine::forInStatement() QCOMPARE(lst.at(0), QString::fromLatin1("p")); QCOMPARE(lst.at(1), QString::fromLatin1("q")); } +} - // properties in prototype +void tst_QScriptEngine::jsForInStatement_prototypeProperties() +{ + QScriptEngine eng; { QScriptValue ret = eng.evaluate("o = { }; o.__proto__ = { p: 123 }; r = [];" "for (var p in o) r[r.length] = p; r"); @@ -3718,6 +3872,11 @@ void tst_QScriptEngine::forInStatement() QCOMPARE(lst.at(0), QString::fromLatin1("p")); } +} + +void tst_QScriptEngine::jsForInStatement_mutateWhileIterating() +{ + QScriptEngine eng; // deleting property during enumeration { QScriptValue ret = eng.evaluate("o = { p: 123 }; r = [];" @@ -3743,7 +3902,11 @@ void tst_QScriptEngine::forInStatement() QCOMPARE(lst.at(0), QString::fromLatin1("p")); } - // arrays +} + +void tst_QScriptEngine::jsForInStatement_arrays() +{ + QScriptEngine eng; { QScriptValue ret = eng.evaluate("a = [123, 456]; r = [];" "for (var p in a) r[r.length] = p; r"); @@ -3774,10 +3937,11 @@ void tst_QScriptEngine::forInStatement() QCOMPARE(lst.at(3), QString::fromLatin1("2")); QCOMPARE(lst.at(4), QString::fromLatin1("bar")); } +} - // null and undefined - // according to the spec, we should throw an exception; however, for - // compability with the real world, we don't +void tst_QScriptEngine::jsForInStatement_nullAndUndefined() +{ + QScriptEngine eng; { QScriptValue ret = eng.evaluate("r = true; for (var p in undefined) r = false; r"); QVERIFY(ret.isBool()); @@ -3790,9 +3954,17 @@ void tst_QScriptEngine::forInStatement() } } -void tst_QScriptEngine::functionExpression() +void tst_QScriptEngine::jsFunctionDeclarationAsStatement() { - // task 175679 + // ECMA-262 does not allow function declarations to be used as statements, + // but several popular implementations (including JSC) do. See the NOTE + // at the beginning of chapter 12 in ECMA-262 5th edition, where it's + // recommended that implementations either disallow this usage or issue + // a warning. + // Since we had a bug report long ago about QtScript not supporting this + // "feature" (and thus deviating from other implementations), we still + // check this behavior. + QScriptEngine eng; QVERIFY(!eng.globalObject().property("bar").isValid()); eng.evaluate("function foo(arg) {\n" @@ -3825,6 +3997,8 @@ void tst_QScriptEngine::functionExpression() void tst_QScriptEngine::stringObjects() { + // See ECMA-262 Section 15.5, "String Objects". + QScriptEngine eng; QString str("ciao"); // in C++ @@ -3886,7 +4060,11 @@ void tst_QScriptEngine::stringObjects() QVERIFY(ret7.isBoolean()); QVERIFY(ret7.toBoolean()); } +} +void tst_QScriptEngine::jsStringPrototypeReplaceBugs() +{ + QScriptEngine eng; // task 212440 { QScriptValue ret = eng.evaluate("replace_args = []; \"a a a\".replace(/(a)/g, function() { replace_args.push(arguments); }); replace_args"); @@ -4024,7 +4202,8 @@ void tst_QScriptEngine::getterSetterThisObject_activation() // read eng.evaluate("act.__defineGetter__('x', function() { return this; })"); QVERIFY(eng.evaluate("x === act").toBoolean()); - QEXPECT_FAIL("", "Exotic overload (don't care for now)", Continue); + QEXPECT_FAIL("", "QTBUG-17605: Not possible to implement local variables as getter/setter properties", Abort); + QVERIFY(!eng.hasUncaughtException()); QVERIFY(eng.evaluate("with (act) x").equals("foo")); QVERIFY(eng.evaluate("(function() { with (act) return x; })() === act").toBoolean()); eng.evaluate("q = {}; with (act) with (q) x").equals(eng.evaluate("act")); @@ -4038,8 +4217,10 @@ void tst_QScriptEngine::getterSetterThisObject_activation() } } -void tst_QScriptEngine::continueInSwitch() +void tst_QScriptEngine::jsContinueInSwitch() { + // This is testing ECMA-262 compliance, not C++ API. + QScriptEngine eng; // switch - continue { @@ -4116,35 +4297,17 @@ void tst_QScriptEngine::continueInSwitch() } } -void tst_QScriptEngine::readOnlyPrototypeProperty() +void tst_QScriptEngine::jsShadowReadOnlyPrototypeProperty() { - QSKIP("JSC semantics differ from old back-end and SpiderMonkey", SkipAll); + // SpiderMonkey has different behavior than JSC and V8; it disallows + // creating a property on the instance if there's a property with the + // same name in the prototype, and that property is read-only. We + // adopted that behavior in the old (4.5) QtScript back-end, but it + // just seems weird -- and non-compliant. Adopt the JSC behavior instead. QScriptEngine eng; - QCOMPARE(eng.evaluate("o = {}; o.__proto__ = parseInt; o.length").toInt32(), 2); - QCOMPARE(eng.evaluate("o.length = 4; o.length").toInt32(), 2); - QVERIFY(!eng.evaluate("o.hasOwnProperty('length')").toBoolean()); - QCOMPARE(eng.evaluate("o.length *= 2; o.length").toInt32(), 2); - QCOMPARE(eng.evaluate("o.length /= 2; o.length").toInt32(), 2); - QCOMPARE(eng.evaluate("o.length %= 2; o.length").toInt32(), 2); - QCOMPARE(eng.evaluate("o.length += 2; o.length").toInt32(), 2); - QCOMPARE(eng.evaluate("o.length -= 2; o.length").toInt32(), 2); - QCOMPARE(eng.evaluate("o.length <<= 2; o.length").toInt32(), 2); - QCOMPARE(eng.evaluate("o.length >>= 2; o.length").toInt32(), 2); - QCOMPARE(eng.evaluate("o.length >>>= 2; o.length").toInt32(), 2); - QCOMPARE(eng.evaluate("o.length &= 0; o.length").toInt32(), 2); - QCOMPARE(eng.evaluate("o.length ^= 255; o.length").toInt32(), 2); - QCOMPARE(eng.evaluate("o.length |= 255; o.length").toInt32(), 2); - - { - QScriptValue ret = eng.evaluate("o.__defineGetter__('length', function() {})"); - QVERIFY(ret.isError()); - QCOMPARE(ret.toString(), QString::fromLatin1("Error: cannot redefine read-only property")); - } - { - QScriptValue ret = eng.evaluate("o.__defineSetter__('length', function() {})"); - QVERIFY(ret.isError()); - QCOMPARE(ret.toString(), QString::fromLatin1("Error: cannot redefine read-only property")); - } + QVERIFY(eng.evaluate("o = {}; o.__proto__ = parseInt; o.length").isNumber()); + QCOMPARE(eng.evaluate("o.length = 123; o.length").toInt32(), 123); + QVERIFY(eng.evaluate("o.hasOwnProperty('length')").toBoolean()); } void tst_QScriptEngine::toObject() @@ -4226,7 +4389,7 @@ void tst_QScriptEngine::toObject() QVERIFY(stringValue.isString()); } -void tst_QScriptEngine::reservedWords_data() +void tst_QScriptEngine::jsReservedWords_data() { QTest::addColumn<QString>("word"); QTest::newRow("break") << QString("break"); @@ -4259,8 +4422,13 @@ void tst_QScriptEngine::reservedWords_data() QTest::newRow("with") << QString("with"); } -void tst_QScriptEngine::reservedWords() +void tst_QScriptEngine::jsReservedWords() { + // See ECMA-262 Section 7.6.1, "Reserved Words". + // We prefer that the implementation is less strict than the spec; e.g. + // it's good to allow reserved words as identifiers in object literals, + // and when accessing properties using dot notation. + QFETCH(QString, word); { QScriptEngine eng; @@ -4298,7 +4466,7 @@ void tst_QScriptEngine::reservedWords() } } -void tst_QScriptEngine::futureReservedWords_data() +void tst_QScriptEngine::jsFutureReservedWords_data() { QTest::addColumn<QString>("word"); QTest::addColumn<bool>("allowed"); @@ -4335,8 +4503,12 @@ void tst_QScriptEngine::futureReservedWords_data() QTest::newRow("volatile") << QString("volatile") << true; } -void tst_QScriptEngine::futureReservedWords() +void tst_QScriptEngine::jsFutureReservedWords() { + // See ECMA-262 Section 7.6.1.2, "Future Reserved Words". + // In real-world implementations, most of these words are + // actually allowed as normal identifiers. + QFETCH(QString, word); QFETCH(bool, allowed); { @@ -4364,8 +4536,10 @@ void tst_QScriptEngine::futureReservedWords() } } -void tst_QScriptEngine::throwInsideWithStatement() +void tst_QScriptEngine::jsThrowInsideWithStatement() { + // This is testing ECMA-262 compliance, not C++ API. + // task 209988 QScriptEngine eng; { @@ -4379,7 +4553,7 @@ void tst_QScriptEngine::throwInsideWithStatement() " bad;" "}"); QVERIFY(ret.isError()); - QCOMPARE(ret.toString(), QString::fromLatin1("ReferenceError: Can't find variable: bad")); + QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError"))); } { QScriptValue ret = eng.evaluate( @@ -4392,7 +4566,7 @@ void tst_QScriptEngine::throwInsideWithStatement() " bad;" "}"); QVERIFY(ret.isError()); - QCOMPARE(ret.toString(), QString::fromLatin1("ReferenceError: Can't find variable: bad")); + QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError"))); } { eng.clearExceptions(); @@ -4419,7 +4593,7 @@ void tst_QScriptEngine::throwInsideWithStatement() QVERIFY(ret.isNumber()); QScriptValue ret2 = eng.evaluate("bug"); QVERIFY(ret2.isError()); - QCOMPARE(ret2.toString(), QString::fromLatin1("ReferenceError: Can't find variable: bug")); + QVERIFY(ret2.toString().contains(QString::fromLatin1("ReferenceError"))); } } @@ -4429,106 +4603,116 @@ public: TestAgent(QScriptEngine *engine) : QScriptEngineAgent(engine) {} }; -void tst_QScriptEngine::getSetAgent() +void tst_QScriptEngine::getSetAgent_ownership() { - // case 1: engine deleted before agent --> agent deleted too - { - QScriptEngine *eng = new QScriptEngine; - QCOMPARE(eng->agent(), (QScriptEngineAgent*)0); - TestAgent *agent = new TestAgent(eng); - eng->setAgent(agent); - QCOMPARE(eng->agent(), (QScriptEngineAgent*)agent); - eng->setAgent(0); // the engine maintains ownership of the old agent - QCOMPARE(eng->agent(), (QScriptEngineAgent*)0); - delete eng; - } - // case 2: agent deleted before engine --> engine's agent should become 0 - { - QScriptEngine *eng = new QScriptEngine; - TestAgent *agent = new TestAgent(eng); - eng->setAgent(agent); - QCOMPARE(eng->agent(), (QScriptEngineAgent*)agent); - delete agent; - QCOMPARE(eng->agent(), (QScriptEngineAgent*)0); - eng->evaluate("(function(){ return 123; })()"); - delete eng; - } - { - QScriptEngine eng; - QScriptEngine eng2; - TestAgent *agent = new TestAgent(&eng); - QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::setAgent(): cannot set agent belonging to different engine"); - eng2.setAgent(agent); - QCOMPARE(eng2.agent(), (QScriptEngineAgent*)0); - } + // engine deleted before agent --> agent deleted too + QScriptEngine *eng = new QScriptEngine; + QCOMPARE(eng->agent(), (QScriptEngineAgent*)0); + TestAgent *agent = new TestAgent(eng); + eng->setAgent(agent); + QCOMPARE(eng->agent(), (QScriptEngineAgent*)agent); + eng->setAgent(0); // the engine maintains ownership of the old agent + QCOMPARE(eng->agent(), (QScriptEngineAgent*)0); + delete eng; } -void tst_QScriptEngine::reentrancy() +void tst_QScriptEngine::getSetAgent_deleteAgent() { + // agent deleted before engine --> engine's agent should become 0 + QScriptEngine *eng = new QScriptEngine; + TestAgent *agent = new TestAgent(eng); + eng->setAgent(agent); + QCOMPARE(eng->agent(), (QScriptEngineAgent*)agent); + delete agent; + QCOMPARE(eng->agent(), (QScriptEngineAgent*)0); + eng->evaluate("(function(){ return 123; })()"); + delete eng; +} + +void tst_QScriptEngine::getSetAgent_differentEngine() +{ + QScriptEngine eng; + QScriptEngine eng2; + TestAgent *agent = new TestAgent(&eng); + QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::setAgent(): cannot set agent belonging to different engine"); + eng2.setAgent(agent); + QCOMPARE(eng2.agent(), (QScriptEngineAgent*)0); +} + +void tst_QScriptEngine::reentrancy_stringHandles() +{ + QScriptEngine eng1; + QScriptEngine eng2; + QScriptString s1 = eng1.toStringHandle("foo"); + QScriptString s2 = eng2.toStringHandle("foo"); + QVERIFY(s1 != s2); +} + +void tst_QScriptEngine::reentrancy_processEventsInterval() +{ + QScriptEngine eng1; + QScriptEngine eng2; + eng1.setProcessEventsInterval(123); + QCOMPARE(eng2.processEventsInterval(), -1); + eng2.setProcessEventsInterval(456); + QCOMPARE(eng1.processEventsInterval(), 123); +} + +void tst_QScriptEngine::reentrancy_typeConversion() +{ + QScriptEngine eng1; + QScriptEngine eng2; + qScriptRegisterMetaType<Foo>(&eng1, fooToScriptValue, fooFromScriptValue); + Foo foo; + foo.x = 12; + foo.y = 34; { - QScriptEngine eng1; - QScriptEngine eng2; - QScriptString s1 = eng1.toStringHandle("foo"); - QScriptString s2 = eng2.toStringHandle("foo"); - QVERIFY(s1 != s2); - } - { - QScriptEngine eng1; - QScriptEngine eng2; - eng1.setProcessEventsInterval(123); - QCOMPARE(eng2.processEventsInterval(), -1); - eng2.setProcessEventsInterval(456); - QCOMPARE(eng1.processEventsInterval(), 123); + QScriptValue fooVal = qScriptValueFromValue(&eng1, foo); + QVERIFY(fooVal.isObject()); + QVERIFY(!fooVal.isVariant()); + QCOMPARE(fooVal.property("x").toInt32(), 12); + QCOMPARE(fooVal.property("y").toInt32(), 34); + fooVal.setProperty("x", 56); + fooVal.setProperty("y", 78); + + Foo foo2 = qScriptValueToValue<Foo>(fooVal); + QCOMPARE(foo2.x, 56); + QCOMPARE(foo2.y, 78); } { - QScriptEngine eng1; - QScriptEngine eng2; - qScriptRegisterMetaType<Foo>(&eng1, fooToScriptValue, fooFromScriptValue); - Foo foo; - foo.x = 12; - foo.y = 34; - { - QScriptValue fooVal = qScriptValueFromValue(&eng1, foo); - QVERIFY(fooVal.isObject()); - QVERIFY(!fooVal.isVariant()); - QCOMPARE(fooVal.property("x").toInt32(), 12); - QCOMPARE(fooVal.property("y").toInt32(), 34); - fooVal.setProperty("x", 56); - fooVal.setProperty("y", 78); - - Foo foo2 = qScriptValueToValue<Foo>(fooVal); - QCOMPARE(foo2.x, 56); - QCOMPARE(foo2.y, 78); - } - { - QScriptValue fooVal = qScriptValueFromValue(&eng2, foo); - QVERIFY(fooVal.isVariant()); + QScriptValue fooVal = qScriptValueFromValue(&eng2, foo); + QVERIFY(fooVal.isVariant()); - Foo foo2 = qScriptValueToValue<Foo>(fooVal); - QCOMPARE(foo2.x, 12); - QCOMPARE(foo2.y, 34); - } - QVERIFY(!eng1.defaultPrototype(qMetaTypeId<Foo>()).isValid()); - QVERIFY(!eng2.defaultPrototype(qMetaTypeId<Foo>()).isValid()); - QScriptValue proto1 = eng1.newObject(); - eng1.setDefaultPrototype(qMetaTypeId<Foo>(), proto1); - QVERIFY(!eng2.defaultPrototype(qMetaTypeId<Foo>()).isValid()); - QScriptValue proto2 = eng2.newObject(); - eng2.setDefaultPrototype(qMetaTypeId<Foo>(), proto2); - QVERIFY(eng2.defaultPrototype(qMetaTypeId<Foo>()).isValid()); - QVERIFY(eng1.defaultPrototype(qMetaTypeId<Foo>()).strictlyEquals(proto1)); - } - { - QScriptEngine eng1; - QScriptEngine eng2; - QVERIFY(!eng2.globalObject().property("a").isValid()); - eng1.evaluate("a = 10"); - QVERIFY(eng1.globalObject().property("a").isNumber()); - QVERIFY(!eng2.globalObject().property("a").isValid()); - eng2.evaluate("a = 20"); - QVERIFY(eng2.globalObject().property("a").isNumber()); - QCOMPARE(eng1.globalObject().property("a").toInt32(), 10); + Foo foo2 = qScriptValueToValue<Foo>(fooVal); + QCOMPARE(foo2.x, 12); + QCOMPARE(foo2.y, 34); } + QVERIFY(!eng1.defaultPrototype(qMetaTypeId<Foo>()).isValid()); + QVERIFY(!eng2.defaultPrototype(qMetaTypeId<Foo>()).isValid()); + QScriptValue proto1 = eng1.newObject(); + eng1.setDefaultPrototype(qMetaTypeId<Foo>(), proto1); + QVERIFY(!eng2.defaultPrototype(qMetaTypeId<Foo>()).isValid()); + QScriptValue proto2 = eng2.newObject(); + eng2.setDefaultPrototype(qMetaTypeId<Foo>(), proto2); + QVERIFY(eng2.defaultPrototype(qMetaTypeId<Foo>()).isValid()); + QVERIFY(eng1.defaultPrototype(qMetaTypeId<Foo>()).strictlyEquals(proto1)); +} + +void tst_QScriptEngine::reentrancy_globalObjectProperties() +{ + QScriptEngine eng1; + QScriptEngine eng2; + QVERIFY(!eng2.globalObject().property("a").isValid()); + eng1.evaluate("a = 10"); + QVERIFY(eng1.globalObject().property("a").isNumber()); + QVERIFY(!eng2.globalObject().property("a").isValid()); + eng2.evaluate("a = 20"); + QVERIFY(eng2.globalObject().property("a").isNumber()); + QCOMPARE(eng1.globalObject().property("a").toInt32(), 10); +} + +void tst_QScriptEngine::reentrancy_Array() +{ // weird bug with JSC backend { QScriptEngine eng; @@ -4540,79 +4724,82 @@ void tst_QScriptEngine::reentrancy() QScriptEngine eng; QCOMPARE(eng.evaluate("Array()").toString(), QString()); } +} +void tst_QScriptEngine::reentrancy_objectCreation() +{ + QScriptEngine eng1; + QScriptEngine eng2; { - QScriptEngine eng1; - QScriptEngine eng2; - { - QScriptValue d1 = eng1.newDate(0); - QScriptValue d2 = eng2.newDate(0); - QCOMPARE(d1.toDateTime(), d2.toDateTime()); - QCOMPARE(d2.toDateTime(), d1.toDateTime()); - } - { - QScriptValue r1 = eng1.newRegExp("foo", "gim"); - QScriptValue r2 = eng2.newRegExp("foo", "gim"); - QCOMPARE(r1.toRegExp(), r2.toRegExp()); - QCOMPARE(r2.toRegExp(), r1.toRegExp()); - } - { - QScriptValue o1 = eng1.newQObject(this); - QScriptValue o2 = eng2.newQObject(this); - QCOMPARE(o1.toQObject(), o2.toQObject()); - QCOMPARE(o2.toQObject(), o1.toQObject()); - } - { - QScriptValue mo1 = eng1.newQMetaObject(&staticMetaObject); - QScriptValue mo2 = eng2.newQMetaObject(&staticMetaObject); - QCOMPARE(mo1.toQMetaObject(), mo2.toQMetaObject()); - QCOMPARE(mo2.toQMetaObject(), mo1.toQMetaObject()); - } + QScriptValue d1 = eng1.newDate(0); + QScriptValue d2 = eng2.newDate(0); + QCOMPARE(d1.toDateTime(), d2.toDateTime()); + QCOMPARE(d2.toDateTime(), d1.toDateTime()); + } + { + QScriptValue r1 = eng1.newRegExp("foo", "gim"); + QScriptValue r2 = eng2.newRegExp("foo", "gim"); + QCOMPARE(r1.toRegExp(), r2.toRegExp()); + QCOMPARE(r2.toRegExp(), r1.toRegExp()); + } + { + QScriptValue o1 = eng1.newQObject(this); + QScriptValue o2 = eng2.newQObject(this); + QCOMPARE(o1.toQObject(), o2.toQObject()); + QCOMPARE(o2.toQObject(), o1.toQObject()); + } + { + QScriptValue mo1 = eng1.newQMetaObject(&staticMetaObject); + QScriptValue mo2 = eng2.newQMetaObject(&staticMetaObject); + QCOMPARE(mo1.toQMetaObject(), mo2.toQMetaObject()); + QCOMPARE(mo2.toQMetaObject(), mo1.toQMetaObject()); } } -void tst_QScriptEngine:: incDecNonObjectProperty() +void tst_QScriptEngine::jsIncDecNonObjectProperty() { + // This is testing ECMA-262 compliance, not C++ API. + QScriptEngine eng; { QScriptValue ret = eng.evaluate("var a; a.n++"); QVERIFY(ret.isError()); - QCOMPARE(ret.toString(), QString::fromLatin1("TypeError: Result of expression 'a' [undefined] is not an object.")); + QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError"))); } { QScriptValue ret = eng.evaluate("var a; a.n--"); QVERIFY(ret.isError()); - QCOMPARE(ret.toString(), QString::fromLatin1("TypeError: Result of expression 'a' [undefined] is not an object.")); + QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError"))); } { QScriptValue ret = eng.evaluate("var a = null; a.n++"); QVERIFY(ret.isError()); - QCOMPARE(ret.toString(), QString::fromLatin1("TypeError: Result of expression 'a' [null] is not an object.")); + QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError"))); } { QScriptValue ret = eng.evaluate("var a = null; a.n--"); QVERIFY(ret.isError()); - QCOMPARE(ret.toString(), QString::fromLatin1("TypeError: Result of expression 'a' [null] is not an object.")); + QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError"))); } { QScriptValue ret = eng.evaluate("var a; ++a.n"); QVERIFY(ret.isError()); - QCOMPARE(ret.toString(), QString::fromLatin1("TypeError: Result of expression 'a' [null] is not an object.")); + QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError"))); } { QScriptValue ret = eng.evaluate("var a; --a.n"); QVERIFY(ret.isError()); - QCOMPARE(ret.toString(), QString::fromLatin1("TypeError: Result of expression 'a' [null] is not an object.")); + QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError"))); } { QScriptValue ret = eng.evaluate("var a; a.n += 1"); QVERIFY(ret.isError()); - QCOMPARE(ret.toString(), QString::fromLatin1("TypeError: Result of expression 'a' [null] is not an object.")); + QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError"))); } { QScriptValue ret = eng.evaluate("var a; a.n -= 1"); QVERIFY(ret.isError()); - QCOMPARE(ret.toString(), QString::fromLatin1("TypeError: Result of expression 'a' [null] is not an object.")); + QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError"))); } { QScriptValue ret = eng.evaluate("var a = 'ciao'; a.length++"); @@ -5125,6 +5312,21 @@ void tst_QScriptEngine::translateScriptUnicodeIdBased() QVERIFY(!engine.hasUncaughtException()); } +void tst_QScriptEngine::translateFromBuiltinCallback() +{ + QScriptEngine eng; + eng.installTranslatorFunctions(); + + // Callback has no translation context. + eng.evaluate("function foo() { qsTr('foo'); }"); + + // Stack at translation time will be: + // qsTr, foo, forEach, global + // qsTr() needs to walk to the outer-most (global) frame before it finds + // a translation context, and this should not crash. + eng.evaluate("[10,20].forEach(foo)", "script.js"); +} + void tst_QScriptEngine::functionScopes() { QScriptEngine eng; @@ -5132,7 +5334,7 @@ void tst_QScriptEngine::functionScopes() // top-level functions have only the global object in their scope QScriptValue fun = eng.evaluate("(function() {})"); QVERIFY(fun.isFunction()); - QEXPECT_FAIL("", "Function scope proxying is not implemented", Abort); + QEXPECT_FAIL("", "QScriptValue::scope() is internal, not implemented", Abort); QVERIFY(fun.scope().isObject()); QVERIFY(fun.scope().strictlyEquals(eng.globalObject())); QVERIFY(!eng.globalObject().scope().isValid()); @@ -5436,6 +5638,7 @@ void tst_QScriptEngine::collectGarbageAfterConnect() .isUndefined()); QVERIFY(widget != 0); engine.evaluate("widget = null;"); + // The connection should not keep the widget alive. collectGarbage_helper(engine); QVERIFY(widget == 0); } @@ -5599,6 +5802,17 @@ void tst_QScriptEngine::reentrency() void tst_QScriptEngine::newFixedStaticScopeObject() { + // "Static scope objects" is an optimization we do for QML. + // It enables the creation of JS objects that can guarantee to the + // compiler that no properties will be added or removed. This enables + // the compiler to generate a very simple (fast) property access, as + // opposed to a full virtual lookup. Due to the inherent use of scope + // chains in QML, this can make a huge difference (10x improvement for + // benchmark in QTBUG-8576). + // Ideally we would not need a special object type for this, and the + // VM would dynamically optimize it to be fast... + // See also QScriptEngine benchmark. + QScriptEngine eng; static const int propertyCount = 4; QString names[] = { "foo", "bar", "baz", "Math" }; @@ -5739,6 +5953,11 @@ void tst_QScriptEngine::newFixedStaticScopeObject() void tst_QScriptEngine::newGrowingStaticScopeObject() { + // The main use case for a growing static scope object is to set it as + // the activation object of a QScriptContext, so that all JS variable + // declarations end up in that object. It needs to be "growable" since + // we don't know in advance how many variables a script will declare. + QScriptEngine eng; QScriptValue scope = QScriptDeclarativeClass::newStaticScopeObject(&eng); @@ -5826,10 +6045,10 @@ void tst_QScriptEngine::newGrowingStaticScopeObject() { QScriptValue fun = eng.evaluate("(function() { return futureProperty; })"); QVERIFY(fun.isFunction()); - QCOMPARE(fun.call().toString(), QString::fromLatin1("ReferenceError: Can't find variable: futureProperty")); + QVERIFY(fun.call().toString().contains(QString::fromLatin1("ReferenceError"))); scope.setProperty("futureProperty", "added after the function was compiled"); // If scope were dynamic, this would return the new property. - QCOMPARE(fun.call().toString(), QString::fromLatin1("ReferenceError: Can't find variable: futureProperty")); + QVERIFY(fun.call().toString().contains(QString::fromLatin1("ReferenceError"))); } eng.popContext(); |