1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 
43 #include <QtTest/QtTest>
44 
45 #include <qscriptengine.h>
46 #include <qscriptengineagent.h>
47 #include <qscriptprogram.h>
48 #include <qscriptvalueiterator.h>
49 #include <qgraphicsitem.h>
50 #include <qstandarditemmodel.h>
51 #include <QtCore/qnumeric.h>
52 #include <stdlib.h>
53 
54 #include <QtScript/private/qscriptdeclarativeclass_p.h>
55 
56 Q_DECLARE_METATYPE(QList<int>)
Q_DECLARE_METATYPE(QObjectList)57 Q_DECLARE_METATYPE(QObjectList)
58 Q_DECLARE_METATYPE(QScriptProgram)
59 
60 //TESTED_CLASS=
61 //TESTED_FILES=
62 
63 #if defined(Q_OS_SYMBIAN)
64 # define STRINGIFY(x) #x
65 # define TOSTRING(x) STRINGIFY(x)
66 # define SRCDIR "C:/Private/" TOSTRING(SYMBIAN_SRCDIR_UID)
67 #endif
68 
69 // The JavaScriptCore GC marks the C stack. To try to ensure that there is
70 // no JSObject* left in stack memory by the compiler, we call this function
71 // to zap some bytes of memory before calling collectGarbage().
72 static void zapSomeStack()
73 {
74     char buf[4096];
75     memset(buf, 0, sizeof(buf));
76 }
77 
collectGarbage_helper(QScriptEngine & eng)78 static void collectGarbage_helper(QScriptEngine &eng)
79 {
80     zapSomeStack();
81     eng.collectGarbage();
82 }
83 
84 class tst_QScriptEngine : public QObject
85 {
86     Q_OBJECT
87 
88 public:
89     tst_QScriptEngine();
90     virtual ~tst_QScriptEngine();
91 
92 private slots:
93     void constructWithParent();
94     void currentContext();
95     void pushPopContext();
96     void getSetDefaultPrototype_int();
97     void getSetDefaultPrototype_customType();
98     void newFunction();
99     void newFunctionWithArg();
100     void newFunctionWithProto();
101     void newObject();
102     void newArray();
103     void newArray_HooliganTask218092();
104     void newArray_HooliganTask233836();
105     void newVariant();
106     void newVariant_defaultPrototype();
107     void newVariant_promoteObject();
108     void newVariant_replaceValue();
109     void newVariant_valueOfToString();
110     void newVariant_promoteNonObject();
111     void newVariant_promoteNonQScriptObject();
112     void newRegExp();
113     void jsRegExp();
114     void newDate();
115     void jsParseDate();
116     void newQObject();
117     void newQObject_ownership();
118     void newQObject_promoteObject();
119     void newQObject_sameQObject();
120     void newQObject_defaultPrototype();
121     void newQObject_promoteNonObject();
122     void newQObject_promoteNonQScriptObject();
123     void newQMetaObject();
124     void newActivationObject();
125     void getSetGlobalObject();
126     void globalObjectProperties();
127     void globalObjectProperties_enumerate();
128     void createGlobalObjectProperty();
129     void globalObjectGetterSetterProperty();
130     void customGlobalObjectWithPrototype();
131     void globalObjectWithCustomPrototype();
132     void builtinFunctionNames_data();
133     void builtinFunctionNames();
134     void checkSyntax_data();
135     void checkSyntax();
136     void canEvaluate_data();
137     void canEvaluate();
138     void evaluate_data();
139     void evaluate();
140     void nestedEvaluate();
141     void uncaughtException();
142     void errorMessage_QT679();
143     void valueConversion_basic();
144     void valueConversion_customType();
145     void valueConversion_sequence();
146     void valueConversion_QVariant();
147     void valueConversion_hooliganTask248802();
148     void valueConversion_basic2();
149     void valueConversion_dateTime();
150     void valueConversion_regExp();
151     void qScriptValueFromValue_noEngine();
152     void importExtension();
153     void infiniteRecursion();
154     void castWithPrototypeChain();
155     void castWithMultipleInheritance();
156     void collectGarbage();
157     void reportAdditionalMemoryCost();
158     void gcWithNestedDataStructure();
159     void processEventsWhileRunning();
160     void throwErrorFromProcessEvents_data();
161     void throwErrorFromProcessEvents();
162     void disableProcessEventsInterval();
163     void stacktrace();
164     void numberParsing_data();
165     void numberParsing();
166     void automaticSemicolonInsertion();
167     void abortEvaluation_notEvaluating();
168     void abortEvaluation_data();
169     void abortEvaluation();
170     void abortEvaluation_tryCatch();
171     void abortEvaluation_fromNative();
172     void abortEvaluation_QTBUG9433();
173     void isEvaluating_notEvaluating();
174     void isEvaluating_fromNative();
175     void isEvaluating_fromEvent();
176     void printFunctionWithCustomHandler();
177     void printThrowsException();
178     void errorConstructors();
179     void argumentsProperty_globalContext();
180     void argumentsProperty_JS();
181     void argumentsProperty_evaluateInNativeFunction();
182     void jsNumberClass();
183     void jsForInStatement_simple();
184     void jsForInStatement_prototypeProperties();
185     void jsForInStatement_mutateWhileIterating();
186     void jsForInStatement_arrays();
187     void jsForInStatement_nullAndUndefined();
188     void jsFunctionDeclarationAsStatement();
189     void stringObjects();
190     void jsStringPrototypeReplaceBugs();
191     void getterSetterThisObject_global();
192     void getterSetterThisObject_plain();
193     void getterSetterThisObject_prototypeChain();
194     void getterSetterThisObject_activation();
195     void jsContinueInSwitch();
196     void jsShadowReadOnlyPrototypeProperty();
197     void toObject();
198     void jsReservedWords_data();
199     void jsReservedWords();
200     void jsFutureReservedWords_data();
201     void jsFutureReservedWords();
202     void jsThrowInsideWithStatement();
203     void getSetAgent_ownership();
204     void getSetAgent_deleteAgent();
205     void getSetAgent_differentEngine();
206     void reentrancy_stringHandles();
207     void reentrancy_processEventsInterval();
208     void reentrancy_typeConversion();
209     void reentrancy_globalObjectProperties();
210     void reentrancy_Array();
211     void reentrancy_objectCreation();
212     void jsIncDecNonObjectProperty();
213     void installTranslatorFunctions_data();
214     void installTranslatorFunctions();
215     void translateScript_data();
216     void translateScript();
217     void translateScript_crossScript();
218     void translateScript_callQsTrFromNative();
219     void translateScript_trNoOp();
220     void translateScript_callQsTrFromCpp();
221     void translateWithInvalidArgs_data();
222     void translateWithInvalidArgs();
223     void translationContext_data();
224     void translationContext();
225     void translateScriptIdBased();
226     void translateScriptUnicode_data();
227     void translateScriptUnicode();
228     void translateScriptUnicodeIdBased_data();
229     void translateScriptUnicodeIdBased();
230     void translateFromBuiltinCallback();
231     void functionScopes();
232     void nativeFunctionScopes();
233     void evaluateProgram();
234     void evaluateProgram_customScope();
235     void evaluateProgram_closure();
236     void evaluateProgram_executeLater();
237     void evaluateProgram_multipleEngines();
238     void evaluateProgram_empty();
239     void collectGarbageAfterConnect();
240     void collectGarbageAfterNativeArguments();
241     void promoteThisObjectToQObjectInConstructor();
242     void scriptValueFromQMetaObject();
243 
244     void qRegExpInport_data();
245     void qRegExpInport();
246     void reentrency();
247     void newFixedStaticScopeObject();
248     void newGrowingStaticScopeObject();
249     void dateRoundtripJSQtJS();
250     void dateRoundtripQtJSQt();
251     void dateConversionJSQt();
252     void dateConversionQtJS();
253     void stringListFromArrayWithEmptyElement();
254 };
255 
tst_QScriptEngine()256 tst_QScriptEngine::tst_QScriptEngine()
257 {
258 }
259 
~tst_QScriptEngine()260 tst_QScriptEngine::~tst_QScriptEngine()
261 {
262 }
263 
constructWithParent()264 void tst_QScriptEngine::constructWithParent()
265 {
266     QPointer<QScriptEngine> ptr;
267     {
268         QObject obj;
269         QScriptEngine *engine = new QScriptEngine(&obj);
270         ptr = engine;
271     }
272     QVERIFY(ptr == 0);
273 }
274 
currentContext()275 void tst_QScriptEngine::currentContext()
276 {
277     QScriptEngine eng;
278     QScriptContext *globalCtx = eng.currentContext();
279     QVERIFY(globalCtx != 0);
280     QVERIFY(globalCtx->parentContext() == 0);
281     QCOMPARE(globalCtx->engine(), &eng);
282     QCOMPARE(globalCtx->argumentCount(), 0);
283     QCOMPARE(globalCtx->backtrace().size(), 1);
284     QVERIFY(!globalCtx->isCalledAsConstructor());
285     QVERIFY(!globalCtx->callee().isValid());
286     QCOMPARE(globalCtx->state(), QScriptContext::NormalState);
287     QVERIFY(globalCtx->thisObject().strictlyEquals(eng.globalObject()));
288     QVERIFY(globalCtx->activationObject().strictlyEquals(eng.globalObject()));
289     QVERIFY(globalCtx->argumentsObject().isObject());
290 }
291 
pushPopContext()292 void tst_QScriptEngine::pushPopContext()
293 {
294     QScriptEngine eng;
295     QScriptContext *globalCtx = eng.currentContext();
296     QScriptContext *ctx = eng.pushContext();
297     QVERIFY(ctx != 0);
298     QCOMPARE(ctx->parentContext(), globalCtx);
299     QVERIFY(!ctx->isCalledAsConstructor());
300     QVERIFY(!ctx->callee().isValid());
301     QVERIFY(ctx->thisObject().strictlyEquals(eng.globalObject()));
302     QCOMPARE(ctx->argumentCount(), 0);
303     QCOMPARE(ctx->backtrace().size(), 2);
304     QCOMPARE(ctx->engine(), &eng);
305     QCOMPARE(ctx->state(), QScriptContext::NormalState);
306     QVERIFY(ctx->activationObject().isObject());
307     QVERIFY(ctx->argumentsObject().isObject());
308 
309     QScriptContext *ctx2 = eng.pushContext();
310     QVERIFY(ctx2 != 0);
311     QCOMPARE(ctx2->parentContext(), ctx);
312     QVERIFY(!ctx2->activationObject().strictlyEquals(ctx->activationObject()));
313     QVERIFY(!ctx2->argumentsObject().strictlyEquals(ctx->argumentsObject()));
314 
315     eng.popContext();
316     eng.popContext();
317     QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::popContext() doesn't match with pushContext()");
318     eng.popContext(); // ignored
319     QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::popContext() doesn't match with pushContext()");
320     eng.popContext(); // ignored
321 }
322 
myFunction(QScriptContext *,QScriptEngine * eng)323 static QScriptValue myFunction(QScriptContext *, QScriptEngine *eng)
324 {
325     return eng->nullValue();
326 }
327 
myFunctionWithVoidArg(QScriptContext *,QScriptEngine * eng,void *)328 static QScriptValue myFunctionWithVoidArg(QScriptContext *, QScriptEngine *eng, void *)
329 {
330     return eng->nullValue();
331 }
332 
myThrowingFunction(QScriptContext * ctx,QScriptEngine *)333 static QScriptValue myThrowingFunction(QScriptContext *ctx, QScriptEngine *)
334 {
335     return ctx->throwError("foo");
336 }
337 
newFunction()338 void tst_QScriptEngine::newFunction()
339 {
340     QScriptEngine eng;
341     {
342         QScriptValue fun = eng.newFunction(myFunction);
343         QCOMPARE(fun.isValid(), true);
344         QCOMPARE(fun.isFunction(), true);
345         QCOMPARE(fun.isObject(), true);
346         QCOMPARE(fun.scriptClass(), (QScriptClass*)0);
347         // a prototype property is automatically constructed
348         {
349             QScriptValue prot = fun.property("prototype", QScriptValue::ResolveLocal);
350             QVERIFY(prot.isObject());
351             QVERIFY(prot.property("constructor").strictlyEquals(fun));
352             QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable | QScriptValue::SkipInEnumeration);
353             QCOMPARE(prot.propertyFlags("constructor"), QScriptValue::SkipInEnumeration);
354         }
355         // prototype should be Function.prototype
356         QCOMPARE(fun.prototype().isValid(), true);
357         QCOMPARE(fun.prototype().isFunction(), true);
358         QCOMPARE(fun.prototype().strictlyEquals(eng.evaluate("Function.prototype")), true);
359 
360         QCOMPARE(fun.call().isNull(), true);
361         QCOMPARE(fun.construct().isObject(), true);
362     }
363 }
364 
newFunctionWithArg()365 void tst_QScriptEngine::newFunctionWithArg()
366 {
367     QScriptEngine eng;
368     {
369         QScriptValue fun = eng.newFunction(myFunctionWithVoidArg, (void*)this);
370         QVERIFY(fun.isFunction());
371         QCOMPARE(fun.scriptClass(), (QScriptClass*)0);
372         // a prototype property is automatically constructed
373         {
374             QScriptValue prot = fun.property("prototype", QScriptValue::ResolveLocal);
375             QVERIFY(prot.isObject());
376             QVERIFY(prot.property("constructor").strictlyEquals(fun));
377             QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable | QScriptValue::SkipInEnumeration);
378             QCOMPARE(prot.propertyFlags("constructor"), QScriptValue::SkipInEnumeration);
379         }
380         // prototype should be Function.prototype
381         QCOMPARE(fun.prototype().isValid(), true);
382         QCOMPARE(fun.prototype().isFunction(), true);
383         QCOMPARE(fun.prototype().strictlyEquals(eng.evaluate("Function.prototype")), true);
384 
385         QCOMPARE(fun.call().isNull(), true);
386         QCOMPARE(fun.construct().isObject(), true);
387     }
388 }
389 
newFunctionWithProto()390 void tst_QScriptEngine::newFunctionWithProto()
391 {
392     QScriptEngine eng;
393     {
394         QScriptValue proto = eng.newObject();
395         QScriptValue fun = eng.newFunction(myFunction, proto);
396         QCOMPARE(fun.isValid(), true);
397         QCOMPARE(fun.isFunction(), true);
398         QCOMPARE(fun.isObject(), true);
399         // internal prototype should be Function.prototype
400         QCOMPARE(fun.prototype().isValid(), true);
401         QCOMPARE(fun.prototype().isFunction(), true);
402         QCOMPARE(fun.prototype().strictlyEquals(eng.evaluate("Function.prototype")), true);
403         // public prototype should be the one we passed
404         QCOMPARE(fun.property("prototype").strictlyEquals(proto), true);
405         QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable | QScriptValue::SkipInEnumeration);
406         QCOMPARE(proto.property("constructor").strictlyEquals(fun), true);
407         QCOMPARE(proto.propertyFlags("constructor"), QScriptValue::SkipInEnumeration);
408 
409         QCOMPARE(fun.call().isNull(), true);
410         QCOMPARE(fun.construct().isObject(), true);
411     }
412 }
413 
newObject()414 void tst_QScriptEngine::newObject()
415 {
416     QScriptEngine eng;
417     QScriptValue object = eng.newObject();
418     QCOMPARE(object.isValid(), true);
419     QCOMPARE(object.isObject(), true);
420     QCOMPARE(object.isFunction(), false);
421     QCOMPARE(object.scriptClass(), (QScriptClass*)0);
422     // prototype should be Object.prototype
423     QCOMPARE(object.prototype().isValid(), true);
424     QCOMPARE(object.prototype().isObject(), true);
425     QCOMPARE(object.prototype().strictlyEquals(eng.evaluate("Object.prototype")), true);
426 }
427 
newArray()428 void tst_QScriptEngine::newArray()
429 {
430     QScriptEngine eng;
431     QScriptValue array = eng.newArray();
432     QCOMPARE(array.isValid(), true);
433     QCOMPARE(array.isArray(), true);
434     QCOMPARE(array.isObject(), true);
435     QVERIFY(!array.isFunction());
436     QCOMPARE(array.scriptClass(), (QScriptClass*)0);
437     // prototype should be Array.prototype
438     QCOMPARE(array.prototype().isValid(), true);
439     QCOMPARE(array.prototype().isArray(), true);
440     QCOMPARE(array.prototype().strictlyEquals(eng.evaluate("Array.prototype")), true);
441 }
442 
newArray_HooliganTask218092()443 void tst_QScriptEngine::newArray_HooliganTask218092()
444 {
445     QScriptEngine eng;
446     {
447         QScriptValue ret = eng.evaluate("[].splice(0, 0, 'a')");
448         QVERIFY(ret.isArray());
449         QCOMPARE(ret.property("length").toInt32(), 0);
450     }
451     {
452         QScriptValue ret = eng.evaluate("['a'].splice(0, 1, 'b')");
453         QVERIFY(ret.isArray());
454         QCOMPARE(ret.property("length").toInt32(), 1);
455     }
456     {
457         QScriptValue ret = eng.evaluate("['a', 'b'].splice(0, 1, 'c')");
458         QVERIFY(ret.isArray());
459         QCOMPARE(ret.property("length").toInt32(), 1);
460     }
461     {
462         QScriptValue ret = eng.evaluate("['a', 'b', 'c'].splice(0, 2, 'd')");
463         QVERIFY(ret.isArray());
464         QCOMPARE(ret.property("length").toInt32(), 2);
465     }
466     {
467         QScriptValue ret = eng.evaluate("['a', 'b', 'c'].splice(1, 2, 'd', 'e', 'f')");
468         QVERIFY(ret.isArray());
469         QCOMPARE(ret.property("length").toInt32(), 2);
470     }
471 }
472 
newArray_HooliganTask233836()473 void tst_QScriptEngine::newArray_HooliganTask233836()
474 {
475     QScriptEngine eng;
476     {
477         // According to ECMA-262, this should cause a RangeError.
478         QScriptValue ret = eng.evaluate("a = new Array(4294967295); a.push('foo')");
479         QEXPECT_FAIL("", "ECMA compliance bug in Array.prototype.push: https://bugs.webkit.org/show_bug.cgi?id=55033", Continue);
480         QVERIFY(ret.isError() && ret.toString().contains(QLatin1String("RangeError")));
481     }
482     {
483         QScriptValue ret = eng.newArray(0xFFFFFFFF);
484         QCOMPARE(ret.property("length").toUInt32(), uint(0xFFFFFFFF));
485         ret.setProperty(0xFFFFFFFF, 123);
486         QCOMPARE(ret.property("length").toUInt32(), uint(0xFFFFFFFF));
487         QVERIFY(ret.property(0xFFFFFFFF).isNumber());
488         QCOMPARE(ret.property(0xFFFFFFFF).toInt32(), 123);
489         ret.setProperty(123, 456);
490         QCOMPARE(ret.property("length").toUInt32(), uint(0xFFFFFFFF));
491         QVERIFY(ret.property(123).isNumber());
492         QCOMPARE(ret.property(123).toInt32(), 456);
493     }
494 }
495 
newVariant()496 void tst_QScriptEngine::newVariant()
497 {
498     QScriptEngine eng;
499     {
500         QScriptValue opaque = eng.newVariant(QVariant());
501         QCOMPARE(opaque.isValid(), true);
502         QCOMPARE(opaque.isVariant(), true);
503         QVERIFY(!opaque.isFunction());
504         QCOMPARE(opaque.isObject(), true);
505         QCOMPARE(opaque.prototype().isValid(), true);
506         QCOMPARE(opaque.prototype().isVariant(), true);
507         QVERIFY(opaque.property("valueOf").call(opaque).isUndefined());
508     }
509 }
510 
newVariant_defaultPrototype()511 void tst_QScriptEngine::newVariant_defaultPrototype()
512 {
513     // default prototype should be set automatically
514     QScriptEngine eng;
515     {
516         QScriptValue proto = eng.newObject();
517         eng.setDefaultPrototype(qMetaTypeId<QString>(), proto);
518         QScriptValue ret = eng.newVariant(QVariant(QString::fromLatin1("hello")));
519         QVERIFY(ret.isVariant());
520         QCOMPARE(ret.scriptClass(), (QScriptClass*)0);
521         QVERIFY(ret.prototype().strictlyEquals(proto));
522         eng.setDefaultPrototype(qMetaTypeId<QString>(), QScriptValue());
523         QScriptValue ret2 = eng.newVariant(QVariant(QString::fromLatin1("hello")));
524         QVERIFY(ret2.isVariant());
525         QVERIFY(!ret2.prototype().strictlyEquals(proto));
526     }
527 }
528 
newVariant_promoteObject()529 void tst_QScriptEngine::newVariant_promoteObject()
530 {
531     // "promote" plain object to variant
532     QScriptEngine eng;
533     {
534         QScriptValue object = eng.newObject();
535         object.setProperty("foo", eng.newObject());
536         object.setProperty("bar", object.property("foo"));
537         QVERIFY(object.property("foo").isObject());
538         QVERIFY(!object.property("foo").isVariant());
539         QScriptValue originalProto = object.property("foo").prototype();
540         QScriptValue ret = eng.newVariant(object.property("foo"), QVariant(123));
541         QVERIFY(ret.isValid());
542         QVERIFY(ret.strictlyEquals(object.property("foo")));
543         QVERIFY(ret.isVariant());
544         QVERIFY(object.property("foo").isVariant());
545         QVERIFY(object.property("bar").isVariant());
546         QCOMPARE(ret.toVariant(), QVariant(123));
547         QVERIFY(ret.prototype().strictlyEquals(originalProto));
548     }
549 }
550 
newVariant_replaceValue()551 void tst_QScriptEngine::newVariant_replaceValue()
552 {
553     // replace value of existing object
554     QScriptEngine eng;
555     {
556         QScriptValue object = eng.newVariant(QVariant(123));
557         for (int x = 0; x < 2; ++x) {
558             QScriptValue ret = eng.newVariant(object, QVariant(456));
559             QVERIFY(ret.isValid());
560             QVERIFY(ret.strictlyEquals(object));
561             QVERIFY(ret.isVariant());
562             QCOMPARE(ret.toVariant(), QVariant(456));
563         }
564     }
565 }
566 
newVariant_valueOfToString()567 void tst_QScriptEngine::newVariant_valueOfToString()
568 {
569     // valueOf() and toString()
570     QScriptEngine eng;
571     {
572         QScriptValue object = eng.newVariant(QVariant(123));
573         QScriptValue value = object.property("valueOf").call(object);
574         QVERIFY(value.isNumber());
575         QCOMPARE(value.toInt32(), 123);
576         QCOMPARE(object.toString(), QString::fromLatin1("123"));
577         QCOMPARE(object.toVariant().toString(), object.toString());
578     }
579     {
580         QScriptValue object = eng.newVariant(QVariant(QString::fromLatin1("hello")));
581         QScriptValue value = object.property("valueOf").call(object);
582         QVERIFY(value.isString());
583         QCOMPARE(value.toString(), QString::fromLatin1("hello"));
584         QCOMPARE(object.toString(), QString::fromLatin1("hello"));
585         QCOMPARE(object.toVariant().toString(), object.toString());
586     }
587     {
588         QScriptValue object = eng.newVariant(QVariant(false));
589         QScriptValue value = object.property("valueOf").call(object);
590         QVERIFY(value.isBoolean());
591         QCOMPARE(value.toBoolean(), false);
592         QCOMPARE(object.toString(), QString::fromLatin1("false"));
593         QCOMPARE(object.toVariant().toString(), object.toString());
594     }
595     {
596         QScriptValue object = eng.newVariant(QVariant(QPoint(10, 20)));
597         QScriptValue value = object.property("valueOf").call(object);
598         QVERIFY(value.isObject());
599         QVERIFY(value.strictlyEquals(object));
600         QCOMPARE(object.toString(), QString::fromLatin1("QVariant(QPoint)"));
601     }
602 }
603 
newVariant_promoteNonObject()604 void tst_QScriptEngine::newVariant_promoteNonObject()
605 {
606     QScriptEngine eng;
607     {
608         QVariant var(456);
609         QScriptValue ret = eng.newVariant(123, var);
610         QVERIFY(ret.isVariant());
611         QCOMPARE(ret.toVariant(), var);
612     }
613 }
614 
newVariant_promoteNonQScriptObject()615 void tst_QScriptEngine::newVariant_promoteNonQScriptObject()
616 {
617     QScriptEngine eng;
618     {
619         QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::newVariant(): changing class of non-QScriptObject not supported");
620         QScriptValue ret = eng.newVariant(eng.newArray(), 123);
621         QVERIFY(!ret.isValid());
622     }
623 }
624 
newRegExp()625 void tst_QScriptEngine::newRegExp()
626 {
627     QScriptEngine eng;
628     for (int x = 0; x < 2; ++x) {
629         QScriptValue rexp;
630         if (x == 0)
631             rexp = eng.newRegExp("foo", "bar");
632         else
633             rexp = eng.newRegExp(QRegExp("foo"));
634         QCOMPARE(rexp.isValid(), true);
635         QCOMPARE(rexp.isRegExp(), true);
636         QCOMPARE(rexp.isObject(), true);
637         QVERIFY(rexp.isFunction()); // in JSC, RegExp objects are callable
638         // prototype should be RegExp.prototype
639         QCOMPARE(rexp.prototype().isValid(), true);
640         QCOMPARE(rexp.prototype().isObject(), true);
641         QCOMPARE(rexp.prototype().isRegExp(), false);
642         QCOMPARE(rexp.prototype().strictlyEquals(eng.evaluate("RegExp.prototype")), true);
643 
644         QCOMPARE(rexp.toRegExp().pattern(), QRegExp("foo").pattern());
645     }
646 }
647 
jsRegExp()648 void tst_QScriptEngine::jsRegExp()
649 {
650     // See ECMA-262 Section 15.10, "RegExp Objects".
651     // These should really be JS-only tests, as they test the implementation's
652     // ECMA-compliance, not the C++ API. Compliance should already be covered
653     // by the Mozilla tests (qscriptjstestsuite).
654     // We can consider updating the expected results of this test if the
655     // RegExp implementation changes.
656 
657     QScriptEngine eng;
658     QScriptValue r = eng.evaluate("/foo/gim");
659     QVERIFY(r.isRegExp());
660     QCOMPARE(r.toString(), QString::fromLatin1("/foo/gim"));
661 
662     QScriptValue rxCtor = eng.globalObject().property("RegExp");
663     QScriptValue r2 = rxCtor.call(QScriptValue(), QScriptValueList() << r);
664     QVERIFY(r2.isRegExp());
665     QVERIFY(r2.strictlyEquals(r));
666 
667     QScriptValue r3 = rxCtor.call(QScriptValue(), QScriptValueList() << r << "gim");
668     QVERIFY(r3.isError());
669     QVERIFY(r3.toString().contains(QString::fromLatin1("TypeError"))); // Cannot supply flags when constructing one RegExp from another
670 
671     QScriptValue r4 = rxCtor.call(QScriptValue(), QScriptValueList() << "foo" << "gim");
672     QVERIFY(r4.isRegExp());
673 
674     QScriptValue r5 = rxCtor.construct(QScriptValueList() << r);
675     QVERIFY(r5.isRegExp());
676     QCOMPARE(r5.toString(), QString::fromLatin1("/foo/gim"));
677     // In JSC, constructing a RegExp from another produces the same identical object.
678     // This is different from SpiderMonkey and old back-end.
679     QEXPECT_FAIL("", "RegExp copy-constructor should return a new object: https://bugs.webkit.org/show_bug.cgi?id=55040", Continue);
680     QVERIFY(!r5.strictlyEquals(r));
681 
682     QScriptValue r6 = rxCtor.construct(QScriptValueList() << "foo" << "bar");
683     QVERIFY(r6.isError());
684     QVERIFY(r6.toString().contains(QString::fromLatin1("SyntaxError"))); // Invalid regular expression flag
685 
686     QScriptValue r7 = eng.evaluate("/foo/gimp");
687     QVERIFY(r7.isError());
688     QVERIFY(r7.toString().contains(QString::fromLatin1("SyntaxError"))); // Invalid regular expression flag
689 
690     // JSC doesn't complain about duplicate flags.
691     QScriptValue r8 = eng.evaluate("/foo/migmigmig");
692     QVERIFY(r8.isRegExp());
693     QCOMPARE(r8.toString(), QString::fromLatin1("/foo/gim"));
694 
695     QScriptValue r9 = rxCtor.construct();
696     QVERIFY(r9.isRegExp());
697     QCOMPARE(r9.toString(), QString::fromLatin1("/(?:)/"));
698 
699     QScriptValue r10 = rxCtor.construct(QScriptValueList() << "" << "gim");
700     QVERIFY(r10.isRegExp());
701     QCOMPARE(r10.toString(), QString::fromLatin1("/(?:)/gim"));
702 
703     QScriptValue r11 = rxCtor.construct(QScriptValueList() << "{1.*}" << "g");
704     QVERIFY(r11.isRegExp());
705     QCOMPARE(r11.toString(), QString::fromLatin1("/{1.*}/g"));
706 }
707 
newDate()708 void tst_QScriptEngine::newDate()
709 {
710     QScriptEngine eng;
711 
712     {
713         QScriptValue date = eng.newDate(0);
714         QCOMPARE(date.isValid(), true);
715         QCOMPARE(date.isDate(), true);
716         QCOMPARE(date.isObject(), true);
717         QVERIFY(!date.isFunction());
718         // prototype should be Date.prototype
719         QCOMPARE(date.prototype().isValid(), true);
720         QCOMPARE(date.prototype().isDate(), true);
721         QCOMPARE(date.prototype().strictlyEquals(eng.evaluate("Date.prototype")), true);
722     }
723 
724     {
725         QDateTime dt = QDateTime(QDate(1, 2, 3), QTime(4, 5, 6, 7), Qt::LocalTime);
726         QScriptValue date = eng.newDate(dt);
727         QCOMPARE(date.isValid(), true);
728         QCOMPARE(date.isDate(), true);
729         QCOMPARE(date.isObject(), true);
730         // prototype should be Date.prototype
731         QCOMPARE(date.prototype().isValid(), true);
732         QCOMPARE(date.prototype().isDate(), true);
733         QCOMPARE(date.prototype().strictlyEquals(eng.evaluate("Date.prototype")), true);
734 
735         QCOMPARE(date.toDateTime(), dt);
736     }
737 
738     {
739         QDateTime dt = QDateTime(QDate(1, 2, 3), QTime(4, 5, 6, 7), Qt::UTC);
740         QScriptValue date = eng.newDate(dt);
741         // toDateTime() result should be in local time
742         QCOMPARE(date.toDateTime(), dt.toLocalTime());
743     }
744 }
745 
jsParseDate()746 void tst_QScriptEngine::jsParseDate()
747 {
748     QScriptEngine eng;
749     // Date.parse() should return NaN when it fails
750     {
751         QScriptValue ret = eng.evaluate("Date.parse()");
752         QVERIFY(ret.isNumber());
753         QVERIFY(qIsNaN(ret.toNumber()));
754     }
755 
756     // Date.parse() should be able to parse the output of Date().toString()
757 #ifndef Q_WS_WIN // TODO: Test and remove this since 169701 has been fixed
758     {
759         QScriptValue ret = eng.evaluate("var x = new Date(); var s = x.toString(); s == new Date(Date.parse(s)).toString()");
760         QVERIFY(ret.isBoolean());
761         QCOMPARE(ret.toBoolean(), true);
762     }
763 #endif
764 }
765 
newQObject()766 void tst_QScriptEngine::newQObject()
767 {
768     QScriptEngine eng;
769 
770     {
771         QScriptValue qobject = eng.newQObject(0);
772         QCOMPARE(qobject.isValid(), true);
773         QCOMPARE(qobject.isNull(), true);
774         QCOMPARE(qobject.isObject(), false);
775         QCOMPARE(qobject.toQObject(), (QObject *)0);
776     }
777     {
778         QScriptValue qobject = eng.newQObject(this);
779         QCOMPARE(qobject.isValid(), true);
780         QCOMPARE(qobject.isQObject(), true);
781         QCOMPARE(qobject.isObject(), true);
782         QCOMPARE(qobject.toQObject(), (QObject *)this);
783         QVERIFY(!qobject.isFunction());
784         // prototype should be QObject.prototype
785         QCOMPARE(qobject.prototype().isValid(), true);
786         QCOMPARE(qobject.prototype().isQObject(), true);
787         QCOMPARE(qobject.scriptClass(), (QScriptClass*)0);
788     }
789 }
790 
newQObject_ownership()791 void tst_QScriptEngine::newQObject_ownership()
792 {
793     QScriptEngine eng;
794     {
795         QPointer<QObject> ptr = new QObject();
796         QVERIFY(ptr != 0);
797         {
798             QScriptValue v = eng.newQObject(ptr, QScriptEngine::ScriptOwnership);
799         }
800         eng.evaluate("gc()");
801         if (ptr)
802             QEXPECT_FAIL("", "In the JSC-based back-end, script-owned QObjects are not always deleted immediately during GC", Continue);
803         QVERIFY(ptr == 0);
804     }
805     {
806         QPointer<QObject> ptr = new QObject();
807         QVERIFY(ptr != 0);
808         {
809             QScriptValue v = eng.newQObject(ptr, QScriptEngine::QtOwnership);
810         }
811         QObject *before = ptr;
812         eng.evaluate("gc()");
813         QVERIFY(ptr == before);
814         delete ptr;
815     }
816     {
817         QObject *parent = new QObject();
818         QObject *child = new QObject(parent);
819         QScriptValue v = eng.newQObject(child, QScriptEngine::QtOwnership);
820         QCOMPARE(v.toQObject(), child);
821         delete parent;
822         QCOMPARE(v.toQObject(), (QObject *)0);
823     }
824     {
825         QPointer<QObject> ptr = new QObject();
826         QVERIFY(ptr != 0);
827         {
828             QScriptValue v = eng.newQObject(ptr, QScriptEngine::AutoOwnership);
829         }
830         eng.evaluate("gc()");
831         // no parent, so it should be like ScriptOwnership
832         if (ptr)
833             QEXPECT_FAIL("", "In the JSC-based back-end, script-owned QObjects are not always deleted immediately during GC", Continue);
834         QVERIFY(ptr == 0);
835     }
836     {
837         QObject *parent = new QObject();
838         QPointer<QObject> child = new QObject(parent);
839         QVERIFY(child != 0);
840         {
841             QScriptValue v = eng.newQObject(child, QScriptEngine::AutoOwnership);
842         }
843         eng.evaluate("gc()");
844         // has parent, so it should be like QtOwnership
845         QVERIFY(child != 0);
846         delete parent;
847     }
848 }
849 
newQObject_promoteObject()850 void tst_QScriptEngine::newQObject_promoteObject()
851 {
852     QScriptEngine eng;
853     // "promote" plain object to QObject
854     {
855         QScriptValue obj = eng.newObject();
856         QScriptValue originalProto = obj.prototype();
857         QScriptValue ret = eng.newQObject(obj, this);
858         QVERIFY(ret.isValid());
859         QVERIFY(ret.isQObject());
860         QVERIFY(ret.strictlyEquals(obj));
861         QVERIFY(obj.isQObject());
862         QCOMPARE(ret.toQObject(), (QObject *)this);
863         QVERIFY(ret.prototype().strictlyEquals(originalProto));
864         QScriptValue val = ret.property("objectName");
865         QVERIFY(val.isString());
866     }
867     // "promote" variant object to QObject
868     {
869         QScriptValue obj = eng.newVariant(123);
870         QVERIFY(obj.isVariant());
871         QScriptValue originalProto = obj.prototype();
872         QScriptValue ret = eng.newQObject(obj, this);
873         QVERIFY(ret.isQObject());
874         QVERIFY(ret.strictlyEquals(obj));
875         QVERIFY(obj.isQObject());
876         QCOMPARE(ret.toQObject(), (QObject *)this);
877         QVERIFY(ret.prototype().strictlyEquals(originalProto));
878     }
879     // replace QObject* of existing object
880     {
881         QScriptValue object = eng.newVariant(123);
882         QScriptValue originalProto = object.prototype();
883         QObject otherQObject;
884         for (int x = 0; x < 2; ++x) {
885             QScriptValue ret = eng.newQObject(object, &otherQObject);
886             QVERIFY(ret.isValid());
887             QVERIFY(ret.isQObject());
888             QVERIFY(ret.strictlyEquals(object));
889             QCOMPARE(ret.toQObject(), (QObject *)&otherQObject);
890             QVERIFY(ret.prototype().strictlyEquals(originalProto));
891         }
892     }
893 }
894 
newQObject_sameQObject()895 void tst_QScriptEngine::newQObject_sameQObject()
896 {
897     QScriptEngine eng;
898     // calling newQObject() several times with same object
899     for (int x = 0; x < 2; ++x) {
900         QObject qobj;
901         // the default is to create a new wrapper object
902         QScriptValue obj1 = eng.newQObject(&qobj);
903         QScriptValue obj2 = eng.newQObject(&qobj);
904         QVERIFY(!obj2.strictlyEquals(obj1));
905 
906         QScriptEngine::QObjectWrapOptions opt = 0;
907         bool preferExisting = (x != 0);
908         if (preferExisting)
909             opt |= QScriptEngine::PreferExistingWrapperObject;
910 
911         QScriptValue obj3 = eng.newQObject(&qobj, QScriptEngine::AutoOwnership, opt);
912         QVERIFY(!obj3.strictlyEquals(obj2));
913         QScriptValue obj4 = eng.newQObject(&qobj, QScriptEngine::AutoOwnership, opt);
914         QCOMPARE(obj4.strictlyEquals(obj3), preferExisting);
915 
916         QScriptValue obj5 = eng.newQObject(&qobj, QScriptEngine::ScriptOwnership, opt);
917         QVERIFY(!obj5.strictlyEquals(obj4));
918         QScriptValue obj6 = eng.newQObject(&qobj, QScriptEngine::ScriptOwnership, opt);
919         QCOMPARE(obj6.strictlyEquals(obj5), preferExisting);
920 
921         QScriptValue obj7 = eng.newQObject(&qobj, QScriptEngine::ScriptOwnership,
922                                            QScriptEngine::ExcludeSuperClassMethods | opt);
923         QVERIFY(!obj7.strictlyEquals(obj6));
924         QScriptValue obj8 = eng.newQObject(&qobj, QScriptEngine::ScriptOwnership,
925                                            QScriptEngine::ExcludeSuperClassMethods | opt);
926         QCOMPARE(obj8.strictlyEquals(obj7), preferExisting);
927     }
928 }
929 
newQObject_defaultPrototype()930 void tst_QScriptEngine::newQObject_defaultPrototype()
931 {
932     QScriptEngine eng;
933     // newQObject() should set the default prototype, if one has been registered
934     {
935         QScriptValue oldQObjectProto = eng.defaultPrototype(qMetaTypeId<QObject*>());
936 
937         QScriptValue qobjectProto = eng.newObject();
938         eng.setDefaultPrototype(qMetaTypeId<QObject*>(), qobjectProto);
939         {
940             QScriptValue ret = eng.newQObject(this);
941             QVERIFY(ret.prototype().equals(qobjectProto));
942         }
943         QScriptValue tstProto = eng.newObject();
944         int typeId = qRegisterMetaType<tst_QScriptEngine*>("tst_QScriptEngine*");
945         eng.setDefaultPrototype(typeId, tstProto);
946         {
947             QScriptValue ret = eng.newQObject(this);
948             QVERIFY(ret.prototype().equals(tstProto));
949         }
950 
951         eng.setDefaultPrototype(qMetaTypeId<QObject*>(), oldQObjectProto);
952         eng.setDefaultPrototype(typeId, QScriptValue());
953     }
954 }
955 
newQObject_promoteNonObject()956 void tst_QScriptEngine::newQObject_promoteNonObject()
957 {
958     QScriptEngine eng;
959     {
960         QScriptValue ret = eng.newQObject(123, this);
961         QVERIFY(ret.isQObject());
962         QCOMPARE(ret.toQObject(), this);
963     }
964 }
965 
newQObject_promoteNonQScriptObject()966 void tst_QScriptEngine::newQObject_promoteNonQScriptObject()
967 {
968     QScriptEngine eng;
969     {
970         QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::newQObject(): changing class of non-QScriptObject not supported");
971         QScriptValue ret = eng.newQObject(eng.newArray(), this);
972         QVERIFY(!ret.isValid());
973     }
974 }
975 
976 QT_BEGIN_NAMESPACE
Q_SCRIPT_DECLARE_QMETAOBJECT(QObject,QObject *)977 Q_SCRIPT_DECLARE_QMETAOBJECT(QObject, QObject*)
978 Q_SCRIPT_DECLARE_QMETAOBJECT(QWidget, QWidget*)
979 QT_END_NAMESPACE
980 
981 static QScriptValue myConstructor(QScriptContext *ctx, QScriptEngine *eng)
982 {
983     QScriptValue obj;
984     if (ctx->isCalledAsConstructor()) {
985         obj = ctx->thisObject();
986     } else {
987         obj = eng->newObject();
988         obj.setPrototype(ctx->callee().property("prototype"));
989     }
990     obj.setProperty("isCalledAsConstructor", QScriptValue(eng, ctx->isCalledAsConstructor()));
991     return obj;
992 }
993 
instanceofJS(const QScriptValue & inst,const QScriptValue & ctor)994 static QScriptValue instanceofJS(const QScriptValue &inst, const QScriptValue &ctor)
995 {
996     return inst.engine()->evaluate("(function(inst, ctor) { return inst instanceof ctor; })")
997         .call(QScriptValue(), QScriptValueList() << inst << ctor);
998 }
999 
newQMetaObject()1000 void tst_QScriptEngine::newQMetaObject()
1001 {
1002     QScriptEngine eng;
1003 #if 0
1004     QScriptValue qclass = eng.newQMetaObject<QObject>();
1005     QScriptValue qclass2 = eng.newQMetaObject<QWidget>();
1006 #else
1007     QScriptValue qclass = qScriptValueFromQMetaObject<QObject>(&eng);
1008     QScriptValue qclass2 = qScriptValueFromQMetaObject<QWidget>(&eng);
1009 #endif
1010     QCOMPARE(qclass.isValid(), true);
1011     QCOMPARE(qclass.isQMetaObject(), true);
1012     QCOMPARE(qclass.toQMetaObject(), &QObject::staticMetaObject);
1013     QCOMPARE(qclass.isFunction(), true);
1014     QVERIFY(qclass.property("prototype").isObject());
1015 
1016     QCOMPARE(qclass2.isValid(), true);
1017     QCOMPARE(qclass2.isQMetaObject(), true);
1018     QCOMPARE(qclass2.toQMetaObject(), &QWidget::staticMetaObject);
1019     QCOMPARE(qclass2.isFunction(), true);
1020     QVERIFY(qclass2.property("prototype").isObject());
1021 
1022     // prototype should be QMetaObject.prototype
1023     QCOMPARE(qclass.prototype().isObject(), true);
1024     QCOMPARE(qclass2.prototype().isObject(), true);
1025 
1026     QScriptValue instance = qclass.construct();
1027     QCOMPARE(instance.isQObject(), true);
1028     QCOMPARE(instance.toQObject()->metaObject(), qclass.toQMetaObject());
1029     QVERIFY(instance.instanceOf(qclass));
1030     QVERIFY(instanceofJS(instance, qclass).strictlyEquals(true));
1031 
1032     QScriptValue instance2 = qclass2.construct();
1033     QCOMPARE(instance2.isQObject(), true);
1034     QCOMPARE(instance2.toQObject()->metaObject(), qclass2.toQMetaObject());
1035     QVERIFY(instance2.instanceOf(qclass2));
1036     QVERIFY(instanceofJS(instance2, qclass2).strictlyEquals(true));
1037     QVERIFY(!instance2.instanceOf(qclass));
1038     QVERIFY(instanceofJS(instance2, qclass).strictlyEquals(false));
1039 
1040     QScriptValueList args;
1041     args << instance;
1042     QScriptValue instance3 = qclass.construct(args);
1043     QCOMPARE(instance3.isQObject(), true);
1044     QCOMPARE(instance3.toQObject()->parent(), instance.toQObject());
1045     QVERIFY(instance3.instanceOf(qclass));
1046     QVERIFY(instanceofJS(instance3, qclass).strictlyEquals(true));
1047     QVERIFY(!instance3.instanceOf(qclass2));
1048     QVERIFY(instanceofJS(instance3, qclass2).strictlyEquals(false));
1049     args.clear();
1050 
1051     QPointer<QObject> qpointer1 = instance.toQObject();
1052     QPointer<QObject> qpointer2 = instance2.toQObject();
1053     QPointer<QObject> qpointer3 = instance3.toQObject();
1054 
1055     QVERIFY(qpointer1);
1056     QVERIFY(qpointer2);
1057     QVERIFY(qpointer3);
1058 
1059     // verify that AutoOwnership is in effect
1060     instance = QScriptValue();
1061     collectGarbage_helper(eng);
1062 
1063     QVERIFY(!qpointer1);
1064     QVERIFY(qpointer2);
1065     QVERIFY(!qpointer3); // was child of instance
1066 
1067     QVERIFY(instance.toQObject() == 0);
1068     QVERIFY(instance3.toQObject() == 0); // was child of instance
1069     QVERIFY(instance2.toQObject() != 0);
1070     instance2 = QScriptValue();
1071     collectGarbage_helper(eng);
1072     QVERIFY(instance2.toQObject() == 0);
1073 
1074     // with custom constructor
1075     QScriptValue ctor = eng.newFunction(myConstructor);
1076     QScriptValue qclass3 = eng.newQMetaObject(&QObject::staticMetaObject, ctor);
1077     QVERIFY(qclass3.property("prototype").equals(ctor.property("prototype")));
1078     {
1079         QScriptValue ret = qclass3.call();
1080         QVERIFY(ret.isObject());
1081         QVERIFY(ret.property("isCalledAsConstructor").isBoolean());
1082         QVERIFY(!ret.property("isCalledAsConstructor").toBoolean());
1083         QVERIFY(ret.instanceOf(qclass3));
1084         QVERIFY(instanceofJS(ret, qclass3).strictlyEquals(true));
1085         QVERIFY(!ret.instanceOf(qclass));
1086         QVERIFY(instanceofJS(ret, qclass).strictlyEquals(false));
1087     }
1088     {
1089         QScriptValue ret = qclass3.construct();
1090         QVERIFY(ret.isObject());
1091         QVERIFY(ret.property("isCalledAsConstructor").isBoolean());
1092         QVERIFY(ret.property("isCalledAsConstructor").toBoolean());
1093         QVERIFY(ret.instanceOf(qclass3));
1094         QVERIFY(instanceofJS(ret, qclass3).strictlyEquals(true));
1095         QVERIFY(!ret.instanceOf(qclass2));
1096         QVERIFY(instanceofJS(ret, qclass2).strictlyEquals(false));
1097     }
1098 
1099     // subclassing
1100     qclass2.setProperty("prototype", qclass.construct());
1101     QVERIFY(qclass2.construct().instanceOf(qclass));
1102     QVERIFY(instanceofJS(qclass2.construct(), qclass).strictlyEquals(true));
1103 
1104     // with meta-constructor
1105     QScriptValue qclass4 = eng.newQMetaObject(&QObject::staticMetaObject);
1106     {
1107         QScriptValue inst = qclass4.construct();
1108         QVERIFY(inst.isQObject());
1109         QVERIFY(inst.toQObject() != 0);
1110         QCOMPARE(inst.toQObject()->parent(), (QObject*)0);
1111         QVERIFY(inst.instanceOf(qclass4));
1112         QVERIFY(instanceofJS(inst, qclass4).strictlyEquals(true));
1113         QVERIFY(!inst.instanceOf(qclass3));
1114         QVERIFY(instanceofJS(inst, qclass3).strictlyEquals(false));
1115     }
1116     {
1117         QScriptValue inst = qclass4.construct(QScriptValueList() << eng.newQObject(this));
1118         QVERIFY(inst.isQObject());
1119         QVERIFY(inst.toQObject() != 0);
1120         QCOMPARE(inst.toQObject()->parent(), (QObject*)this);
1121         QVERIFY(inst.instanceOf(qclass4));
1122         QVERIFY(instanceofJS(inst, qclass4).strictlyEquals(true));
1123         QVERIFY(!inst.instanceOf(qclass2));
1124         QVERIFY(instanceofJS(inst, qclass2).strictlyEquals(false));
1125     }
1126 }
1127 
newActivationObject()1128 void tst_QScriptEngine::newActivationObject()
1129 {
1130     QSKIP("internal function not implemented in JSC-based back-end", SkipAll);
1131     QScriptEngine eng;
1132     QScriptValue act = eng.newActivationObject();
1133     QEXPECT_FAIL("", "", Continue);
1134     QCOMPARE(act.isValid(), true);
1135     QEXPECT_FAIL("", "", Continue);
1136     QCOMPARE(act.isObject(), true);
1137     QVERIFY(!act.isFunction());
1138     QScriptValue v(&eng, 123);
1139     act.setProperty("prop", v);
1140     QEXPECT_FAIL("", "", Continue);
1141     QCOMPARE(act.property("prop").strictlyEquals(v), true);
1142     QCOMPARE(act.scope().isValid(), false);
1143     QEXPECT_FAIL("", "", Continue);
1144     QVERIFY(act.prototype().isNull());
1145 }
1146 
getSetGlobalObject()1147 void tst_QScriptEngine::getSetGlobalObject()
1148 {
1149     QScriptEngine eng;
1150     QScriptValue glob = eng.globalObject();
1151     QCOMPARE(glob.isValid(), true);
1152     QCOMPARE(glob.isObject(), true);
1153     QVERIFY(!glob.isFunction());
1154     QVERIFY(eng.currentContext()->thisObject().strictlyEquals(glob));
1155     QVERIFY(eng.currentContext()->activationObject().strictlyEquals(glob));
1156     QCOMPARE(glob.toString(), QString::fromLatin1("[object global]"));
1157     // prototype should be Object.prototype
1158     QCOMPARE(glob.prototype().isValid(), true);
1159     QCOMPARE(glob.prototype().isObject(), true);
1160     QCOMPARE(glob.prototype().strictlyEquals(eng.evaluate("Object.prototype")), true);
1161 
1162     eng.setGlobalObject(glob);
1163     QVERIFY(eng.globalObject().equals(glob));
1164     eng.setGlobalObject(123);
1165     QVERIFY(eng.globalObject().equals(glob));
1166 
1167     QScriptValue obj = eng.newObject();
1168     eng.setGlobalObject(obj);
1169     QVERIFY(eng.globalObject().strictlyEquals(obj));
1170     QVERIFY(eng.currentContext()->thisObject().strictlyEquals(obj));
1171     QVERIFY(eng.currentContext()->activationObject().strictlyEquals(obj));
1172     QVERIFY(eng.evaluate("this").strictlyEquals(obj));
1173     QCOMPARE(eng.globalObject().toString(), QString::fromLatin1("[object Object]"));
1174 
1175     glob = QScriptValue(); // kill reference to old global object
1176     collectGarbage_helper(eng);
1177     obj = eng.newObject();
1178     eng.setGlobalObject(obj);
1179     QVERIFY(eng.globalObject().strictlyEquals(obj));
1180     QVERIFY(eng.currentContext()->thisObject().strictlyEquals(obj));
1181     QVERIFY(eng.currentContext()->activationObject().strictlyEquals(obj));
1182 
1183     collectGarbage_helper(eng);
1184     QVERIFY(eng.globalObject().strictlyEquals(obj));
1185     QVERIFY(eng.currentContext()->thisObject().strictlyEquals(obj));
1186     QVERIFY(eng.currentContext()->activationObject().strictlyEquals(obj));
1187 
1188     QVERIFY(!obj.property("foo").isValid());
1189     eng.evaluate("var foo = 123");
1190     {
1191         QScriptValue ret = obj.property("foo");
1192         QVERIFY(ret.isNumber());
1193         QCOMPARE(ret.toInt32(), 123);
1194     }
1195 
1196     QVERIFY(!obj.property("bar").isValid());
1197     eng.evaluate("bar = 456");
1198     {
1199         QScriptValue ret = obj.property("bar");
1200         QVERIFY(ret.isNumber());
1201         QCOMPARE(ret.toInt32(), 456);
1202     }
1203 
1204     QVERIFY(!obj.property("baz").isValid());
1205     eng.evaluate("this['baz'] = 789");
1206     {
1207         QScriptValue ret = obj.property("baz");
1208         QVERIFY(ret.isNumber());
1209         QCOMPARE(ret.toInt32(), 789);
1210     }
1211 
1212     {
1213         QScriptValue ret = eng.evaluate("(function() { return this; })()");
1214         QVERIFY(ret.strictlyEquals(obj));
1215     }
1216 
1217     // Delete property.
1218     {
1219         QScriptValue ret = eng.evaluate("delete foo");
1220         QVERIFY(ret.isBool());
1221         QVERIFY(ret.toBool());
1222         QVERIFY(!obj.property("foo").isValid());
1223     }
1224 
1225     // Getter/setter property.
1226     QVERIFY(eng.evaluate("this.__defineGetter__('oof', function() { return this.bar; })").isUndefined());
1227     QVERIFY(eng.evaluate("this.__defineSetter__('oof', function(v) { this.bar = v; })").isUndefined());
1228     QVERIFY(eng.evaluate("this.__lookupGetter__('oof')").isFunction());
1229     QVERIFY(eng.evaluate("this.__lookupSetter__('oof')").isFunction());
1230     eng.evaluate("oof = 123");
1231     QVERIFY(eng.evaluate("oof").equals(obj.property("bar")));
1232 
1233     // Enumeration.
1234     {
1235         QScriptValue ret = eng.evaluate("a = []; for (var p in this) a.push(p); a");
1236         QCOMPARE(ret.toString(), QString::fromLatin1("bar,baz,oof,p,a"));
1237     }
1238 }
1239 
getSetFoo(QScriptContext * ctx,QScriptEngine *)1240 static QScriptValue getSetFoo(QScriptContext *ctx, QScriptEngine *)
1241 {
1242     if (ctx->argumentCount() > 0)
1243         ctx->thisObject().setProperty("foo", ctx->argument(0));
1244     return ctx->thisObject().property("foo");
1245 }
1246 
globalObjectProperties()1247 void tst_QScriptEngine::globalObjectProperties()
1248 {
1249     // See ECMA-262 Section 15.1, "The Global Object".
1250 
1251     QScriptEngine eng;
1252     QScriptValue global = eng.globalObject();
1253 
1254     QVERIFY(global.property("NaN").isNumber());
1255     QVERIFY(qIsNaN(global.property("NaN").toNumber()));
1256     QCOMPARE(global.propertyFlags("NaN"), QScriptValue::SkipInEnumeration | QScriptValue::Undeletable);
1257 
1258     QVERIFY(global.property("Infinity").isNumber());
1259     QVERIFY(qIsInf(global.property("Infinity").toNumber()));
1260     QCOMPARE(global.propertyFlags("NaN"), QScriptValue::SkipInEnumeration | QScriptValue::Undeletable);
1261 
1262     QVERIFY(global.property("undefined").isUndefined());
1263     QCOMPARE(global.propertyFlags("undefined"), QScriptValue::SkipInEnumeration | QScriptValue::Undeletable);
1264 
1265     QVERIFY(global.property("eval").isFunction());
1266     QCOMPARE(global.propertyFlags("eval"), QScriptValue::SkipInEnumeration);
1267 
1268     QVERIFY(global.property("parseInt").isFunction());
1269     QCOMPARE(global.propertyFlags("parseInt"), QScriptValue::SkipInEnumeration);
1270 
1271     QVERIFY(global.property("parseFloat").isFunction());
1272     QCOMPARE(global.propertyFlags("parseFloat"), QScriptValue::SkipInEnumeration);
1273 
1274     QVERIFY(global.property("isNaN").isFunction());
1275     QCOMPARE(global.propertyFlags("isNaN"), QScriptValue::SkipInEnumeration);
1276 
1277     QVERIFY(global.property("isFinite").isFunction());
1278     QCOMPARE(global.propertyFlags("isFinite"), QScriptValue::SkipInEnumeration);
1279 
1280     QVERIFY(global.property("decodeURI").isFunction());
1281     QCOMPARE(global.propertyFlags("decodeURI"), QScriptValue::SkipInEnumeration);
1282 
1283     QVERIFY(global.property("decodeURIComponent").isFunction());
1284     QCOMPARE(global.propertyFlags("decodeURIComponent"), QScriptValue::SkipInEnumeration);
1285 
1286     QVERIFY(global.property("encodeURI").isFunction());
1287     QCOMPARE(global.propertyFlags("encodeURI"), QScriptValue::SkipInEnumeration);
1288 
1289     QVERIFY(global.property("encodeURIComponent").isFunction());
1290     QCOMPARE(global.propertyFlags("encodeURIComponent"), QScriptValue::SkipInEnumeration);
1291 
1292     QVERIFY(global.property("Object").isFunction());
1293     QCOMPARE(global.propertyFlags("Object"), QScriptValue::SkipInEnumeration);
1294     QVERIFY(global.property("Function").isFunction());
1295     QCOMPARE(global.propertyFlags("Function"), QScriptValue::SkipInEnumeration);
1296     QVERIFY(global.property("Array").isFunction());
1297     QCOMPARE(global.propertyFlags("Array"), QScriptValue::SkipInEnumeration);
1298     QVERIFY(global.property("String").isFunction());
1299     QCOMPARE(global.propertyFlags("String"), QScriptValue::SkipInEnumeration);
1300     QVERIFY(global.property("Boolean").isFunction());
1301     QCOMPARE(global.propertyFlags("Boolean"), QScriptValue::SkipInEnumeration);
1302     QVERIFY(global.property("Number").isFunction());
1303     QCOMPARE(global.propertyFlags("Number"), QScriptValue::SkipInEnumeration);
1304     QVERIFY(global.property("Date").isFunction());
1305     QCOMPARE(global.propertyFlags("Date"), QScriptValue::SkipInEnumeration);
1306     QVERIFY(global.property("RegExp").isFunction());
1307     QCOMPARE(global.propertyFlags("RegExp"), QScriptValue::SkipInEnumeration);
1308     QVERIFY(global.property("Error").isFunction());
1309     QCOMPARE(global.propertyFlags("Error"), QScriptValue::SkipInEnumeration);
1310     QVERIFY(global.property("EvalError").isFunction());
1311     QCOMPARE(global.propertyFlags("EvalError"), QScriptValue::SkipInEnumeration);
1312     QVERIFY(global.property("RangeError").isFunction());
1313     QCOMPARE(global.propertyFlags("RangeError"), QScriptValue::SkipInEnumeration);
1314     QVERIFY(global.property("ReferenceError").isFunction());
1315     QCOMPARE(global.propertyFlags("ReferenceError"), QScriptValue::SkipInEnumeration);
1316     QVERIFY(global.property("SyntaxError").isFunction());
1317     QCOMPARE(global.propertyFlags("SyntaxError"), QScriptValue::SkipInEnumeration);
1318     QVERIFY(global.property("TypeError").isFunction());
1319     QCOMPARE(global.propertyFlags("TypeError"), QScriptValue::SkipInEnumeration);
1320     QVERIFY(global.property("URIError").isFunction());
1321     QCOMPARE(global.propertyFlags("URIError"), QScriptValue::SkipInEnumeration);
1322     QVERIFY(global.property("Math").isObject());
1323     QVERIFY(!global.property("Math").isFunction());
1324     QEXPECT_FAIL("", "[ECMA compliance] JSC sets DontDelete flag for Math object: https://bugs.webkit.org/show_bug.cgi?id=55034", Continue);
1325     QCOMPARE(global.propertyFlags("Math"), QScriptValue::SkipInEnumeration);
1326 }
1327 
globalObjectProperties_enumerate()1328 void tst_QScriptEngine::globalObjectProperties_enumerate()
1329 {
1330     QScriptEngine eng;
1331     QScriptValue global = eng.globalObject();
1332 
1333     QSet<QString> expectedNames;
1334     expectedNames
1335         << "isNaN"
1336         << "parseFloat"
1337         << "String"
1338         << "EvalError"
1339         << "URIError"
1340         << "Math"
1341         << "encodeURIComponent"
1342         << "RangeError"
1343         << "eval"
1344         << "isFinite"
1345         << "ReferenceError"
1346         << "Infinity"
1347         << "Function"
1348         << "RegExp"
1349         << "Number"
1350         << "parseInt"
1351         << "Object"
1352         << "decodeURI"
1353         << "TypeError"
1354         << "Boolean"
1355         << "encodeURI"
1356         << "NaN"
1357         << "Error"
1358         << "decodeURIComponent"
1359         << "Date"
1360         << "Array"
1361         << "escape"
1362         << "unescape"
1363         << "SyntaxError"
1364         << "undefined"
1365         // non-standard
1366         << "gc"
1367         << "version"
1368         << "print"
1369         // JavaScriptCore
1370         << "JSON"
1371         ;
1372     QSet<QString> actualNames;
1373     {
1374         QScriptValueIterator it(global);
1375         while (it.hasNext()) {
1376             it.next();
1377             actualNames.insert(it.name());
1378         }
1379     }
1380 
1381     QSet<QString> remainingNames = actualNames;
1382     {
1383         QSet<QString>::const_iterator it;
1384         for (it = expectedNames.constBegin(); it != expectedNames.constEnd(); ++it) {
1385             QString name = *it;
1386             QVERIFY(actualNames.contains(name));
1387             remainingNames.remove(name);
1388         }
1389     }
1390     QVERIFY(remainingNames.isEmpty());
1391 }
1392 
createGlobalObjectProperty()1393 void tst_QScriptEngine::createGlobalObjectProperty()
1394 {
1395     QScriptEngine eng;
1396     QScriptValue global = eng.globalObject();
1397     // create property with no attributes
1398     {
1399         QString name = QString::fromLatin1("foo");
1400         QVERIFY(!global.property(name).isValid());
1401         QScriptValue val(123);
1402         global.setProperty(name, val);
1403         QVERIFY(global.property(name).equals(val));
1404         QVERIFY(global.propertyFlags(name) == 0);
1405         global.setProperty(name, QScriptValue());
1406         QVERIFY(!global.property(name).isValid());
1407     }
1408     // create property with attributes
1409     {
1410         QString name = QString::fromLatin1("bar");
1411         QVERIFY(!global.property(name).isValid());
1412         QScriptValue val(QString::fromLatin1("ciao"));
1413         QScriptValue::PropertyFlags flags = QScriptValue::ReadOnly | QScriptValue::SkipInEnumeration;
1414         global.setProperty(name, val, flags);
1415         QVERIFY(global.property(name).equals(val));
1416         QEXPECT_FAIL("", "QTBUG-6134: custom Global Object properties don't retain attributes", Continue);
1417         QCOMPARE(global.propertyFlags(name), flags);
1418         global.setProperty(name, QScriptValue());
1419         QVERIFY(!global.property(name).isValid());
1420     }
1421 }
1422 
globalObjectGetterSetterProperty()1423 void tst_QScriptEngine::globalObjectGetterSetterProperty()
1424 {
1425     QScriptEngine engine;
1426     QScriptValue global = engine.globalObject();
1427     global.setProperty("bar", engine.newFunction(getSetFoo),
1428                        QScriptValue::PropertySetter | QScriptValue::PropertyGetter);
1429     global.setProperty("foo", 123);
1430     QVERIFY(global.property("bar").equals(global.property("foo")));
1431     QVERIFY(engine.evaluate("bar").equals(global.property("foo")));
1432     global.setProperty("bar", 456);
1433     QVERIFY(global.property("bar").equals(global.property("foo")));
1434 
1435     engine.evaluate("__defineGetter__('baz', function() { return 789; })");
1436     QVERIFY(engine.evaluate("baz").equals(789));
1437     QVERIFY(global.property("baz").equals(789));
1438 }
1439 
customGlobalObjectWithPrototype()1440 void tst_QScriptEngine::customGlobalObjectWithPrototype()
1441 {
1442     for (int x = 0; x < 2; ++x) {
1443         QScriptEngine engine;
1444         QScriptValue wrap = engine.newObject();
1445         QScriptValue global = engine.globalObject();
1446         QScriptValue originalGlobalProto = global.prototype();
1447         if (!x) {
1448             // Set prototype before setting global object
1449             wrap.setPrototype(global);
1450             QVERIFY(wrap.prototype().strictlyEquals(global));
1451             engine.setGlobalObject(wrap);
1452         } else {
1453             // Set prototype after setting global object
1454             engine.setGlobalObject(wrap);
1455             wrap.setPrototype(global);
1456             QVERIFY(wrap.prototype().strictlyEquals(global));
1457         }
1458         {
1459             QScriptValue ret = engine.evaluate("print");
1460             QVERIFY(ret.isFunction());
1461             QVERIFY(ret.strictlyEquals(wrap.property("print")));
1462         }
1463         {
1464             QScriptValue ret = engine.evaluate("this.print");
1465             QVERIFY(ret.isFunction());
1466             QVERIFY(ret.strictlyEquals(wrap.property("print")));
1467         }
1468         {
1469             QScriptValue ret = engine.evaluate("hasOwnProperty('print')");
1470             QVERIFY(ret.isBool());
1471             QVERIFY(!ret.toBool());
1472         }
1473         {
1474             QScriptValue ret = engine.evaluate("this.hasOwnProperty('print')");
1475             QVERIFY(ret.isBool());
1476             QVERIFY(!ret.toBool());
1477         }
1478 
1479         QScriptValue anotherProto = engine.newObject();
1480         anotherProto.setProperty("anotherProtoProperty", 123);
1481         global.setPrototype(anotherProto);
1482         {
1483             QScriptValue ret = engine.evaluate("print");
1484             QVERIFY(ret.isFunction());
1485             QVERIFY(ret.strictlyEquals(wrap.property("print")));
1486         }
1487         {
1488             QScriptValue ret = engine.evaluate("anotherProtoProperty");
1489             QVERIFY(ret.isNumber());
1490             QVERIFY(ret.strictlyEquals(wrap.property("anotherProtoProperty")));
1491         }
1492         {
1493             QScriptValue ret = engine.evaluate("this.anotherProtoProperty");
1494             QVERIFY(ret.isNumber());
1495             QVERIFY(ret.strictlyEquals(wrap.property("anotherProtoProperty")));
1496         }
1497 
1498         wrap.setPrototype(anotherProto);
1499         {
1500             QScriptValue ret = engine.evaluate("print");
1501             QVERIFY(ret.isError());
1502             QCOMPARE(ret.toString(), QString::fromLatin1("ReferenceError: Can't find variable: print"));
1503         }
1504         {
1505             QScriptValue ret = engine.evaluate("anotherProtoProperty");
1506             QVERIFY(ret.isNumber());
1507             QVERIFY(ret.strictlyEquals(wrap.property("anotherProtoProperty")));
1508         }
1509         QVERIFY(global.prototype().strictlyEquals(anotherProto));
1510 
1511         global.setPrototype(originalGlobalProto);
1512         engine.setGlobalObject(global);
1513         {
1514             QScriptValue ret = engine.evaluate("anotherProtoProperty");
1515             QVERIFY(ret.isError());
1516             QCOMPARE(ret.toString(), QString::fromLatin1("ReferenceError: Can't find variable: anotherProtoProperty"));
1517         }
1518         {
1519             QScriptValue ret = engine.evaluate("print");
1520             QVERIFY(ret.isFunction());
1521             QVERIFY(ret.strictlyEquals(global.property("print")));
1522         }
1523         QVERIFY(!anotherProto.property("print").isValid());
1524     }
1525 }
1526 
globalObjectWithCustomPrototype()1527 void tst_QScriptEngine::globalObjectWithCustomPrototype()
1528 {
1529     QScriptEngine engine;
1530     QScriptValue proto = engine.newObject();
1531     proto.setProperty("protoProperty", 123);
1532     QScriptValue global = engine.globalObject();
1533     QScriptValue originalProto = global.prototype();
1534     global.setPrototype(proto);
1535     {
1536         QScriptValue ret = engine.evaluate("protoProperty");
1537         QVERIFY(ret.isNumber());
1538         QVERIFY(ret.strictlyEquals(global.property("protoProperty")));
1539     }
1540     {
1541         QScriptValue ret = engine.evaluate("this.protoProperty");
1542         QVERIFY(ret.isNumber());
1543         QVERIFY(ret.strictlyEquals(global.property("protoProperty")));
1544     }
1545     {
1546         QScriptValue ret = engine.evaluate("hasOwnProperty('protoProperty')");
1547         QVERIFY(ret.isBool());
1548         QVERIFY(!ret.toBool());
1549     }
1550     {
1551         QScriptValue ret = engine.evaluate("this.hasOwnProperty('protoProperty')");
1552         QVERIFY(ret.isBool());
1553         QVERIFY(!ret.toBool());
1554     }
1555 
1556     // Custom prototype set from JS
1557     {
1558         QScriptValue ret = engine.evaluate("this.__proto__ = { 'a': 123 }; a");
1559         QVERIFY(ret.isNumber());
1560         QEXPECT_FAIL("", "QTBUG-9737: Prototype change in JS not reflected on C++ side", Continue);
1561         QVERIFY(ret.strictlyEquals(global.property("a")));
1562     }
1563 }
1564 
builtinFunctionNames_data()1565 void tst_QScriptEngine::builtinFunctionNames_data()
1566 {
1567     QTest::addColumn<QString>("expression");
1568     QTest::addColumn<QString>("expectedName");
1569 
1570     // See ECMA-262 Chapter 15, "Standard Built-in ECMAScript Objects".
1571 
1572     QTest::newRow("print") << QString("print") << QString("print"); // QtScript extension.
1573     QTest::newRow("parseInt") << QString("parseInt") << QString("parseInt");
1574     QTest::newRow("parseFloat") << QString("parseFloat") << QString("parseFloat");
1575     QTest::newRow("isNaN") << QString("isNaN") << QString("isNaN");
1576     QTest::newRow("isFinite") << QString("isFinite") << QString("isFinite");
1577     QTest::newRow("decodeURI") << QString("decodeURI") << QString("decodeURI");
1578     QTest::newRow("decodeURIComponent") << QString("decodeURIComponent") << QString("decodeURIComponent");
1579     QTest::newRow("encodeURI") << QString("encodeURI") << QString("encodeURI");
1580     QTest::newRow("encodeURIComponent") << QString("encodeURIComponent") << QString("encodeURIComponent");
1581     QTest::newRow("escape") << QString("escape") << QString("escape");
1582     QTest::newRow("unescape") << QString("unescape") << QString("unescape");
1583     QTest::newRow("version") << QString("version") << QString("version"); // QtScript extension.
1584     QTest::newRow("gc") << QString("gc") << QString("gc"); // QtScript extension.
1585 
1586     QTest::newRow("Array") << QString("Array") << QString("Array");
1587     QTest::newRow("Array.prototype.toString") << QString("Array.prototype.toString") << QString("toString");
1588     QTest::newRow("Array.prototype.toLocaleString") << QString("Array.prototype.toLocaleString") << QString("toLocaleString");
1589     QTest::newRow("Array.prototype.concat") << QString("Array.prototype.concat") << QString("concat");
1590     QTest::newRow("Array.prototype.join") << QString("Array.prototype.join") << QString("join");
1591     QTest::newRow("Array.prototype.pop") << QString("Array.prototype.pop") << QString("pop");
1592     QTest::newRow("Array.prototype.push") << QString("Array.prototype.push") << QString("push");
1593     QTest::newRow("Array.prototype.reverse") << QString("Array.prototype.reverse") << QString("reverse");
1594     QTest::newRow("Array.prototype.shift") << QString("Array.prototype.shift") << QString("shift");
1595     QTest::newRow("Array.prototype.slice") << QString("Array.prototype.slice") << QString("slice");
1596     QTest::newRow("Array.prototype.sort") << QString("Array.prototype.sort") << QString("sort");
1597     QTest::newRow("Array.prototype.splice") << QString("Array.prototype.splice") << QString("splice");
1598     QTest::newRow("Array.prototype.unshift") << QString("Array.prototype.unshift") << QString("unshift");
1599 
1600     QTest::newRow("Boolean") << QString("Boolean") << QString("Boolean");
1601     QTest::newRow("Boolean.prototype.toString") << QString("Boolean.prototype.toString") << QString("toString");
1602 
1603     QTest::newRow("Date") << QString("Date") << QString("Date");
1604     QTest::newRow("Date.prototype.toString") << QString("Date.prototype.toString") << QString("toString");
1605     QTest::newRow("Date.prototype.toDateString") << QString("Date.prototype.toDateString") << QString("toDateString");
1606     QTest::newRow("Date.prototype.toTimeString") << QString("Date.prototype.toTimeString") << QString("toTimeString");
1607     QTest::newRow("Date.prototype.toLocaleString") << QString("Date.prototype.toLocaleString") << QString("toLocaleString");
1608     QTest::newRow("Date.prototype.toLocaleDateString") << QString("Date.prototype.toLocaleDateString") << QString("toLocaleDateString");
1609     QTest::newRow("Date.prototype.toLocaleTimeString") << QString("Date.prototype.toLocaleTimeString") << QString("toLocaleTimeString");
1610     QTest::newRow("Date.prototype.valueOf") << QString("Date.prototype.valueOf") << QString("valueOf");
1611     QTest::newRow("Date.prototype.getTime") << QString("Date.prototype.getTime") << QString("getTime");
1612     QTest::newRow("Date.prototype.getYear") << QString("Date.prototype.getYear") << QString("getYear");
1613     QTest::newRow("Date.prototype.getFullYear") << QString("Date.prototype.getFullYear") << QString("getFullYear");
1614     QTest::newRow("Date.prototype.getUTCFullYear") << QString("Date.prototype.getUTCFullYear") << QString("getUTCFullYear");
1615     QTest::newRow("Date.prototype.getMonth") << QString("Date.prototype.getMonth") << QString("getMonth");
1616     QTest::newRow("Date.prototype.getUTCMonth") << QString("Date.prototype.getUTCMonth") << QString("getUTCMonth");
1617     QTest::newRow("Date.prototype.getDate") << QString("Date.prototype.getDate") << QString("getDate");
1618     QTest::newRow("Date.prototype.getUTCDate") << QString("Date.prototype.getUTCDate") << QString("getUTCDate");
1619     QTest::newRow("Date.prototype.getDay") << QString("Date.prototype.getDay") << QString("getDay");
1620     QTest::newRow("Date.prototype.getUTCDay") << QString("Date.prototype.getUTCDay") << QString("getUTCDay");
1621     QTest::newRow("Date.prototype.getHours") << QString("Date.prototype.getHours") << QString("getHours");
1622     QTest::newRow("Date.prototype.getUTCHours") << QString("Date.prototype.getUTCHours") << QString("getUTCHours");
1623     QTest::newRow("Date.prototype.getMinutes") << QString("Date.prototype.getMinutes") << QString("getMinutes");
1624     QTest::newRow("Date.prototype.getUTCMinutes") << QString("Date.prototype.getUTCMinutes") << QString("getUTCMinutes");
1625     QTest::newRow("Date.prototype.getSeconds") << QString("Date.prototype.getSeconds") << QString("getSeconds");
1626     QTest::newRow("Date.prototype.getUTCSeconds") << QString("Date.prototype.getUTCSeconds") << QString("getUTCSeconds");
1627     QTest::newRow("Date.prototype.getMilliseconds") << QString("Date.prototype.getMilliseconds") << QString("getMilliseconds");
1628     QTest::newRow("Date.prototype.getUTCMilliseconds") << QString("Date.prototype.getUTCMilliseconds") << QString("getUTCMilliseconds");
1629     QTest::newRow("Date.prototype.getTimezoneOffset") << QString("Date.prototype.getTimezoneOffset") << QString("getTimezoneOffset");
1630     QTest::newRow("Date.prototype.setTime") << QString("Date.prototype.setTime") << QString("setTime");
1631     QTest::newRow("Date.prototype.setMilliseconds") << QString("Date.prototype.setMilliseconds") << QString("setMilliseconds");
1632     QTest::newRow("Date.prototype.setUTCMilliseconds") << QString("Date.prototype.setUTCMilliseconds") << QString("setUTCMilliseconds");
1633     QTest::newRow("Date.prototype.setSeconds") << QString("Date.prototype.setSeconds") << QString("setSeconds");
1634     QTest::newRow("Date.prototype.setUTCSeconds") << QString("Date.prototype.setUTCSeconds") << QString("setUTCSeconds");
1635     QTest::newRow("Date.prototype.setMinutes") << QString("Date.prototype.setMinutes") << QString("setMinutes");
1636     QTest::newRow("Date.prototype.setUTCMinutes") << QString("Date.prototype.setUTCMinutes") << QString("setUTCMinutes");
1637     QTest::newRow("Date.prototype.setHours") << QString("Date.prototype.setHours") << QString("setHours");
1638     QTest::newRow("Date.prototype.setUTCHours") << QString("Date.prototype.setUTCHours") << QString("setUTCHours");
1639     QTest::newRow("Date.prototype.setDate") << QString("Date.prototype.setDate") << QString("setDate");
1640     QTest::newRow("Date.prototype.setUTCDate") << QString("Date.prototype.setUTCDate") << QString("setUTCDate");
1641     QTest::newRow("Date.prototype.setMonth") << QString("Date.prototype.setMonth") << QString("setMonth");
1642     QTest::newRow("Date.prototype.setUTCMonth") << QString("Date.prototype.setUTCMonth") << QString("setUTCMonth");
1643     QTest::newRow("Date.prototype.setYear") << QString("Date.prototype.setYear") << QString("setYear");
1644     QTest::newRow("Date.prototype.setFullYear") << QString("Date.prototype.setFullYear") << QString("setFullYear");
1645     QTest::newRow("Date.prototype.setUTCFullYear") << QString("Date.prototype.setUTCFullYear") << QString("setUTCFullYear");
1646     QTest::newRow("Date.prototype.toUTCString") << QString("Date.prototype.toUTCString") << QString("toUTCString");
1647     QTest::newRow("Date.prototype.toGMTString") << QString("Date.prototype.toGMTString") << QString("toGMTString");
1648 
1649     QTest::newRow("Error") << QString("Error") << QString("Error");
1650 //    QTest::newRow("Error.prototype.backtrace") << QString("Error.prototype.backtrace") << QString("backtrace");
1651     QTest::newRow("Error.prototype.toString") << QString("Error.prototype.toString") << QString("toString");
1652 
1653     QTest::newRow("EvalError") << QString("EvalError") << QString("EvalError");
1654     QTest::newRow("RangeError") << QString("RangeError") << QString("RangeError");
1655     QTest::newRow("ReferenceError") << QString("ReferenceError") << QString("ReferenceError");
1656     QTest::newRow("SyntaxError") << QString("SyntaxError") << QString("SyntaxError");
1657     QTest::newRow("TypeError") << QString("TypeError") << QString("TypeError");
1658     QTest::newRow("URIError") << QString("URIError") << QString("URIError");
1659 
1660     QTest::newRow("Function") << QString("Function") << QString("Function");
1661     QTest::newRow("Function.prototype.toString") << QString("Function.prototype.toString") << QString("toString");
1662     QTest::newRow("Function.prototype.apply") << QString("Function.prototype.apply") << QString("apply");
1663     QTest::newRow("Function.prototype.call") << QString("Function.prototype.call") << QString("call");
1664     QTest::newRow("Function.prototype.connect") << QString("Function.prototype.connect") << QString("connect");
1665     QTest::newRow("Function.prototype.disconnect") << QString("Function.prototype.disconnect") << QString("disconnect");
1666 
1667     QTest::newRow("Math.abs") << QString("Math.abs") << QString("abs");
1668     QTest::newRow("Math.acos") << QString("Math.acos") << QString("acos");
1669     QTest::newRow("Math.asin") << QString("Math.asin") << QString("asin");
1670     QTest::newRow("Math.atan") << QString("Math.atan") << QString("atan");
1671     QTest::newRow("Math.atan2") << QString("Math.atan2") << QString("atan2");
1672     QTest::newRow("Math.ceil") << QString("Math.ceil") << QString("ceil");
1673     QTest::newRow("Math.cos") << QString("Math.cos") << QString("cos");
1674     QTest::newRow("Math.exp") << QString("Math.exp") << QString("exp");
1675     QTest::newRow("Math.floor") << QString("Math.floor") << QString("floor");
1676     QTest::newRow("Math.log") << QString("Math.log") << QString("log");
1677     QTest::newRow("Math.max") << QString("Math.max") << QString("max");
1678     QTest::newRow("Math.min") << QString("Math.min") << QString("min");
1679     QTest::newRow("Math.pow") << QString("Math.pow") << QString("pow");
1680     QTest::newRow("Math.random") << QString("Math.random") << QString("random");
1681     QTest::newRow("Math.round") << QString("Math.round") << QString("round");
1682     QTest::newRow("Math.sin") << QString("Math.sin") << QString("sin");
1683     QTest::newRow("Math.sqrt") << QString("Math.sqrt") << QString("sqrt");
1684     QTest::newRow("Math.tan") << QString("Math.tan") << QString("tan");
1685 
1686     QTest::newRow("Number") << QString("Number") << QString("Number");
1687     QTest::newRow("Number.prototype.toString") << QString("Number.prototype.toString") << QString("toString");
1688     QTest::newRow("Number.prototype.toLocaleString") << QString("Number.prototype.toLocaleString") << QString("toLocaleString");
1689     QTest::newRow("Number.prototype.valueOf") << QString("Number.prototype.valueOf") << QString("valueOf");
1690     QTest::newRow("Number.prototype.toFixed") << QString("Number.prototype.toFixed") << QString("toFixed");
1691     QTest::newRow("Number.prototype.toExponential") << QString("Number.prototype.toExponential") << QString("toExponential");
1692     QTest::newRow("Number.prototype.toPrecision") << QString("Number.prototype.toPrecision") << QString("toPrecision");
1693 
1694     QTest::newRow("Object") << QString("Object") << QString("Object");
1695     QTest::newRow("Object.prototype.toString") << QString("Object.prototype.toString") << QString("toString");
1696     QTest::newRow("Object.prototype.toLocaleString") << QString("Object.prototype.toLocaleString") << QString("toLocaleString");
1697     QTest::newRow("Object.prototype.valueOf") << QString("Object.prototype.valueOf") << QString("valueOf");
1698     QTest::newRow("Object.prototype.hasOwnProperty") << QString("Object.prototype.hasOwnProperty") << QString("hasOwnProperty");
1699     QTest::newRow("Object.prototype.isPrototypeOf") << QString("Object.prototype.isPrototypeOf") << QString("isPrototypeOf");
1700     QTest::newRow("Object.prototype.propertyIsEnumerable") << QString("Object.prototype.propertyIsEnumerable") << QString("propertyIsEnumerable");
1701     QTest::newRow("Object.prototype.__defineGetter__") << QString("Object.prototype.__defineGetter__") << QString("__defineGetter__");
1702     QTest::newRow("Object.prototype.__defineSetter__") << QString("Object.prototype.__defineSetter__") << QString("__defineSetter__");
1703 
1704     QTest::newRow("RegExp") << QString("RegExp") << QString("RegExp");
1705     QTest::newRow("RegExp.prototype.exec") << QString("RegExp.prototype.exec") << QString("exec");
1706     QTest::newRow("RegExp.prototype.test") << QString("RegExp.prototype.test") << QString("test");
1707     QTest::newRow("RegExp.prototype.toString") << QString("RegExp.prototype.toString") << QString("toString");
1708 
1709     QTest::newRow("String") << QString("String") << QString("String");
1710     QTest::newRow("String.prototype.toString") << QString("String.prototype.toString") << QString("toString");
1711     QTest::newRow("String.prototype.valueOf") << QString("String.prototype.valueOf") << QString("valueOf");
1712     QTest::newRow("String.prototype.charAt") << QString("String.prototype.charAt") << QString("charAt");
1713     QTest::newRow("String.prototype.charCodeAt") << QString("String.prototype.charCodeAt") << QString("charCodeAt");
1714     QTest::newRow("String.prototype.concat") << QString("String.prototype.concat") << QString("concat");
1715     QTest::newRow("String.prototype.indexOf") << QString("String.prototype.indexOf") << QString("indexOf");
1716     QTest::newRow("String.prototype.lastIndexOf") << QString("String.prototype.lastIndexOf") << QString("lastIndexOf");
1717     QTest::newRow("String.prototype.localeCompare") << QString("String.prototype.localeCompare") << QString("localeCompare");
1718     QTest::newRow("String.prototype.match") << QString("String.prototype.match") << QString("match");
1719     QTest::newRow("String.prototype.replace") << QString("String.prototype.replace") << QString("replace");
1720     QTest::newRow("String.prototype.search") << QString("String.prototype.search") << QString("search");
1721     QTest::newRow("String.prototype.slice") << QString("String.prototype.slice") << QString("slice");
1722     QTest::newRow("String.prototype.split") << QString("String.prototype.split") << QString("split");
1723     QTest::newRow("String.prototype.substring") << QString("String.prototype.substring") << QString("substring");
1724     QTest::newRow("String.prototype.toLowerCase") << QString("String.prototype.toLowerCase") << QString("toLowerCase");
1725     QTest::newRow("String.prototype.toLocaleLowerCase") << QString("String.prototype.toLocaleLowerCase") << QString("toLocaleLowerCase");
1726     QTest::newRow("String.prototype.toUpperCase") << QString("String.prototype.toUpperCase") << QString("toUpperCase");
1727     QTest::newRow("String.prototype.toLocaleUpperCase") << QString("String.prototype.toLocaleUpperCase") << QString("toLocaleUpperCase");
1728 }
1729 
builtinFunctionNames()1730 void tst_QScriptEngine::builtinFunctionNames()
1731 {
1732     QFETCH(QString, expression);
1733     QFETCH(QString, expectedName);
1734     QScriptEngine eng;
1735     // The "name" property is actually non-standard, but JSC supports it.
1736     QScriptValue ret = eng.evaluate(QString::fromLatin1("%0.name").arg(expression));
1737     QVERIFY(ret.isString());
1738     QCOMPARE(ret.toString(), expectedName);
1739 }
1740 
checkSyntax_data()1741 void tst_QScriptEngine::checkSyntax_data()
1742 {
1743     QTest::addColumn<QString>("code");
1744     QTest::addColumn<int>("expectedState");
1745     QTest::addColumn<int>("errorLineNumber");
1746     QTest::addColumn<int>("errorColumnNumber");
1747     QTest::addColumn<QString>("errorMessage");
1748 
1749     QTest::newRow("0")
1750         << QString("0") << int(QScriptSyntaxCheckResult::Valid)
1751         << -1 << -1 << "";
1752     QTest::newRow("if (")
1753         << QString("if (\n") << int(QScriptSyntaxCheckResult::Intermediate)
1754         << 1 << 4 << "";
1755     QTest::newRow("if else")
1756         << QString("\nif else") << int(QScriptSyntaxCheckResult::Error)
1757         << 2 << 4 << "Expected `('";
1758     QTest::newRow("foo[")
1759         << QString("foo[") << int(QScriptSyntaxCheckResult::Error)
1760         << 1 << 4 << "";
1761     QTest::newRow("foo['bar']")
1762         << QString("foo['bar']") << int(QScriptSyntaxCheckResult::Valid)
1763         << -1 << -1 << "";
1764 
1765     QTest::newRow("/*")
1766         << QString("/*") << int(QScriptSyntaxCheckResult::Intermediate)
1767         << 1 << 1 << "Unclosed comment at end of file";
1768     QTest::newRow("/*\nMy comment")
1769         << QString("/*\nMy comment") << int(QScriptSyntaxCheckResult::Intermediate)
1770         << 1 << 1 << "Unclosed comment at end of file";
1771     QTest::newRow("/*\nMy comment */\nfoo = 10")
1772         << QString("/*\nMy comment */\nfoo = 10") << int(QScriptSyntaxCheckResult::Valid)
1773         << -1 << -1 << "";
1774     QTest::newRow("foo = 10 /*")
1775         << QString("foo = 10 /*") << int(QScriptSyntaxCheckResult::Intermediate)
1776         << -1 << -1 << "";
1777     QTest::newRow("foo = 10; /*")
1778         << QString("foo = 10; /*") << int(QScriptSyntaxCheckResult::Intermediate)
1779         << 1 << 11 << "Expected `end of file'";
1780     QTest::newRow("foo = 10 /* My comment */")
1781         << QString("foo = 10 /* My comment */") << int(QScriptSyntaxCheckResult::Valid)
1782         << -1 << -1 << "";
1783 
1784     QTest::newRow("/=/")
1785         << QString("/=/") << int(QScriptSyntaxCheckResult::Valid) << -1 << -1 << "";
1786     QTest::newRow("/=/g")
1787         << QString("/=/g") << int(QScriptSyntaxCheckResult::Valid) << -1 << -1 << "";
1788     QTest::newRow("/a/")
1789         << QString("/a/") << int(QScriptSyntaxCheckResult::Valid) << -1 << -1 << "";
1790     QTest::newRow("/a/g")
1791         << QString("/a/g") << int(QScriptSyntaxCheckResult::Valid) << -1 << -1 << "";
1792 }
1793 
checkSyntax()1794 void tst_QScriptEngine::checkSyntax()
1795 {
1796     QFETCH(QString, code);
1797     QFETCH(int, expectedState);
1798     QFETCH(int, errorLineNumber);
1799     QFETCH(int, errorColumnNumber);
1800     QFETCH(QString, errorMessage);
1801 
1802     QScriptSyntaxCheckResult result = QScriptEngine::checkSyntax(code);
1803     QCOMPARE(result.state(), QScriptSyntaxCheckResult::State(expectedState));
1804     QCOMPARE(result.errorLineNumber(), errorLineNumber);
1805     QCOMPARE(result.errorColumnNumber(), errorColumnNumber);
1806     QCOMPARE(result.errorMessage(), errorMessage);
1807 
1808     // assignment
1809     {
1810         QScriptSyntaxCheckResult copy = result;
1811         QCOMPARE(copy.state(), result.state());
1812         QCOMPARE(copy.errorLineNumber(), result.errorLineNumber());
1813         QCOMPARE(copy.errorColumnNumber(), result.errorColumnNumber());
1814         QCOMPARE(copy.errorMessage(), result.errorMessage());
1815     }
1816     {
1817         QScriptSyntaxCheckResult copy(result);
1818         QCOMPARE(copy.state(), result.state());
1819         QCOMPARE(copy.errorLineNumber(), result.errorLineNumber());
1820         QCOMPARE(copy.errorColumnNumber(), result.errorColumnNumber());
1821         QCOMPARE(copy.errorMessage(), result.errorMessage());
1822     }
1823 }
1824 
canEvaluate_data()1825 void tst_QScriptEngine::canEvaluate_data()
1826 {
1827     QTest::addColumn<QString>("code");
1828     QTest::addColumn<bool>("expectSuccess");
1829 
1830     QTest::newRow("") << QString("") << true;
1831     QTest::newRow("0") << QString("0") << true;
1832     QTest::newRow("!") << QString("!\n") << false;
1833     QTest::newRow("if (") << QString("if (\n") << false;
1834     QTest::newRow("if (10) //") << QString("if (10) //\n") << false;
1835     QTest::newRow("a = 1; if (") << QString("a = 1;\nif (\n") << false;
1836     QTest::newRow("./test.js") << QString("./test.js\n") << true;
1837     QTest::newRow("if (0) print(1)") << QString("if (0)\nprint(1)\n") << true;
1838     QTest::newRow("0 = ") << QString("0 = \n") << false;
1839     QTest::newRow("0 = 0") << QString("0 = 0\n") << true;
1840     QTest::newRow("foo[") << QString("foo[") << true; // automatic semicolon will be inserted
1841     QTest::newRow("foo[") << QString("foo[\n") << false;
1842     QTest::newRow("foo['bar']") << QString("foo['bar']") << true;
1843 
1844     QTest::newRow("/*") << QString("/*") << false;
1845     QTest::newRow("/*\nMy comment") << QString("/*\nMy comment") << false;
1846     QTest::newRow("/*\nMy comment */\nfoo = 10") << QString("/*\nMy comment */\nfoo = 10") << true;
1847     QTest::newRow("foo = 10 /*") << QString("foo = 10 /*") << false;
1848     QTest::newRow("foo = 10; /*") << QString("foo = 10; /*") << false;
1849     QTest::newRow("foo = 10 /* My comment */") << QString("foo = 10 /* My comment */") << true;
1850 
1851     QTest::newRow("/=/") << QString("/=/") << true;
1852     QTest::newRow("/=/g") << QString("/=/g") << true;
1853     QTest::newRow("/a/") << QString("/a/") << true;
1854     QTest::newRow("/a/g") << QString("/a/g") << true;
1855 }
1856 
canEvaluate()1857 void tst_QScriptEngine::canEvaluate()
1858 {
1859     QFETCH(QString, code);
1860     QFETCH(bool, expectSuccess);
1861 
1862     QScriptEngine eng;
1863     QCOMPARE(eng.canEvaluate(code), expectSuccess);
1864 }
1865 
evaluate_data()1866 void tst_QScriptEngine::evaluate_data()
1867 {
1868     QTest::addColumn<QString>("code");
1869     QTest::addColumn<int>("lineNumber");
1870     QTest::addColumn<bool>("expectHadError");
1871     QTest::addColumn<int>("expectErrorLineNumber");
1872 
1873     QTest::newRow("(newline)") << QString("\n") << -1 << false << -1;
1874     QTest::newRow("0 //")   << QString("0 //") << -1 << false << -1;
1875     QTest::newRow("/* */")   << QString("/* */") << -1 << false << -1;
1876     QTest::newRow("//") << QString("//") << -1 << false << -1;
1877     QTest::newRow("(spaces)")  << QString("  ") << -1 << false << -1;
1878     QTest::newRow("(empty)")   << QString("") << -1 << false << -1;
1879     QTest::newRow("0")     << QString("0")       << -1 << false << -1;
1880     QTest::newRow("0=1")   << QString("\n0=1;\n") << -1 << true  << 2;
1881     QTest::newRow("a=1")   << QString("a=1\n")   << -1 << false << -1;
1882     QTest::newRow("a=1;K") << QString("a=1;\nK") << -1 << true  << 2;
1883 
1884     QTest::newRow("f()") << QString("function f()\n"
1885                                     "{\n"
1886                                     "  var a;\n"
1887                                     "  var b=\";\n" // here's the error
1888                                     "}\n"
1889                                     "f();\n")
1890                          << -1 << true << 4;
1891 
1892     QTest::newRow("0")     << QString("0")       << 10 << false << -1;
1893     QTest::newRow("0=1")   << QString("\n\n0=1\n") << 10 << true  << 13;
1894     QTest::newRow("a=1")   << QString("a=1\n")   << 10 << false << -1;
1895     QTest::newRow("a=1;K") << QString("a=1;\n\nK") << 10 << true  << 12;
1896 
1897     QTest::newRow("f()") << QString("function f()\n"
1898                                     "{\n"
1899                                     "  var a;\n"
1900                                     "\n\n"
1901                                     "  var b=\";\n" // here's the error
1902                                     "}\n"
1903                                     "f();\n")
1904                          << 10 << true << 15;
1905     QTest::newRow("functionThatDoesntExist()")
1906         << QString(";\n;\n;\nfunctionThatDoesntExist()")
1907         << -1 << true << 4;
1908     QTest::newRow("for (var p in this) { continue labelThatDoesntExist; }")
1909         << QString("for (var p in this) {\ncontinue labelThatDoesntExist; }")
1910         << 4 << true << 5;
1911     QTest::newRow("duplicateLabel: { duplicateLabel: ; }")
1912         << QString("duplicateLabel: { duplicateLabel: ; }")
1913         << 12 << true << 12;
1914 
1915     QTest::newRow("/=/") << QString("/=/") << -1 << false << -1;
1916     QTest::newRow("/=/g") << QString("/=/g") << -1 << false << -1;
1917     QTest::newRow("/a/") << QString("/a/") << -1 << false << -1;
1918     QTest::newRow("/a/g") << QString("/a/g") << -1 << false << -1;
1919     QTest::newRow("/a/gim") << QString("/a/gim") << -1 << false << -1;
1920     QTest::newRow("/a/gimp") << QString("/a/gimp") << 1 << true << 1;
1921 }
1922 
evaluate()1923 void tst_QScriptEngine::evaluate()
1924 {
1925     QFETCH(QString, code);
1926     QFETCH(int, lineNumber);
1927     QFETCH(bool, expectHadError);
1928     QFETCH(int, expectErrorLineNumber);
1929 
1930     QScriptEngine eng;
1931     QScriptValue ret;
1932     if (lineNumber != -1)
1933         ret = eng.evaluate(code, /*fileName =*/QString(), lineNumber);
1934     else
1935         ret = eng.evaluate(code);
1936     QCOMPARE(eng.hasUncaughtException(), expectHadError);
1937     QCOMPARE(eng.uncaughtExceptionLineNumber(), expectErrorLineNumber);
1938     if (eng.hasUncaughtException() && ret.isError())
1939         QVERIFY(ret.property("lineNumber").strictlyEquals(QScriptValue(&eng, expectErrorLineNumber)));
1940     else
1941         QVERIFY(eng.uncaughtExceptionBacktrace().isEmpty());
1942 }
1943 
eval_nested(QScriptContext * ctx,QScriptEngine * eng)1944 static QScriptValue eval_nested(QScriptContext *ctx, QScriptEngine *eng)
1945 {
1946     QScriptValue result = eng->newObject();
1947     eng->evaluate("var bar = 'local';");
1948     result.setProperty("thisObjectIdBefore", ctx->thisObject().property("id"));
1949     QScriptValue evaluatedThisObject = eng->evaluate("this");
1950     result.setProperty("thisObjectIdAfter", ctx->thisObject().property("id"));
1951     result.setProperty("evaluatedThisObjectId", evaluatedThisObject.property("id"));
1952     result.setProperty("local_bar", eng->evaluate("bar"));
1953 
1954     return result;
1955 }
1956 
1957 // Tests that nested evaluate uses the "this" that was passed.
nestedEvaluate()1958 void tst_QScriptEngine::nestedEvaluate()
1959 {
1960     QScriptEngine eng;
1961     QScriptValue fun = eng.newFunction(eval_nested);
1962     eng.globalObject().setProperty("fun", fun);
1963     // From JS function call
1964     {
1965         QScriptValue result = eng.evaluate("o = { id:'foo'}; o.fun = fun; o.fun()");
1966         QCOMPARE(result.property("local_bar").toString(), QString("local"));
1967         QCOMPARE(result.property("thisObjectIdBefore").toString(), QString("foo"));
1968         QCOMPARE(result.property("thisObjectIdAfter").toString(), QString("foo"));
1969         QCOMPARE(result.property("evaluatedThisObjectId").toString(), QString("foo"));
1970         QScriptValue bar = eng.evaluate("bar"); // Was introduced in local scope.
1971         QVERIFY(bar.isError());
1972         QVERIFY(bar.toString().contains(QString::fromLatin1("ReferenceError")));
1973     }
1974     // From QScriptValue::call()
1975     {
1976         QScriptValue result = fun.call(eng.evaluate("p = { id:'foo' }") , QScriptValueList() );
1977         QCOMPARE(result.property("local_bar").toString(), QString("local"));
1978         QCOMPARE(result.property("thisObjectIdBefore").toString(), QString("foo"));
1979         QCOMPARE(result.property("thisObjectIdAfter").toString(), QString("foo"));
1980         QCOMPARE(result.property("evaluatedThisObjectId").toString(), QString("foo"));
1981         QScriptValue bar = eng.evaluate("bar");
1982         QVERIFY(bar.isError());
1983         QVERIFY(bar.toString().contains(QString::fromLatin1("ReferenceError")));
1984     }
1985 }
1986 
uncaughtException()1987 void tst_QScriptEngine::uncaughtException()
1988 {
1989     QScriptEngine eng;
1990     QScriptValue fun = eng.newFunction(myFunction);
1991     QScriptValue throwFun = eng.newFunction(myThrowingFunction);
1992     for (int x = -1; x < 2; ++x) {
1993         {
1994             QScriptValue ret = eng.evaluate("a = 10;\nb = 20;\n0 = 0;\n", /*fileName=*/QString(), /*lineNumber=*/x);
1995             QVERIFY(eng.hasUncaughtException());
1996             QCOMPARE(eng.uncaughtExceptionLineNumber(), x+2);
1997             QVERIFY(eng.uncaughtException().strictlyEquals(ret));
1998             (void)ret.toString();
1999             QVERIFY(eng.hasUncaughtException());
2000             QVERIFY(eng.uncaughtException().strictlyEquals(ret));
2001             QVERIFY(fun.call().isNull());
2002             QVERIFY(eng.hasUncaughtException());
2003             QCOMPARE(eng.uncaughtExceptionLineNumber(), x+2);
2004             QVERIFY(eng.uncaughtException().strictlyEquals(ret));
2005             eng.clearExceptions();
2006             QVERIFY(!eng.hasUncaughtException());
2007             QCOMPARE(eng.uncaughtExceptionLineNumber(), -1);
2008             QVERIFY(!eng.uncaughtException().isValid());
2009 
2010             eng.evaluate("2 = 3");
2011             QVERIFY(eng.hasUncaughtException());
2012             QScriptValue ret2 = throwFun.call();
2013             QVERIFY(ret2.isError());
2014             QVERIFY(eng.hasUncaughtException());
2015             QVERIFY(eng.uncaughtException().strictlyEquals(ret2));
2016             QCOMPARE(eng.uncaughtExceptionLineNumber(), 0);
2017             eng.clearExceptions();
2018             QVERIFY(!eng.hasUncaughtException());
2019             eng.evaluate("1 + 2");
2020             QVERIFY(!eng.hasUncaughtException());
2021         }
2022         {
2023             QScriptValue ret = eng.evaluate("a = 10");
2024             QVERIFY(!eng.hasUncaughtException());
2025             QVERIFY(!eng.uncaughtException().isValid());
2026         }
2027         {
2028             QScriptValue ret = eng.evaluate("1 = 2");
2029             QVERIFY(eng.hasUncaughtException());
2030             eng.clearExceptions();
2031             QVERIFY(!eng.hasUncaughtException());
2032         }
2033         {
2034             eng.globalObject().setProperty("throwFun", throwFun);
2035             eng.evaluate("1;\nthrowFun();");
2036             QVERIFY(eng.hasUncaughtException());
2037             QCOMPARE(eng.uncaughtExceptionLineNumber(), 2);
2038             eng.clearExceptions();
2039             QVERIFY(!eng.hasUncaughtException());
2040         }
2041     }
2042 }
2043 
errorMessage_QT679()2044 void tst_QScriptEngine::errorMessage_QT679()
2045 {
2046     QScriptEngine engine;
2047     engine.globalObject().setProperty("foo", 15);
2048     QScriptValue error = engine.evaluate("'hello world';\nfoo.bar.blah");
2049     QVERIFY(error.isError());
2050     // The exact message is back-end specific and subject to change.
2051     QCOMPARE(error.toString(), QString::fromLatin1("TypeError: Result of expression 'foo.bar' [undefined] is not an object."));
2052 }
2053 
2054 struct Foo {
2055 public:
2056     int x, y;
FooFoo2057     Foo() : x(-1), y(-1) { }
2058 };
2059 
2060 Q_DECLARE_METATYPE(Foo)
Q_DECLARE_METATYPE(Foo *)2061 Q_DECLARE_METATYPE(Foo*)
2062 
2063 void tst_QScriptEngine::getSetDefaultPrototype_int()
2064 {
2065     QScriptEngine eng;
2066 
2067     QScriptValue object = eng.newObject();
2068     QCOMPARE(eng.defaultPrototype(qMetaTypeId<int>()).isValid(), false);
2069     eng.setDefaultPrototype(qMetaTypeId<int>(), object);
2070     QCOMPARE(eng.defaultPrototype(qMetaTypeId<int>()).strictlyEquals(object), true);
2071     QScriptValue value = eng.newVariant(int(123));
2072     QCOMPARE(value.prototype().isObject(), true);
2073     QCOMPARE(value.prototype().strictlyEquals(object), true);
2074 
2075     eng.setDefaultPrototype(qMetaTypeId<int>(), QScriptValue());
2076     QCOMPARE(eng.defaultPrototype(qMetaTypeId<int>()).isValid(), false);
2077     QScriptValue value2 = eng.newVariant(int(123));
2078     QCOMPARE(value2.prototype().strictlyEquals(object), false);
2079 }
2080 
getSetDefaultPrototype_customType()2081 void tst_QScriptEngine::getSetDefaultPrototype_customType()
2082 {
2083     QScriptEngine eng;
2084 
2085     QScriptValue object = eng.newObject();
2086     QCOMPARE(eng.defaultPrototype(qMetaTypeId<Foo>()).isValid(), false);
2087     eng.setDefaultPrototype(qMetaTypeId<Foo>(), object);
2088     QCOMPARE(eng.defaultPrototype(qMetaTypeId<Foo>()).strictlyEquals(object), true);
2089     QScriptValue value = eng.newVariant(qVariantFromValue(Foo()));
2090     QCOMPARE(value.prototype().isObject(), true);
2091     QCOMPARE(value.prototype().strictlyEquals(object), true);
2092 
2093     eng.setDefaultPrototype(qMetaTypeId<Foo>(), QScriptValue());
2094     QCOMPARE(eng.defaultPrototype(qMetaTypeId<Foo>()).isValid(), false);
2095     QScriptValue value2 = eng.newVariant(qVariantFromValue(Foo()));
2096     QCOMPARE(value2.prototype().strictlyEquals(object), false);
2097 }
2098 
fooToScriptValue(QScriptEngine * eng,const Foo & foo)2099 static QScriptValue fooToScriptValue(QScriptEngine *eng, const Foo &foo)
2100 {
2101     QScriptValue obj = eng->newObject();
2102     obj.setProperty("x", QScriptValue(eng, foo.x));
2103     obj.setProperty("y", QScriptValue(eng, foo.y));
2104     return obj;
2105 }
2106 
fooFromScriptValue(const QScriptValue & value,Foo & foo)2107 static void fooFromScriptValue(const QScriptValue &value, Foo &foo)
2108 {
2109     foo.x = value.property("x").toInt32();
2110     foo.y = value.property("y").toInt32();
2111 }
2112 
fooToScriptValueV2(QScriptEngine * eng,const Foo & foo)2113 static QScriptValue fooToScriptValueV2(QScriptEngine *eng, const Foo &foo)
2114 {
2115     return QScriptValue(eng, foo.x);
2116 }
2117 
fooFromScriptValueV2(const QScriptValue & value,Foo & foo)2118 static void fooFromScriptValueV2(const QScriptValue &value, Foo &foo)
2119 {
2120     foo.x = value.toInt32();
2121 }
2122 
2123 Q_DECLARE_METATYPE(QLinkedList<QString>)
Q_DECLARE_METATYPE(QList<Foo>)2124 Q_DECLARE_METATYPE(QList<Foo>)
2125 Q_DECLARE_METATYPE(QVector<QChar>)
2126 Q_DECLARE_METATYPE(QStack<int>)
2127 Q_DECLARE_METATYPE(QQueue<char>)
2128 Q_DECLARE_METATYPE(QLinkedList<QStack<int> >)
2129 
2130 void tst_QScriptEngine::valueConversion_basic()
2131 {
2132     QScriptEngine eng;
2133     {
2134         QScriptValue num = qScriptValueFromValue(&eng, 123);
2135         QCOMPARE(num.isNumber(), true);
2136         QCOMPARE(num.strictlyEquals(QScriptValue(&eng, 123)), true);
2137 
2138         int inum = qScriptValueToValue<int>(num);
2139         QCOMPARE(inum, 123);
2140 
2141         QString snum = qScriptValueToValue<QString>(num);
2142         QCOMPARE(snum, QLatin1String("123"));
2143     }
2144     {
2145         QScriptValue num = eng.toScriptValue(123);
2146         QCOMPARE(num.isNumber(), true);
2147         QCOMPARE(num.strictlyEquals(QScriptValue(&eng, 123)), true);
2148 
2149         int inum = eng.fromScriptValue<int>(num);
2150         QCOMPARE(inum, 123);
2151 
2152         QString snum = eng.fromScriptValue<QString>(num);
2153         QCOMPARE(snum, QLatin1String("123"));
2154     }
2155     {
2156         QScriptValue num(&eng, 123);
2157         QCOMPARE(qScriptValueToValue<char>(num), char(123));
2158         QCOMPARE(qScriptValueToValue<unsigned char>(num), (unsigned char)(123));
2159         QCOMPARE(qScriptValueToValue<short>(num), short(123));
2160         QCOMPARE(qScriptValueToValue<unsigned short>(num), (unsigned short)(123));
2161         QCOMPARE(qScriptValueToValue<float>(num), float(123));
2162         QCOMPARE(qScriptValueToValue<double>(num), double(123));
2163         QCOMPARE(qScriptValueToValue<qlonglong>(num), qlonglong(123));
2164         QCOMPARE(qScriptValueToValue<qulonglong>(num), qulonglong(123));
2165     }
2166     {
2167         QScriptValue num(123);
2168         QCOMPARE(qScriptValueToValue<char>(num), char(123));
2169         QCOMPARE(qScriptValueToValue<unsigned char>(num), (unsigned char)(123));
2170         QCOMPARE(qScriptValueToValue<short>(num), short(123));
2171         QCOMPARE(qScriptValueToValue<unsigned short>(num), (unsigned short)(123));
2172         QCOMPARE(qScriptValueToValue<float>(num), float(123));
2173         QCOMPARE(qScriptValueToValue<double>(num), double(123));
2174         QCOMPARE(qScriptValueToValue<qlonglong>(num), qlonglong(123));
2175         QCOMPARE(qScriptValueToValue<qulonglong>(num), qulonglong(123));
2176     }
2177 
2178     {
2179         QScriptValue num = qScriptValueFromValue(&eng, Q_INT64_C(0x100000000));
2180         QCOMPARE(qScriptValueToValue<qlonglong>(num), Q_INT64_C(0x100000000));
2181         QCOMPARE(qScriptValueToValue<qulonglong>(num), Q_UINT64_C(0x100000000));
2182     }
2183 
2184     {
2185         QChar c = QLatin1Char('c');
2186         QScriptValue str = QScriptValue(&eng, "ciao");
2187         QCOMPARE(qScriptValueToValue<QChar>(str), c);
2188         QScriptValue code = QScriptValue(&eng, c.unicode());
2189         QCOMPARE(qScriptValueToValue<QChar>(code), c);
2190         QCOMPARE(qScriptValueToValue<QChar>(qScriptValueFromValue(&eng, c)), c);
2191     }
2192 }
2193 
valueConversion_customType()2194 void tst_QScriptEngine::valueConversion_customType()
2195 {
2196     QScriptEngine eng;
2197     {
2198         // a type that we don't have built-in conversion of
2199         // (it's stored as a variant)
2200         QTime tm(1, 2, 3, 4);
2201         QScriptValue val = qScriptValueFromValue(&eng, tm);
2202         QCOMPARE(qScriptValueToValue<QTime>(val), tm);
2203     }
2204 
2205     {
2206         Foo foo;
2207         foo.x = 12;
2208         foo.y = 34;
2209         QScriptValue fooVal = qScriptValueFromValue(&eng, foo);
2210         QCOMPARE(fooVal.isVariant(), true);
2211 
2212         Foo foo2 = qScriptValueToValue<Foo>(fooVal);
2213         QCOMPARE(foo2.x, foo.x);
2214         QCOMPARE(foo2.y, foo.y);
2215     }
2216 
2217     qScriptRegisterMetaType<Foo>(&eng, fooToScriptValue, fooFromScriptValue);
2218 
2219     {
2220         Foo foo;
2221         foo.x = 12;
2222         foo.y = 34;
2223         QScriptValue fooVal = qScriptValueFromValue(&eng, foo);
2224         QCOMPARE(fooVal.isObject(), true);
2225         QVERIFY(fooVal.prototype().strictlyEquals(eng.evaluate("Object.prototype")));
2226         QCOMPARE(fooVal.property("x").strictlyEquals(QScriptValue(&eng, 12)), true);
2227         QCOMPARE(fooVal.property("y").strictlyEquals(QScriptValue(&eng, 34)), true);
2228         fooVal.setProperty("x", QScriptValue(&eng, 56));
2229         fooVal.setProperty("y", QScriptValue(&eng, 78));
2230 
2231         Foo foo2 = qScriptValueToValue<Foo>(fooVal);
2232         QCOMPARE(foo2.x, 56);
2233         QCOMPARE(foo2.y, 78);
2234 
2235         QScriptValue fooProto = eng.newObject();
2236         eng.setDefaultPrototype(qMetaTypeId<Foo>(), fooProto);
2237         QScriptValue fooVal2 = qScriptValueFromValue(&eng, foo2);
2238         QVERIFY(fooVal2.prototype().strictlyEquals(fooProto));
2239         QVERIFY(fooVal2.property("x").strictlyEquals(QScriptValue(&eng, 56)));
2240         QVERIFY(fooVal2.property("y").strictlyEquals(QScriptValue(&eng, 78)));
2241     }
2242 }
2243 
valueConversion_sequence()2244 void tst_QScriptEngine::valueConversion_sequence()
2245 {
2246     QScriptEngine eng;
2247     qScriptRegisterSequenceMetaType<QLinkedList<QString> >(&eng);
2248 
2249     {
2250         QLinkedList<QString> lst;
2251         lst << QLatin1String("foo") << QLatin1String("bar");
2252         QScriptValue lstVal = qScriptValueFromValue(&eng, lst);
2253         QCOMPARE(lstVal.isArray(), true);
2254         QCOMPARE(lstVal.property("length").toInt32(), 2);
2255         QCOMPARE(lstVal.property("0").isString(), true);
2256         QCOMPARE(lstVal.property("0").toString(), QLatin1String("foo"));
2257         QCOMPARE(lstVal.property("1").isString(), true);
2258         QCOMPARE(lstVal.property("1").toString(), QLatin1String("bar"));
2259     }
2260 
2261     qScriptRegisterSequenceMetaType<QList<Foo> >(&eng);
2262     qScriptRegisterSequenceMetaType<QStack<int> >(&eng);
2263     qScriptRegisterSequenceMetaType<QVector<QChar> >(&eng);
2264     qScriptRegisterSequenceMetaType<QQueue<char> >(&eng);
2265     qScriptRegisterSequenceMetaType<QLinkedList<QStack<int> > >(&eng);
2266 
2267     {
2268         QLinkedList<QStack<int> > lst;
2269         QStack<int> first; first << 13 << 49; lst << first;
2270         QStack<int> second; second << 99999;lst << second;
2271         QScriptValue lstVal = qScriptValueFromValue(&eng, lst);
2272         QCOMPARE(lstVal.isArray(), true);
2273         QCOMPARE(lstVal.property("length").toInt32(), 2);
2274         QCOMPARE(lstVal.property("0").isArray(), true);
2275         QCOMPARE(lstVal.property("0").property("length").toInt32(), 2);
2276         QCOMPARE(lstVal.property("0").property("0").toInt32(), first.at(0));
2277         QCOMPARE(lstVal.property("0").property("1").toInt32(), first.at(1));
2278         QCOMPARE(lstVal.property("1").isArray(), true);
2279         QCOMPARE(lstVal.property("1").property("length").toInt32(), 1);
2280         QCOMPARE(lstVal.property("1").property("0").toInt32(), second.at(0));
2281         QCOMPARE(qscriptvalue_cast<QStack<int> >(lstVal.property("0")), first);
2282         QCOMPARE(qscriptvalue_cast<QStack<int> >(lstVal.property("1")), second);
2283         QCOMPARE(qscriptvalue_cast<QLinkedList<QStack<int> > >(lstVal), lst);
2284     }
2285 
2286     // pointers
2287     {
2288         Foo foo;
2289         {
2290             QScriptValue v = qScriptValueFromValue(&eng, &foo);
2291             Foo *pfoo = qscriptvalue_cast<Foo*>(v);
2292             QCOMPARE(pfoo, &foo);
2293         }
2294         {
2295             Foo *pfoo = 0;
2296             QScriptValue v = qScriptValueFromValue(&eng, pfoo);
2297             QCOMPARE(v.isNull(), true);
2298             QVERIFY(qscriptvalue_cast<Foo*>(v) == 0);
2299         }
2300     }
2301 
2302     // QList<int> and QObjectList should be converted from/to arrays by default
2303     {
2304         QList<int> lst;
2305         lst << 1 << 2 << 3;
2306         QScriptValue val = qScriptValueFromValue(&eng, lst);
2307         QVERIFY(val.isArray());
2308         QCOMPARE(val.property("length").toInt32(), lst.size());
2309         QCOMPARE(val.property(0).toInt32(), lst.at(0));
2310         QCOMPARE(val.property(1).toInt32(), lst.at(1));
2311         QCOMPARE(val.property(2).toInt32(), lst.at(2));
2312 
2313         QCOMPARE(qscriptvalue_cast<QList<int> >(val), lst);
2314     }
2315     {
2316         QObjectList lst;
2317         lst << this;
2318         QScriptValue val = qScriptValueFromValue(&eng, lst);
2319         QVERIFY(val.isArray());
2320         QCOMPARE(val.property("length").toInt32(), lst.size());
2321         QCOMPARE(val.property(0).toQObject(), (QObject *)this);
2322 
2323         QCOMPARE(qscriptvalue_cast<QObjectList>(val), lst);
2324     }
2325 }
2326 
valueConversion_QVariant()2327 void tst_QScriptEngine::valueConversion_QVariant()
2328 {
2329     QScriptEngine eng;
2330     // qScriptValueFromValue() should be "smart" when the argument is a QVariant
2331     {
2332         QScriptValue val = qScriptValueFromValue(&eng, QVariant());
2333         QVERIFY(!val.isVariant());
2334         QVERIFY(val.isUndefined());
2335     }
2336     // Checking nested QVariants
2337     {
2338         QVariant tmp1;
2339         QVariant tmp2(QMetaType::QVariant, &tmp1);
2340         QVERIFY(QMetaType::Type(tmp2.type()) == QMetaType::QVariant);
2341 
2342         QScriptValue val1 = qScriptValueFromValue(&eng, tmp1);
2343         QScriptValue val2 = qScriptValueFromValue(&eng, tmp2);
2344         QVERIFY(val1.isValid());
2345         QVERIFY(val2.isValid());
2346         QVERIFY(val1.isUndefined());
2347         QVERIFY(!val2.isUndefined());
2348         QVERIFY(!val1.isVariant());
2349         QVERIFY(val2.isVariant());
2350     }
2351     {
2352         QVariant tmp1(123);
2353         QVariant tmp2(QMetaType::QVariant, &tmp1);
2354         QVariant tmp3(QMetaType::QVariant, &tmp2);
2355         QVERIFY(QMetaType::Type(tmp1.type()) == QMetaType::Int);
2356         QVERIFY(QMetaType::Type(tmp2.type()) == QMetaType::QVariant);
2357         QVERIFY(QMetaType::Type(tmp3.type()) == QMetaType::QVariant);
2358 
2359         QScriptValue val1 = qScriptValueFromValue(&eng, tmp2);
2360         QScriptValue val2 = qScriptValueFromValue(&eng, tmp3);
2361         QVERIFY(val1.isValid());
2362         QVERIFY(val2.isValid());
2363         QVERIFY(val1.isVariant());
2364         QVERIFY(val2.isVariant());
2365         QVERIFY(val1.toVariant().toInt() == 123);
2366         QVERIFY(qScriptValueFromValue(&eng, val2.toVariant()).toVariant().toInt() == 123);
2367     }
2368     {
2369         QScriptValue val = qScriptValueFromValue(&eng, QVariant(true));
2370         QVERIFY(!val.isVariant());
2371         QVERIFY(val.isBoolean());
2372         QCOMPARE(val.toBoolean(), true);
2373     }
2374     {
2375         QScriptValue val = qScriptValueFromValue(&eng, QVariant(int(123)));
2376         QVERIFY(!val.isVariant());
2377         QVERIFY(val.isNumber());
2378         QCOMPARE(val.toNumber(), qsreal(123));
2379     }
2380     {
2381         QScriptValue val = qScriptValueFromValue(&eng, QVariant(qsreal(1.25)));
2382         QVERIFY(!val.isVariant());
2383         QVERIFY(val.isNumber());
2384         QCOMPARE(val.toNumber(), qsreal(1.25));
2385     }
2386     {
2387         QString str = QString::fromLatin1("ciao");
2388         QScriptValue val = qScriptValueFromValue(&eng, QVariant(str));
2389         QVERIFY(!val.isVariant());
2390         QVERIFY(val.isString());
2391         QCOMPARE(val.toString(), str);
2392     }
2393     {
2394         QScriptValue val = qScriptValueFromValue(&eng, qVariantFromValue((QObject*)this));
2395         QVERIFY(!val.isVariant());
2396         QVERIFY(val.isQObject());
2397         QCOMPARE(val.toQObject(), (QObject*)this);
2398     }
2399     {
2400         QVariant var = qVariantFromValue(QPoint(123, 456));
2401         QScriptValue val = qScriptValueFromValue(&eng, var);
2402         QVERIFY(val.isVariant());
2403         QCOMPARE(val.toVariant(), var);
2404     }
2405 
2406     QCOMPARE(qscriptvalue_cast<QVariant>(QScriptValue(123)), QVariant(123));
2407 }
2408 
valueConversion_hooliganTask248802()2409 void tst_QScriptEngine::valueConversion_hooliganTask248802()
2410 {
2411     QScriptEngine eng;
2412     qScriptRegisterMetaType<Foo>(&eng, fooToScriptValueV2, fooFromScriptValueV2);
2413     {
2414         QScriptValue num(&eng, 123);
2415         Foo foo = qScriptValueToValue<Foo>(num);
2416         QCOMPARE(foo.x, 123);
2417     }
2418     {
2419         QScriptValue num(123);
2420         Foo foo = qScriptValueToValue<Foo>(num);
2421         QCOMPARE(foo.x, -1);
2422     }
2423     {
2424         QScriptValue str(&eng, "123");
2425         Foo foo = qScriptValueToValue<Foo>(str);
2426         QCOMPARE(foo.x, 123);
2427     }
2428 
2429 }
2430 
valueConversion_basic2()2431 void tst_QScriptEngine::valueConversion_basic2()
2432 {
2433     QScriptEngine eng;
2434     // more built-in types
2435     {
2436         QScriptValue val = qScriptValueFromValue(&eng, uint(123));
2437         QVERIFY(val.isNumber());
2438         QCOMPARE(val.toInt32(), 123);
2439     }
2440     {
2441         QScriptValue val = qScriptValueFromValue(&eng, qulonglong(123));
2442         QVERIFY(val.isNumber());
2443         QCOMPARE(val.toInt32(), 123);
2444     }
2445     {
2446         QScriptValue val = qScriptValueFromValue(&eng, float(123));
2447         QVERIFY(val.isNumber());
2448         QCOMPARE(val.toInt32(), 123);
2449     }
2450     {
2451         QScriptValue val = qScriptValueFromValue(&eng, short(123));
2452         QVERIFY(val.isNumber());
2453         QCOMPARE(val.toInt32(), 123);
2454     }
2455     {
2456         QScriptValue val = qScriptValueFromValue(&eng, ushort(123));
2457         QVERIFY(val.isNumber());
2458         QCOMPARE(val.toInt32(), 123);
2459     }
2460     {
2461         QScriptValue val = qScriptValueFromValue(&eng, char(123));
2462         QVERIFY(val.isNumber());
2463         QCOMPARE(val.toInt32(), 123);
2464     }
2465     {
2466         QScriptValue val = qScriptValueFromValue(&eng, uchar(123));
2467         QVERIFY(val.isNumber());
2468         QCOMPARE(val.toInt32(), 123);
2469     }
2470 }
2471 
valueConversion_dateTime()2472 void tst_QScriptEngine::valueConversion_dateTime()
2473 {
2474     QScriptEngine eng;
2475     {
2476         QDateTime in = QDateTime::currentDateTime();
2477         QScriptValue val = qScriptValueFromValue(&eng, in);
2478         QVERIFY(val.isDate());
2479         QCOMPARE(val.toDateTime(), in);
2480     }
2481     {
2482         QDate in = QDate::currentDate();
2483         QScriptValue val = qScriptValueFromValue(&eng, in);
2484         QVERIFY(val.isDate());
2485         QCOMPARE(val.toDateTime().date(), in);
2486     }
2487 }
2488 
valueConversion_regExp()2489 void tst_QScriptEngine::valueConversion_regExp()
2490 {
2491     QScriptEngine eng;
2492     {
2493         QRegExp in = QRegExp("foo");
2494         QScriptValue val = qScriptValueFromValue(&eng, in);
2495         QVERIFY(val.isRegExp());
2496         QRegExp out = val.toRegExp();
2497         QEXPECT_FAIL("", "QTBUG-6136: JSC-based back-end doesn't preserve QRegExp::patternSyntax (always uses RegExp2)", Continue);
2498         QCOMPARE(out.patternSyntax(), in.patternSyntax());
2499         QCOMPARE(out.pattern(), in.pattern());
2500         QCOMPARE(out.caseSensitivity(), in.caseSensitivity());
2501         QCOMPARE(out.isMinimal(), in.isMinimal());
2502     }
2503     {
2504         QRegExp in = QRegExp("foo", Qt::CaseSensitive, QRegExp::RegExp2);
2505         QScriptValue val = qScriptValueFromValue(&eng, in);
2506         QVERIFY(val.isRegExp());
2507         QCOMPARE(val.toRegExp(), in);
2508     }
2509     {
2510         QRegExp in = QRegExp("foo");
2511         in.setMinimal(true);
2512         QScriptValue val = qScriptValueFromValue(&eng, in);
2513         QVERIFY(val.isRegExp());
2514         QEXPECT_FAIL("", "QTBUG-6136: JSC-based back-end doesn't preserve QRegExp::minimal (always false)", Continue);
2515         QCOMPARE(val.toRegExp().isMinimal(), in.isMinimal());
2516     }
2517 }
2518 
qScriptValueFromValue_noEngine()2519 void tst_QScriptEngine::qScriptValueFromValue_noEngine()
2520 {
2521     QVERIFY(!qScriptValueFromValue(0, 123).isValid());
2522     QVERIFY(!qScriptValueFromValue(0, QVariant(123)).isValid());
2523 }
2524 
__import__(QScriptContext * ctx,QScriptEngine * eng)2525 static QScriptValue __import__(QScriptContext *ctx, QScriptEngine *eng)
2526 {
2527     return eng->importExtension(ctx->argument(0).toString());
2528 }
2529 
importExtension()2530 void tst_QScriptEngine::importExtension()
2531 {
2532     QStringList libPaths = QCoreApplication::instance()->libraryPaths();
2533     QCoreApplication::instance()->setLibraryPaths(QStringList() << SRCDIR);
2534 
2535     QStringList availableExtensions;
2536     {
2537         QScriptEngine eng;
2538         QVERIFY(eng.importedExtensions().isEmpty());
2539         QStringList ret = eng.availableExtensions();
2540         QCOMPARE(ret.size(), 4);
2541         QCOMPARE(ret.at(0), QString::fromLatin1("com"));
2542         QCOMPARE(ret.at(1), QString::fromLatin1("com.trolltech"));
2543         QCOMPARE(ret.at(2), QString::fromLatin1("com.trolltech.recursive"));
2544         QCOMPARE(ret.at(3), QString::fromLatin1("com.trolltech.syntaxerror"));
2545         availableExtensions = ret;
2546     }
2547 
2548     // try to import something that doesn't exist
2549     {
2550         QScriptEngine eng;
2551         QScriptValue ret = eng.importExtension("this.extension.does.not.exist");
2552         QCOMPARE(eng.hasUncaughtException(), true);
2553         QCOMPARE(ret.isError(), true);
2554         QCOMPARE(ret.toString(), QString::fromLatin1("Error: Unable to import this.extension.does.not.exist: no such extension"));
2555     }
2556 
2557     {
2558         QScriptEngine eng;
2559         for (int x = 0; x < 2; ++x) {
2560             QCOMPARE(eng.globalObject().property("com").isValid(), x == 1);
2561             QScriptValue ret = eng.importExtension("com.trolltech");
2562             QCOMPARE(eng.hasUncaughtException(), false);
2563             QCOMPARE(ret.isUndefined(), true);
2564 
2565             QScriptValue com = eng.globalObject().property("com");
2566             QCOMPARE(com.isObject(), true);
2567             QCOMPARE(com.property("wasDefinedAlready")
2568                      .strictlyEquals(QScriptValue(&eng, false)), true);
2569             QCOMPARE(com.property("name")
2570                      .strictlyEquals(QScriptValue(&eng, "com")), true);
2571             QCOMPARE(com.property("level")
2572                      .strictlyEquals(QScriptValue(&eng, 1)), true);
2573             QVERIFY(com.property("originalPostInit").isUndefined());
2574             QVERIFY(com.property("postInitCallCount").strictlyEquals(1));
2575 
2576             QScriptValue trolltech = com.property("trolltech");
2577             QCOMPARE(trolltech.isObject(), true);
2578             QCOMPARE(trolltech.property("wasDefinedAlready")
2579                      .strictlyEquals(QScriptValue(&eng, false)), true);
2580             QCOMPARE(trolltech.property("name")
2581                      .strictlyEquals(QScriptValue(&eng, "com.trolltech")), true);
2582             QCOMPARE(trolltech.property("level")
2583                      .strictlyEquals(QScriptValue(&eng, 2)), true);
2584             QVERIFY(trolltech.property("originalPostInit").isUndefined());
2585             QVERIFY(trolltech.property("postInitCallCount").strictlyEquals(1));
2586         }
2587         QStringList imp = eng.importedExtensions();
2588         QCOMPARE(imp.size(), 2);
2589         QCOMPARE(imp.at(0), QString::fromLatin1("com"));
2590         QCOMPARE(imp.at(1), QString::fromLatin1("com.trolltech"));
2591         QCOMPARE(eng.availableExtensions(), availableExtensions);
2592     }
2593 
2594     // recursive import should throw an error
2595     {
2596         QScriptEngine eng;
2597         QVERIFY(eng.importedExtensions().isEmpty());
2598         eng.globalObject().setProperty("__import__", eng.newFunction(__import__));
2599         QScriptValue ret = eng.importExtension("com.trolltech.recursive");
2600         QCOMPARE(eng.hasUncaughtException(), true);
2601         QVERIFY(ret.isError());
2602         QCOMPARE(ret.toString(), QString::fromLatin1("Error: recursive import of com.trolltech.recursive"));
2603         QStringList imp = eng.importedExtensions();
2604         QCOMPARE(imp.size(), 2);
2605         QCOMPARE(imp.at(0), QString::fromLatin1("com"));
2606         QCOMPARE(imp.at(1), QString::fromLatin1("com.trolltech"));
2607         QCOMPARE(eng.availableExtensions(), availableExtensions);
2608     }
2609 
2610     {
2611         QScriptEngine eng;
2612         eng.globalObject().setProperty("__import__", eng.newFunction(__import__));
2613         for (int x = 0; x < 2; ++x) {
2614             if (x == 0)
2615                 QVERIFY(eng.importedExtensions().isEmpty());
2616             QScriptValue ret = eng.importExtension("com.trolltech.syntaxerror");
2617             QVERIFY(eng.hasUncaughtException());
2618             QEXPECT_FAIL("", "JSC throws syntax error eagerly", Continue);
2619             QCOMPARE(eng.uncaughtExceptionLineNumber(), 4);
2620             QVERIFY(ret.isError());
2621             QVERIFY(ret.toString().contains(QLatin1String("SyntaxError")));
2622         }
2623         QStringList imp = eng.importedExtensions();
2624         QCOMPARE(imp.size(), 2);
2625         QCOMPARE(imp.at(0), QString::fromLatin1("com"));
2626         QCOMPARE(imp.at(1), QString::fromLatin1("com.trolltech"));
2627         QCOMPARE(eng.availableExtensions(), availableExtensions);
2628     }
2629 
2630     QCoreApplication::instance()->setLibraryPaths(libPaths);
2631 }
2632 
recurse(QScriptContext * ctx,QScriptEngine * eng)2633 static QScriptValue recurse(QScriptContext *ctx, QScriptEngine *eng)
2634 {
2635     Q_UNUSED(eng);
2636     return ctx->callee().call();
2637 }
2638 
recurse2(QScriptContext * ctx,QScriptEngine * eng)2639 static QScriptValue recurse2(QScriptContext *ctx, QScriptEngine *eng)
2640 {
2641     Q_UNUSED(eng);
2642     return ctx->callee().construct();
2643 }
2644 
infiniteRecursion()2645 void tst_QScriptEngine::infiniteRecursion()
2646 {
2647     // Infinite recursion in JS should cause the VM to throw an error
2648     // when the JS stack is exhausted.
2649     // The exact error is back-end specific and subject to change.
2650     const QString stackOverflowError = QString::fromLatin1("RangeError: Maximum call stack size exceeded.");
2651     QScriptEngine eng;
2652     {
2653         QScriptValue ret = eng.evaluate("function foo() { foo(); }; foo();");
2654         QCOMPARE(ret.isError(), true);
2655         QCOMPARE(ret.toString(), stackOverflowError);
2656     }
2657 #if 0 //The native C++ stack overflow before the JS stack
2658     {
2659         QScriptValue fun = eng.newFunction(recurse);
2660         QScriptValue ret = fun.call();
2661         QCOMPARE(ret.isError(), true);
2662         QCOMPARE(ret.toString(), stackOverflowError);
2663     }
2664     {
2665         QScriptValue fun = eng.newFunction(recurse2);
2666         QScriptValue ret = fun.construct();
2667         QCOMPARE(ret.isError(), true);
2668         QCOMPARE(ret.toString(), stackOverflowError);
2669     }
2670 #endif
2671 }
2672 
2673 struct Bar {
2674     int a;
2675 };
2676 
2677 struct Baz : public Bar {
2678     int b;
2679 };
2680 
2681 Q_DECLARE_METATYPE(Bar*)
2682 Q_DECLARE_METATYPE(Baz*)
2683 
2684 Q_DECLARE_METATYPE(QGradient)
2685 Q_DECLARE_METATYPE(QGradient*)
2686 Q_DECLARE_METATYPE(QLinearGradient)
2687 
2688 class Zoo : public QObject
2689 {
2690     Q_OBJECT
2691 public:
Zoo()2692     Zoo() { }
2693 public slots:
toBaz(Bar * b)2694     Baz *toBaz(Bar *b) { return reinterpret_cast<Baz*>(b); }
2695 };
2696 
castWithPrototypeChain()2697 void tst_QScriptEngine::castWithPrototypeChain()
2698 {
2699     QScriptEngine eng;
2700     Bar bar;
2701     Baz baz;
2702     QScriptValue barProto = qScriptValueFromValue(&eng, &bar);
2703     QScriptValue bazProto = qScriptValueFromValue(&eng, &baz);
2704     eng.setDefaultPrototype(qMetaTypeId<Bar*>(), barProto);
2705     eng.setDefaultPrototype(qMetaTypeId<Baz*>(), bazProto);
2706 
2707     Baz baz2;
2708     baz2.a = 123;
2709     baz2.b = 456;
2710     QScriptValue baz2Value = qScriptValueFromValue(&eng, &baz2);
2711     {
2712         // qscriptvalue_cast() does magic; if the QScriptValue contains
2713         // t of type T, and the target type is T*, &t is returned.
2714         Baz *pbaz = qscriptvalue_cast<Baz*>(baz2Value);
2715         QVERIFY(pbaz != 0);
2716         QCOMPARE(pbaz->b, baz2.b);
2717 
2718         Zoo zoo;
2719         QScriptValue scriptZoo = eng.newQObject(&zoo);
2720         QScriptValue toBaz = scriptZoo.property("toBaz");
2721         QVERIFY(toBaz.isFunction());
2722 
2723         // no relation between Bar and Baz's proto --> casting fails
2724         {
2725             Bar *pbar = qscriptvalue_cast<Bar*>(baz2Value);
2726             QVERIFY(pbar == 0);
2727         }
2728 
2729         {
2730             QScriptValue ret = toBaz.call(scriptZoo, QScriptValueList() << baz2Value);
2731             QVERIFY(ret.isError());
2732             QCOMPARE(ret.toString(), QLatin1String("TypeError: incompatible type of argument(s) in call to toBaz(); candidates were\n    toBaz(Bar*)"));
2733         }
2734 
2735         // establish chain -- now casting should work
2736         // Why? because qscriptvalue_cast() does magic again.
2737         // It the instance itself is not of type T, qscriptvalue_cast()
2738         // searches the prototype chain for T, and if it finds one, it infers
2739         // that the instance can also be casted to that type. This cast is
2740         // _not_ safe and thus relies on the developer doing the right thing.
2741         // This is an undocumented feature to enable qscriptvalue_cast() to
2742         // be used by prototype functions to cast the JS this-object to C++.
2743         bazProto.setPrototype(barProto);
2744 
2745         {
2746             Bar *pbar = qscriptvalue_cast<Bar*>(baz2Value);
2747             QVERIFY(pbar != 0);
2748             QCOMPARE(pbar->a, baz2.a);
2749         }
2750 
2751         {
2752             QScriptValue ret = toBaz.call(scriptZoo, QScriptValueList() << baz2Value);
2753             QVERIFY(!ret.isError());
2754             QCOMPARE(qscriptvalue_cast<Baz*>(ret), pbaz);
2755         }
2756     }
2757 
2758     bazProto.setPrototype(barProto.prototype()); // kill chain
2759     {
2760         Baz *pbaz = qscriptvalue_cast<Baz*>(baz2Value);
2761         QVERIFY(pbaz != 0);
2762         // should not work anymore
2763         Bar *pbar = qscriptvalue_cast<Bar*>(baz2Value);
2764         QVERIFY(pbar == 0);
2765     }
2766 
2767     bazProto.setPrototype(eng.newQObject(this));
2768     {
2769         Baz *pbaz = qscriptvalue_cast<Baz*>(baz2Value);
2770         QVERIFY(pbaz != 0);
2771         // should not work now either
2772         Bar *pbar = qscriptvalue_cast<Bar*>(baz2Value);
2773         QVERIFY(pbar == 0);
2774     }
2775 
2776     {
2777         QScriptValue b = qScriptValueFromValue(&eng, QBrush());
2778         b.setPrototype(barProto);
2779         // this shows that a "wrong" cast is possible, if you
2780         // don't play by the rules (the pointer is actually a QBrush*)...
2781         Bar *pbar = qscriptvalue_cast<Bar*>(b);
2782         QVERIFY(pbar != 0);
2783     }
2784 
2785     {
2786         QScriptValue gradientProto = qScriptValueFromValue(&eng, QGradient());
2787         QScriptValue linearGradientProto = qScriptValueFromValue(&eng, QLinearGradient());
2788         linearGradientProto.setPrototype(gradientProto);
2789         QLinearGradient lg(10, 20, 30, 40);
2790         QScriptValue linearGradient = qScriptValueFromValue(&eng, lg);
2791         {
2792             QGradient *pgrad = qscriptvalue_cast<QGradient*>(linearGradient);
2793             QVERIFY(pgrad == 0);
2794         }
2795         linearGradient.setPrototype(linearGradientProto);
2796         {
2797             QGradient *pgrad = qscriptvalue_cast<QGradient*>(linearGradient);
2798             QVERIFY(pgrad != 0);
2799             QCOMPARE(pgrad->type(), QGradient::LinearGradient);
2800             QLinearGradient *plingrad = static_cast<QLinearGradient*>(pgrad);
2801             QCOMPARE(plingrad->start(), lg.start());
2802             QCOMPARE(plingrad->finalStop(), lg.finalStop());
2803         }
2804     }
2805 }
2806 
2807 class Klazz : public QWidget,
2808               public QStandardItem,
2809               public QGraphicsItem
2810 {
2811     Q_OBJECT
2812 public:
Klazz(QWidget * parent=0)2813     Klazz(QWidget *parent = 0) : QWidget(parent) { }
boundingRect() const2814     virtual QRectF boundingRect() const { return QRectF(); }
paint(QPainter *,const QStyleOptionGraphicsItem *,QWidget *)2815     virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) { }
2816 };
2817 
2818 Q_DECLARE_METATYPE(Klazz*)
Q_DECLARE_METATYPE(QStandardItem *)2819 Q_DECLARE_METATYPE(QStandardItem*)
2820 
2821 void tst_QScriptEngine::castWithMultipleInheritance()
2822 {
2823     QScriptEngine eng;
2824     Klazz klz;
2825     QScriptValue v = eng.newQObject(&klz);
2826 
2827     QCOMPARE(qscriptvalue_cast<Klazz*>(v), &klz);
2828     QCOMPARE(qscriptvalue_cast<QWidget*>(v), (QWidget *)&klz);
2829     QCOMPARE(qscriptvalue_cast<QObject*>(v), (QObject *)&klz);
2830     QCOMPARE(qscriptvalue_cast<QStandardItem*>(v), (QStandardItem *)&klz);
2831     QCOMPARE(qscriptvalue_cast<QGraphicsItem*>(v), (QGraphicsItem *)&klz);
2832 }
2833 
collectGarbage()2834 void tst_QScriptEngine::collectGarbage()
2835 {
2836     QScriptEngine eng;
2837     eng.evaluate("a = new Object(); a = new Object(); a = new Object()");
2838     QScriptValue a = eng.newObject();
2839     a = eng.newObject();
2840     a = eng.newObject();
2841     QPointer<QObject> ptr = new QObject();
2842     QVERIFY(ptr != 0);
2843     (void)eng.newQObject(ptr, QScriptEngine::ScriptOwnership);
2844     collectGarbage_helper(eng);
2845     QVERIFY(ptr == 0);
2846 }
2847 
reportAdditionalMemoryCost()2848 void tst_QScriptEngine::reportAdditionalMemoryCost()
2849 {
2850     QScriptEngine eng;
2851     // There isn't any reliable way to test whether calling
2852     // this function affects garbage collection responsiveness;
2853     // the best we can do is call it with a few different values.
2854     for (int x = 0; x < 1000; ++x) {
2855         eng.reportAdditionalMemoryCost(0);
2856         eng.reportAdditionalMemoryCost(10);
2857         eng.reportAdditionalMemoryCost(1000);
2858         eng.reportAdditionalMemoryCost(10000);
2859         eng.reportAdditionalMemoryCost(100000);
2860         eng.reportAdditionalMemoryCost(1000000);
2861         eng.reportAdditionalMemoryCost(10000000);
2862         eng.reportAdditionalMemoryCost(-1);
2863         eng.reportAdditionalMemoryCost(-1000);
2864         QScriptValue obj = eng.newObject();
2865         eng.collectGarbage();
2866     }
2867 }
2868 
gcWithNestedDataStructure()2869 void tst_QScriptEngine::gcWithNestedDataStructure()
2870 {
2871     // The GC must be able to traverse deeply nested objects, otherwise this
2872     // test would crash.
2873     QScriptEngine eng;
2874     eng.evaluate(
2875         "function makeList(size)"
2876         "{"
2877         "  var head = { };"
2878         "  var l = head;"
2879         "  for (var i = 0; i < size; ++i) {"
2880         "    l.data = i + \"\";"
2881         "    l.next = { }; l = l.next;"
2882         "  }"
2883         "  l.next = null;"
2884         "  return head;"
2885         "}");
2886     QCOMPARE(eng.hasUncaughtException(), false);
2887     const int size = 200;
2888     QScriptValue head = eng.evaluate(QString::fromLatin1("makeList(%0)").arg(size));
2889     QCOMPARE(eng.hasUncaughtException(), false);
2890     for (int x = 0; x < 2; ++x) {
2891         if (x == 1)
2892             eng.evaluate("gc()");
2893         QScriptValue l = head;
2894         // Make sure all the nodes are still alive.
2895         for (int i = 0; i < 200; ++i) {
2896             QCOMPARE(l.property("data").toString(), QString::number(i));
2897             l = l.property("next");
2898         }
2899     }
2900 }
2901 
2902 class EventReceiver : public QObject
2903 {
2904 public:
EventReceiver()2905     EventReceiver() {
2906         received = false;
2907     }
2908 
event(QEvent * e)2909     bool event(QEvent *e) {
2910         received |= (e->type() == QEvent::User + 1);
2911         return QObject::event(e);
2912     }
2913 
2914     bool received;
2915 };
2916 
processEventsWhileRunning()2917 void tst_QScriptEngine::processEventsWhileRunning()
2918 {
2919     for (int x = 0; x < 2; ++x) {
2920         QScriptEngine eng;
2921         if (x == 0)
2922             eng.pushContext();
2923 
2924         // This is running for a silly amount of time just to make sure
2925         // the script doesn't finish before event processing is triggered.
2926         QString script = QString::fromLatin1(
2927             "var end = Number(new Date()) + 2000;"
2928             "var x = 0;"
2929             "while (Number(new Date()) < end) {"
2930             "    ++x;"
2931             "}");
2932 
2933         EventReceiver receiver;
2934         QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1)));
2935 
2936         eng.evaluate(script);
2937         QVERIFY(!eng.hasUncaughtException());
2938         QVERIFY(!receiver.received);
2939 
2940         QCOMPARE(eng.processEventsInterval(), -1);
2941         eng.setProcessEventsInterval(100);
2942         eng.evaluate(script);
2943         QVERIFY(!eng.hasUncaughtException());
2944         QVERIFY(receiver.received);
2945 
2946         if (x == 0)
2947             eng.popContext();
2948     }
2949 }
2950 
2951 class EventReceiver2 : public QObject
2952 {
2953 public:
EventReceiver2(QScriptEngine * eng)2954     EventReceiver2(QScriptEngine *eng) {
2955         engine = eng;
2956     }
2957 
event(QEvent * e)2958     bool event(QEvent *e) {
2959         if (e->type() == QEvent::User + 1) {
2960             engine->currentContext()->throwError("Killed");
2961         }
2962         return QObject::event(e);
2963     }
2964 
2965     QScriptEngine *engine;
2966 };
2967 
throwErrorFromProcessEvents_data()2968 void tst_QScriptEngine::throwErrorFromProcessEvents_data()
2969 {
2970     QTest::addColumn<QString>("script");
2971     QTest::addColumn<QString>("error");
2972 
2973     QTest::newRow("while (1)")
2974         << QString::fromLatin1("while (1) { }")
2975         << QString::fromLatin1("Error: Killed");
2976     QTest::newRow("while (1) i++")
2977         << QString::fromLatin1("i = 0; while (1) { i++; }")
2978         << QString::fromLatin1("Error: Killed");
2979     // Unlike abortEvaluation(), scripts should be able to catch the
2980     // exception.
2981     QTest::newRow("try catch")
2982         << QString::fromLatin1("try {"
2983                                "    while (1) { }"
2984                                "} catch(e) {"
2985                                "    throw new Error('Caught');"
2986                                "}")
2987         << QString::fromLatin1("Error: Caught");
2988 }
2989 
throwErrorFromProcessEvents()2990 void tst_QScriptEngine::throwErrorFromProcessEvents()
2991 {
2992     QFETCH(QString, script);
2993     QFETCH(QString, error);
2994 
2995     QScriptEngine eng;
2996 
2997     EventReceiver2 receiver(&eng);
2998     QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1)));
2999 
3000     eng.setProcessEventsInterval(100);
3001     QScriptValue ret = eng.evaluate(script);
3002     QVERIFY(ret.isError());
3003     QCOMPARE(ret.toString(), error);
3004 }
3005 
disableProcessEventsInterval()3006 void tst_QScriptEngine::disableProcessEventsInterval()
3007 {
3008     QScriptEngine eng;
3009     eng.setProcessEventsInterval(100);
3010     QCOMPARE(eng.processEventsInterval(), 100);
3011     eng.setProcessEventsInterval(0);
3012     QCOMPARE(eng.processEventsInterval(), 0);
3013     eng.setProcessEventsInterval(-1);
3014     QCOMPARE(eng.processEventsInterval(), -1);
3015     eng.setProcessEventsInterval(-100);
3016     QCOMPARE(eng.processEventsInterval(), -100);
3017 }
3018 
stacktrace()3019 void tst_QScriptEngine::stacktrace()
3020 {
3021     QString script = QString::fromLatin1(
3022         "function foo(counter) {\n"
3023         "    switch (counter) {\n"
3024         "        case 0: foo(counter+1); break;\n"
3025         "        case 1: foo(counter+1); break;\n"
3026         "        case 2: foo(counter+1); break;\n"
3027         "        case 3: foo(counter+1); break;\n"
3028         "        case 4: foo(counter+1); break;\n"
3029         "        default:\n"
3030         "        throw new Error('blah');\n"
3031         "    }\n"
3032         "}\n"
3033         "foo(0);");
3034 
3035     const QString fileName("testfile");
3036 
3037     QStringList backtrace;
3038     backtrace << "foo(5)@testfile:9"
3039               << "foo(4)@testfile:7"
3040               << "foo(3)@testfile:6"
3041               << "foo(2)@testfile:5"
3042               << "foo(1)@testfile:4"
3043               << "foo(0)@testfile:3"
3044               << "<global>()@testfile:12";
3045 
3046     QScriptEngine eng;
3047     QScriptValue result = eng.evaluate(script, fileName);
3048     QVERIFY(eng.hasUncaughtException());
3049     QVERIFY(result.isError());
3050 
3051     QEXPECT_FAIL("", "QTBUG-6139: uncaughtExceptionBacktrace() doesn't give the full backtrace", Abort);
3052     QCOMPARE(eng.uncaughtExceptionBacktrace(), backtrace);
3053     QVERIFY(eng.hasUncaughtException());
3054     QVERIFY(result.strictlyEquals(eng.uncaughtException()));
3055 
3056     QCOMPARE(result.property("fileName").toString(), fileName);
3057     QCOMPARE(result.property("lineNumber").toInt32(), 9);
3058 
3059     QScriptValue stack = result.property("stack");
3060     QVERIFY(stack.isArray());
3061 
3062     QCOMPARE(stack.property("length").toInt32(), 7);
3063 
3064     QScriptValueIterator it(stack);
3065     int counter = 5;
3066     while (it.hasNext()) {
3067         it.next();
3068         QScriptValue obj = it.value();
3069         QScriptValue frame = obj.property("frame");
3070 
3071         QCOMPARE(obj.property("fileName").toString(), fileName);
3072         if (counter >= 0) {
3073             QScriptValue callee = frame.property("arguments").property("callee");
3074             QVERIFY(callee.strictlyEquals(eng.globalObject().property("foo")));
3075             QCOMPARE(obj.property("functionName").toString(), QString("foo"));
3076             int line = obj.property("lineNumber").toInt32();
3077             if (counter == 5)
3078                 QCOMPARE(line, 9);
3079             else
3080                 QCOMPARE(line, 3 + counter);
3081         } else {
3082             QVERIFY(frame.strictlyEquals(eng.globalObject()));
3083             QVERIFY(obj.property("functionName").toString().isEmpty());
3084         }
3085 
3086         --counter;
3087     }
3088 
3089     {
3090         QScriptValue bt = result.property("backtrace").call(result);
3091         QCOMPARE(qscriptvalue_cast<QStringList>(bt), backtrace);
3092     }
3093 
3094     // throw something that isn't an Error object
3095     eng.clearExceptions();
3096     QVERIFY(eng.uncaughtExceptionBacktrace().isEmpty());
3097     QString script2 = QString::fromLatin1(
3098         "function foo(counter) {\n"
3099         "    switch (counter) {\n"
3100         "        case 0: foo(counter+1); break;\n"
3101         "        case 1: foo(counter+1); break;\n"
3102         "        case 2: foo(counter+1); break;\n"
3103         "        case 3: foo(counter+1); break;\n"
3104         "        case 4: foo(counter+1); break;\n"
3105         "        default:\n"
3106         "        throw 'just a string';\n"
3107         "    }\n"
3108         "}\n"
3109         "foo(0);");
3110 
3111     QScriptValue result2 = eng.evaluate(script2, fileName);
3112     QVERIFY(eng.hasUncaughtException());
3113     QVERIFY(!result2.isError());
3114     QVERIFY(result2.isString());
3115 
3116     QCOMPARE(eng.uncaughtExceptionBacktrace(), backtrace);
3117     QVERIFY(eng.hasUncaughtException());
3118 
3119     eng.clearExceptions();
3120     QVERIFY(!eng.hasUncaughtException());
3121     QVERIFY(eng.uncaughtExceptionBacktrace().isEmpty());
3122 }
3123 
numberParsing_data()3124 void tst_QScriptEngine::numberParsing_data()
3125 {
3126     QTest::addColumn<QString>("string");
3127     QTest::addColumn<qsreal>("expect");
3128 
3129     QTest::newRow("decimal 0") << QString("0") << qsreal(0);
3130     QTest::newRow("octal 0") << QString("00") << qsreal(00);
3131     QTest::newRow("hex 0") << QString("0x0") << qsreal(0x0);
3132     QTest::newRow("decimal 100") << QString("100") << qsreal(100);
3133     QTest::newRow("hex 100") << QString("0x100") << qsreal(0x100);
3134     QTest::newRow("octal 100") << QString("0100") << qsreal(0100);
3135     QTest::newRow("decimal 4G") << QString("4294967296") << qsreal(Q_UINT64_C(4294967296));
3136     QTest::newRow("hex 4G") << QString("0x100000000") << qsreal(Q_UINT64_C(0x100000000));
3137     QTest::newRow("octal 4G") << QString("040000000000") << qsreal(Q_UINT64_C(040000000000));
3138     QTest::newRow("0.5") << QString("0.5") << qsreal(0.5);
3139     QTest::newRow("1.5") << QString("1.5") << qsreal(1.5);
3140     QTest::newRow("1e2") << QString("1e2") << qsreal(100);
3141 }
3142 
numberParsing()3143 void tst_QScriptEngine::numberParsing()
3144 {
3145     QFETCH(QString, string);
3146     QFETCH(qsreal, expect);
3147 
3148     QScriptEngine eng;
3149     QScriptValue ret = eng.evaluate(string);
3150     QVERIFY(ret.isNumber());
3151     qsreal actual = ret.toNumber();
3152     QCOMPARE(actual, expect);
3153 }
3154 
3155 // see ECMA-262, section 7.9
3156 // This is testing ECMA compliance, not our C++ API, but it's important that
3157 // the back-end is conformant in this regard.
automaticSemicolonInsertion()3158 void tst_QScriptEngine::automaticSemicolonInsertion()
3159 {
3160     QScriptEngine eng;
3161     {
3162         QScriptValue ret = eng.evaluate("{ 1 2 } 3");
3163         QVERIFY(ret.isError());
3164         QVERIFY(ret.toString().contains("SyntaxError"));
3165     }
3166     {
3167         QScriptValue ret = eng.evaluate("{ 1\n2 } 3");
3168         QVERIFY(ret.isNumber());
3169         QCOMPARE(ret.toInt32(), 3);
3170     }
3171     {
3172         QScriptValue ret = eng.evaluate("for (a; b\n)");
3173         QVERIFY(ret.isError());
3174         QVERIFY(ret.toString().contains("SyntaxError"));
3175     }
3176     {
3177         QScriptValue ret = eng.evaluate("(function() { return\n1 + 2 })()");
3178         QVERIFY(ret.isUndefined());
3179     }
3180     {
3181         eng.evaluate("c = 2; b = 1");
3182         QScriptValue ret = eng.evaluate("a = b\n++c");
3183         QVERIFY(ret.isNumber());
3184         QCOMPARE(ret.toInt32(), 3);
3185     }
3186     {
3187         QScriptValue ret = eng.evaluate("if (a > b)\nelse c = d");
3188         QVERIFY(ret.isError());
3189         QVERIFY(ret.toString().contains("SyntaxError"));
3190     }
3191     {
3192         eng.evaluate("function c() { return { foo: function() { return 5; } } }");
3193         eng.evaluate("b = 1; d = 2; e = 3");
3194         QScriptValue ret = eng.evaluate("a = b + c\n(d + e).foo()");
3195         QVERIFY(ret.isNumber());
3196         QCOMPARE(ret.toInt32(), 6);
3197     }
3198     {
3199         QScriptValue ret = eng.evaluate("throw\n1");
3200         QVERIFY(ret.isError());
3201         QVERIFY(ret.toString().contains("SyntaxError"));
3202     }
3203     {
3204         QScriptValue ret = eng.evaluate("a = Number(1)\n++a");
3205         QVERIFY(ret.isNumber());
3206         QCOMPARE(ret.toInt32(), 2);
3207     }
3208 
3209     // "a semicolon is never inserted automatically if the semicolon
3210     // would then be parsed as an empty statement"
3211     {
3212         eng.evaluate("a = 123");
3213         QScriptValue ret = eng.evaluate("if (0)\n ++a; a");
3214         QVERIFY(ret.isNumber());
3215         QCOMPARE(ret.toInt32(), 123);
3216     }
3217     {
3218         eng.evaluate("a = 123");
3219         QScriptValue ret = eng.evaluate("if (0)\n --a; a");
3220         QVERIFY(ret.isNumber());
3221         QCOMPARE(ret.toInt32(), 123);
3222     }
3223     {
3224         eng.evaluate("a = 123");
3225         QScriptValue ret = eng.evaluate("if ((0))\n ++a; a");
3226         QVERIFY(ret.isNumber());
3227         QCOMPARE(ret.toInt32(), 123);
3228     }
3229     {
3230         eng.evaluate("a = 123");
3231         QScriptValue ret = eng.evaluate("if ((0))\n --a; a");
3232         QVERIFY(ret.isNumber());
3233         QCOMPARE(ret.toInt32(), 123);
3234     }
3235     {
3236         eng.evaluate("a = 123");
3237         QScriptValue ret = eng.evaluate("if (0\n)\n ++a; a");
3238         QVERIFY(ret.isNumber());
3239         QCOMPARE(ret.toInt32(), 123);
3240     }
3241     {
3242         eng.evaluate("a = 123");
3243         QScriptValue ret = eng.evaluate("if (0\n ++a; a");
3244         QVERIFY(ret.isError());
3245     }
3246     {
3247         eng.evaluate("a = 123");
3248         QScriptValue ret = eng.evaluate("if (0))\n ++a; a");
3249         QVERIFY(ret.isError());
3250     }
3251     {
3252         QScriptValue ret = eng.evaluate("n = 0; for (i = 0; i < 10; ++i)\n ++n; n");
3253         QVERIFY(ret.isNumber());
3254         QCOMPARE(ret.toInt32(), 10);
3255     }
3256     {
3257         QScriptValue ret = eng.evaluate("n = 30; for (i = 0; i < 10; ++i)\n --n; n");
3258         QVERIFY(ret.isNumber());
3259         QCOMPARE(ret.toInt32(), 20);
3260     }
3261     {
3262         QScriptValue ret = eng.evaluate("n = 0; for (var i = 0; i < 10; ++i)\n ++n; n");
3263         QVERIFY(ret.isNumber());
3264         QCOMPARE(ret.toInt32(), 10);
3265     }
3266     {
3267         QScriptValue ret = eng.evaluate("n = 30; for (var i = 0; i < 10; ++i)\n --n; n");
3268         QVERIFY(ret.isNumber());
3269         QCOMPARE(ret.toInt32(), 20);
3270     }
3271     {
3272         QScriptValue ret = eng.evaluate("n = 0; i = 0; while (i++ < 10)\n ++n; n");
3273         QVERIFY(ret.isNumber());
3274         QCOMPARE(ret.toInt32(), 10);
3275     }
3276     {
3277         QScriptValue ret = eng.evaluate("n = 30; i = 0; while (i++ < 10)\n --n; n");
3278         QVERIFY(ret.isNumber());
3279         QCOMPARE(ret.toInt32(), 20);
3280     }
3281     {
3282         QScriptValue ret = eng.evaluate("o = { a: 0, b: 1, c: 2 }; n = 0; for (i in o)\n ++n; n");
3283         QVERIFY(ret.isNumber());
3284         QCOMPARE(ret.toInt32(), 3);
3285     }
3286     {
3287         QScriptValue ret = eng.evaluate("o = { a: 0, b: 1, c: 2 }; n = 9; for (i in o)\n --n; n");
3288         QVERIFY(ret.isNumber());
3289         QCOMPARE(ret.toInt32(), 6);
3290     }
3291     {
3292         QScriptValue ret = eng.evaluate("o = { a: 0, b: 1, c: 2 }; n = 0; for (var i in o)\n ++n; n");
3293         QVERIFY(ret.isNumber());
3294         QCOMPARE(ret.toInt32(), 3);
3295     }
3296     {
3297         QScriptValue ret = eng.evaluate("o = { a: 0, b: 1, c: 2 }; n = 9; for (var i in o)\n --n; n");
3298         QVERIFY(ret.isNumber());
3299         QCOMPARE(ret.toInt32(), 6);
3300     }
3301     {
3302         QScriptValue ret = eng.evaluate("o = { n: 3 }; n = 5; with (o)\n ++n; n");
3303         QVERIFY(ret.isNumber());
3304         QCOMPARE(ret.toInt32(), 5);
3305     }
3306     {
3307         QScriptValue ret = eng.evaluate("o = { n: 3 }; n = 10; with (o)\n --n; n");
3308         QVERIFY(ret.isNumber());
3309         QCOMPARE(ret.toInt32(), 10);
3310     }
3311     {
3312         QScriptValue ret = eng.evaluate("n = 5; i = 0; do\n ++n; while (++i < 10); n");
3313         QVERIFY(ret.isNumber());
3314         QCOMPARE(ret.toInt32(), 15);
3315     }
3316     {
3317         QScriptValue ret = eng.evaluate("n = 20; i = 0; do\n --n; while (++i < 10); n");
3318         QVERIFY(ret.isNumber());
3319         QCOMPARE(ret.toInt32(), 10);
3320     }
3321 
3322     {
3323         QScriptValue ret = eng.evaluate("n = 1; i = 0; if (n) i\n++n; n");
3324         QVERIFY(ret.isNumber());
3325         QCOMPARE(ret.toInt32(), 2);
3326     }
3327     {
3328         QScriptValue ret = eng.evaluate("n = 1; i = 0; if (n) i\n--n; n");
3329         QVERIFY(ret.isNumber());
3330         QCOMPARE(ret.toInt32(), 0);
3331     }
3332 
3333     {
3334         QScriptValue ret = eng.evaluate("if (0)");
3335         QVERIFY(ret.isError());
3336     }
3337     {
3338         QScriptValue ret = eng.evaluate("while (0)");
3339         QVERIFY(ret.isError());
3340     }
3341     {
3342         QScriptValue ret = eng.evaluate("for (;;)");
3343         QVERIFY(ret.isError());
3344     }
3345     {
3346         QScriptValue ret = eng.evaluate("for (p in this)");
3347         QVERIFY(ret.isError());
3348     }
3349     {
3350         QScriptValue ret = eng.evaluate("with (this)");
3351         QVERIFY(ret.isError());
3352     }
3353     {
3354         QScriptValue ret = eng.evaluate("do");
3355         QVERIFY(ret.isError());
3356     }
3357 }
3358 
3359 class EventReceiver3 : public QObject
3360 {
3361 public:
3362     enum AbortionResult {
3363         None = 0,
3364         String = 1,
3365         Error = 2,
3366         Number = 3
3367     };
3368 
EventReceiver3(QScriptEngine * eng)3369     EventReceiver3(QScriptEngine *eng) {
3370         engine = eng;
3371         resultType = None;
3372     }
3373 
event(QEvent * e)3374     bool event(QEvent *e) {
3375         if (e->type() == QEvent::User + 1) {
3376             switch (resultType) {
3377             case None:
3378                 engine->abortEvaluation();
3379                 break;
3380             case String:
3381                 engine->abortEvaluation(QScriptValue(engine, QString::fromLatin1("Aborted")));
3382                 break;
3383             case Error:
3384                 engine->abortEvaluation(engine->currentContext()->throwError("AbortedWithError"));
3385                 break;
3386             case Number:
3387                 engine->abortEvaluation(QScriptValue(1234));
3388             }
3389         }
3390         return QObject::event(e);
3391     }
3392 
3393     AbortionResult resultType;
3394     QScriptEngine *engine;
3395 };
3396 
myFunctionAbortingEvaluation(QScriptContext *,QScriptEngine * eng)3397 static QScriptValue myFunctionAbortingEvaluation(QScriptContext *, QScriptEngine *eng)
3398 {
3399     eng->abortEvaluation();
3400     return eng->nullValue(); // should be ignored
3401 }
3402 
abortEvaluation_notEvaluating()3403 void tst_QScriptEngine::abortEvaluation_notEvaluating()
3404 {
3405     QScriptEngine eng;
3406 
3407     eng.abortEvaluation();
3408     QVERIFY(!eng.hasUncaughtException());
3409 
3410     eng.abortEvaluation(123);
3411     {
3412         QScriptValue ret = eng.evaluate("'ciao'");
3413         QVERIFY(ret.isString());
3414         QCOMPARE(ret.toString(), QString::fromLatin1("ciao"));
3415     }
3416 }
3417 
abortEvaluation_data()3418 void tst_QScriptEngine::abortEvaluation_data()
3419 {
3420     QTest::addColumn<QString>("script");
3421 
3422     QTest::newRow("while (1)")
3423         << QString::fromLatin1("while (1) { }");
3424     QTest::newRow("while (1) i++")
3425         << QString::fromLatin1("i = 0; while (1) { i++; }");
3426     QTest::newRow("try catch")
3427         << QString::fromLatin1("try {"
3428                                "    while (1) { }"
3429                                "} catch(e) {"
3430                                "    throw new Error('Caught');"
3431                                "}");
3432 }
3433 
abortEvaluation()3434 void tst_QScriptEngine::abortEvaluation()
3435 {
3436     QFETCH(QString, script);
3437 
3438     QScriptEngine eng;
3439     EventReceiver3 receiver(&eng);
3440 
3441     eng.setProcessEventsInterval(100);
3442     for (int x = 0; x < 4; ++x) {
3443         QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1)));
3444         receiver.resultType = EventReceiver3::AbortionResult(x);
3445         QScriptValue ret = eng.evaluate(script);
3446         switch (receiver.resultType) {
3447         case EventReceiver3::None:
3448             QVERIFY(!eng.hasUncaughtException());
3449             QVERIFY(!ret.isValid());
3450             break;
3451         case EventReceiver3::Number:
3452             QVERIFY(!eng.hasUncaughtException());
3453             QVERIFY(ret.isNumber());
3454             QCOMPARE(ret.toInt32(), 1234);
3455             break;
3456         case EventReceiver3::String:
3457             QVERIFY(!eng.hasUncaughtException());
3458             QVERIFY(ret.isString());
3459             QCOMPARE(ret.toString(), QString::fromLatin1("Aborted"));
3460             break;
3461         case EventReceiver3::Error:
3462             QVERIFY(eng.hasUncaughtException());
3463             QVERIFY(ret.isError());
3464             QCOMPARE(ret.toString(), QString::fromLatin1("Error: AbortedWithError"));
3465             break;
3466         }
3467     }
3468 
3469 }
3470 
abortEvaluation_tryCatch()3471 void tst_QScriptEngine::abortEvaluation_tryCatch()
3472 {
3473     QScriptEngine eng;
3474     EventReceiver3 receiver(&eng);
3475     eng.setProcessEventsInterval(100);
3476     // scripts cannot intercept the abortion with try/catch
3477     for (int y = 0; y < 4; ++y) {
3478         QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1)));
3479         receiver.resultType = EventReceiver3::AbortionResult(y);
3480         QScriptValue ret = eng.evaluate(QString::fromLatin1(
3481                                             "while (1) {\n"
3482                                             "  try {\n"
3483                                             "    (function() { while (1) { } })();\n"
3484                                             "  } catch (e) {\n"
3485                                             "    ;\n"
3486                                             "  }\n"
3487                                             "}"));
3488         switch (receiver.resultType) {
3489         case EventReceiver3::None:
3490             QVERIFY(!eng.hasUncaughtException());
3491             QVERIFY(!ret.isValid());
3492             break;
3493         case EventReceiver3::Number:
3494             QVERIFY(!eng.hasUncaughtException());
3495             QVERIFY(ret.isNumber());
3496             QCOMPARE(ret.toInt32(), 1234);
3497             break;
3498         case EventReceiver3::String:
3499             QVERIFY(!eng.hasUncaughtException());
3500             QVERIFY(ret.isString());
3501             QCOMPARE(ret.toString(), QString::fromLatin1("Aborted"));
3502             break;
3503         case EventReceiver3::Error:
3504             QVERIFY(eng.hasUncaughtException());
3505             QVERIFY(ret.isError());
3506             break;
3507         }
3508     }
3509 }
3510 
abortEvaluation_fromNative()3511 void tst_QScriptEngine::abortEvaluation_fromNative()
3512 {
3513     QScriptEngine eng;
3514     QScriptValue fun = eng.newFunction(myFunctionAbortingEvaluation);
3515     eng.globalObject().setProperty("myFunctionAbortingEvaluation", fun);
3516     QScriptValue ret = eng.evaluate("myFunctionAbortingEvaluation()");
3517     QVERIFY(!ret.isValid());
3518 }
3519 
3520 class ThreadedEngine : public QThread {
3521     Q_OBJECT;
3522 
3523 private:
3524     QScriptEngine* m_engine;
3525 protected:
run()3526     void run() {
3527         m_engine = new QScriptEngine();
3528         m_engine->setGlobalObject(m_engine->newQObject(this));
3529         m_engine->evaluate("while(1) { sleep(); }");
3530         delete m_engine;
3531     }
3532 
3533 public slots:
sleep()3534     void sleep()
3535     {
3536         QTest::qSleep(25);
3537         m_engine->abortEvaluation();
3538     }
3539 };
3540 
abortEvaluation_QTBUG9433()3541 void tst_QScriptEngine::abortEvaluation_QTBUG9433()
3542 {
3543     ThreadedEngine engine;
3544     engine.start();
3545     QVERIFY(engine.isRunning());
3546     QTest::qSleep(50);
3547     for (uint i = 0; i < 50; ++i) { // up to ~2500 ms
3548         if (engine.isFinished())
3549             return;
3550         QTest::qSleep(50);
3551     }
3552     if (!engine.isFinished()) {
3553         engine.terminate();
3554         engine.wait(7000);
3555         QFAIL("abortEvaluation doesn't work");
3556     }
3557 
3558 }
3559 
myFunctionReturningIsEvaluating(QScriptContext *,QScriptEngine * eng)3560 static QScriptValue myFunctionReturningIsEvaluating(QScriptContext *, QScriptEngine *eng)
3561 {
3562     return QScriptValue(eng, eng->isEvaluating());
3563 }
3564 
3565 class EventReceiver4 : public QObject
3566 {
3567 public:
EventReceiver4(QScriptEngine * eng)3568     EventReceiver4(QScriptEngine *eng) {
3569         engine = eng;
3570         wasEvaluating = false;
3571     }
3572 
event(QEvent * e)3573     bool event(QEvent *e) {
3574         if (e->type() == QEvent::User + 1) {
3575             wasEvaluating = engine->isEvaluating();
3576         }
3577         return QObject::event(e);
3578     }
3579 
3580     QScriptEngine *engine;
3581     bool wasEvaluating;
3582 };
3583 
isEvaluating_notEvaluating()3584 void tst_QScriptEngine::isEvaluating_notEvaluating()
3585 {
3586     QScriptEngine eng;
3587 
3588     QVERIFY(!eng.isEvaluating());
3589 
3590     eng.evaluate("");
3591     QVERIFY(!eng.isEvaluating());
3592     eng.evaluate("123");
3593     QVERIFY(!eng.isEvaluating());
3594     eng.evaluate("0 = 0");
3595     QVERIFY(!eng.isEvaluating());
3596 }
3597 
isEvaluating_fromNative()3598 void tst_QScriptEngine::isEvaluating_fromNative()
3599 {
3600     QScriptEngine eng;
3601     QScriptValue fun = eng.newFunction(myFunctionReturningIsEvaluating);
3602     eng.globalObject().setProperty("myFunctionReturningIsEvaluating", fun);
3603     QScriptValue ret = eng.evaluate("myFunctionReturningIsEvaluating()");
3604     QVERIFY(ret.isBoolean());
3605     QVERIFY(ret.toBoolean());
3606 }
3607 
isEvaluating_fromEvent()3608 void tst_QScriptEngine::isEvaluating_fromEvent()
3609 {
3610     QScriptEngine eng;
3611     EventReceiver4 receiver(&eng);
3612     QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1)));
3613 
3614     QString script = QString::fromLatin1(
3615         "var end = Number(new Date()) + 1000;"
3616         "var x = 0;"
3617         "while (Number(new Date()) < end) {"
3618         "    ++x;"
3619         "}");
3620 
3621     eng.setProcessEventsInterval(100);
3622     eng.evaluate(script);
3623     QVERIFY(receiver.wasEvaluating);
3624 }
3625 
3626 static QtMsgType theMessageType;
3627 static QString theMessage;
3628 
myMsgHandler(QtMsgType type,const char * msg)3629 static void myMsgHandler(QtMsgType type, const char *msg)
3630 {
3631     theMessageType = type;
3632     theMessage = QString::fromLatin1(msg);
3633 }
3634 
printFunctionWithCustomHandler()3635 void tst_QScriptEngine::printFunctionWithCustomHandler()
3636 {
3637     // The built-in print() function passes the output to Qt's message
3638     // handler. By installing a custom message handler, the output can be
3639     // redirected without changing the print() function itself.
3640     // This behavior is not documented.
3641     QScriptEngine eng;
3642     QtMsgHandler oldHandler = qInstallMsgHandler(myMsgHandler);
3643     QVERIFY(eng.globalObject().property("print").isFunction());
3644 
3645     theMessageType = QtSystemMsg;
3646     QVERIFY(theMessage.isEmpty());
3647     QVERIFY(eng.evaluate("print('test')").isUndefined());
3648     QCOMPARE(theMessageType, QtDebugMsg);
3649     QCOMPARE(theMessage, QString::fromLatin1("test"));
3650 
3651     theMessageType = QtSystemMsg;
3652     theMessage.clear();
3653     QVERIFY(eng.evaluate("print(3, true, 'little pigs')").isUndefined());
3654     QCOMPARE(theMessageType, QtDebugMsg);
3655     QCOMPARE(theMessage, QString::fromLatin1("3 true little pigs"));
3656 
3657     qInstallMsgHandler(oldHandler);
3658 }
3659 
printThrowsException()3660 void tst_QScriptEngine::printThrowsException()
3661 {
3662     // If an argument to print() causes an exception to be thrown when
3663     // it's converted to a string, print() should propagate the exception.
3664     QScriptEngine eng;
3665     QScriptValue ret = eng.evaluate("print({ toString: function() { throw 'foo'; } });");
3666     QVERIFY(eng.hasUncaughtException());
3667     QVERIFY(ret.strictlyEquals(QScriptValue(&eng, QLatin1String("foo"))));
3668 }
3669 
errorConstructors()3670 void tst_QScriptEngine::errorConstructors()
3671 {
3672     QScriptEngine eng;
3673     QStringList prefixes;
3674     prefixes << "" << "Eval" << "Range" << "Reference" << "Syntax" << "Type" << "URI";
3675     for (int x = 0; x < 3; ++x) {
3676         for (int i = 0; i < prefixes.size(); ++i) {
3677             QString name = prefixes.at(i) + QLatin1String("Error");
3678             QString code = QString(i+1, QLatin1Char('\n'));
3679             if (x == 0)
3680                 code += QLatin1String("throw ");
3681             else if (x == 1)
3682                 code += QLatin1String("new ");
3683             code += name + QLatin1String("()");
3684             QScriptValue ret = eng.evaluate(code);
3685             QVERIFY(ret.isError());
3686             QCOMPARE(eng.hasUncaughtException(), x == 0);
3687             eng.clearExceptions();
3688             QVERIFY(ret.toString().startsWith(name));
3689             if (x != 0)
3690                 QEXPECT_FAIL("", "QTBUG-6138: JSC doesn't assign lineNumber when errors are not thrown", Continue);
3691             QCOMPARE(ret.property("lineNumber").toInt32(), i+2);
3692         }
3693     }
3694 }
3695 
argumentsProperty_globalContext()3696 void tst_QScriptEngine::argumentsProperty_globalContext()
3697 {
3698     QScriptEngine eng;
3699     {
3700         // Unlike function calls, the global context doesn't have an
3701         // arguments property.
3702         QScriptValue ret = eng.evaluate("arguments");
3703         QVERIFY(ret.isError());
3704         QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError")));
3705     }
3706     eng.evaluate("arguments = 10");
3707     {
3708         QScriptValue ret = eng.evaluate("arguments");
3709         QVERIFY(ret.isNumber());
3710         QCOMPARE(ret.toInt32(), 10);
3711     }
3712     QVERIFY(eng.evaluate("delete arguments").toBoolean());
3713     QVERIFY(!eng.globalObject().property("arguments").isValid());
3714 }
3715 
argumentsProperty_JS()3716 void tst_QScriptEngine::argumentsProperty_JS()
3717 {
3718     {
3719         QScriptEngine eng;
3720         eng.evaluate("o = { arguments: 123 }");
3721         QScriptValue ret = eng.evaluate("with (o) { arguments; }");
3722         QVERIFY(ret.isNumber());
3723         QCOMPARE(ret.toInt32(), 123);
3724     }
3725     {
3726         QScriptEngine eng;
3727         QVERIFY(!eng.globalObject().property("arguments").isValid());
3728         // This is testing ECMA-262 compliance. In function calls, "arguments"
3729         // appears like a local variable, and it can be replaced.
3730         QScriptValue ret = eng.evaluate("(function() { arguments = 456; return arguments; })()");
3731         QVERIFY(ret.isNumber());
3732         QCOMPARE(ret.toInt32(), 456);
3733         QVERIFY(!eng.globalObject().property("arguments").isValid());
3734     }
3735 }
3736 
argumentsProperty_fun(QScriptContext *,QScriptEngine * eng)3737 static QScriptValue argumentsProperty_fun(QScriptContext *, QScriptEngine *eng)
3738 {
3739     // Since evaluate() is done in the current context, "arguments" should
3740     // refer to currentContext()->argumentsObject().
3741     // This is for consistency with the built-in JS eval() function.
3742     eng->evaluate("var a = arguments[0];");
3743     eng->evaluate("arguments[0] = 200;");
3744     return eng->evaluate("a + arguments[0]");
3745 }
3746 
argumentsProperty_evaluateInNativeFunction()3747 void tst_QScriptEngine::argumentsProperty_evaluateInNativeFunction()
3748 {
3749     QScriptEngine eng;
3750     QScriptValue fun = eng.newFunction(argumentsProperty_fun);
3751     eng.globalObject().setProperty("fun", eng.newFunction(argumentsProperty_fun));
3752     QScriptValue result = eng.evaluate("fun(18)");
3753     QVERIFY(result.isNumber());
3754     QCOMPARE(result.toInt32(), 200+18);
3755 }
3756 
jsNumberClass()3757 void tst_QScriptEngine::jsNumberClass()
3758 {
3759     // See ECMA-262 Section 15.7, "Number Objects".
3760 
3761     QScriptEngine eng;
3762 
3763     QScriptValue ctor = eng.globalObject().property("Number");
3764     QVERIFY(ctor.property("length").isNumber());
3765     QCOMPARE(ctor.property("length").toNumber(), qsreal(1));
3766     QScriptValue proto = ctor.property("prototype");
3767     QVERIFY(proto.isObject());
3768     {
3769         QScriptValue::PropertyFlags flags = QScriptValue::SkipInEnumeration
3770                                             | QScriptValue::Undeletable
3771                                             | QScriptValue::ReadOnly;
3772         QCOMPARE(ctor.propertyFlags("prototype"), flags);
3773         QVERIFY(ctor.property("MAX_VALUE").isNumber());
3774         QCOMPARE(ctor.propertyFlags("MAX_VALUE"), flags);
3775         QVERIFY(ctor.property("MIN_VALUE").isNumber());
3776         QCOMPARE(ctor.propertyFlags("MIN_VALUE"), flags);
3777         QVERIFY(ctor.property("NaN").isNumber());
3778         QCOMPARE(ctor.propertyFlags("NaN"), flags);
3779         QVERIFY(ctor.property("NEGATIVE_INFINITY").isNumber());
3780         QCOMPARE(ctor.propertyFlags("NEGATIVE_INFINITY"), flags);
3781         QVERIFY(ctor.property("POSITIVE_INFINITY").isNumber());
3782         QCOMPARE(ctor.propertyFlags("POSITIVE_INFINITY"), flags);
3783     }
3784     QVERIFY(proto.instanceOf(eng.globalObject().property("Object")));
3785     QCOMPARE(proto.toNumber(), qsreal(0));
3786     QVERIFY(proto.property("constructor").strictlyEquals(ctor));
3787 
3788     {
3789         QScriptValue ret = eng.evaluate("Number()");
3790         QVERIFY(ret.isNumber());
3791         QCOMPARE(ret.toNumber(), qsreal(0));
3792     }
3793     {
3794         QScriptValue ret = eng.evaluate("Number(123)");
3795         QVERIFY(ret.isNumber());
3796         QCOMPARE(ret.toNumber(), qsreal(123));
3797     }
3798     {
3799         QScriptValue ret = eng.evaluate("Number('456')");
3800         QVERIFY(ret.isNumber());
3801         QCOMPARE(ret.toNumber(), qsreal(456));
3802     }
3803     {
3804         QScriptValue ret = eng.evaluate("new Number()");
3805         QVERIFY(!ret.isNumber());
3806         QVERIFY(ret.isObject());
3807         QCOMPARE(ret.toNumber(), qsreal(0));
3808     }
3809     {
3810         QScriptValue ret = eng.evaluate("new Number(123)");
3811         QVERIFY(!ret.isNumber());
3812         QVERIFY(ret.isObject());
3813         QCOMPARE(ret.toNumber(), qsreal(123));
3814     }
3815     {
3816         QScriptValue ret = eng.evaluate("new Number('456')");
3817         QVERIFY(!ret.isNumber());
3818         QVERIFY(ret.isObject());
3819         QCOMPARE(ret.toNumber(), qsreal(456));
3820     }
3821 
3822     QVERIFY(proto.property("toString").isFunction());
3823     {
3824         QScriptValue ret = eng.evaluate("new Number(123).toString()");
3825         QVERIFY(ret.isString());
3826         QCOMPARE(ret.toString(), QString::fromLatin1("123"));
3827     }
3828     {
3829         QScriptValue ret = eng.evaluate("new Number(123).toString(8)");
3830         QVERIFY(ret.isString());
3831         QCOMPARE(ret.toString(), QString::fromLatin1("173"));
3832     }
3833     {
3834         QScriptValue ret = eng.evaluate("new Number(123).toString(16)");
3835         QVERIFY(ret.isString());
3836         QCOMPARE(ret.toString(), QString::fromLatin1("7b"));
3837     }
3838     QVERIFY(proto.property("toLocaleString").isFunction());
3839     {
3840         QScriptValue ret = eng.evaluate("new Number(123).toLocaleString()");
3841         QVERIFY(ret.isString());
3842         QCOMPARE(ret.toString(), QString::fromLatin1("123"));
3843     }
3844     QVERIFY(proto.property("valueOf").isFunction());
3845     {
3846         QScriptValue ret = eng.evaluate("new Number(123).valueOf()");
3847         QVERIFY(ret.isNumber());
3848         QCOMPARE(ret.toNumber(), qsreal(123));
3849     }
3850     QVERIFY(proto.property("toExponential").isFunction());
3851     {
3852         QScriptValue ret = eng.evaluate("new Number(123).toExponential()");
3853         QVERIFY(ret.isString());
3854         QCOMPARE(ret.toString(), QString::fromLatin1("1.23e+2"));
3855     }
3856     QVERIFY(proto.property("toFixed").isFunction());
3857     {
3858         QScriptValue ret = eng.evaluate("new Number(123).toFixed()");
3859         QVERIFY(ret.isString());
3860         QCOMPARE(ret.toString(), QString::fromLatin1("123"));
3861     }
3862     QVERIFY(proto.property("toPrecision").isFunction());
3863     {
3864         QScriptValue ret = eng.evaluate("new Number(123).toPrecision()");
3865         QVERIFY(ret.isString());
3866         QCOMPARE(ret.toString(), QString::fromLatin1("123"));
3867     }
3868 }
3869 
jsForInStatement_simple()3870 void tst_QScriptEngine::jsForInStatement_simple()
3871 {
3872     QScriptEngine eng;
3873     {
3874         QScriptValue ret = eng.evaluate("o = { }; r = []; for (var p in o) r[r.length] = p; r");
3875         QStringList lst = qscriptvalue_cast<QStringList>(ret);
3876         QVERIFY(lst.isEmpty());
3877     }
3878     {
3879         QScriptValue ret = eng.evaluate("o = { p: 123 }; r = [];"
3880                                         "for (var p in o) r[r.length] = p; r");
3881         QStringList lst = qscriptvalue_cast<QStringList>(ret);
3882         QCOMPARE(lst.size(), 1);
3883         QCOMPARE(lst.at(0), QString::fromLatin1("p"));
3884     }
3885     {
3886         QScriptValue ret = eng.evaluate("o = { p: 123, q: 456 }; r = [];"
3887                                         "for (var p in o) r[r.length] = p; r");
3888         QStringList lst = qscriptvalue_cast<QStringList>(ret);
3889         QCOMPARE(lst.size(), 2);
3890         QCOMPARE(lst.at(0), QString::fromLatin1("p"));
3891         QCOMPARE(lst.at(1), QString::fromLatin1("q"));
3892     }
3893 }
3894 
jsForInStatement_prototypeProperties()3895 void tst_QScriptEngine::jsForInStatement_prototypeProperties()
3896 {
3897     QScriptEngine eng;
3898     {
3899         QScriptValue ret = eng.evaluate("o = { }; o.__proto__ = { p: 123 }; r = [];"
3900                                         "for (var p in o) r[r.length] = p; r");
3901         QStringList lst = qscriptvalue_cast<QStringList>(ret);
3902         QCOMPARE(lst.size(), 1);
3903         QCOMPARE(lst.at(0), QString::fromLatin1("p"));
3904     }
3905     {
3906         QScriptValue ret = eng.evaluate("o = { p: 123 }; o.__proto__ = { q: 456 }; r = [];"
3907                                         "for (var p in o) r[r.length] = p; r");
3908         QStringList lst = qscriptvalue_cast<QStringList>(ret);
3909         QCOMPARE(lst.size(), 2);
3910         QCOMPARE(lst.at(0), QString::fromLatin1("p"));
3911         QCOMPARE(lst.at(1), QString::fromLatin1("q"));
3912     }
3913     {
3914         // shadowed property
3915         QScriptValue ret = eng.evaluate("o = { p: 123 }; o.__proto__ = { p: 456 }; r = [];"
3916                                         "for (var p in o) r[r.length] = p; r");
3917         QStringList lst = qscriptvalue_cast<QStringList>(ret);
3918         QCOMPARE(lst.size(), 1);
3919         QCOMPARE(lst.at(0), QString::fromLatin1("p"));
3920     }
3921 
3922 }
3923 
jsForInStatement_mutateWhileIterating()3924 void tst_QScriptEngine::jsForInStatement_mutateWhileIterating()
3925 {
3926     QScriptEngine eng;
3927     // deleting property during enumeration
3928     {
3929         QScriptValue ret = eng.evaluate("o = { p: 123 }; r = [];"
3930                                         "for (var p in o) { r[r.length] = p; delete r[p]; } r");
3931         QStringList lst = qscriptvalue_cast<QStringList>(ret);
3932         QCOMPARE(lst.size(), 1);
3933         QCOMPARE(lst.at(0), QString::fromLatin1("p"));
3934     }
3935     {
3936         QScriptValue ret = eng.evaluate("o = { p: 123, q: 456 }; r = [];"
3937                                         "for (var p in o) { r[r.length] = p; delete o.q; } r");
3938         QStringList lst = qscriptvalue_cast<QStringList>(ret);
3939         QCOMPARE(lst.size(), 1);
3940         QCOMPARE(lst.at(0), QString::fromLatin1("p"));
3941     }
3942 
3943     // adding property during enumeration
3944     {
3945         QScriptValue ret = eng.evaluate("o = { p: 123 }; r = [];"
3946                                         "for (var p in o) { r[r.length] = p; o.q = 456; } r");
3947         QStringList lst = qscriptvalue_cast<QStringList>(ret);
3948         QCOMPARE(lst.size(), 1);
3949         QCOMPARE(lst.at(0), QString::fromLatin1("p"));
3950     }
3951 
3952 }
3953 
jsForInStatement_arrays()3954 void tst_QScriptEngine::jsForInStatement_arrays()
3955 {
3956     QScriptEngine eng;
3957     {
3958         QScriptValue ret = eng.evaluate("a = [123, 456]; r = [];"
3959                                         "for (var p in a) r[r.length] = p; r");
3960         QStringList lst = qscriptvalue_cast<QStringList>(ret);
3961         QCOMPARE(lst.size(), 2);
3962         QCOMPARE(lst.at(0), QString::fromLatin1("0"));
3963         QCOMPARE(lst.at(1), QString::fromLatin1("1"));
3964     }
3965     {
3966         QScriptValue ret = eng.evaluate("a = [123, 456]; a.foo = 'bar'; r = [];"
3967                                         "for (var p in a) r[r.length] = p; r");
3968         QStringList lst = qscriptvalue_cast<QStringList>(ret);
3969         QCOMPARE(lst.size(), 3);
3970         QCOMPARE(lst.at(0), QString::fromLatin1("0"));
3971         QCOMPARE(lst.at(1), QString::fromLatin1("1"));
3972         QCOMPARE(lst.at(2), QString::fromLatin1("foo"));
3973     }
3974     {
3975         QScriptValue ret = eng.evaluate("a = [123, 456]; a.foo = 'bar';"
3976                                         "b = [111, 222, 333]; b.bar = 'baz';"
3977                                         "a.__proto__ = b; r = [];"
3978                                         "for (var p in a) r[r.length] = p; r");
3979         QStringList lst = qscriptvalue_cast<QStringList>(ret);
3980         QCOMPARE(lst.size(), 5);
3981         QCOMPARE(lst.at(0), QString::fromLatin1("0"));
3982         QCOMPARE(lst.at(1), QString::fromLatin1("1"));
3983         QCOMPARE(lst.at(2), QString::fromLatin1("foo"));
3984         QCOMPARE(lst.at(3), QString::fromLatin1("2"));
3985         QCOMPARE(lst.at(4), QString::fromLatin1("bar"));
3986     }
3987 }
3988 
jsForInStatement_nullAndUndefined()3989 void tst_QScriptEngine::jsForInStatement_nullAndUndefined()
3990 {
3991     QScriptEngine eng;
3992     {
3993         QScriptValue ret = eng.evaluate("r = true; for (var p in undefined) r = false; r");
3994         QVERIFY(ret.isBool());
3995         QVERIFY(ret.toBool());
3996     }
3997     {
3998         QScriptValue ret = eng.evaluate("r = true; for (var p in null) r = false; r");
3999         QVERIFY(ret.isBool());
4000         QVERIFY(ret.toBool());
4001     }
4002 }
4003 
jsFunctionDeclarationAsStatement()4004 void tst_QScriptEngine::jsFunctionDeclarationAsStatement()
4005 {
4006     // ECMA-262 does not allow function declarations to be used as statements,
4007     // but several popular implementations (including JSC) do. See the NOTE
4008     // at the beginning of chapter 12 in ECMA-262 5th edition, where it's
4009     // recommended that implementations either disallow this usage or issue
4010     // a warning.
4011     // Since we had a bug report long ago about QtScript not supporting this
4012     // "feature" (and thus deviating from other implementations), we still
4013     // check this behavior.
4014 
4015     QScriptEngine eng;
4016     QVERIFY(!eng.globalObject().property("bar").isValid());
4017     eng.evaluate("function foo(arg) {\n"
4018                  "  if (arg == 'bar')\n"
4019                  "    function bar() { return 'bar'; }\n"
4020                  "  else\n"
4021                  "    function baz() { return 'baz'; }\n"
4022                  "  return (arg == 'bar') ? bar : baz;\n"
4023                  "}");
4024     QVERIFY(!eng.globalObject().property("bar").isValid());
4025     QVERIFY(!eng.globalObject().property("baz").isValid());
4026     QVERIFY(eng.evaluate("foo").isFunction());
4027     {
4028         QScriptValue ret = eng.evaluate("foo('bar')");
4029         QVERIFY(ret.isFunction());
4030         QScriptValue ret2 = ret.call(QScriptValue());
4031         QCOMPARE(ret2.toString(), QString::fromLatin1("bar"));
4032         QVERIFY(!eng.globalObject().property("bar").isValid());
4033         QVERIFY(!eng.globalObject().property("baz").isValid());
4034     }
4035     {
4036         QScriptValue ret = eng.evaluate("foo('baz')");
4037         QVERIFY(ret.isFunction());
4038         QScriptValue ret2 = ret.call(QScriptValue());
4039         QCOMPARE(ret2.toString(), QString::fromLatin1("baz"));
4040         QVERIFY(!eng.globalObject().property("bar").isValid());
4041         QVERIFY(!eng.globalObject().property("baz").isValid());
4042     }
4043 }
4044 
stringObjects()4045 void tst_QScriptEngine::stringObjects()
4046 {
4047     // See ECMA-262 Section 15.5, "String Objects".
4048 
4049     QScriptEngine eng;
4050     QString str("ciao");
4051     // in C++
4052     {
4053         QScriptValue obj = QScriptValue(&eng, str).toObject();
4054         QCOMPARE(obj.property("length").toInt32(), str.length());
4055         QCOMPARE(obj.propertyFlags("length"), QScriptValue::PropertyFlags(QScriptValue::Undeletable | QScriptValue::SkipInEnumeration | QScriptValue::ReadOnly));
4056         for (int i = 0; i < str.length(); ++i) {
4057             QString pname = QString::number(i);
4058             QVERIFY(obj.property(pname).isString());
4059             QCOMPARE(obj.property(pname).toString(), QString(str.at(i)));
4060             QCOMPARE(obj.propertyFlags(pname), QScriptValue::PropertyFlags(QScriptValue::Undeletable | QScriptValue::ReadOnly));
4061             obj.setProperty(pname, QScriptValue());
4062             obj.setProperty(pname, QScriptValue(&eng, 123));
4063             QVERIFY(obj.property(pname).isString());
4064             QCOMPARE(obj.property(pname).toString(), QString(str.at(i)));
4065         }
4066         QVERIFY(!obj.property("-1").isValid());
4067         QVERIFY(!obj.property(QString::number(str.length())).isValid());
4068 
4069         QScriptValue val(&eng, 123);
4070         obj.setProperty("-1", val);
4071         QVERIFY(obj.property("-1").strictlyEquals(val));
4072         obj.setProperty("100", val);
4073         QVERIFY(obj.property("100").strictlyEquals(val));
4074     }
4075 
4076     // in script
4077     {
4078         QScriptValue ret = eng.evaluate("s = new String('ciao'); r = []; for (var p in s) r.push(p); r");
4079         QVERIFY(ret.isArray());
4080         QStringList lst = qscriptvalue_cast<QStringList>(ret);
4081         QCOMPARE(lst.size(), str.length());
4082         for (int i = 0; i < str.length(); ++i)
4083             QCOMPARE(lst.at(i), QString::number(i));
4084 
4085         QScriptValue ret2 = eng.evaluate("s[0] = 123; s[0]");
4086         QVERIFY(ret2.isString());
4087         QCOMPARE(ret2.toString().length(), 1);
4088         QCOMPARE(ret2.toString().at(0), str.at(0));
4089 
4090         QScriptValue ret3 = eng.evaluate("s[-1] = 123; s[-1]");
4091         QVERIFY(ret3.isNumber());
4092         QCOMPARE(ret3.toInt32(), 123);
4093 
4094         QScriptValue ret4 = eng.evaluate("s[s.length] = 456; s[s.length]");
4095         QVERIFY(ret4.isNumber());
4096         QCOMPARE(ret4.toInt32(), 456);
4097 
4098         QScriptValue ret5 = eng.evaluate("delete s[0]");
4099         QVERIFY(ret5.isBoolean());
4100         QVERIFY(!ret5.toBoolean());
4101 
4102         QScriptValue ret6 = eng.evaluate("delete s[-1]");
4103         QVERIFY(ret6.isBoolean());
4104         QVERIFY(ret6.toBoolean());
4105 
4106         QScriptValue ret7 = eng.evaluate("delete s[s.length]");
4107         QVERIFY(ret7.isBoolean());
4108         QVERIFY(ret7.toBoolean());
4109     }
4110 }
4111 
jsStringPrototypeReplaceBugs()4112 void tst_QScriptEngine::jsStringPrototypeReplaceBugs()
4113 {
4114     QScriptEngine eng;
4115     // task 212440
4116     {
4117         QScriptValue ret = eng.evaluate("replace_args = []; \"a a a\".replace(/(a)/g, function() { replace_args.push(arguments); }); replace_args");
4118         QVERIFY(ret.isArray());
4119         int len = ret.property("length").toInt32();
4120         QCOMPARE(len, 3);
4121         for (int i = 0; i < len; ++i) {
4122             QScriptValue args = ret.property(i);
4123             QCOMPARE(args.property("length").toInt32(), 4);
4124             QCOMPARE(args.property(0).toString(), QString::fromLatin1("a")); // matched string
4125             QCOMPARE(args.property(1).toString(), QString::fromLatin1("a")); // capture
4126             QVERIFY(args.property(2).isNumber());
4127             QCOMPARE(args.property(2).toInt32(), i*2); // index of match
4128             QCOMPARE(args.property(3).toString(), QString::fromLatin1("a a a"));
4129         }
4130     }
4131     // task 212501
4132     {
4133         QScriptValue ret = eng.evaluate("\"foo\".replace(/a/g, function() {})");
4134         QVERIFY(ret.isString());
4135         QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
4136     }
4137 }
4138 
getterSetterThisObject_global()4139 void tst_QScriptEngine::getterSetterThisObject_global()
4140 {
4141     {
4142         QScriptEngine eng;
4143         // read
4144         eng.evaluate("__defineGetter__('x', function() { return this; });");
4145         {
4146             QScriptValue ret = eng.evaluate("x");
4147             QVERIFY(ret.equals(eng.globalObject()));
4148         }
4149         {
4150             QScriptValue ret = eng.evaluate("(function() { return x; })()");
4151             QVERIFY(ret.equals(eng.globalObject()));
4152         }
4153         {
4154             QScriptValue ret = eng.evaluate("with (this) x");
4155             QVERIFY(ret.equals(eng.globalObject()));
4156         }
4157         {
4158             QScriptValue ret = eng.evaluate("with ({}) x");
4159             QVERIFY(ret.equals(eng.globalObject()));
4160         }
4161         {
4162             QScriptValue ret = eng.evaluate("(function() { with ({}) return x; })()");
4163             QVERIFY(ret.equals(eng.globalObject()));
4164         }
4165         // write
4166         eng.evaluate("__defineSetter__('x', function() { return this; });");
4167         {
4168             QScriptValue ret = eng.evaluate("x = 'foo'");
4169             // SpiderMonkey says setter return value, JSC says RHS.
4170             QVERIFY(ret.isString());
4171             QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
4172         }
4173         {
4174             QScriptValue ret = eng.evaluate("(function() { return x = 'foo'; })()");
4175             // SpiderMonkey says setter return value, JSC says RHS.
4176             QVERIFY(ret.isString());
4177             QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
4178         }
4179         {
4180             QScriptValue ret = eng.evaluate("with (this) x = 'foo'");
4181             // SpiderMonkey says setter return value, JSC says RHS.
4182             QVERIFY(ret.isString());
4183             QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
4184         }
4185         {
4186             QScriptValue ret = eng.evaluate("with ({}) x = 'foo'");
4187             // SpiderMonkey says setter return value, JSC says RHS.
4188             QVERIFY(ret.isString());
4189             QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
4190         }
4191         {
4192             QScriptValue ret = eng.evaluate("(function() { with ({}) return x = 'foo'; })()");
4193             // SpiderMonkey says setter return value, JSC says RHS.
4194             QVERIFY(ret.isString());
4195             QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
4196         }
4197     }
4198 }
4199 
getterSetterThisObject_plain()4200 void tst_QScriptEngine::getterSetterThisObject_plain()
4201 {
4202     {
4203         QScriptEngine eng;
4204         eng.evaluate("o = {}");
4205         // read
4206         eng.evaluate("o.__defineGetter__('x', function() { return this; })");
4207         QVERIFY(eng.evaluate("o.x === o").toBoolean());
4208         QVERIFY(eng.evaluate("with (o) x").equals(eng.evaluate("o")));
4209         QVERIFY(eng.evaluate("(function() { with (o) return x; })() === o").toBoolean());
4210         eng.evaluate("q = {}; with (o) with (q) x").equals(eng.evaluate("o"));
4211         // write
4212         eng.evaluate("o.__defineSetter__('x', function() { return this; });");
4213         // SpiderMonkey says setter return value, JSC says RHS.
4214         QVERIFY(eng.evaluate("(o.x = 'foo') === 'foo'").toBoolean());
4215         QVERIFY(eng.evaluate("with (o) x = 'foo'").equals("foo"));
4216         QVERIFY(eng.evaluate("with (o) with (q) x = 'foo'").equals("foo"));
4217     }
4218 }
4219 
getterSetterThisObject_prototypeChain()4220 void tst_QScriptEngine::getterSetterThisObject_prototypeChain()
4221 {
4222     {
4223         QScriptEngine eng;
4224         eng.evaluate("o = {}; p = {}; o.__proto__ = p");
4225         // read
4226         eng.evaluate("p.__defineGetter__('x', function() { return this; })");
4227         QVERIFY(eng.evaluate("o.x === o").toBoolean());
4228         QVERIFY(eng.evaluate("with (o) x").equals(eng.evaluate("o")));
4229         QVERIFY(eng.evaluate("(function() { with (o) return x; })() === o").toBoolean());
4230         eng.evaluate("q = {}; with (o) with (q) x").equals(eng.evaluate("o"));
4231         eng.evaluate("with (q) with (o) x").equals(eng.evaluate("o"));
4232         // write
4233         eng.evaluate("o.__defineSetter__('x', function() { return this; });");
4234         // SpiderMonkey says setter return value, JSC says RHS.
4235         QVERIFY(eng.evaluate("(o.x = 'foo') === 'foo'").toBoolean());
4236         QVERIFY(eng.evaluate("with (o) x = 'foo'").equals("foo"));
4237         QVERIFY(eng.evaluate("with (o) with (q) x = 'foo'").equals("foo"));
4238     }
4239 }
4240 
getterSetterThisObject_activation()4241 void tst_QScriptEngine::getterSetterThisObject_activation()
4242 {
4243     {
4244         QScriptEngine eng;
4245         QScriptContext *ctx = eng.pushContext();
4246         QVERIFY(ctx != 0);
4247         QScriptValue act = ctx->activationObject();
4248         act.setProperty("act", act);
4249         // read
4250         eng.evaluate("act.__defineGetter__('x', function() { return this; })");
4251         QVERIFY(eng.evaluate("x === act").toBoolean());
4252         QEXPECT_FAIL("", "QTBUG-17605: Not possible to implement local variables as getter/setter properties", Abort);
4253         QVERIFY(!eng.hasUncaughtException());
4254         QVERIFY(eng.evaluate("with (act) x").equals("foo"));
4255         QVERIFY(eng.evaluate("(function() { with (act) return x; })() === act").toBoolean());
4256         eng.evaluate("q = {}; with (act) with (q) x").equals(eng.evaluate("act"));
4257         eng.evaluate("with (q) with (act) x").equals(eng.evaluate("act"));
4258         // write
4259         eng.evaluate("act.__defineSetter__('x', function() { return this; });");
4260         QVERIFY(eng.evaluate("(x = 'foo') === 'foo'").toBoolean());
4261         QVERIFY(eng.evaluate("with (act) x = 'foo'").equals("foo"));
4262         QVERIFY(eng.evaluate("with (act) with (q) x = 'foo'").equals("foo"));
4263         eng.popContext();
4264     }
4265 }
4266 
jsContinueInSwitch()4267 void tst_QScriptEngine::jsContinueInSwitch()
4268 {
4269     // This is testing ECMA-262 compliance, not C++ API.
4270 
4271     QScriptEngine eng;
4272     // switch - continue
4273     {
4274         QScriptValue ret = eng.evaluate("switch (1) { default: continue; }");
4275         QVERIFY(ret.isError());
4276     }
4277     // for - switch - case - continue
4278     {
4279         QScriptValue ret = eng.evaluate("j = 0; for (i = 0; i < 100000; ++i) {\n"
4280                                         "  switch (i) {\n"
4281                                         "    case 1: ++j; continue;\n"
4282                                         "    case 100: ++j; continue;\n"
4283                                         "    case 1000: ++j; continue;\n"
4284                                         "  }\n"
4285                                         "}; j");
4286         QVERIFY(ret.isNumber());
4287         QCOMPARE(ret.toInt32(), 3);
4288     }
4289     // for - switch - case - default - continue
4290     {
4291         QScriptValue ret = eng.evaluate("j = 0; for (i = 0; i < 100000; ++i) {\n"
4292                                         "  switch (i) {\n"
4293                                         "    case 1: ++j; continue;\n"
4294                                         "    case 100: ++j; continue;\n"
4295                                         "    case 1000: ++j; continue;\n"
4296                                         "    default: if (i < 50000) break; else continue;\n"
4297                                         "  }\n"
4298                                         "}; j");
4299         QVERIFY(ret.isNumber());
4300         QCOMPARE(ret.toInt32(), 3);
4301     }
4302     // switch - for - continue
4303     {
4304         QScriptValue ret = eng.evaluate("j = 123; switch (j) {\n"
4305                                         "  case 123:\n"
4306                                         "  for (i = 0; i < 100000; ++i) {\n"
4307                                         "    continue;\n"
4308                                         "  }\n"
4309                                         "}; i\n");
4310         QVERIFY(ret.isNumber());
4311         QCOMPARE(ret.toInt32(), 100000);
4312     }
4313     // switch - switch - continue
4314     {
4315         QScriptValue ret = eng.evaluate("i = 1; switch (i) { default: switch (i) { case 1: continue; } }");
4316         QVERIFY(ret.isError());
4317     }
4318     // for - switch - switch - continue
4319     {
4320         QScriptValue ret = eng.evaluate("j = 0; for (i = 0; i < 100000; ++i) {\n"
4321                                         "  switch (i) {\n"
4322                                         "    case 1:\n"
4323                                         "    switch (i) {\n"
4324                                         "      case 1: ++j; continue;\n"
4325                                         "    }\n"
4326                                         "  }\n"
4327                                         "}; j");
4328         QVERIFY(ret.isNumber());
4329         QCOMPARE(ret.toInt32(), 1);
4330     }
4331     // switch - for - switch - continue
4332     {
4333         QScriptValue ret = eng.evaluate("j = 123; switch (j) {\n"
4334                                         "  case 123:\n"
4335                                         "  for (i = 0; i < 100000; ++i) {\n"
4336                                         "    switch (i) {\n"
4337                                         "      case 1:\n"
4338                                         "      ++j; continue;\n"
4339                                         "    }\n"
4340                                         "  }\n"
4341                                         "}; i\n");
4342         QVERIFY(ret.isNumber());
4343         QCOMPARE(ret.toInt32(), 100000);
4344     }
4345 }
4346 
jsShadowReadOnlyPrototypeProperty()4347 void tst_QScriptEngine::jsShadowReadOnlyPrototypeProperty()
4348 {
4349     // SpiderMonkey has different behavior than JSC and V8; it disallows
4350     // creating a property on the instance if there's a property with the
4351     // same name in the prototype, and that property is read-only. We
4352     // adopted that behavior in the old (4.5) QtScript back-end, but it
4353     // just seems weird -- and non-compliant. Adopt the JSC behavior instead.
4354     QScriptEngine eng;
4355     QVERIFY(eng.evaluate("o = {}; o.__proto__ = parseInt; o.length").isNumber());
4356     QCOMPARE(eng.evaluate("o.length = 123; o.length").toInt32(), 123);
4357     QVERIFY(eng.evaluate("o.hasOwnProperty('length')").toBoolean());
4358 }
4359 
toObject()4360 void tst_QScriptEngine::toObject()
4361 {
4362     QScriptEngine eng;
4363 
4364     QVERIFY(!eng.toObject(eng.undefinedValue()).isValid());
4365 
4366     QVERIFY(!eng.toObject(eng.nullValue()).isValid());
4367 
4368     QScriptValue falskt(false);
4369     {
4370         QScriptValue tmp = eng.toObject(falskt);
4371         QVERIFY(tmp.isObject());
4372         QCOMPARE(tmp.toNumber(), falskt.toNumber());
4373     }
4374     QVERIFY(falskt.isBool());
4375 
4376     QScriptValue sant(true);
4377     {
4378         QScriptValue tmp = eng.toObject(sant);
4379         QVERIFY(tmp.isObject());
4380         QCOMPARE(tmp.toNumber(), sant.toNumber());
4381     }
4382     QVERIFY(sant.isBool());
4383 
4384     QScriptValue number(123.0);
4385     {
4386         QScriptValue tmp = eng.toObject(number);
4387         QVERIFY(tmp.isObject());
4388         QCOMPARE(tmp.toNumber(), number.toNumber());
4389     }
4390     QVERIFY(number.isNumber());
4391 
4392     QScriptValue str = QScriptValue(&eng, QString("ciao"));
4393     {
4394         QScriptValue tmp = eng.toObject(str);
4395         QVERIFY(tmp.isObject());
4396         QCOMPARE(tmp.toString(), str.toString());
4397     }
4398     QVERIFY(str.isString());
4399 
4400     QScriptValue object = eng.newObject();
4401     {
4402         QScriptValue tmp = eng.toObject(object);
4403         QVERIFY(tmp.isObject());
4404         QVERIFY(tmp.strictlyEquals(object));
4405     }
4406 
4407     QScriptValue qobject = eng.newQObject(this);
4408     QVERIFY(eng.toObject(qobject).strictlyEquals(qobject));
4409 
4410     QVERIFY(!eng.toObject(QScriptValue()).isValid());
4411 
4412     // v1 constructors
4413 
4414     QScriptValue boolValue(&eng, true);
4415     {
4416         QScriptValue ret = eng.toObject(boolValue);
4417         QVERIFY(ret.isObject());
4418         QCOMPARE(ret.toBool(), boolValue.toBool());
4419     }
4420     QVERIFY(boolValue.isBool());
4421 
4422     QScriptValue numberValue(&eng, 123.0);
4423     {
4424         QScriptValue ret = eng.toObject(numberValue);
4425         QVERIFY(ret.isObject());
4426         QCOMPARE(ret.toNumber(), numberValue.toNumber());
4427     }
4428     QVERIFY(numberValue.isNumber());
4429 
4430     QScriptValue stringValue(&eng, QString::fromLatin1("foo"));
4431     {
4432         QScriptValue ret = eng.toObject(stringValue);
4433         QVERIFY(ret.isObject());
4434         QCOMPARE(ret.toString(), stringValue.toString());
4435     }
4436     QVERIFY(stringValue.isString());
4437 }
4438 
jsReservedWords_data()4439 void tst_QScriptEngine::jsReservedWords_data()
4440 {
4441     QTest::addColumn<QString>("word");
4442     QTest::newRow("break") << QString("break");
4443     QTest::newRow("case") << QString("case");
4444     QTest::newRow("catch") << QString("catch");
4445     QTest::newRow("continue") << QString("continue");
4446     QTest::newRow("default") << QString("default");
4447     QTest::newRow("delete") << QString("delete");
4448     QTest::newRow("do") << QString("do");
4449     QTest::newRow("else") << QString("else");
4450     QTest::newRow("false") << QString("false");
4451     QTest::newRow("finally") << QString("finally");
4452     QTest::newRow("for") << QString("for");
4453     QTest::newRow("function") << QString("function");
4454     QTest::newRow("if") << QString("if");
4455     QTest::newRow("in") << QString("in");
4456     QTest::newRow("instanceof") << QString("instanceof");
4457     QTest::newRow("new") << QString("new");
4458     QTest::newRow("null") << QString("null");
4459     QTest::newRow("return") << QString("return");
4460     QTest::newRow("switch") << QString("switch");
4461     QTest::newRow("this") << QString("this");
4462     QTest::newRow("throw") << QString("throw");
4463     QTest::newRow("true") << QString("true");
4464     QTest::newRow("try") << QString("try");
4465     QTest::newRow("typeof") << QString("typeof");
4466     QTest::newRow("var") << QString("var");
4467     QTest::newRow("void") << QString("void");
4468     QTest::newRow("while") << QString("while");
4469     QTest::newRow("with") << QString("with");
4470 }
4471 
jsReservedWords()4472 void tst_QScriptEngine::jsReservedWords()
4473 {
4474     // See ECMA-262 Section 7.6.1, "Reserved Words".
4475     // We prefer that the implementation is less strict than the spec; e.g.
4476     // it's good to allow reserved words as identifiers in object literals,
4477     // and when accessing properties using dot notation.
4478 
4479     QFETCH(QString, word);
4480     {
4481         QScriptEngine eng;
4482         QScriptValue ret = eng.evaluate(word + " = 123");
4483         QVERIFY(ret.isError());
4484         QString str = ret.toString();
4485         QVERIFY(str.startsWith("SyntaxError") || str.startsWith("ReferenceError"));
4486     }
4487     {
4488         QScriptEngine eng;
4489         QScriptValue ret = eng.evaluate("var " + word + " = 123");
4490         QVERIFY(ret.isError());
4491         QVERIFY(ret.toString().startsWith("SyntaxError"));
4492     }
4493     {
4494         QScriptEngine eng;
4495         QScriptValue ret = eng.evaluate("o = {}; o." + word + " = 123");
4496         // in the old back-end and in SpiderMonkey this is allowed, but not in JSC
4497         QVERIFY(ret.isError());
4498         QVERIFY(ret.toString().startsWith("SyntaxError"));
4499     }
4500     {
4501         QScriptEngine eng;
4502         QScriptValue ret = eng.evaluate("o = { " + word + ": 123 }");
4503         // in the old back-end and in SpiderMonkey this is allowed, but not in JSC
4504         QVERIFY(ret.isError());
4505         QVERIFY(ret.toString().startsWith("SyntaxError"));
4506     }
4507     {
4508         // SpiderMonkey allows this, but we don't
4509         QScriptEngine eng;
4510         QScriptValue ret = eng.evaluate("function " + word + "() {}");
4511         QVERIFY(ret.isError());
4512         QVERIFY(ret.toString().startsWith("SyntaxError"));
4513     }
4514 }
4515 
jsFutureReservedWords_data()4516 void tst_QScriptEngine::jsFutureReservedWords_data()
4517 {
4518     QTest::addColumn<QString>("word");
4519     QTest::addColumn<bool>("allowed");
4520     QTest::newRow("abstract") << QString("abstract") << true;
4521     QTest::newRow("boolean") << QString("boolean") << true;
4522     QTest::newRow("byte") << QString("byte") << true;
4523     QTest::newRow("char") << QString("char") << true;
4524     QTest::newRow("class") << QString("class") << false;
4525     QTest::newRow("const") << QString("const") << false;
4526     QTest::newRow("debugger") << QString("debugger") << false;
4527     QTest::newRow("double") << QString("double") << true;
4528     QTest::newRow("enum") << QString("enum") << false;
4529     QTest::newRow("export") << QString("export") << false;
4530     QTest::newRow("extends") << QString("extends") << false;
4531     QTest::newRow("final") << QString("final") << true;
4532     QTest::newRow("float") << QString("float") << true;
4533     QTest::newRow("goto") << QString("goto") << true;
4534     QTest::newRow("implements") << QString("implements") << true;
4535     QTest::newRow("import") << QString("import") << false;
4536     QTest::newRow("int") << QString("int") << true;
4537     QTest::newRow("interface") << QString("interface") << true;
4538     QTest::newRow("long") << QString("long") << true;
4539     QTest::newRow("native") << QString("native") << true;
4540     QTest::newRow("package") << QString("package") << true;
4541     QTest::newRow("private") << QString("private") << true;
4542     QTest::newRow("protected") << QString("protected") << true;
4543     QTest::newRow("public") << QString("public") << true;
4544     QTest::newRow("short") << QString("short") << true;
4545     QTest::newRow("static") << QString("static") << true;
4546     QTest::newRow("super") << QString("super") << false;
4547     QTest::newRow("synchronized") << QString("synchronized") << true;
4548     QTest::newRow("throws") << QString("throws") << true;
4549     QTest::newRow("transient") << QString("transient") << true;
4550     QTest::newRow("volatile") << QString("volatile") << true;
4551 }
4552 
jsFutureReservedWords()4553 void tst_QScriptEngine::jsFutureReservedWords()
4554 {
4555     // See ECMA-262 Section 7.6.1.2, "Future Reserved Words".
4556     // In real-world implementations, most of these words are
4557     // actually allowed as normal identifiers.
4558 
4559     QFETCH(QString, word);
4560     QFETCH(bool, allowed);
4561     {
4562         QScriptEngine eng;
4563         QScriptValue ret = eng.evaluate(word + " = 123");
4564         QCOMPARE(!ret.isError(), allowed);
4565     }
4566     {
4567         QScriptEngine eng;
4568         QScriptValue ret = eng.evaluate("var " + word + " = 123");
4569         QCOMPARE(!ret.isError(), allowed);
4570     }
4571     {
4572         // this should probably be allowed (see task 162567)
4573         QScriptEngine eng;
4574         QScriptValue ret = eng.evaluate("o = {}; o." + word + " = 123");
4575         QCOMPARE(ret.isNumber(), allowed);
4576         QCOMPARE(!ret.isError(), allowed);
4577     }
4578     {
4579         // this should probably be allowed (see task 162567)
4580         QScriptEngine eng;
4581         QScriptValue ret = eng.evaluate("o = { " + word + ": 123 }");
4582         QCOMPARE(!ret.isError(), allowed);
4583     }
4584 }
4585 
jsThrowInsideWithStatement()4586 void tst_QScriptEngine::jsThrowInsideWithStatement()
4587 {
4588     // This is testing ECMA-262 compliance, not C++ API.
4589 
4590     // task 209988
4591     QScriptEngine eng;
4592     {
4593         QScriptValue ret = eng.evaluate(
4594             "try {"
4595             "  o = { bad : \"bug\" };"
4596             "  with (o) {"
4597             "    throw 123;"
4598             "  }"
4599             "} catch (e) {"
4600             "  bad;"
4601             "}");
4602         QVERIFY(ret.isError());
4603         QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError")));
4604     }
4605     {
4606         QScriptValue ret = eng.evaluate(
4607             "try {"
4608             "  o = { bad : \"bug\" };"
4609             "  with (o) {"
4610             "    throw 123;"
4611             "  }"
4612             "} finally {"
4613             "  bad;"
4614             "}");
4615         QVERIFY(ret.isError());
4616         QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError")));
4617     }
4618     {
4619         eng.clearExceptions();
4620         QScriptValue ret = eng.evaluate(
4621             "o = { bug : \"no bug\" };"
4622             "with (o) {"
4623             "  try {"
4624             "    throw 123;"
4625             "  } finally {"
4626             "    bug;"
4627             "  }"
4628             "}");
4629         QVERIFY(ret.isNumber());
4630         QCOMPARE(ret.toInt32(), 123);
4631         QVERIFY(eng.hasUncaughtException());
4632     }
4633     {
4634         eng.clearExceptions();
4635         QScriptValue ret = eng.evaluate(
4636             "o = { bug : \"no bug\" };"
4637             "with (o) {"
4638             "    throw 123;"
4639             "}");
4640         QVERIFY(ret.isNumber());
4641         QScriptValue ret2 = eng.evaluate("bug");
4642         QVERIFY(ret2.isError());
4643         QVERIFY(ret2.toString().contains(QString::fromLatin1("ReferenceError")));
4644     }
4645 }
4646 
4647 class TestAgent : public QScriptEngineAgent
4648 {
4649 public:
TestAgent(QScriptEngine * engine)4650     TestAgent(QScriptEngine *engine) : QScriptEngineAgent(engine) {}
4651 };
4652 
getSetAgent_ownership()4653 void tst_QScriptEngine::getSetAgent_ownership()
4654 {
4655     // engine deleted before agent --> agent deleted too
4656     QScriptEngine *eng = new QScriptEngine;
4657     QCOMPARE(eng->agent(), (QScriptEngineAgent*)0);
4658     TestAgent *agent = new TestAgent(eng);
4659     eng->setAgent(agent);
4660     QCOMPARE(eng->agent(), (QScriptEngineAgent*)agent);
4661     eng->setAgent(0); // the engine maintains ownership of the old agent
4662     QCOMPARE(eng->agent(), (QScriptEngineAgent*)0);
4663     delete eng;
4664 }
4665 
getSetAgent_deleteAgent()4666 void tst_QScriptEngine::getSetAgent_deleteAgent()
4667 {
4668     // agent deleted before engine --> engine's agent should become 0
4669     QScriptEngine *eng = new QScriptEngine;
4670     TestAgent *agent = new TestAgent(eng);
4671     eng->setAgent(agent);
4672     QCOMPARE(eng->agent(), (QScriptEngineAgent*)agent);
4673     delete agent;
4674     QCOMPARE(eng->agent(), (QScriptEngineAgent*)0);
4675     eng->evaluate("(function(){ return 123; })()");
4676     delete eng;
4677 }
4678 
getSetAgent_differentEngine()4679 void tst_QScriptEngine::getSetAgent_differentEngine()
4680 {
4681     QScriptEngine eng;
4682     QScriptEngine eng2;
4683     TestAgent *agent = new TestAgent(&eng);
4684     QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::setAgent(): cannot set agent belonging to different engine");
4685     eng2.setAgent(agent);
4686     QCOMPARE(eng2.agent(), (QScriptEngineAgent*)0);
4687 }
4688 
reentrancy_stringHandles()4689 void tst_QScriptEngine::reentrancy_stringHandles()
4690 {
4691     QScriptEngine eng1;
4692     QScriptEngine eng2;
4693     QScriptString s1 = eng1.toStringHandle("foo");
4694     QScriptString s2 = eng2.toStringHandle("foo");
4695     QVERIFY(s1 != s2);
4696 }
4697 
reentrancy_processEventsInterval()4698 void tst_QScriptEngine::reentrancy_processEventsInterval()
4699 {
4700     QScriptEngine eng1;
4701     QScriptEngine eng2;
4702     eng1.setProcessEventsInterval(123);
4703     QCOMPARE(eng2.processEventsInterval(), -1);
4704     eng2.setProcessEventsInterval(456);
4705     QCOMPARE(eng1.processEventsInterval(), 123);
4706 }
4707 
reentrancy_typeConversion()4708 void tst_QScriptEngine::reentrancy_typeConversion()
4709 {
4710     QScriptEngine eng1;
4711     QScriptEngine eng2;
4712     qScriptRegisterMetaType<Foo>(&eng1, fooToScriptValue, fooFromScriptValue);
4713     Foo foo;
4714     foo.x = 12;
4715     foo.y = 34;
4716     {
4717         QScriptValue fooVal = qScriptValueFromValue(&eng1, foo);
4718         QVERIFY(fooVal.isObject());
4719         QVERIFY(!fooVal.isVariant());
4720         QCOMPARE(fooVal.property("x").toInt32(), 12);
4721         QCOMPARE(fooVal.property("y").toInt32(), 34);
4722         fooVal.setProperty("x", 56);
4723         fooVal.setProperty("y", 78);
4724 
4725         Foo foo2 = qScriptValueToValue<Foo>(fooVal);
4726         QCOMPARE(foo2.x, 56);
4727         QCOMPARE(foo2.y, 78);
4728     }
4729     {
4730         QScriptValue fooVal = qScriptValueFromValue(&eng2, foo);
4731         QVERIFY(fooVal.isVariant());
4732 
4733         Foo foo2 = qScriptValueToValue<Foo>(fooVal);
4734         QCOMPARE(foo2.x, 12);
4735         QCOMPARE(foo2.y, 34);
4736     }
4737     QVERIFY(!eng1.defaultPrototype(qMetaTypeId<Foo>()).isValid());
4738     QVERIFY(!eng2.defaultPrototype(qMetaTypeId<Foo>()).isValid());
4739     QScriptValue proto1 = eng1.newObject();
4740     eng1.setDefaultPrototype(qMetaTypeId<Foo>(), proto1);
4741     QVERIFY(!eng2.defaultPrototype(qMetaTypeId<Foo>()).isValid());
4742     QScriptValue proto2 = eng2.newObject();
4743     eng2.setDefaultPrototype(qMetaTypeId<Foo>(), proto2);
4744     QVERIFY(eng2.defaultPrototype(qMetaTypeId<Foo>()).isValid());
4745     QVERIFY(eng1.defaultPrototype(qMetaTypeId<Foo>()).strictlyEquals(proto1));
4746 }
4747 
reentrancy_globalObjectProperties()4748 void tst_QScriptEngine::reentrancy_globalObjectProperties()
4749 {
4750     QScriptEngine eng1;
4751     QScriptEngine eng2;
4752     QVERIFY(!eng2.globalObject().property("a").isValid());
4753     eng1.evaluate("a = 10");
4754     QVERIFY(eng1.globalObject().property("a").isNumber());
4755     QVERIFY(!eng2.globalObject().property("a").isValid());
4756     eng2.evaluate("a = 20");
4757     QVERIFY(eng2.globalObject().property("a").isNumber());
4758     QCOMPARE(eng1.globalObject().property("a").toInt32(), 10);
4759 }
4760 
reentrancy_Array()4761 void tst_QScriptEngine::reentrancy_Array()
4762 {
4763     // weird bug with JSC backend
4764     {
4765         QScriptEngine eng;
4766         QCOMPARE(eng.evaluate("Array()").toString(), QString());
4767         eng.evaluate("Array.prototype.toString");
4768         QCOMPARE(eng.evaluate("Array()").toString(), QString());
4769     }
4770     {
4771         QScriptEngine eng;
4772         QCOMPARE(eng.evaluate("Array()").toString(), QString());
4773     }
4774 }
4775 
reentrancy_objectCreation()4776 void tst_QScriptEngine::reentrancy_objectCreation()
4777 {
4778     QScriptEngine eng1;
4779     QScriptEngine eng2;
4780     {
4781         QScriptValue d1 = eng1.newDate(0);
4782         QScriptValue d2 = eng2.newDate(0);
4783         QCOMPARE(d1.toDateTime(), d2.toDateTime());
4784         QCOMPARE(d2.toDateTime(), d1.toDateTime());
4785     }
4786     {
4787         QScriptValue r1 = eng1.newRegExp("foo", "gim");
4788         QScriptValue r2 = eng2.newRegExp("foo", "gim");
4789         QCOMPARE(r1.toRegExp(), r2.toRegExp());
4790         QCOMPARE(r2.toRegExp(), r1.toRegExp());
4791     }
4792     {
4793         QScriptValue o1 = eng1.newQObject(this);
4794         QScriptValue o2 = eng2.newQObject(this);
4795         QCOMPARE(o1.toQObject(), o2.toQObject());
4796         QCOMPARE(o2.toQObject(), o1.toQObject());
4797     }
4798     {
4799         QScriptValue mo1 = eng1.newQMetaObject(&staticMetaObject);
4800         QScriptValue mo2 = eng2.newQMetaObject(&staticMetaObject);
4801         QCOMPARE(mo1.toQMetaObject(), mo2.toQMetaObject());
4802         QCOMPARE(mo2.toQMetaObject(), mo1.toQMetaObject());
4803     }
4804 }
4805 
jsIncDecNonObjectProperty()4806 void tst_QScriptEngine::jsIncDecNonObjectProperty()
4807 {
4808     // This is testing ECMA-262 compliance, not C++ API.
4809 
4810     QScriptEngine eng;
4811     {
4812         QScriptValue ret = eng.evaluate("var a; a.n++");
4813         QVERIFY(ret.isError());
4814         QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
4815     }
4816     {
4817         QScriptValue ret = eng.evaluate("var a; a.n--");
4818         QVERIFY(ret.isError());
4819         QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
4820     }
4821     {
4822         QScriptValue ret = eng.evaluate("var a = null; a.n++");
4823         QVERIFY(ret.isError());
4824         QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
4825     }
4826     {
4827         QScriptValue ret = eng.evaluate("var a = null; a.n--");
4828         QVERIFY(ret.isError());
4829         QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
4830     }
4831     {
4832         QScriptValue ret = eng.evaluate("var a; ++a.n");
4833         QVERIFY(ret.isError());
4834         QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
4835     }
4836     {
4837         QScriptValue ret = eng.evaluate("var a; --a.n");
4838         QVERIFY(ret.isError());
4839         QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
4840     }
4841     {
4842         QScriptValue ret = eng.evaluate("var a; a.n += 1");
4843         QVERIFY(ret.isError());
4844         QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
4845     }
4846     {
4847         QScriptValue ret = eng.evaluate("var a; a.n -= 1");
4848         QVERIFY(ret.isError());
4849         QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
4850     }
4851     {
4852         QScriptValue ret = eng.evaluate("var a = 'ciao'; a.length++");
4853         QVERIFY(ret.isNumber());
4854         QCOMPARE(ret.toInt32(), 4);
4855     }
4856     {
4857         QScriptValue ret = eng.evaluate("var a = 'ciao'; a.length--");
4858         QVERIFY(ret.isNumber());
4859         QCOMPARE(ret.toInt32(), 4);
4860     }
4861     {
4862         QScriptValue ret = eng.evaluate("var a = 'ciao'; ++a.length");
4863         QVERIFY(ret.isNumber());
4864         QCOMPARE(ret.toInt32(), 5);
4865     }
4866     {
4867         QScriptValue ret = eng.evaluate("var a = 'ciao'; --a.length");
4868         QVERIFY(ret.isNumber());
4869         QCOMPARE(ret.toInt32(), 3);
4870     }
4871 }
4872 
installTranslatorFunctions_data()4873 void tst_QScriptEngine::installTranslatorFunctions_data()
4874 {
4875     QTest::addColumn<bool>("useCustomGlobalObject");
4876 
4877     QTest::newRow("Default global object") << false;
4878     QTest::newRow("Custom global object") << true;
4879 }
4880 
installTranslatorFunctions()4881 void tst_QScriptEngine::installTranslatorFunctions()
4882 {
4883     QFETCH(bool, useCustomGlobalObject);
4884 
4885     QScriptEngine eng;
4886     QScriptValue globalOrig = eng.globalObject();
4887     QScriptValue global;
4888     if (useCustomGlobalObject) {
4889         global = eng.newObject();
4890         eng.setGlobalObject(global);
4891     } else {
4892         global = globalOrig;
4893     }
4894     QVERIFY(!global.property("qsTranslate").isValid());
4895     QVERIFY(!global.property("QT_TRANSLATE_NOOP").isValid());
4896     QVERIFY(!global.property("qsTr").isValid());
4897     QVERIFY(!global.property("QT_TR_NOOP").isValid());
4898     QVERIFY(!global.property("qsTrId").isValid());
4899     QVERIFY(!global.property("QT_TRID_NOOP").isValid());
4900     QVERIFY(!globalOrig.property("String").property("prototype").property("arg").isValid());
4901 
4902     eng.installTranslatorFunctions();
4903     QVERIFY(global.property("qsTranslate").isFunction());
4904     QVERIFY(global.property("QT_TRANSLATE_NOOP").isFunction());
4905     QVERIFY(global.property("qsTr").isFunction());
4906     QVERIFY(global.property("QT_TR_NOOP").isFunction());
4907     QVERIFY(global.property("qsTrId").isFunction());
4908     QVERIFY(global.property("QT_TRID_NOOP").isFunction());
4909     QVERIFY(globalOrig.property("String").property("prototype").property("arg").isFunction());
4910 
4911     if (useCustomGlobalObject) {
4912         QVERIFY(!globalOrig.property("qsTranslate").isValid());
4913         QVERIFY(!globalOrig.property("QT_TRANSLATE_NOOP").isValid());
4914         QVERIFY(!globalOrig.property("qsTr").isValid());
4915         QVERIFY(!globalOrig.property("QT_TR_NOOP").isValid());
4916         QVERIFY(!globalOrig.property("qsTrId").isValid());
4917         QVERIFY(!globalOrig.property("QT_TRID_NOOP").isValid());
4918     }
4919 
4920     {
4921         QScriptValue ret = eng.evaluate("qsTr('foo')");
4922         QVERIFY(ret.isString());
4923         QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
4924     }
4925     {
4926         QScriptValue ret = eng.evaluate("qsTranslate('foo', 'bar')");
4927         QVERIFY(ret.isString());
4928         QCOMPARE(ret.toString(), QString::fromLatin1("bar"));
4929     }
4930     {
4931         QScriptValue ret = eng.evaluate("QT_TR_NOOP('foo')");
4932         QVERIFY(ret.isString());
4933         QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
4934     }
4935     {
4936         QScriptValue ret = eng.evaluate("QT_TRANSLATE_NOOP('foo', 'bar')");
4937         QVERIFY(ret.isString());
4938         QCOMPARE(ret.toString(), QString::fromLatin1("bar"));
4939     }
4940     {
4941         QScriptValue ret = eng.evaluate("'foo%0'.arg('bar')");
4942         QVERIFY(ret.isString());
4943         QCOMPARE(ret.toString(), QString::fromLatin1("foobar"));
4944     }
4945     {
4946         QScriptValue ret = eng.evaluate("'foo%0'.arg(123)");
4947         QVERIFY(ret.isString());
4948         QCOMPARE(ret.toString(), QString::fromLatin1("foo123"));
4949     }
4950     {
4951         // Maybe this should throw an error?
4952         QScriptValue ret = eng.evaluate("'foo%0'.arg()");
4953         QVERIFY(ret.isString());
4954         QCOMPARE(ret.toString(), QString());
4955     }
4956 
4957     {
4958         QScriptValue ret = eng.evaluate("qsTrId('foo')");
4959         QVERIFY(ret.isString());
4960         QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
4961     }
4962     {
4963         QScriptValue ret = eng.evaluate("QT_TRID_NOOP('foo')");
4964         QVERIFY(ret.isString());
4965         QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
4966     }
4967     QVERIFY(eng.evaluate("QT_TRID_NOOP()").isUndefined());
4968 }
4969 
4970 class TranslationScope
4971 {
4972 public:
TranslationScope(const QString & fileName)4973     TranslationScope(const QString &fileName)
4974     {
4975         translator.load(fileName);
4976         QCoreApplication::instance()->installTranslator(&translator);
4977     }
~TranslationScope()4978     ~TranslationScope()
4979     {
4980         QCoreApplication::instance()->removeTranslator(&translator);
4981     }
4982 
4983 private:
4984     QTranslator translator;
4985 };
4986 
translateScript_data()4987 void tst_QScriptEngine::translateScript_data()
4988 {
4989     QTest::addColumn<QString>("expression");
4990     QTest::addColumn<QString>("fileName");
4991     QTest::addColumn<QString>("expectedTranslation");
4992 
4993     QString fileName = QString::fromLatin1("translatable.js");
4994     // Top-level
4995     QTest::newRow("qsTr('One')@translatable.js")
4996             << QString::fromLatin1("qsTr('One')") << fileName << QString::fromLatin1("En");
4997     QTest::newRow("qsTr('Hello')@translatable.js")
4998             << QString::fromLatin1("qsTr('Hello')") << fileName << QString::fromLatin1("Hallo");
4999     // From function
5000     QTest::newRow("(function() { return qsTr('One'); })()@translatable.js")
5001             << QString::fromLatin1("(function() { return qsTr('One'); })()") << fileName << QString::fromLatin1("En");
5002     QTest::newRow("(function() { return qsTr('Hello'); })()@translatable.js")
5003             << QString::fromLatin1("(function() { return qsTr('Hello'); })()") << fileName << QString::fromLatin1("Hallo");
5004     // From eval
5005     QTest::newRow("eval('qsTr(\\'One\\')')@translatable.js")
5006             << QString::fromLatin1("eval('qsTr(\\'One\\')')") << fileName << QString::fromLatin1("En");
5007     QTest::newRow("eval('qsTr(\\'Hello\\')')@translatable.js")
5008             << QString::fromLatin1("eval('qsTr(\\'Hello\\')')") << fileName << QString::fromLatin1("Hallo");
5009     // Plural
5010     QTest::newRow("qsTr('%n message(s) saved', '', 1)@translatable.js")
5011             << QString::fromLatin1("qsTr('%n message(s) saved', '', 1)") << fileName << QString::fromLatin1("1 melding lagret");
5012     QTest::newRow("qsTr('%n message(s) saved', '', 3).arg@translatable.js")
5013             << QString::fromLatin1("qsTr('%n message(s) saved', '', 3)") << fileName << QString::fromLatin1("3 meldinger lagret");
5014 
5015     // Top-level
5016     QTest::newRow("qsTranslate('FooContext', 'Two')@translatable.js")
5017             << QString::fromLatin1("qsTranslate('FooContext', 'Two')") << fileName << QString::fromLatin1("To");
5018     QTest::newRow("qsTranslate('FooContext', 'Goodbye')@translatable.js")
5019             << QString::fromLatin1("qsTranslate('FooContext', 'Goodbye')") << fileName << QString::fromLatin1("Farvel");
5020     // From eval
5021     QTest::newRow("eval('qsTranslate(\\'FooContext\\', \\'Two\\')')@translatable.js")
5022             << QString::fromLatin1("eval('qsTranslate(\\'FooContext\\', \\'Two\\')')") << fileName << QString::fromLatin1("To");
5023     QTest::newRow("eval('qsTranslate(\\'FooContext\\', \\'Goodbye\\')')@translatable.js")
5024             << QString::fromLatin1("eval('qsTranslate(\\'FooContext\\', \\'Goodbye\\')')") << fileName << QString::fromLatin1("Farvel");
5025 
5026     QTest::newRow("qsTranslate('FooContext', 'Goodbye', '', 'UnicodeUTF8')@translatable.js")
5027             << QString::fromLatin1("qsTranslate('FooContext', 'Goodbye', '', 'UnicodeUTF8')") << fileName << QString::fromLatin1("Farvel");
5028     QTest::newRow("qsTranslate('FooContext', 'Goodbye', '', 'CodecForTr')@translatable.js")
5029             << QString::fromLatin1("qsTranslate('FooContext', 'Goodbye', '', 'CodecForTr')") << fileName << QString::fromLatin1("Farvel");
5030 
5031     QTest::newRow("qsTranslate('FooContext', 'Goodbye', '', 'UnicodeUTF8', 42)@translatable.js")
5032             << QString::fromLatin1("qsTranslate('FooContext', 'Goodbye', '', 'UnicodeUTF8', 42)") << fileName << QString::fromLatin1("Goodbye");
5033 
5034     QTest::newRow("qsTr('One', 'not the same one')@translatable.js")
5035             << QString::fromLatin1("qsTr('One', 'not the same one')") << fileName << QString::fromLatin1("Enda en");
5036 
5037     QTest::newRow("qsTr('One', 'not the same one', 42)@translatable.js")
5038             << QString::fromLatin1("qsTr('One', 'not the same one', 42)") << fileName << QString::fromLatin1("One");
5039 
5040     // Plural
5041     QTest::newRow("qsTranslate('FooContext', '%n fooish bar(s) found', '', 'UnicodeUTF8', 1)@translatable.js")
5042             << QString::fromLatin1("qsTranslate('FooContext', '%n fooish bar(s) found', '', 'UnicodeUTF8', 1)") << fileName << QString::fromLatin1("1 fooaktig bar funnet");
5043     QTest::newRow("qsTranslate('FooContext', '%n fooish bar(s) found', '', 'UnicodeUTF8', 2)@translatable.js")
5044             << QString::fromLatin1("qsTranslate('FooContext', '%n fooish bar(s) found', '', 'UnicodeUTF8', 2)") << fileName << QString::fromLatin1("2 fooaktige barer funnet");
5045 
5046     // Don't exist in translation
5047     QTest::newRow("qsTr('Three')@translatable.js")
5048             << QString::fromLatin1("qsTr('Three')") << fileName << QString::fromLatin1("Three");
5049     QTest::newRow("qsTranslate('FooContext', 'So long')@translatable.js")
5050             << QString::fromLatin1("qsTranslate('FooContext', 'So long')") << fileName << QString::fromLatin1("So long");
5051     QTest::newRow("qsTranslate('BarContext', 'Goodbye')@translatable.js")
5052             << QString::fromLatin1("qsTranslate('BarContext', 'Goodbye')") << fileName << QString::fromLatin1("Goodbye");
5053 
5054     // Translate strings from the second script (translatable2.js)
5055 
5056     QString fileName2 = QString::fromLatin1("translatable2.js");
5057     QTest::newRow("qsTr('Three')@translatable2.js")
5058             << QString::fromLatin1("qsTr('Three')") << fileName2 << QString::fromLatin1("Tre");
5059     QTest::newRow("qsTr('Happy birthday!')@translatable2.js")
5060             << QString::fromLatin1("qsTr('Happy birthday!')") << fileName2 << QString::fromLatin1("Gratulerer med dagen!");
5061 
5062     // Not translated because translation is only in translatable.js
5063     QTest::newRow("qsTr('One')@translatable2.js")
5064             << QString::fromLatin1("qsTr('One')") << fileName2 << QString::fromLatin1("One");
5065     QTest::newRow("(function() { return qsTr('One'); })()@translatable2.js")
5066             << QString::fromLatin1("(function() { return qsTr('One'); })()") << fileName2 << QString::fromLatin1("One");
5067 
5068     // For qsTranslate() the filename shouldn't matter
5069     QTest::newRow("qsTranslate('FooContext', 'Two')@translatable2.js")
5070             << QString::fromLatin1("qsTranslate('FooContext', 'Two')") << fileName2 << QString::fromLatin1("To");
5071     QTest::newRow("qsTranslate('BarContext', 'Congratulations!')@translatable.js")
5072             << QString::fromLatin1("qsTranslate('BarContext', 'Congratulations!')") << fileName << QString::fromLatin1("Gratulerer!");
5073 }
5074 
translateScript()5075 void tst_QScriptEngine::translateScript()
5076 {
5077     QFETCH(QString, expression);
5078     QFETCH(QString, fileName);
5079     QFETCH(QString, expectedTranslation);
5080 
5081     QScriptEngine engine;
5082 
5083     TranslationScope tranScope(":/translations/translatable_la");
5084     engine.installTranslatorFunctions();
5085 
5086     QCOMPARE(engine.evaluate(expression, fileName).toString(), expectedTranslation);
5087     QVERIFY(!engine.hasUncaughtException());
5088 }
5089 
translateScript_crossScript()5090 void tst_QScriptEngine::translateScript_crossScript()
5091 {
5092     QScriptEngine engine;
5093     TranslationScope tranScope(":/translations/translatable_la");
5094     engine.installTranslatorFunctions();
5095 
5096     QString fileName = QString::fromLatin1("translatable.js");
5097     QString fileName2 = QString::fromLatin1("translatable2.js");
5098     // qsTr() should use the innermost filename as context
5099     engine.evaluate("function foo(s) { return bar(s); }", fileName);
5100     engine.evaluate("function bar(s) { return qsTr(s); }", fileName2);
5101     QCOMPARE(engine.evaluate("bar('Three')", fileName2).toString(), QString::fromLatin1("Tre"));
5102     QCOMPARE(engine.evaluate("bar('Three')", fileName).toString(), QString::fromLatin1("Tre"));
5103     QCOMPARE(engine.evaluate("bar('One')", fileName2).toString(), QString::fromLatin1("One"));
5104 
5105     engine.evaluate("function foo(s) { return bar(s); }", fileName2);
5106     engine.evaluate("function bar(s) { return qsTr(s); }", fileName);
5107     QCOMPARE(engine.evaluate("bar('Three')", fileName2).toString(), QString::fromLatin1("Three"));
5108     QCOMPARE(engine.evaluate("bar('One')", fileName).toString(), QString::fromLatin1("En"));
5109     QCOMPARE(engine.evaluate("bar('One')", fileName2).toString(), QString::fromLatin1("En"));
5110 }
5111 
callQsTr(QScriptContext * ctx,QScriptEngine * eng)5112 static QScriptValue callQsTr(QScriptContext *ctx, QScriptEngine *eng)
5113 {
5114     return eng->globalObject().property("qsTr").call(ctx->thisObject(), ctx->argumentsObject());
5115 }
5116 
translateScript_callQsTrFromNative()5117 void tst_QScriptEngine::translateScript_callQsTrFromNative()
5118 {
5119     QScriptEngine engine;
5120     TranslationScope tranScope(":/translations/translatable_la");
5121     engine.installTranslatorFunctions();
5122 
5123     QString fileName = QString::fromLatin1("translatable.js");
5124     QString fileName2 = QString::fromLatin1("translatable2.js");
5125     // Calling qsTr() from a native function
5126     engine.globalObject().setProperty("qsTrProxy", engine.newFunction(callQsTr));
5127     QCOMPARE(engine.evaluate("qsTrProxy('One')", fileName).toString(), QString::fromLatin1("En"));
5128     QCOMPARE(engine.evaluate("qsTrProxy('One')", fileName2).toString(), QString::fromLatin1("One"));
5129     QCOMPARE(engine.evaluate("qsTrProxy('Three')", fileName).toString(), QString::fromLatin1("Three"));
5130     QCOMPARE(engine.evaluate("qsTrProxy('Three')", fileName2).toString(), QString::fromLatin1("Tre"));
5131 }
5132 
translateScript_trNoOp()5133 void tst_QScriptEngine::translateScript_trNoOp()
5134 {
5135     QScriptEngine engine;
5136     TranslationScope tranScope(":/translations/translatable_la");
5137     engine.installTranslatorFunctions();
5138 
5139     QVERIFY(engine.evaluate("QT_TR_NOOP()").isUndefined());
5140     QCOMPARE(engine.evaluate("QT_TR_NOOP('One')").toString(), QString::fromLatin1("One"));
5141 
5142     QVERIFY(engine.evaluate("QT_TRANSLATE_NOOP()").isUndefined());
5143     QVERIFY(engine.evaluate("QT_TRANSLATE_NOOP('FooContext')").isUndefined());
5144     QCOMPARE(engine.evaluate("QT_TRANSLATE_NOOP('FooContext', 'Two')").toString(), QString::fromLatin1("Two"));
5145 }
5146 
translateScript_callQsTrFromCpp()5147 void tst_QScriptEngine::translateScript_callQsTrFromCpp()
5148 {
5149     QScriptEngine engine;
5150     TranslationScope tranScope(":/translations/translatable_la");
5151     engine.installTranslatorFunctions();
5152 
5153     // There is no context, but it shouldn't crash
5154     QCOMPARE(engine.globalObject().property("qsTr").call(
5155              QScriptValue(), QScriptValueList() << "One").toString(), QString::fromLatin1("One"));
5156 }
5157 
translateWithInvalidArgs_data()5158 void tst_QScriptEngine::translateWithInvalidArgs_data()
5159 {
5160     QTest::addColumn<QString>("expression");
5161     QTest::addColumn<QString>("expectedError");
5162 
5163     QTest::newRow("qsTr()")  << "qsTr()" << "Error: qsTr() requires at least one argument";
5164     QTest::newRow("qsTr(123)")  << "qsTr(123)" << "Error: qsTr(): first argument (text) must be a string";
5165     QTest::newRow("qsTr('foo', 123)")  << "qsTr('foo', 123)" << "Error: qsTr(): second argument (comment) must be a string";
5166     QTest::newRow("qsTr('foo', 'bar', 'baz')")  << "qsTr('foo', 'bar', 'baz')" << "Error: qsTr(): third argument (n) must be a number";
5167     QTest::newRow("qsTr('foo', 'bar', true)")  << "qsTr('foo', 'bar', true)" << "Error: qsTr(): third argument (n) must be a number";
5168 
5169     QTest::newRow("qsTranslate()")  << "qsTranslate()" << "Error: qsTranslate() requires at least two arguments";
5170     QTest::newRow("qsTranslate('foo')")  << "qsTranslate('foo')" << "Error: qsTranslate() requires at least two arguments";
5171     QTest::newRow("qsTranslate(123, 'foo')")  << "qsTranslate(123, 'foo')" << "Error: qsTranslate(): first argument (context) must be a string";
5172     QTest::newRow("qsTranslate('foo', 123)")  << "qsTranslate('foo', 123)" << "Error: qsTranslate(): second argument (text) must be a string";
5173     QTest::newRow("qsTranslate('foo', 'bar', 123)")  << "qsTranslate('foo', 'bar', 123)" << "Error: qsTranslate(): third argument (comment) must be a string";
5174     QTest::newRow("qsTranslate('foo', 'bar', 'baz', 123)")  << "qsTranslate('foo', 'bar', 'baz', 123)" << "Error: qsTranslate(): fourth argument (encoding) must be a string";
5175     QTest::newRow("qsTranslate('foo', 'bar', 'baz', 'zab', 'rab')")  << "qsTranslate('foo', 'bar', 'baz', 'zab', 'rab')" << "Error: qsTranslate(): fifth argument (n) must be a number";
5176     QTest::newRow("qsTranslate('foo', 'bar', 'baz', 'zab', 123)")  << "qsTranslate('foo', 'bar', 'baz', 'zab', 123)" << "Error: qsTranslate(): invalid encoding 'zab'";
5177 
5178     QTest::newRow("qsTrId()")  << "qsTrId()" << "Error: qsTrId() requires at least one argument";
5179     QTest::newRow("qsTrId(123)")  << "qsTrId(123)" << "TypeError: qsTrId(): first argument (id) must be a string";
5180     QTest::newRow("qsTrId('foo', 'bar')")  << "qsTrId('foo', 'bar')" << "TypeError: qsTrId(): second argument (n) must be a number";
5181 }
5182 
translateWithInvalidArgs()5183 void tst_QScriptEngine::translateWithInvalidArgs()
5184 {
5185     QFETCH(QString, expression);
5186     QFETCH(QString, expectedError);
5187     QScriptEngine engine;
5188     engine.installTranslatorFunctions();
5189     QScriptValue result = engine.evaluate(expression);
5190     QVERIFY(result.isError());
5191     QCOMPARE(result.toString(), expectedError);
5192 }
5193 
translationContext_data()5194 void tst_QScriptEngine::translationContext_data()
5195 {
5196     QTest::addColumn<QString>("path");
5197     QTest::addColumn<QString>("text");
5198     QTest::addColumn<QString>("expectedTranslation");
5199 
5200     QTest::newRow("translatable.js")  << "translatable.js" << "One" << "En";
5201     QTest::newRow("/translatable.js")  << "/translatable.js" << "One" << "En";
5202     QTest::newRow("/foo/translatable.js")  << "/foo/translatable.js" << "One" << "En";
5203     QTest::newRow("/foo/bar/translatable.js")  << "/foo/bar/translatable.js" << "One" << "En";
5204     QTest::newRow("./translatable.js")  << "./translatable.js" << "One" << "En";
5205     QTest::newRow("../translatable.js")  << "../translatable.js" << "One" << "En";
5206     QTest::newRow("foo/translatable.js")  << "foo/translatable.js" << "One" << "En";
5207     QTest::newRow("file:///home/qt/translatable.js")  << "file:///home/qt/translatable.js" << "One" << "En";
5208     QTest::newRow(":/resources/translatable.js")  << ":/resources/translatable.js" << "One" << "En";
5209     QTest::newRow("/translatable.js.foo")  << "/translatable.js.foo" << "One" << "En";
5210     QTest::newRow("/translatable.txt")  << "/translatable.txt" << "One" << "En";
5211     QTest::newRow("translatable")  << "translatable" << "One" << "En";
5212     QTest::newRow("foo/translatable")  << "foo/translatable" << "One" << "En";
5213 
5214     QTest::newRow("native separators")
5215         << (QDir::toNativeSeparators(QDir::currentPath()) + QDir::separator() + "translatable.js")
5216         << "One" << "En";
5217 
5218     QTest::newRow("translatable.js/")  << "translatable.js/" << "One" << "One";
5219     QTest::newRow("nosuchscript.js")  << "" << "One" << "One";
5220     QTest::newRow("(empty)")  << "" << "One" << "One";
5221 }
5222 
translationContext()5223 void tst_QScriptEngine::translationContext()
5224 {
5225     TranslationScope tranScope(":/translations/translatable_la");
5226 
5227     QScriptEngine engine;
5228     engine.installTranslatorFunctions();
5229 
5230     QFETCH(QString, path);
5231     QFETCH(QString, text);
5232     QFETCH(QString, expectedTranslation);
5233     QScriptValue ret = engine.evaluate(QString::fromLatin1("qsTr('%0')").arg(text), path);
5234     QVERIFY(ret.isString());
5235     QCOMPARE(ret.toString(), expectedTranslation);
5236 }
5237 
translateScriptIdBased()5238 void tst_QScriptEngine::translateScriptIdBased()
5239 {
5240     QScriptEngine engine;
5241 
5242     TranslationScope tranScope(":/translations/idtranslatable_la");
5243     engine.installTranslatorFunctions();
5244 
5245     QString fileName = QString::fromLatin1("idtranslatable.js");
5246 
5247     QHash<QString, QString> expectedTranslations;
5248     expectedTranslations["qtn_foo_bar"] = "First string";
5249     expectedTranslations["qtn_needle"] = "Second string";
5250     expectedTranslations["qtn_haystack"] = "Third string";
5251     expectedTranslations["qtn_bar_baz"] = "Fourth string";
5252 
5253     QHash<QString, QString>::const_iterator it;
5254     for (it = expectedTranslations.constBegin(); it != expectedTranslations.constEnd(); ++it) {
5255         for (int x = 0; x < 2; ++x) {
5256             QString fn;
5257             if (x)
5258                 fn = fileName;
5259             // Top-level
5260             QCOMPARE(engine.evaluate(QString::fromLatin1("qsTrId('%0')")
5261                                      .arg(it.key()), fn).toString(),
5262                      it.value());
5263             QCOMPARE(engine.evaluate(QString::fromLatin1("QT_TRID_NOOP('%0')")
5264                                      .arg(it.key()), fn).toString(),
5265                      it.key());
5266             // From function
5267             QCOMPARE(engine.evaluate(QString::fromLatin1("(function() { return qsTrId('%0'); })()")
5268                                      .arg(it.key()), fn).toString(),
5269                      it.value());
5270             QCOMPARE(engine.evaluate(QString::fromLatin1("(function() { return QT_TRID_NOOP('%0'); })()")
5271                                      .arg(it.key()), fn).toString(),
5272                      it.key());
5273         }
5274     }
5275 
5276     // Plural form
5277     QCOMPARE(engine.evaluate("qsTrId('qtn_bar_baz', 10)").toString(),
5278              QString::fromLatin1("10 fooish bar(s) found"));
5279     QCOMPARE(engine.evaluate("qsTrId('qtn_foo_bar', 10)").toString(),
5280              QString::fromLatin1("qtn_foo_bar")); // Doesn't have plural
5281 }
5282 
5283 // How to add a new test row:
5284 // - Find a nice list of Unicode characters to choose from
5285 // - Write source string/context/comment in .js using Unicode escape sequences (\uABCD)
5286 // - Update corresponding .ts file (e.g. lupdate foo.js -ts foo.ts -codecfortr UTF-8)
5287 // - Enter translation in Linguist
5288 // - Update corresponding .qm file (e.g. lrelease foo.ts)
5289 // - Evaluate script that performs translation; make sure the correct result is returned
5290 //   (e.g. by setting the resulting string as the text of a QLabel and visually verifying
5291 //   that it looks the same as what you entered in Linguist :-) )
5292 // - Generate the expectedTranslation column data using toUtf8().toHex()
translateScriptUnicode_data()5293 void tst_QScriptEngine::translateScriptUnicode_data()
5294 {
5295     QTest::addColumn<QString>("expression");
5296     QTest::addColumn<QString>("fileName");
5297     QTest::addColumn<QString>("expectedTranslation");
5298 
5299     QString fileName = QString::fromLatin1("translatable-unicode.js");
5300     QTest::newRow("qsTr('H\\u2082O')@translatable-unicode.js")
5301             << QString::fromLatin1("qsTr('H\\u2082O')") << fileName << QString::fromUtf8("\xcd\xbb\xcd\xbc\xcd\xbd");
5302     QTest::newRow("qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')@translatable-unicode.js")
5303             << QString::fromLatin1("qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')") << fileName << QString::fromUtf8("\xd7\x91\xd7\x9a\xd7\xa2");
5304     QTest::newRow("qsTr('\\u0391\\u0392\\u0393')@translatable-unicode.js")
5305             << QString::fromLatin1("qsTr('\\u0391\\u0392\\u0393')") << fileName << QString::fromUtf8("\xd3\x9c\xd2\xb4\xd1\xbc");
5306     QTest::newRow("qsTranslate('\\u010C\\u0101\\u011F\\u0115', '\\u0414\\u0415\\u0416')@translatable-unicode.js")
5307             << QString::fromLatin1("qsTranslate('\\u010C\\u0101\\u011F\\u0115', '\\u0414\\u0415\\u0416')") << fileName << QString::fromUtf8("\xd8\xae\xd8\xb3\xd8\xb3");
5308     QTest::newRow("qsTr('H\\u2082O', 'not the same H\\u2082O')@translatable-unicode.js")
5309             << QString::fromLatin1("qsTr('H\\u2082O', 'not the same H\\u2082O')") << fileName << QString::fromUtf8("\xd4\xb6\xd5\x8a\xd5\x92");
5310     QTest::newRow("qsTr('H\\u2082O')")
5311             << QString::fromLatin1("qsTr('H\\u2082O')") << QString() << QString::fromUtf8("\x48\xe2\x82\x82\x4f");
5312     QTest::newRow("qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')")
5313             << QString::fromLatin1("qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')") << QString() << QString::fromUtf8("\xd7\x91\xd7\x9a\xd7\xa2");
5314 }
5315 
translateScriptUnicode()5316 void tst_QScriptEngine::translateScriptUnicode()
5317 {
5318     QFETCH(QString, expression);
5319     QFETCH(QString, fileName);
5320     QFETCH(QString, expectedTranslation);
5321 
5322     QScriptEngine engine;
5323 
5324     TranslationScope tranScope(":/translations/translatable-unicode");
5325     engine.installTranslatorFunctions();
5326 
5327     QCOMPARE(engine.evaluate(expression, fileName).toString(), expectedTranslation);
5328     QVERIFY(!engine.hasUncaughtException());
5329 }
5330 
translateScriptUnicodeIdBased_data()5331 void tst_QScriptEngine::translateScriptUnicodeIdBased_data()
5332 {
5333     QTest::addColumn<QString>("expression");
5334     QTest::addColumn<QString>("expectedTranslation");
5335 
5336     QTest::newRow("qsTrId('\\u01F8\\u01D2\\u0199\\u01D0\\u01E1'')")
5337             << QString::fromLatin1("qsTrId('\\u01F8\\u01D2\\u0199\\u01D0\\u01E1')") << QString::fromUtf8("\xc6\xa7\xc6\xb0\xc6\x88\xc8\xbc\xc8\x9d\xc8\xbf\xc8\x99");
5338     QTest::newRow("qsTrId('\\u0191\\u01CE\\u0211\\u0229\\u019C\\u018E\\u019A\\u01D0')")
5339             << QString::fromLatin1("qsTrId('\\u0191\\u01CE\\u0211\\u0229\\u019C\\u018E\\u019A\\u01D0')") << QString::fromUtf8("\xc7\xa0\xc8\xa1\xc8\x8b\xc8\x85\xc8\x95");
5340     QTest::newRow("qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C', 10)")
5341             << QString::fromLatin1("qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C', 10)") << QString::fromUtf8("\x31\x30\x20\xc6\x92\xc6\xa1\xc7\x92\x28\xc8\x99\x29");
5342     QTest::newRow("qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C')")
5343             << QString::fromLatin1("qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C')") << QString::fromUtf8("\xc6\x91\xc6\xb0\xc7\xb9");
5344     QTest::newRow("qsTrId('\\u01CD\\u0180\\u01A8\\u0190\\u019E\\u01AB')")
5345             << QString::fromLatin1("qsTrId('\\u01CD\\u0180\\u01A8\\u0190\\u019E\\u01AB')") << QString::fromUtf8("\xc7\x8d\xc6\x80\xc6\xa8\xc6\x90\xc6\x9e\xc6\xab");
5346 }
5347 
translateScriptUnicodeIdBased()5348 void tst_QScriptEngine::translateScriptUnicodeIdBased()
5349 {
5350     QFETCH(QString, expression);
5351     QFETCH(QString, expectedTranslation);
5352 
5353     QScriptEngine engine;
5354 
5355     TranslationScope tranScope(":/translations/idtranslatable-unicode");
5356     engine.installTranslatorFunctions();
5357 
5358     QCOMPARE(engine.evaluate(expression).toString(), expectedTranslation);
5359     QVERIFY(!engine.hasUncaughtException());
5360 }
5361 
translateFromBuiltinCallback()5362 void tst_QScriptEngine::translateFromBuiltinCallback()
5363 {
5364     QScriptEngine eng;
5365     eng.installTranslatorFunctions();
5366 
5367     // Callback has no translation context.
5368     eng.evaluate("function foo() { qsTr('foo'); }");
5369 
5370     // Stack at translation time will be:
5371     // qsTr, foo, forEach, global
5372     // qsTr() needs to walk to the outer-most (global) frame before it finds
5373     // a translation context, and this should not crash.
5374     eng.evaluate("[10,20].forEach(foo)", "script.js");
5375 }
5376 
functionScopes()5377 void tst_QScriptEngine::functionScopes()
5378 {
5379     QScriptEngine eng;
5380     {
5381         // top-level functions have only the global object in their scope
5382         QScriptValue fun = eng.evaluate("(function() {})");
5383         QVERIFY(fun.isFunction());
5384         QEXPECT_FAIL("", "QScriptValue::scope() is internal, not implemented", Abort);
5385         QVERIFY(fun.scope().isObject());
5386         QVERIFY(fun.scope().strictlyEquals(eng.globalObject()));
5387         QVERIFY(!eng.globalObject().scope().isValid());
5388     }
5389     {
5390         QScriptValue fun = eng.globalObject().property("Object");
5391         QVERIFY(fun.isFunction());
5392         // native built-in functions don't have scope
5393         QVERIFY(!fun.scope().isValid());
5394     }
5395     {
5396         // closure
5397         QScriptValue fun = eng.evaluate("(function(arg) { var foo = arg; return function() { return foo; }; })(123)");
5398         QVERIFY(fun.isFunction());
5399         {
5400             QScriptValue ret = fun.call();
5401             QVERIFY(ret.isNumber());
5402             QCOMPARE(ret.toInt32(), 123);
5403         }
5404         QScriptValue scope = fun.scope();
5405         QVERIFY(scope.isObject());
5406         {
5407             QScriptValue ret = scope.property("foo");
5408             QVERIFY(ret.isNumber());
5409             QCOMPARE(ret.toInt32(), 123);
5410             QCOMPARE(scope.propertyFlags("foo"), QScriptValue::Undeletable);
5411         }
5412         {
5413             QScriptValue ret = scope.property("arg");
5414             QVERIFY(ret.isNumber());
5415             QCOMPARE(ret.toInt32(), 123);
5416             QCOMPARE(scope.propertyFlags("arg"), QScriptValue::Undeletable | QScriptValue::SkipInEnumeration);
5417         }
5418 
5419         scope.setProperty("foo", 456);
5420         {
5421             QScriptValue ret = fun.call();
5422             QVERIFY(ret.isNumber());
5423             QCOMPARE(ret.toInt32(), 456);
5424         }
5425 
5426         scope = scope.scope();
5427         QVERIFY(scope.isObject());
5428         QVERIFY(scope.strictlyEquals(eng.globalObject()));
5429     }
5430 }
5431 
counter_inner(QScriptContext * ctx,QScriptEngine *)5432 static QScriptValue counter_inner(QScriptContext *ctx, QScriptEngine *)
5433 {
5434      QScriptValue outerAct = ctx->callee().scope();
5435      double count = outerAct.property("count").toNumber();
5436      outerAct.setProperty("count", count+1);
5437      return count;
5438 }
5439 
counter(QScriptContext * ctx,QScriptEngine * eng)5440 static QScriptValue counter(QScriptContext *ctx, QScriptEngine *eng)
5441 {
5442      QScriptValue act = ctx->activationObject();
5443      act.setProperty("count", ctx->argument(0).toInt32());
5444      QScriptValue result = eng->newFunction(counter_inner);
5445      result.setScope(act);
5446      return result;
5447 }
5448 
counter_hybrid(QScriptContext * ctx,QScriptEngine * eng)5449 static QScriptValue counter_hybrid(QScriptContext *ctx, QScriptEngine *eng)
5450 {
5451      QScriptValue act = ctx->activationObject();
5452      act.setProperty("count", ctx->argument(0).toInt32());
5453      return eng->evaluate("(function() { return count++; })");
5454 }
5455 
nativeFunctionScopes()5456 void tst_QScriptEngine::nativeFunctionScopes()
5457 {
5458     QScriptEngine eng;
5459     {
5460         QScriptValue fun = eng.newFunction(counter);
5461         QScriptValue cnt = fun.call(QScriptValue(), QScriptValueList() << 123);
5462         QVERIFY(cnt.isFunction());
5463         {
5464             QScriptValue ret = cnt.call();
5465             QVERIFY(ret.isNumber());
5466             QCOMPARE(ret.toInt32(), 123);
5467         }
5468     }
5469     {
5470         QScriptValue fun = eng.newFunction(counter_hybrid);
5471         QScriptValue cnt = fun.call(QScriptValue(), QScriptValueList() << 123);
5472         QVERIFY(cnt.isFunction());
5473         {
5474             QScriptValue ret = cnt.call();
5475             QVERIFY(ret.isNumber());
5476             QCOMPARE(ret.toInt32(), 123);
5477         }
5478     }
5479 
5480     //from http://doc.trolltech.com/latest/qtscript.html#nested-functions-and-the-scope-chain
5481     {
5482         QScriptEngine eng;
5483         eng.evaluate("function counter() { var count = 0; return function() { return count++; } }\n"
5484                      "var c1 = counter();  var c2 = counter(); ");
5485         QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("0"));
5486         QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("1"));
5487         QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("0"));
5488         QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("1"));
5489         QVERIFY(!eng.hasUncaughtException());
5490     }
5491     {
5492         QScriptEngine eng;
5493         eng.globalObject().setProperty("counter", eng.newFunction(counter));
5494         eng.evaluate("var c1 = counter();  var c2 = counter(); ");
5495         QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("0"));
5496         QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("1"));
5497         QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("0"));
5498         QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("1"));
5499         QVERIFY(!eng.hasUncaughtException());
5500     }
5501     {
5502         QScriptEngine eng;
5503         eng.globalObject().setProperty("counter", eng.newFunction(counter_hybrid));
5504         eng.evaluate("var c1 = counter();  var c2 = counter(); ");
5505         QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("0"));
5506         QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("1"));
5507         QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("0"));
5508         QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("1"));
5509         QVERIFY(!eng.hasUncaughtException());
5510     }
5511 }
5512 
createProgram(QScriptContext * ctx,QScriptEngine * eng)5513 static QScriptValue createProgram(QScriptContext *ctx, QScriptEngine *eng)
5514 {
5515     QString code = ctx->argument(0).toString();
5516     QScriptProgram result(code);
5517     return qScriptValueFromValue(eng, result);
5518 }
5519 
evaluateProgram()5520 void tst_QScriptEngine::evaluateProgram()
5521 {
5522     QScriptEngine eng;
5523 
5524     {
5525         QString code("1 + 2");
5526         QString fileName("hello.js");
5527         int lineNumber(123);
5528         QScriptProgram program(code, fileName, lineNumber);
5529         QVERIFY(!program.isNull());
5530         QCOMPARE(program.sourceCode(), code);
5531         QCOMPARE(program.fileName(), fileName);
5532         QCOMPARE(program.firstLineNumber(), lineNumber);
5533 
5534         QScriptValue expected = eng.evaluate(code);
5535         for (int x = 0; x < 10; ++x) {
5536             QScriptValue ret = eng.evaluate(program);
5537             QVERIFY(ret.equals(expected));
5538         }
5539 
5540         // operator=
5541         QScriptProgram sameProgram = program;
5542         QVERIFY(sameProgram == program);
5543         QVERIFY(eng.evaluate(sameProgram).equals(expected));
5544 
5545         // copy constructor
5546         QScriptProgram sameProgram2(program);
5547         QVERIFY(sameProgram2 == program);
5548         QVERIFY(eng.evaluate(sameProgram2).equals(expected));
5549 
5550         QScriptProgram differentProgram("2 + 3");
5551         QVERIFY(differentProgram != program);
5552         QVERIFY(!eng.evaluate(differentProgram).equals(expected));
5553     }
5554 }
5555 
evaluateProgram_customScope()5556 void tst_QScriptEngine::evaluateProgram_customScope()
5557 {
5558     QScriptEngine eng;
5559     {
5560         QScriptProgram program("a");
5561         QVERIFY(!program.isNull());
5562         {
5563             QScriptValue ret = eng.evaluate(program);
5564             QVERIFY(ret.isError());
5565             QCOMPARE(ret.toString(), QString::fromLatin1("ReferenceError: Can't find variable: a"));
5566         }
5567 
5568         QScriptValue obj = eng.newObject();
5569         obj.setProperty("a", 123);
5570         QScriptContext *ctx = eng.currentContext();
5571         ctx->pushScope(obj);
5572         {
5573             QScriptValue ret = eng.evaluate(program);
5574             QVERIFY(!ret.isError());
5575             QVERIFY(ret.equals(obj.property("a")));
5576         }
5577 
5578         obj.setProperty("a", QScriptValue());
5579         {
5580             QScriptValue ret = eng.evaluate(program);
5581             QVERIFY(ret.isError());
5582         }
5583 
5584         QScriptValue obj2 = eng.newObject();
5585         obj2.setProperty("a", 456);
5586         ctx->pushScope(obj2);
5587         {
5588             QScriptValue ret = eng.evaluate(program);
5589             QVERIFY(!ret.isError());
5590             QVERIFY(ret.equals(obj2.property("a")));
5591         }
5592 
5593         ctx->popScope();
5594     }
5595 }
5596 
evaluateProgram_closure()5597 void tst_QScriptEngine::evaluateProgram_closure()
5598 {
5599     QScriptEngine eng;
5600     {
5601         QScriptProgram program("(function() { var count = 0; return function() { return count++; }; })");
5602         QVERIFY(!program.isNull());
5603         QScriptValue createCounter = eng.evaluate(program);
5604         QVERIFY(createCounter.isFunction());
5605         QScriptValue counter = createCounter.call();
5606         QVERIFY(counter.isFunction());
5607         {
5608             QScriptValue ret = counter.call();
5609             QVERIFY(ret.isNumber());
5610         }
5611         QScriptValue counter2 = createCounter.call();
5612         QVERIFY(counter2.isFunction());
5613         QVERIFY(!counter2.equals(counter));
5614         {
5615             QScriptValue ret = counter2.call();
5616             QVERIFY(ret.isNumber());
5617         }
5618     }
5619 }
5620 
evaluateProgram_executeLater()5621 void tst_QScriptEngine::evaluateProgram_executeLater()
5622 {
5623     QScriptEngine eng;
5624     // Program created in a function call, then executed later
5625     {
5626         QScriptValue fun = eng.newFunction(createProgram);
5627         QScriptProgram program = qscriptvalue_cast<QScriptProgram>(
5628             fun.call(QScriptValue(), QScriptValueList() << "a + 1"));
5629         QVERIFY(!program.isNull());
5630         eng.globalObject().setProperty("a", QScriptValue());
5631         {
5632             QScriptValue ret = eng.evaluate(program);
5633             QVERIFY(ret.isError());
5634             QCOMPARE(ret.toString(), QString::fromLatin1("ReferenceError: Can't find variable: a"));
5635         }
5636         eng.globalObject().setProperty("a", 122);
5637         {
5638             QScriptValue ret = eng.evaluate(program);
5639             QVERIFY(!ret.isError());
5640             QVERIFY(ret.isNumber());
5641             QCOMPARE(ret.toInt32(), 123);
5642         }
5643     }
5644 }
5645 
evaluateProgram_multipleEngines()5646 void tst_QScriptEngine::evaluateProgram_multipleEngines()
5647 {
5648     QScriptEngine eng;
5649     {
5650         QString code("1 + 2");
5651         QScriptProgram program(code);
5652         QVERIFY(!program.isNull());
5653         double expected = eng.evaluate(program).toNumber();
5654         for (int x = 0; x < 2; ++x) {
5655             QScriptEngine eng2;
5656             for (int y = 0; y < 2; ++y) {
5657                 double ret = eng2.evaluate(program).toNumber();
5658                 QCOMPARE(ret, expected);
5659             }
5660         }
5661     }
5662 }
5663 
evaluateProgram_empty()5664 void tst_QScriptEngine::evaluateProgram_empty()
5665 {
5666     QScriptEngine eng;
5667     {
5668         QScriptProgram program;
5669         QVERIFY(program.isNull());
5670         QScriptValue ret = eng.evaluate(program);
5671         QVERIFY(!ret.isValid());
5672     }
5673 }
5674 
collectGarbageAfterConnect()5675 void tst_QScriptEngine::collectGarbageAfterConnect()
5676 {
5677     // QTBUG-6366
5678     QScriptEngine engine;
5679     QPointer<QWidget> widget = new QWidget;
5680     engine.globalObject().setProperty(
5681         "widget", engine.newQObject(widget, QScriptEngine::ScriptOwnership));
5682     QVERIFY(engine.evaluate("widget.customContextMenuRequested.connect(\n"
5683                             "  function() { print('hello'); }\n"
5684                             ");")
5685             .isUndefined());
5686     QVERIFY(widget != 0);
5687     engine.evaluate("widget = null;");
5688     // The connection should not keep the widget alive.
5689     collectGarbage_helper(engine);
5690     QVERIFY(widget == 0);
5691 }
5692 
collectGarbageAfterNativeArguments()5693 void tst_QScriptEngine::collectGarbageAfterNativeArguments()
5694 {
5695     // QTBUG-17788
5696     QScriptEngine eng;
5697     QScriptContext *ctx = eng.pushContext();
5698     QScriptValue arguments = ctx->argumentsObject();
5699     // Shouldn't crash when marking the arguments object.
5700     collectGarbage_helper(eng);
5701 }
5702 
constructQObjectFromThisObject(QScriptContext * ctx,QScriptEngine * eng)5703 static QScriptValue constructQObjectFromThisObject(QScriptContext *ctx, QScriptEngine *eng)
5704 {
5705     if (!ctx->isCalledAsConstructor()) {
5706         qWarning("%s: ctx->isCalledAsConstructor() returned false", Q_FUNC_INFO);
5707         return QScriptValue();
5708     }
5709     return eng->newQObject(ctx->thisObject(), new QObject, QScriptEngine::ScriptOwnership);
5710 }
5711 
promoteThisObjectToQObjectInConstructor()5712 void tst_QScriptEngine::promoteThisObjectToQObjectInConstructor()
5713 {
5714     QScriptEngine engine;
5715     QScriptValue ctor = engine.newFunction(constructQObjectFromThisObject);
5716     engine.globalObject().setProperty("Ctor", ctor);
5717     QScriptValue object = engine.evaluate("new Ctor");
5718     QVERIFY(!object.isError());
5719     QVERIFY(object.isQObject());
5720     QVERIFY(object.toQObject() != 0);
5721     QVERIFY(object.property("objectName").isString());
5722     QVERIFY(object.property("deleteLater").isFunction());
5723 }
5724 
minimal(QRegExp r)5725 static QRegExp minimal(QRegExp r) { r.setMinimal(true); return r; }
5726 
qRegExpInport_data()5727 void tst_QScriptEngine::qRegExpInport_data()
5728 {
5729     QTest::addColumn<QRegExp>("rx");
5730     QTest::addColumn<QString>("string");
5731     QTest::addColumn<QString>("matched");
5732 
5733     QTest::newRow("normal")  << QRegExp("(test|foo)") << "test _ foo _ test _ Foo";
5734     QTest::newRow("normal2")  << QRegExp("(Test|Foo)") << "test _ foo _ test _ Foo";
5735     QTest::newRow("case insensitive)")  << QRegExp("(test|foo)", Qt::CaseInsensitive) << "test _ foo _ test _ Foo";
5736     QTest::newRow("case insensitive2)")  << QRegExp("(Test|Foo)", Qt::CaseInsensitive) << "test _ foo _ test _ Foo";
5737     QTest::newRow("b(a*)(b*)")  << QRegExp("b(a*)(b*)", Qt::CaseInsensitive) << "aaabbBbaAabaAaababaaabbaaab";
5738     QTest::newRow("greedy")  << QRegExp("a*(a*)", Qt::CaseInsensitive, QRegExp::RegExp2) << "aaaabaaba";
5739     // this one will fail because we do not support the QRegExp::RegExp in JSC
5740     //QTest::newRow("not_greedy")  << QRegExp("a*(a*)", Qt::CaseInsensitive, QRegExp::RegExp) << "aaaabaaba";
5741     QTest::newRow("willcard")  << QRegExp("*.txt", Qt::CaseSensitive, QRegExp::Wildcard) << "file.txt";
5742     QTest::newRow("willcard 2")  << QRegExp("a?b.txt", Qt::CaseSensitive, QRegExp::Wildcard) << "ab.txt abb.rtc acb.txt";
5743     QTest::newRow("slash")  << QRegExp("g/.*/s", Qt::CaseInsensitive, QRegExp::RegExp2) << "string/string/string";
5744     QTest::newRow("slash2")  << QRegExp("g / .* / s", Qt::CaseInsensitive, QRegExp::RegExp2) << "string / string / string";
5745     QTest::newRow("fixed")  << QRegExp("a*aa.a(ba)*a\\ba", Qt::CaseInsensitive, QRegExp::FixedString) << "aa*aa.a(ba)*a\\ba";
5746     QTest::newRow("fixed insensitive")  << QRegExp("A*A", Qt::CaseInsensitive, QRegExp::FixedString) << "a*A A*a A*A a*a";
5747     QTest::newRow("fixed sensitive")  << QRegExp("A*A", Qt::CaseSensitive, QRegExp::FixedString) << "a*A A*a A*A a*a";
5748     QTest::newRow("html")  << QRegExp("<b>(.*)</b>", Qt::CaseSensitive, QRegExp::RegExp2) << "<b>bold</b><i>italic</i><b>bold</b>";
5749     QTest::newRow("html minimal")  << minimal(QRegExp("<b>(.*)</b>", Qt::CaseSensitive, QRegExp::RegExp2)) << "<b>bold</b><i>italic</i><b>bold</b>";
5750     QTest::newRow("aaa")  << QRegExp("a{2,5}") << "aAaAaaaaaAa";
5751     QTest::newRow("aaa minimal")  << minimal(QRegExp("a{2,5}")) << "aAaAaaaaaAa";
5752     QTest::newRow("minimal")  << minimal(QRegExp(".*\\} [*8]")) << "}?} ?} *";
5753     QTest::newRow(".? minimal")  << minimal(QRegExp(".?")) << ".?";
5754     QTest::newRow(".+ minimal")  << minimal(QRegExp(".+")) << ".+";
5755     QTest::newRow("[.?] minimal")  << minimal(QRegExp("[.?]")) << ".?";
5756     QTest::newRow("[.+] minimal")  << minimal(QRegExp("[.+]")) << ".+";
5757 }
5758 
qRegExpInport()5759 void tst_QScriptEngine::qRegExpInport()
5760 {
5761     QFETCH(QRegExp, rx);
5762     QFETCH(QString, string);
5763 
5764     QScriptEngine eng;
5765     QScriptValue rexp;
5766     rexp = eng.newRegExp(rx);
5767 
5768     QCOMPARE(rexp.isValid(), true);
5769     QCOMPARE(rexp.isRegExp(), true);
5770     QVERIFY(rexp.isFunction());
5771 
5772     QScriptValue func = eng.evaluate("(function(string, regexp) { return string.match(regexp); })");
5773     QScriptValue result = func.call(QScriptValue(),  QScriptValueList() << string << rexp);
5774 
5775     rx.indexIn(string);
5776     for (int i = 0; i <= rx.captureCount(); i++)  {
5777         QCOMPARE(result.property(i).toString(), rx.cap(i));
5778     }
5779 }
5780 
5781 // QScriptValue::toDateTime() returns a local time, whereas JS dates
5782 // are always stored as UTC. QtScript must respect the current time
5783 // zone, and correctly adjust for daylight saving time that may be in
5784 // effect at a given date (QTBUG-9770).
dateRoundtripJSQtJS()5785 void tst_QScriptEngine::dateRoundtripJSQtJS()
5786 {
5787     uint secs = QDateTime(QDate(2009, 1, 1)).toUTC().toTime_t();
5788     QScriptEngine eng;
5789     for (int i = 0; i < 8000; ++i) {
5790         QScriptValue jsDate = eng.evaluate(QString::fromLatin1("new Date(%0)").arg(secs * 1000.0));
5791         QDateTime qtDate = jsDate.toDateTime();
5792         QScriptValue jsDate2 = eng.newDate(qtDate);
5793         if (jsDate2.toNumber() != jsDate.toNumber())
5794             QFAIL(qPrintable(jsDate.toString()));
5795         secs += 2*60*60;
5796     }
5797 }
5798 
dateRoundtripQtJSQt()5799 void tst_QScriptEngine::dateRoundtripQtJSQt()
5800 {
5801     QDateTime qtDate = QDateTime(QDate(2009, 1, 1));
5802     QScriptEngine eng;
5803     for (int i = 0; i < 8000; ++i) {
5804         QScriptValue jsDate = eng.newDate(qtDate);
5805         QDateTime qtDate2 = jsDate.toDateTime();
5806         if (qtDate2 != qtDate)
5807             QFAIL(qPrintable(qtDate.toString()));
5808         qtDate = qtDate.addSecs(2*60*60);
5809     }
5810 }
5811 
dateConversionJSQt()5812 void tst_QScriptEngine::dateConversionJSQt()
5813 {
5814     uint secs = QDateTime(QDate(2009, 1, 1)).toUTC().toTime_t();
5815     QScriptEngine eng;
5816     for (int i = 0; i < 8000; ++i) {
5817         QScriptValue jsDate = eng.evaluate(QString::fromLatin1("new Date(%0)").arg(secs * 1000.0));
5818         QDateTime qtDate = jsDate.toDateTime();
5819         QString qtUTCDateStr = qtDate.toUTC().toString(Qt::ISODate);
5820         QString jsUTCDateStr = jsDate.property("toISOString").call(jsDate).toString();
5821         jsUTCDateStr.chop(5); // get rid of milliseconds (".000Z")
5822         jsUTCDateStr.append("Z"); // append the timezone specifier again
5823         if (qtUTCDateStr != jsUTCDateStr)
5824             QFAIL(qPrintable(jsDate.toString()));
5825         secs += 2*60*60;
5826     }
5827 }
5828 
dateConversionQtJS()5829 void tst_QScriptEngine::dateConversionQtJS()
5830 {
5831     QDateTime qtDate = QDateTime(QDate(2009, 1, 1));
5832     QScriptEngine eng;
5833     for (int i = 0; i < 8000; ++i) {
5834         QScriptValue jsDate = eng.newDate(qtDate);
5835         QString jsUTCDateStr = jsDate.property("toISOString").call(jsDate).toString();
5836         jsUTCDateStr.chop(5); // get rid of milliseconds (".000Z")
5837         jsUTCDateStr.append("Z"); // append the timezone specifier again
5838         QString qtUTCDateStr = qtDate.toUTC().toString(Qt::ISODate);
5839         if (jsUTCDateStr != qtUTCDateStr)
5840             QFAIL(qPrintable(qtDate.toString()));
5841         qtDate = qtDate.addSecs(2*60*60);
5842     }
5843 }
5844 
createAnotherEngine(QScriptContext *,QScriptEngine *)5845 static QScriptValue createAnotherEngine(QScriptContext *, QScriptEngine *)
5846 {
5847     QScriptEngine eng;
5848     eng.evaluate("function foo(x, y) { return x + y; }" );
5849     eng.evaluate("hello = 5; world = 6" );
5850     return eng.evaluate("foo(hello,world)").toInt32();
5851 }
5852 
5853 
reentrency()5854 void tst_QScriptEngine::reentrency()
5855 {
5856     QScriptEngine eng;
5857     eng.globalObject().setProperty("foo", eng.newFunction(createAnotherEngine));
5858     eng.evaluate("function bar() { return foo(); }  hello = 9; function getHello() { return hello; }");
5859     QCOMPARE(eng.evaluate("foo() + getHello() + foo()").toInt32(), 5+6 + 9 + 5+6);
5860     QCOMPARE(eng.evaluate("foo").call().toInt32(), 5+6);
5861     QCOMPARE(eng.evaluate("hello").toInt32(), 9);
5862     QCOMPARE(eng.evaluate("foo() + hello").toInt32(), 5+6+9);
5863 }
5864 
newFixedStaticScopeObject()5865 void tst_QScriptEngine::newFixedStaticScopeObject()
5866 {
5867     // "Static scope objects" is an optimization we do for QML.
5868     // It enables the creation of JS objects that can guarantee to the
5869     // compiler that no properties will be added or removed. This enables
5870     // the compiler to generate a very simple (fast) property access, as
5871     // opposed to a full virtual lookup. Due to the inherent use of scope
5872     // chains in QML, this can make a huge difference (10x improvement for
5873     // benchmark in QTBUG-8576).
5874     // Ideally we would not need a special object type for this, and the
5875     // VM would dynamically optimize it to be fast...
5876     // See also QScriptEngine benchmark.
5877 
5878     QScriptEngine eng;
5879     static const int propertyCount = 4;
5880     QString names[] = { "foo", "bar", "baz", "Math" };
5881     QScriptValue values[] = { 123, "ciao", true, false };
5882     QScriptValue::PropertyFlags flags[] = { QScriptValue::Undeletable,
5883                                             QScriptValue::ReadOnly | QScriptValue::Undeletable,
5884                                             QScriptValue::SkipInEnumeration | QScriptValue::Undeletable,
5885                                             QScriptValue::Undeletable };
5886     QScriptValue scope = QScriptDeclarativeClass::newStaticScopeObject(&eng, propertyCount, names, values, flags);
5887 
5888     // Query property.
5889     for (int i = 0; i < propertyCount; ++i) {
5890         for (int x = 0; x < 2; ++x) {
5891             if (x) {
5892                 // Properties can't be deleted.
5893                 scope.setProperty(names[i], QScriptValue());
5894             }
5895             QVERIFY(scope.property(names[i]).equals(values[i]));
5896             QCOMPARE(scope.propertyFlags(names[i]), flags[i]);
5897         }
5898     }
5899 
5900     // Property that doesn't exist.
5901     QVERIFY(!scope.property("noSuchProperty").isValid());
5902     QCOMPARE(scope.propertyFlags("noSuchProperty"), QScriptValue::PropertyFlags());
5903 
5904     // Write to writable property.
5905     {
5906         QScriptValue oldValue = scope.property("foo");
5907         QVERIFY(oldValue.isNumber());
5908         QScriptValue newValue = oldValue.toNumber() * 2;
5909         scope.setProperty("foo", newValue);
5910         QVERIFY(scope.property("foo").equals(newValue));
5911         scope.setProperty("foo", oldValue);
5912         QVERIFY(scope.property("foo").equals(oldValue));
5913     }
5914 
5915     // Write to read-only property.
5916     scope.setProperty("bar", 456);
5917     QVERIFY(scope.property("bar").equals("ciao"));
5918 
5919     // Iterate.
5920     {
5921         QScriptValueIterator it(scope);
5922         QSet<QString> iteratedNames;
5923         while (it.hasNext()) {
5924             it.next();
5925             iteratedNames.insert(it.name());
5926         }
5927         for (int i = 0; i < propertyCount; ++i)
5928             QVERIFY(iteratedNames.contains(names[i]));
5929     }
5930 
5931     // Push it on the scope chain of a new context.
5932     QScriptContext *ctx = eng.pushContext();
5933     ctx->pushScope(scope);
5934     QCOMPARE(ctx->scopeChain().size(), 3); // Global Object, native activation, custom scope
5935     QVERIFY(ctx->activationObject().equals(scope));
5936 
5937     // Read property from JS.
5938     for (int i = 0; i < propertyCount; ++i) {
5939         for (int x = 0; x < 2; ++x) {
5940             if (x) {
5941                 // Property can't be deleted from JS.
5942                 QScriptValue ret = eng.evaluate(QString::fromLatin1("delete %0").arg(names[i]));
5943                 QVERIFY(ret.equals(false));
5944             }
5945             QVERIFY(eng.evaluate(names[i]).equals(values[i]));
5946         }
5947     }
5948 
5949     // Property that doesn't exist.
5950     QVERIFY(eng.evaluate("noSuchProperty").equals("ReferenceError: Can't find variable: noSuchProperty"));
5951 
5952     // Write property from JS.
5953     {
5954         QScriptValue oldValue = eng.evaluate("foo");
5955         QVERIFY(oldValue.isNumber());
5956         QScriptValue newValue = oldValue.toNumber() * 2;
5957         QVERIFY(eng.evaluate("foo = foo * 2; foo").equals(newValue));
5958         scope.setProperty("foo", oldValue);
5959         QVERIFY(eng.evaluate("foo").equals(oldValue));
5960     }
5961 
5962     // Write to read-only property.
5963     QVERIFY(eng.evaluate("bar = 456; bar").equals("ciao"));
5964 
5965     // Create a closure and return properties from there.
5966     {
5967         QScriptValue props = eng.evaluate("(function() { var baz = 'shadow'; return [foo, bar, baz, Math, Array]; })()");
5968         QVERIFY(props.isArray());
5969         // "foo" and "bar" come from scope object.
5970         QVERIFY(props.property(0).equals(scope.property("foo")));
5971         QVERIFY(props.property(1).equals(scope.property("bar")));
5972         // "baz" shadows property in scope object.
5973         QVERIFY(props.property(2).equals("shadow"));
5974         // "Math" comes from scope object, and shadows Global Object's "Math".
5975         QVERIFY(props.property(3).equals(scope.property("Math")));
5976         QVERIFY(!props.property(3).equals(eng.globalObject().property("Math")));
5977         // "Array" comes from Global Object.
5978         QVERIFY(props.property(4).equals(eng.globalObject().property("Array")));
5979     }
5980 
5981     // As with normal JS, assigning to an undefined variable will create
5982     // the property on the Global Object, not the inner scope.
5983     QVERIFY(!eng.globalObject().property("newProperty").isValid());
5984     QVERIFY(eng.evaluate("(function() { newProperty = 789; })()").isUndefined());
5985     QVERIFY(!scope.property("newProperty").isValid());
5986     QVERIFY(eng.globalObject().property("newProperty").isNumber());
5987 
5988     // Nested static scope.
5989     {
5990         static const int propertyCount2 = 2;
5991         QString names2[] = { "foo", "hum" };
5992         QScriptValue values2[] = { 321, "hello" };
5993         QScriptValue::PropertyFlags flags2[] = { QScriptValue::Undeletable,
5994                                                  QScriptValue::ReadOnly | QScriptValue::Undeletable };
5995         QScriptValue scope2 = QScriptDeclarativeClass::newStaticScopeObject(&eng, propertyCount2, names2, values2, flags2);
5996         ctx->pushScope(scope2);
5997 
5998         // "foo" shadows scope.foo.
5999         QVERIFY(eng.evaluate("foo").equals(scope2.property("foo")));
6000         QVERIFY(!eng.evaluate("foo").equals(scope.property("foo")));
6001         // "hum" comes from scope2.
6002         QVERIFY(eng.evaluate("hum").equals(scope2.property("hum")));
6003         // "Array" comes from Global Object.
6004         QVERIFY(eng.evaluate("Array").equals(eng.globalObject().property("Array")));
6005 
6006         ctx->popScope();
6007     }
6008 
6009     QScriptValue fun = eng.evaluate("(function() { return foo; })");
6010     QVERIFY(fun.isFunction());
6011     eng.popContext();
6012     // Function's scope chain persists after popContext().
6013     QVERIFY(fun.call().equals(scope.property("foo")));
6014 }
6015 
newGrowingStaticScopeObject()6016 void tst_QScriptEngine::newGrowingStaticScopeObject()
6017 {
6018     // The main use case for a growing static scope object is to set it as
6019     // the activation object of a QScriptContext, so that all JS variable
6020     // declarations end up in that object. It needs to be "growable" since
6021     // we don't know in advance how many variables a script will declare.
6022 
6023     QScriptEngine eng;
6024     QScriptValue scope = QScriptDeclarativeClass::newStaticScopeObject(&eng);
6025 
6026     // Initially empty.
6027     QVERIFY(!QScriptValueIterator(scope).hasNext());
6028     QVERIFY(!scope.property("foo").isValid());
6029 
6030     // Add a static property.
6031     scope.setProperty("foo", 123);
6032     QVERIFY(scope.property("foo").equals(123));
6033     QCOMPARE(scope.propertyFlags("foo"), QScriptValue::Undeletable);
6034 
6035     // Modify existing property.
6036     scope.setProperty("foo", 456);
6037     QVERIFY(scope.property("foo").equals(456));
6038 
6039     // Add a read-only property.
6040     scope.setProperty("bar", "ciao", QScriptValue::ReadOnly);
6041     QVERIFY(scope.property("bar").equals("ciao"));
6042     QCOMPARE(scope.propertyFlags("bar"), QScriptValue::ReadOnly | QScriptValue::Undeletable);
6043 
6044     // Attempt to modify read-only property.
6045     scope.setProperty("bar", "hello");
6046     QVERIFY(scope.property("bar").equals("ciao"));
6047 
6048     // Properties can't be deleted.
6049     scope.setProperty("foo", QScriptValue());
6050     QVERIFY(scope.property("foo").equals(456));
6051     scope.setProperty("bar", QScriptValue());
6052     QVERIFY(scope.property("bar").equals("ciao"));
6053 
6054     // Iterate.
6055     {
6056         QScriptValueIterator it(scope);
6057         QSet<QString> iteratedNames;
6058         while (it.hasNext()) {
6059             it.next();
6060             iteratedNames.insert(it.name());
6061         }
6062         QCOMPARE(iteratedNames.size(), 2);
6063         QVERIFY(iteratedNames.contains("foo"));
6064         QVERIFY(iteratedNames.contains("bar"));
6065     }
6066 
6067     // Push it on the scope chain of a new context.
6068     QScriptContext *ctx = eng.pushContext();
6069     ctx->pushScope(scope);
6070     QCOMPARE(ctx->scopeChain().size(), 3); // Global Object, native activation, custom scope
6071     QVERIFY(ctx->activationObject().equals(scope));
6072 
6073     // Read property from JS.
6074     QVERIFY(eng.evaluate("foo").equals(scope.property("foo")));
6075     QVERIFY(eng.evaluate("bar").equals(scope.property("bar")));
6076 
6077     // Write property from JS.
6078     {
6079         QScriptValue oldValue = eng.evaluate("foo");
6080         QVERIFY(oldValue.isNumber());
6081         QScriptValue newValue = oldValue.toNumber() * 2;
6082         QVERIFY(eng.evaluate("foo = foo * 2; foo").equals(newValue));
6083         scope.setProperty("foo", oldValue);
6084         QVERIFY(eng.evaluate("foo").equals(oldValue));
6085     }
6086 
6087     // Write to read-only property.
6088     QVERIFY(eng.evaluate("bar = 456; bar").equals("ciao"));
6089 
6090     // Shadow property.
6091     QVERIFY(eng.evaluate("Math").equals(eng.globalObject().property("Math")));
6092     scope.setProperty("Math", "fake Math");
6093     QVERIFY(eng.evaluate("Math").equals(scope.property("Math")));
6094 
6095     // Variable declarations will create properties on the scope.
6096     eng.evaluate("var baz = 456");
6097     QVERIFY(scope.property("baz").equals(456));
6098 
6099     // Function declarations will create properties on the scope.
6100     eng.evaluate("function fun() { return baz; }");
6101     QVERIFY(scope.property("fun").isFunction());
6102     QVERIFY(scope.property("fun").call().equals(scope.property("baz")));
6103 
6104     // Demonstrate the limitation of a growable static scope: Once a function that
6105     // uses the scope has been compiled, it won't pick up properties that are added
6106     // to the scope later.
6107     {
6108         QScriptValue fun = eng.evaluate("(function() { return futureProperty; })");
6109         QVERIFY(fun.isFunction());
6110         QVERIFY(fun.call().toString().contains(QString::fromLatin1("ReferenceError")));
6111         scope.setProperty("futureProperty", "added after the function was compiled");
6112         // If scope were dynamic, this would return the new property.
6113         QVERIFY(fun.call().toString().contains(QString::fromLatin1("ReferenceError")));
6114     }
6115 
6116     eng.popContext();
6117 }
6118 
6119 QT_BEGIN_NAMESPACE
Q_SCRIPT_DECLARE_QMETAOBJECT(QStandardItemModel,QObject *)6120 Q_SCRIPT_DECLARE_QMETAOBJECT(QStandardItemModel, QObject*)
6121 QT_END_NAMESPACE
6122 
6123 void tst_QScriptEngine::scriptValueFromQMetaObject()
6124 {
6125     QScriptEngine eng;
6126     {
6127         QScriptValue meta = eng.scriptValueFromQMetaObject<QScriptEngine>();
6128         QVERIFY(meta.isQMetaObject());
6129         QCOMPARE(meta.toQMetaObject(), &QScriptEngine::staticMetaObject);
6130         // Because of missing Q_SCRIPT_DECLARE_QMETAOBJECT() for QScriptEngine.
6131         QVERIFY(!meta.construct().isValid());
6132     }
6133     {
6134         QScriptValue meta = eng.scriptValueFromQMetaObject<QStandardItemModel>();
6135         QVERIFY(meta.isQMetaObject());
6136         QCOMPARE(meta.toQMetaObject(), &QStandardItemModel::staticMetaObject);
6137         QScriptValue obj = meta.construct(QScriptValueList() << eng.newQObject(&eng));
6138         QVERIFY(obj.isQObject());
6139         QStandardItemModel *model = qobject_cast<QStandardItemModel*>(obj.toQObject());
6140         QVERIFY(model != 0);
6141         QCOMPARE(model->parent(), (QObject*)&eng);
6142     }
6143 }
6144 
6145 // QTBUG-21896
stringListFromArrayWithEmptyElement()6146 void tst_QScriptEngine::stringListFromArrayWithEmptyElement()
6147 {
6148     QScriptEngine eng;
6149     QCOMPARE(qscriptvalue_cast<QStringList>(eng.evaluate("[,'hello']")),
6150              QStringList() << "" << "hello");
6151 }
6152 
6153 QTEST_MAIN(tst_QScriptEngine)
6154 #include "tst_qscriptengine.moc"
6155